Files
orca/src/shared/protocol-version.ts
T
Brennan Benson 0c96550ee9 fix(native-chat): queued messages carry on in order after any turn, and nothing sends by itself after a restart (#24586)
* fix(native-chat): drop the queue-paused header and Resume button

A Stop, a restart or /clear holds the queued cards. The hold stays; only the
header row naming why, and its Resume button, go. A held card shows no
caption, and its own Steer, or any new message, releases the queue.

* test(native-chat): type the unknown hold reason a newer host may publish

* fix(native-chat): a held card offers Send, not Steer, when no turn runs

Steer vs Send now follows whether a turn is running, not the card's hold,
so a card held after a Stop, a restart or /clear reads Send.

* fix(native-chat): the queue sends past held cards instead of stalling behind them

A card queued after a Stop (or written after a restart or /clear) sent only
once the cards held before it were released; with no header to explain or
release the hold, it sat silently. The next sendable card now skips held
cards; a returned card still blocks what is behind it.

* fix(native-chat): the queue's send of a card is the person's turn, so held cards follow it

After a Stop, a card queued later sent past the held cards, but the queue
recorded that send as Orca's own turn. It never ended the Stop's pause, so
the held cards then waited forever with nothing on the card saying why.

A queued card is always something the person wrote: only the client send
RPC may now create one. The queue's send of it is therefore recorded as the
person's turn, which ends the Stop's pause once the agent takes it, and the
held cards then drain in order.

* fix(native-chat): a queued card carries its author, so the queue's send of it is that author's turn

Main now lets Orca's own sends ask to queue (sendAgentTurn's 'queue' delivery),
so "every card is a person's" no longer holds by refusing host sends. Each card
records who wrote it (the submission's client/host vocabulary) in a new nullable
column; the drain records that origin, so a person's card ends a Stop's pause
and Orca's does not. /clear carries the author. Rows from before the column
read as a person's. The userSend-only admission gate is removed.

* docs(native-chat): state why an unrecorded card author reads as a person's

* fix(native-chat): a restart holds only cards written before it, and an idle held queue offers Resume

A restart's pause held every waiting card, including one a person typed after the restart while
Orca's own continuation ran, and nothing released it except a per-card Send. It now holds only
cards another host process wrote, the same way a Stop holds only cards queued before it.

The composer's primary button becomes Resume (Play) while nothing is typed, no turn runs and the
host holds a card Resume would send, whatever held it (Stop, restart or /clear). It calls the
existing agentSession.queuedMessagesResume, guarded against a second press in flight.

A card nothing holds keeps the run going between a turn's end and the queue's send of it, so its
Steer no longer flips to Send for the frame in between.

* fix(native-chat): the host publishes which pause holds each queued card

The host published one pause for the whole queue, so a client held every waiting card while it was
set. Between a turn's end and the queue's send of a card queued after a Stop or restart, the
composer could flash Resume and the cards Send, and a card queued after a Stop lost its
"Waiting for your answer" caption.

Each published card now carries an optional `heldBy`: the pause holding it, or null, derived from
the same rule the drain reads. A client holds only those cards; against a host without the field
it falls back to the queue-level pause.

* test(native-chat): Resume needs the queue capability and is disabled whenever Send is

* docs(native-chat): describe per-card holds in the queue contract and table comments

* fix(native-chat): the composer goes from Resume straight to Stop, and Resume returns focus

After Resume, the host lifts the hold in one update and sends the first card in a later one. In
between nothing was running, so the composer's button flashed a disabled Send. A card nothing
holds now keeps the queue's run going for the button too: an empty composer shows Stop, disabled
until the turn starts. Not when the host refuses every send (a rewind whose outcome is unknown,
read from its status), where nothing is coming. The same fix removes the Stop, Send, Stop flip
between queued turns.

Resume disables the button, which dropped keyboard focus; focus now returns to the composer.

* fix(native-chat): the host names the card its queue sends next, so the chat stays working across the gap

A turn's end, or a Resume, and the queue's send of the next card commit as two host updates. In
between nothing was running, so the working status, timer, pickers and composer button flipped
for one update. The client guessed the drain from its own copy of the host's gates, which missed a
/clear-replaced source and covered only the button.

The queue publication now carries `nextQueuedMessageId`: the drain's own next card through the
drain's own gate (`nextStructuredQueuedMessage`, which the drain step now calls), null whenever the
host would refuse the send. The client derives one fact, the queue is about to send, and every
working reader follows it; Stop stays disabled until a turn can be stopped. The client-side copy of
the gates and the status-feed rewind read are removed.

* test(native-chat): the queue's next card survives the coalescer, the reducer and a history page

* test(native-chat): build the snapshot that names the next card through its helper

* feat(native-chat): a held queue keeps its header row, and a new message asks before passing it

The queue's header row ("Queue paused because you interrupted", or Orca
restarted, or you cleared the conversation) comes back above the cards it
holds, with Resume; it names the oldest held card's pause, as the host
publishes it per card, and hides over cards held only on their own or
returned. The header's Resume and the composer's share one in-flight guard.

