From 5d374e967c90d50bb6ee686c4f2ecb2badd100e2 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 14:42:09 -0700 Subject: [PATCH 01/26] Add agent session identity to orchestration runs --- pnpm-lock.yaml | 19 +++++++++++++++++++ .../orchestration/db/row-column-lists.ts | 1 + .../orchestration/db/runs/run-create.ts | 11 +++++++++-- .../db/schema/create-core-tables-sql.ts | 1 + .../orchestration/db/schema/migrate-v2-v12.ts | 4 ++++ src/main/runtime/orchestration/types.ts | 2 ++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7add63b397bc..baf05a740844 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,9 @@ importers: .: configDependencies: {} packageManagerDependencies: + '@pnpm/exe': + specifier: 12.0.0 + version: 12.0.0 pnpm: specifier: 12.0.0 version: 12.0.0 @@ -56,6 +59,11 @@ packages: cpu: [x64] os: [win32] + '@pnpm/exe@12.0.0': + resolution: {integrity: sha512-405vw2qYPPghNxoPRt2cvkGtGNU5gnInDcIe5TztQxe73yUZLqFJN/jCJFGs1xbVaIWDXEVuQ71P8npFZeatZQ==} + engines: {node: '>=18.*'} + hasBin: true + pnpm@12.0.0: resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==} engines: {node: '>=18.*'} @@ -87,6 +95,17 @@ snapshots: '@pnpm/exe.win32-x64@12.0.0': optional: true + '@pnpm/exe@12.0.0': + optionalDependencies: + '@pnpm/exe.darwin-arm64': 12.0.0 + '@pnpm/exe.darwin-x64': 12.0.0 + '@pnpm/exe.linux-arm64': 12.0.0 + '@pnpm/exe.linux-arm64-musl': 12.0.0 + '@pnpm/exe.linux-x64': 12.0.0 + '@pnpm/exe.linux-x64-musl': 12.0.0 + '@pnpm/exe.win32-arm64': 12.0.0 + '@pnpm/exe.win32-x64': 12.0.0 + pnpm@12.0.0: optionalDependencies: '@pnpm/exe.darwin-arm64': 12.0.0 diff --git a/src/main/runtime/orchestration/db/row-column-lists.ts b/src/main/runtime/orchestration/db/row-column-lists.ts index 368c97ede3c2..8148885de068 100644 --- a/src/main/runtime/orchestration/db/row-column-lists.ts +++ b/src/main/runtime/orchestration/db/row-column-lists.ts @@ -12,6 +12,7 @@ export const RUN_COLUMNS = [ 'objective', 'home_database', 'coordinator_handle', + 'coordinator_agent_session_id', 'coordinator_pane_key', 'consumer_generation', 'legacy', diff --git a/src/main/runtime/orchestration/db/runs/run-create.ts b/src/main/runtime/orchestration/db/runs/run-create.ts index 3f99e0189362..efb56a8667ab 100644 --- a/src/main/runtime/orchestration/db/runs/run-create.ts +++ b/src/main/runtime/orchestration/db/runs/run-create.ts @@ -10,6 +10,7 @@ export function createRun( objective: string coordinatorHandle: string coordinatorPaneKey: string + coordinatorAgentSessionId?: string | null } ): RunRow { const id = generateId('run') @@ -19,11 +20,17 @@ export function createRun( this.db .prepare( `INSERT INTO runs ( - id, objective, coordinator_handle, coordinator_pane_key, + id, objective, coordinator_handle, coordinator_pane_key, coordinator_agent_session_id, consumer_generation, legacy ) VALUES (?, ?, ?, ?, 1, 0)` ) - .run(id, params.objective, params.coordinatorHandle, params.coordinatorPaneKey) + .run( + id, + params.objective, + params.coordinatorHandle, + params.coordinatorPaneKey, + params.coordinatorAgentSessionId ?? null + ) this.rememberRunCoordinatorHandle(id, params.coordinatorHandle) this.db.exec('COMMIT') } catch (error) { diff --git a/src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts b/src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts index f02e7e8c15ac..dee412c768c4 100644 --- a/src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts +++ b/src/main/runtime/orchestration/db/schema/create-core-tables-sql.ts @@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS runs ( objective TEXT NOT NULL, home_database TEXT NOT NULL DEFAULT 'this_database', coordinator_handle TEXT, + coordinator_agent_session_id TEXT, coordinator_pane_key TEXT, consumer_generation INTEGER NOT NULL DEFAULT 0, legacy INTEGER NOT NULL DEFAULT 0, diff --git a/src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts b/src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts index 140a41160303..ffa2c1179323 100644 --- a/src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts +++ b/src/main/runtime/orchestration/db/schema/migrate-v2-v12.ts @@ -2,6 +2,10 @@ import { LEGACY_RUN_ID } from '../contract-constants' import type { OrchestrationDb } from '../orchestration-db' export function applySchemaMigrationsV2ToV12(this: OrchestrationDb, current: number): void { + // Session identity is authoritative for orchestration; retain terminal columns during migration. + if (!this.hasColumn('runs', 'coordinator_agent_session_id')) { + this.db.exec('ALTER TABLE runs ADD COLUMN coordinator_agent_session_id TEXT') + } // v1 → v2: SQLite can't ALTER a CHECK, so rebuild messages to allow 'heartbeat'; fold in v3's delivered_at to skip a second rebuild. if (current < 2) { if (!this.hasColumn('dispatch_contexts', 'last_heartbeat_at')) { diff --git a/src/main/runtime/orchestration/types.ts b/src/main/runtime/orchestration/types.ts index 85d5dcfc1596..05fb7a22d1c7 100644 --- a/src/main/runtime/orchestration/types.ts +++ b/src/main/runtime/orchestration/types.ts @@ -45,6 +45,8 @@ export type RunRow = { objective: string home_database: string coordinator_handle: string | null + coordinator_agent_session_id: string | null + coordinator_agent_session_id: string | null coordinator_pane_key: string | null consumer_generation: number legacy: number From 1e6f94dbd288d660b54b741be3efe7350391dafb Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 14:44:36 -0700 Subject: [PATCH 02/26] Add agent session identity columns to orchestration records --- src/main/runtime/orchestration/db/contract-constants.ts | 2 +- src/main/runtime/orchestration/db/row-column-lists.ts | 2 ++ src/main/runtime/orchestration/db/schema/migrate-v41.ts | 9 +++++++++ src/main/runtime/orchestration/db/schema/migrate.ts | 2 ++ src/main/runtime/orchestration/types.ts | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/main/runtime/orchestration/db/schema/migrate-v41.ts diff --git a/src/main/runtime/orchestration/db/contract-constants.ts b/src/main/runtime/orchestration/db/contract-constants.ts index 47e0cd6165a0..4b49efb9d0b2 100644 --- a/src/main/runtime/orchestration/db/contract-constants.ts +++ b/src/main/runtime/orchestration/db/contract-constants.ts @@ -7,4 +7,4 @@ export const LEGACY_CONTRACT_VERSION = 0 export const CURRENT_CONTRACT_VERSION = ORCHESTRATION_CONTRACT_VERSION // Schema versions: v2 'heartbeat'+last_heartbeat_at, v3 delivered_at, v4 task-creator terminal, v5 task_title/display_name, v6 pane identity, v7 lightweight Runs, v8 crash-safe Run deliveries, v9 durable question threads, v10 Dispatch capabilities, v11 durable mutation receipts, v12 composed worker state, v18 post-v6 version-skew repair, v19 adopted legacy Runs and compatibility receipts, v20 legacy question backfill, v21 legacy scheduler-loss provenance, v22 dispatch assignee lookup, v23 worker terminal resource ownership, v24 creator-incarnation authority, v25 active Dispatch handle lookup, v26 indexed mutation receipt capacity, v27 durable federation acknowledgments, v28 durable local mutation caller identity, v31 dispatch/resource identity links, v32 bounded worker-terminal recovery metadata, v33 durable mailbox pointer Enter state, v34 role-addressed mailbox deliveries, v35 mailbox delivery default and index-predicate repair, v36 dispatch mailbox consumer generation, v37 recorded dispatch creator identity, v39 structured session journal archives. -export const SCHEMA_VERSION = 40 +export const SCHEMA_VERSION = 41 diff --git a/src/main/runtime/orchestration/db/row-column-lists.ts b/src/main/runtime/orchestration/db/row-column-lists.ts index 8148885de068..447f39ff86ed 100644 --- a/src/main/runtime/orchestration/db/row-column-lists.ts +++ b/src/main/runtime/orchestration/db/row-column-lists.ts @@ -13,6 +13,7 @@ export const RUN_COLUMNS = [ 'home_database', 'coordinator_handle', 'coordinator_agent_session_id', + 'coordinator_agent_session_id', 'coordinator_pane_key', 'consumer_generation', 'legacy', @@ -45,6 +46,7 @@ export const DISPATCH_CONTEXT_COLUMNS = [ 'contract_version', 'launch_token_hash', 'assignee_handle', + 'assignee_agent_session_id', 'assignee_pane_key', 'capability_hash', 'process_incarnation', diff --git a/src/main/runtime/orchestration/db/schema/migrate-v41.ts b/src/main/runtime/orchestration/db/schema/migrate-v41.ts new file mode 100644 index 000000000000..843f12dbb114 --- /dev/null +++ b/src/main/runtime/orchestration/db/schema/migrate-v41.ts @@ -0,0 +1,9 @@ +import type { OrchestrationDb } from '../orchestration-db' + +export function migrateV41(this: OrchestrationDb, current: number): void { + if (current >= 41) { + return + } + this.db.exec('ALTER TABLE runs ADD COLUMN coordinator_agent_session_id TEXT') + this.db.exec('ALTER TABLE dispatch_contexts ADD COLUMN assignee_agent_session_id TEXT') +} diff --git a/src/main/runtime/orchestration/db/schema/migrate.ts b/src/main/runtime/orchestration/db/schema/migrate.ts index 582dedf4752c..33362e595a24 100644 --- a/src/main/runtime/orchestration/db/schema/migrate.ts +++ b/src/main/runtime/orchestration/db/schema/migrate.ts @@ -11,6 +11,7 @@ import { migrateV37 } from './migrate-v37' import { migrateV38 } from './migrate-v38' import { migrateV39 } from './migrate-v39' import { migrateV40 } from './migrate-v40' +import { migrateV41 } from './migrate-v41' // Why: CREATE TABLE IF NOT EXISTS won't alter existing DBs; migrate in a txn that bumps user_version only on success (atomic all-or-nothing). export function migrate(this: OrchestrationDb): void { @@ -32,6 +33,7 @@ export function migrate(this: OrchestrationDb): void { migrateV38.call(this, current) migrateV39.call(this, current) migrateV40.call(this, current) + migrateV41.call(this, current) this.createMailboxDeliveryIndexesIfPossible() this.db.pragma(`user_version = ${SCHEMA_VERSION}`) this.db.exec('COMMIT') diff --git a/src/main/runtime/orchestration/types.ts b/src/main/runtime/orchestration/types.ts index 05fb7a22d1c7..a3e63effe61a 100644 --- a/src/main/runtime/orchestration/types.ts +++ b/src/main/runtime/orchestration/types.ts @@ -273,6 +273,7 @@ export type TaskRow = { } export type DispatchContextRow = { + assignee_agent_session_id: string | null id: string run_id: string task_id: string From 7817970bc58eb1a434dd29df0124aab21c6f34dc Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:02:07 -0700 Subject: [PATCH 03/26] Fix run creation values for agent session identity --- src/main/runtime/orchestration/db/runs/run-create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/runtime/orchestration/db/runs/run-create.ts b/src/main/runtime/orchestration/db/runs/run-create.ts index efb56a8667ab..9a565da9c252 100644 --- a/src/main/runtime/orchestration/db/runs/run-create.ts +++ b/src/main/runtime/orchestration/db/runs/run-create.ts @@ -22,7 +22,7 @@ export function createRun( `INSERT INTO runs ( id, objective, coordinator_handle, coordinator_pane_key, coordinator_agent_session_id, consumer_generation, legacy - ) VALUES (?, ?, ?, ?, 1, 0)` + ) VALUES (?, ?, ?, ?, ?, 1, 0)` ) .run( id, From f80202be1ee121fa34079db4a287ba4c5871fb0a Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:04:28 -0700 Subject: [PATCH 04/26] Resolve run mailbox owners by agent session id --- .../runtime/orchestration/db/runs/run-lookup.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/runtime/orchestration/db/runs/run-lookup.ts b/src/main/runtime/orchestration/db/runs/run-lookup.ts index 7563effa8c3d..56ecf752740f 100644 --- a/src/main/runtime/orchestration/db/runs/run-lookup.ts +++ b/src/main/runtime/orchestration/db/runs/run-lookup.ts @@ -65,6 +65,18 @@ export function getRunMailboxOwnerIdsForHandle( return [...new Set(runIds)].sort() } +/** Resolves mailbox ownership by durable agent session identity. */ +export function getRunMailboxOwnerIdsForAgentSession( + this: OrchestrationDb, + agentSessionId: string +): string[] { + return ( + this.db + .prepare('SELECT id FROM runs WHERE legacy = 0 AND coordinator_agent_session_id = ?') + .all(agentSessionId) as { id: string }[] + ).map((row) => row.id) +} + export function listRuns( this: OrchestrationDb, params: { limit?: number; cursor?: string } = {} @@ -160,6 +172,7 @@ export type RunLookupMethods = { getRun: typeof getRun getLegacyAdoptedRunMailboxOwner: typeof getLegacyAdoptedRunMailboxOwner getRunMailboxOwnerIdsForHandle: typeof getRunMailboxOwnerIdsForHandle + getRunMailboxOwnerIdsForAgentSession: typeof getRunMailboxOwnerIdsForAgentSession listRuns: typeof listRuns getCurrentRunForPane: typeof getCurrentRunForPane runsBoundToPane: typeof runsBoundToPane @@ -174,6 +187,7 @@ export function attachRunLookup(ctor: { prototype: object }): void { getRun, getLegacyAdoptedRunMailboxOwner, getRunMailboxOwnerIdsForHandle, + getRunMailboxOwnerIdsForAgentSession, listRuns, getCurrentRunForPane, runsBoundToPane, From d4ee9028a1ef20d2067b80a655913a2b5b86ced8 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:04:51 -0700 Subject: [PATCH 05/26] fix orchestration session identity column projections --- src/main/runtime/orchestration/db/row-column-lists.ts | 1 - src/main/runtime/orchestration/types.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/runtime/orchestration/db/row-column-lists.ts b/src/main/runtime/orchestration/db/row-column-lists.ts index 447f39ff86ed..32ae818a9c22 100644 --- a/src/main/runtime/orchestration/db/row-column-lists.ts +++ b/src/main/runtime/orchestration/db/row-column-lists.ts @@ -13,7 +13,6 @@ export const RUN_COLUMNS = [ 'home_database', 'coordinator_handle', 'coordinator_agent_session_id', - 'coordinator_agent_session_id', 'coordinator_pane_key', 'consumer_generation', 'legacy', diff --git a/src/main/runtime/orchestration/types.ts b/src/main/runtime/orchestration/types.ts index a3e63effe61a..cac74f4da24c 100644 --- a/src/main/runtime/orchestration/types.ts +++ b/src/main/runtime/orchestration/types.ts @@ -46,7 +46,6 @@ export type RunRow = { home_database: string coordinator_handle: string | null coordinator_agent_session_id: string | null - coordinator_agent_session_id: string | null coordinator_pane_key: string | null consumer_generation: number legacy: number From a0cfed369ae1963f9dffae999be802322f16c26a Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:05:10 -0700 Subject: [PATCH 06/26] Persist dispatch assignee agent session identity --- .../orchestration/db/dispatch-row-writer.ts | 5 ++++- .../db/schema/create-graph-tables-sql.ts | 2 ++ .../runtime/orchestration/db/schema/migrate-v41.ts | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/runtime/orchestration/db/dispatch-row-writer.ts b/src/main/runtime/orchestration/db/dispatch-row-writer.ts index 84862ee343d2..9ee6cdf7fa7d 100644 --- a/src/main/runtime/orchestration/db/dispatch-row-writer.ts +++ b/src/main/runtime/orchestration/db/dispatch-row-writer.ts @@ -14,7 +14,7 @@ import { DISPATCH_PANE_KEY_MATCH_SUFFIX_SQL } from './pane-key-match' export const DISPATCH_CONTEXT_CLAIM_SQL = `INSERT INTO dispatch_contexts ( id, run_id, task_id, contract_version, launch_token_hash, - assignee_handle, assignee_pane_key, process_incarnation, + assignee_handle, assignee_agent_session_id, assignee_pane_key, process_incarnation, creator_dispatch_id, creator_handle, creator_pane_key, status, failure_count, depth, dispatched_at ) @@ -69,6 +69,7 @@ export function claimDispatchContextRow( contractVersion: number launchTokenHash: string | null assigneeHandle: string + assigneeAgentSessionId?: string | null assigneePaneKey: string | null processIncarnation: string | null creatorDispatchId?: string | null @@ -88,6 +89,7 @@ export function claimDispatchContextRow( params.contractVersion, params.launchTokenHash, params.assigneeHandle, + params.assigneeAgentSessionId ?? null, params.assigneePaneKey, params.processIncarnation, params.creatorDispatchId ?? null, @@ -97,6 +99,7 @@ export function claimDispatchContextRow( params.depth, params.taskId, params.assigneeHandle, + params.assigneeAgentSessionId ?? null, params.assigneePaneKey, params.assigneePaneKey, params.paneSuffix, diff --git a/src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts b/src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts index 0897cb852de2..263756e2c45c 100644 --- a/src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts +++ b/src/main/runtime/orchestration/db/schema/create-graph-tables-sql.ts @@ -143,6 +143,7 @@ CREATE TABLE IF NOT EXISTS dispatch_contexts ( contract_version INTEGER NOT NULL DEFAULT ${CURRENT_CONTRACT_VERSION}, launch_token_hash TEXT, assignee_handle TEXT, + assignee_agent_session_id TEXT, assignee_pane_key TEXT, capability_hash TEXT, process_incarnation TEXT, @@ -205,6 +206,7 @@ CREATE TABLE IF NOT EXISTS coordinator_runs ( status TEXT NOT NULL DEFAULT 'idle' CHECK(status IN ('idle', 'running', 'completed', 'failed')), coordinator_handle TEXT NOT NULL, + coordinator_agent_session_id TEXT, poll_interval_ms INTEGER NOT NULL DEFAULT 2000, created_at TEXT NOT NULL DEFAULT (datetime('now')), completed_at TEXT, diff --git a/src/main/runtime/orchestration/db/schema/migrate-v41.ts b/src/main/runtime/orchestration/db/schema/migrate-v41.ts index 843f12dbb114..ac1372cd581b 100644 --- a/src/main/runtime/orchestration/db/schema/migrate-v41.ts +++ b/src/main/runtime/orchestration/db/schema/migrate-v41.ts @@ -4,6 +4,16 @@ export function migrateV41(this: OrchestrationDb, current: number): void { if (current >= 41) { return } - this.db.exec('ALTER TABLE runs ADD COLUMN coordinator_agent_session_id TEXT') - this.db.exec('ALTER TABLE dispatch_contexts ADD COLUMN assignee_agent_session_id TEXT') + for (const statement of [ + 'ALTER TABLE runs ADD COLUMN coordinator_agent_session_id TEXT', + 'ALTER TABLE dispatch_contexts ADD COLUMN assignee_agent_session_id TEXT' + ]) { + try { + this.db.exec(statement) + } catch (error) { + if (!(error instanceof Error) || !error.message.includes('duplicate column name')) { + throw error + } + } + } } From bb73d36a9d203f3a0fa932d080636417d25bbdc2 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:06:56 -0700 Subject: [PATCH 07/26] fix dispatch session identity propagation --- .../db/dispatch-context/dispatch-context-store.ts | 2 ++ src/main/runtime/orchestration/db/dispatch-row-writer.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/runtime/orchestration/db/dispatch-context/dispatch-context-store.ts b/src/main/runtime/orchestration/db/dispatch-context/dispatch-context-store.ts index 38905f30434d..a285b1805fe2 100644 --- a/src/main/runtime/orchestration/db/dispatch-context/dispatch-context-store.ts +++ b/src/main/runtime/orchestration/db/dispatch-context/dispatch-context-store.ts @@ -15,6 +15,7 @@ export function createDispatchContext( params: { taskId: string assigneeHandle: string + assigneeAgentSessionId?: string | null // Why: pane key is the remint-stable identity behind the handle — lets worker_done ownership survive handle reissue. assigneePaneKey?: string launchTokenHash?: string @@ -64,6 +65,7 @@ export function createDispatchContext( contractVersion: CURRENT_CONTRACT_VERSION, launchTokenHash: launchTokenHash ?? null, assigneeHandle, + assigneeAgentSessionId: params.assigneeAgentSessionId, assigneePaneKey: assigneePaneKey ?? null, processIncarnation: processIncarnation ?? null, creatorDispatchId, diff --git a/src/main/runtime/orchestration/db/dispatch-row-writer.ts b/src/main/runtime/orchestration/db/dispatch-row-writer.ts index 9ee6cdf7fa7d..41b6a724e817 100644 --- a/src/main/runtime/orchestration/db/dispatch-row-writer.ts +++ b/src/main/runtime/orchestration/db/dispatch-row-writer.ts @@ -18,7 +18,7 @@ export const DISPATCH_CONTEXT_CLAIM_SQL = `INSERT INTO dispatch_contexts ( creator_dispatch_id, creator_handle, creator_pane_key, status, failure_count, depth, dispatched_at ) -SELECT ?, run_id, id, ?, ?, ?, ?, ?, ?, ?, ?, 'dispatched', ?, ?, datetime('now') +SELECT ?, run_id, id, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'dispatched', ?, ?, datetime('now') FROM tasks WHERE id = ? AND status = 'ready' AND NOT EXISTS ( @@ -99,7 +99,6 @@ export function claimDispatchContextRow( params.depth, params.taskId, params.assigneeHandle, - params.assigneeAgentSessionId ?? null, params.assigneePaneKey, params.assigneePaneKey, params.paneSuffix, From 7062cfaf1016f82d369968213db50fb0f4ccf960 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:46:02 -0700 Subject: [PATCH 08/26] fix optional legacy orchestration session columns --- src/main/runtime/orchestration/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/runtime/orchestration/types.ts b/src/main/runtime/orchestration/types.ts index cac74f4da24c..41cc6cd2ca28 100644 --- a/src/main/runtime/orchestration/types.ts +++ b/src/main/runtime/orchestration/types.ts @@ -45,7 +45,7 @@ export type RunRow = { objective: string home_database: string coordinator_handle: string | null - coordinator_agent_session_id: string | null + coordinator_agent_session_id?: string | null coordinator_pane_key: string | null consumer_generation: number legacy: number @@ -272,7 +272,7 @@ export type TaskRow = { } export type DispatchContextRow = { - assignee_agent_session_id: string | null + assignee_agent_session_id?: string | null id: string run_id: string task_id: string From 5e3df531ef08b208867ac88a63dbaae92e42edc2 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:47:01 -0700 Subject: [PATCH 09/26] Expose agent session identity from orchestration environment --- src/cli/handlers/orchestration/terminal-identity.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/cli/handlers/orchestration/terminal-identity.ts b/src/cli/handlers/orchestration/terminal-identity.ts index 505bfaac2623..9b9984892291 100644 --- a/src/cli/handlers/orchestration/terminal-identity.ts +++ b/src/cli/handlers/orchestration/terminal-identity.ts @@ -48,6 +48,17 @@ export async function resolveOrchestrationTerminalHandle( return await getTerminalHandle(flags, cwd, client) } +/** Durable orchestration identity propagated to PTY and native chat providers. */ +export function resolveOrchestrationAgentSessionId(): string | undefined { + const value = process.env.ORCA_AGENT_SESSION_ID + return value && value.length > 0 ? value : undefined +} + +export function resolveOrchestrationRuntimeFence(): string | undefined { + const value = process.env.ORCA_AGENT_SESSION_RUNTIME_FENCE + return value && value.length > 0 ? value : undefined +} + /** * Whether the handle this process was born with still names a live identity. * From 431520447d49191dcb5924b953f53219e4616259 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:49:31 -0700 Subject: [PATCH 10/26] allow native session runs without terminal projections --- .../runtime/orchestration/db/runs/run-create.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/runtime/orchestration/db/runs/run-create.ts b/src/main/runtime/orchestration/db/runs/run-create.ts index 9a565da9c252..a9ea2ab9deec 100644 --- a/src/main/runtime/orchestration/db/runs/run-create.ts +++ b/src/main/runtime/orchestration/db/runs/run-create.ts @@ -8,15 +8,17 @@ export function createRun( this: OrchestrationDb, params: { objective: string - coordinatorHandle: string - coordinatorPaneKey: string + coordinatorHandle?: string | null + coordinatorPaneKey?: string | null coordinatorAgentSessionId?: string | null } ): RunRow { const id = generateId('run') this.db.exec('BEGIN IMMEDIATE') try { - this.unbindOtherRunsForPane(params.coordinatorPaneKey) + if (params.coordinatorPaneKey) { + this.unbindOtherRunsForPane(params.coordinatorPaneKey) + } this.db .prepare( `INSERT INTO runs ( @@ -27,11 +29,13 @@ export function createRun( .run( id, params.objective, - params.coordinatorHandle, - params.coordinatorPaneKey, + params.coordinatorHandle ?? null, + params.coordinatorPaneKey ?? null, params.coordinatorAgentSessionId ?? null ) - this.rememberRunCoordinatorHandle(id, params.coordinatorHandle) + if (params.coordinatorHandle) { + this.rememberRunCoordinatorHandle(id, params.coordinatorHandle) + } this.db.exec('COMMIT') } catch (error) { this.db.exec('ROLLBACK') From ebd6360ba9835483108c6bf13ea63338f180693b Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:51:48 -0700 Subject: [PATCH 11/26] allow native session coordinators to create runs --- .../handlers/orchestration/run-handlers.ts | 13 ++++++--- .../rpc/methods/orchestration/runs/runs.ts | 27 ++++++++++++------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index 7c77913df158..5072de9a2c94 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -7,16 +7,23 @@ import { } from '../../flags' import { ORCHESTRATION_RUN_PAGE_LIMIT } from '../../../shared/orchestration-run-pagination' import { callOrchestrationMutation } from './mutation-request' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { + resolveCoordinatorTerminalHandle, + resolveOrchestrationAgentSessionId +} from './terminal-identity' export const ORCHESTRATION_RUN_HANDLERS: Record = { 'orchestration run-create': async ({ flags, client, cwd, json }) => { - const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const agentSessionId = resolveOrchestrationAgentSessionId() + const from = agentSessionId + ? undefined + : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await callOrchestrationMutation<{ run: { id: string; objective: string; consumer_generation: number } }>(client, flags, 'orchestration.runCreate', { objective: getRequiredStringFlag(flags, 'objective'), - from + ...(from ? { from } : {}), + ...(agentSessionId ? { agentSessionId } : {}) }) printResult(result, json, (r) => `Run ${r.run.id} created and bound: ${r.run.objective}`) }, diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index 77bcea4924c5..546e197ca956 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -8,7 +8,8 @@ import { exposeRun } from './run-receipt' const RunCreateParams = z.object({ objective: requiredString('Missing --objective'), - from: requiredString('Missing coordinator terminal') + from: OptionalString, + agentSessionId: OptionalString }) const RunUseParams = z.object({ @@ -29,19 +30,27 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ name: 'orchestration.runCreate', params: RunCreateParams, handler: (params, { orchestrationCompatibilityEvidence, runtime }) => { - const paneKey = resolveOrchestrationCaller(runtime, { - callerTerminalHandle: params.from, - callerEvidence: orchestrationCompatibilityEvidence, - requireStablePane: true - }) + const paneKey = params.from + ? resolveOrchestrationCaller(runtime, { + callerTerminalHandle: params.from, + callerEvidence: orchestrationCompatibilityEvidence, + requireStablePane: true + }) + : null + if (!params.from && !params.agentSessionId) { + throw new OrchestrationError('stable_pane_required', 'Missing coordinator identity.') + } const db = runtime.getOrchestrationDb() - const priorRun = db.getCurrentRunForPane(paneKey) + const priorRun = paneKey ? db.getCurrentRunForPane(paneKey) : undefined const run = db.createRun({ objective: params.objective, coordinatorHandle: params.from, - coordinatorPaneKey: paneKey + coordinatorPaneKey: paneKey, + coordinatorAgentSessionId: params.agentSessionId }) - runtime.cancelMessageWaiters(params.from) + if (params.from) { + runtime.cancelMessageWaiters(params.from) + } if (priorRun) { runtime.cancelMessageWaiters(`run:${priorRun.id}`) } From 281e4a1d3595248a7cf4c0d2d11e880a550b84e7 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:52:10 -0700 Subject: [PATCH 12/26] Revert "allow native session coordinators to create runs" This reverts commit ebd6360ba9835483108c6bf13ea63338f180693b. --- .../handlers/orchestration/run-handlers.ts | 13 +++------ .../rpc/methods/orchestration/runs/runs.ts | 27 +++++++------------ 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index 5072de9a2c94..7c77913df158 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -7,23 +7,16 @@ import { } from '../../flags' import { ORCHESTRATION_RUN_PAGE_LIMIT } from '../../../shared/orchestration-run-pagination' import { callOrchestrationMutation } from './mutation-request' -import { - resolveCoordinatorTerminalHandle, - resolveOrchestrationAgentSessionId -} from './terminal-identity' +import { resolveCoordinatorTerminalHandle } from './terminal-identity' export const ORCHESTRATION_RUN_HANDLERS: Record = { 'orchestration run-create': async ({ flags, client, cwd, json }) => { - const agentSessionId = resolveOrchestrationAgentSessionId() - const from = agentSessionId - ? undefined - : await resolveCoordinatorTerminalHandle(flags, cwd, client) + const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await callOrchestrationMutation<{ run: { id: string; objective: string; consumer_generation: number } }>(client, flags, 'orchestration.runCreate', { objective: getRequiredStringFlag(flags, 'objective'), - ...(from ? { from } : {}), - ...(agentSessionId ? { agentSessionId } : {}) + from }) printResult(result, json, (r) => `Run ${r.run.id} created and bound: ${r.run.objective}`) }, diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index 546e197ca956..77bcea4924c5 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -8,8 +8,7 @@ import { exposeRun } from './run-receipt' const RunCreateParams = z.object({ objective: requiredString('Missing --objective'), - from: OptionalString, - agentSessionId: OptionalString + from: requiredString('Missing coordinator terminal') }) const RunUseParams = z.object({ @@ -30,27 +29,19 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ name: 'orchestration.runCreate', params: RunCreateParams, handler: (params, { orchestrationCompatibilityEvidence, runtime }) => { - const paneKey = params.from - ? resolveOrchestrationCaller(runtime, { - callerTerminalHandle: params.from, - callerEvidence: orchestrationCompatibilityEvidence, - requireStablePane: true - }) - : null - if (!params.from && !params.agentSessionId) { - throw new OrchestrationError('stable_pane_required', 'Missing coordinator identity.') - } + const paneKey = resolveOrchestrationCaller(runtime, { + callerTerminalHandle: params.from, + callerEvidence: orchestrationCompatibilityEvidence, + requireStablePane: true + }) const db = runtime.getOrchestrationDb() - const priorRun = paneKey ? db.getCurrentRunForPane(paneKey) : undefined + const priorRun = db.getCurrentRunForPane(paneKey) const run = db.createRun({ objective: params.objective, coordinatorHandle: params.from, - coordinatorPaneKey: paneKey, - coordinatorAgentSessionId: params.agentSessionId + coordinatorPaneKey: paneKey }) - if (params.from) { - runtime.cancelMessageWaiters(params.from) - } + runtime.cancelMessageWaiters(params.from) if (priorRun) { runtime.cancelMessageWaiters(`run:${priorRun.id}`) } From b79c81c3279a4f647e469bfea54ea29db92d223c Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:53:28 -0700 Subject: [PATCH 13/26] authenticate native session run creation --- .../handlers/orchestration/run-handlers.ts | 13 ++++++++--- .../methods/orchestration/runs/run-scope.ts | 17 ++++++++++++++ .../rpc/methods/orchestration/runs/runs.ts | 22 +++++++++++++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index 7c77913df158..847f6ebece1d 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -7,16 +7,23 @@ import { } from '../../flags' import { ORCHESTRATION_RUN_PAGE_LIMIT } from '../../../shared/orchestration-run-pagination' import { callOrchestrationMutation } from './mutation-request' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { + resolveCoordinatorTerminalHandle, + resolveOrchestrationAgentSessionId, + resolveOrchestrationRuntimeFence +} from './terminal-identity' export const ORCHESTRATION_RUN_HANDLERS: Record = { 'orchestration run-create': async ({ flags, client, cwd, json }) => { - const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const sessionId = resolveOrchestrationAgentSessionId() + const fence = resolveOrchestrationRuntimeFence() + const from = sessionId ? undefined : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await callOrchestrationMutation<{ run: { id: string; objective: string; consumer_generation: number } }>(client, flags, 'orchestration.runCreate', { objective: getRequiredStringFlag(flags, 'objective'), - from + ...(from ? { from } : {}), + ...(sessionId ? { agentSessionId: sessionId, runtimeFence: Number(fence) } : {}) }) printResult(result, json, (r) => `Run ${r.run.id} created and bound: ${r.run.objective}`) }, diff --git a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts index 6b0667233151..027a4a474269 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts @@ -6,6 +6,23 @@ import type { OrcaRuntimeService, OrchestrationCompatibilityCallerAuthority } from '../../../../orca-runtime' +import { structuredWorkerIdentities } from '../../../../structured-worker-identity' +import { resolveStructuredWorkerAuthority } from '../../../../structured-worker-authority' + +export function resolveNativeCoordinatorSession( + runtime: OrcaRuntimeService, + sessionId: string, + runtimeFence: number +): { sessionId: string; worktreeId: string } { + const identity = structuredWorkerIdentities.getBySessionId(sessionId) + const authority = identity + ? resolveStructuredWorkerAuthority(identity.handle, runtime.getOrchestrationDb()) + : null + if (!authority || authority.record.lease.runtimeFence !== runtimeFence) { + throw new OrchestrationError('consumer_fenced', 'The native session lease is not current.') + } + return { sessionId, worktreeId: authority.identity.worktreeId } +} export type RunScopeParams = { runId?: string diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index 77bcea4924c5..dae7d1b2bbee 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -3,12 +3,18 @@ import { defineMethod, type RpcMethod } from '../../../core' import { OptionalBoolean, OptionalString, requiredString } from '../../../schemas' import { ORCHESTRATION_RUN_PAGE_LIMIT } from '../../../../../../shared/orchestration-run-pagination' import { OrchestrationError } from '../../../../orchestration/orchestration-error' -import { assertCallerHandleMatchesEvidence, resolveOrchestrationCaller } from './run-scope' +import { + assertCallerHandleMatchesEvidence, + resolveNativeCoordinatorSession, + resolveOrchestrationCaller +} from './run-scope' import { exposeRun } from './run-receipt' const RunCreateParams = z.object({ objective: requiredString('Missing --objective'), - from: requiredString('Missing coordinator terminal') + from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional() }) const RunUseParams = z.object({ @@ -29,6 +35,18 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ name: 'orchestration.runCreate', params: RunCreateParams, handler: (params, { orchestrationCompatibilityEvidence, runtime }) => { + if (params.agentSessionId) { + if (params.runtimeFence === undefined) { + throw new OrchestrationError('consumer_fenced', 'Missing native session lease fence.') + } + resolveNativeCoordinatorSession(runtime, params.agentSessionId, params.runtimeFence) + const db = runtime.getOrchestrationDb() + const run = db.createRun({ + objective: params.objective, + coordinatorAgentSessionId: params.agentSessionId + }) + return { run: exposeRun(run) } + } const paneKey = resolveOrchestrationCaller(runtime, { callerTerminalHandle: params.from, callerEvidence: orchestrationCompatibilityEvidence, From e724beab8e23ebc6db0c09ed8fd55cbcbab06212 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:54:06 -0700 Subject: [PATCH 14/26] fix native run authority type narrowing --- .../runtime/rpc/methods/orchestration/runs/runs.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index dae7d1b2bbee..9a1023ad48b1 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -47,8 +47,12 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ }) return { run: exposeRun(run) } } + const coordinatorHandle = params.from + if (!coordinatorHandle) { + throw new OrchestrationError('stable_pane_required', 'Missing coordinator identity.') + } const paneKey = resolveOrchestrationCaller(runtime, { - callerTerminalHandle: params.from, + callerTerminalHandle: coordinatorHandle, callerEvidence: orchestrationCompatibilityEvidence, requireStablePane: true }) @@ -56,10 +60,10 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ const priorRun = db.getCurrentRunForPane(paneKey) const run = db.createRun({ objective: params.objective, - coordinatorHandle: params.from, + coordinatorHandle, coordinatorPaneKey: paneKey }) - runtime.cancelMessageWaiters(params.from) + runtime.cancelMessageWaiters(coordinatorHandle) if (priorRun) { runtime.cancelMessageWaiters(`run:${priorRun.id}`) } From f3e111842ff0288a0d7372743cf08b61c4b0daa7 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:56:01 -0700 Subject: [PATCH 15/26] support authenticated native run rebinding --- .../handlers/orchestration/run-handlers.ts | 7 +++-- .../orchestration/db/runs/run-binding.ts | 27 +++++++++++++++++-- .../rpc/methods/orchestration/runs/runs.ts | 21 ++++++++++++++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index 847f6ebece1d..d40c8c97673f 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -29,12 +29,15 @@ export const ORCHESTRATION_RUN_HANDLERS: Record = { }, 'orchestration run-use': async ({ flags, client, cwd, json }) => { - const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const sessionId = resolveOrchestrationAgentSessionId() + const fence = resolveOrchestrationRuntimeFence() + const from = sessionId ? undefined : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await callOrchestrationMutation<{ run: { id: string; objective: string; consumer_generation: number } }>(client, flags, 'orchestration.runUse', { id: getRequiredStringFlag(flags, 'id'), - from, + ...(from ? { from } : {}), + ...(sessionId ? { agentSessionId: sessionId, runtimeFence: Number(fence) } : {}), ...(flags.has('takeover-legacy') ? { takeoverLegacy: true } : {}) }) printResult(result, json, (r) => `Using Run ${r.run.id}: ${r.run.objective}`) diff --git a/src/main/runtime/orchestration/db/runs/run-binding.ts b/src/main/runtime/orchestration/db/runs/run-binding.ts index a4110dd589f9..4bc65e137378 100644 --- a/src/main/runtime/orchestration/db/runs/run-binding.ts +++ b/src/main/runtime/orchestration/db/runs/run-binding.ts @@ -8,8 +8,9 @@ export function bindRun( this: OrchestrationDb, params: { runId: string - coordinatorHandle: string - coordinatorPaneKey: string + coordinatorHandle?: string | null + coordinatorPaneKey?: string | null + coordinatorAgentSessionId?: string | null takeoverLegacy?: boolean legacyCoordinatorAuthority?: { runId: string @@ -27,6 +28,28 @@ export function bindRun( this.db.exec('ROLLBACK') return undefined } + if ( + params.coordinatorAgentSessionId && + !params.coordinatorHandle && + !params.coordinatorPaneKey + ) { + this.db + .prepare( + `UPDATE runs SET coordinator_agent_session_id = ?, consumer_generation = consumer_generation + 1, + updated_at = datetime('now') WHERE id = ?` + ) + .run(params.coordinatorAgentSessionId, params.runId) + this.fenceOutstandingDelivery(params.runId) + this.db.exec('COMMIT') + return this.getRun(params.runId) + } + if (!params.coordinatorHandle || !params.coordinatorPaneKey) { + this.db.exec('ROLLBACK') + throw new OrchestrationError( + 'stable_pane_required', + 'A terminal binding requires handle and pane.' + ) + } const sameBinding = run.coordinator_pane_key !== null && isEquivalentPaneKey(run.coordinator_pane_key, params.coordinatorPaneKey) diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index 9a1023ad48b1..db00cac3b5bc 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -19,7 +19,9 @@ const RunCreateParams = z.object({ const RunUseParams = z.object({ id: requiredString('Missing --id'), - from: requiredString('Missing coordinator terminal'), + from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional(), takeoverLegacy: OptionalBoolean }) @@ -82,6 +84,23 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ orchestrationCompatibilityCallerAuthority: callerAuthority } ) => { + if (params.agentSessionId) { + if (params.runtimeFence === undefined) { + throw new OrchestrationError('consumer_fenced', 'Missing native session lease fence.') + } + resolveNativeCoordinatorSession(runtime, params.agentSessionId, params.runtimeFence) + const run = runtime.getOrchestrationDb().bindRun({ + runId: params.id, + coordinatorAgentSessionId: params.agentSessionId + }) + if (!run) { + throw new OrchestrationError('run_not_found', `Run ${params.id} was not found.`) + } + return { run: exposeRun(run) } + } + if (!params.from) { + throw new OrchestrationError('stable_pane_required', 'Missing coordinator identity.') + } const paneKey = resolveOrchestrationCaller(runtime, { callerTerminalHandle: params.from, callerEvidence: orchestrationCompatibilityEvidence, From 168053449aa8edf029a1d91112ee1207076ac6c6 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 15:57:40 -0700 Subject: [PATCH 16/26] resolve native current runs by session identity --- src/cli/handlers/orchestration/run-handlers.ts | 8 ++++++-- .../runtime/orchestration/db/runs/run-lookup.ts | 15 +++++++++++++++ .../rpc/methods/orchestration/runs/runs.ts | 9 ++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index d40c8c97673f..ee1efa87895e 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -44,10 +44,14 @@ export const ORCHESTRATION_RUN_HANDLERS: Record = { }, 'orchestration run-current': async ({ flags, client, cwd, json }) => { - const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const sessionId = resolveOrchestrationAgentSessionId() + const from = sessionId ? undefined : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await client.call<{ run: { id: string; objective: string } | null - }>('orchestration.runCurrent', { from }) + }>('orchestration.runCurrent', { + ...(from ? { from } : {}), + ...(sessionId ? { agentSessionId: sessionId } : {}) + }) printResult(result, json, (r) => r.run ? `${r.run.id} ${r.run.objective}` : 'No Run is bound to this terminal.' ) diff --git a/src/main/runtime/orchestration/db/runs/run-lookup.ts b/src/main/runtime/orchestration/db/runs/run-lookup.ts index 56ecf752740f..d64d01e60e21 100644 --- a/src/main/runtime/orchestration/db/runs/run-lookup.ts +++ b/src/main/runtime/orchestration/db/runs/run-lookup.ts @@ -119,6 +119,19 @@ export function getCurrentRunForPane(this: OrchestrationDb, paneKey: string): Ru return run ? exposeRunTimestamps(run) : undefined } +export function getCurrentRunForAgentSession( + this: OrchestrationDb, + agentSessionId: string +): RunRow | undefined { + const run = this.db + .prepare( + `SELECT * FROM runs WHERE legacy = 0 AND coordinator_agent_session_id = ? + ORDER BY updated_at DESC, id DESC LIMIT 1` + ) + .get(agentSessionId) as RunRow | undefined + return run ? exposeRunTimestamps(run) : undefined +} + // Why: the indexed suffix only narrows candidates; isEquivalentPaneKey still decides, so // reminted tab halves keep matching and unparseable keys keep requiring an exact match. export function runsBoundToPane(this: OrchestrationDb, paneKey: string): RunRow[] { @@ -175,6 +188,7 @@ export type RunLookupMethods = { getRunMailboxOwnerIdsForAgentSession: typeof getRunMailboxOwnerIdsForAgentSession listRuns: typeof listRuns getCurrentRunForPane: typeof getCurrentRunForPane + getCurrentRunForAgentSession: typeof getCurrentRunForAgentSession runsBoundToPane: typeof runsBoundToPane getRunRaw: typeof getRunRaw unbindOtherRunsForPane: typeof unbindOtherRunsForPane @@ -190,6 +204,7 @@ export function attachRunLookup(ctor: { prototype: object }): void { getRunMailboxOwnerIdsForAgentSession, listRuns, getCurrentRunForPane, + getCurrentRunForAgentSession, runsBoundToPane, getRunRaw, unbindOtherRunsForPane, diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index db00cac3b5bc..441b0198cdc1 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -25,7 +25,7 @@ const RunUseParams = z.object({ takeoverLegacy: OptionalBoolean }) -const RunCurrentParams = z.object({ from: requiredString('Missing coordinator terminal') }) +const RunCurrentParams = z.object({ from: OptionalString, agentSessionId: OptionalString }) const RunListParams = z.object({ limit: z.number().int().min(1).max(ORCHESTRATION_RUN_PAGE_LIMIT).optional(), cursor: z.string().min(1).optional() @@ -146,6 +146,13 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ name: 'orchestration.runCurrent', params: RunCurrentParams, handler: (params, { orchestrationCompatibilityEvidence, runtime }) => { + if (params.agentSessionId) { + const run = runtime.getOrchestrationDb().getCurrentRunForAgentSession(params.agentSessionId) + return { run: run ? exposeRun(run) : null } + } + if (!params.from) { + throw new OrchestrationError('stable_pane_required', 'Missing coordinator identity.') + } const paneKey = resolveOrchestrationCaller(runtime, { callerTerminalHandle: params.from, callerEvidence: orchestrationCompatibilityEvidence, From eafcb48e0ba844c80fe8d02868b91828f71ead3a Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:02:19 -0700 Subject: [PATCH 17/26] route orchestration scope by authenticated agent session --- .../rpc/methods/orchestration/gates/gates.ts | 14 ++++++++++++ .../messaging/message-methods.ts | 2 ++ .../orchestration/runs/dispatch-methods.ts | 2 ++ .../methods/orchestration/runs/run-scope.ts | 18 +++++++++++++++ .../rpc/methods/orchestration/runs/runs.ts | 10 ++++++++- .../rpc/methods/orchestration/schemas.ts | 22 +++++++++++++++++-- 6 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/main/runtime/rpc/methods/orchestration/gates/gates.ts b/src/main/runtime/rpc/methods/orchestration/gates/gates.ts index 76bfd23b76e0..f41396a10907 100644 --- a/src/main/runtime/rpc/methods/orchestration/gates/gates.ts +++ b/src/main/runtime/rpc/methods/orchestration/gates/gates.ts @@ -14,6 +14,8 @@ let activeCoordinator: Coordinator | null = null const RunParams = z.object({ spec: requiredString('Missing --spec'), from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional(), pollIntervalMs: OptionalFiniteNumber, maxConcurrent: OptionalFiniteNumber, worktree: OptionalString @@ -26,6 +28,8 @@ const GateCreateParams = z.object({ question: requiredString('Missing --question'), options: OptionalString, from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional(), run: OptionalString }) @@ -33,6 +37,8 @@ const GateResolveParams = z.object({ id: requiredString('Missing --id'), resolution: requiredString('Missing --resolution'), from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional(), run: OptionalString }) @@ -40,6 +46,8 @@ const GateListParams = z.object({ task: OptionalString, status: z.enum(['pending', 'resolved', 'timeout']).optional(), from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional(), run: OptionalString }) @@ -132,6 +140,8 @@ export const ORCHESTRATION_GATE_METHODS: RpcMethod[] = [ const run = resolveRunScope(runtime, { runId: params.run, callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, requireCurrentConsumer: true, legacyCoordinatorRunId, callerEvidence: orchestrationCompatibilityEvidence @@ -163,6 +173,8 @@ export const ORCHESTRATION_GATE_METHODS: RpcMethod[] = [ const run = resolveRunScope(runtime, { runId: params.run, callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, requireCurrentConsumer: true, legacyCoordinatorRunId, callerEvidence: orchestrationCompatibilityEvidence @@ -192,6 +204,8 @@ export const ORCHESTRATION_GATE_METHODS: RpcMethod[] = [ : resolveRunScope(runtime, { runId: params.run, callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, requireCurrentConsumer: params.run === undefined, legacyCoordinatorRunId, callerEvidence: orchestrationCompatibilityEvidence diff --git a/src/main/runtime/rpc/methods/orchestration/messaging/message-methods.ts b/src/main/runtime/rpc/methods/orchestration/messaging/message-methods.ts index 61808c19aedd..ea212fca699b 100644 --- a/src/main/runtime/rpc/methods/orchestration/messaging/message-methods.ts +++ b/src/main/runtime/rpc/methods/orchestration/messaging/message-methods.ts @@ -71,6 +71,8 @@ export const ORCHESTRATION_MESSAGE_METHODS: RpcMethod[] = [ const run = resolveRunScope(runtime, { runId: params.run ?? question.run_id, callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, requireCurrentConsumer: true, legacyCoordinatorRunId, callerEvidence: orchestrationCompatibilityEvidence diff --git a/src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts b/src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts index abc941bf98c2..3fe70ac3fe77 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts @@ -32,6 +32,8 @@ export const ORCHESTRATION_DISPATCH_METHODS: RpcMethod[] = [ const run = resolveRunScope(runtime, { runId: params.run, callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, requireCurrentConsumer: true, legacyCoordinatorRunId, callerEvidence: orchestrationCompatibilityEvidence diff --git a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts index 027a4a474269..c9dc9bac7734 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts @@ -28,6 +28,8 @@ export type RunScopeParams = { runId?: string callerTerminalHandle?: string callerPaneKey?: string + callerAgentSessionId?: string + callerRuntimeFence?: number requireCurrentConsumer: boolean legacyCoordinatorRunId?: string // Why: the caller's declared handle is a user param; this is the attested one to check it against. @@ -55,6 +57,8 @@ export function assertCallerHandleMatchesEvidence( export type OrchestrationCallerParams = { callerTerminalHandle: string + callerAgentSessionId?: string + callerRuntimeFence?: number callerEvidence?: OrchestrationCompatibilityEvidence callerAuthority?: OrchestrationCompatibilityCallerAuthority /** Preserve legacy callers that treated a missing pane as an ordinary fence. */ @@ -107,6 +111,20 @@ export function resolveRunScope(runtime: OrcaRuntimeService, params: RunScopePar if (!params.requireCurrentConsumer && explicit) { return explicit } + if (params.callerAgentSessionId) { + if (params.callerRuntimeFence === undefined) { + throw new OrchestrationError('consumer_fenced', 'Missing native session lease fence.') + } + resolveNativeCoordinatorSession(runtime, params.callerAgentSessionId, params.callerRuntimeFence) + const current = db.getCurrentRunForAgentSession(params.callerAgentSessionId) + if (!current || (explicit && current.id !== explicit.id)) { + throw new OrchestrationError( + 'consumer_fenced', + 'This native session is not bound to that Run.' + ) + } + return current + } if (!params.callerTerminalHandle) { throw new OrchestrationError( 'run_required', diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index 441b0198cdc1..c2f5ca907c23 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -25,7 +25,11 @@ const RunUseParams = z.object({ takeoverLegacy: OptionalBoolean }) -const RunCurrentParams = z.object({ from: OptionalString, agentSessionId: OptionalString }) +const RunCurrentParams = z.object({ + from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: z.number().int().positive().optional() +}) const RunListParams = z.object({ limit: z.number().int().min(1).max(ORCHESTRATION_RUN_PAGE_LIMIT).optional(), cursor: z.string().min(1).optional() @@ -103,6 +107,8 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ } const paneKey = resolveOrchestrationCaller(runtime, { callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, callerEvidence: orchestrationCompatibilityEvidence, callerAuthority, requireStablePane: true, @@ -155,6 +161,8 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ } const paneKey = resolveOrchestrationCaller(runtime, { callerTerminalHandle: params.from, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, callerEvidence: orchestrationCompatibilityEvidence, requireStablePane: true }) diff --git a/src/main/runtime/rpc/methods/orchestration/schemas.ts b/src/main/runtime/rpc/methods/orchestration/schemas.ts index 51b51137475f..7a8fe1e5dfe5 100644 --- a/src/main/runtime/rpc/methods/orchestration/schemas.ts +++ b/src/main/runtime/rpc/methods/orchestration/schemas.ts @@ -104,6 +104,8 @@ export const SendParams = z to: OptionalString, subject: requiredString('Missing --subject'), from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, body: OptionalString, type: z .enum(MESSAGE_TYPES, { @@ -133,6 +135,8 @@ export const SendParams = z export const CheckParams = z .object({ + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, terminal: OptionalString, terminalPaneKey: OptionalString, unread: OptionalBoolean, @@ -170,6 +174,8 @@ export const ReplyParams = z.object({ id: requiredString('Missing --id'), body: requiredString('Missing --body'), from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, run: OptionalString }) @@ -186,6 +192,8 @@ export const TaskCreateParams = z.object({ deps: OptionalString, parent: OptionalString, callerTerminalHandle: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, run: OptionalString }) @@ -195,7 +203,9 @@ export const TaskListParams = z.object({ // Why: server-side truncation keeps --brief cheap over SSH/relay instead of shipping full specs the CLI throws away. brief: OptionalBoolean, run: OptionalString, - callerTerminalHandle: OptionalString + callerTerminalHandle: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber }) export const TaskUpdateParams = z.object({ @@ -215,7 +225,9 @@ export const TaskUpdateParams = z.object({ ), result: OptionalString, run: OptionalString, - callerTerminalHandle: OptionalString + callerTerminalHandle: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber }) export const DispatchParams = z.object({ @@ -223,6 +235,8 @@ export const DispatchParams = z.object({ // Why: --to is optional so --dry-run can preview without a target; the handler enforces presence before any side-effecting work. to: OptionalString, from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, inject: OptionalBoolean, dryRun: OptionalBoolean, returnPreamble: OptionalBoolean, @@ -234,6 +248,8 @@ export const DispatchShowParams = z.object({ task: OptionalString, preamble: OptionalBoolean, from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, devMode: OptionalBoolean }) @@ -245,6 +261,8 @@ export const AskParams = z options: OptionalString, timeoutMs: OptionalFiniteNumber, from: OptionalString, + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, run: OptionalString, compatibilityCliCommand: z.enum(['orca', 'orca-ide', 'orca-dev']).optional(), compatibilityWindowsCommand: z.enum(['orca', 'orca-ide']).optional() From cc3a18cd910d8e8a8ae68bc983fa9cf477d75f80 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:04:11 -0700 Subject: [PATCH 18/26] propagate native session identity through CLI orchestration --- .../handlers/orchestration/gate-handlers.ts | 19 ++++++++++---- .../handlers/orchestration/task-handlers.ts | 25 +++++++++++++------ .../orchestration/terminal-identity.ts | 9 +++++++ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/cli/handlers/orchestration/gate-handlers.ts b/src/cli/handlers/orchestration/gate-handlers.ts index f9ec4c6583fb..280b09a4ac40 100644 --- a/src/cli/handlers/orchestration/gate-handlers.ts +++ b/src/cli/handlers/orchestration/gate-handlers.ts @@ -2,7 +2,7 @@ import type { CommandHandler } from '../../dispatch' import { printResult } from '../../format' import { getOptionalJsonFlag, getOptionalStringFlag, getRequiredStringFlag } from '../../flags' import { callOrchestrationMutation } from './mutation-request' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveCoordinatorTerminalHandle } from './terminal-identity' export const ORCHESTRATION_GATE_HANDLERS: Record = { 'orchestration gate-create': async ({ flags, client, cwd, json }) => { @@ -13,7 +13,9 @@ export const ORCHESTRATION_GATE_HANDLERS: Record = { question: getRequiredStringFlag(flags, 'question'), options: getOptionalJsonFlag(flags, 'options'), // Why: gates are Run-scoped, so the coordinator handle is the authorized caller identity. - from: await resolveCoordinatorTerminalHandle(flags, cwd, client) + ...(orchestrationSessionPayload().agentSessionId + ? orchestrationSessionPayload() + : { from: await resolveCoordinatorTerminalHandle(flags, cwd, client) }) }) printResult( result, @@ -29,7 +31,9 @@ export const ORCHESTRATION_GATE_HANDLERS: Record = { }>(client, flags, 'orchestration.gateResolve', { id: getRequiredStringFlag(flags, 'id'), resolution: getRequiredStringFlag(flags, 'resolution'), - from: await resolveCoordinatorTerminalHandle(flags, cwd, client) + ...(orchestrationSessionPayload().agentSessionId + ? orchestrationSessionPayload() + : { from: await resolveCoordinatorTerminalHandle(flags, cwd, client) }) }) printResult(result, json, (value) => `Gate ${value.gate.id} resolved: ${value.gate.resolution}`) }, @@ -37,7 +41,11 @@ export const ORCHESTRATION_GATE_HANDLERS: Record = { 'orchestration gate-list': async ({ flags, client, cwd, json }) => { const run = getOptionalStringFlag(flags, 'run') // Why: named runs remain inspectable without a pane; only implicit runs resolve identity. - const from = run ? undefined : await resolveCoordinatorTerminalHandle(flags, cwd, client) + const session = orchestrationSessionPayload() + const from = + run || session.agentSessionId + ? undefined + : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await client.call<{ gates: { id: string; task_id: string; question: string; status: string }[] count: number @@ -46,7 +54,8 @@ export const ORCHESTRATION_GATE_HANDLERS: Record = { task: getOptionalStringFlag(flags, 'task'), status: getOptionalStringFlag(flags, 'status'), run, - from + from, + ...session }) printResult(result, json, (value) => { if (value.gates.length === 0) { diff --git a/src/cli/handlers/orchestration/task-handlers.ts b/src/cli/handlers/orchestration/task-handlers.ts index c4c943261e8c..5e6b7c435dd5 100644 --- a/src/cli/handlers/orchestration/task-handlers.ts +++ b/src/cli/handlers/orchestration/task-handlers.ts @@ -4,7 +4,7 @@ import { getOptionalStringFlag, getRequiredStringFlag } from '../../flags' import { RuntimeClientError } from '../../runtime-client' import { abbreviateOrchestrationTasks } from '../../../shared/orchestration-task-summary' import { callOrchestrationMutation } from './mutation-request' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveCoordinatorTerminalHandle } from './terminal-identity' const TASK_STATUS_VALUES = [ 'pending', @@ -17,7 +17,10 @@ const TASK_STATUS_VALUES = [ export const ORCHESTRATION_TASK_HANDLERS: Record = { 'orchestration task-create': async ({ flags, client, cwd, json }) => { - const callerTerminalHandle = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const session = orchestrationSessionPayload() + const callerTerminalHandle = session.agentSessionId + ? undefined + : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await callOrchestrationMutation<{ task: { id: string; status: string } }>( client, flags, @@ -29,7 +32,8 @@ export const ORCHESTRATION_TASK_HANDLERS: Record = { deps: getOptionalStringFlag(flags, 'deps'), parent: getOptionalStringFlag(flags, 'parent'), run: getOptionalStringFlag(flags, 'run'), - callerTerminalHandle + callerTerminalHandle, + ...session } ) printResult(result, json, (r) => `Created ${r.task.id} [${r.task.status}]`) @@ -38,9 +42,11 @@ export const ORCHESTRATION_TASK_HANDLERS: Record = { 'orchestration task-list': async ({ flags, client, cwd, json }) => { const brief = flags.has('brief') const run = getOptionalStringFlag(flags, 'run') - const callerTerminalHandle = run - ? undefined - : await resolveCoordinatorTerminalHandle(flags, cwd, client) + const session = orchestrationSessionPayload() + const callerTerminalHandle = + run || session.agentSessionId + ? undefined + : await resolveCoordinatorTerminalHandle(flags, cwd, client) const result = await client.call<{ tasks: { id: string @@ -60,7 +66,8 @@ export const ORCHESTRATION_TASK_HANDLERS: Record = { ready: flags.has('ready') ? true : undefined, brief: brief ? true : undefined, run, - callerTerminalHandle + callerTerminalHandle, + ...session }) // Why: only older runtimes (no spec_truncated) skip server-side abbreviation and need this client-side fallback. const needsClientAbbreviation = @@ -106,7 +113,9 @@ export const ORCHESTRATION_TASK_HANDLERS: Record = { status, result: getOptionalStringFlag(flags, 'result'), run: getOptionalStringFlag(flags, 'run'), - callerTerminalHandle: await resolveCoordinatorTerminalHandle(flags, cwd, client) + ...(orchestrationSessionPayload().agentSessionId + ? orchestrationSessionPayload() + : { callerTerminalHandle: await resolveCoordinatorTerminalHandle(flags, cwd, client) }) } ) printResult(result, json, (r) => `Updated ${r.task.id} -> ${r.task.status}`) diff --git a/src/cli/handlers/orchestration/terminal-identity.ts b/src/cli/handlers/orchestration/terminal-identity.ts index 9b9984892291..10d6c5abf58b 100644 --- a/src/cli/handlers/orchestration/terminal-identity.ts +++ b/src/cli/handlers/orchestration/terminal-identity.ts @@ -59,6 +59,15 @@ export function resolveOrchestrationRuntimeFence(): string | undefined { return value && value.length > 0 ? value : undefined } +export function orchestrationSessionPayload(): { + agentSessionId?: string + runtimeFence?: number +} { + const agentSessionId = resolveOrchestrationAgentSessionId() + const fence = resolveOrchestrationRuntimeFence() + return agentSessionId && fence ? { agentSessionId, runtimeFence: Number(fence) } : {} +} + /** * Whether the handle this process was born with still names a live identity. * From 98069cbc77525851a3572e30ee1b5bc531ddfd26 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:04:50 -0700 Subject: [PATCH 19/26] support native session identity for orchestration send --- src/cli/handlers/orchestration/message-send-handler.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cli/handlers/orchestration/message-send-handler.ts b/src/cli/handlers/orchestration/message-send-handler.ts index 088d1361e69e..580d1f71fbc7 100644 --- a/src/cli/handlers/orchestration/message-send-handler.ts +++ b/src/cli/handlers/orchestration/message-send-handler.ts @@ -7,6 +7,7 @@ import { getOptionalStructuredMessagePayload } from './message-payload' import { callOrchestrationMutation } from './mutation-request' import { isDevCliInvocation } from './runtime-compatibility' import { + orchestrationSessionPayload, resolveOrchestrationTerminalHandle, throwNoActiveSenderTerminal } from './terminal-identity' @@ -75,16 +76,21 @@ export const ORCHESTRATION_SEND_HANDLER: Record = { if ( (type === 'worker_done' || type === 'heartbeat') && !getOptionalStringFlag(flags, 'from') && - !process.env.ORCA_TERMINAL_HANDLE + !process.env.ORCA_TERMINAL_HANDLE && + !orchestrationSessionPayload().agentSessionId ) { // Why: focus isn't lifecycle authority — an identity-less subprocess must fail closed rather than guess the worker. throwNoActiveSenderTerminal() } // Why: lifecycle senders preserve ORCA_TERMINAL_HANDLE across restarts for older runtimes. - const from = await resolveOrchestrationTerminalHandle(flags, cwd, client, 'from') + const session = orchestrationSessionPayload() + const from = session.agentSessionId + ? undefined + : await resolveOrchestrationTerminalHandle(flags, cwd, client, 'from') const sendParams = { from, + ...session, to, run: getOptionalStringFlag(flags, 'run'), subject: getRequiredStringFlag(flags, 'subject'), From 429011f1e4f840590274e4ec781aade788e76f35 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:08:33 -0700 Subject: [PATCH 20/26] fence native current run lookup --- src/cli/handlers/orchestration/run-handlers.ts | 4 +++- src/main/runtime/rpc/methods/orchestration/runs/runs.ts | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cli/handlers/orchestration/run-handlers.ts b/src/cli/handlers/orchestration/run-handlers.ts index ee1efa87895e..9367cda0ead3 100644 --- a/src/cli/handlers/orchestration/run-handlers.ts +++ b/src/cli/handlers/orchestration/run-handlers.ts @@ -50,7 +50,9 @@ export const ORCHESTRATION_RUN_HANDLERS: Record = { run: { id: string; objective: string } | null }>('orchestration.runCurrent', { ...(from ? { from } : {}), - ...(sessionId ? { agentSessionId: sessionId } : {}) + ...(sessionId + ? { agentSessionId: sessionId, runtimeFence: Number(resolveOrchestrationRuntimeFence()) } + : {}) }) printResult(result, json, (r) => r.run ? `${r.run.id} ${r.run.objective}` : 'No Run is bound to this terminal.' diff --git a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts index c2f5ca907c23..e89bc73aa983 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/runs.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/runs.ts @@ -153,6 +153,10 @@ export const ORCHESTRATION_RUN_METHODS: RpcMethod[] = [ params: RunCurrentParams, handler: (params, { orchestrationCompatibilityEvidence, runtime }) => { if (params.agentSessionId) { + if (params.runtimeFence === undefined) { + throw new OrchestrationError('consumer_fenced', 'Missing native session lease fence.') + } + resolveNativeCoordinatorSession(runtime, params.agentSessionId, params.runtimeFence) const run = runtime.getOrchestrationDb().getCurrentRunForAgentSession(params.agentSessionId) return { run: run ? exposeRun(run) : null } } From 9c23b61ada5b728cb99fcdfe62aec31312043b0c Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:13:57 -0700 Subject: [PATCH 21/26] propagate native session identity to structured children --- .../claude-structured-launch-resolution.ts | 26 +++++++++++-------- .../worker/worker-start-schema.ts | 6 +++-- .../methods/orchestration/worker/workers.ts | 24 ++++++++++++----- .../structured-worker-child-identity-env.ts | 7 ++++- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/main/claude/claude-structured-launch-resolution.ts b/src/main/claude/claude-structured-launch-resolution.ts index 667ebfb8ddde..44018725a876 100644 --- a/src/main/claude/claude-structured-launch-resolution.ts +++ b/src/main/claude/claude-structured-launch-resolution.ts @@ -240,17 +240,21 @@ export function createClaudeStructuredLaunchResolver( command, // Only a dispatched structured worker gets the orchestration identity and the Orca CLI on // PATH; an ordinary chat session's env passes through untouched. - structuredWorkerChildIdentityEnv(record.sessionId, { - ...applyClaudeEnvPatch( - cloneDefinedEnv(process.env), - {}, - { - stripAuthEnv: auth.stripAuthEnv, - platform: process.platform - } - ), - ...(overlay ? cloneDefinedEnv(overlay) : {}) - }), + structuredWorkerChildIdentityEnv( + record.sessionId, + { + ...applyClaudeEnvPatch( + cloneDefinedEnv(process.env), + {}, + { + stripAuthEnv: auth.stripAuthEnv, + platform: process.platform + } + ), + ...(overlay ? cloneDefinedEnv(overlay) : {}) + }, + record.lease.runtimeFence + ), { platform: process.platform } ) return { diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts index 2f9d94566096..8ef88f9c9358 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod' -import { OptionalFiniteNumber, OptionalString, requiredString } from '../../../schemas' +import { OptionalFiniteNumber, OptionalString } from '../../../schemas' export const OptionalWorkerLaunchPreference = z .string() @@ -17,7 +17,9 @@ export const WorkerStartParams = z parent: OptionalString, on: OptionalString, run: OptionalString, - from: requiredString('Missing --from'), + from: z.string().min(1).optional(), + agentSessionId: OptionalString, + runtimeFence: OptionalFiniteNumber, worktree: OptionalString, name: OptionalString, repo: OptionalString, diff --git a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts index 8b14ec044cf5..8c341230182e 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts @@ -6,7 +6,7 @@ import { decideWorkerStartMode, readWorkerStartModeSettings } from '../../orchestration-worker-start-mode' -import { resolveOrchestrationCaller } from '../runs/run-scope' +import { resolveOrchestrationCaller, resolveRunScope } from '../runs/run-scope' import { WorkerStartParams } from './worker-start-schema' import { isWorkerStartTimeoutWithinTimerLimit, @@ -30,11 +30,23 @@ export const ORCHESTRATION_WORKER_START_METHODS: RpcMethod[] = [ } const readinessTimeoutMs = resolveWorkerStartReadinessTimeoutMs(params.timeoutMs) const db = runtime.getOrchestrationDb() - const coordinatorPane = resolveOrchestrationCaller(runtime, { - callerTerminalHandle: params.from, - callerEvidence: orchestrationCompatibilityEvidence - }) - const run = coordinatorPane ? db.getCurrentRunForPane(coordinatorPane) : undefined + const native = Boolean(params.agentSessionId) + const coordinatorPane = native + ? null + : resolveOrchestrationCaller(runtime, { + callerTerminalHandle: params.from!, + callerEvidence: orchestrationCompatibilityEvidence + }) + const run = native + ? resolveRunScope(runtime, { + runId: params.run, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, + requireCurrentConsumer: true + }) + : coordinatorPane + ? db.getCurrentRunForPane(coordinatorPane) + : undefined if (!run || (params.run && params.run !== run.id)) { throw new OrchestrationError( 'consumer_fenced', diff --git a/src/main/runtime/structured-worker-child-identity-env.ts b/src/main/runtime/structured-worker-child-identity-env.ts index 6cf9f46e910c..45745d81ae22 100644 --- a/src/main/runtime/structured-worker-child-identity-env.ts +++ b/src/main/runtime/structured-worker-child-identity-env.ts @@ -42,7 +42,8 @@ import { structuredWorkerIdentities } from './structured-worker-identity' export function structuredWorkerChildIdentityEnv( sessionId: string, - childEnv: Record + childEnv: Record, + runtimeFence?: number ): Record { const identity = structuredWorkerIdentities.getBySessionId(sessionId) if (!identity) { @@ -51,6 +52,10 @@ export function structuredWorkerChildIdentityEnv( const env: Record = { ...childEnv, ORCA_TERMINAL_HANDLE: identity.handle, + ORCA_AGENT_SESSION_ID: sessionId, + ...(runtimeFence !== undefined + ? { ORCA_AGENT_SESSION_RUNTIME_FENCE: String(runtimeFence) } + : {}), ORCA_CLI_COMMAND: 'orca' } applyOrcaCliPath(env) From 301ad25321c8523c3e332d0339952bc7dc1b79a7 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:14:49 -0700 Subject: [PATCH 22/26] support native session worker start --- .../orchestration/worker-launch-handler.ts | 7 ++++-- .../worker/local-worker-start.ts | 23 +++++++++++-------- .../methods/orchestration/worker/workers.ts | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/cli/handlers/orchestration/worker-launch-handler.ts b/src/cli/handlers/orchestration/worker-launch-handler.ts index b6e4d92799c2..9e3667582e70 100644 --- a/src/cli/handlers/orchestration/worker-launch-handler.ts +++ b/src/cli/handlers/orchestration/worker-launch-handler.ts @@ -7,7 +7,7 @@ import { ORCHESTRATION_WORKER_LAUNCH_PREFERENCES_RUNTIME_CAPABILITY } from '../. import { callOrchestrationMutation } from './mutation-request' import { getOptionalPositiveIntegerValueFlag } from './numeric-flags' import { isDevCliInvocation } from './runtime-compatibility' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveCoordinatorTerminalHandle } from './terminal-identity' import { formatWorkerStart } from './worker-output' import { renderResolvedOrchestrationCommand } from '../../orchestration-mutation-recovery' @@ -33,6 +33,7 @@ export const ORCHESTRATION_WORKER_LAUNCH_HANDLER: Record const taskTitle = getOptionalStringFlag(flags, 'task-title') const deps = getOptionalStringFlag(flags, 'deps') const parent = getOptionalStringFlag(flags, 'parent') + const sessionPayload = orchestrationSessionPayload() const result = await callOrchestrationMutation<{ runId: string taskId: string @@ -66,7 +67,9 @@ export const ORCHESTRATION_WORKER_LAUNCH_HANDLER: Record retryOf: getOptionalStringFlag(flags, 'retry-of'), timeoutMs: getOptionalPositiveIntegerValueFlag(flags, 'timeout-ms'), run: getOptionalStringFlag(flags, 'run'), - from: await resolveCoordinatorTerminalHandle(flags, cwd, client), + ...(Object.keys(sessionPayload).length > 0 + ? sessionPayload + : { from: await resolveCoordinatorTerminalHandle(flags, cwd, client) }), devMode: isDevCliInvocation() }) if (result.result.state !== 'ready') { diff --git a/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts b/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts index d67d09b766a2..a4696d50cf03 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts @@ -51,11 +51,16 @@ export async function startLocalWorker(args: { mode: WorkerStartModeReceipt }): Promise { const { params, runtime, db, run, coordinatorPane, existingTask, orchestrationMutation } = args - const requestedWorktree = params.worktree ?? 'current' + const paramsWithFrom = { ...params, from: params.from ?? '' } + const requestedWorktree = paramsWithFrom.worktree ?? 'current' const createsWorktree = requestedWorktree === 'new-child' || requestedWorktree === 'new-top-level' - const { agent, launch } = prepareLocalWorkerStart({ params, createsWorktree, runtime }) + const { agent, launch } = prepareLocalWorkerStart({ + params: paramsWithFrom, + createsWorktree, + runtime + }) - const coordinatorWorktreeId = await resolveDispatchCallerWorktreeId(runtime, params.from) + const coordinatorWorktreeId = await resolveDispatchCallerWorktreeId(runtime, paramsWithFrom.from) const creationWorktree = createsWorktree ? await runtime.showManagedWorktree(`id:${coordinatorWorktreeId}`) : undefined @@ -75,7 +80,7 @@ export async function startLocalWorker(args: { await assertExplicitWorkerTerminalUsable({ runtime, terminal: params.terminal, - from: params.from, + from: paramsWithFrom.from, coordinatorPane, resolvedWorktreeId: resolvedWorktree?.id }) @@ -101,7 +106,7 @@ export async function startLocalWorker(args: { : 'existing_worktree' } const started = db.createStartingWorkerDispatch({ - creator: resolveDispatchCreator(runtime, params.from), + creator: resolveDispatchCreator(runtime, paramsWithFrom.from), maxDepth: runtime.getNestedWorkerMaxDepth(), taskId: existingTask?.id, taskSpec: params.spec, @@ -109,10 +114,10 @@ export async function startLocalWorker(args: { taskDeps: parseTaskDeps(params.deps), taskParentId: params.parent, taskRunId: run.id, - taskCreatedByTerminalHandle: params.from, + taskCreatedByTerminalHandle: paramsWithFrom.from, taskCreatedByPaneKey: coordinatorPane ?? undefined, taskCreatedByProcessIncarnation: - runtime.getTerminalProcessIncarnation(params.from) ?? undefined, + runtime.getTerminalProcessIncarnation(paramsWithFrom.from) ?? undefined, taskCreatedByRunGeneration: run.consumer_generation, retryOf: params.retryOf, startOptions, @@ -150,7 +155,7 @@ export async function startLocalWorker(args: { dispatchId: started.dispatch.id, requestedWorktree, coordinatorWorktree: creationWorktree, - params, + params: paramsWithFrom, agent: agent as TuiAgent, launchPreferences: launch.preferences, effects @@ -251,7 +256,7 @@ export async function startLocalWorker(args: { dispatchDepth: started.dispatch.depth, taskId: task.id, taskSpec: task.spec, - coordinatorHandle: params.from, + coordinatorHandle: paramsWithFrom.from, dispatchCapability: capability, devMode: params.devMode, requestId: orchestrationMutation?.requestId ?? started.dispatch.id diff --git a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts index 8c341230182e..8036ee788107 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts @@ -79,7 +79,7 @@ export const ORCHESTRATION_WORKER_START_METHODS: RpcMethod[] = [ return receipt && typeof receipt === 'object' ? { ...receipt, mode } : receipt } return startLocalWorker({ - params: { ...params, timeoutMs: readinessTimeoutMs }, + params: { ...params, from: params.from ?? '', timeoutMs: readinessTimeoutMs } as never, runtime, db, run, From 4027bf54114e4f814c51cabb61a66b35970116f8 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:16:35 -0700 Subject: [PATCH 23/26] resolve native worker launch through session identity --- .../orchestration/dispatch-handlers.ts | 12 +++-- .../orchestration/message-check-handler.ts | 16 +++++-- .../orchestration/question-handler.ts | 12 +++-- .../codex-structured-child-environment.ts | 5 +- .../codex/codex-structured-session-acquire.ts | 2 +- ...e-get-agent-session-execution-namespace.ts | 5 ++ ...ime-structured-agent-session-launch-tui.ts | 1 + .../orchestration/messaging/check-methods.ts | 2 +- .../orchestration/messaging/check-run.ts | 20 ++++++-- .../orchestration/runs/dispatch-creator.ts | 11 +++++ .../methods/orchestration/runs/run-scope.ts | 22 +++++---- .../worker/local-worker-start.ts | 10 +--- .../worker/worker-start-schema.ts | 3 ++ .../methods/orchestration/worker/workers.ts | 2 +- .../structured-worker-child-identity-env.ts | 48 ++++--------------- 15 files changed, 95 insertions(+), 76 deletions(-) diff --git a/src/cli/handlers/orchestration/dispatch-handlers.ts b/src/cli/handlers/orchestration/dispatch-handlers.ts index afe79ab8e2f3..ba1264b62742 100644 --- a/src/cli/handlers/orchestration/dispatch-handlers.ts +++ b/src/cli/handlers/orchestration/dispatch-handlers.ts @@ -5,11 +5,14 @@ import { RuntimeClientError } from '../../runtime-client' import { orchestrationMigrationData } from '../../../shared/orchestration-rpc-contract' import { callOrchestrationMutation } from './mutation-request' import { isDevCliInvocation } from './runtime-compatibility' -import { resolveCoordinatorTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveCoordinatorTerminalHandle } from './terminal-identity' export const ORCHESTRATION_DISPATCH_HANDLER: Record = { 'orchestration dispatch': async ({ flags, client, cwd, json }) => { - const from = await resolveCoordinatorTerminalHandle(flags, cwd, client) + const session = flags.has('from') ? {} : orchestrationSessionPayload() + const from = session.agentSessionId + ? undefined + : await resolveCoordinatorTerminalHandle(flags, cwd, client) const dryRun = flags.has('dry-run') ? true : undefined const returnPreamble = flags.has('return-preamble') ? true : undefined // Why: --to is only required for non-dry-run; the RPC handler re-enforces. @@ -23,6 +26,7 @@ export const ORCHESTRATION_DISPATCH_HANDLER: Record = { task: getRequiredStringFlag(flags, 'task'), run: getOptionalStringFlag(flags, 'run'), to, + ...session, from, inject: flags.has('inject') ? true : undefined, dryRun, @@ -43,7 +47,8 @@ export const ORCHESTRATION_DISPATCH_INSPECTION_HANDLERS: Record { const showPreamble = flags.has('preamble') ? true : undefined // Why: a preview must embed the same real coordinator handle as an actual dispatch. - const from = showPreamble + const session = flags.has('from') ? {} : orchestrationSessionPayload() + const from = showPreamble && !session.agentSessionId ? await resolveCoordinatorTerminalHandle(flags, cwd, client) : undefined const result = await client.call<{ @@ -52,6 +57,7 @@ export const ORCHESTRATION_DISPATCH_INSPECTION_HANDLERS: Record('orchestration.dispatchShow', { task: getRequiredStringFlag(flags, 'task'), preamble: showPreamble, + ...session, from, devMode: isDevCliInvocation() }) diff --git a/src/cli/handlers/orchestration/message-check-handler.ts b/src/cli/handlers/orchestration/message-check-handler.ts index f2af827ee048..5c489127d5cf 100644 --- a/src/cli/handlers/orchestration/message-check-handler.ts +++ b/src/cli/handlers/orchestration/message-check-handler.ts @@ -13,7 +13,7 @@ import { startCheckKeepalive } from './check-keepalive' import { callOrchestrationMutation } from './mutation-request' import { getOptionalPositiveIntegerValueFlag } from './numeric-flags' import { flushOrchestrationStdout, resolveCompatibilityCliCommand } from './runtime-compatibility' -import { resolveOrchestrationTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveOrchestrationTerminalHandle } from './terminal-identity' type CheckResult = { messages: MessageSummary[] @@ -40,13 +40,18 @@ export const ORCHESTRATION_CHECK_HANDLER: Record = { } const timeoutMs = getOptionalPositiveIntegerValueFlag(flags, 'timeout-ms') const explicitTerminal = getOptionalStringFlag(flags, 'terminal') - const terminal = await resolveOrchestrationTerminalHandle(flags, cwd, client, 'terminal') + const session = explicitTerminal ? {} : orchestrationSessionPayload() + const terminal = session.agentSessionId + ? undefined + : await resolveOrchestrationTerminalHandle(flags, cwd, client, 'terminal') + const checkedIdentity = terminal ?? session.agentSessionId! const stopKeepalive = wait ? startCheckKeepalive(timeoutMs) : null let result: Awaited>> try { result = await callOrchestrationMutation(client, flags, 'orchestration.check', { + ...session, terminal, - terminalPaneKey: explicitTerminal ? undefined : process.env.ORCA_PANE_KEY || undefined, + terminalPaneKey: explicitTerminal || session.agentSessionId ? undefined : process.env.ORCA_PANE_KEY || undefined, // Why: old runtimes degrade peek to non-consuming all mode instead of destructive mark-read. unread: flags.has('unread') ? true : peek ? false : undefined, peek: peek ? true : undefined, @@ -68,13 +73,14 @@ export const ORCHESTRATION_CHECK_HANDLER: Record = { } result = { ...result, - result: prepareOrchestrationCheckOutput(result.result, terminal, flags.has('format')) + result: prepareOrchestrationCheckOutput(result.result, checkedIdentity, flags.has('format')) } - printResult(result, json, (value) => formatOrchestrationCheckText(value, terminal)) + printResult(result, json, (value) => formatOrchestrationCheckText(value, checkedIdentity)) const compatibilityAck = result.result.legacyCompatibility?.ackMessageIds if (compatibilityAck && compatibilityAck.length > 0) { await flushOrchestrationStdout() await client.call('orchestration.check', { + ...session, terminal, compatibilityAck: JSON.stringify({ messageIds: compatibilityAck, diff --git a/src/cli/handlers/orchestration/question-handler.ts b/src/cli/handlers/orchestration/question-handler.ts index bd9e239b6d59..f497b192fa3d 100644 --- a/src/cli/handlers/orchestration/question-handler.ts +++ b/src/cli/handlers/orchestration/question-handler.ts @@ -16,13 +16,16 @@ import { resolveCompatibilityCliCommand, resolvePackagedWindowsCompatibilityCommand } from './runtime-compatibility' -import { resolveOrchestrationTerminalHandle } from './terminal-identity' +import { orchestrationSessionPayload, resolveOrchestrationTerminalHandle } from './terminal-identity' export const ORCHESTRATION_QUESTION_HANDLER: Record = { 'orchestration ask': async ({ flags, client, cwd, json }) => { const parsedTimeoutMs = getOptionalPositiveIntegerValueFlag(flags, 'timeout-ms') const timeoutMs = clampOrchestrationAskTimeoutMs(parsedTimeoutMs) - const from = await resolveOrchestrationTerminalHandle(flags, cwd, client, 'from') + const session = flags.has('from') ? {} : orchestrationSessionPayload() + const from = session.agentSessionId + ? undefined + : await resolveOrchestrationTerminalHandle(flags, cwd, client, 'from') const question = getOptionalStringFlag(flags, 'question') const resume = getOptionalStringFlag(flags, 'resume') if ((question ? 1 : 0) + (resume ? 1 : 0) !== 1) { @@ -58,6 +61,7 @@ export const ORCHESTRATION_QUESTION_HANDLER: Record = { resume, options: getOptionalStringFlag(flags, 'options'), timeoutMs: parsedTimeoutMs === undefined ? undefined : timeoutMs, + ...session, from, compatibilityCliCommand: resolveCompatibilityCliCommand(), compatibilityWindowsCommand: resolvePackagedWindowsCompatibilityCommand() @@ -86,6 +90,7 @@ export const ORCHESTRATION_QUESTION_HANDLER: Record = { if (answerAck && result.result.answer !== null) { await flushOrchestrationStdout() await client.call('orchestration.check', { + ...session, terminal: from, compatibilityQuestionAck: JSON.stringify(answerAck) }) @@ -104,7 +109,8 @@ export const ORCHESTRATION_QUESTION_HANDLER: Record = { 'orchestration', 'ask', '--from', - from, + ...session, + from, ...(dispatchCapability ? ['--dispatch-capability', dispatchCapability] : []), '--resume', messageId, diff --git a/src/main/codex/codex-structured-child-environment.ts b/src/main/codex/codex-structured-child-environment.ts index 72bf17a1bcef..65c97b75f567 100644 --- a/src/main/codex/codex-structured-child-environment.ts +++ b/src/main/codex/codex-structured-child-environment.ts @@ -5,7 +5,8 @@ import { structuredWorkerChildIdentityEnv } from '../runtime/structured-worker-c export function buildCodexStructuredChildEnvironment( launch: CodexStructuredLaunch, spawnToken: string, - sessionId: string + sessionId: string, + runtimeFence?: number ): Record { return { // Only a dispatched structured worker gets the orchestration identity and the Orca CLI on @@ -13,7 +14,7 @@ export function buildCodexStructuredChildEnvironment( ...structuredWorkerChildIdentityEnv(sessionId, { ...launch.env, ...(launch.codexHome ? { CODEX_HOME: launch.codexHome } : {}) - }), + }, runtimeFence), [CODEX_SPAWN_TOKEN_ENV]: spawnToken } } diff --git a/src/main/codex/codex-structured-session-acquire.ts b/src/main/codex/codex-structured-session-acquire.ts index 8c8b39ca48be..53987ff6090e 100644 --- a/src/main/codex/codex-structured-session-acquire.ts +++ b/src/main/codex/codex-structured-session-acquire.ts @@ -106,7 +106,7 @@ export async function acquireCodexStructuredSession(input: { command: launch.command, args: launch.args, cwd: launch.cwd, - env: buildCodexStructuredChildEnvironment(launch, acquireInput.spawnToken, sessionId) + env: buildCodexStructuredChildEnvironment(launch, acquireInput.spawnToken, sessionId, acquireInput.fence) }, { onNotification: (method, params) => diff --git a/src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts b/src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts index f70cf033718e..58496e1487dc 100644 --- a/src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts +++ b/src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts @@ -95,6 +95,7 @@ export class OrcaRuntimeWithGetAgentSessionExecutionNamespace extends OrcaRuntim spawnToken: string providerRoot: string sessionId: string + runtimeFence: number launchArgs?: AgentSessionLaunchArgs } ): Promise { @@ -150,6 +151,10 @@ export class OrcaRuntimeWithGetAgentSessionExecutionNamespace extends OrcaRuntim }), agentEnv: { ...resolveTuiAgentLaunchEnv(request.agent, settings.agentDefaultEnv), + ...(handoffAuthority ? { + ORCA_AGENT_SESSION_ID: handoffAuthority.sessionId, + ORCA_AGENT_SESSION_RUNTIME_FENCE: String(handoffAuthority.runtimeFence) + } : {}), ...(handoffAuthority && request.agent === 'codex' ? { CODEX_HOME: handoffAuthority.providerRoot } : handoffAuthority && request.agent === 'claude' diff --git a/src/main/runtime/orca-runtime-structured-agent-session-launch-tui.ts b/src/main/runtime/orca-runtime-structured-agent-session-launch-tui.ts index 89836d1e0270..6514d6d37cd1 100644 --- a/src/main/runtime/orca-runtime-structured-agent-session-launch-tui.ts +++ b/src/main/runtime/orca-runtime-structured-agent-session-launch-tui.ts @@ -32,6 +32,7 @@ export class OrcaRuntimeWithStructuredAgentSessionLaunchTui extends OrcaRuntimeW spawnToken, providerRoot: record.accountHome.path, sessionId: record.sessionId, + runtimeFence: fence, ...(record.launchArgs !== undefined ? { launchArgs: record.launchArgs } : {}) } ) diff --git a/src/main/runtime/rpc/methods/orchestration/messaging/check-methods.ts b/src/main/runtime/rpc/methods/orchestration/messaging/check-methods.ts index a12428253c3b..aff913db99ed 100644 --- a/src/main/runtime/rpc/methods/orchestration/messaging/check-methods.ts +++ b/src/main/runtime/rpc/methods/orchestration/messaging/check-methods.ts @@ -34,7 +34,7 @@ export const ORCHESTRATION_CHECK_METHODS: RpcMethod[] = [ // Why: a live runtime handle is authoritative; pane metadata is only the restart fallback. const paneKey = runtime.getTerminalPaneKey(handle) ?? params.terminalPaneKey const boundRun = paneKey ? db.getCurrentRunForPane(paneKey) : undefined - if (params.run || boundRun) { + if (params.agentSessionId || params.run || boundRun) { return checkRunMailbox({ params, runtime, diff --git a/src/main/runtime/rpc/methods/orchestration/messaging/check-run.ts b/src/main/runtime/rpc/methods/orchestration/messaging/check-run.ts index 6db89cf4a201..41e92ede56f6 100644 --- a/src/main/runtime/rpc/methods/orchestration/messaging/check-run.ts +++ b/src/main/runtime/rpc/methods/orchestration/messaging/check-run.ts @@ -50,6 +50,8 @@ export async function checkRunMailbox(args: { } const run = resolveRunScope(runtime, { runId: params.run, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, callerTerminalHandle: handle, callerPaneKey: paneKey, requireCurrentConsumer: true, @@ -59,9 +61,11 @@ export async function checkRunMailbox(args: { const generation = run.consumer_generation const address = `run:${run.id}` runtime.ensureOrchestrationFederationRelay(run.id) - await routeDirectSnapshot(run.id, handle, (throughSequence) => - db.routeUnreadDirectMessagesToRunMailbox(run.id, handle, throughSequence) - ) + if (params.terminal) { + await routeDirectSnapshot(run.id, handle, (throughSequence) => + db.routeUnreadDirectMessagesToRunMailbox(run.id, handle, throughSequence) + ) + } const coordinatorHandle = run.coordinator_handle if (coordinatorHandle && coordinatorHandle !== handle) { await routeDirectSnapshot(run.id, coordinatorHandle, (throughSequence) => @@ -71,6 +75,8 @@ export async function checkRunMailbox(args: { revalidateLegacyCoordinator?.() const currentRun = resolveRunScope(runtime, { runId: run.id, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, callerTerminalHandle: handle, callerPaneKey: paneKey, requireCurrentConsumer: true, @@ -170,6 +176,14 @@ export async function checkRunMailbox(args: { }) try { revalidateLegacyCoordinator?.() + if (params.agentSessionId) { + resolveRunScope(runtime, { + runId: run.id, + callerAgentSessionId: params.agentSessionId, + callerRuntimeFence: params.runtimeFence, + requireCurrentConsumer: true + }) + } } catch (error) { if (!acknowledged) { throw error diff --git a/src/main/runtime/rpc/methods/orchestration/runs/dispatch-creator.ts b/src/main/runtime/rpc/methods/orchestration/runs/dispatch-creator.ts index 8cdbcb8da115..c8d98308078f 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/dispatch-creator.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/dispatch-creator.ts @@ -1,3 +1,4 @@ +import { structuredWorkerIdentities } from '../../../../structured-worker-identity' import type { DispatchCreator } from '../../../../orchestration/db/dispatch-depth' import type { OrcaRuntimeService } from '../../../../orca-runtime' @@ -25,3 +26,13 @@ export function resolveDispatchCreator( processIncarnation: authority?.processIncarnation ?? undefined } } + +export function resolveWorkerStartCallerHandle(params: { + agentSessionId?: string + from?: string +}): string { + const identity = params.agentSessionId + ? structuredWorkerIdentities.getBySessionId(params.agentSessionId) + : undefined + return identity?.handle ?? params.from ?? '' +} diff --git a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts index c9dc9bac7734..0f792a5f8d87 100644 --- a/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts +++ b/src/main/runtime/rpc/methods/orchestration/runs/run-scope.ts @@ -6,22 +6,26 @@ import type { OrcaRuntimeService, OrchestrationCompatibilityCallerAuthority } from '../../../../orca-runtime' -import { structuredWorkerIdentities } from '../../../../structured-worker-identity' -import { resolveStructuredWorkerAuthority } from '../../../../structured-worker-authority' +import { structuredWorkerRecordIsCurrent } from '../../../../structured-worker-identity' +import { readStructuredAgentSessionRecord } from '../../../../structured-worker-authority' export function resolveNativeCoordinatorSession( - runtime: OrcaRuntimeService, + _runtime: OrcaRuntimeService, sessionId: string, runtimeFence: number ): { sessionId: string; worktreeId: string } { - const identity = structuredWorkerIdentities.getBySessionId(sessionId) - const authority = identity - ? resolveStructuredWorkerAuthority(identity.handle, runtime.getOrchestrationDb()) - : null - if (!authority || authority.record.lease.runtimeFence !== runtimeFence) { + const record = readStructuredAgentSessionRecord(sessionId) + if ( + !record || + !structuredWorkerRecordIsCurrent(record) || + record.lease.claimStatus !== 'live' || + record.lease.unreconciled || + record.lease.handoffStage !== null || + record.lease.runtimeFence !== runtimeFence + ) { throw new OrchestrationError('consumer_fenced', 'The native session lease is not current.') } - return { sessionId, worktreeId: authority.identity.worktreeId } + return { sessionId, worktreeId: record.location.workspaceId } } export type RunScopeParams = { diff --git a/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts b/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts index a4696d50cf03..e983329647d8 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts @@ -2,7 +2,7 @@ import type { TuiAgent } from '../../../../../../shared/tui-agent' import type { OrcaRuntimeService } from '../../../../orca-runtime' import type { OrchestrationDb } from '../../../../orchestration/db' import type { RunRow, TaskRow } from '../../../../orchestration/types' -import { resolveDispatchCreator } from '../runs/dispatch-creator' +import { resolveDispatchCreator, resolveWorkerStartCallerHandle } from '../runs/dispatch-creator' import { resolveDispatchCallerWorktreeId } from '../../orchestration-caller-workspace' import { resolveWorkerStartModeOnHost, @@ -31,14 +31,12 @@ import { type WorkerSetupReceipt } from './worker-topology' import { prepareLocalWorkerStart } from './worker-start-validation' - type WorkerStartMutation = { callerFingerprint: string requestId: string method: string payloadHash: string } - export async function startLocalWorker(args: { params: WorkerStartInput runtime: OrcaRuntimeService @@ -51,7 +49,7 @@ export async function startLocalWorker(args: { mode: WorkerStartModeReceipt }): Promise { const { params, runtime, db, run, coordinatorPane, existingTask, orchestrationMutation } = args - const paramsWithFrom = { ...params, from: params.from ?? '' } + const paramsWithFrom = { ...params, from: resolveWorkerStartCallerHandle(params) } const requestedWorktree = paramsWithFrom.worktree ?? 'current' const createsWorktree = requestedWorktree === 'new-child' || requestedWorktree === 'new-top-level' const { agent, launch } = prepareLocalWorkerStart({ @@ -59,7 +57,6 @@ export async function startLocalWorker(args: { createsWorktree, runtime }) - const coordinatorWorktreeId = await resolveDispatchCallerWorktreeId(runtime, paramsWithFrom.from) const creationWorktree = createsWorktree ? await runtime.showManagedWorktree(`id:${coordinatorWorktreeId}`) @@ -86,7 +83,6 @@ export async function startLocalWorker(args: { }) } const mode = await resolveWorkerStartModeOnHost(runtime, args.mode, resolvedWorktree?.id, agent) - const startOptions = { worktree: requestedWorktree, mode, @@ -215,7 +211,6 @@ export async function startLocalWorker(args: { throw new Error('Setup terminal failed to start before the gated agent launch.') } persistWorkerReadinessStage(setupStage) - failedStage = 'agent_readiness' // A structured session is ready the moment its attach returns ok: there is no boot-to-idle // gap and no terminal title to read an idle edge from. @@ -246,7 +241,6 @@ export async function startLocalWorker(args: { setupState: setupReceipt.state, terminalOwnership: params.terminal ? 'external' : 'created' }) - failedStage = 'dispatch_input' const promptDelivery = await deliverWorkerDispatchPreamble({ runtime, diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts index 8ef88f9c9358..ef82125beabb 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-start-schema.ts @@ -36,6 +36,9 @@ export const WorkerStartParams = z devMode: z.boolean().optional() }) .superRefine((params, ctx) => { + if (!params.from && !params.agentSessionId) { + ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['from'], message: 'Missing --from or agent session identity' }) + } if (!params.task && !params.spec) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts index 8036ee788107..8c341230182e 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/workers.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/workers.ts @@ -79,7 +79,7 @@ export const ORCHESTRATION_WORKER_START_METHODS: RpcMethod[] = [ return receipt && typeof receipt === 'object' ? { ...receipt, mode } : receipt } return startLocalWorker({ - params: { ...params, from: params.from ?? '', timeoutMs: readinessTimeoutMs } as never, + params: { ...params, timeoutMs: readinessTimeoutMs }, runtime, db, run, diff --git a/src/main/runtime/structured-worker-child-identity-env.ts b/src/main/runtime/structured-worker-child-identity-env.ts index 45745d81ae22..6e4956d20afe 100644 --- a/src/main/runtime/structured-worker-child-identity-env.ts +++ b/src/main/runtime/structured-worker-child-identity-env.ts @@ -1,39 +1,4 @@ -/** - * The orchestration identity — and the CLI reachability — a structured worker's own child needs - * to speak for itself. - * - * Without `ORCA_TERMINAL_HANDLE` the worker's Bash tool has nothing to pass as `--from`, and - * `resolveOrchestrationTerminalHandle` falls back to a cwd lookup that returns whichever leaf in - * the worktree comes first. Two attacks follow from that: a bare `check` reads and consumes a - * SIBLING's dispatch mailbox, and a bare `send --type worker_done` can settle a sibling's - * context-only dispatch, a tier that has no capability token to reject on. - * - * `ORCA_CLI_COMMAND: 'orca'` is honest ONLY because of the PATH prepend below. Orca's Linux CLI - * installs as `orca-ide` so it never claims GNOME Orca's /usr/bin/orca (stablyai/orca#7904), and - * on packaged macOS/Windows the bundled launcher is reachable only from the app's own resources - * dir. A PTY worker gets that treatment from `buildPtyHostEnv`; a structured worker has no PTY, - * so it applies the SAME function here rather than a second, drifting copy of the rule. - * - * Deliberately NOT `ORCA_PANE_KEY`. Claude structured sessions run hooks, and a pane key in their - * environment starts flowing into hook-emitted agent-status payloads and the hook-attestation, - * agent-row and mobile-projection pipelines, every one of which assumes a pane key names a live - * PTY leaf. It would also open `selectExactWorkerProviderSession`, which is fail-closed today - * precisely because a structured session emits no hook agent status. The CLI needs none of it once - * the handle is present. - * - * A session that is not a dispatched worker gets ONE variable, `ORCA_STRUCTURED_SESSION`, and it - * names nothing: no handle, no pane key, no session id, no token. Its only meaning is "this child - * is a structured session with no orchestration identity", which is what a verb needs in order to - * REFUSE rather than guess one. Because it names nothing it cannot be replayed, cannot impersonate, - * and cannot flow into the hook, agent-row or mobile-projection pipelines the way a pane key would - * — which is why it is a different decision from withholding `ORCA_PANE_KEY`, not a reversal of it. - * Without it, `check` fell through to the active-terminal guess and destructively consumed a - * SIBLING pane's oldest unread batch; `requireUnambiguous` only narrows that, because with exactly - * one terminal pane in the worktree the guess still resolves — to a sibling. - * - * The handle is read from the registry at spawn time, so an in-host recovery respawn re-bakes the - * SAME handle rather than a stale or fresh one. - */ +/** Child CLI routing identity comes from the host lease; legacy worker handles remain optional. */ import { getAppEnvironment, hasAppEnvironment } from '../../shared/app-environment' import { prependOrcaCliDirToChildPath } from '../cli/orca-cli-child-path' @@ -46,12 +11,15 @@ export function structuredWorkerChildIdentityEnv( runtimeFence?: number ): Record { const identity = structuredWorkerIdentities.getBySessionId(sessionId) - if (!identity) { - return { ...childEnv, [ORCA_STRUCTURED_SESSION_ENV]: '1' } + const envBase = { ...childEnv } + delete envBase.ORCA_AGENT_SESSION_ID + delete envBase.ORCA_AGENT_SESSION_RUNTIME_FENCE + if (!identity && runtimeFence === undefined) { + return { ...envBase, [ORCA_STRUCTURED_SESSION_ENV]: '1' } } const env: Record = { - ...childEnv, - ORCA_TERMINAL_HANDLE: identity.handle, + ...envBase, + ...(identity ? { ORCA_TERMINAL_HANDLE: identity.handle } : {}), ORCA_AGENT_SESSION_ID: sessionId, ...(runtimeFence !== undefined ? { ORCA_AGENT_SESSION_RUNTIME_FENCE: String(runtimeFence) } From 3a24b10a862684a22a126ede2f7eb80f22dce701 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:16:49 -0700 Subject: [PATCH 24/26] validate durable native session authority --- .../runs/native-session-authority.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/runtime/rpc/methods/orchestration/runs/native-session-authority.test.ts diff --git a/src/main/runtime/rpc/methods/orchestration/runs/native-session-authority.test.ts b/src/main/runtime/rpc/methods/orchestration/runs/native-session-authority.test.ts new file mode 100644 index 000000000000..02f18734c7bd --- /dev/null +++ b/src/main/runtime/rpc/methods/orchestration/runs/native-session-authority.test.ts @@ -0,0 +1,65 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { AgentSessionRecord } from '../../../../../../shared/agent-session-record' +import type { OrcaRuntimeService } from '../../../../orca-runtime' +import * as authority from '../../../../structured-worker-authority' +import { resolveNativeCoordinatorSession } from './run-scope' + +const runtime = {} as OrcaRuntimeService + +function record(): AgentSessionRecord { + return { + sessionId: 'native-session', + location: { + executionHostId: 'local', + wslDistro: null, + workspaceId: 'folder', + workspaceKind: 'folder' + }, + lease: { + runtimeKind: 'native', + runtimeFence: 7, + claimStatus: 'live', + unreconciled: false, + handoffStage: null + } + } as AgentSessionRecord +} + +afterEach(() => vi.restoreAllMocks()) + +describe('native orchestration authority', () => { + it('resolves an ordinary native folder session without a worker registry or PTY', () => { + vi.spyOn(authority, 'readStructuredAgentSessionRecord').mockReturnValue(record()) + expect(resolveNativeCoordinatorSession(runtime, 'native-session', 7)).toEqual({ + sessionId: 'native-session', + worktreeId: 'folder' + }) + }) + + it('rejects unknown sessions and stale fences', () => { + const read = vi.spyOn(authority, 'readStructuredAgentSessionRecord').mockReturnValue(null) + expect(() => resolveNativeCoordinatorSession(runtime, 'unknown', 7)).toThrow( + 'lease is not current' + ) + read.mockReturnValue(record()) + expect(() => resolveNativeCoordinatorSession(runtime, 'native-session', 6)).toThrow( + 'lease is not current' + ) + }) + + it.each([ + { claimStatus: 'released' }, + { claimStatus: 'reserved' }, + { claimStatus: 'conflicted' }, + { unreconciled: true }, + { handoffStage: 'preparing' }, + { runtimeKind: 'tui' } + ])('rejects a lease without current native ownership: %j', (patch) => { + const value = record() + Object.assign(value.lease, patch) + vi.spyOn(authority, 'readStructuredAgentSessionRecord').mockReturnValue(value) + expect(() => resolveNativeCoordinatorSession(runtime, 'native-session', 7)).toThrow( + 'lease is not current' + ) + }) +}) From 9d3e85a4bca0f0be927114f58bc35f94204818f5 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:16:51 -0700 Subject: [PATCH 25/26] route native orchestration mailbox checks by session --- .../orchestration/question-handler.ts | 4 +-- ...codex-structured-child-environment.test.ts | 4 ++- src/main/pty/wsl-orca-env.ts | 2 ++ ...ructured-worker-child-identity-env.test.ts | 26 +++++++++++++++++++ .../structured-worker-child-identity-env.ts | 3 +++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/cli/handlers/orchestration/question-handler.ts b/src/cli/handlers/orchestration/question-handler.ts index f497b192fa3d..3ec240704082 100644 --- a/src/cli/handlers/orchestration/question-handler.ts +++ b/src/cli/handlers/orchestration/question-handler.ts @@ -108,9 +108,7 @@ export const ORCHESTRATION_QUESTION_HANDLER: Record = { resolveOrchestrationCliExecutable(), 'orchestration', 'ask', - '--from', - ...session, - from, + ...(from ? ['--from', from] : []), ...(dispatchCapability ? ['--dispatch-capability', dispatchCapability] : []), '--resume', messageId, diff --git a/src/main/codex/codex-structured-child-environment.test.ts b/src/main/codex/codex-structured-child-environment.test.ts index 201efc0b0c5b..ee52ab8bc530 100644 --- a/src/main/codex/codex-structured-child-environment.test.ts +++ b/src/main/codex/codex-structured-child-environment.test.ts @@ -59,7 +59,9 @@ describe('buildCodexStructuredChildEnvironment', () => { hostScope: { kind: 'local', hostId: 'local' } }) try { - const env = buildCodexStructuredChildEnvironment(launch, 'spawn-token', sessionId) + const env = buildCodexStructuredChildEnvironment(launch, 'spawn-token', sessionId, 7) + expect(env.ORCA_AGENT_SESSION_ID).toBe(sessionId) + expect(env.ORCA_AGENT_SESSION_RUNTIME_FENCE).toBe('7') expect(env.ORCA_TERMINAL_HANDLE).toBe(handle) expect(env.ORCA_CLI_COMMAND).toBe('orca') // A pane key here would leak into hook-emitted agent statuses, which assume a PTY leaf. diff --git a/src/main/pty/wsl-orca-env.ts b/src/main/pty/wsl-orca-env.ts index 262df20b3b2f..4edb455eb99d 100644 --- a/src/main/pty/wsl-orca-env.ts +++ b/src/main/pty/wsl-orca-env.ts @@ -72,6 +72,8 @@ export function addOrcaWslInteropEnv(env: Record): void { // Why: wsl.exe only imports selected Windows env vars, so WSL needs the wrapper root, pane identity, and hook/OMP coordinates at start. const passthroughEntries = [ 'ORCA_TERMINAL_HANDLE/u', + 'ORCA_AGENT_SESSION_ID/u', + 'ORCA_AGENT_SESSION_RUNTIME_FENCE/u', 'ORCA_USER_DATA_PATH/p', // Why /p: the guest reads the content-addressed wrapper tree through /mnt/c, // and it cannot derive the hash segment from ORCA_USER_DATA_PATH alone. diff --git a/src/main/runtime/structured-worker-child-identity-env.test.ts b/src/main/runtime/structured-worker-child-identity-env.test.ts index e966dd558240..8bc2abe13a75 100644 --- a/src/main/runtime/structured-worker-child-identity-env.test.ts +++ b/src/main/runtime/structured-worker-child-identity-env.test.ts @@ -76,6 +76,32 @@ describe('structuredWorkerChildIdentityEnv', () => { expect(shim.ensureLinuxTerminalOrcaCliShimDir).not.toHaveBeenCalled() }) + it('replaces inherited orchestration credentials with the current native lease', () => { + const env = structuredWorkerChildIdentityEnv(SESSION_ID, { + ORCA_AGENT_SESSION_ID: 'parent-session', + ORCA_AGENT_SESSION_RUNTIME_FENCE: '999', + ORCA_TERMINAL_HANDLE: 'parent-handle', + ORCA_PANE_KEY: 'parent-pane', + ORCA_STRUCTURED_SESSION: '1' + }, 3) + expect(env.ORCA_AGENT_SESSION_ID).toBe(SESSION_ID) + expect(env.ORCA_AGENT_SESSION_RUNTIME_FENCE).toBe('3') + expect(env.ORCA_TERMINAL_HANDLE).toBeUndefined() + expect(env.ORCA_PANE_KEY).toBeUndefined() + expect(env.ORCA_STRUCTURED_SESSION).toBeUndefined() + expect(env.ORCA_CLI_COMMAND).toBe('orca') + }) + + it('does not inherit parent session credentials without a host lease', () => { + const env = structuredWorkerChildIdentityEnv(SESSION_ID, { + ORCA_AGENT_SESSION_ID: 'parent-session', + ORCA_AGENT_SESSION_RUNTIME_FENCE: '999' + }) + expect(env.ORCA_AGENT_SESSION_ID).toBeUndefined() + expect(env.ORCA_AGENT_SESSION_RUNTIME_FENCE).toBeUndefined() + expect(env.ORCA_STRUCTURED_SESSION).toBe('1') + }) + it('gives a packaged-Linux worker the bare-orca shim its ORCA_CLI_COMMAND assumes', () => { // Without this the child's first `orca orchestration check` execs GNOME Orca — the CLI // installs as `orca-ide` on Linux (stablyai/orca#7904) — and the dispatch hangs to timeout. diff --git a/src/main/runtime/structured-worker-child-identity-env.ts b/src/main/runtime/structured-worker-child-identity-env.ts index 6e4956d20afe..b5efd75d0b77 100644 --- a/src/main/runtime/structured-worker-child-identity-env.ts +++ b/src/main/runtime/structured-worker-child-identity-env.ts @@ -12,6 +12,9 @@ export function structuredWorkerChildIdentityEnv( ): Record { const identity = structuredWorkerIdentities.getBySessionId(sessionId) const envBase = { ...childEnv } + delete envBase.ORCA_TERMINAL_HANDLE + delete envBase.ORCA_PANE_KEY + delete envBase[ORCA_STRUCTURED_SESSION_ENV] delete envBase.ORCA_AGENT_SESSION_ID delete envBase.ORCA_AGENT_SESSION_RUNTIME_FENCE if (!identity && runtimeFence === undefined) { From 2ab240580fc88971b325bdd69c1d1f00d9ac3397 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Tue, 8 Sep 2026 16:17:15 -0700 Subject: [PATCH 26/26] format native orchestration question handler --- src/cli/handlers/orchestration/question-handler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/handlers/orchestration/question-handler.ts b/src/cli/handlers/orchestration/question-handler.ts index 3ec240704082..3b66a331173d 100644 --- a/src/cli/handlers/orchestration/question-handler.ts +++ b/src/cli/handlers/orchestration/question-handler.ts @@ -16,7 +16,10 @@ import { resolveCompatibilityCliCommand, resolvePackagedWindowsCompatibilityCommand } from './runtime-compatibility' -import { orchestrationSessionPayload, resolveOrchestrationTerminalHandle } from './terminal-identity' +import { + orchestrationSessionPayload, + resolveOrchestrationTerminalHandle +} from './terminal-identity' export const ORCHESTRATION_QUESTION_HANDLER: Record = { 'orchestration ask': async ({ flags, client, cwd, json }) => {