Files
orca/src/shared/agent-session-launch-args.ts
T
Brennan Benson f0b3f44f10 feat(agent-session): let the host own a chat's tab id and let a create reserve it (#22616)
* feat(agent-session): let the host own a chat's tab id and let a create reserve it

A structured chat's tab id was derived from its session id by every layer that
needed one: the renderer, the host snapshot and the status address each built
their own spelling. The join between a conversation and the tab that shows it
must be a pointer the host owns, not a derivation each client repeats.

The session record now carries surfaceTabId. A create pins it: the tab half of
the pane agent.launch reserved, an optional tabId on agentSession.create, or a
host-minted UUID. Records written before the field existed are backfilled at
open with the string clients derived, in memory at once and on disk with the
store's first transaction, so nothing keyed by it (read state, notification
ids, worker rows) moves on upgrade. A second record under a held id is refused.

Only the record and the two create wires change here. The snapshot still
publishes agent-session:<sid> and the renderer still derives its local id;
those move in the next two changes. agentSession.create is a strict object, so
the field is advertised as a capability a client checks before sending it.

* fix(agent-session): record the derived tab id for an unreserved create

A create that reserved no tab minted a random UUID that no reader uses: the
renderer, status address, worker rows and host-shared read state all still key
by structured-agent-session-<sid>. Persisted, that id would move every chat
created before readers switch to the recorded one, orphaning its read state
and worker rows the way the backfill exists to prevent. An unreserved create
now records the derived id, the same rule the backfill applies, so the record
always matches the prefix every existing key uses; an opaque mint belongs with
the change that moves the last reader.

Also:
- a chat tab id must be a host tab id on the record, the create wire and in
  admission, matching what agent.launch already requires of paneKey; a
  web-surface id would decode as another tab
- the stored launch-result guard checks the structured outcome's tabId
- comments no longer claim a retry naming another tab conflicts; replay keys
  on the attach fingerprint and answers with the recorded id (now pinned)
- the wire refusal test used a non-hex digest, so the schema refused it for
  that reason; it now reaches the tab id rule
- pin that the reload path refills the id without forcing a save

* test(agent-session): correct the tab-id fingerprint comment to match replay
2026-09-24 14:42:12 -07:00

15 lines
561 B
TypeScript

import type { AgentSessionLaunchArgs } from './agent-session-record'
const MAX_LAUNCH_ARGS = 256
const MAX_LAUNCH_ARGS_BYTES = 16 * 1024
/** Arguments pinned on a record's first reservation, bounded before they are persisted. */
export function isAgentSessionLaunchArgs(value: unknown): value is AgentSessionLaunchArgs {
return (
Array.isArray(value) &&
value.length <= MAX_LAUNCH_ARGS &&
value.every((arg) => typeof arg === 'string' && !arg.includes('\0')) &&
Buffer.byteLength(JSON.stringify(value), 'utf8') <= MAX_LAUNCH_ARGS_BYTES
)
}