diff --git a/extensions/codex/src/app-server/attempt-notification-state.ts b/extensions/codex/src/app-server/attempt-notification-state.ts index de498223cab0..2bc9f1648d8c 100644 --- a/extensions/codex/src/app-server/attempt-notification-state.ts +++ b/extensions/codex/src/app-server/attempt-notification-state.ts @@ -1,3 +1,6 @@ +/** + * State machine for Codex app-server turn notifications and idle-watch updates. + */ import { codexExecutionToolName, describeNotificationActivity, @@ -28,6 +31,7 @@ type CodexExecutionPhase = | { phase: "assistant_output_started" } | { phase: "tool_execution_started"; itemId?: string; tool: string }; +/** Emits coarse execution phases exactly once from app-server notifications. */ export function reportCodexExecutionNotification(params: { notification: CodexServerNotification; emitExecutionPhaseOnce: (key: string, info: CodexExecutionPhase) => void; @@ -58,6 +62,7 @@ export function reportCodexExecutionNotification(params: { }); } +/** Returns true when a notification ends the current app-server turn. */ export function isTerminalCodexTurnNotificationForTurn(params: { notification: CodexServerNotification; threadId: string; @@ -75,6 +80,10 @@ export function isTerminalCodexTurnNotificationForTurn(params: { ); } +/** + * Applies one notification to active item tracking, idle watches, and terminal + * turn state. + */ export function applyCodexTurnNotificationState(params: { notification: CodexServerNotification; threadId: string; diff --git a/extensions/codex/src/app-server/attempt-notifications.ts b/extensions/codex/src/app-server/attempt-notifications.ts index fdf9fcd7e594..558662216e7c 100644 --- a/extensions/codex/src/app-server/attempt-notifications.ts +++ b/extensions/codex/src/app-server/attempt-notifications.ts @@ -1,3 +1,6 @@ +/** + * Predicates and readers for Codex app-server notification envelopes. + */ import { asBoolean } from "openclaw/plugin-sdk/string-coerce-runtime"; import { describeCodexNotificationCorrelation, @@ -18,6 +21,7 @@ const CODEX_INTERRUPTED_USER_GUIDANCE = const CODEX_INTERRUPTED_DEVELOPER_GUIDANCE = "The previous turn was interrupted on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed."; +/** Builds compact activity metadata for watchdog and diagnostic updates. */ export function describeNotificationActivity( notification: CodexServerNotification, ): Record | undefined { @@ -40,6 +44,7 @@ export function describeNotificationActivity( }; } +/** Tracks active app-server item ids from item start/completion notifications. */ export function updateActiveTurnItemIds( notification: CodexServerNotification, activeItemIds: Set, @@ -73,6 +78,7 @@ function isCompletedAssistantNotification(notification: CodexServerNotification) ); } +/** Returns true for completed app-server reasoning items. */ export function isReasoningItemCompletionNotification( notification: CodexServerNotification, ): boolean { @@ -83,6 +89,7 @@ export function isReasoningItemCompletionNotification( return item ? readString(item, "type") === "reasoning" : false; } +/** Returns true for completed assistant commentary items. */ export function isAssistantCommentaryCompletionNotification( notification: CodexServerNotification, ): boolean { @@ -97,6 +104,7 @@ export function isAssistantCommentaryCompletionNotification( ); } +/** Returns true for completed raw response reasoning items. */ export function isRawReasoningCompletionNotification( notification: CodexServerNotification, ): boolean { @@ -107,6 +115,7 @@ export function isRawReasoningCompletionNotification( return item ? readString(item, "type") === "reasoning" : false; } +/** Returns true when assistant completion can release the short idle watch. */ export function isAssistantCompletionReleaseNotification( notification: CodexServerNotification, turnCrossedToolHandoff: boolean, @@ -117,6 +126,7 @@ export function isAssistantCompletionReleaseNotification( return !turnCrossedToolHandoff && isRawAssistantCompletionNotification(notification); } +/** Returns true when a notification proves assistant output is still active. */ export function shouldDisarmAssistantCompletionIdleWatch( notification: CodexServerNotification, ): boolean { @@ -132,6 +142,7 @@ export function shouldDisarmAssistantCompletionIdleWatch( return false; } +/** Reads an item id from supported notification envelope shapes. */ export function readNotificationItemId(notification: CodexServerNotification): string | undefined { if (!isJsonObject(notification.params)) { return undefined; @@ -144,6 +155,7 @@ export function readNotificationItemId(notification: CodexServerNotification): s ); } +/** Detects completion for an OpenClaw dynamic tool result still awaited by Codex. */ export function isPendingOpenClawDynamicToolCompletionNotification( notification: CodexServerNotification, pendingOpenClawDynamicToolCompletionIds: ReadonlySet, @@ -160,6 +172,7 @@ export function isPendingOpenClawDynamicToolCompletionNotification( return itemType === undefined || itemType === "dynamicToolCall"; } +/** Returns true for raw response tool-output completion notifications. */ export function isRawToolOutputCompletionNotification( notification: CodexServerNotification, ): boolean { @@ -170,6 +183,7 @@ export function isRawToolOutputCompletionNotification( return item ? readString(item, "type") === "custom_tool_call_output" : false; } +/** Returns true for progress on Codex-native tool item types. */ export function isNativeToolProgressNotification(notification: CodexServerNotification): boolean { if ( notification.method !== "item/started" && @@ -193,12 +207,14 @@ export function isNativeToolProgressNotification(notification: CodexServerNotifi } } +/** Returns true for raw native response stream delta events. */ export function isNativeResponseStreamDeltaNotification( notification: CodexServerNotification, ): boolean { return notification.method.startsWith("response.") && notification.method.endsWith(".delta"); } +/** Returns true for file-change patch update notifications. */ export function isFileChangePatchUpdatedNotification( notification: CodexServerNotification, ): boolean { @@ -207,6 +223,7 @@ export function isFileChangePatchUpdatedNotification( ); } +/** Returns true for raw assistant message progress with readable text. */ export function isRawAssistantProgressNotification(notification: CodexServerNotification): boolean { if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) { return false; @@ -220,6 +237,7 @@ export function isRawAssistantProgressNotification(notification: CodexServerNoti ); } +/** Returns true for raw assistant completion outside commentary phase. */ export function isRawAssistantCompletionNotification( notification: CodexServerNotification, ): boolean { @@ -250,6 +268,7 @@ function readRawAssistantTextPreview(item: JsonObject): string | undefined { return text.length > 240 ? `${text.slice(0, 237)}...` : text; } +/** Returns true when notification params correlate to a specific thread/turn. */ export function isTurnNotification( value: JsonValue | undefined, threadId: string, @@ -258,6 +277,7 @@ export function isTurnNotification( return isCodexNotificationForTurn(value, threadId, turnId); } +/** Returns true when a correlated notification belongs to another active run. */ export function isCodexNotificationOutsideActiveRun( correlation: ReturnType, ): boolean { @@ -272,6 +292,7 @@ export function isCodexNotificationOutsideActiveRun( return hasTurnScope && correlation.matchesActiveTurn === false; } +/** Checks request params that must contain the current thread and turn ids. */ export function isCurrentThreadTurnRequestParams( value: JsonValue | undefined, threadId: string, @@ -283,6 +304,7 @@ export function isCurrentThreadTurnRequestParams( return readString(value, "threadId") === threadId && readString(value, "turnId") === turnId; } +/** Checks approval request params, accepting `conversationId` as thread id. */ export function isCurrentApprovalTurnRequestParams( value: JsonValue | undefined, threadId: string, @@ -295,6 +317,7 @@ export function isCurrentApprovalTurnRequestParams( return requestThreadId === threadId && readString(value, "turnId") === turnId; } +/** Checks request params where `turnId` may be omitted or null for the thread. */ export function isCurrentThreadOptionalTurnRequestParams( value: JsonValue | undefined, threadId: string, @@ -307,6 +330,7 @@ export function isCurrentThreadOptionalTurnRequestParams( return requestTurnId === null || requestTurnId === undefined || requestTurnId === turnId; } +/** Returns true for app-server error notifications that will retry. */ export function isRetryableErrorNotification(value: JsonValue | undefined): boolean { if (!isJsonObject(value)) { return false; @@ -314,10 +338,15 @@ export function isRetryableErrorNotification(value: JsonValue | undefined): bool return readBoolean(value, "willRetry") === true || readBoolean(value, "will_retry") === true; } +/** Returns true for terminal app-server thread status strings. */ export function isTerminalTurnStatus(status: string | undefined): boolean { return status === "completed" || status === "interrupted" || status === "failed"; } +/** + * Detects Codex's synthetic interrupted-turn marker while ignoring the current + * user prompt echoed through raw response events. + */ export function isCodexTurnAbortMarkerNotification( notification: CodexServerNotification, options: { currentPromptText?: string; currentPromptTexts?: readonly string[] } = {}, @@ -385,6 +414,7 @@ function readBoolean(record: JsonObject, key: string): boolean | undefined { return asBoolean(record[key]); } +/** Reads a typed Codex item from notification params when id/type are present. */ export function readCodexNotificationItem( params: JsonValue | undefined, ): CodexThreadItem | undefined { @@ -397,6 +427,7 @@ export function readCodexNotificationItem( : undefined; } +/** Maps Codex item types to the tool name shown in execution progress. */ export function codexExecutionToolName(item: CodexThreadItem): string | undefined { if (item.type === "dynamicToolCall" && typeof item.tool === "string") { return item.tool;