Files
orca/src/shared/protocol-version.ts
T
Brennan Benson 5cf3585b78 fix(native-chat): keep a message accepted before a quit or crash as a held card (#24660)
* fix(native-chat): keep a message accepted before a quit or crash as a held card

A send the host accepted while the agent was still starting, and never handed
over, was rejected unseen at quit or at the next open after a crash. The next
open now keeps a person's message (typed, or a launch's first prompt) as a
waiting card at the head of the queue, held until Resume, Send now, Edit or
Delete; quit no longer rejects it. Each submission records its source so a
restart knows which leftovers to keep. A direct send's replay answers from its
own record, never the queued arm. Cards shown without the queue capability
hide Turn off queueing and the steer chord.

* fix(native-chat): keep an unsent message as a held card at every close, not only a restart

The host now keeps a person's message it accepted and never handed over with one
rule wherever it can no longer hand it over: a quit or crash (settled at the next
open) and a close of the chat (tab close, worktree teardown, orchestration stop).

- The hold is card state: a new per-card hold_reason 'kept', published as the
  existing pausedReason, instead of a fake host_instance value. host_instance
  means the owner again, and the pause clause, adoption filter and /clear carry
  special cases are gone. A kept card holds the cards behind it until the
  person sends, edits or deletes it; a send_failed card still does not.
- A card hand-off rejected by a restart or a close returns as a kept card
  (rejectedDraftSettlement), so a person's next message can never release it.
- One hold function, parameterized by cause (hostRestarted / chatClosed),
  replaces the close path's plain rejection.
- The phone shows published cards and per-card holds whatever the queue
  capability says; only queueing a new send stays gated.
- source gains 'dispatch' for the orchestration preamble (still rejected); an
  unknown source is kept as written and never takes the legacy rule.
- Kept cards from earlier settlements stay ahead of a batch's new ones.

* test(native-chat): the dispatch preamble records its source

* fix(native-chat): skip a kept card like a failed one, and make a quit leave the queue as a crash does

- A kept card is held on its own, as a send_failed one is: the queue sends the
  cards behind it, and the "a message ahead needs attention" caption no longer
  appears behind it (desktop and phone).
- Quit disposes the queue's drain together with delivery, so it mints no
  hand-off that only the next process could settle.
- Only a Send the person asked for (origin client) that a restart or close cut
  short returns kept; the queue's own hand-off returns where it stood, under the
  restart's pause, as on main.
- Comments that said only a capable host gets cards or the card actions now say
  the capability gates only queueing a new send.

* refactor(native-chat): one dispose gate in the queue drain's step

* test(native-chat): the downgrade test names the older build's /clear exception

* fix(native-chat): re-check the drain's quit gate right before it appends a hand-off

A drain step already past its first check when quit begins no longer makes a
hand-off. Adds regression tests for that, for Send now on an ordinary card cut
short by a quit, and for the open-time repair deriving kept from the hand-off's
origin; fixes the pause and settlement comments that called a kept card one the
queue never passes.

* fix(native-chat): a card held on its own starts no restart pause for the others

After a second restart a kept (or send_failed) card is another process's, but it
waits for its own Send, so it no longer pauses every other card under
"Queue paused because Orca restarted".

* fix(native-chat): record on a kept send which card holds it, so no trace survives an Edit or Delete

The rejection row of a send the host kept as a card now names that card
(`keptAsQueuedMessageId`), in the same transaction that writes the card, and the
fold publishes it on the submission. The shared projection draws such a send
only as its card: once the card is sent, edited or deleted, neither the send
nor the sending desktop's local copy of it shows.

* chore(native-chat): leave the unused submission schema as main has it

