import * as plugins from './plugins.js'; import * as interfaces from '../ts_interfaces/index.js'; const controllerEventTypes: ReadonlySet = new Set(interfaces.controllerEventTypes); const controllerRequestTimeoutMs = 35_000; const textEncoder = new TextEncoder(); const isQualifiedRuntimeId = ( valueArg: unknown, allowedHarnessIdsArg: ReadonlySet, ): valueArg is interfaces.IControllerRuntimeId => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; return ( Object.keys(candidate).length === 2 && typeof candidate.harnessId === 'string' && allowedHarnessIdsArg.has(candidate.harnessId as interfaces.TControllerHarnessId) && typeof candidate.nativeId === 'string' && candidate.nativeId.length > 0 && textEncoder.encode(candidate.nativeId).byteLength <= 512 ); }; const sessionHarnessIds: ReadonlySet = new Set(['opencode', 'flex', 'codex']); const terminalHarnessIds: ReadonlySet = new Set(['controller']); /** * The keys an exact-key admission check accepts, tied to the interface they belong to. * * A payload is admitted only when every key it carries is declared, so an undeclared field can * never reach the client as if it were contract. Spelling that list out by hand beside an * interface is exactly how it drifts: `title` was declared on `IControllerToolExecution` and * missing from the list, so every Codex tool event carrying a parsed command title was rejected * whole and its card stopped updating live. Declaring the list as `Record` * makes both directions a compile error — a field added to the interface without a line here, and * a line here without a field — and the set is built once so admission stays a hash lookup. */ const contractKeys = (declarationArg: Record): ReadonlySet => ( new Set(Object.keys(declarationArg)) ); const toolExecutionKeys = contractKeys({ sessionId: true, messageId: true, partId: true, callId: true, toolName: true, status: true, title: true, input: true, output: true, exitCode: true, errorText: true, outputTruncated: true, errorTextTruncated: true, childSessionId: true, model: true, startedAt: true, finishedAt: true, order: true, sourceUpdatedAt: true, revision: true, streamEpoch: true, }); const reasoningUpdateKeys = contractKeys({ sessionId: true, messageId: true, partId: true, text: true, status: true, order: true, sourceUpdatedAt: true, revision: true, streamEpoch: true, }); const textUpdateKeys = contractKeys({ sessionId: true, messageId: true, partId: true, text: true, status: true, order: true, sourceUpdatedAt: true, revision: true, streamEpoch: true, }); // The two delta shapes are one wire shape; the intersection makes either one growing a field fail. type TControllerMessageDelta = interfaces.IControllerReasoningDelta & interfaces.IControllerTextDelta; const messageDeltaKeys = contractKeys({ sessionId: true, messageId: true, partId: true, delta: true, baseTextUtf8Bytes: true, textUtf8Bytes: true, order: true, sourceUpdatedAt: true, revision: true, streamEpoch: true, }); /** `order` is the one optional key, so omitting it here leaves exactly the required ones. */ const messageDeltaRequiredKeys: readonly string[] = [ ...contractKeys>({ sessionId: true, messageId: true, partId: true, delta: true, baseTextUtf8Bytes: true, textUtf8Bytes: true, sourceUpdatedAt: true, revision: true, streamEpoch: true, }), ]; const draftAttachmentKeys = contractKeys({ id: true, name: true, mediaType: true, size: true, kind: true, dataBase64: true, }); const sessionDraftUpdateKeys = contractKeys({ revision: true, text: true, attachments: true, }); const codexCollaborationModeKeys = contractKeys({ mode: true, model: true, effort: true, }); const codexActivitySummaryKeys = contractKeys({ writer: true, turnId: true, canInterrupt: true, collaborationModeAuthority: true, }); const childEventKeys = contractKeys({ projectId: true, parentSessionId: true, childSessionId: true, scopeGeneration: true, mode: true, expiresAt: true, sequence: true, timestamp: true, kind: true, }); /** Bound shared by a tool's name and its one-line title, matching the producers' own title cap. */ const maxToolTextBytes = 2048; const isOptionalFiniteNumber = (valueArg: unknown): boolean => ( valueArg === undefined || (typeof valueArg === 'number' && Number.isFinite(valueArg)) ); const isTranscriptOrder = (valueArg: unknown): boolean => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; return Object.keys(candidate).length === 2 && Number.isSafeInteger(candidate.messageIndex) && (candidate.messageIndex ?? -1) >= 0 && Number.isSafeInteger(candidate.partIndex) && (candidate.partIndex ?? -1) >= 0; }; const serializedBytesWithin = (valueArg: unknown, byteLimitArg: number): boolean => { try { return textEncoder.encode(JSON.stringify(valueArg)).byteLength <= byteLimitArg; } catch { return false; } }; const hasExactDeltaUtf8Coordinates = ( deltaArg: string, baseTextUtf8BytesArg: number, textUtf8BytesArg: number, ): boolean => { const encodedDeltaBytes = textEncoder.encode(deltaArg).byteLength; const increment = textUtf8BytesArg - baseTextUtf8BytesArg; if (increment === encodedDeltaBytes) return true; const firstCodeUnit = deltaArg.charCodeAt(0); return firstCodeUnit >= 0xDC00 && firstCodeUnit <= 0xDFFF && increment === encodedDeltaBytes - 2; }; const isDraftAttachment = (valueArg: unknown): valueArg is interfaces.IControllerDraftAttachment => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; if ( !Object.keys(candidate).every((key) => draftAttachmentKeys.has(key)) || typeof candidate.id !== 'string' || candidate.id.length === 0 || textEncoder.encode(candidate.id).byteLength > 512 || typeof candidate.name !== 'string' || candidate.name.length === 0 || textEncoder.encode(candidate.name).byteLength > 255 || typeof candidate.mediaType !== 'string' || candidate.mediaType.length === 0 || textEncoder.encode(candidate.mediaType).byteLength > 129 || !Number.isSafeInteger(candidate.size) || (candidate.size ?? -1) < 0 || (candidate.size ?? 0) > interfaces.controllerMaxDraftAttachmentBytes || !['image', 'text', 'binary'].includes(candidate.kind ?? '') || typeof candidate.dataBase64 !== 'string' ) return false; try { const decoded = globalThis.atob(candidate.dataBase64); return decoded.length === candidate.size && globalThis.btoa(decoded) === candidate.dataBase64; } catch { return false; } }; const isSessionDraftUpdate = ( valueArg: unknown, ): valueArg is interfaces.IControllerSessionDraftUpdate => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; if ( !Object.keys(candidate).every((key) => sessionDraftUpdateKeys.has(key)) || !Number.isSafeInteger(candidate.revision) || (candidate.revision ?? -1) < 0 || (candidate.text === undefined && candidate.attachments === undefined) || (candidate.text !== undefined && ( typeof candidate.text !== 'string' || textEncoder.encode(candidate.text).byteLength > interfaces.controllerMaxDraftTextBytes )) || (candidate.attachments !== undefined && ( !Array.isArray(candidate.attachments) || candidate.attachments.length > interfaces.controllerMaxDraftAttachments || !candidate.attachments.every(isDraftAttachment) || candidate.attachments.reduce((sum, attachment) => sum + attachment.size, 0) > interfaces.controllerMaxDraftAttachmentTotalBytes )) ) return false; return true; }; /** Exported for the admission tests; the client itself only reaches it through `isControllerEvent`. */ export const isControllerToolExecution = ( valueArg: unknown, ): valueArg is interfaces.IControllerToolExecution => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false; const executionHarnessIds: ReadonlySet = new Set([ candidate.sessionId.harnessId, ]); return ( Object.keys(candidate).every((key) => toolExecutionKeys.has(key)) && isQualifiedRuntimeId(candidate.messageId, executionHarnessIds) && isQualifiedRuntimeId(candidate.partId, executionHarnessIds) && isQualifiedRuntimeId(candidate.callId, executionHarnessIds) && typeof candidate.toolName === 'string' && candidate.toolName.length > 0 && textEncoder.encode(candidate.toolName).byteLength <= maxToolTextBytes && ['pending', 'running', 'completed', 'error', 'stopped'].includes(candidate.status ?? '') && (candidate.title === undefined || (typeof candidate.title === 'string' && textEncoder.encode(candidate.title).byteLength <= maxToolTextBytes)) // Any safe integer, including a negative one: Codex reports -1 for a process that ended // without an exit status of its own (`exit_status.code().unwrap_or(-1)` in its `core/src/exec.rs`), // and that code is the only outcome a killed command has to show. && (candidate.exitCode === undefined || Number.isSafeInteger(candidate.exitCode)) && (candidate.errorText === undefined || typeof candidate.errorText === 'string') // A truncation flag only ever describes a payload that is present and textual. && (candidate.outputTruncated === undefined || (candidate.outputTruncated === true && typeof candidate.output === 'string')) && (candidate.errorTextTruncated === undefined || (candidate.errorTextTruncated === true && typeof candidate.errorText === 'string')) && (candidate.childSessionId === undefined || isQualifiedRuntimeId(candidate.childSessionId, executionHarnessIds)) && (candidate.model === undefined || typeof candidate.model === 'string') && isOptionalFiniteNumber(candidate.startedAt) && isOptionalFiniteNumber(candidate.finishedAt) && (candidate.order === undefined || isTranscriptOrder(candidate.order)) && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined) && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined) && typeof candidate.sourceUpdatedAt === 'number' && Number.isFinite(candidate.sourceUpdatedAt) && candidate.sourceUpdatedAt >= 0 && Number.isSafeInteger(candidate.revision) && (candidate.revision ?? -1) >= 0 && Number.isSafeInteger(candidate.streamEpoch) && (candidate.streamEpoch ?? -1) >= 0 ); }; const isControllerReasoningUpdate = ( valueArg: unknown, ): valueArg is interfaces.IControllerReasoningUpdate => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false; const harnessIds: ReadonlySet = new Set([ candidate.sessionId.harnessId, ]); return Object.keys(candidate).every((key) => reasoningUpdateKeys.has(key)) && isQualifiedRuntimeId(candidate.messageId, harnessIds) && isQualifiedRuntimeId(candidate.partId, harnessIds) && typeof candidate.text === 'string' && textEncoder.encode(candidate.text).byteLength <= interfaces.controllerMaxLiveMessageEventBytes && ['running', 'completed', 'cancelled'].includes(candidate.status ?? '') && (candidate.order === undefined || isTranscriptOrder(candidate.order)) && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined) && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined) && typeof candidate.sourceUpdatedAt === 'number' && Number.isFinite(candidate.sourceUpdatedAt) && Number.isSafeInteger(candidate.revision) && (candidate.revision ?? -1) >= 0 && Number.isSafeInteger(candidate.streamEpoch) && (candidate.streamEpoch ?? -1) >= 0; }; const isControllerTextUpdate = ( valueArg: unknown, ): valueArg is interfaces.IControllerTextUpdate => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false; const harnessIds: ReadonlySet = new Set([ candidate.sessionId.harnessId, ]); return Object.keys(candidate).every((key) => textUpdateKeys.has(key)) && isQualifiedRuntimeId(candidate.messageId, harnessIds) && isQualifiedRuntimeId(candidate.partId, harnessIds) && typeof candidate.text === 'string' && textEncoder.encode(candidate.text).byteLength <= interfaces.controllerMaxLiveMessageEventBytes && ['running', 'completed'].includes(candidate.status ?? '') && (candidate.order === undefined || isTranscriptOrder(candidate.order)) && (candidate.sessionId.harnessId !== 'flex' || candidate.order !== undefined) && (candidate.sessionId.harnessId !== 'opencode' || candidate.order === undefined) && typeof candidate.sourceUpdatedAt === 'number' && Number.isFinite(candidate.sourceUpdatedAt) && Number.isSafeInteger(candidate.revision) && (candidate.revision ?? -1) >= 0 && Number.isSafeInteger(candidate.streamEpoch) && (candidate.streamEpoch ?? -1) >= 0; }; const isControllerMessageDelta = ( valueArg: unknown, ): valueArg is interfaces.IControllerReasoningDelta | interfaces.IControllerTextDelta => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; if (!isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) return false; const harnessIds: ReadonlySet = new Set([ candidate.sessionId.harnessId, ]); const keys = Object.keys(candidate); const hasOrder = Object.prototype.hasOwnProperty.call(candidate, 'order'); return keys.length === messageDeltaRequiredKeys.length + (hasOrder ? 1 : 0) && keys.every((key) => messageDeltaKeys.has(key)) && messageDeltaRequiredKeys.every((key) => ( Object.prototype.hasOwnProperty.call(candidate, key) )) && isQualifiedRuntimeId(candidate.messageId, harnessIds) && isQualifiedRuntimeId(candidate.partId, harnessIds) && typeof candidate.delta === 'string' && candidate.delta.length > 0 && textEncoder.encode(candidate.delta).byteLength <= interfaces.controllerMaxLiveMessageEventBytes && Number.isSafeInteger(candidate.baseTextUtf8Bytes) && (candidate.baseTextUtf8Bytes ?? -1) >= 0 && Number.isSafeInteger(candidate.textUtf8Bytes) && (candidate.textUtf8Bytes ?? -1) > (candidate.baseTextUtf8Bytes ?? -1) && hasExactDeltaUtf8Coordinates( candidate.delta, candidate.baseTextUtf8Bytes!, candidate.textUtf8Bytes!, ) && (!hasOrder || isTranscriptOrder(candidate.order)) && (candidate.sessionId.harnessId !== 'flex' || hasOrder) && (candidate.sessionId.harnessId !== 'opencode' || !hasOrder) && typeof candidate.sourceUpdatedAt === 'number' && Number.isFinite(candidate.sourceUpdatedAt) && Number.isSafeInteger(candidate.revision) && (candidate.revision ?? -1) >= 0 && Number.isSafeInteger(candidate.streamEpoch) && (candidate.streamEpoch ?? -1) >= 0; }; export interface IControllerSocketClientOptions { onStatus: (status: plugins.typedsocket.TConnectionStatus) => void; onControllerEvent: (event: interfaces.IControllerEvent) => void | Promise; onChildEvent?: (event: interfaces.IControllerChildEvent) => void | Promise; /** Streamed PTY output for a terminal this client may be viewing. */ onTerminalOutput?: ( output: interfaces.IReq_ControllerTerminalOutput['request'], ) => void | Promise; /** * The controller stopped delivering output for this terminal. Best effort — the browser must * also recover from an output gap and from an explicit re-selection. */ onTerminalDetached?: ( detached: interfaces.IReq_ControllerTerminalDetached['request'], ) => void | Promise; /** Fires when the transport fail-closes the connection (e.g. frame limits). */ onTransportClose?: (closeCode: number, reason: string) => void; onReconnectExhausted?: (attempts: number, endpoint: string) => void; } export interface IControllerSocketRequestOptions extends plugins.typedrequest.ITypedRequestFireOptions { abortSignal?: AbortSignal; } const isCodexCollaborationMode = ( valueArg: unknown, ): valueArg is interfaces.IControllerCodexCollaborationMode => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; const keys = Object.keys(candidate); return keys.length >= 2 && keys.length <= codexCollaborationModeKeys.size && keys.every((keyArg) => codexCollaborationModeKeys.has(keyArg)) && (candidate.mode === 'default' || candidate.mode === 'plan') && typeof candidate.model === 'string' && candidate.model.length <= 512 && (candidate.effort === undefined || (typeof candidate.effort === 'string' && candidate.effort.length <= 128)); }; const isCodexCollaborationModeAuthority = ( valueArg: unknown, ): valueArg is interfaces.TControllerCodexCollaborationModeAuthority => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; return Object.keys(candidate).length === 2 && Object.keys(candidate).every((keyArg) => ['status', 'requested'].includes(keyArg)) && (candidate.status === 'pending' || candidate.status === 'unconfirmed') && isCodexCollaborationMode(candidate.requested); }; const isCodexActivitySummary = ( valueArg: unknown, ): valueArg is interfaces.IControllerCodexActivitySummary => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; const keys = Object.keys(candidate); return keys.length >= 2 && keys.length <= codexActivitySummaryKeys.size && keys.every((keyArg) => codexActivitySummaryKeys.has(keyArg)) && ['released', 'agl', 'external', 'idle'].includes(candidate.writer ?? '') && typeof candidate.canInterrupt === 'boolean' && (candidate.turnId === undefined || (typeof candidate.turnId === 'string' && candidate.turnId.length > 0)) && ( candidate.collaborationModeAuthority === undefined || isCodexCollaborationModeAuthority(candidate.collaborationModeAuthority) ); }; export const isControllerEvent = (valueArg: unknown): valueArg is interfaces.IControllerEvent => { if (!valueArg || typeof valueArg !== 'object') { return false; } const candidate = valueArg as Partial; const hasCodexActivity = candidate.codexActivity !== undefined; const hasSessionMarker = candidate.sessionStatus !== undefined || candidate.sessionError !== undefined || hasCodexActivity; const hasToolExecution = candidate.toolExecution !== undefined; const hasReasoningUpdate = candidate.reasoningUpdate !== undefined; const hasReasoningDelta = candidate.reasoningDelta !== undefined; const hasTextUpdate = candidate.textUpdate !== undefined; const hasTextDelta = candidate.textDelta !== undefined; const hasDraftUpdate = candidate.sessionDraftUpdate !== undefined; const hasUpgrade = candidate.upgrade !== undefined; const livePayloadCount = [ hasToolExecution, hasReasoningUpdate, hasReasoningDelta, hasTextUpdate, hasTextDelta, ].filter(Boolean).length; return ( typeof candidate.type === 'string' && controllerEventTypes.has(candidate.type as interfaces.TControllerEventType) && typeof candidate.timestamp === 'number' && Number.isFinite(candidate.timestamp) && (candidate.harnessId === undefined || candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && (candidate.sessionId === undefined || isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds)) && (candidate.projectId === undefined || (typeof candidate.projectId === 'string' && candidate.projectId.length > 0)) && ( candidate.sessionStatus === undefined || ['idle', 'busy', 'retry', 'error'].includes(candidate.sessionStatus) ) && (candidate.sessionError === undefined || candidate.sessionError === true) && (candidate.codexActivity === undefined || isCodexActivitySummary(candidate.codexActivity)) && (candidate.toolStreamEpoch === undefined || ( Number.isSafeInteger(candidate.toolStreamEpoch) && candidate.toolStreamEpoch >= 0 )) && (candidate.messageStreamEpoch === undefined || ( Number.isSafeInteger(candidate.messageStreamEpoch) && candidate.messageStreamEpoch >= 0 )) && (candidate.toolExecution === undefined || isControllerToolExecution(candidate.toolExecution)) && (candidate.reasoningUpdate === undefined || isControllerReasoningUpdate(candidate.reasoningUpdate)) && (candidate.reasoningDelta === undefined || isControllerMessageDelta(candidate.reasoningDelta)) && (candidate.textUpdate === undefined || isControllerTextUpdate(candidate.textUpdate)) && (candidate.textDelta === undefined || isControllerMessageDelta(candidate.textDelta)) && (candidate.sessionDraftUpdate === undefined || isSessionDraftUpdate(candidate.sessionDraftUpdate)) && livePayloadCount <= 1 && (!hasToolExecution || serializedBytesWithin( candidate, interfaces.controllerMaxToolEventBytes, )) && (!(hasReasoningUpdate || hasReasoningDelta || hasTextUpdate || hasTextDelta) || serializedBytesWithin( candidate, interfaces.controllerMaxLiveMessageEventBytes, )) && (!hasSessionMarker || ( candidate.type === 'session.changed' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) )) && (!hasCodexActivity || candidate.sessionId?.harnessId === 'codex') && ( candidate.harnessId === undefined || candidate.sessionId === undefined || candidate.harnessId === candidate.sessionId.harnessId ) && !(candidate.sessionStatus !== undefined && candidate.sessionError === true) && (!hasToolExecution || ( candidate.type === 'session.tool.updated' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) && candidate.harnessId === candidate.sessionId.harnessId && candidate.harnessId === candidate.toolExecution!.sessionId.harnessId && interfaces.controllerRuntimeIdKey(candidate.sessionId) === interfaces.controllerRuntimeIdKey(candidate.toolExecution!.sessionId) && candidate.toolStreamEpoch === undefined )) && (hasToolExecution || candidate.type !== 'session.tool.updated') && (!hasReasoningUpdate || ( candidate.type === 'session.reasoning.updated' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) && candidate.harnessId === candidate.reasoningUpdate!.sessionId.harnessId && interfaces.controllerRuntimeIdKey(candidate.sessionId) === interfaces.controllerRuntimeIdKey(candidate.reasoningUpdate!.sessionId) )) && (hasReasoningUpdate || candidate.type !== 'session.reasoning.updated') && (!hasReasoningDelta || ( candidate.type === 'session.reasoning.delta' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) && candidate.harnessId === candidate.reasoningDelta!.sessionId.harnessId && interfaces.controllerRuntimeIdKey(candidate.sessionId) === interfaces.controllerRuntimeIdKey(candidate.reasoningDelta!.sessionId) )) && (hasReasoningDelta || candidate.type !== 'session.reasoning.delta') && (!hasTextUpdate || ( candidate.type === 'session.text.updated' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) && candidate.harnessId === candidate.textUpdate!.sessionId.harnessId && interfaces.controllerRuntimeIdKey(candidate.sessionId) === interfaces.controllerRuntimeIdKey(candidate.textUpdate!.sessionId) )) && (hasTextUpdate || candidate.type !== 'session.text.updated') && (!hasTextDelta || ( candidate.type === 'session.text.delta' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) && candidate.harnessId === candidate.textDelta!.sessionId.harnessId && interfaces.controllerRuntimeIdKey(candidate.sessionId) === interfaces.controllerRuntimeIdKey(candidate.textDelta!.sessionId) )) && (hasTextDelta || candidate.type !== 'session.text.delta') && (!hasDraftUpdate || ( candidate.type === 'session.draft.changed' && typeof candidate.projectId === 'string' && isQualifiedRuntimeId(candidate.sessionId, sessionHarnessIds) )) && (hasDraftUpdate || candidate.type !== 'session.draft.changed') && (candidate.toolStreamEpoch === undefined || ( candidate.type === 'harness.changed' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && !hasToolExecution )) && (candidate.messageStreamEpoch === undefined || ( candidate.type === 'harness.changed' && (candidate.harnessId === 'opencode' || candidate.harnessId === 'flex' || candidate.harnessId === 'codex') && !hasReasoningUpdate && !hasReasoningDelta && !hasTextUpdate && !hasTextDelta )) && (candidate.upgrade === undefined || ( typeof candidate.upgrade === 'object' && candidate.upgrade !== null && Object.keys(candidate.upgrade).length === 3 && typeof candidate.upgrade.fromVersion === 'string' && candidate.upgrade.fromVersion.length > 0 && textEncoder.encode(candidate.upgrade.fromVersion).byteLength <= 128 && typeof candidate.upgrade.toVersion === 'string' && candidate.upgrade.toVersion.length > 0 && textEncoder.encode(candidate.upgrade.toVersion).byteLength <= 128 && ['preparing', 'pausing', 'installing', 'restarting', 'continuing', 'completed', 'failed'] .includes(candidate.upgrade.phase) )) && (!hasUpgrade || candidate.type === 'upgrade.changed') && (hasUpgrade || candidate.type !== 'upgrade.changed') ); }; export const isControllerChildEvent = ( valueArg: unknown, ): valueArg is interfaces.IControllerChildEvent => { if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) return false; const candidate = valueArg as Partial; const keys = Object.keys(candidate); const openCodeHarnessIds: ReadonlySet = new Set(['opencode']); return keys.length === childEventKeys.size && keys.every((key) => childEventKeys.has(key)) && typeof candidate.projectId === 'string' && candidate.projectId.length > 0 && textEncoder.encode(candidate.projectId).byteLength <= 128 && isQualifiedRuntimeId(candidate.parentSessionId, openCodeHarnessIds) && isQualifiedRuntimeId(candidate.childSessionId, openCodeHarnessIds) && interfaces.controllerRuntimeIdKey(candidate.parentSessionId) !== interfaces.controllerRuntimeIdKey(candidate.childSessionId) && typeof candidate.scopeGeneration === 'string' && /^[A-Za-z0-9_-]{43}$/.test(candidate.scopeGeneration) && (candidate.mode === 'active' || candidate.mode === 'terminal') && typeof candidate.expiresAt === 'number' && Number.isFinite(candidate.expiresAt) && candidate.expiresAt >= 0 && Number.isSafeInteger(candidate.sequence) && (candidate.sequence ?? -1) >= 1 && typeof candidate.timestamp === 'number' && Number.isFinite(candidate.timestamp) && candidate.timestamp >= 0 && [ 'session.changed', 'transcript.changed', 'attention.changed', 'scope.revoked', ].includes(candidate.kind ?? ''); }; /** * Grid a reconstructed screen state may be restored into. The lower bounds are * `DeesTerminalView.restore()`'s own: it rejects anything narrower than two columns with a * `RangeError` that is deliberately not pre-handled, so a grid it would refuse is rejected here * rather than reaching it, and the upper bound is the 16-bit PTY window size field. */ const minTerminalSnapshotCols = 2; const minTerminalSnapshotRows = 1; const maxTerminalSnapshotGridSize = 0xffff; const isTerminalSnapshotGridDimension = (valueArg: unknown, minArg: number): boolean => ( typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= minArg && valueArg <= maxTerminalSnapshotGridSize ); const isControllerTerminalSnapshotFrame = ( valueArg: unknown, ): valueArg is interfaces.IControllerTerminalSnapshotFrame => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; return ( typeof candidate.index === 'number' && Number.isSafeInteger(candidate.index) && candidate.index >= 0 && typeof candidate.last === 'boolean' && isTerminalSnapshotGridDimension(candidate.cols, minTerminalSnapshotCols) && isTerminalSnapshotGridDimension(candidate.rows, minTerminalSnapshotRows) ); }; export const isControllerTerminalOutput = ( valueArg: unknown, ): valueArg is interfaces.IReq_ControllerTerminalOutput['request'] => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; return ( isQualifiedRuntimeId(candidate.terminalId, terminalHarnessIds) && typeof candidate.offset === 'number' && Number.isSafeInteger(candidate.offset) && candidate.offset >= 0 && typeof candidate.dataBase64 === 'string' && (candidate.snapshot === undefined || isControllerTerminalSnapshotFrame(candidate.snapshot)) && (candidate.ended === undefined || typeof candidate.ended === 'boolean') ); }; export const isControllerTerminalDetached = ( valueArg: unknown, ): valueArg is interfaces.IReq_ControllerTerminalDetached['request'] => { if (!valueArg || typeof valueArg !== 'object') return false; const candidate = valueArg as Partial; return ( isQualifiedRuntimeId(candidate.terminalId, terminalHarnessIds) && candidate.reason === 'delivery_failed' ); }; export class ControllerSocketClient { private readonly typedRouter = new plugins.typedrequest.TypedRouter(); private typedSocket: plugins.typedsocket.TypedSocket | undefined; private statusSubscription: { unsubscribe: () => void } | undefined; private diagnosticsSubscription: { unsubscribe: () => void } | undefined; private abortController: AbortController | undefined; private startPromise: Promise | undefined; constructor(private readonly options: IControllerSocketClientOptions) { this.typedRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'controller.event', async (eventArg) => { if (isControllerEvent(eventArg)) { await this.options.onControllerEvent(eventArg); } return { received: true }; }, ), ); this.typedRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'controller.terminal.output', async (outputArg) => { if (isControllerTerminalOutput(outputArg)) { await this.options.onTerminalOutput?.(outputArg); } return {}; }, ), ); this.typedRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'controller.terminal.detached', async (detachedArg) => { if (isControllerTerminalDetached(detachedArg)) { await this.options.onTerminalDetached?.(detachedArg); } return { received: true }; }, ), ); this.typedRouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'controller.session.child.event', async (eventArg) => { if (isControllerChildEvent(eventArg)) { await this.options.onChildEvent?.(eventArg); } return { received: true }; }, ), ); } public get status(): plugins.typedsocket.TConnectionStatus { return this.typedSocket?.getStatus() ?? 'new'; } public get isConnected(): boolean { return this.status === 'connected'; } public async start(): Promise { if (this.typedSocket?.getStatus() === 'connected') { return; } if (this.startPromise) { return this.startPromise; } const startPromise = this.startInternal(); this.startPromise = startPromise; try { await startPromise; } finally { if (this.startPromise === startPromise) { this.startPromise = undefined; } } } public async stop(): Promise { this.abortController?.abort(); this.abortController = undefined; this.statusSubscription?.unsubscribe(); this.statusSubscription = undefined; this.diagnosticsSubscription?.unsubscribe(); this.diagnosticsSubscription = undefined; const typedSocket = this.typedSocket; this.typedSocket = undefined; if (typedSocket) { await typedSocket.stop(); } if (this.startPromise) { await this.startPromise.catch(() => undefined); } } public async fire( method: TRequest['method'], request: TRequest['request'], optionsArg?: IControllerSocketRequestOptions, ): Promise { const typedSocket = this.typedSocket; if (!typedSocket || typedSocket.getStatus() !== 'connected') { throw new Error('The controller connection is not available.'); } const timeoutMs = optionsArg?.timeoutMs ?? controllerRequestTimeoutMs; const { abortSignal, ...fireOptions } = optionsArg ?? {}; return typedSocket.createTypedRequest(method, undefined, { timeoutMs, abortSignal, }).fire(request, { ...fireOptions, timeoutMs, }); } private async startInternal(): Promise { this.options.onStatus('connecting'); this.abortController = new AbortController(); const typedSocket = await plugins.typedsocket.TypedSocket.createClient( this.typedRouter, plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(), { autoReconnect: true, maxRetries: 100, initialBackoffMs: 500, maxBackoffMs: 10_000, abortSignal: this.abortController.signal, }, ); this.typedSocket = typedSocket; this.statusSubscription = typedSocket.statusSubject.subscribe((statusArg) => { this.options.onStatus(statusArg); }); this.diagnosticsSubscription = typedSocket.diagnosticsSubject.subscribe((diagnosticArg) => { if ( diagnosticArg.kind === 'connectionClosed' && typeof diagnosticArg.closeCode === 'number' && diagnosticArg.closeCode !== 1000 ) { this.options.onTransportClose?.( diagnosticArg.closeCode, typeof diagnosticArg.reason === 'string' ? diagnosticArg.reason : '', ); } else if (diagnosticArg.kind === 'reconnectExhausted') { this.options.onReconnectExhausted?.( diagnosticArg.attempts, diagnosticArg.endpoint, ); } }); // createClient resolves after the first connected emission, so subscribers // installed here intentionally receive the current state once explicitly. this.options.onStatus(typedSocket.getStatus()); } }