agent-ecosystem/src/shared/utils/idleNotificationSemantics.ts

86 lines
2.7 KiB
TypeScript

import { isInboxNoiseMessage, parseInboxJson } from './inboxNoise';
export interface IdleNotificationPayload {
type: 'idle_notification';
from?: string;
timestamp?: string;
idleReason?: 'available' | 'interrupted' | 'failed';
summary?: string;
completedTaskId?: string;
completedStatus?: 'resolved' | 'blocked' | 'failed';
failureReason?: string;
}
export type IdleNotificationPrimaryKind = 'heartbeat' | 'interrupted' | 'task_terminal' | 'failure';
export interface ClassifiedIdleNotification {
payload: IdleNotificationPayload;
primaryKind: IdleNotificationPrimaryKind;
hasPeerSummary: boolean;
peerSummary: string | null;
countsAsBootstrapConfirmation: boolean;
}
function getTrimmedOptionalString(value: unknown): string | null {
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}
function isIdleNotificationPayload(value: unknown): value is IdleNotificationPayload {
return (
!!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
(value as { type?: unknown }).type === 'idle_notification'
);
}
export function classifyIdleNotificationText(text: string): ClassifiedIdleNotification | null {
const parsed = parseInboxJson(text);
if (!isIdleNotificationPayload(parsed)) return null;
const peerSummary = getTrimmedOptionalString(parsed.summary);
const hasPeerSummary = peerSummary !== null;
const failureReason = getTrimmedOptionalString(parsed.failureReason);
const completedTaskId = getTrimmedOptionalString(parsed.completedTaskId);
let primaryKind: IdleNotificationPrimaryKind;
if (
parsed.idleReason === 'failed' ||
failureReason !== null ||
parsed.completedStatus === 'failed'
) {
primaryKind = 'failure';
} else if (
completedTaskId !== null ||
parsed.completedStatus === 'resolved' ||
parsed.completedStatus === 'blocked'
) {
primaryKind = 'task_terminal';
} else if (parsed.idleReason === 'interrupted') {
primaryKind = 'interrupted';
} else {
primaryKind = 'heartbeat';
}
return {
payload: parsed,
primaryKind,
hasPeerSummary,
peerSummary,
countsAsBootstrapConfirmation: primaryKind !== 'failure',
};
}
export function shouldSuppressDesktopNotificationForInboxText(text: string): boolean {
return classifyIdleNotificationText(text) !== null || isInboxNoiseMessage(text);
}
export function shouldExcludeInboxTextFromReplyCandidates(text: string): boolean {
return classifyIdleNotificationText(text) !== null || isInboxNoiseMessage(text);
}
export function getIdleGraphLabel(text: string): string | null {
const classified = classifyIdleNotificationText(text);
if (!classified) return null;
return classified.hasPeerSummary && classified.peerSummary ? classified.peerSummary : 'idle';
}