No client parses published submissions with it (they arrive as typed frames,
and the host's history pages carry the field, as the Edit/Delete test reads);
listing the field put the file over its line budget.

* fix(native-chat): retire a kept send's local copy instead of only hiding it

The outbox reconcile and the send disposition drop an entry whose submission
the host rejected as kept as a card, as they already do for a Stop's
withdrawal, so the copy never comes back as "Not sent / Retry" once the
submission falls out of the loaded page. The projection reads the reconciled
outbox, so its separate filter goes.

* test(native-chat): move the queued-message rig's scripted provider into its own fixture

The rig fixture grew past the 300-line limit once main's changes merged in.

* fix(native-chat): hide a kept send by its own record, not by its card still existing

The transcript hid a rejected send while a queued card held it under its id, so the card's Edit or
Delete brought back a "Not sent" row. It now reads the send's own keptAsQueuedMessageId, which the
host records with the rejection, and the live card list is no longer threaded to the transcript or
the delivery notices.

* refactor(native-chat): move a sent message's row writes into their own journal collaborator

The journal store went past its line limit once main's ledger receipt joined this branch's
transaction hook. The submission and dispatch-transition writes, and what commits in their
transaction, now live in JournalSubmissionWriter; the store's methods delegate to it unchanged.

* fix(native-chat): list a kept send's card id in the submission schema

The schema drops keys it does not list, so a reader that kept a parsed submission would lose
keptAsQueuedMessageId and source, both persisted with the row. The submission schema and the
failure fact it shares with item bodies move to their own modules, with room for both fields.

* test(native-chat): match the transcript and outbox hook signatures main and the swap changed

* test(native-chat): import the journal types once in the queue-delivery test

* fix(mobile): a resend the host kept as a card shows no error and returns no text

The host answers a resend of a message it kept as a card with that
message's rejected submission, marked keptAsQueuedMessageId. The phone read
it as any rejection: "Message not sent" and the text back in the composer,
while the card showed the same text. It now answers like a queued send, as
the desktop's send disposition does: the id is spent, no error, and the card
holds the text.

* test(native-chat): pin that quit's first step stops the queue's hand-off

Quit now stops delivery, the queue drain included, at its first step
(stopDelivery), before teardown drains recovery. The drain-step quit test
runs from that step as well as from the flush.

* test(native-chat): give cards their source and store unknown sources as another build would

#25078 made a card's source required, so the tests that insert a card pass the
person's. The hold's unknown-kind and unreadable-source cases now rewrite the
stored row the way a newer build would leave it, instead of casting a type.

* refactor(native-chat): stop delivery and the queue drain in one line at quit

Main's #25159 left the host at its line limit; quit's stop now disposes both in one
expression instead of a block.

* test(native-chat): hold the drain step without reading the call stack

Bun formats a method's stack frame without its class ("at step"), so the quit
test's caller check never matched, the step was never held, and both cases timed
out once CI ran Vitest on Bun (#25840). Only the drain step heals owed queue
bookkeeping, so the hold needs no caller check.
2026-10-06 05:35:24 -07:00

451 lines
28 KiB
TypeScript

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'
export {
AGENT_SESSION_CURSOR_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_KIMI_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_OPENCODE2_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_MUSE_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_DSH_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_CODEBUDDY_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_QODER_CN_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_QWEN_CODE_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_QODER_RESUME_RUNTIME_CAPABILITY,
AGENT_SESSION_ZCODE_RESUME_RUNTIME_CAPABILITY
} 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 { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update'
import {
SKILL_BUNDLE_INSTALL_CAPABILITY,
SKILL_DELETE_CAPABILITY,
SKILL_INSTALL_CAPABILITY,
SKILL_INSTALL_CANCEL_CAPABILITY,
SKILL_INSTALL_PROGRESS_CAPABILITY,
SKILL_INSTALL_PROVIDERS_CAPABILITY,
SKILL_INSTALL_RESULT_V2_CAPABILITY,
SKILL_MANAGEMENT_CAPABILITY,
SKILL_UPLOAD_CAPABILITY
} from './skill-install-capability'
export { SKILL_INSTALL_RESULT_V2_CAPABILITY } from './skill-install-capability'
import {
AGENT_LAUNCH_RUNTIME_CAPABILITIES,
AGENT_LAUNCH_RUNTIME_CAPABILITY
} from './agent-launch-runtime-capability'
// Why: declares the Orca runtime RPC compatibility contract. Desktop,
// headless server, CLI, and mobile builds may drift in app version, but
// they must agree on this protocol range before runtime RPCs are allowed.
//
// Bump RUNTIME_PROTOCOL_VERSION when:
// - You remove an RPC method or required parameter that clients use.
// - You change the meaning (units, nullability) of an existing field
// clients read.
// - You change encrypted framing, terminal stream framing, or auth.
// Do NOT bump for:
// - Adding new RPC methods.
// - Adding new optional fields on existing methods.
// - Adding new ignorable event types.
//
// Bump MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION when a runtime server must
// refuse older clients. Bump MIN_COMPATIBLE_RUNTIME_SERVER_VERSION when
// this client build requires a newer server. Exact app-version equality is
// never required; these numbers define the supported compatibility window.
export const RUNTIME_PROTOCOL_VERSION = 3
export const MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION = 2
export const MIN_COMPATIBLE_RUNTIME_SERVER_VERSION = 2
export const PROJECT_HOST_SETUP_RUNTIME_CAPABILITY = 'project-host-setup.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_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
export const JIRA_USER_FIELDS_RUNTIME_CAPABILITY = 'jira.user-fields.v1' as const
export const JIRA_USER_FIELDS_UPDATE_REQUIRED_MESSAGE =
'Creating Jira issues with user fields requires a newer Orca server. Update the server and try again.'
// 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
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: 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
export const CODEX_RESET_CREDIT_RUNTIME_CAPABILITY = 'accounts.codex-reset-credit.v1' as const
export const ACCOUNT_IMPORT_RUNTIME_CAPABILITY = 'accounts.import-host-credentials.v1' as const
export const ANTIGRAVITY_ACCOUNTS_RUNTIME_CAPABILITY = 'accounts.antigravity-native.v1' as const
export const DATA_ACCOUNT_RUNTIME_CAPABILITY = 'accounts.managed-data-profiles.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: 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 =
'session-tabs.split-group-placement.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
export const AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY =
'agent-session.session-boundary.v1' as const
export { REMOTE_SERVER_UPDATE_CAPABILITY } from './remote-server-update'
export * from './agent-session-stop-capabilities'
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 the user's next
// turn; 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: 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: a host's structured agents are the ones it registered, not a list every build ships. A host
// advertising this accepts any agent it lists through `agentSession.agents` (with each agent's
// capability record) in `agentSession.*` params, and refuses one it did not register; a client
// offers an agent beyond Claude and Codex only to such a host. A client advertising it renders an
// `agent-session` tab of any agent its host lists; the host withholds every other agent's tabs from
// a client that does not (an older client would list them with an empty pane).
export const STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY =
'agent-session.structured.registered-agents.v1' as const
// Why: paired structured clients explicitly hold every visible session surface, allowing the host
// to stop provider children after the last surface closes without tying lifetime to a transport.
export const STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY =
'agent-session.structured.hold.v1' as const
// Why: a client holding only a session id — an Agent Session History row — asks the host to
// republish that chat's tab. An older host has no such method, and a client must learn that during
// negotiation rather than by calling and reading a refusal it cannot distinguish from a real one.
export const STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY =
'agent-session.structured.reveal.v1' as const
// Why: `agentSession.create` gains an optional `resumeFrom`, and its params are a STRICT union — an
// older host rejects the unknown key as a schema error, which a client cannot tell from a real
// refusal. Worse, without probing, a client cannot know whether a host that accepted the call
// adopted the conversation or quietly started a blank one. Negotiate before offering the action.
export const STRUCTURED_AGENT_SESSION_RESUME_HISTORY_RUNTIME_CAPABILITY =
'agent-session.structured.resume-history.v1' as const
// Why: agentSession.subscribeStatus is additive to a surface that already shipped, so a host
// advertising agent-session.structured.v1 may still answer it with method_not_found. Clients must
// probe before subscribing or they reconnect forever and never show any status at all.
export const AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY = 'agent-session.status-feed.v1' as const
// Why separate from the status feed: a host can carry the status feed and not this stream, and a
// decoder drops an unknown stream opcode in silence. A client that subscribed without probing
// would wait forever for completions the host never sends and report nothing wrong.
export const AGENT_SESSION_TURN_COMPLETION_RUNTIME_CAPABILITY =
'agent-session.turn-completion.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
// 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
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: 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 drop automation.list's selector and answer with the whole authority, so a scoped client must not read that as one host's rows.
export const AUTOMATION_LIST_HOST_SCOPE_RUNTIME_CAPABILITY =
'automation.list-host-scope.v1' as const
export const AUTOMATION_LIST_HOST_SCOPE_UPDATE_REQUIRED_MESSAGE =
'Filtering automations by host requires a newer Orca server. Update the HUB and try again.'
// Why: without server-side owner preconditions a mutation could run against a host the user never saw, so unfenced rows stay view-only.
export const AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY = 'automation.owner-fencing.v1' as const
export const AUTOMATION_OWNER_FENCING_UPDATE_REQUIRED_MESSAGE =
'Editing automations on this host requires a newer Orca server. Update the HUB and try again.'
export const AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY =
'automation.create-idempotency.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
// Generic native clients include the CLI and must not claim Electron-only page
// placement support.
export const NATIVE_REMOTE_RUNTIME_CLIENT_CAPABILITIES = [
SESSION_TAB_CLOSE_INTENT_RUNTIME_CAPABILITY,
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
WORKTREE_VISIBILITY_DEFAULTS_RUNTIME_CAPABILITY,
WORKTREE_VISIBILITY_SOURCE_DEFAULTS_RUNTIME_CAPABILITY,
WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY,
REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY,
AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY,
AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY,
AGENT_LAUNCH_RUNTIME_CAPABILITY
] as const
export const ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY =
'git.antigravity-configured-model.v1' as const
// Why: `agentSession.create` is a strict object, so an older host refuses a payload carrying the
// reserved `tabId` rather than ignoring it. A client sends the field only to a host advertising this.
export const AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY =
'agentSession.create.tab-id.v1' as const
export const RUNTIME_CAPABILITIES = [
QODER_OWNED_TERMINAL_CREATE_CAPABILITY,
...AGENT_SESSION_STOP_RUNTIME_CAPABILITIES,
AGENT_SESSION_CREATE_TAB_ID_RUNTIME_CAPABILITY,
ANTIGRAVITY_CONFIGURED_MODEL_RUNTIME_CAPABILITY,
'files.pathsExist',
'runtime.status.compat.v1',
'runtime.environments.v1',
REMOTE_RUNTIME_SHARED_CONTROL_CAPABILITY,
...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',
'terminal.multiplex.v1',
'workspace-ports.v1',
'mobile.tasks.v1',
PROJECT_HOST_SETUP_RUNTIME_CAPABILITY,
TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY,
WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY,
WORKTREE_LINKED_WORK_ITEM_CONTEXT_RUNTIME_CAPABILITY,
WORKTREE_GITHUB_PR_SUPPRESSION_RUNTIME_CAPABILITY,
REPO_SEARCH_QUALIFIED_REFS_RUNTIME_CAPABILITY,
FOLDER_WORKSPACE_PATH_STATUS_RUNTIME_CAPABILITY,
LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY,
JIRA_USER_FIELDS_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_QUICK_COMMANDS_RUNTIME_CAPABILITY,
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_SPLIT_GROUP_PLACEMENT_RUNTIME_CAPABILITY,
AGENT_SESSION_BOUNDARY_RUNTIME_CAPABILITY,
REMOTE_SERVER_UPDATE_CAPABILITY,
AGENT_SESSION_HOST_AUTHORITY_RUNTIME_CAPABILITY,
AGENT_SESSION_OMP_RESUME_PATH_RUNTIME_CAPABILITY,
AGENT_SESSION_KEYBOARD_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_RUNTIME_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,
STRUCTURED_AGENT_SESSION_HOLD_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_REVEAL_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_REGISTERED_AGENTS_RUNTIME_CAPABILITY,
STRUCTURED_AGENT_SESSION_RESUME_HISTORY_RUNTIME_CAPABILITY,
AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY,
AGENT_SESSION_TURN_COMPLETION_RUNTIME_CAPABILITY,
AGENT_SESSION_REWIND_RUNTIME_CAPABILITY,
AGENT_SESSION_REWIND_RECOVERY_CAPABILITY,
AGENT_SESSION_CONVERSATION_OUTLINE_RUNTIME_CAPABILITY,
AGENT_SESSION_BACKGROUND_TASK_STOP_CAPABILITY,
AGENT_SESSION_QUESTION_ANSWERS_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,
ACCOUNT_IMPORT_RUNTIME_CAPABILITY,
ANTIGRAVITY_ACCOUNTS_RUNTIME_CAPABILITY,
DATA_ACCOUNT_RUNTIME_CAPABILITY,
CODEX_RESET_CREDIT_RUNTIME_CAPABILITY,
SKILL_INSTALL_CAPABILITY,
SKILL_BUNDLE_INSTALL_CAPABILITY,
SKILL_INSTALL_CANCEL_CAPABILITY,
SKILL_INSTALL_PROGRESS_CAPABILITY,
SKILL_INSTALL_RESULT_V2_CAPABILITY,
SKILL_UPLOAD_CAPABILITY,
SKILL_MANAGEMENT_CAPABILITY,
SKILL_INSTALL_PROVIDERS_CAPABILITY,
SKILL_DELETE_CAPABILITY,
AUTOMATION_LIST_HOST_SCOPE_RUNTIME_CAPABILITY,
AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY,
AUTOMATION_CREATE_IDEMPOTENCY_RUNTIME_CAPABILITY,
NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY,
...AGENT_LAUNCH_RUNTIME_CAPABILITIES
] as const
export type RuntimeCapability = (typeof RUNTIME_CAPABILITIES)[number] | (string & {})
// COMPAT(mobileProtocolAliases): added 2026-05-15 for mobile builds that
// still read desktop/mobile names; remove once mobile reads runtime names.
export const DESKTOP_PROTOCOL_VERSION = RUNTIME_PROTOCOL_VERSION
export const MIN_COMPATIBLE_MOBILE_VERSION = MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION