From c93514db5bd14950878376b68e01b3ef459875ba Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:12:19 -0700 Subject: [PATCH] refactor(protocol): each area owns its published runtime capability list (#30641) * refactor(protocol): each area owns its published runtime capability list RUNTIME_CAPABILITIES now spreads one array per area instead of a flat hand-appended list, and runtime-capability-constants.ts is split into the areas that own each constant. The published set is unchanged and pinned by an exact sorted test. * test: preserve capability baseline while allowing additions --- ...e-structured-agent-session-host-support.ts | 2 +- mobile/src/tasks/mobile-tasks-capability.ts | 2 +- src/cli/handlers/project.ts | 2 +- src/cli/index-project-setup.test.ts | 2 +- ...session-continue-interrupted-capability.ts | 2 +- ...ent-session-permission-modes-capability.ts | 2 +- .../agent-session-runtime-capabilities.ts | 170 +++++++++ src/shared/ai-vault-runtime-capabilities.ts | 14 + src/shared/browser-runtime-capabilities.ts | 34 ++ src/shared/files-runtime-capabilities.ts | 13 + src/shared/host-runtime-capabilities.ts | 28 ++ src/shared/mobile-runtime-capabilities.ts | 10 + src/shared/preflight-runtime-capabilities.ts | 15 + ...col-version-published-capabilities.test.ts | 182 ++++++++++ src/shared/protocol-version.ts | 342 ++++++------------ src/shared/runtime-capability-constants.ts | 272 -------------- .../session-tabs-runtime-capabilities.ts | 21 ++ src/shared/session-tabs-tab-delta.test.ts | 2 +- src/shared/session-tabs-tab-delta.ts | 2 +- .../source-control-runtime-capabilities.ts | 16 + .../task-source-runtime-capabilities.ts | 23 ++ .../terminal-quick-command-capabilities.ts | 5 +- src/shared/terminal-runtime-capabilities.ts | 37 ++ .../workspace-ports-runtime-capabilities.ts | 9 + src/shared/worktree-runtime-capabilities.ts | 58 +++ 25 files changed, 743 insertions(+), 522 deletions(-) create mode 100644 src/shared/agent-session-runtime-capabilities.ts create mode 100644 src/shared/ai-vault-runtime-capabilities.ts create mode 100644 src/shared/browser-runtime-capabilities.ts create mode 100644 src/shared/files-runtime-capabilities.ts create mode 100644 src/shared/host-runtime-capabilities.ts create mode 100644 src/shared/mobile-runtime-capabilities.ts create mode 100644 src/shared/preflight-runtime-capabilities.ts create mode 100644 src/shared/protocol-version-published-capabilities.test.ts delete mode 100644 src/shared/runtime-capability-constants.ts create mode 100644 src/shared/source-control-runtime-capabilities.ts create mode 100644 src/shared/task-source-runtime-capabilities.ts create mode 100644 src/shared/terminal-runtime-capabilities.ts create mode 100644 src/shared/workspace-ports-runtime-capabilities.ts create mode 100644 src/shared/worktree-runtime-capabilities.ts diff --git a/mobile/src/session/mobile-structured-agent-session-host-support.ts b/mobile/src/session/mobile-structured-agent-session-host-support.ts index d3eb1f47e7c..5efe36061b4 100644 --- a/mobile/src/session/mobile-structured-agent-session-host-support.ts +++ b/mobile/src/session/mobile-structured-agent-session-host-support.ts @@ -12,7 +12,7 @@ import { } from '../../../src/shared/protocol-version' import { AGENT_SESSION_SEND_OPTIONS_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session-create-capabilities' import { AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session-permission-modes-capability' -import { AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY } from '../../../src/shared/runtime-capability-constants' +import { AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session-runtime-capabilities' /** Structured-session features the connected host advertised; null until the status probe answers. */ export type StructuredAgentSessionHostSupport = { diff --git a/mobile/src/tasks/mobile-tasks-capability.ts b/mobile/src/tasks/mobile-tasks-capability.ts index f8498bfd968..d8f6fe48fb5 100644 --- a/mobile/src/tasks/mobile-tasks-capability.ts +++ b/mobile/src/tasks/mobile-tasks-capability.ts @@ -1,5 +1,5 @@ // Runtime capability the desktop advertises when it supports the mobile Tasks RPC // surface (github/gitlab/linear work items + repo.searchRefs). Older paired // desktops omit it, so mobile must degrade to blank/new-branch sources only. -// Mirrors the 'mobile.tasks.v1' entry in src/shared/protocol-version.ts. +// Mirrors the 'mobile.tasks.v1' entry in src/shared/mobile-runtime-capabilities.ts. export const MOBILE_TASKS_CAPABILITY = 'mobile.tasks.v1' diff --git a/src/cli/handlers/project.ts b/src/cli/handlers/project.ts index cb155299d9a..1040add7904 100644 --- a/src/cli/handlers/project.ts +++ b/src/cli/handlers/project.ts @@ -13,7 +13,7 @@ import type { import { getSshTargetIdForExecutionHost, type ExecutionHostId } from '../../shared/execution-host' import type { RepoKind } from '../../shared/repo-types' import type { RuntimeStatus } from '../../shared/runtime-types' -import { PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY } from '../../shared/runtime-capability-constants' +import { PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY } from '../../shared/worktree-runtime-capabilities' import type { CommandHandler, HandlerContext } from '../dispatch' import { formatProjectHostSetupCreateResult, diff --git a/src/cli/index-project-setup.test.ts b/src/cli/index-project-setup.test.ts index f1bef94cdce..1bf8b8895c3 100644 --- a/src/cli/index-project-setup.test.ts +++ b/src/cli/index-project-setup.test.ts @@ -43,7 +43,7 @@ vi.mock('child_process', async () => { import { main } from './index' import { okFixture, queueFixtures } from './test-fixtures' -import { PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY } from '../shared/runtime-capability-constants' +import { PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY } from '../shared/worktree-runtime-capabilities' import { pairRuntimeEnvironment, useWorktreeAwarenessEnvironment } from './index-test-harness' const ORCA_PROJECT = { diff --git a/src/shared/agent-session-continue-interrupted-capability.ts b/src/shared/agent-session-continue-interrupted-capability.ts index 359e4c5ad12..791f1639372 100644 --- a/src/shared/agent-session-continue-interrupted-capability.ts +++ b/src/shared/agent-session-continue-interrupted-capability.ts @@ -1,4 +1,4 @@ -// Split out of protocol-version.ts, which lists it in RUNTIME_CAPABILITIES. Import it from here. +// Split out of protocol-version.ts; listed in AGENT_SESSION_RUNTIME_CAPABILITIES. Import it from here. // Why: a client may call `agentSession.continueInterrupted` (Continue on a reply an Orca stop cut // off) only on a host that has it; an older host also writes no row naming that stop, so a client diff --git a/src/shared/agent-session-permission-modes-capability.ts b/src/shared/agent-session-permission-modes-capability.ts index 2a54f37ff8f..da45e15bfc1 100644 --- a/src/shared/agent-session-permission-modes-capability.ts +++ b/src/shared/agent-session-permission-modes-capability.ts @@ -1,4 +1,4 @@ -// Split out of protocol-version.ts, which lists it in RUNTIME_CAPABILITIES. Import it from here. +// Split out of protocol-version.ts; listed in AGENT_SESSION_RUNTIME_CAPABILITIES. Import it from here. // Why: a host predating per-chat modes ignores them and runs its Agent Permissions, so a client // must know before it lets a chat there start or take a turn. 1.4.225–1.4.226 apply modes without diff --git a/src/shared/agent-session-runtime-capabilities.ts b/src/shared/agent-session-runtime-capabilities.ts new file mode 100644 index 00000000000..db2caf9990e --- /dev/null +++ b/src/shared/agent-session-runtime-capabilities.ts @@ -0,0 +1,170 @@ +import { AGENT_SESSION_ATTENTION_RUNTIME_CAPABILITIES } from './agent-session-attention-capabilities' +import { AGENT_SESSION_BACKGROUND_TASK_CHILD_VIEWS_CAPABILITY } from './agent-session-background-task-child-views-capability' +import { AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from './agent-session-continue-interrupted-capability' +import { AGENT_SESSION_CREATE_RUNTIME_CAPABILITIES } from './agent-session-create-capabilities' +import { AGENT_SESSION_CURRENT_REQUESTS_CLIENT_CAPABILITY } from './agent-session-current-requests-capability' +import { AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY } from './agent-session-permission-modes-capability' +import { AGENT_SESSION_RESUME_RUNTIME_CAPABILITIES } from './agent-session-resume-runtime-capabilities' +import { AGENT_SESSION_STOP_RUNTIME_CAPABILITIES } from './agent-session-stop-capabilities' +import { STRUCTURED_AGENT_SESSION_SURFACE_RUNTIME_CAPABILITIES } from './structured-agent-session-surface-capabilities' + +export const AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY = + 'agent-session.session-boundary.v1' as const +export const AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY = + 'agent-session.host-authority.v1' as const +// Older launch schemas reject unknown fields; advertise before clients send keyboard support. +export const AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY = 'agent-session.keyboard.v1' as const +export const AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY = + 'agent-session.omp-resume-path.v1' as const +// Why: structured sessions are journal-backed, not PTY-backed, so an incapable client must not +// receive their journal or drive their lifecycle. Mobile may receive a metadata-only placeholder; +// the host still refuses agentSession.* methods and destructive tab mutations without capability. +export const STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY = 'agent-session.structured.v1' as const +// Why: hosts before this gated `agentSession.*` on their own chat setting while still advertising +// agent-session.structured.v1, so that capability alone cannot tell a client its chat will be +// admitted. A host advertising this admits by client capability alone; a client advertising it +// picks each launch's mode itself. A client without it asks `agentSession.createSupport` to pick +// for it (released phones), so the host keeps answering that with its own setting. +export const STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY = + 'agent-session.structured.client-launch-mode.v1' as const +// Why: older structured clients render durable pending replies as uncertain delivery. Capable +// clients skip the host's bounded best-effort settlement observation. +export const AGENT_SESSION_PENDING_SEND_RESULT_RUNTIME_CAPABILITY = + 'agent-session.pending-send-result.v1' as const +// Why: a send is now answered once the host accepts it, before any agent has it. A client without +// this cannot show a message rejected after that answer, so the host holds its reply until the +// message is handed over or rejected. Transitional: drop the hold once no supported desktop or +// mobile client lacks the capability; mobile must first show a rejected message in place. +export const AGENT_SESSION_ACCEPTED_SEND_RUNTIME_CAPABILITY = + 'agent-session.accepted-send.v1' as const +// Why: a host advertising this answers a resent send id from its record before anything else may +// refuse it, so a refusal `agentSession.send` RETURNS is proof; a thrown error never is, a thrown +// refusal included (host not installed, journal database won't open, host disabled). Reading a +// returned `ok: false`: `agent_session_operation_unknown` with `outcomeUnknown` or `resultLost` — +// the host cannot tell yet, resend the same id; with `rewindUnconfirmed` — settled, nothing was +// written. `agent_session_operation_expired` — only the transcript can tell. An +// `agent_session_operation_conflict` or `messageIdReused` — the id holds a different payload, +// which proves nothing about this message; nor does `sessionNotAttached` (the chat's record is +// gone or unreadable on this host). Any other — the chat holds no message under that id and none +// is in flight, but a resend of that id may still run as a new send, so a client that hands the +// text back must not resend the old id. An older host may refuse an id it recorded: none of this +// holds there. +export const AGENT_SESSION_SEND_ANSWERS_PROOF_RUNTIME_CAPABILITY = + 'agent-session.send-answers-proof.v1' as const +// Why: `agentSession.send`'s params are strict, so an older host rejects `delivery`; and only a +// capable client can render the `queued` result arm. It gates `delivery: 'queue-if-active'` and +// the client's queueing setting and chord, never the published draft list or its card actions: a +// host without it still publishes a message it kept unsent across a restart or a close, and both +// clients show that card and call its actions. DARK ON PURPOSE — not in +// AGENT_SESSION_RUNTIME_CAPABILITIES: advertising still requires the integrated Codex steer matrix (#21062) in the shipped host. +// v1 includes `submission.queuedMessageId` on every draft hand-off: a client reads that link and +// never compares a draft id with a submission id. It also publishes the queue's pause once, as +// `queuePause` beside the list, lifted by `agentSession.queuedMessagesResume` or any turn sent +// after it starting; while it is set every waiting card waits. `nextQueuedMessageId` beside the list +// names the card the queue sends next once nothing runs, null while anything holds it; absent (an +// older host) reads as null. A card carries a hold of its own when its conversion failed +// (`send_failed`) or the host kept it unsent (`kept`). The host mechanism lands first; the constant +// gates the rollout. +export const AGENT_SESSION_QUEUED_MESSAGES_RUNTIME_CAPABILITY = + 'agent-session.queued-messages.v1' as const +// Why: a host advertising this edits a queued card in place (`agentSession.queuedMessageUpdate`) +// and holds it from automatic delivery while edited (`agentSession.queuedMessageEditHold`). Apart +// from queued-messages.v1: kept and agent-mail cards are editable with mid-turn queueing still off. +// A client offers Edit only when the host advertises it; there is no copy-and-delete fallback. +export const AGENT_SESSION_QUEUED_MESSAGE_EDIT_RUNTIME_CAPABILITY = + 'agent-session.queued-message-edit.v1' as const +// Why: `agentSession.queuedMessageUpdate`'s params are strict, so an older host rejects `images`. +// A host advertising this replaces a queued card's image set on Save; otherwise Save is text only. +export const AGENT_SESSION_QUEUED_MESSAGE_IMAGE_EDIT_RUNTIME_CAPABILITY = + 'agent-session.queued-message-image-edit.v1' as const +// Why: a client advertising this shows a queued card's images and edits its text in place, keeping +// them. Released readers show only a card's text and edit by copying it and deleting the card, which +// would drop the images, so the host publishes no card holding an image to a reader without it. +export const AGENT_SESSION_QUEUED_IMAGE_CARDS_CLIENT_CAPABILITY = + 'agent-session.queued-image-cards.v1' as const +// Why: `agentSession.conversationCommand`'s params are strict, so an older host rejects +// `delivery`. A host advertising this holds a /compact sent while the agent works as a queued +// card instead of refusing it. Clients ask only when queued-messages.v1 is advertised too: +// the card is the only place the waiting command shows. +export const AGENT_SESSION_QUEUED_COMMANDS_RUNTIME_CAPABILITY = + 'agent-session.queued-commands.v1' as const +// Why: paired clients advertise Claude-structured support so the host can gate its agent-specific +// journal and lifecycle surfaces independently from Codex support. +export const CLAUDE_STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY = + 'agent-session.structured.claude.v1' as const +// Why: agentSession.conversationOutline is additive; a client probes this before calling so an +// older host leaves the message rail on loaded messages instead of answering method_not_found. +export const AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY = + 'agent-session.conversation-outline.v1' as const +// Why: agentSession.item is additive; a client probes this before offering "Show full output" so an +// older host, whose rows keep only a head, is never asked for more. +export const AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY = 'agent-session.item-detail.v1' as const +// The RPC is registered unconditionally; per-session rewind support is a separate check. +export const AGENT_SESSION_REWIND_RUNTIME_CAPABILITY = 'agent-session.rewind.v1' as const +// Why: the rewind UI relies on the next send settling an in-doubt rewind, even with the agent up. +export const AGENT_SESSION_REWIND_RECOVERY_CAPABILITY = 'agent-session.rewind-recovery.v1' as const +// Readers must understand a monitoring roster with no available stop control. +// Why: a `turn` journal item replaced the status row that used to carry a turn's lifecycle. A +// client that predates it would render the unknown kind as text, so the host publishes the legacy +// status form to clients that do not advertise this. Transitional: drop the downgrade once no +// supported release lacks the capability. +export const AGENT_SESSION_TURN_ITEM_CAPABILITY = 'agent-session.turn-item.v1' as const +/** The client retires acknowledged echoes even when a catch-up snapshot skips their rows. */ +export const AGENT_SESSION_SNAPSHOT_CATCH_UP_CLIENT_CAPABILITY = + 'agent-session.snapshot-catch-up.v1' as const +export const AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY = + 'agent-session.background-task-stop.v1' as const +// Why: agentSession.respondToQuestion has a strict schema, so clients must not send structured +// `answers` to an older host; they fall back to the answer packed into `optionId`. +export const AGENT_SESSION_QUESTION_ANSWERS_RUNTIME_CAPABILITY = + 'agent-session.question-answers.v1' as const +// Why: a structured chat on a paired server stores attached files on that server +// (agentSessionAttachment.*); an older server has no store, so clients must refuse +// the attach rather than hand the agent a path from this machine. +export const AGENT_SESSION_ATTACHMENTS_RUNTIME_CAPABILITY = 'agent-session.attachments.v1' as const +// Why: hosts before this advertise attachments.v1 but refuse the upload methods from a phone. +export const AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY = + 'agent-session.attachments.phone.v1' as const +// Why: the host now publishes rows for work that is live inside a turn, and such +// a row carries `stoppable: false` because no targeted stop can reach it. A +// reader that predates the field draws a per-row Stop on every row it is given, +// so it must be told apart from one that honours the field — and NOT by the +// stop capability above, which a client can advertise while predating this. +export const AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY = + 'agent-session.background-task-row-stop.v1' as const + +export const AGENT_SESSION_RUNTIME_CAPABILITIES = [ + ...AGENT_SESSION_STOP_RUNTIME_CAPABILITIES, + ...AGENT_SESSION_CREATE_RUNTIME_CAPABILITIES, + AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY, + AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY, + AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY, + AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY, + AGENT_SESSION_CURRENT_REQUESTS_CLIENT_CAPABILITY, + STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY, + AGENT_SESSION_PENDING_SEND_RESULT_RUNTIME_CAPABILITY, + // The host side: it accepts a send before any agent has it, and a Stop with no writer before a + // turn starts, so a client may gate on either. + AGENT_SESSION_ACCEPTED_SEND_RUNTIME_CAPABILITY, + AGENT_SESSION_SEND_ANSWERS_PROOF_RUNTIME_CAPABILITY, + AGENT_SESSION_QUEUED_COMMANDS_RUNTIME_CAPABILITY, + AGENT_SESSION_QUEUED_MESSAGE_EDIT_RUNTIME_CAPABILITY, + AGENT_SESSION_QUEUED_MESSAGE_IMAGE_EDIT_RUNTIME_CAPABILITY, + AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY, + AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY, + ...STRUCTURED_AGENT_SESSION_SURFACE_RUNTIME_CAPABILITIES, + ...AGENT_SESSION_ATTENTION_RUNTIME_CAPABILITIES, + AGENT_SESSION_REWIND_RUNTIME_CAPABILITY, + AGENT_SESSION_REWIND_RECOVERY_CAPABILITY, + AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY, + AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY, + AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY, + AGENT_SESSION_QUESTION_ANSWERS_RUNTIME_CAPABILITY, + AGENT_SESSION_ATTACHMENTS_RUNTIME_CAPABILITY, + AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY, + AGENT_SESSION_TURN_ITEM_CAPABILITY, + AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY, + AGENT_SESSION_BACKGROUND_TASK_CHILD_VIEWS_CAPABILITY, + ...AGENT_SESSION_RESUME_RUNTIME_CAPABILITIES +] as const diff --git a/src/shared/ai-vault-runtime-capabilities.ts b/src/shared/ai-vault-runtime-capabilities.ts new file mode 100644 index 00000000000..78157a26b4e --- /dev/null +++ b/src/shared/ai-vault-runtime-capabilities.ts @@ -0,0 +1,14 @@ +import { AI_VAULT_CHAT_FORK_SOURCE_CAPABILITY } from './ai-vault-resume-preparation' + +// Why: signals the host exposes the Agent Session History scanner over RPC +// (aiVault.listSessions). Registered unconditionally for every build, so it is a +// STATIC capability advertised by getStatus() automatically — NOT a runtime +// conditional like browser.headless.v1. +export const AI_VAULT_RUNTIME_CAPABILITY = 'aiVault.v1' as const +export const AI_VAULT_SESSION_TITLES_RUNTIME_CAPABILITY = 'aiVault.session-titles.v1' as const + +export const AI_VAULT_RUNTIME_CAPABILITIES = [ + AI_VAULT_RUNTIME_CAPABILITY, + AI_VAULT_SESSION_TITLES_RUNTIME_CAPABILITY, + AI_VAULT_CHAT_FORK_SOURCE_CAPABILITY +] as const diff --git a/src/shared/browser-runtime-capabilities.ts b/src/shared/browser-runtime-capabilities.ts new file mode 100644 index 00000000000..fb122936697 --- /dev/null +++ b/src/shared/browser-runtime-capabilities.ts @@ -0,0 +1,34 @@ +// Why: signals a host owns browser pages with no renderer (headless serve via the +// offscreen backend). Advertised only when that backend is actually available, so +// clients never fall back to a local desktop browser tab for a remote-owned page. +export const BROWSER_HEADLESS_RUNTIME_CAPABILITY = 'browser.headless.v1' as const +export const BROWSER_IDENTITY_RUNTIME_CAPABILITY = 'browser.identity.v1' as const +export const BROWSER_SCREENCAST_RUNTIME_CAPABILITY = 'browser.screencast.v1' as const +export const BROWSER_CERTIFICATE_TRUST_RUNTIME_CAPABILITY = 'browser.certificate-trust.v1' as const +// Why: older hosts discard browser.tabCreate's page field, so clients may only +// treat a preallocated page ID as canonical when this is advertised. +export const BROWSER_TAB_CREATE_KNOWN_ID_RUNTIME_CAPABILITY = + 'browser.tab-create-known-id.v1' as const +export const BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY = 'browser.clientHost.v1' as const +export const BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY = + 'browser.clientHost.pageMetadata.v1' as const +export const BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY = + 'browser.clientHost.automation.v1' as const +// Why: without it a client-placed browser.upload would resolve remote paths on the desktop filesystem, so uploads fail closed instead. +export const BROWSER_CLIENT_FILE_CHANNEL_RUNTIME_CAPABILITY = + 'browser.clientHost.fileChannel.v1' as const +export const BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY = 'network.browserTunnel.v1' as const +export const BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY = + 'network.browserTunnel.executionHosts.v1' as const + +// Headless, identity and certificate trust are host-conditional, so getStatus adds them itself. +export const BROWSER_RUNTIME_CAPABILITIES = [ + BROWSER_SCREENCAST_RUNTIME_CAPABILITY, + BROWSER_TAB_CREATE_KNOWN_ID_RUNTIME_CAPABILITY, + BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY, + BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY, + BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY, + BROWSER_CLIENT_FILE_CHANNEL_RUNTIME_CAPABILITY, + BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY, + BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/files-runtime-capabilities.ts b/src/shared/files-runtime-capabilities.ts new file mode 100644 index 00000000000..cae75d98bc8 --- /dev/null +++ b/src/shared/files-runtime-capabilities.ts @@ -0,0 +1,13 @@ +import { FILE_WRITE_DISK_PRECONDITION_RUNTIME_CAPABILITY } from './file-write-disk-precondition' + +// Why: older runtimes strip mutation owner fields, so clients must fence writes before RPC. +export const FILE_MUTATION_OWNERSHIP_RUNTIME_CAPABILITY = 'files.mutation-ownership.v1' as const +export const FILE_MUTATION_OWNERSHIP_UPDATE_REQUIRED_MESSAGE = + 'Remote file changes require a newer Orca server. Update the HUB and try again.' + +export const FILES_RUNTIME_CAPABILITIES = [ + 'files.pathsExist', + 'files.resolve-terminal-path.cross-workspace.v1', + FILE_WRITE_DISK_PRECONDITION_RUNTIME_CAPABILITY, + FILE_MUTATION_OWNERSHIP_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/host-runtime-capabilities.ts b/src/shared/host-runtime-capabilities.ts new file mode 100644 index 00000000000..debd2b0d710 --- /dev/null +++ b/src/shared/host-runtime-capabilities.ts @@ -0,0 +1,28 @@ +import { + MANAGED_RUNTIME_INSTALL_RUNTIME_CAPABILITY, + MANAGED_RUNTIME_REMOVE_RUNTIME_CAPABILITY +} from './managed-runtime-install' +import { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update' +import { SSH_TARGET_MANAGEMENT_RUNTIME_CAPABILITY } from './ssh-target-management' + +export const REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY = 'remote-runtime.shared-control.v1' as const +// Why conditional: only a desktop runtime holds the SSH registry managed servers are run from. +export const MANAGED_SERVER_RUNTIME_CAPABILITY = 'managedServer.v1' as const +// Why: clients offer "Resume automatic restores" only on hosts that can clear a recorded pause. +export const RUNTIME_AUTOMATIC_WORK_RUNTIME_CAPABILITY = 'runtime.automatic-work.v1' as const +// Hosts without this capability ignore a request's expectedRuntimeSource; the CLI's pre-send probe +// is then the only fence. +export const RUNTIME_SOURCE_FENCE_RUNTIME_CAPABILITY = 'runtime-source-fence.v1' as const + +// The paired host itself: status, environments, control, updates and the servers it manages. +export const HOST_RUNTIME_CAPABILITIES = [ + 'runtime.status.compat.v1', + 'runtime.environments.v1', + REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY, + RUNTIME_AUTOMATIC_WORK_RUNTIME_CAPABILITY, + REMOTE_SERVER_UPDATE_CAPABILITY, + MANAGED_RUNTIME_INSTALL_RUNTIME_CAPABILITY, + MANAGED_RUNTIME_REMOVE_RUNTIME_CAPABILITY, + RUNTIME_SOURCE_FENCE_RUNTIME_CAPABILITY, + SSH_TARGET_MANAGEMENT_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/mobile-runtime-capabilities.ts b/src/shared/mobile-runtime-capabilities.ts new file mode 100644 index 00000000000..34ce8f765e9 --- /dev/null +++ b/src/shared/mobile-runtime-capabilities.ts @@ -0,0 +1,10 @@ +import { MOBILE_RELAY_RUNTIME_CAPABILITIES } from './mobile-desktop-relay-contract' + +// Hosts without this capability have no notifications.registerPush RPC. +export const NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY = 'notifications.remote-push.v1' as const + +export const MOBILE_RUNTIME_CAPABILITIES = [ + 'mobile.tasks.v1', + NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY, + ...MOBILE_RELAY_RUNTIME_CAPABILITIES +] as const diff --git a/src/shared/preflight-runtime-capabilities.ts b/src/shared/preflight-runtime-capabilities.ts new file mode 100644 index 00000000000..877a664c349 --- /dev/null +++ b/src/shared/preflight-runtime-capabilities.ts @@ -0,0 +1,15 @@ +// Why: older hosts discard preflight.detectAgents params and probe their own PATH, so a client +// must not read that answer as the workspace's (a WSL project on a Windows host differs). +export const PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY = + 'preflight.workspace-scoped.v1' as const +// Why: an older host answers its own agents for a workspace another runtime owns; one with this +// refuses with WORKSPACE_ON_OTHER_RUNTIME, so a client may let it decide for a shared repo id. +export const PREFLIGHT_OTHER_RUNTIME_REFUSAL_RUNTIME_CAPABILITY = + 'preflight.other-runtime-refusal.v1' as const +/** What such a host answers, instead of probing itself, for another runtime's workspace. */ +export const WORKSPACE_ON_OTHER_RUNTIME = 'workspace_on_other_runtime' + +export const PREFLIGHT_RUNTIME_CAPABILITIES = [ + PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY, + PREFLIGHT_OTHER_RUNTIME_REFUSAL_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/protocol-version-published-capabilities.test.ts b/src/shared/protocol-version-published-capabilities.test.ts new file mode 100644 index 00000000000..a305406ca28 --- /dev/null +++ b/src/shared/protocol-version-published-capabilities.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it } from 'vitest' +import { RUNTIME_CAPABILITIES } from './protocol-version' + +// Frozen baseline: additions need no edit; remove a name only when deliberately retiring support. +const PUBLISHED_RUNTIME_CAPABILITIES = [ + 'accounts.antigravity-chat-sign-in.v1', + 'accounts.antigravity-native.v1', + 'accounts.claude-sign-in.v1', + 'accounts.codex-reset-credit.v1', + 'accounts.host-launch-env.v1', + 'accounts.import-host-credentials.v1', + 'accounts.managed-data-profiles.v1', + 'agent-session.accepted-send.v1', + 'agent-session.account-env.v1', + 'agent-session.attachments.phone.v1', + 'agent-session.attachments.v1', + 'agent-session.attention-ack.v1', + 'agent-session.background-task-child-views.v1', + 'agent-session.background-task-row-stop.v1', + 'agent-session.background-task-stop.v1', + 'agent-session.codebuddy-resume.v1', + 'agent-session.continue-interrupted.v1', + 'agent-session.conversation-outline.v1', + 'agent-session.conversation-stop.v1', + 'agent-session.current-requests.v1', + 'agent-session.cursor-resume.v1', + 'agent-session.dsh-resume.v1', + 'agent-session.host-authority.v1', + 'agent-session.keyboard.v1', + 'agent-session.kimi-resume.v1', + 'agent-session.kiro-resume.v1', + 'agent-session.model-catalog.saved-only.v1', + 'agent-session.muse-resume.v1', + 'agent-session.omp-resume-path.v1', + 'agent-session.opencode2-resume.v1', + 'agent-session.pending-send-result.v1', + 'agent-session.permission-modes.v1', + 'agent-session.prompt-cancel.v1', + 'agent-session.qoder-cn-resume.v1', + 'agent-session.qoder-resume.v1', + 'agent-session.question-answers.v1', + 'agent-session.queued-commands.v1', + 'agent-session.queued-message-edit.v1', + 'agent-session.qwen-code-resume.v1', + 'agent-session.repeated-stop.v1', + 'agent-session.rewind-recovery.v1', + 'agent-session.rewind.v1', + 'agent-session.send-answers-proof.v1', + 'agent-session.send-options.v1', + 'agent-session.session-boundary.v1', + 'agent-session.status-feed.v1', + 'agent-session.structured.availability.v1', + 'agent-session.structured.cli-source.v1', + 'agent-session.structured.client-launch-mode.v1', + 'agent-session.structured.hold.v1', + 'agent-session.structured.pi-dialogs.v1', + 'agent-session.structured.registered-agents.v1', + 'agent-session.structured.resume-history.v1', + 'agent-session.structured.reveal.v1', + 'agent-session.structured.v1', + 'agent-session.turn-completion.v1', + 'agent-session.turn-item.v1', + 'agent-session.zcode-resume.v1', + 'agent.launch.create-folder-workspace.v1', + 'agent.launch.local-presentation.v1', + 'agent.launch.placement.v1', + 'agent.launch.prompt-carry.v1', + 'agent.launch.prompt-unconfirmed.v1', + 'agent.launch.replay-required.v1', + 'agent.launch.replay.v1', + 'agent.launch.v2', + 'agentSession.create.message.v1', + 'agentSession.create.tab-id.v1', + 'ai-vault.chat-fork-source.v1', + 'aiVault.session-titles.v1', + 'aiVault.v1', + 'automation.create-idempotency.v1', + 'automation.list-host-scope.v1', + 'automation.owner-fencing.v1', + 'automations.extra-agent-args.v1', + 'browser.clientHost.automation.v1', + 'browser.clientHost.fileChannel.v1', + 'browser.clientHost.pageMetadata.v1', + 'browser.clientHost.v1', + 'browser.screencast.v1', + 'browser.tab-create-known-id.v1', + 'files.mutation-ownership.v1', + 'files.pathsExist', + 'files.resolve-terminal-path.cross-workspace.v1', + 'folder-workspace.path-status.v1', + 'git.antigravity-configured-model.v1', + 'github.markPRReadyForReview', + 'gitlab.updateMR.readyForReview.v1', + 'jira.agent-access.v1', + 'jira.attachment-images.v1', + 'jira.current-sprint.v1', + 'jira.issue-list-statuses.v1', + 'jira.issue-summary-generation.v1', + 'jira.saved-filter-state.v1', + 'jira.transition-fields.v1', + 'jira.user-fields.v1', + 'linear.issue-attribute-filter.v1', + 'managed-runtime.install.v1', + 'managed-runtime.remove.v1', + 'mobile.desktop-relay.v1', + 'mobile.tasks.v1', + 'network.browserTunnel.executionHosts.v1', + 'network.browserTunnel.v1', + 'notifications.remote-push.v1', + 'orcad.migration-catalog.v1', + 'orcad.terminal-census.v1', + 'orchestration.contract.v1', + 'orchestration.federation-control-mail.v1', + 'orchestration.federation-fleet-snapshot.v1', + 'orchestration.federation-lifecycle-settlement.v1', + 'orchestration.federation-release-archive.v1', + 'orchestration.federation-structured-read.v1', + 'orchestration.federation.v1', + 'orchestration.worker-launch-preferences.v1', + 'orchestration.worker-stop-verdict.v1', + 'pairing.delegated-mobile-devices.v1', + 'pairing.device-key-ephemeral.v1', + 'pairing.device-key.v1', + 'preflight.other-runtime-refusal.v1', + 'preflight.workspace-scoped.v1', + 'project-host-setup.v1', + 'remote-runtime.shared-control.v1', + 'repo.search-qualified-refs.v1', + 'runtime-source-fence.v1', + 'runtime.automatic-work.v1', + 'runtime.environments.v1', + 'runtime.status.compat.v1', + 'session-tabs.authoritative-inventory.v1', + 'session-tabs.close-intent.v1', + 'session-tabs.create-terminal-shell.v1', + 'session-tabs.empty-worktree-answer.v1', + 'session-tabs.split-group-placement.v1', + 'session.tabs.chat-fork-cli.v1', + 'session.tabs.qoderOwnedCreate.v1', + 'settings.agent-launch.v1', + 'skills.delete.v1', + 'skills.install-cancel.v1', + 'skills.install-progress.v1', + 'skills.install-providers.v1', + 'skills.install-result.v2', + 'skills.install.bundle.v1', + 'skills.install.v1', + 'skills.manage.v1', + 'skills.upload.v1', + 'ssh.target-management.v1', + 'task-source-context.v1', + 'terminal.binary-stream.v1', + 'terminal.create-idempotency.v2', + 'terminal.create-shell-selection.v1', + 'terminal.multiplex.v1', + 'terminal.paired-parking.v1', + 'terminal.program-status.v1', + 'terminal.prompt-delivery.v1', + 'terminal.query-reply-input.v1', + 'terminal.quick-commands.long-prompts.v1', + 'terminal.quick-commands.v1', + 'terminal.spawn-intent.v1', + 'updater.remote-control.v1', + 'workspace-ports.host-scoped.v1', + 'workspace-ports.v1', + 'workspace-run-context.v1', + 'worktree.archive-failure-blocking.v1', + 'worktree.create-idempotency.v1', + 'worktree.github-pr-suppression.v1', + 'worktree.linked-items-delta.v1', + 'worktree.linked-items.v1', + 'worktree.linked-work-item-context.v1', + 'worktree.provisioned-root.v1', + 'worktree.visibility-defaults.v1', + 'worktree.visibility-source-defaults.v1' +] + +describe('RUNTIME_CAPABILITIES', () => { + it('preserves every previously published capability', () => { + expect(RUNTIME_CAPABILITIES).toEqual(expect.arrayContaining(PUBLISHED_RUNTIME_CAPABILITIES)) + }) +}) diff --git a/src/shared/protocol-version.ts b/src/shared/protocol-version.ts index 46d963ef281..fff76fcd1ea 100644 --- a/src/shared/protocol-version.ts +++ b/src/shared/protocol-version.ts @@ -1,147 +1,38 @@ -import { AGENT_SESSION_CURRENT_REQUESTS_CLIENT_CAPABILITY } from './agent-session-current-requests-capability' -import { AI_VAULT_CHAT_FORK_SOURCE_CAPABILITY } from './ai-vault-resume-preparation' -import { PROGRAM_STATUS_RUNTIME_CAPABILITY } from './program-status/protocol' -import { JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY } from './jira-agent-capability' -import { JIRA_STATUS_FILTER_RUNTIME_CAPABILITY } from './jira-quick-filters' -import { JIRA_SAVED_FILTER_RUNTIME_CAPABILITY } from './jira-saved-filter-capability' -import * as jiraDetailCapabilities from './jira-detail-capabilities' -export * from './jira-detail-capabilities' -import { WORKSPACE_ATTACHMENT_RUNTIME_CAPABILITIES } from './workspace-attachment-capabilities' -import { SSH_TARGET_MANAGEMENT_RUNTIME_CAPABILITY } from './ssh-target-management' +import { ACCOUNT_RUNTIME_CAPABILITIES } from './account-runtime-capabilities' +import * as agentLaunchCapabilities from './agent-launch-runtime-capability' +import { + AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY, + AGENT_SESSION_RUNTIME_CAPABILITIES +} from './agent-session-runtime-capabilities' +import { AI_VAULT_RUNTIME_CAPABILITIES } from './ai-vault-runtime-capabilities' import { AUTOMATION_RUNTIME_CAPABILITIES, AUTOMATION_RUNTIME_CLIENT_CAPABILITIES } from './automation-runtime-capabilities' -export { - AUTOMATION_LIST_HOST_SCOPE_RUNTIME_CAPABILITY, - AUTOMATION_LIST_HOST_SCOPE_UPDATE_REQUIRED_MESSAGE, - AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY, - AUTOMATION_OWNER_FENCING_UPDATE_REQUIRED_MESSAGE, - AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, - AUTOMATION_EXTRA_AGENT_ARGS_RUNTIME_CAPABILITY -} from './automation-runtime-capabilities' -import { STRUCTURED_AGENT_SESSION_SURFACE_RUNTIME_CAPABILITIES } from './structured-agent-session-surface-capabilities' -export { - STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY, - PI_STRUCTURED_DIALOGS_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_RESUME_HISTORY_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_CLI_SOURCE_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_AVAILABILITY_RUNTIME_CAPABILITY -} from './structured-agent-session-surface-capabilities' -import { AGENT_SESSION_ATTENTION_RUNTIME_CAPABILITIES } from './agent-session-attention-capabilities' -export { - AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY, - AGENT_SESSION_TURN_COMPLETION_RUNTIME_CAPABILITY, - AGENT_SESSION_ATTENTION_ACK_RUNTIME_CAPABILITY -} from './agent-session-attention-capabilities' -import { ORCHESTRATION_RUNTIME_CAPABILITIES } from './orchestration-runtime-capabilities' -export { - ORCHESTRATION_FEDERATION_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_CONTROL_MAIL_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_LIFECYCLE_SETTLEMENT_RUNTIME_CAPABILITY, - ORCHESTRATION_WORKER_STOP_VERDICT_RUNTIME_CAPABILITY, - ORCHESTRATION_WORKER_LAUNCH_PREFERENCES_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_STRUCTURED_READ_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_FLEET_SNAPSHOT_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_RELEASE_ARCHIVE_RUNTIME_CAPABILITY, - ORCHESTRATION_FEDERATION_CONTROL_MAIL_PROTOCOL_VERSION, - ORCHESTRATION_FEDERATION_LIFECYCLE_SETTLEMENT_PROTOCOL_VERSION, - ORCHESTRATION_CONTRACT_VERSION, - ORCHESTRATION_CONTRACT_RUNTIME_CAPABILITY -} from './orchestration-runtime-capabilities' -import { AGENT_SESSION_RESUME_RUNTIME_CAPABILITIES } from './agent-session-resume-runtime-capabilities' -import { QODER_OWNED_TERMINAL_CREATE_CAPABILITY } from './qoder-terminal-create-capability' -import { SESSION_TABS_RUNTIME_CAPABILITIES } from './session-tabs-runtime-capabilities' -export { SESSION_TABS_SPLIT_GROUP_PLACEMENT_RUNTIME_CAPABILITY } from './session-tabs-runtime-capabilities' -import { AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from './agent-session-continue-interrupted-capability' -import { AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY } from './agent-session-permission-modes-capability' -import { ORCAD_RUNTIME_CAPABILITIES } from './orcad-runtime-capabilities' +import { BROWSER_RUNTIME_CAPABILITIES } from './browser-runtime-capabilities' import { DEVICE_KEY_RUNTIME_CAPABILITIES } from './device-key-capability' -import { TERMINAL_QUICK_COMMAND_RUNTIME_CAPABILITIES } from './terminal-quick-command-capabilities' -import { FILE_WRITE_DISK_PRECONDITION_RUNTIME_CAPABILITY } from './file-write-disk-precondition' -// Why a star: every resume capability is part of the runtime contract this module re-exports. -export * from './agent-session-resume-runtime-capabilities' -import { AGENT_SESSION_BACKGROUND_TASK_CHILD_VIEWS_CAPABILITY } from './agent-session-background-task-child-views-capability' -import { AGENT_SESSION_STOP_RUNTIME_CAPABILITIES } from './agent-session-stop-capabilities' -import { AGENT_SESSION_CREATE_RUNTIME_CAPABILITIES } from './agent-session-create-capabilities' +import { FILES_RUNTIME_CAPABILITIES } from './files-runtime-capabilities' +import { HOST_RUNTIME_CAPABILITIES } from './host-runtime-capabilities' +import { MOBILE_RUNTIME_CAPABILITIES } from './mobile-runtime-capabilities' +import { ORCAD_RUNTIME_CAPABILITIES } from './orcad-runtime-capabilities' +import { ORCHESTRATION_RUNTIME_CAPABILITIES } from './orchestration-runtime-capabilities' +import { PREFLIGHT_RUNTIME_CAPABILITIES } from './preflight-runtime-capabilities' import { - MANAGED_RUNTIME_INSTALL_RUNTIME_CAPABILITY, - MANAGED_RUNTIME_REMOVE_RUNTIME_CAPABILITY -} from './managed-runtime-install' -import { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update' -import { MOBILE_RELAY_RUNTIME_CAPABILITIES } from './mobile-desktop-relay-contract' -import { SKILL_RUNTIME_CAPABILITIES } from './skill-install-capability' -import { ACCOUNT_RUNTIME_CAPABILITIES } from './account-runtime-capabilities' -import * as agentLaunchCapabilities from './agent-launch-runtime-capability' -import { - AGENT_SESSION_ACCEPTED_SEND_RUNTIME_CAPABILITY, - AGENT_SESSION_ATTACHMENTS_RUNTIME_CAPABILITY, - AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY, - AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY, - AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY, - AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY, - AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY, - AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY, - AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY, - AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY, - AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY, - AGENT_SESSION_PENDING_SEND_RESULT_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_COMMANDS_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_MESSAGE_EDIT_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_MESSAGE_IMAGE_EDIT_RUNTIME_CAPABILITY, - AGENT_SESSION_QUESTION_ANSWERS_RUNTIME_CAPABILITY, - AGENT_SESSION_REWIND_RECOVERY_CAPABILITY, - AGENT_SESSION_REWIND_RUNTIME_CAPABILITY, - AGENT_SESSION_SEND_ANSWERS_PROOF_RUNTIME_CAPABILITY, - AGENT_SESSION_TURN_ITEM_CAPABILITY, - AI_VAULT_RUNTIME_CAPABILITY, - AI_VAULT_SESSION_TITLES_RUNTIME_CAPABILITY, - ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY, - BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY, - BROWSER_CLIENT_FILE_CHANNEL_RUNTIME_CAPABILITY, - BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY, - BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY, - BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY, - BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY, - BROWSER_SCREENCAST_RUNTIME_CAPABILITY, - BROWSER_TAB_CREATE_KNOWN_ID_RUNTIME_CAPABILITY, - FILE_MUTATION_OWNERSHIP_RUNTIME_CAPABILITY, - FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY, - GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY, - GITLAB_READY_FOR_REVIEW_RUNTIME_CAPABILITY, - LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY, - NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY, - PREFLIGHT_OTHER_RUNTIME_REFUSAL_RUNTIME_CAPABILITY, - PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY, - PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY, - PROJECT_HOST_SETUP_RUNTIME_CAPABILITY, - REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY, - REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY, - RUNTIME_AUTOMATIC_WORK_RUNTIME_CAPABILITY, - RUNTIME_SOURCE_FENCE_RUNTIME_CAPABILITY, SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY, - SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY, - STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY, - TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY, - TERMINAL_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, - TERMINAL_CREATE_SHELL_SELECTION_RUNTIME_CAPABILITY, - TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY, - TERMINAL_PROMPT_DELIVERY_RUNTIME_CAPABILITY, - TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY, - TERMINAL_SPAWN_INTENT_RUNTIME_CAPABILITY, - WORKSPACE_PORTS_HOST_SCOPED_RUNTIME_CAPABILITY, - WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY, - WORKTREE_ARCHIVE_FAILURE_BLOCKING_RUNTIME_CAPABILITY, - WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, + SESSION_TABS_RUNTIME_CAPABILITIES +} from './session-tabs-runtime-capabilities' +import { SKILL_RUNTIME_CAPABILITIES } from './skill-install-capability' +import { SOURCE_CONTROL_RUNTIME_CAPABILITIES } from './source-control-runtime-capabilities' +import { TASK_SOURCE_RUNTIME_CAPABILITIES } from './task-source-runtime-capabilities' +import { TERMINAL_RUNTIME_CAPABILITIES } from './terminal-runtime-capabilities' +import { WORKSPACE_PORTS_RUNTIME_CAPABILITIES } from './workspace-ports-runtime-capabilities' +import { + REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY, WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY, - WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY, - WORKTREE_PROVISIONED_ROOT_RUNTIME_CAPABILITY, + WORKTREE_RUNTIME_CAPABILITIES, WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY, WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY -} from './runtime-capability-constants' +} from './worktree-runtime-capabilities' // Why: declares the Orca runtime RPC compatibility contract. Desktop, // headless server, CLI, and mobile builds may drift in app version, but @@ -166,9 +57,68 @@ export const RUNTIME_PROTOCOL_VERSION = 3 export const MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION = 2 export const MIN_COMPATIBLE_RUNTIME_SERVER_VERSION = 2 -export * from './runtime-capability-constants' -export { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update' +// Why stars: importers keep reading every capability from here while each area owns its module. +export * from './agent-session-runtime-capabilities' +export * from './agent-session-resume-runtime-capabilities' export * from './agent-session-stop-capabilities' +export * from './ai-vault-runtime-capabilities' +export * from './browser-runtime-capabilities' +export * from './files-runtime-capabilities' +export * from './host-runtime-capabilities' +export * from './jira-detail-capabilities' +export * from './mobile-runtime-capabilities' +export * from './preflight-runtime-capabilities' +export * from './source-control-runtime-capabilities' +export * from './task-source-runtime-capabilities' +export * from './terminal-runtime-capabilities' +export * from './workspace-ports-runtime-capabilities' +export * from './worktree-runtime-capabilities' +export { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update' +export { TERMINAL_QUICK_COMMANDS_RUNTIME_CAPABILITY } from './terminal-quick-command-capabilities' +export { + AUTOMATION_LIST_HOST_SCOPE_RUNTIME_CAPABILITY, + AUTOMATION_LIST_HOST_SCOPE_UPDATE_REQUIRED_MESSAGE, + AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY, + AUTOMATION_OWNER_FENCING_UPDATE_REQUIRED_MESSAGE, + AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, + AUTOMATION_EXTRA_AGENT_ARGS_RUNTIME_CAPABILITY +} from './automation-runtime-capabilities' +export { + STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY, + PI_STRUCTURED_DIALOGS_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_RESUME_HISTORY_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_CLI_SOURCE_RUNTIME_CAPABILITY, + STRUCTURED_AGENT_SESSION_AVAILABILITY_RUNTIME_CAPABILITY +} from './structured-agent-session-surface-capabilities' +export { + AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY, + AGENT_SESSION_TURN_COMPLETION_RUNTIME_CAPABILITY, + AGENT_SESSION_ATTENTION_ACK_RUNTIME_CAPABILITY +} from './agent-session-attention-capabilities' +export { + ORCHESTRATION_FEDERATION_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_CONTROL_MAIL_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_LIFECYCLE_SETTLEMENT_RUNTIME_CAPABILITY, + ORCHESTRATION_WORKER_STOP_VERDICT_RUNTIME_CAPABILITY, + ORCHESTRATION_WORKER_LAUNCH_PREFERENCES_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_STRUCTURED_READ_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_FLEET_SNAPSHOT_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_RELEASE_ARCHIVE_RUNTIME_CAPABILITY, + ORCHESTRATION_FEDERATION_CONTROL_MAIL_PROTOCOL_VERSION, + ORCHESTRATION_FEDERATION_LIFECYCLE_SETTLEMENT_PROTOCOL_VERSION, + ORCHESTRATION_CONTRACT_VERSION, + ORCHESTRATION_CONTRACT_RUNTIME_CAPABILITY +} from './orchestration-runtime-capabilities' +export { + AGENT_STATUS_RESTORED_UNCONFIRMED_RUNTIME_CAPABILITY, + SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY, + SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY, + SESSION_TABS_RETIREMENT_PROOF_DELTA_RUNTIME_CAPABILITY, + SESSION_TABS_SPLIT_GROUP_PLACEMENT_RUNTIME_CAPABILITY, + SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY +} from './session-tabs-runtime-capabilities' // Native clients include the CLI, which cannot place Electron browser pages. export const NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES = [ @@ -182,116 +132,28 @@ export const NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES = [ agentLaunchCapabilities.AGENT_LAUNCH_RUNTIME_CAPABILITY ] as const +// Each area lists what it publishes in its own module; a new capability edits only that module. export const RUNTIME_CAPABILITIES = [ - AI_VAULT_CHAT_FORK_SOURCE_CAPABILITY, - jiraDetailCapabilities.JIRA_TRANSITION_FIELDS_RUNTIME_CAPABILITY, - JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY, - jiraDetailCapabilities.JIRA_CURRENT_SPRINT_RUNTIME_CAPABILITY, - ...ORCAD_RUNTIME_CAPABILITIES, + ...HOST_RUNTIME_CAPABILITIES, ...DEVICE_KEY_RUNTIME_CAPABILITIES, - QODER_OWNED_TERMINAL_CREATE_CAPABILITY, - ...AGENT_SESSION_STOP_RUNTIME_CAPABILITIES, - ...AGENT_SESSION_CREATE_RUNTIME_CAPABILITIES, - ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY, - 'files.pathsExist', - 'files.resolve-terminal-path.cross-workspace.v1', - FILE_WRITE_DISK_PRECONDITION_RUNTIME_CAPABILITY, - 'runtime.status.compat.v1', - 'runtime.environments.v1', - REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY, + ...MOBILE_RUNTIME_CAPABILITIES, + ...ORCAD_RUNTIME_CAPABILITIES, ...ORCHESTRATION_RUNTIME_CAPABILITIES, - BROWSER_SCREENCAST_RUNTIME_CAPABILITY, - BROWSER_TAB_CREATE_KNOWN_ID_RUNTIME_CAPABILITY, - BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY, - BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY, - BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY, - BROWSER_CLIENT_FILE_CHANNEL_RUNTIME_CAPABILITY, - BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY, - BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY, - 'terminal.binary-stream.v1', - PROGRAM_STATUS_RUNTIME_CAPABILITY, - 'terminal.multiplex.v1', - 'workspace-ports.v1', - 'mobile.tasks.v1', - PROJECT_HOST_SETUP_RUNTIME_CAPABILITY, - PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY, - TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY, - WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY, - WORKTREE_PROVISIONED_ROOT_RUNTIME_CAPABILITY, - WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY, - ...WORKSPACE_ATTACHMENT_RUNTIME_CAPABILITIES, - WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY, - REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY, - FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY, - LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY, - ...jiraDetailCapabilities.JIRA_DETAIL_RUNTIME_CAPABILITIES, - JIRA_SAVED_FILTER_RUNTIME_CAPABILITY, - JIRA_STATUS_FILTER_RUNTIME_CAPABILITY, - AI_VAULT_RUNTIME_CAPABILITY, - AI_VAULT_SESSION_TITLES_RUNTIME_CAPABILITY, - TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY, - TERMINAL_PROMPT_DELIVERY_RUNTIME_CAPABILITY, - TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY, - TERMINAL_SPAWN_INTENT_RUNTIME_CAPABILITY, - RUNTIME_AUTOMATIC_WORK_RUNTIME_CAPABILITY, - ...TERMINAL_QUICK_COMMAND_RUNTIME_CAPABILITIES, - WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, - WORKTREE_ARCHIVE_FAILURE_BLOCKING_RUNTIME_CAPABILITY, - TERMINAL_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, - TERMINAL_CREATE_SHELL_SELECTION_RUNTIME_CAPABILITY, - SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY, - SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY, - ...SESSION_TABS_RUNTIME_CAPABILITIES, - AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY, - REMOTE_SERVER_UPDATE_CAPABILITY, - MANAGED_RUNTIME_INSTALL_RUNTIME_CAPABILITY, - MANAGED_RUNTIME_REMOVE_RUNTIME_CAPABILITY, - AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY, - AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY, - AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY, - STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY, - AGENT_SESSION_CURRENT_REQUESTS_CLIENT_CAPABILITY, - STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY, - AGENT_SESSION_PENDING_SEND_RESULT_RUNTIME_CAPABILITY, - // The host side: it accepts a send before any agent has it, and a Stop with no writer before a - // turn starts, so a client may gate on either. - AGENT_SESSION_ACCEPTED_SEND_RUNTIME_CAPABILITY, - AGENT_SESSION_SEND_ANSWERS_PROOF_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_COMMANDS_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_MESSAGE_EDIT_RUNTIME_CAPABILITY, - AGENT_SESSION_QUEUED_MESSAGE_IMAGE_EDIT_RUNTIME_CAPABILITY, - AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY, - AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY, - ...STRUCTURED_AGENT_SESSION_SURFACE_RUNTIME_CAPABILITIES, - ...AGENT_SESSION_ATTENTION_RUNTIME_CAPABILITIES, - AGENT_SESSION_REWIND_RUNTIME_CAPABILITY, - AGENT_SESSION_REWIND_RECOVERY_CAPABILITY, - AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY, - AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY, - AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY, - AGENT_SESSION_QUESTION_ANSWERS_RUNTIME_CAPABILITY, - AGENT_SESSION_ATTACHMENTS_RUNTIME_CAPABILITY, - AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY, - AGENT_SESSION_TURN_ITEM_CAPABILITY, - AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY, - AGENT_SESSION_BACKGROUND_TASK_CHILD_VIEWS_CAPABILITY, - ...AGENT_SESSION_RESUME_RUNTIME_CAPABILITIES, - FILE_MUTATION_OWNERSHIP_RUNTIME_CAPABILITY, - GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY, - GITLAB_READY_FOR_REVIEW_RUNTIME_CAPABILITY, - WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY, - WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY, + ...agentLaunchCapabilities.AGENT_LAUNCH_RUNTIME_CAPABILITIES, + ...AGENT_SESSION_RUNTIME_CAPABILITIES, ...ACCOUNT_RUNTIME_CAPABILITIES, ...SKILL_RUNTIME_CAPABILITIES, ...AUTOMATION_RUNTIME_CAPABILITIES, - NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY, - ...MOBILE_RELAY_RUNTIME_CAPABILITIES, - PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY, - PREFLIGHT_OTHER_RUNTIME_REFUSAL_RUNTIME_CAPABILITY, - WORKSPACE_PORTS_HOST_SCOPED_RUNTIME_CAPABILITY, - RUNTIME_SOURCE_FENCE_RUNTIME_CAPABILITY, - SSH_TARGET_MANAGEMENT_RUNTIME_CAPABILITY, - ...agentLaunchCapabilities.AGENT_LAUNCH_RUNTIME_CAPABILITIES + ...WORKTREE_RUNTIME_CAPABILITIES, + ...PREFLIGHT_RUNTIME_CAPABILITIES, + ...WORKSPACE_PORTS_RUNTIME_CAPABILITIES, + ...SESSION_TABS_RUNTIME_CAPABILITIES, + ...TERMINAL_RUNTIME_CAPABILITIES, + ...FILES_RUNTIME_CAPABILITIES, + ...BROWSER_RUNTIME_CAPABILITIES, + ...SOURCE_CONTROL_RUNTIME_CAPABILITIES, + ...TASK_SOURCE_RUNTIME_CAPABILITIES, + ...AI_VAULT_RUNTIME_CAPABILITIES ] as const export type RuntimeCapability = (typeof RUNTIME_CAPABILITIES)[number] | (string & {}) diff --git a/src/shared/runtime-capability-constants.ts b/src/shared/runtime-capability-constants.ts deleted file mode 100644 index 601c5e50d6c..00000000000 --- a/src/shared/runtime-capability-constants.ts +++ /dev/null @@ -1,272 +0,0 @@ -export const PROJECT_HOST_SETUP_RUNTIME_CAPABILITY = 'project-host-setup.v1' as const -// Why: older hosts strip `hostId` from setup update/delete and act on the first same-id twin. -export const PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY = - 'project-host-setup.host-target.v1' as const -export const TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY = 'task-source-context.v1' as const -export const WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY = 'workspace-run-context.v1' as const -export const WORKTREE_PROVISIONED_ROOT_RUNTIME_CAPABILITY = 'worktree.provisioned-root.v1' as const -export const WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY = - 'worktree.linked-work-item-context.v1' as const -export const WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY = - 'worktree.github-pr-suppression.v1' as const -// Legacy clients cannot safely derive a branch name from a qualified ref selector. -export const REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY = - 'repo.search-qualified-refs.v1' as const -export const REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY = 'remote-runtime.shared-control.v1' as const -export const FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY = - 'folder-workspace.path-status.v1' as const -export const LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY = - 'linear.issue-attribute-filter.v1' as const -// Why: signals the host exposes the Agent Session History scanner over RPC -// (aiVault.listSessions). Registered unconditionally for every build, so it is a -// STATIC capability advertised by getStatus() automatically — NOT a runtime -// conditional like browser.headless.v1. -export const AI_VAULT_RUNTIME_CAPABILITY = 'aiVault.v1' as const -export const AI_VAULT_SESSION_TITLES_RUNTIME_CAPABILITY = 'aiVault.session-titles.v1' as const -// Why: signals a host owns browser pages with no renderer (headless serve via the -// offscreen backend). Advertised only when that backend is actually available, so -// clients never fall back to a local desktop browser tab for a remote-owned page. -export const BROWSER_HEADLESS_RUNTIME_CAPABILITY = 'browser.headless.v1' as const -export const BROWSER_IDENTITY_RUNTIME_CAPABILITY = 'browser.identity.v1' as const -// Why conditional: only a desktop runtime holds the SSH registry managed servers are run from. -export const MANAGED_SERVER_RUNTIME_CAPABILITY = 'managedServer.v1' as const -export const BROWSER_SCREENCAST_RUNTIME_CAPABILITY = 'browser.screencast.v1' as const -export const BROWSER_CERTIFICATE_TRUST_RUNTIME_CAPABILITY = 'browser.certificate-trust.v1' as const -// Why: older hosts discard browser.tabCreate's page field, so clients may only -// treat a preallocated page ID as canonical when this is advertised. -export const BROWSER_TAB_CREATE_KNOWN_ID_RUNTIME_CAPABILITY = - 'browser.tab-create-known-id.v1' as const -export const BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY = 'browser.clientHost.v1' as const -export const BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY = - 'browser.clientHost.pageMetadata.v1' as const -export const BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY = - 'browser.clientHost.automation.v1' as const -// Why: without it a client-placed browser.upload would resolve remote paths on the desktop filesystem, so uploads fail closed instead. -export const BROWSER_CLIENT_FILE_CHANNEL_RUNTIME_CAPABILITY = - 'browser.clientHost.fileChannel.v1' as const -export const BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY = 'network.browserTunnel.v1' as const -export const BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY = - 'network.browserTunnel.executionHosts.v1' as const -// Why: hosts without this strip terminal.send's inputKind (zod object drops -// unknown keys), so a mobile xterm query reply would land as ordinary -// floor-taking input. Mobile must not forward replies unless advertised. -export const TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY = - 'terminal.query-reply-input.v1' as const -// Why: without this, prompt request IDs and waitSubmitMs are stripped and a retry would resend raw input. -export const TERMINAL_PROMPT_DELIVERY_RUNTIME_CAPABILITY = 'terminal.prompt-delivery.v1' as const -// Why: paired clients may unmount xterm only when the host can return a -// bounded, sequenced scrollback snapshot for lossless reveal. -export const TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY = 'terminal.paired-parking.v1' as const -// Why: terminal.ensureAgentSession params are strict, so an older host rejects `spawnIntent`; a -// host that advertises this also may answer a declared restore with `terminal_spawn_deferred`. -export const TERMINAL_SPAWN_INTENT_RUNTIME_CAPABILITY = 'terminal.spawn-intent.v1' as const -// Why: clients offer "Resume automatic restores" only on hosts that can clear a recorded pause. -export const RUNTIME_AUTOMATIC_WORK_RUNTIME_CAPABILITY = 'runtime.automatic-work.v1' as const -// Why: older hosts lack the targeted settings RPCs and strip agentPrompt from -// terminal creation, so mobile must hide Quick Commands unless both are present. -export const TERMINAL_QUICK_COMMANDS_RUNTIME_CAPABILITY = 'terminal.quick-commands.v1' as const -// Why: older hosts strip worktree.create's clientMutationId, so mobile must only -// replay ambiguous cutovers when the host advertises idempotent create support; -// status.worktreeCreateIdempotency carries the optional host retention policy. -export const WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY = - 'worktree.create-idempotency.v1' as const -// Scope of the claim: a hook that RUNS and fails cannot delete the checkout. It does not promise -// the hook was found — an SSH host whose orca.yaml cannot be read answers "no hook" and the removal -// proceeds, because a failed read is indistinguishable from an absent file across the relay -// (#20196 tracks the provider contract that would separate them). -// Why (#19334): "accepts --run-hooks" and "refuses to delete when the archive hook fails" were -// indistinguishable from the outside — both take the flag and behave identically on success, so -// the only way to tell an unfixed host apart was to fail a hook and see whether the checkout -// survived. Lifecycle integrations keep teardown evidence inside the checkout and cannot risk -// that. Advertised unconditionally: every build carrying this constant has the gate. -export const WORKTREE_ARCHIVE_FAILURE_BLOCKING_RUNTIME_CAPABILITY = - 'worktree.archive-failure-blocking.v1' as const -// Why: older hosts cannot reconcile terminal.create's mutation after losing the reply, so clients may only retry unknown outcomes when advertised. -export const TERMINAL_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY = - 'terminal.create-idempotency.v2' as const -// Why: an older host strips terminal.create's unknown `shell` and answers with a terminal running -// the host default shell. That reply is indistinguishable from success, so a client asking for a -// shell must refuse rather than create the wrong one. -export const TERMINAL_CREATE_SHELL_SELECTION_RUNTIME_CAPABILITY = - 'terminal.create-shell-selection.v1' as const -export const SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY = 'session-tabs.close-intent.v1' as const -export const SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY = - 'session-tabs.authoritative-inventory.v1' as const -// Why: a client advertising this retains every terminal retirement proof it receives until the -// surface is published live again, so a session-tabs stream sends each proof once instead of -// repeating the host's whole bounded list on every title tick. -export const SESSION_TABS_RETIREMENT_PROOF_DELTA_RUNTIME_CAPABILITY = - 'session-tabs.retirement-proof-delta.v1' as const -// Why: a client advertising this rebuilds each `updated` frame's tab list from the previous frame -// on the same stream, so one tab's status change no longer re-sends every tab in the worktree. -export const SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY = 'session-tabs.tab-delta.v1' as const -// Why: a client advertising this reads `restoredUnconfirmed` agent rows as unverifiable; older -// clients would read their last known state as live, so they keep the legacy `done` projection. -export const AGENT_STATUS_RESTORED_UNCONFIRMED_RUNTIME_CAPABILITY = - 'agent-status.restored-unconfirmed.v1' as const -export const AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY = - 'agent-session.session-boundary.v1' as const -export const AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY = - 'agent-session.host-authority.v1' as const -// Older launch schemas reject unknown fields; advertise before clients send keyboard support. -export const AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY = 'agent-session.keyboard.v1' as const -export const AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY = - 'agent-session.omp-resume-path.v1' as const -// Why: structured sessions are journal-backed, not PTY-backed, so an incapable client must not -// receive their journal or drive their lifecycle. Mobile may receive a metadata-only placeholder; -// the host still refuses agentSession.* methods and destructive tab mutations without capability. -export const STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY = 'agent-session.structured.v1' as const -// Why: hosts before this gated `agentSession.*` on their own chat setting while still advertising -// agent-session.structured.v1, so that capability alone cannot tell a client its chat will be -// admitted. A host advertising this admits by client capability alone; a client advertising it -// picks each launch's mode itself. A client without it asks `agentSession.createSupport` to pick -// for it (released phones), so the host keeps answering that with its own setting. -export const STRUCTURED_AGENT_SESSION_CLIENT_LAUNCH_MODE_CAPABILITY = - 'agent-session.structured.client-launch-mode.v1' as const -// Why: older structured clients render durable pending replies as uncertain delivery. Capable -// clients skip the host's bounded best-effort settlement observation. -export const AGENT_SESSION_PENDING_SEND_RESULT_RUNTIME_CAPABILITY = - 'agent-session.pending-send-result.v1' as const -// Why: a send is now answered once the host accepts it, before any agent has it. A client without -// this cannot show a message rejected after that answer, so the host holds its reply until the -// message is handed over or rejected. Transitional: drop the hold once no supported desktop or -// mobile client lacks the capability; mobile must first show a rejected message in place. -export const AGENT_SESSION_ACCEPTED_SEND_RUNTIME_CAPABILITY = - 'agent-session.accepted-send.v1' as const -// Why: a host advertising this answers a resent send id from its record before anything else may -// refuse it, so a refusal `agentSession.send` RETURNS is proof; a thrown error never is, a thrown -// refusal included (host not installed, journal database won't open, host disabled). Reading a -// returned `ok: false`: `agent_session_operation_unknown` with `outcomeUnknown` or `resultLost` — -// the host cannot tell yet, resend the same id; with `rewindUnconfirmed` — settled, nothing was -// written. `agent_session_operation_expired` — only the transcript can tell. An -// `agent_session_operation_conflict` or `messageIdReused` — the id holds a different payload, -// which proves nothing about this message; nor does `sessionNotAttached` (the chat's record is -// gone or unreadable on this host). Any other — the chat holds no message under that id and none -// is in flight, but a resend of that id may still run as a new send, so a client that hands the -// text back must not resend the old id. An older host may refuse an id it recorded: none of this -// holds there. -export const AGENT_SESSION_SEND_ANSWERS_PROOF_RUNTIME_CAPABILITY = - 'agent-session.send-answers-proof.v1' as const -// Why: `agentSession.send`'s params are strict, so an older host rejects `delivery`; and only a -// capable client can render the `queued` result arm. It gates `delivery: 'queue-if-active'` and -// the client's queueing setting and chord, never the published draft list or its card actions: a -// host without it still publishes a message it kept unsent across a restart or a close, and both -// clients show that card and call its actions. DARK ON PURPOSE — not in RUNTIME_CAPABILITIES: -// advertising still requires the integrated Codex steer matrix (#21062) in the shipped host. -// v1 includes `submission.queuedMessageId` on every draft hand-off: a client reads that link and -// never compares a draft id with a submission id. It also publishes the queue's pause once, as -// `queuePause` beside the list, lifted by `agentSession.queuedMessagesResume` or any turn sent -// after it starting; while it is set every waiting card waits. `nextQueuedMessageId` beside the list -// names the card the queue sends next once nothing runs, null while anything holds it; absent (an -// older host) reads as null. A card carries a hold of its own when its conversion failed -// (`send_failed`) or the host kept it unsent (`kept`). The host mechanism lands first; the constant -// gates the rollout. -export const AGENT_SESSION_QUEUED_MESSAGES_RUNTIME_CAPABILITY = - 'agent-session.queued-messages.v1' as const -// Why: a host advertising this edits a queued card in place (`agentSession.queuedMessageUpdate`) -// and holds it from automatic delivery while edited (`agentSession.queuedMessageEditHold`). Apart -// from queued-messages.v1: kept and agent-mail cards are editable with mid-turn queueing still off. -// A client offers Edit only when the host advertises it; there is no copy-and-delete fallback. -export const AGENT_SESSION_QUEUED_MESSAGE_EDIT_RUNTIME_CAPABILITY = - 'agent-session.queued-message-edit.v1' as const -// Why: `agentSession.queuedMessageUpdate`'s params are strict, so an older host rejects `images`. -// A host advertising this replaces a queued card's image set on Save; otherwise Save is text only. -export const AGENT_SESSION_QUEUED_MESSAGE_IMAGE_EDIT_RUNTIME_CAPABILITY = - 'agent-session.queued-message-image-edit.v1' as const -// Why: a client advertising this shows a queued card's images and edits its text in place, keeping -// them. Released readers show only a card's text and edit by copying it and deleting the card, which -// would drop the images, so the host publishes no card holding an image to a reader without it. -export const AGENT_SESSION_QUEUED_IMAGE_CARDS_CLIENT_CAPABILITY = - 'agent-session.queued-image-cards.v1' as const -// Why: `agentSession.conversationCommand`'s params are strict, so an older host rejects -// `delivery`. A host advertising this holds a /compact sent while the agent works as a queued -// card instead of refusing it. Clients ask only when queued-messages.v1 is advertised too: -// the card is the only place the waiting command shows. -export const AGENT_SESSION_QUEUED_COMMANDS_RUNTIME_CAPABILITY = - 'agent-session.queued-commands.v1' as const -// Why: paired clients advertise Claude-structured support so the host can gate its agent-specific -// journal and lifecycle surfaces independently from Codex support. -export const CLAUDE_STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY = - 'agent-session.structured.claude.v1' as const -// Why: agentSession.conversationOutline is additive; a client probes this before calling so an -// older host leaves the message rail on loaded messages instead of answering method_not_found. -export const AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY = - 'agent-session.conversation-outline.v1' as const -// Why: agentSession.item is additive; a client probes this before offering "Show full output" so an -// older host, whose rows keep only a head, is never asked for more. -export const AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY = 'agent-session.item-detail.v1' as const -// The RPC is registered unconditionally; per-session rewind support is a separate check. -export const AGENT_SESSION_REWIND_RUNTIME_CAPABILITY = 'agent-session.rewind.v1' as const -// Why: the rewind UI relies on the next send settling an in-doubt rewind, even with the agent up. -export const AGENT_SESSION_REWIND_RECOVERY_CAPABILITY = 'agent-session.rewind-recovery.v1' as const -// Readers must understand a monitoring roster with no available stop control. -// Why: a `turn` journal item replaced the status row that used to carry a turn's lifecycle. A -// client that predates it would render the unknown kind as text, so the host publishes the legacy -// status form to clients that do not advertise this. Transitional: drop the downgrade once no -// supported release lacks the capability. -export const AGENT_SESSION_TURN_ITEM_CAPABILITY = 'agent-session.turn-item.v1' as const -/** The client retires acknowledged echoes even when a catch-up snapshot skips their rows. */ -export const AGENT_SESSION_SNAPSHOT_CATCH_UP_CLIENT_CAPABILITY = - 'agent-session.snapshot-catch-up.v1' as const -export const AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY = - 'agent-session.background-task-stop.v1' as const -// Why: agentSession.respondToQuestion has a strict schema, so clients must not send structured -// `answers` to an older host; they fall back to the answer packed into `optionId`. -export const AGENT_SESSION_QUESTION_ANSWERS_RUNTIME_CAPABILITY = - 'agent-session.question-answers.v1' as const -// Why: a structured chat on a paired server stores attached files on that server -// (agentSessionAttachment.*); an older server has no store, so clients must refuse -// the attach rather than hand the agent a path from this machine. -export const AGENT_SESSION_ATTACHMENTS_RUNTIME_CAPABILITY = 'agent-session.attachments.v1' as const -// Why: hosts before this advertise attachments.v1 but refuse the upload methods from a phone. -export const AGENT_SESSION_PHONE_ATTACHMENTS_RUNTIME_CAPABILITY = - 'agent-session.attachments.phone.v1' as const -// Why: the host now publishes rows for work that is live inside a turn, and such -// a row carries `stoppable: false` because no targeted stop can reach it. A -// reader that predates the field draws a per-row Stop on every row it is given, -// so it must be told apart from one that honours the field — and NOT by the -// stop capability above, which a client can advertise while predating this. -export const AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY = - 'agent-session.background-task-row-stop.v1' as const -// Why: older runtimes strip mutation owner fields, so clients must fence writes before RPC. -export const FILE_MUTATION_OWNERSHIP_RUNTIME_CAPABILITY = 'files.mutation-ownership.v1' as const -export const FILE_MUTATION_OWNERSHIP_UPDATE_REQUIRED_MESSAGE = - 'Remote file changes require a newer Orca server. Update the HUB and try again.' -export const GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY = 'github.markPRReadyForReview' as const -export const GITHUB_MARK_PR_READY_UPDATE_REQUIRED_MESSAGE = - 'Marking a pull request ready requires a newer Orca server. Update the server and try again.' -export const GITLAB_READY_FOR_REVIEW_RUNTIME_CAPABILITY = - 'gitlab.updateMR.readyForReview.v1' as const -export const GITLAB_READY_FOR_REVIEW_UPDATE_REQUIRED_MESSAGE = - 'Marking a merge request ready requires a newer Orca server. Update the server and try again.' -export const WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY = - 'worktree.visibility-defaults.v1' as const -export const WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY = - 'worktree.visibility-source-defaults.v1' as const -// Why: Git's checkout delete outlives worktree.rm's older client timeouts. A client with this waits -// for the reply and shows `removing` rows as Deleting; one without it is answered on acceptance and -// would re-show that row as a normal workspace, so the host leaves such rows out of its listings. -export const WORKTREE_BACKGROUND_REMOVAL_RUNTIME_CAPABILITY = - 'worktree.background-removal.v1' as const -// Why: older hosts discard preflight.detectAgents params and probe their own PATH, so a client -// must not read that answer as the workspace's (a WSL project on a Windows host differs). -export const PREFLIGHT_WORKSPACE_SCOPED_RUNTIME_CAPABILITY = - 'preflight.workspace-scoped.v1' as const -// Why: an older host answers its own agents for a workspace another runtime owns; one with this -// refuses with WORKSPACE_ON_OTHER_RUNTIME, so a client may let it decide for a shared repo id. -export const PREFLIGHT_OTHER_RUNTIME_REFUSAL_RUNTIME_CAPABILITY = - 'preflight.other-runtime-refusal.v1' as const -/** What such a host answers, instead of probing itself, for another runtime's workspace. */ -export const WORKSPACE_ON_OTHER_RUNTIME = 'workspace_on_other_runtime' -// Hosts without this capability have no workspacePorts.scanHost/killHost; their scan and Stop act -// only on the endpoint itself, never on a workspace's SSH host. -export const WORKSPACE_PORTS_HOST_SCOPED_RUNTIME_CAPABILITY = - 'workspace-ports.host-scoped.v1' as const -// Hosts without this capability ignore a request's expectedRuntimeSource; the CLI's pre-send probe -// is then the only fence. -export const RUNTIME_SOURCE_FENCE_RUNTIME_CAPABILITY = 'runtime-source-fence.v1' as const -// Hosts without this capability have no notifications.registerPush RPC. -export const NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY = 'notifications.remote-push.v1' as const - -export const ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY = - 'git.antigravity-configured-model.v1' as const diff --git a/src/shared/session-tabs-runtime-capabilities.ts b/src/shared/session-tabs-runtime-capabilities.ts index 17e16a661c6..da93fcb572a 100644 --- a/src/shared/session-tabs-runtime-capabilities.ts +++ b/src/shared/session-tabs-runtime-capabilities.ts @@ -1,3 +1,5 @@ +import { QODER_OWNED_TERMINAL_CREATE_CAPABILITY } from './qoder-terminal-create-capability' + // Why: this proves both headed and runtime-owned host paths place after a complete split parent. // Legacy host paths disagree, so clients without this capability defer placement to the snapshot. export const SESSION_TABS_SPLIT_GROUP_PLACEMENT_RUNTIME_CAPABILITY = @@ -14,7 +16,26 @@ export const SESSION_TABS_EMPTY_WORKTREE_ANSWER_RUNTIME_CAPABILITY = export const SESSION_TABS_CREATE_TERMINAL_SHELL_RUNTIME_CAPABILITY = 'session-tabs.create-terminal-shell.v1' as const +export const SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY = 'session-tabs.close-intent.v1' as const +export const SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY = + 'session-tabs.authoritative-inventory.v1' as const +// Why: a client advertising this retains every terminal retirement proof it receives until the +// surface is published live again, so a session-tabs stream sends each proof once instead of +// repeating the host's whole bounded list on every title tick. +export const SESSION_TABS_RETIREMENT_PROOF_DELTA_RUNTIME_CAPABILITY = + 'session-tabs.retirement-proof-delta.v1' as const +// Why: a client advertising this rebuilds each `updated` frame's tab list from the previous frame +// on the same stream, so one tab's status change no longer re-sends every tab in the worktree. +export const SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY = 'session-tabs.tab-delta.v1' as const +// Why: a client advertising this reads `restoredUnconfirmed` agent rows as unverifiable; older +// clients would read their last known state as live, so they keep the legacy `done` projection. +export const AGENT_STATUS_RESTORED_UNCONFIRMED_RUNTIME_CAPABILITY = + 'agent-status.restored-unconfirmed.v1' as const + export const SESSION_TABS_RUNTIME_CAPABILITIES = [ + QODER_OWNED_TERMINAL_CREATE_CAPABILITY, + SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY, + SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY, SESSION_TABS_SPLIT_GROUP_PLACEMENT_RUNTIME_CAPABILITY, SESSION_TABS_CHAT_FORK_CLI_RUNTIME_CAPABILITY, SESSION_TABS_EMPTY_WORKTREE_ANSWER_RUNTIME_CAPABILITY, diff --git a/src/shared/session-tabs-tab-delta.test.ts b/src/shared/session-tabs-tab-delta.test.ts index 6bd672ece2e..bb0e20faa9a 100644 --- a/src/shared/session-tabs-tab-delta.test.ts +++ b/src/shared/session-tabs-tab-delta.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY } from './runtime-capability-constants' +import { SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY } from './session-tabs-runtime-capabilities' import { createSessionTabsTabDeltaDecoder, createSessionTabsTabDeltaEncoder diff --git a/src/shared/session-tabs-tab-delta.ts b/src/shared/session-tabs-tab-delta.ts index fcf525893bb..316fb20b2fc 100644 --- a/src/shared/session-tabs-tab-delta.ts +++ b/src/shared/session-tabs-tab-delta.ts @@ -4,7 +4,7 @@ // may then replace `tabs` with `tabsDelta`: each entry is either the index of an identical tab in // the previous frame for that worktree on this stream, or the tab itself. Everything else in the // frame is sent as before. A client that never advertised the capability never sees either field. -import { SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY } from './runtime-capability-constants' +import { SESSION_TABS_TAB_DELTA_RUNTIME_CAPABILITY } from './session-tabs-runtime-capabilities' export type SessionTabsTabDeltaRef = number | Record diff --git a/src/shared/source-control-runtime-capabilities.ts b/src/shared/source-control-runtime-capabilities.ts new file mode 100644 index 00000000000..64ba834bd04 --- /dev/null +++ b/src/shared/source-control-runtime-capabilities.ts @@ -0,0 +1,16 @@ +export const GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY = 'github.markPRReadyForReview' as const +export const GITHUB_MARK_PR_READY_UPDATE_REQUIRED_MESSAGE = + 'Marking a pull request ready requires a newer Orca server. Update the server and try again.' +export const GITLAB_READY_FOR_REVIEW_RUNTIME_CAPABILITY = + 'gitlab.updateMR.readyForReview.v1' as const +export const GITLAB_READY_FOR_REVIEW_UPDATE_REQUIRED_MESSAGE = + 'Marking a merge request ready requires a newer Orca server. Update the server and try again.' +// Commit-message generation: the host plans Antigravity with the model the user configured. +export const ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY = + 'git.antigravity-configured-model.v1' as const + +export const SOURCE_CONTROL_RUNTIME_CAPABILITIES = [ + ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY, + GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY, + GITLAB_READY_FOR_REVIEW_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/task-source-runtime-capabilities.ts b/src/shared/task-source-runtime-capabilities.ts new file mode 100644 index 00000000000..4b4111046a3 --- /dev/null +++ b/src/shared/task-source-runtime-capabilities.ts @@ -0,0 +1,23 @@ +import { JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY } from './jira-agent-capability' +import { + JIRA_CURRENT_SPRINT_RUNTIME_CAPABILITY, + JIRA_DETAIL_RUNTIME_CAPABILITIES, + JIRA_TRANSITION_FIELDS_RUNTIME_CAPABILITY +} from './jira-detail-capabilities' +import { JIRA_STATUS_FILTER_RUNTIME_CAPABILITY } from './jira-quick-filters' +import { JIRA_SAVED_FILTER_RUNTIME_CAPABILITY } from './jira-saved-filter-capability' + +export const TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY = 'task-source-context.v1' as const +export const LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY = + 'linear.issue-attribute-filter.v1' as const + +export const TASK_SOURCE_RUNTIME_CAPABILITIES = [ + TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY, + LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY, + JIRA_TRANSITION_FIELDS_RUNTIME_CAPABILITY, + JIRA_AGENT_ACCESS_RUNTIME_CAPABILITY, + JIRA_CURRENT_SPRINT_RUNTIME_CAPABILITY, + ...JIRA_DETAIL_RUNTIME_CAPABILITIES, + JIRA_SAVED_FILTER_RUNTIME_CAPABILITY, + JIRA_STATUS_FILTER_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/terminal-quick-command-capabilities.ts b/src/shared/terminal-quick-command-capabilities.ts index abd8387ada8..e212a72c3ba 100644 --- a/src/shared/terminal-quick-command-capabilities.ts +++ b/src/shared/terminal-quick-command-capabilities.ts @@ -1,5 +1,6 @@ -import { TERMINAL_QUICK_COMMANDS_RUNTIME_CAPABILITY } from './runtime-capability-constants' -export { TERMINAL_QUICK_COMMANDS_RUNTIME_CAPABILITY } from './runtime-capability-constants' +// Why: older hosts lack the targeted settings RPCs and strip agentPrompt from +// terminal creation, so mobile must hide Quick Commands unless both are present. +export const TERMINAL_QUICK_COMMANDS_RUNTIME_CAPABILITY = 'terminal.quick-commands.v1' as const // Why: prompts used to be capped at 6,000 characters. A host advertising this stores longer ones; a // client advertising it can read them, so the host withholds longer prompts from older clients, // which reject a whole list holding one. Transitional once no supported release lacks it. diff --git a/src/shared/terminal-runtime-capabilities.ts b/src/shared/terminal-runtime-capabilities.ts new file mode 100644 index 00000000000..3eb0a8f34f2 --- /dev/null +++ b/src/shared/terminal-runtime-capabilities.ts @@ -0,0 +1,37 @@ +import { PROGRAM_STATUS_RUNTIME_CAPABILITY } from './program-status/protocol' +import { TERMINAL_QUICK_COMMAND_RUNTIME_CAPABILITIES } from './terminal-quick-command-capabilities' + +// Why: hosts without this strip terminal.send's inputKind (zod object drops +// unknown keys), so a mobile xterm query reply would land as ordinary +// floor-taking input. Mobile must not forward replies unless advertised. +export const TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY = + 'terminal.query-reply-input.v1' as const +// Why: without this, prompt request IDs and waitSubmitMs are stripped and a retry would resend raw input. +export const TERMINAL_PROMPT_DELIVERY_RUNTIME_CAPABILITY = 'terminal.prompt-delivery.v1' as const +// Why: paired clients may unmount xterm only when the host can return a +// bounded, sequenced scrollback snapshot for lossless reveal. +export const TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY = 'terminal.paired-parking.v1' as const +// Why: terminal.ensureAgentSession params are strict, so an older host rejects `spawnIntent`; a +// host that advertises this also may answer a declared restore with `terminal_spawn_deferred`. +export const TERMINAL_SPAWN_INTENT_RUNTIME_CAPABILITY = 'terminal.spawn-intent.v1' as const +// Why: older hosts cannot reconcile terminal.create's mutation after losing the reply, so clients may only retry unknown outcomes when advertised. +export const TERMINAL_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY = + 'terminal.create-idempotency.v2' as const +// Why: an older host strips terminal.create's unknown `shell` and answers with a terminal running +// the host default shell. That reply is indistinguishable from success, so a client asking for a +// shell must refuse rather than create the wrong one. +export const TERMINAL_CREATE_SHELL_SELECTION_RUNTIME_CAPABILITY = + 'terminal.create-shell-selection.v1' as const + +export const TERMINAL_RUNTIME_CAPABILITIES = [ + 'terminal.binary-stream.v1', + PROGRAM_STATUS_RUNTIME_CAPABILITY, + 'terminal.multiplex.v1', + TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY, + TERMINAL_PROMPT_DELIVERY_RUNTIME_CAPABILITY, + TERMINAL_PAIRED_PARKING_RUNTIME_CAPABILITY, + TERMINAL_SPAWN_INTENT_RUNTIME_CAPABILITY, + ...TERMINAL_QUICK_COMMAND_RUNTIME_CAPABILITIES, + TERMINAL_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, + TERMINAL_CREATE_SHELL_SELECTION_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/workspace-ports-runtime-capabilities.ts b/src/shared/workspace-ports-runtime-capabilities.ts new file mode 100644 index 00000000000..79b062371f4 --- /dev/null +++ b/src/shared/workspace-ports-runtime-capabilities.ts @@ -0,0 +1,9 @@ +// Hosts without this capability have no workspacePorts.scanHost/killHost; their scan and Stop act +// only on the endpoint itself, never on a workspace's SSH host. +export const WORKSPACE_PORTS_HOST_SCOPED_RUNTIME_CAPABILITY = + 'workspace-ports.host-scoped.v1' as const + +export const WORKSPACE_PORTS_RUNTIME_CAPABILITIES = [ + 'workspace-ports.v1', + WORKSPACE_PORTS_HOST_SCOPED_RUNTIME_CAPABILITY +] as const diff --git a/src/shared/worktree-runtime-capabilities.ts b/src/shared/worktree-runtime-capabilities.ts new file mode 100644 index 00000000000..99d5fbd390c --- /dev/null +++ b/src/shared/worktree-runtime-capabilities.ts @@ -0,0 +1,58 @@ +import { WORKSPACE_ATTACHMENT_RUNTIME_CAPABILITIES } from './workspace-attachment-capabilities' + +export const PROJECT_HOST_SETUP_RUNTIME_CAPABILITY = 'project-host-setup.v1' as const +// Why: older hosts strip `hostId` from setup update/delete and act on the first same-id twin. +export const PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY = + 'project-host-setup.host-target.v1' as const +export const WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY = 'workspace-run-context.v1' as const +export const WORKTREE_PROVISIONED_ROOT_RUNTIME_CAPABILITY = 'worktree.provisioned-root.v1' as const +export const WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY = + 'worktree.linked-work-item-context.v1' as const +export const WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY = + 'worktree.github-pr-suppression.v1' as const +// Legacy clients cannot safely derive a branch name from a qualified ref selector. +export const REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY = + 'repo.search-qualified-refs.v1' as const +export const FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY = + 'folder-workspace.path-status.v1' as const +// Why: older hosts strip worktree.create's clientMutationId, so mobile must only +// replay ambiguous cutovers when the host advertises idempotent create support; +// status.worktreeCreateIdempotency carries the optional host retention policy. +export const WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY = + 'worktree.create-idempotency.v1' as const +// Scope of the claim: a hook that RUNS and fails cannot delete the checkout. It does not promise +// the hook was found — an SSH host whose orca.yaml cannot be read answers "no hook" and the removal +// proceeds, because a failed read is indistinguishable from an absent file across the relay +// (#20196 tracks the provider contract that would separate them). +// Why (#19334): "accepts --run-hooks" and "refuses to delete when the archive hook fails" were +// indistinguishable from the outside — both take the flag and behave identically on success, so +// the only way to tell an unfixed host apart was to fail a hook and see whether the checkout +// survived. Lifecycle integrations keep teardown evidence inside the checkout and cannot risk +// that. Advertised unconditionally: every build carrying this constant has the gate. +export const WORKTREE_ARCHIVE_FAILURE_BLOCKING_RUNTIME_CAPABILITY = + 'worktree.archive-failure-blocking.v1' as const +export const WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY = + 'worktree.visibility-defaults.v1' as const +export const WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY = + 'worktree.visibility-source-defaults.v1' as const +// Why: Git's checkout delete outlives worktree.rm's older client timeouts. A client with this waits +// for the reply and shows `removing` rows as Deleting; one without it is answered on acceptance and +// would re-show that row as a normal workspace, so the host leaves such rows out of its listings. +export const WORKTREE_BACKGROUND_REMOVAL_RUNTIME_CAPABILITY = + 'worktree.background-removal.v1' as const + +export const WORKTREE_RUNTIME_CAPABILITIES = [ + PROJECT_HOST_SETUP_RUNTIME_CAPABILITY, + PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY, + WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY, + WORKTREE_PROVISIONED_ROOT_RUNTIME_CAPABILITY, + WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY, + ...WORKSPACE_ATTACHMENT_RUNTIME_CAPABILITIES, + WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY, + REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY, + FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY, + WORKTREE_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY, + WORKTREE_ARCHIVE_FAILURE_BLOCKING_RUNTIME_CAPABILITY, + WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY, + WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY +] as const