A held card reads Steer again whether or not a turn runs; a card held on its
own or returned keeps Send.

Sending a message while the header shows (Enter or the button) first asks
"Send message?": Clear queue deletes every card and then sends (a failed
delete sends nothing), Send message sends and keeps the cards, which follow
the new turn, and dismissing sends nothing and keeps the draft. Host
commands send as they are.

* fix(native-chat): the paused row goes while your own message is on its way to lift it

After "Send message" over a held queue, the row kept saying "Queue paused…"
until the agent accepted the new turn. The chat now reads that gap from the
outbox: while this composer's direct send is recorded by the host and not yet
accepted, the controller shows no paused row (and so no Resume or
confirmation). A refusal settles the entry and the row comes back, since the
hold did not lift. Orca's own sends never enter this outbox, and the queue's
send of a card goes under a fresh id, so neither hides it. Nothing is stored.

* fix(native-chat): a "Send message?" choice is taken once, and a failed Clear queue is one toast

The closing dialog stays mounted and clickable through its exit animation,
and a double-click or a held Enter lands twice before any re-render, so
Send message (or Clear queue) could send the captured message twice. The
pending send now lives in a ref that the first choice takes; a second one
finds nothing.

Clear queue deletes one card at a time and stops at the first failure, so a
failed press shows one toast instead of one per card.

The dialog keeps its compact width at desktop sizes and the primitive's
narrow-window gutter (`max-w-sm sm:max-w-sm`, as the other compact
confirmations).

* fix(native-chat): Clear queue's message goes out once, and keeps text typed while it waits

After Clear queue, the message waited in the composer while the cards were
deleted one by one. A second Enter in that window sent it again, and text
typed meanwhile was wiped when the chained send was accepted.

From the Clear queue choice until its message has gone out, the composer's
structured send does nothing. The chained send (and Send message's) now
carries the composition it was taken from, and the composer is cleared on
acceptance only if it still holds exactly that, as host commands already do.

Also: the v1 contract comment names `nextQueuedMessageId` and its absent-
means-null fallback, and the own-send check returns at once on an empty
outbox.

* fix(native-chat): the queue carries on after any turn, in order, and a restart sends nothing by itself

- Any accepted turn ends a Stop's or a /clear's pause, whoever sent it (a person,
  Orca's own messages, or the queue), and so does Resume. The card and submission
  author fields that only fed the old person-only rule are gone.
- The queue sends strictly in order: a card never overtakes a held one.
- After a restart nothing sends by itself and no paused row shows: the chat's next
  turn (the carry-on, or the person's own message) runs first, then the cards.
- Resume and "Send message?" are offered only while nothing runs and no prompt waits.

* fix(native-chat): after a restart no queue pause shows, and a card written before the next turn waits for it too

* fix(native-chat): a quit hands no queued card off, and the paused row goes while any turn that will lift it is on its way

- The queue stops handing cards off when the host tears down. A card sent during
  a quit was refused at close, and that refused send withdrew the chat's restart
  offer, so resuming after the relaunch sent nothing.
- The host publishes no pause while a turn sent after it (your message, Steer, or
  Orca's own) waits for the agent; a refusal shows it again. This replaces the
  client's own-send check.
- A card written after a restart is an ordinary card again: it waits while any
  card from before the restart still waits.

* refactor(native-chat): the host's paused-row-while-a-turn-is-on-its-way check in one expression

* refactor(native-chat): the composer's queue Resume rides the structured transport beside the held queue

* fix(native-chat): the "Send message?" choice ends with the pause it asked about; tests follow main's draft props

- The open dialog closes when the queue's pause lifts under it (Orca's mail, another client's
  Resume, any accepted turn): nothing is sent, the draft stays, and the next Enter sends as
  usual. The pending choice records the hold it was asked under; nothing new is stored.
- The composer-field Resume test passes main's dropScopeKey/draftScopeKey.
- The dialog test expects main's rule: only the sent text leaves the composer.

* test(mobile): a host-kept card's test stands in a Stop's pause, as this host publishes no restart pause

Main's #24660 test published queuePause 'restarted', which this branch's wire
type no longer lists, so the mobile tests typecheck ratchet failed.
2026-10-06 18:11:54 -07:00

431 lines
27 KiB
TypeScript

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,
STRUCTURED_AGENT_SESSION_RESUME_HISTORY_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'
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 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: 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
// 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_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_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