import type { ILiveBrowserDialog, ILiveBrowserDialogResponse, ILiveVideoOffer, ILiveVideoDescription, ILiveVideoStatistics, ILiveVideoAcceleration } from '@push.rocks/smartbrowser/web'; export const controllerProtocolVersion = 36 as const; export const controllerUpgradeManagementVersion = 2 as const; export const controllerPackageName = 'agl' as const; export const controllerMaxToolEventBytes = 48 * 1024; export const controllerMaxLiveMessageEventBytes = 128 * 1024; export const controllerLiveMessageDeltaTargetBytes = 16 * 1024; export const controllerInitialMessageBundleLimit = 20; export const controllerMaximumMessageBundles = 200; export const controllerMaximumTranscriptBytes = 512 * 1024; /** * The whole value a live tool payload is replaced by when it does not fit the event budget and no * prefix of it would be meaningful. It is the only in-band signal the contract has; a truncated * payload carries a prefix plus its own flag instead. See `IControllerToolExecution`. */ export const controllerLiveToolPayloadNotice = '[elided: this tool payload exceeds the live transfer budget]' as const; export const controllerMaxDraftTextBytes = 64 * 1024; export const controllerMaxDraftAttachments = 8; export const controllerMaxDraftAttachmentBytes = 10 * 1024 * 1024; export const controllerMaxDraftAttachmentTotalBytes = 10 * 1024 * 1024; export const controllerMaxAttachmentPromptSuffixBytes = 16 * 1024; /** * How many subjects one resource may be attached to at once. Deliberately below * `@modelprofile.com/browser-runtime`'s own 64-session capability cap, so a lifecycle path can * never grow a set the runtime would refuse with INVALID_INPUT: AGL refuses it first, in the * attach gate, with an error that names the limit. */ export const controllerResourceAttachmentLimit = 32; /** Bounds of the controller-wide sidebar layout: it holds every project's items at once. */ export const controllerSessionLayoutGroupLimit = 256; export const controllerSessionLayoutUngroupedLimit = 8192; /** Standard project directories a controller offers as project locations. */ export const controllerStandardProjectDirectoryLimit = 32; export const controllerHarnessIds = ['opencode', 'flex', 'codex', 'controller'] as const; export const controllerSessionHarnessIds = ['opencode', 'flex', 'codex'] as const; export type TControllerHarnessId = (typeof controllerHarnessIds)[number]; export type TControllerSessionHarnessId = (typeof controllerSessionHarnessIds)[number]; export interface IControllerRuntimeId { harnessId: TControllerHarnessId; nativeId: string; } export type TControllerSessionId = IControllerRuntimeId & { harnessId: TControllerSessionHarnessId; }; export type TControllerTerminalId = IControllerRuntimeId & { harnessId: 'controller'; }; export type TControllerLayoutItemId = TControllerSessionId | TControllerTerminalId; /** Collision-free key for maps and sets containing qualified runtime IDs. */ export const controllerRuntimeIdKey = (runtimeIdArg: IControllerRuntimeId): string => JSON.stringify([runtimeIdArg.harnessId, runtimeIdArg.nativeId]); export type TControllerLifecycleState = 'starting' | 'ready' | 'stopping' | 'stopped'; export type TControllerProcessMode = 'detached' | 'foreground'; export type TControllerHarnessLifecycleState = 'starting' | 'ready' | 'stopping' | 'stopped' | 'failed'; export type TControllerAuthState = 'setupRequired' | 'ready'; export type TControllerSessionStatus = 'idle' | 'busy' | 'retry' | 'error'; export type TControllerMessageRole = 'user' | 'assistant' | 'system' | 'tool'; export type TControllerToolStatus = 'pending' | 'running' | 'completed' | 'error' | 'stopped'; export type TControllerPermissionReply = 'once' | 'reject'; /** * Built-in OpenCode session operations exposed as slash commands, next to the * per-project custom command catalog. */ export const controllerBuiltinCommands = ['compact', 'undo', 'redo', 'init'] as const; export type TControllerBuiltinCommand = (typeof controllerBuiltinCommands)[number]; export type TControllerSlashCommandKind = 'builtin' | 'template' | 'handler'; export type TControllerSlashWorkspaceReversion = 'supported' | 'unsupported' | 'not-applicable'; export type TControllerSlashClientAction = | 'model' | 'new' | 'clear' | 'resume' | 'copy' | 'status' | 'usage' | 'subagents' | 'archive-confirm' | 'delete-confirm'; export type TControllerSlashInput = | { type: 'ordinary' } | { type: 'malformed' } | { type: 'command'; name: string; arguments: string }; /** * Shared composer classifier. A leading absolute path is ordinary prompt text; a valid first * slash token is a command even when the provider does not recognize its name. */ export const classifyControllerSlashInput = (textArg: string): TControllerSlashInput => { const trimmed = textArg.trim(); if (!trimmed.startsWith('/')) return { type: 'ordinary' }; const withoutSlash = trimmed.slice(1); const spaceIndex = withoutSlash.search(/\s/u); const name = spaceIndex < 0 ? withoutSlash : withoutSlash.slice(0, spaceIndex); if (name.includes('/') || name.includes('\\')) return { type: 'ordinary' }; const argumentsText = spaceIndex < 0 ? '' : withoutSlash.slice(spaceIndex).trim(); // eslint-disable-next-line no-control-regex if (!name || new TextEncoder().encode(name).byteLength > 128 || /[\x00-\x1f\x7f\s]/u.test(name)) { return { type: 'malformed' }; } if (new TextEncoder().encode(argumentsText).byteLength > 32 * 1024) return { type: 'malformed' }; return { type: 'command', name, arguments: argumentsText }; }; export interface IControllerAffectedWorkspace { id: string; label: string; } export interface IControllerSessionReversionGroup { runId: string; kind: 'candidate' | 'barrier' | 'no-change'; visibility: 'visible' | 'hidden'; affectedWorkspaces: IControllerAffectedWorkspace[]; affectedWorkspacesTruncated: boolean; } export interface IControllerSessionReversionInfo { undoAvailable: boolean; redoAvailable: boolean; groups: IControllerSessionReversionGroup[]; } export interface IControllerSlashCommandDescriptor< TMetadata extends Record = Record, > { name: string; description: string; kind: TControllerSlashCommandKind; hints: string[]; available: boolean; unavailableReason?: string; workspaceReversion: TControllerSlashWorkspaceReversion; metadata: TMetadata; } export interface IControllerSlashListResult< TDescriptor extends IControllerSlashCommandDescriptor = IControllerSlashCommandDescriptor, > { commands: TDescriptor[]; reversion: IControllerSessionReversionInfo; } export type TControllerSlashExecuteResult = | { type: 'operation'; name: string } | { type: 'handler-result'; name: string; result: TResult } | { type: 'mode-updated'; name: string; collaborationMode: import('./codex.js').IControllerCodexCollaborationMode } | { type: 'session-created'; name: string; session: IControllerSession; creationPending?: true } | { type: 'client-action'; name: string; action: TControllerSlashClientAction } | { type: 'prompt-admission'; name: string; queueId?: string; runId?: string } | { type: 'literal-prompt'; name: string }; export type TControllerTodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'; export type TControllerResourceKind = 'terminal' | 'browser'; /** Coding agents AGL can own as the root process of a terminal resource. */ export type TControllerTerminalAgentKind = 'claude'; export type TControllerTerminalAgentDesiredState = 'running' | 'stopped'; export type TControllerTerminalAgentLaunchMode = 'resume' | 'new'; export type TControllerTerminalAgentFailure = | 'binary_missing' | 'cwd_rebound' | 'session_in_use' | 'liveness_unverified' | 'spawn_failed'; export type TControllerResourceLifecycle = 'active' | 'retiring' | 'retired'; export type TControllerResourceAgentAvailability = | 'detached' | 'transitioning' | 'available' | 'dependencyUnavailable'; export type TControllerTerminalProcessState = 'running' | 'stopped'; export type TControllerBrowserRuntimeState = 'available' | 'unavailable'; export type TControllerUpgradePhase = | 'preparing' | 'pausing' | 'installing' | 'restarting' | 'continuing' | 'completed' | 'failed'; export interface IControllerUpgradeStatus { fromVersion: string; toVersion: string; phase: TControllerUpgradePhase; } export interface IControllerStatus { packageName: string; packageVersion: string; protocolVersion: typeof controllerProtocolVersion; upgradeManagementVersion: number; controllerPid: number; processGroupId: number; processFingerprint: string; processMode: TControllerProcessMode; lifecycleState: TControllerLifecycleState; startedAt: number; setupRequired: boolean; upgrade?: IControllerUpgradeStatus; harnesses: Array<{ harnessId: TControllerSessionHarnessId; pid?: number; state: TControllerHarnessLifecycleState; healthy: boolean; version?: string; supportsLocalAttachments?: boolean; connectionMode?: 'local' | 'shared' | 'remote'; diagnostic?: import('./codex.js').IControllerCodexDiagnostic; }>; } /** One mounted filesystem, as shown in the status bar. */ export interface IControllerFilesystemUsage { /** Absolute mount path, for example `/` or `/mnt/data`. */ mountPoint: string; /** Last path segment of the mount point; `/` for the root filesystem. */ label: string; fsType: string; /** * Percentage of usable capacity in use, or null when the mount could not be * measured. Every byte figure is null for the same reason. */ usedPercent: number | null; usedBytes: number | null; totalBytes: number | null; /** Why the mount could not be measured; null when the numbers are real. */ unavailableReason: string | null; /** Activity of the disks behind the mount between two samples; null until it can be measured. */ io: IControllerDiskIo | null; /** Why `io` is null; null while it is set. */ ioUnavailableReason: string | null; } /** * Disk activity behind one volume, measured between two samples. The figures * cover all I/O on the disks named in `devices` — every partition, filesystem * and swap area on them — so volumes on the same disk carry the same numbers. */ export interface IControllerDiskIo { /** * Share of the interval, 0–100, in which the busiest of `devices` had I/O in * flight. It is a share of time, not of throughput capacity: a device that * serves requests in parallel — any SSD, NVMe in particular — reads 100% as * soon as one request is always outstanding, long before it is saturated. */ busyPercent: number; readBytesPerSecond: number; writeBytesPerSecond: number; readsPerSecond: number; writesPerSecond: number; /** Kernel names of the disks the figures come from, for example `nvme0n1`. */ devices: string[]; } /** * System-wide I/O pressure stall information (/proc/pressure/io): the * percentage of wall time in which tasks were stalled waiting on I/O, averaged * over the last 10 and 60 seconds. */ export interface IControllerIoPressure { /** Share of time in which at least one task was stalled waiting on I/O. */ someAvg10: number; someAvg60: number; /** Share of time in which every non-idle task was stalled on I/O at once. */ fullAvg10: number; fullAvg60: number; } export interface IControllerSystemMetrics { sampledAt: number; cpuUsagePercent: number | null; /** Actively used memory, excluding reclaimable buffers and caches. */ memoryUsedBytes: number | null; memoryTotalBytes: number | null; networkReceiveBytesPerSecond: number | null; networkTransmitBytesPerSecond: number | null; /** The filesystem holding `/`; null when it could not be enumerated. */ mainDisk: IControllerFilesystemUsage | null; /** * Every other monitored volume, sorted by usage descending with unmeasurable * mounts last. Pseudo filesystems and volumes below 1 GiB are excluded. */ volumes: IControllerFilesystemUsage[]; /** Share of time tasks stalled on I/O; null where the kernel does not report it. */ ioPressure: IControllerIoPressure | null; } /** One recorded point in the controller's preceding hour of host telemetry. */ export interface IControllerSystemMetricsHistoryPoint { sampledAt: number; cpuUsagePercent: number | null; memoryUsedPercent: number | null; networkReceiveBytesPerSecond: number | null; networkTransmitBytesPerSecond: number | null; disks: Array<{ mountPoint: string; usedPercent: number | null; busyPercent: number | null; }>; } /** * Error code of the deliberately generic controller failure response. The wire text stays * 'The controller operation failed.' so no internal detail leaves the process, and the * accompanying `reference` names the journal record the owner UI can read the cause from. */ export const controllerOperationFailedErrorCode = 'controller_operation_failed' as const; /** Upper bound of references one failure-detail read may ask for. */ export const controllerFailureDetailRequestLimit = 64; /** * The cause behind one generic controller failure, kept in memory only and readable by the * authenticated owner. It is internal error text by nature and never leaves the owner UI. */ export interface IControllerFailureDetail { /** Opaque short reference carried on the generic failure response. */ reference: string; at: number; /** The typed-request method, audited operation, or host label that failed. */ operation: string; /** Error name of the cause, for example `TypeError`. */ name: string; message: string; stack: string; } export interface IControllerProject { id: string; /** Path relative to the configured projects root; doubles as display name. */ name: string; /** Absolute resolved path of the project worktree. */ directory: string; createdAt: number; } /** * Single source of the deterministic project-removal failure vocabulary: the controller persists * one of these codes, and the browser renders it, so the two cannot drift apart. Retrying such an * attempt cannot succeed while its cause stands, so the controller stops retrying and names it. */ export const controllerProjectRemovalBlockedCodes = [ 'git.discovery-limit', 'git.fenced', 'git.dirty-worktree', 'filesystem.permission', ] as const; export type TControllerProjectRemovalBlockedCode = (typeof controllerProjectRemovalBlockedCodes)[number]; /** Upper bound of the persisted operator-readable block explanation. */ export const controllerProjectRemovalBlockedReasonMaxLength = 256; /** * A project whose removal is durably pending but no longer retried. It is absent from the normal * project list: it accepts no new work, and only an operator retry resumes it. */ export interface IControllerBlockedProjectRemoval { id: string; name: string; directory: string; code: TControllerProjectRemovalBlockedCode; /** Short operator-readable explanation of the deterministic failure. */ reason: string; blockedAt: number; } /** One attachment subject: a task, or a terminal resource. */ export type TControllerResourceAttachmentTarget = | { kind: 'session'; sessionId: TControllerSessionId } | { kind: 'terminal'; resourceId: string }; export interface IControllerResourceTerminalTarget { /** The terminal resource this resource is attached to. */ resourceId: string; /** Present when the terminal root is a coding agent; fences the exact conversation. */ agentSessionId?: string; } /** * One membership in a resource's attachment set. * * A resource may be attached to several subjects at once, and every attached subject may use it. * Concurrent use is serialized by the attachment revision each caller observed and by browser * view ownership; it is not prevented. A second chat attaching therefore does not steal the * resource from the first. */ export interface IControllerResourceAttachmentEntry { kind: 'session' | 'terminal'; /** Qualified runtime id for a task; the resource id for a terminal. */ id: TControllerSessionId | string; projectId: string; attachedAt: number; /** Exact managed-session identity; present on session entries. */ sessionIdentityId?: string; /** Fences the exact conversation of a terminal subject across its restarts. */ agentSessionId?: string; } export interface IControllerResourceAttachment { /** Stable authority owned by this resource for its complete lifetime. */ authorityId: string; /** Monotonic fence advanced by every add, remove, or replace across the whole set. */ revision: number; entries: IControllerResourceAttachmentEntry[]; } export interface IControllerResourceBase { id: string; projectId: string; kind: TControllerResourceKind; title: string; lifecycle: TControllerResourceLifecycle; attachment: IControllerResourceAttachment; agentAvailability: TControllerResourceAgentAvailability; createdAt: number; updatedAt: number; } export interface IControllerTerminalResourceAgent { kind: TControllerTerminalAgentKind; /** The coding agent's own conversation id, stable across every restart of this resource. */ sessionId: string; desiredState: TControllerTerminalAgentDesiredState; /** Which upstream flag the newest start resolved to. Absent until the first start. */ launchMode?: TControllerTerminalAgentLaunchMode; lastFailure?: TControllerTerminalAgentFailure; lastFailureMessage?: string; } export interface IControllerTerminalResource extends IControllerResourceBase { kind: 'terminal'; command: string; cwd: string; processState: TControllerTerminalProcessState; stoppedAt?: number; lastExitCode?: number; /** Present only when the terminal root is a controller-owned coding agent. */ agent?: IControllerTerminalResourceAgent; } export interface IControllerBrowserResource extends IControllerResourceBase { kind: 'browser'; browserRuntimeState: TControllerBrowserRuntimeState; } export type TControllerResource = IControllerTerminalResource | IControllerBrowserResource; export interface IControllerBrowserViewport { width: number; height: number; deviceScaleFactor: number; } export interface IControllerBrowserTabState { dialog?: ILiveBrowserDialog; id: string; url: string; title: string; active: boolean; status: 'open' | 'crashed'; generation: number; appliedViewportRevision: number; streaming: boolean; } export interface IControllerBrowserVideoOffer extends Omit {} export interface IControllerBrowserVideoDescription extends ILiveVideoDescription {} export interface IControllerBrowserVideoStatistics extends ILiveVideoStatistics {} export interface IControllerBrowserViewState { videoAcceleration?: ILiveVideoAcceleration; videoSource?: ILiveVideoOffer['source']; /** Monotonic for the complete lifetime of one browser view. */ revision: number; status: 'stopped' | 'starting' | 'running' | 'stopping'; activeTabId: string | null; viewportRevision: number; viewport: IControllerBrowserViewport; tabs: IControllerBrowserTabState[]; lastError?: { code: string; message: string; fatal: boolean; tabId?: string }; } export type TControllerBrowserViewEvent = | { type: 'state'; state: IControllerBrowserViewState } | { type: 'error'; error: { code: string; message: string; fatal: boolean; tabId?: string } }; export interface IControllerBrowserInputBase { tabId: string; generation: number; viewportRevision: number; } export interface IControllerBrowserModifierState { alt?: boolean; control?: boolean; meta?: boolean; shift?: boolean; } export interface IControllerBrowserMouseInput extends IControllerBrowserInputBase { /** Monotonic occurrence time from the originating viewer's clock. */ timestampMs?: number; type: 'move' | 'down' | 'up'; x: number; y: number; button?: 'none' | 'left' | 'middle' | 'right' | 'back' | 'forward'; buttons?: number; clickCount?: number; modifiers?: IControllerBrowserModifierState; } export interface IControllerBrowserWheelInput extends IControllerBrowserInputBase { x: number; y: number; deltaX: number; deltaY: number; modifiers?: IControllerBrowserModifierState; } export interface IControllerBrowserKeyInput extends IControllerBrowserInputBase { type: 'down' | 'up'; key: string; code?: string; text?: string; unmodifiedText?: string; windowsVirtualKeyCode?: number; nativeVirtualKeyCode?: number; autoRepeat?: boolean; isKeypad?: boolean; location?: number; modifiers?: IControllerBrowserModifierState; } export type TControllerBrowserViewOperation = | { type: 'respondToDialog'; input: ILiveBrowserDialogResponse } | { type: 'openVideoPeer' | 'closeVideoPeer' | 'getVideoStatistics' } | { type: 'answerVideoPeer'; negotiationId: string; description: IControllerBrowserVideoDescription } | { type: 'setViewport'; viewport: IControllerBrowserViewport } | { type: 'dispatchMouse'; input: IControllerBrowserMouseInput } | { type: 'dispatchWheel'; input: IControllerBrowserWheelInput } | { type: 'dispatchKey'; input: IControllerBrowserKeyInput } | { type: 'insertText'; input: IControllerBrowserInputBase & { text: string } } | { type: 'navigate'; url: string; tabId?: string; timeoutMs?: number } | { type: 'createTab'; url?: string; activate?: boolean } | { type: 'activateTab'; tabId: string } | { type: 'closeTab'; tabId: string } | { type: 'back' | 'forward' | 'reload'; tabId?: string }; export interface IControllerPathSuggestion { /** * Path usable directly for project registration: relative to the projects * root, or absolute when the query that produced it was absolute. */ path: string; /** Last path segment, for display. */ name: string; } export interface IControllerOpenCodeModelChoice { harnessId: 'opencode'; providerID: string; modelID: string; /** Reasoning-effort variant name as advertised by the model (e.g. 'high'). */ variant?: string; } export interface IControllerFlexModelChoice { harnessId: 'flex'; providerID: string; modelID: string; /** Reasoning-effort variant name as advertised by the model (e.g. 'high'). */ variant?: string; } export interface IControllerCodexModelChoice { harnessId: 'codex'; /** Catalog namespace; Codex owns its configured model provider and credentials. */ providerID: 'codex'; modelID: string; variant?: string; } export type TControllerModelChoice = | IControllerOpenCodeModelChoice | IControllerFlexModelChoice | IControllerCodexModelChoice; export interface IControllerOpenCodeModelOption extends IControllerOpenCodeModelChoice { providerName: string; modelName: string; /** Available reasoning-effort variant names, in catalog order. Empty when the model has none. */ variants: string[]; } export interface IControllerFlexModelAvailability { providerConnectionId: string; /** Variants advertised by this exact provider account, in catalog order. */ variants: string[]; /** Whether this exact provider account advertises the model as its default. */ isDefault: boolean; } export interface IControllerFlexModelOption extends IControllerFlexModelChoice { providerName: string; modelName: string; /** Union of variants across available accounts, for account-independent settings. */ variants: string[]; /** Exact account-specific availability for this model. */ availability: IControllerFlexModelAvailability[]; } export interface IControllerCodexModelOption extends IControllerCodexModelChoice { providerName: string; modelName: string; variants: string[]; } export type TControllerModelOption = | IControllerOpenCodeModelOption | IControllerFlexModelOption | IControllerCodexModelOption; export interface IControllerSettings { /** Desired backend; absent means Chromium. Changes apply on controller restart. */ browserVideoBackend?: 'native'; /** Backend running in this controller; absent means Chromium. */ activeBrowserVideoBackend?: 'native'; /** At most one default per session harness. */ defaultModels: TControllerModelChoice[]; /** When true, the controller replies 'once' to every pending permission request automatically. */ autoAcceptPermissions?: boolean; /** * Absolute existing directories AGL offers as project locations. Their immediate * subdirectories are search and creation candidates; nothing is registered until a * conversation is created or opened in one. */ standardProjectDirectories: string[]; /** * Harness of the conversation started last from the workspace. The new-conversation box * preselects it; while it is absent the harness is an explicit choice, never a default. */ lastSessionHarnessId?: TControllerSessionHarnessId; } export interface IControllerSession { id: IControllerRuntimeId; title: string; parentId?: IControllerRuntimeId; createdAt: number; updatedAt: number; /** Unix milliseconds when the session was archived in OpenCode; absent for active sessions. */ archivedAt?: number; status: TControllerSessionStatus; /** Bounded native writer state for Codex sidebar rows. */ codexActivity?: import('./codex.js').IControllerCodexActivitySummary; /** True while OpenCode has a pending permission or question for this session. */ attention?: boolean; } /** How a conversation entered AGL's tracked set. */ export type TControllerConversationOrigin = 'created' | 'opened'; /** * One conversation AGL tracks explicitly. Harness conversations are never mirrored into * this set: it holds exactly what was created through AGL or opened through the conversation * search, across every registered project. */ export interface IControllerTrackedConversation { projectId: string; /** Display name of the owning project; the sidebar spans projects. */ projectName: string; session: IControllerSession; origin: TControllerConversationOrigin; trackedAt: number; /** * Unix milliseconds when the conversation was archived in AGL. Independent of * `session.archivedAt`, which is the harness's own archive. */ archivedAt?: number; } /** One harness conversation offered by the cross-project conversation search. */ export interface IControllerConversationSearchResult { /** Absent while the directory is not a registered project yet. */ projectId?: string; projectName: string; projectDirectory: string; session: IControllerSession; /** True when this conversation is already in AGL's tracked set. */ tracked: boolean; } export interface IControllerReasoningPart { id: IControllerRuntimeId; text: string; startedAt?: number; endedAt?: number; } export interface IControllerToolCall { id: IControllerRuntimeId; name: string; status: TControllerToolStatus; /** * Producer-supplied one-line description of the call — OpenCode's tool title, or a Codex command * action rendered as "Read SKILL.md". Absent whenever the harness offers nothing; FlexHarness * never does. */ title?: string; input?: unknown; output?: unknown; /** Process exit code when output is one combined stream. */ exitCode?: number; errorText?: string; /** * Raised on a call whose payload was filled from a bounded live snapshot, where `output` is a * marker-free prefix of the real output. See `IControllerToolExecution` for the rule. A harness * transcript bounds its own payloads separately and does not raise this flag. */ outputTruncated?: true; /** As `outputTruncated`, for `errorText`. */ errorTextTruncated?: true; /** Session spawned by this call (subagent/task tools). */ childSessionId?: IControllerRuntimeId; /** "providerID/modelID" the spawned session runs on (subagent/task tools). */ model?: string; startedAt?: number; finishedAt?: number; } /** * Replace-only live snapshot for one harness tool execution. * * A live snapshot travels under a per-event transfer budget the durable transcript does not * share, so a payload may reach the receiver bounded. Exactly two bounding operations exist and * both are signalled explicitly, never by content: * * - **elision** replaces a whole payload with `controllerLiveToolPayloadNotice`. Used for a * payload no prefix of which is meaningful — an input object, a structured output. * - **truncation** keeps a marker-free PREFIX of the real payload and raises the matching * `…Truncated` flag below. Only the two text streams are ever truncated. * * A receiver therefore never infers truncation from the payload text, and a producer never writes * an ellipsis or a "[truncated]" notice into a value on the wire: a bounded value is always a * value the durable transcript still starts with. Rendering the fact to a human is the receiver's * own presentation decision, taken from the flag. */ export interface IControllerToolExecution { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; partId: IControllerRuntimeId; callId: IControllerRuntimeId; toolName: string; status: TControllerToolStatus; /** * Producer-supplied one-line description of the call — OpenCode's tool title, or a Codex command * action rendered as "Read SKILL.md". Absent whenever the harness offers nothing; FlexHarness * never does. */ title?: string; input?: unknown; output?: unknown; exitCode?: number; errorText?: string; /** * Raised when `output` is a marker-free prefix of the real output, bounded for transfer. Like * every marker in this contract it is present or absent, never `false`: an untruncated payload * is the absence of the flag. */ outputTruncated?: true; /** As `outputTruncated`, for `errorText`. */ errorTextTruncated?: true; childSessionId?: IControllerRuntimeId; model?: string; startedAt?: number; finishedAt?: number; /** Authoritative source coordinates; required for Flex and absent for OpenCode. */ order?: IControllerTranscriptOrder; /** Harness tool-state timestamp used to reject delayed state regressions. */ sourceUpdatedAt: number; /** Controller-process monotonic ordering for delivered snapshots. */ revision: number; /** Changes whenever the controller observes a new event stream for this harness. */ streamEpoch: number; } /** Replace-only live Assistant reasoning snapshot from either session harness. */ export interface IControllerReasoningUpdate { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; partId: IControllerRuntimeId; text: string; status: 'running' | 'completed' | 'cancelled'; /** Authoritative source coordinates; required for Flex and absent for OpenCode. */ order?: IControllerTranscriptOrder; sourceUpdatedAt: number; revision: number; streamEpoch: number; } /** Replace-only live Assistant text snapshot from either session harness. */ export interface IControllerTextUpdate { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; partId: IControllerRuntimeId; text: string; status: 'running' | 'completed'; /** Authoritative source coordinates; required for Flex and absent for OpenCode. */ order?: IControllerTranscriptOrder; sourceUpdatedAt: number; revision: number; streamEpoch: number; } export interface IControllerReasoningDelta { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; partId: IControllerRuntimeId; delta: string; baseTextUtf8Bytes: number; textUtf8Bytes: number; /** Authoritative source coordinates; required for Flex and absent for OpenCode. */ order?: IControllerTranscriptOrder; sourceUpdatedAt: number; revision: number; streamEpoch: number; } export interface IControllerTextDelta { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; partId: IControllerRuntimeId; delta: string; baseTextUtf8Bytes: number; textUtf8Bytes: number; /** Authoritative source coordinates; required for Flex and absent for OpenCode. */ order?: IControllerTranscriptOrder; sourceUpdatedAt: number; revision: number; streamEpoch: number; } export interface IControllerToolStreamCursor { streamEpoch: number; revision: number; } export interface IControllerMessageStreamCursor { streamEpoch: number; revision: number; } export interface IControllerUsage { inputTokens?: number; outputTokens?: number; totalTokens?: number; cacheReadTokens?: number; cacheWriteTokens?: number; } export interface IControllerTranscriptOrder { messageIndex: number; partIndex: number; } export interface IControllerDraftAttachment { id: string; name: string; mediaType: string; size: number; kind: 'image' | 'text' | 'binary'; dataBase64: string; } /** Controller-memory-only composer state for one exact session. */ export interface IControllerSessionDraft { text: string; attachments: IControllerDraftAttachment[]; revision: number; } /** Authoritative replacement fields changed by one draft mutation. */ export interface IControllerSessionDraftUpdate { revision: number; text?: string; attachments?: IControllerDraftAttachment[]; } /** Controller-memory projection of a harness-accepted prompt not yet present in transcript hydration. */ export interface IControllerPendingPrompt { id: IControllerRuntimeId; text: string; attachments: Array>; createdAt: number; } /** Session-level token facts. Metrics that cannot be proven stay absent. */ export interface IControllerSessionMetrics { lifetimeUsedTokens?: number; compactionCount?: number; tokensBeforeLatestCompaction?: number; maxContextTokens?: number; currentContextTokens?: number; } export type TControllerScratchpadUpdater = 'user' | 'intelligence' | 'agent' | 'application'; export interface IControllerSessionScratchpad { /** Stable controller/project/qualified-session identity. */ id: string; text: string; revision: number; updatedAt?: number; updatedBy?: TControllerScratchpadUpdater; } export type TControllerIntelligenceStatus = 'running' | 'completed' | 'error'; export interface IControllerIntelligenceExchange { id: string; question: string; status: TControllerIntelligenceStatus; answer?: string; error?: string; model?: string; /** Luna answered, but a newer user scratchpad revision won the write race. */ scratchpadConflict?: true; createdAt: number; completedAt?: number; } export interface IControllerMessage { id: IControllerRuntimeId; role: TControllerMessageRole; text: string; createdAt: number; /** Authoritative source chronology when the harness exposes ordered messages and parts. */ order?: IControllerTranscriptOrder; updatedAt?: number; streaming?: boolean; reasoning?: IControllerReasoningPart[]; toolCall?: IControllerToolCall; usage?: IControllerUsage; error?: string; /** "providerID/modelID" that produced this assistant message. */ model?: string; /** Model variant (reasoning effort) this message ran with. */ effort?: string; } export interface IControllerPermission { id: IControllerRuntimeId; sessionId: IControllerRuntimeId; title: string; type: string; patterns: string[]; metadata: Record; createdAt?: number; } export interface IControllerTodo { id?: IControllerRuntimeId; content: string; status: TControllerTodoStatus; } export interface IControllerQuestionOption { /** Short display text. */ label: string; /** Explanation of the choice. */ description: string; } export interface IControllerQuestionItem { /** Complete question text. */ question: string; /** Very short label for the question, as advertised by OpenCode. */ header: string; options: IControllerQuestionOption[]; /** Multiple answers may be selected. */ multiple?: boolean; /** A free-text answer is accepted besides the listed options. */ custom?: boolean; } export interface IControllerQuestion { id: IControllerRuntimeId; sessionId: IControllerRuntimeId; questions: IControllerQuestionItem[]; /** Tool call that raised the ask; links the request to its transcript entry. */ toolCallId?: IControllerRuntimeId; } export interface IControllerTerminal { /** Controller-generated opaque PTY identifier. */ id: TControllerTerminalId; title: string; command: string; cwd: string; } /** * One frame of a reconstructed terminal screen. The controller parses every terminal's output in * a headless emulator, so an attaching peer receives the last state instead of a raw scrollback * replay: the frames of one snapshot carry the serialized screen in order, they all share the * stream offset the state is exact at, and live output continues from that offset. */ export interface IControllerTerminalSnapshotFrame { /** * Position of this frame inside the snapshot; a frame 0 at a stream position the client has not * applied yet opens the collection and replaces whatever the client shows. A frame may be re-sent * after an unacknowledged delivery, repeating the identical `offset` and `index`, so a client * ignores an index it already collected — and likewise every frame of a snapshot it has already * applied, which repeats a state anchored where its applied end already is. The `offset` rule raw * frames are deduplicated by cannot separate these, because every frame of one snapshot carries * the same offset. */ index: number; /** * The snapshot is complete with this frame. The collected frames are applied as one state once * it arrives, and the applied end is anchored at `offset`. */ last: boolean; /** Grid the state was serialized at. Restore into this size, then fit the view. */ cols: number; rows: number; } /** * A member of the ordered sidebar layout. Conversations and resources are peers there, and their * identifiers live in separate namespaces — a conversation is a qualified runtime id, a resource * is an opaque id — so the kind is carried explicitly rather than inferred. */ export type TControllerLayoutItemRef = | { kind: 'session'; id: TControllerSessionId; projectId: string } | { kind: 'resource'; id: string; projectId: string }; /** * Collision-free across projects as well as kinds: the sidebar is becoming cross-project, and a * conversation id is only unique within its own project. */ export const controllerLayoutItemRefKey = (refArg: TControllerLayoutItemRef): string => refArg.kind === 'session' ? `${refArg.projectId}:session:${controllerRuntimeIdKey(refArg.id)}` : `${refArg.projectId}:resource:${refArg.id}`; export const controllerLayoutItemRefsEqual = ( leftArg: TControllerLayoutItemRef, rightArg: TControllerLayoutItemRef, ): boolean => controllerLayoutItemRefKey(leftArg) === controllerLayoutItemRefKey(rightArg); export interface IControllerSessionGroup { /** Client-generated stable identifier. */ id: string; name: string; /** Conversations and resources in display order. */ itemIds: TControllerLayoutItemRef[]; } export interface IControllerSessionLayout { groups: IControllerSessionGroup[]; /** Explicit display order for ungrouped layout items. */ ungroupedItemIds: TControllerLayoutItemRef[]; /** Monotonic persistence revision used to reject stale browser updates. */ revision: number; } export interface IControllerSessionDetail { /** Native Codex observations from this connection; modelChoice remains the user preference. */ codexActivity?: import('./codex.js').IControllerCodexActivity; /** Exact authswitch account slot that can represent this session, or why none can. */ authSwitchLimitsContext?: import('./authswitch.js').TControllerAuthSwitchLimitsContext; session: IControllerSession; messagePage: IControllerMessagePage; pendingPrompts: IControllerPendingPrompt[]; permissions: IControllerPermission[]; questions: IControllerQuestion[]; todos: IControllerTodo[]; scratchpad: IControllerSessionScratchpad; intelligenceExchanges: IControllerIntelligenceExchange[]; /** Effective auto-accept state for this chat: its own yolo switch or the global setting. */ autoAcceptPermissions?: boolean; /** Durable per-session choice; the harness default applies when absent. */ modelChoice?: TControllerModelChoice; /** Exact Flex provider account selected for this session, separate from model identity. */ providerConnectionId?: string; /** "providerID/modelID" that answered most recently in this chat. */ model?: string; /** Model variant (reasoning effort) of the most recent answer. */ effort?: string; /** Latest live-tool cursor covered by this authoritative harness snapshot. */ toolStreamCursor: IControllerToolStreamCursor; /** Latest live Assistant text/reasoning cursor covered by this snapshot. */ messageStreamCursor: IControllerMessageStreamCursor; /** Bounded direct-child requests requiring attention; browser session reads only. */ childAttention?: IControllerChildAttention[]; /** More direct-child attention existed than the browser transfer budget allowed. */ childAttentionLimited?: true; } export type TControllerChildScopeMode = 'active' | 'terminal'; export interface IControllerChildScopeDescriptor { projectId: string; parentSessionId: IControllerRuntimeId & { harnessId: 'opencode' }; childSessionId: IControllerRuntimeId & { harnessId: 'opencode' }; /** Random generation fencing every request and event for this exact child scope. */ scopeGeneration: string; mode: TControllerChildScopeMode; expiresAt: number; } /** Bounded child transcript detail without managed-session state or mutations. */ export interface IControllerChildSessionDetail extends IControllerChildScopeDescriptor { session: IControllerSession; messagePage: IControllerMessagePage; permissions: IControllerPermission[]; questions: IControllerQuestion[]; /** Latest invalidation sequence covered by this authoritative provider read. */ sequence: number; toolStreamCursor: IControllerToolStreamCursor; messageStreamCursor: IControllerMessageStreamCursor; autoAcceptPermissions?: boolean; /** "providerID/modelID" that answered most recently in this child. */ model?: string; /** Model variant (reasoning effort) of the most recent child answer. */ effort?: string; } export interface IControllerChildAttention extends IControllerChildScopeDescriptor { session: IControllerSession; permissions: IControllerPermission[]; questions: IControllerQuestion[]; /** Latest invalidation sequence covered by this attention snapshot. */ sequence: number; } export type TControllerChildEventKind = | 'session.changed' | 'transcript.changed' | 'attention.changed' | 'scope.revoked'; /** Exact-peer child invalidation; the browser rehydrates through scoped reads. */ export interface IControllerChildEvent extends IControllerChildScopeDescriptor { sequence: number; timestamp: number; kind: TControllerChildEventKind; } /** One source message and all normalized transcript entries derived from it. */ export interface IControllerMessageBundle { sourceMessageId: IControllerRuntimeId; structuralDigest: string; messages: IControllerMessage[]; } export interface IControllerMessagePage { bundles: IControllerMessageBundle[]; /** Opaque harness cursor for the next older page. */ nextCursor?: string; /** The harness retained only a bounded subset of the source transcript. */ truncated?: boolean; /** The controller omitted source bundles or payloads to enforce its transfer budget. */ historyLimited?: boolean; } export interface IControllerSessionAuxiliary { sessionMetrics: IControllerSessionMetrics; /** Session Intelligence is supported for managed OpenCode and Flex sessions. */ sessionIntelligenceEnabled: boolean; sessionIntelligenceAvailabilityStatus: TControllerSessionIntelligenceAvailabilityStatus; /** Empty while available; otherwise safe actionable copy for the browser. */ sessionIntelligenceUnavailableReason: string; } export type TControllerSessionIntelligenceAvailabilityStatus = 'available' | 'unavailable'; export type TControllerProviderLoginFlow = 'device'; export type TControllerProviderLoginStatus = 'pending' | 'succeeded' | 'cancelled' | 'failed'; export type TControllerProviderConnectionStatus = 'active' | 'reauthRequired'; export type TControllerModelRefreshJobStatus = | 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; export interface IControllerProvider { id: string; name: string; loginFlows: TControllerProviderLoginFlow[]; } export interface IControllerProviderAccountSummary { accountId?: string; email?: string; plan?: string; } export interface IControllerProviderConnection { /** Controller-owned opaque stable identifier. */ id: string; providerID: string; status: TControllerProviderConnectionStatus; account: IControllerProviderAccountSummary; /** True when this account currently authenticates the controller-owned OpenCode runtime. */ selectedForOpenCode?: true; } export interface IControllerProviderRateLimitWindow { usedPercent: number; limitWindowSeconds: number; resetAfterSeconds: number; /** Unix timestamp in seconds. */ resetsAt: number; } export interface IControllerProviderRateLimitDetails { allowed: boolean; limitReached: boolean; primaryWindow?: IControllerProviderRateLimitWindow; secondaryWindow?: IControllerProviderRateLimitWindow; } export interface IControllerProviderAdditionalRateLimit { limitName: string; meteredFeature: string; rateLimit?: IControllerProviderRateLimitDetails; } /** Public metadata-only account quota snapshot. Credentials and raw provider responses stay private. */ export interface IControllerProviderAccountRateLimits { providerConnectionId: string; providerID: 'openai'; plan: string; observedAt: string; rateLimit?: IControllerProviderRateLimitDetails; additionalRateLimits: IControllerProviderAdditionalRateLimit[]; rateLimitReachedType?: string; rateLimitResetCreditsAvailableCount?: number; } /** Public device-login state. Provider credentials and provider responses never cross this boundary. */ export interface IControllerProviderLogin { id: IControllerRuntimeId; verificationUrl: string; userCode: string; status: TControllerProviderLoginStatus; account?: IControllerProviderAccountSummary; } export interface IControllerModelRefreshJob { id: IControllerRuntimeId; providerConnectionId: string; status: TControllerModelRefreshJobStatus; modelCount?: number; } /** * Single source of the controller event vocabulary: the browser socket * client validates incoming events against this list at runtime, so a type * added here is automatically deliverable — the two cannot drift apart. */ export const controllerEventTypes = [ 'sessions.changed', 'session.changed', 'session.draft.changed', 'session.history.changed', 'session.tool.updated', 'session.reasoning.updated', 'session.reasoning.delta', 'session.text.updated', 'session.text.delta', 'permissions.changed', 'harness.changed', 'projects.changed', 'settings.changed', 'sessiongroups.changed', 'resources.changed', 'terminals.changed', 'upgrade.changed', 'accounts.changed', ] as const; export type TControllerEventType = (typeof controllerEventTypes)[number]; export interface IControllerEvent { type: TControllerEventType; harnessId?: TControllerSessionHarnessId; projectId?: string; sessionId?: IControllerRuntimeId; /** Authoritative status carried by harness session status/idle events. */ sessionStatus?: TControllerSessionStatus; /** Bounded native writer state carried by Codex session changes. */ codexActivity?: import('./codex.js').IControllerCodexActivitySummary; /** Literal marker carried only by a session.changed event from session.error. */ sessionError?: true; /** Present only on live OpenCode or Flex tool snapshots. */ toolExecution?: IControllerToolExecution; /** Present only on a live OpenCode or Flex reasoning snapshot. */ reasoningUpdate?: IControllerReasoningUpdate; /** Present only on an exact live OpenCode or Flex reasoning delta. */ reasoningDelta?: IControllerReasoningDelta; /** Present only on a live OpenCode or Flex text snapshot. */ textUpdate?: IControllerTextUpdate; /** Present only on an exact live OpenCode or Flex text delta. */ textDelta?: IControllerTextDelta; /** Present only on a session.draft.changed event. */ sessionDraftUpdate?: IControllerSessionDraftUpdate; /** Ordered harness stream barrier carried by harness.changed. */ toolStreamEpoch?: number; /** Ordered Assistant message stream barrier carried by harness.changed. */ messageStreamEpoch?: number; /** Sanitized upgrade metadata; present only on upgrade.changed. */ upgrade?: IControllerUpgradeStatus; timestamp: number; }