158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
/**
|
|
* Inbox "noise" messages are structured JSON objects that represent internal coordination
|
|
* signals (idle/shutdown/etc.). They should not trigger user-facing notifications or
|
|
* automatic lead relays.
|
|
*/
|
|
export const INBOX_NOISE_TYPES = [
|
|
'idle_notification',
|
|
'shutdown_approved',
|
|
'teammate_terminated',
|
|
'shutdown_request',
|
|
] as const;
|
|
|
|
const INBOX_NOISE_SET = new Set<string>(INBOX_NOISE_TYPES);
|
|
|
|
export function parseInboxJson(text: string): Record<string, unknown> | null {
|
|
const trimmed = text.trim();
|
|
if (!trimmed.startsWith('{')) return null;
|
|
try {
|
|
const parsed = JSON.parse(trimmed) as unknown;
|
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
return parsed as Record<string, unknown>;
|
|
}
|
|
} catch {
|
|
// not JSON
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function getInboxJsonType(text: string): string | null {
|
|
const parsed = parseInboxJson(text);
|
|
if (!parsed) return null;
|
|
return typeof parsed.type === 'string' ? parsed.type : null;
|
|
}
|
|
|
|
export function isInboxNoiseMessage(text: string): boolean {
|
|
const type = getInboxJsonType(text);
|
|
return !!type && INBOX_NOISE_SET.has(type);
|
|
}
|
|
|
|
/**
|
|
* Returns true when a teammate message is substantive enough to count as a real
|
|
* bootstrap/check-in signal. Internal coordination noise like idle/shutdown
|
|
* JSON should keep the runtime marked alive, but must not be treated as a
|
|
* teammate "checked in" signal in launch UX.
|
|
*/
|
|
export function isMeaningfulBootstrapCheckInMessage(text: string): boolean {
|
|
return text.trim().length > 0 && !isInboxNoiseMessage(text);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Teammate permission request parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** A single permission suggestion from the teammate runtime. */
|
|
export interface PermissionSuggestion {
|
|
type: string;
|
|
rules?: { toolName: string }[];
|
|
behavior?: string;
|
|
destination?: string;
|
|
/** Permission mode name (for type: "setMode"). FACT: observed values: "acceptEdits", "bypassPermissions" */
|
|
mode?: string;
|
|
}
|
|
|
|
/** Parsed teammate permission request from inbox message. */
|
|
export interface ParsedPermissionRequest {
|
|
requestId: string;
|
|
agentId: string;
|
|
toolName: string;
|
|
toolUseId: string;
|
|
description: string;
|
|
input: Record<string, unknown>;
|
|
/** Suggestions from teammate runtime on how to resolve the permission.
|
|
* FACT: This field is populated by Claude Code runtime, not by the AI agent.
|
|
* FACT: Observed format: { type: "addRules", rules: [{toolName}], behavior: "allow", destination: "localSettings" }
|
|
*/
|
|
permissionSuggestions: PermissionSuggestion[];
|
|
}
|
|
|
|
/**
|
|
* Parses a `permission_request` JSON message from a teammate's inbox entry.
|
|
* Returns null if the text is not a valid permission_request.
|
|
*/
|
|
export function parsePermissionRequest(text: string): ParsedPermissionRequest | null {
|
|
const parsed = parseInboxJson(text);
|
|
if (parsed?.type !== 'permission_request') return null;
|
|
|
|
const requestId = typeof parsed.request_id === 'string' ? parsed.request_id : null;
|
|
const agentId = typeof parsed.agent_id === 'string' ? parsed.agent_id : null;
|
|
const toolName = typeof parsed.tool_name === 'string' ? parsed.tool_name : null;
|
|
|
|
if (!requestId || !agentId || !toolName) return null;
|
|
|
|
return {
|
|
requestId,
|
|
agentId,
|
|
toolName,
|
|
toolUseId: typeof parsed.tool_use_id === 'string' ? parsed.tool_use_id : '',
|
|
description: typeof parsed.description === 'string' ? parsed.description : '',
|
|
input:
|
|
parsed.input && typeof parsed.input === 'object' && !Array.isArray(parsed.input)
|
|
? (parsed.input as Record<string, unknown>)
|
|
: {},
|
|
permissionSuggestions: Array.isArray(parsed.permission_suggestions)
|
|
? (parsed.permission_suggestions as PermissionSuggestion[])
|
|
: [],
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Teammate-message XML block detection & stripping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const TEAMMATE_MESSAGE_BLOCK_RE = /<teammate-message\s[^>]*>[\s\S]*?<\/teammate-message>/g;
|
|
|
|
/**
|
|
* Removes `<teammate-message>` XML blocks from text.
|
|
* Used to clean protocol artifacts that leak into lead thoughts.
|
|
*/
|
|
export function stripTeammateMessageBlocks(text: string): string {
|
|
return text
|
|
.replace(TEAMMATE_MESSAGE_BLOCK_RE, '\n')
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the entire text consists only of `<teammate-message>` blocks
|
|
* (possibly with whitespace between them) and no meaningful user-visible content.
|
|
*/
|
|
export function isOnlyTeammateMessageBlocks(text: string): boolean {
|
|
const stripped = stripTeammateMessageBlocks(text);
|
|
return stripped.length === 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Combined protocol noise check for lead thoughts
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Detects `<teammate-message>` opening tags (even without closing tag).
|
|
* Claude's lead model sometimes echoes raw teammate message XML in assistant
|
|
* text output — these are always protocol artifacts, never real user content.
|
|
*/
|
|
const TEAMMATE_MESSAGE_OPEN_RE = /^\s*<teammate-message\s/;
|
|
|
|
/**
|
|
* Returns true if a lead thought text is entirely protocol noise and should
|
|
* be hidden from the user. Covers:
|
|
* 1. Structured JSON noise (idle_notification, shutdown_*, etc.)
|
|
* 2. Text that consists solely of `<teammate-message>` XML blocks (closed tags)
|
|
* 3. Text starting with an unclosed `<teammate-message>` tag
|
|
*/
|
|
export function isThoughtProtocolNoise(text: string): boolean {
|
|
if (isInboxNoiseMessage(text)) return true;
|
|
if (isOnlyTeammateMessageBlocks(text)) return true;
|
|
if (TEAMMATE_MESSAGE_OPEN_RE.test(text)) return true;
|
|
return false;
|
|
}
|