// Why: every RPC response needs the same runtimeId envelope, and the // runtime/browser error allowlists define the contract the CLI relies on to // format human-facing messages. Centralizing this mapping keeps the allowlist // auditable in one place instead of spread across per-method branches. import type { RpcEnvelopeMeta, RpcFailure, RpcSuccess } from './core' import { computerUseErrorRecoveryData } from '../../../shared/computer-use-error-recovery' import { COMPUTER_ERROR_CODES } from '../../../shared/runtime-types' import { LINEAR_ERROR_CODES } from '../../../shared/linear-agent-access' import { AGENT_SESSION_RPC_ERROR_CODES } from '../../../shared/agent-session-host-authority' export function successResponse(id: string, meta: RpcEnvelopeMeta, result: unknown): RpcSuccess { return { id, ok: true, result, _meta: meta } } export function errorResponse( id: string, meta: RpcEnvelopeMeta, code: string, message: string, data?: unknown ): RpcFailure { return { id, ok: false, error: data === undefined ? { code, message } : { code, message, data }, _meta: meta } } // Why: the OrcaRuntimeService throws plain Error objects whose `message` is // actually a stable error code. This allowlist is the contract the CLI relies // on — expanding or renaming entries without updating the CLI would silently // change user-visible error codes. const RUNTIME_PASSTHROUGH_CODES: ReadonlySet = new Set([ 'runtime_unavailable', 'selector_not_found', 'selector_ambiguous', 'terminal_handle_stale', 'terminal_not_writable', 'terminal_exited', 'terminal_gone', 'terminal_tab_close_timeout', 'terminal_tab_not_found', 'terminal_tab_pinned', 'no_active_terminal', 'repo_not_found', 'timeout', 'invalid_limit', 'remote_update_manual_required', 'remote_update_not_available', 'remote_update_not_downloaded', ...AGENT_SESSION_RPC_ERROR_CODES ]) const COMPUTER_PASSTHROUGH_CODES: ReadonlySet = new Set(Object.values(COMPUTER_ERROR_CODES)) const LINEAR_PASSTHROUGH_CODES: ReadonlySet = new Set(LINEAR_ERROR_CODES) const STRUCTURED_RUNTIME_PASSTHROUGH_CODES: ReadonlySet = new Set([ 'worktree_id_requires_full_path' ]) export function mapRuntimeError(id: string, meta: RpcEnvelopeMeta, error: unknown): RpcFailure { const message = error instanceof Error ? error.message : String(error) if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' && COMPUTER_PASSTHROUGH_CODES.has((error as { code: string }).code) ) { const code = (error as { code: string }).code return errorResponse(id, meta, code, message, computerErrorData(code)) } if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' && (error as { code: string }).code.startsWith('LINEAGE_') ) { return errorResponse( id, meta, (error as { code: string }).code, message, (error as { data?: unknown }).data ) } if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' && LINEAR_PASSTHROUGH_CODES.has((error as { code: string }).code) ) { return errorResponse( id, meta, (error as { code: string }).code, message, (error as { data?: unknown }).data ) } if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' && STRUCTURED_RUNTIME_PASSTHROUGH_CODES.has((error as { code: string }).code) ) { return errorResponse( id, meta, (error as { code: string }).code, message, (error as { data?: unknown }).data ) } if (RUNTIME_PASSTHROUGH_CODES.has(message)) { return errorResponse(id, meta, message, message) } if (message === 'invalid_terminal_send') { return errorResponse(id, meta, 'invalid_argument', 'Missing terminal send payload') } return errorResponse(id, meta, 'runtime_error', message) } export const computerErrorData = computerUseErrorRecoveryData // Why: browser errors carry a structured .code property (BrowserError from // cdp-bridge.ts) that maps directly to agent-facing error codes. We forward // that code rather than falling back to the runtime allowlist, because the // browser surface area uses its own code namespace (browser_no_tab, etc.). export function mapBrowserError(id: string, meta: RpcEnvelopeMeta, error: unknown): RpcFailure { if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' ) { return errorResponse(id, meta, (error as { code: string }).code, error.message) } return mapRuntimeError(id, meta, error) } // Why: same as browser — emulator errors (EmulatorError) carry .code (emulator_no_active etc.) // so we forward the structured code instead of generic runtime_error. export function mapEmulatorError(id: string, meta: RpcEnvelopeMeta, error: unknown): RpcFailure { if ( error instanceof Error && 'code' in error && typeof (error as { code: unknown }).code === 'string' ) { return errorResponse(id, meta, (error as { code: string }).code, error.message) } return mapRuntimeError(id, meta, error) }