// 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' import { ARTIFACT_SHARING_DISABLED_CODE } from '../../../shared/artifact-sharing-gate' import { AGENT_SKILL_SHARING_DISABLED_CODE } from '../../../shared/agent-skill-sharing-gate' import { AGENT_SKILL_NOT_SHAREABLE_CODE, AGENT_SKILL_SELECTOR_AMBIGUOUS_CODE, AGENT_SKILL_SELECTOR_NOT_FOUND_CODE, AGENT_SKILL_SHARING_BUSY_CODE, AGENT_SKILL_SHARING_UNSUPPORTED_ENVIRONMENT_CODE } from '../../../shared/agent-skill-sharing-contract' import { SKILL_INSTALL_RPC_ERROR_CODE, classifySkillInstallFailureCode } from '../../../shared/skill-install-failure' import { GIT_DIFF_TOO_LARGE_CODE } from '../../../shared/git-diff-transport-budget' import { AUTOMATION_OWNER_CONFLICT_CODES } from '../../../shared/automation-owner-conflict' 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', 'agent_prompt_blocked', 'agent_prompt_stalled', 'no_active_terminal', 'repo_not_found', 'timeout', 'invalid_limit', 'request_aborted', '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', 'run_not_found', 'run_required', 'stable_pane_required', 'consumer_fenced', 'task_not_found', 'task_not_startable', 'dispatch_not_found', 'dispatch_run_mismatch', 'terminal_not_found', 'recipient_ambiguous', 'recipient_run_mismatch', 'dispatch_inactive', 'worker_identity_changed', 'cursor_invalid', 'cursor_dispatch_mismatch', 'source_changed', 'transcript_required', 'server_required', 'worktree_not_found_on_server', 'resource_server_mismatch', 'peer_changed', 'remote_runtime_unavailable', 'runtime_timeout', 'invalid_runtime_response', 'capability_unsupported', 'relay_quota_exceeded', 'dispatch_capability_invalid', 'agent_unconfigured', 'terminal_worktree_mismatch', 'request_mismatch', 'mutation_ledger_full', 'legacy_read_only', 'orchestration_migration_required', 'operation_unknown', 'question_not_found', 'answer_conflict', 'stale_delivery', 'waiter_exists', 'invalid_argument', GIT_DIFF_TOO_LARGE_CODE, ARTIFACT_SHARING_DISABLED_CODE, AGENT_SKILL_SHARING_DISABLED_CODE, AGENT_SKILL_NOT_SHAREABLE_CODE, AGENT_SKILL_SELECTOR_AMBIGUOUS_CODE, AGENT_SKILL_SELECTOR_NOT_FOUND_CODE, AGENT_SKILL_SHARING_BUSY_CODE, AGENT_SKILL_SHARING_UNSUPPORTED_ENVIRONMENT_CODE, SKILL_INSTALL_RPC_ERROR_CODE, // Why: an owner conflict is a distinct client decision (reload the host, re-adopt, // stop offering the action) — flattened to runtime_error it can only be guessed at. ...Object.values(AUTOMATION_OWNER_CONFLICT_CODES) ]) 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, message)) } 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) } const skillInstallFailure = classifySkillInstallFailureCode(message) if (skillInstallFailure) { return errorResponse( id, meta, SKILL_INSTALL_RPC_ERROR_CODE, skillInstallFailure.code, skillInstallFailure ) } 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) }