From 177ff7d9acb24462f680f88c5cf5cbc035becc24 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:53:19 -0700 Subject: [PATCH] refactor(orchestration): drop the session id's terminal-view spawn now that the handoff is gone The terminal handoff was removed, so no terminal is ever a structured session: - delete the terminal-view identity env and its WSL passthrough, and their tests; - strip the session caller keys from every terminal's env unconditionally; - the CLI's own-address spelling moves beside the injected id in src/shared, with a test pinning it to the address the host's party resolver gives that session. --- .../orchestration/dispatch-handlers.ts | 2 +- .../orchestration/message-check-handler.ts | 2 +- .../orchestration/terminal-identity.ts | 6 ++- .../orchestration-session-caller-cli.test.ts | 39 ++++++++------- src/cli/session-caller-flags.ts | 21 ++------ .../pty-daemon-spawn-agent-home-env.test.ts | 12 +---- .../ipc/pty-spawn-env-terminal-basics.test.ts | 7 --- src/main/ipc/pty/host-env/pi-agent.ts | 11 ++-- src/main/pty/wsl-orca-env.test.ts | 20 -------- src/main/pty/wsl-orca-env.ts | 4 -- ...ca-runtime-agent-session-operation.test.ts | 34 ------------- .../orchestration-party-cli-address.test.ts | 50 +++++++++++++++++++ ...uctured-session-child-identity-env.test.ts | 18 +------ .../structured-session-child-identity-env.ts | 18 +------ src/shared/agent-session-caller-env.ts | 28 +++++++++-- 15 files changed, 114 insertions(+), 158 deletions(-) create mode 100644 src/main/runtime/orchestration/orchestration-party-cli-address.test.ts diff --git a/src/cli/handlers/orchestration/dispatch-handlers.ts b/src/cli/handlers/orchestration/dispatch-handlers.ts index 5f28d4bbcd3..c8ade53e907 100644 --- a/src/cli/handlers/orchestration/dispatch-handlers.ts +++ b/src/cli/handlers/orchestration/dispatch-handlers.ts @@ -6,7 +6,7 @@ import { orchestrationMigrationData } from '../../../shared/orchestration-rpc-co import { callOrchestrationMutation } from './mutation-request' import { isDevCliInvocation } from './runtime-compatibility' import { resolveCoordinatorTerminalHandle } from './terminal-identity' -import { injectedSessionAddress } from '../../session-caller-flags' +import { injectedSessionAddress } from '../../../shared/agent-session-caller-env' export const ORCHESTRATION_DISPATCH_HANDLER: Record = { 'orchestration dispatch': async ({ flags, client, cwd, json }) => { diff --git a/src/cli/handlers/orchestration/message-check-handler.ts b/src/cli/handlers/orchestration/message-check-handler.ts index 26cefcc06ea..6ace43eac31 100644 --- a/src/cli/handlers/orchestration/message-check-handler.ts +++ b/src/cli/handlers/orchestration/message-check-handler.ts @@ -41,7 +41,7 @@ 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') - // Why: a session names itself by its id alone; a terminal view's pane is not its identity. + // Why: a session names itself by its id alone; a pane key it inherited is not its identity. const paneKey = explicitTerminal || terminal === undefined ? undefined : process.env.ORCA_PANE_KEY const callerLabel = orchestrationCallerLabel(terminal) diff --git a/src/cli/handlers/orchestration/terminal-identity.ts b/src/cli/handlers/orchestration/terminal-identity.ts index 572b9096bce..b97500db89b 100644 --- a/src/cli/handlers/orchestration/terminal-identity.ts +++ b/src/cli/handlers/orchestration/terminal-identity.ts @@ -3,8 +3,10 @@ import { getOptionalStringFlag } from '../../flags' import { RuntimeClientError } from '../../runtime-client' import { getTerminalHandle } from '../../selectors' import { hasStructuredSessionMarker } from '../../../shared/structured-session-marker' -import { readInjectedAgentSessionId } from '../../../shared/agent-session-caller-env' -import { injectedSessionAddress } from '../../session-caller-flags' +import { + injectedSessionAddress, + readInjectedAgentSessionId +} from '../../../shared/agent-session-caller-env' /** * The caller's terminal handle, or `undefined` when an injected agent session id names the caller: diff --git a/src/cli/orchestration-session-caller-cli.test.ts b/src/cli/orchestration-session-caller-cli.test.ts index f0588dc80d7..59bcae5b845 100644 --- a/src/cli/orchestration-session-caller-cli.test.ts +++ b/src/cli/orchestration-session-caller-cli.test.ts @@ -6,8 +6,9 @@ * naming anyone else is refused before any request — never silently dropped, never allowed to win. * The #21097 accident was a chat that named a sibling's terminal and consumed that sibling's mail. * - * The session env here is the hardest case, a chat in terminal view: it also carries its pane's - * `ORCA_TERMINAL_HANDLE` and `ORCA_PANE_KEY`, and the implicit-terminal guess has a sibling to find. + * The session env here is the hardest case, a chat that inherited a pane's `ORCA_TERMINAL_HANDLE` + * and `ORCA_PANE_KEY` (an Orca launched from an Orca terminal), and the implicit-terminal guess has + * a sibling to find. */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -238,12 +239,12 @@ function setEnv(env: Partial>): vo } } -/** A chat in terminal view: its id, plus the pane identity that view inherits. */ -function asSessionInTerminalView(): void { +/** A chat with its id, plus a pane identity it inherited from the Orca that launched it. */ +function asSessionWithInheritedPane(): void { setEnv({ ORCA_AGENT_SESSION_ID: SESSION, - ORCA_TERMINAL_HANDLE: 'term_view_pane', - ORCA_PANE_KEY: 'tab_view:11111111-1111-4111-8111-111111111111' + ORCA_TERMINAL_HANDLE: 'term_inherited_pane', + ORCA_PANE_KEY: 'tab_inherited:11111111-1111-4111-8111-111111111111' }) } @@ -261,7 +262,7 @@ afterEach(() => { }) describe.each(CALLER_VERBS)('orchestration $command run as an agent session', (verb) => { - beforeEach(asSessionInTerminalView) + beforeEach(asSessionWithInheritedPane) it('acts as the session: no terminal is resolved, guessed or sent', async () => { await invoke(verb.command, flagMap(verb.flags)) @@ -269,7 +270,7 @@ describe.each(CALLER_VERBS)('orchestration $command run as an agent session', (v const [params] = callsTo(verb.method) expect(params, 'the verb reached its method').toBeDefined() expect(params?.[verb.callerParam]).toBeUndefined() - // A terminal view's pane is not the session's identity. + // An inherited pane is not the session's identity. expect(params?.terminalPaneKey).toBeUndefined() expect(params?.senderPaneKey).toBeUndefined() expect(getTerminalHandleMock).not.toHaveBeenCalled() @@ -293,9 +294,9 @@ describe.each(CALLER_VERBS)('orchestration $command run as an agent session', (v ) it.runIf(verb.callerFlag !== undefined)( - "refuses its own terminal view's pane handle too: the session, not the pane, is the caller", + 'refuses an inherited pane handle too: the session, not the pane, is the caller', async () => { - const flags = flagMap({ ...verb.flags, [verb.callerFlag ?? 'from']: 'term_view_pane' }) + const flags = flagMap({ ...verb.flags, [verb.callerFlag ?? 'from']: 'term_inherited_pane' }) await expect(invoke(verb.command, flags)).rejects.toMatchObject({ code: 'consumer_fenced' }) expect(callMock).not.toHaveBeenCalled() @@ -318,7 +319,7 @@ describe.each([ { command: 'gate-list', method: 'gateList', callerParam: 'from' }, { command: 'task-list', method: 'taskList', callerParam: 'callerTerminalHandle' } ])('orchestration $command --run run as an agent session', ({ command, method, callerParam }) => { - beforeEach(asSessionInTerminalView) + beforeEach(asSessionWithInheritedPane) it('needs no caller, but refuses a --from naming another caller, before any request', async () => { await invoke(command, flagMap({ run: 'run_1' })) @@ -406,7 +407,7 @@ describe('the identity a session presents', () => { await invoke('dispatch-show', flagMap({ task: 'task_1', preamble: true, ...flags })) return callsTo('dispatchShow')[0]?.from } - asSessionInTerminalView() + asSessionWithInheritedPane() expect(await preview({})).toBe(`session:${SESSION}`) // Not a caller flag: it names the text to preview, so it is never fenced. expect(await preview({ from: 'term_sibling' })).toBe('term_sibling') @@ -416,7 +417,7 @@ describe('the identity a session presents', () => { }) it('resumes a timed-out ask as the session, without naming a terminal', async () => { - asSessionInTerminalView() + asSessionWithInheritedPane() callMock.mockResolvedValue({ result: { ...RESULT.result, answer: null, timedOut: true } }) const errors = vi.mocked(console.error) @@ -443,7 +444,7 @@ describe('a host refusal of the session', () => { it.each(['check', 'run-current', 'worker-list'])( 'surfaces from %s verbatim, never widened, retried or turned into a terminal guess', async (command) => { - asSessionInTerminalView() + asSessionWithInheritedPane() callMock.mockRejectedValue(WORKER_GONE) await expect(invoke(command, flagMap({}))).rejects.toBe(WORKER_GONE) @@ -481,11 +482,11 @@ describe('the orchestration envelope', () => { it('carries the injected id beside whatever terminal evidence the process also has', () => { const envelope = createOrchestrationCompatibilityEnvelope({ ORCA_AGENT_SESSION_ID: ` ${SESSION} `, - ORCA_TERMINAL_HANDLE: 'term_view_pane' + ORCA_TERMINAL_HANDLE: 'term_inherited_pane' }) expect(envelope.orchestrationCompatibilityEvidence).toEqual({ - terminalHandle: 'term_view_pane', + terminalHandle: 'term_inherited_pane', agentSessionId: SESSION }) }) @@ -539,7 +540,7 @@ describe('which flag names the caller, declared on every spec', () => { it.each(callerFlagVerbs)( '$command refuses --$flag naming another caller, before any request', async ({ command, flag }) => { - asSessionInTerminalView() + asSessionWithInheritedPane() await expect( invoke(command, flagMap({ ...EVERY_REQUIRED_FLAG, [flag]: 'term_sibling' })) ).rejects.toMatchObject({ code: 'consumer_fenced' }) @@ -555,7 +556,7 @@ describe('which flag names the caller, declared on every spec', () => { .map((flag) => ({ command: spec.path[1] ?? '', flag })) ) )('$command passes a --$flag target through unfenced', async ({ command, flag }) => { - asSessionInTerminalView() + asSessionWithInheritedPane() await invoke(command, flagMap({ ...EVERY_REQUIRED_FLAG, [flag]: 'term_sibling' })).catch( (error: unknown) => { expect(error).not.toMatchObject({ code: 'consumer_fenced' }) @@ -608,7 +609,7 @@ describe('every orchestration verb, enumerated', () => { 'worker-start' ]) - asSessionInTerminalView() + asSessionWithInheritedPane() expect(await verbsThatGuess()).toEqual([]) }) }) diff --git a/src/cli/session-caller-flags.ts b/src/cli/session-caller-flags.ts index df3f893dd29..cbefa9ee2b8 100644 --- a/src/cli/session-caller-flags.ts +++ b/src/cli/session-caller-flags.ts @@ -10,8 +10,10 @@ import type { CommandSpec, IdentityFlag } from './command-spec' import { RuntimeClientError } from './runtime/types' -import { readInjectedAgentSessionId } from '../shared/agent-session-caller-env' -import { isStructuredWorkerHandle } from '../shared/structured-worker-handle' +import { + injectedSessionAddress, + readInjectedAgentSessionId +} from '../shared/agent-session-caller-env' import { ORCA_SESSION_ADDRESS_PREFIX } from '../shared/orca-session-address-prefix' export function refuseConflictingSessionCallerFlags( @@ -53,18 +55,3 @@ function namesInjectedSession(value: string, sessionId: string, env: NodeJS.Proc value === injectedSessionAddress(env) ) } - -/** - * The address the host gives this session: a structured worker keeps the handle it was minted, any - * other session is `session:`. Only for text that must match what the host writes. - */ -export function injectedSessionAddress(env: NodeJS.ProcessEnv = process.env): string | undefined { - const sessionId = readInjectedAgentSessionId(env) - if (!sessionId) { - return undefined - } - const ownHandle = env.ORCA_TERMINAL_HANDLE - return isStructuredWorkerHandle(ownHandle) - ? ownHandle - : `${ORCA_SESSION_ADDRESS_PREFIX}${sessionId}` -} diff --git a/src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts b/src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts index 273f08580f3..f6ffae641d3 100644 --- a/src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts +++ b/src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts @@ -413,7 +413,7 @@ describe('registerPtyHandlers', () => { ]) ) }) - it("strips an inherited agent session id and keeps a terminal view's own", async () => { + it('strips an inherited agent session id', async () => { // Why: a daemon forked by an Orca launched inside a structured session inherits its id, // and every daemon pane would present that session as its orchestration caller. const inherited = await daemonSpawnAndGetOptions(undefined, undefined, undefined, { @@ -423,14 +423,6 @@ describe('registerPtyHandlers', () => { expect(inherited.envToDelete).toEqual( expect.arrayContaining(['ORCA_AGENT_SESSION_ID', 'ORCA_STRUCTURED_SESSION']) ) - const own = await daemonSpawnAndGetOptions( - { ORCA_AGENT_SESSION_ID: 'f7a1c0de-1111-4222-8333-444455556666' }, - undefined, - undefined, - { ORCA_AGENT_SESSION_ID: 'a0b1c2d3-0000-4000-8000-00000000abcd' } - ) - expect(own.envToDelete ?? []).not.toContain('ORCA_AGENT_SESSION_ID') - expect(own.env.ORCA_AGENT_SESSION_ID).toBe('f7a1c0de-1111-4222-8333-444455556666') }) it('preserves an explicitly requested Claude child-session stamp', async () => { // Why: only inherited values are poison; a caller deliberately spawning a @@ -462,7 +454,7 @@ describe('registerPtyHandlers', () => { // Why: bare `orca` must resolve to the Orca CLI before /usr/bin/orca (the GNOME screen reader) in Orca terminals (#7904). expect(entries.indexOf(shimDir)).toBeGreaterThanOrEqual(0) expect(entries.indexOf(shimDir)).toBeLessThan(entries.indexOf('/usr/bin')) - // The same absolute spelling a structured session gets, so a terminal view keeps it too. + // The same absolute spelling a structured session gets. expect(env.ORCA_CLI_COMMAND).toBe(join(shimDir, 'orca')) } finally { Object.defineProperty(process, 'platform', { diff --git a/src/main/ipc/pty-spawn-env-terminal-basics.test.ts b/src/main/ipc/pty-spawn-env-terminal-basics.test.ts index e269bc7f6df..bed6953e2b1 100644 --- a/src/main/ipc/pty-spawn-env-terminal-basics.test.ts +++ b/src/main/ipc/pty-spawn-env-terminal-basics.test.ts @@ -361,13 +361,6 @@ describe('registerPtyHandlers', () => { expect(env.ORCA_AGENT_SESSION_ID).toBeUndefined() expect(env.ORCA_STRUCTURED_SESSION).toBeUndefined() }) - it('keeps the session id a terminal view is spawned with', async () => { - const env = await spawnAndGetEnv( - { ORCA_AGENT_SESSION_ID: 'f7a1c0de-1111-4222-8333-444455556666' }, - { ORCA_AGENT_SESSION_ID: 'a0b1c2d3-0000-4000-8000-00000000abcd' } - ) - expect(env.ORCA_AGENT_SESSION_ID).toBe('f7a1c0de-1111-4222-8333-444455556666') - }) it('keeps an explicitly requested Claude child-session stamp on a local spawn', async () => { const env = await spawnAndGetEnv( { CLAUDE_CODE_CHILD_SESSION: '1' }, diff --git a/src/main/ipc/pty/host-env/pi-agent.ts b/src/main/ipc/pty/host-env/pi-agent.ts index aa8a7d2a21c..d2dd8803fa4 100644 --- a/src/main/ipc/pty/host-env/pi-agent.ts +++ b/src/main/ipc/pty/host-env/pi-agent.ts @@ -141,11 +141,12 @@ export function getInheritedAgentSessionStampEnvKeysToDelete( spawnEnv: Record | undefined ): string[] { const env = spawnEnv ?? {} - // Why: strip only values inherited from the pty host; a caller that explicitly - // provides a stamp (a nested Claude child, a structured session's terminal view) keeps it. - return [...CLAUDE_CHILD_SESSION_STAMP_ENV_KEYS, ...ORCA_AGENT_SESSION_CALLER_ENV_KEYS].filter( - (key) => env[key] === undefined - ) + // Why: a caller that explicitly provides a Claude stamp (a nested Claude child) keeps it; no + // terminal is a structured session, so the session caller keys always go. + return [ + ...CLAUDE_CHILD_SESSION_STAMP_ENV_KEYS.filter((key) => env[key] === undefined), + ...ORCA_AGENT_SESSION_CALLER_ENV_KEYS + ] } // Why: a nested terminal can inherit prior OpenCode/Pi/OMP overlay env; restore the user's recorded source dir, else strip only Orca-owned values. diff --git a/src/main/pty/wsl-orca-env.test.ts b/src/main/pty/wsl-orca-env.test.ts index 130b858f24c..e56146ea991 100644 --- a/src/main/pty/wsl-orca-env.test.ts +++ b/src/main/pty/wsl-orca-env.test.ts @@ -16,26 +16,6 @@ describe('addOrcaWslInteropEnv', () => { expect(env.WSLENV).toBe('ORCA_TERMINAL_HANDLE/u:ORCA_SHELL_READY_ROOT/p') }) - it("carries a terminal view's session id into the guest untranslated, beside the WSL stamp", () => { - // Crossing is what makes the claim refusable: without it the pane handle would silently become - // the caller inside WSL, and the host could not tell the session was asking at all. - const env: Record = { - ORCA_TERMINAL_HANDLE: 'term_wsl', - ORCA_AGENT_SESSION_ID: 'f7a1c0de-1111-4222-8333-444455556666' - } - stampWslOrchestrationCompatibilityHost(env, 'local', 'Ubuntu') - - addOrcaWslInteropEnv(env) - - expect(env.WSLENV?.split(':')).toEqual( - expect.arrayContaining([ - 'ORCA_AGENT_SESSION_ID/u', - 'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_KIND/u', - 'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_INCARNATION/u' - ]) - ) - }) - // Why this is published at all: the wrapper tree is content-addressed, so the // in-guest login script cannot rebuild its path from ORCA_USER_DATA_PATH -- it // cannot derive the hash segment. Without this the guest finds no wrapper and diff --git a/src/main/pty/wsl-orca-env.ts b/src/main/pty/wsl-orca-env.ts index 5e89e4eb350..04035a957ca 100644 --- a/src/main/pty/wsl-orca-env.ts +++ b/src/main/pty/wsl-orca-env.ts @@ -9,7 +9,6 @@ import { SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV } from '../../shared/setup-agent-sequencing' import { getShellReadyWrapperRoot } from '../providers/local-pty-shell-ready-wrapper-root' -import { ORCA_AGENT_SESSION_ID_ENV } from '../../shared/agent-session-caller-env' import { ORCA_IMAGE_PROTOCOL_ENV } from '../../shared/terminal-image-protocol' const WSLENV_ENTRY_SEPARATOR = ':' @@ -75,9 +74,6 @@ 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', - // Why: a structured session's terminal view in a WSL shell must still present its id, so the - // host refuses the cross-host claim instead of the pane handle silently becoming its caller. - `${ORCA_AGENT_SESSION_ID_ENV}/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/orca-runtime-agent-session-operation.test.ts b/src/main/runtime/orca-runtime-agent-session-operation.test.ts index e22d6de28e8..8d7d6fa6812 100644 --- a/src/main/runtime/orca-runtime-agent-session-operation.test.ts +++ b/src/main/runtime/orca-runtime-agent-session-operation.test.ts @@ -266,40 +266,6 @@ describe('agent-session create operation ledger', () => { ) }) - it("spawns a structured session's terminal view with its session id, never persisting it", async () => { - // Switching a chat to terminal view must not change who it is as an orchestration caller. The - // id rides the spawn env only: the launch config persists, and a relaunch from it would replay - // the id without the handoff that binds the terminal to the session. - const sessionId = 'f7a1c0de-1111-4222-8333-444455556666' - const runtime = createRuntime() - const createTerminal = vi.spyOn(runtime, 'createTerminal').mockResolvedValue(terminal()) - const resume = { - kind: 'explicit' as const, - worktree: 'id:worktree-1', - agent: 'claude' as const, - providerSession: { key: 'session_id' as const, id: 'provider-session-1' } - } - - await runtime.ensureAgentSession( - resume, - {}, - { - spawnToken: 'spawn-9', - providerRoot: '/accounts/claude', - sessionId - } - ) - await runtime.ensureAgentSession(resume) - - const [handoff, plain] = createTerminal.mock.calls.map(([, opts]) => opts) - expect(handoff).toMatchObject({ - structuredAgentSessionId: sessionId, - env: expect.objectContaining({ ORCA_AGENT_SESSION_ID: sessionId }) - }) - expect(handoff?.launchConfig?.agentEnv).not.toHaveProperty('ORCA_AGENT_SESSION_ID') - expect(plain?.env ?? {}).not.toHaveProperty('ORCA_AGENT_SESSION_ID') - }) - it('selects nested SSH legacy fallback before reading a Pi transcript path locally', async () => { const runtime = createRuntime() const internal = runtime as unknown as { diff --git a/src/main/runtime/orchestration/orchestration-party-cli-address.test.ts b/src/main/runtime/orchestration/orchestration-party-cli-address.test.ts new file mode 100644 index 00000000000..f6f1c1e2574 --- /dev/null +++ b/src/main/runtime/orchestration/orchestration-party-cli-address.test.ts @@ -0,0 +1,50 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { injectedSessionAddress } from '../../../shared/agent-session-caller-env' +import { testOrcaSessionId } from '../../../shared/orca-session-address-test-fixture' +import { + mintStructuredWorkerHandle, + mintStructuredWorkerPaneKey, + structuredWorkerIdentities, + structuredWorkerProcessIncarnation +} from '../structured-worker-identity' +import { resolveOrcaSessionParty } from './orchestration-party' + +// The CLI spells its own address without the host resolver (it runs before the codec's module +// graph), so it must land on the one mailbox address the resolver gives the same session. +const CHAT = testOrcaSessionId('f7a1c0de-1111-4222-8333-444455556666') +const WORKER = testOrcaSessionId('a0b1c2d3-0000-4000-8000-00000000abcd') + +afterEach(() => { + structuredWorkerIdentities.clear() +}) + +describe("the CLI's own address", () => { + it("is a chat's session address, even beside a pane handle it inherited", () => { + const hostAddress = resolveOrcaSessionParty(CHAT, null).address + expect(injectedSessionAddress({ ORCA_AGENT_SESSION_ID: CHAT })).toBe(hostAddress) + expect( + injectedSessionAddress({ + ORCA_AGENT_SESSION_ID: CHAT, + ORCA_TERMINAL_HANDLE: 'term_inherited' + }) + ).toBe(hostAddress) + }) + + it("is a structured worker's minted handle, as the host resolves it", () => { + const handle = mintStructuredWorkerHandle() + structuredWorkerIdentities.register({ + handle, + sessionId: WORKER, + agent: 'claude', + paneKey: mintStructuredWorkerPaneKey(WORKER), + processIncarnation: structuredWorkerProcessIncarnation(WORKER), + worktreeId: 'wt_1', + hostScope: { kind: 'local', hostId: 'local' } + }) + const hostAddress = resolveOrcaSessionParty(WORKER, null).address + expect(hostAddress).toBe(handle) + expect( + injectedSessionAddress({ ORCA_AGENT_SESSION_ID: WORKER, ORCA_TERMINAL_HANDLE: handle }) + ).toBe(hostAddress) + }) +}) diff --git a/src/main/runtime/structured-session-child-identity-env.test.ts b/src/main/runtime/structured-session-child-identity-env.test.ts index 34f746979a4..1289e979ad6 100644 --- a/src/main/runtime/structured-session-child-identity-env.test.ts +++ b/src/main/runtime/structured-session-child-identity-env.test.ts @@ -5,10 +5,7 @@ import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-p const shim = vi.hoisted(() => ({ ensureLinuxTerminalOrcaCliShimDir: vi.fn() })) vi.mock('../cli/linux-terminal-orca-cli-shim', () => shim) -import { - structuredSessionChildIdentityEnv, - withStructuredSessionTerminalViewEnv -} from './structured-session-child-identity-env' +import { structuredSessionChildIdentityEnv } from './structured-session-child-identity-env' import { mintStructuredWorkerHandle, mintStructuredWorkerPaneKey, @@ -163,19 +160,6 @@ describe('structuredSessionChildIdentityEnv', () => { expect(console.warn).toHaveBeenCalledOnce() }) - it("gives the terminal view the same id, and leaves any other terminal's env as given", () => { - expect(withStructuredSessionTerminalViewEnv({ CLAUDE_CONFIG_DIR: '/c' }, SESSION_ID)).toEqual({ - CLAUDE_CONFIG_DIR: '/c', - ORCA_AGENT_SESSION_ID: SESSION_ID - }) - expect(withStructuredSessionTerminalViewEnv(undefined, SESSION_ID)).toEqual({ - ORCA_AGENT_SESSION_ID: SESSION_ID - }) - const plain = { CLAUDE_CONFIG_DIR: '/c' } - expect(withStructuredSessionTerminalViewEnv(plain, undefined)).toBe(plain) - expect(withStructuredSessionTerminalViewEnv(undefined, undefined)).toBeUndefined() - }) - it('never puts a pane key in the child environment', () => { // A pane key here flows into hook-emitted agent statuses and the attestation, agent-row and // mobile-projection pipelines, all of which assume it names a live PTY leaf. diff --git a/src/main/runtime/structured-session-child-identity-env.ts b/src/main/runtime/structured-session-child-identity-env.ts index af17056b23d..62a901860cf 100644 --- a/src/main/runtime/structured-session-child-identity-env.ts +++ b/src/main/runtime/structured-session-child-identity-env.ts @@ -37,9 +37,7 @@ * `ORCA_STRUCTURED_SESSION` stays beside the id for a CLI that predates it — one reached through a * global install when a shell rc resets PATH — which would otherwise guess a sibling's terminal; * such a CLI refuses on the marker. A current CLI checks the id first, so the marker never makes a - * session with an id identity-less. The terminal view deliberately gets the id WITHOUT the marker: - * there an older CLI has the view's own pane handle and legitimately acts as that pane, and the - * marker would make it refuse its own pane. + * session with an id identity-less. * * 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. @@ -66,20 +64,6 @@ export function structuredSessionChildIdentityEnv( return env } -/** - * The same session id for its terminal view, so switching views never changes who the session is. - * Same-host only, as above: the host refuses the claim from a terminal that runs in WSL or over SSH. - * A terminal that is not a session's view keeps its env exactly as given. No marker (see above), and - * no CLI command: the PTY lane names this app's launcher for every local terminal, and this env also - * crosses to SSH hosts, where a local path means nothing. - */ -export function withStructuredSessionTerminalViewEnv( - env: Record | undefined, - sessionId: string | undefined -): Record | undefined { - return sessionId ? { ...env, [ORCA_AGENT_SESSION_ID_ENV]: sessionId } : env -} - /** * A host with no app environment installed — a plain-Node fork, or a unit test — has no userData * root to resolve, and inventing one would write a shim into the wrong directory. diff --git a/src/shared/agent-session-caller-env.ts b/src/shared/agent-session-caller-env.ts index e13da348b5a..5f66128dc5f 100644 --- a/src/shared/agent-session-caller-env.ts +++ b/src/shared/agent-session-caller-env.ts @@ -1,13 +1,15 @@ /** - * The Orca-minted agent session id, injected into a structured session's own child processes, in - * native chat and in terminal view alike. When it is present it IS the orchestration caller: the - * CLI sends it in the orchestration envelope and the host resolves the session it names, so no - * terminal is resolved or guessed on its behalf. + * The Orca-minted agent session id, injected into a structured session's own child processes. When + * it is present it IS the orchestration caller: the CLI sends it in the orchestration envelope and + * the host resolves the session it names, so no terminal is resolved or guessed on its behalf. * * Identity by session id assumes one machine and one user. A host boundary (SSH, a paired peer, * WSL) re-opens that decision: the host refuses a claim that arrives across one, and the SSH * passthrough never carries the id. */ +import { ORCA_SESSION_ADDRESS_PREFIX } from './orca-session-address-prefix' +import { isStructuredWorkerHandle } from './structured-worker-handle' + export const ORCA_AGENT_SESSION_ID_ENV = 'ORCA_AGENT_SESSION_ID' export function readInjectedAgentSessionId( @@ -16,3 +18,21 @@ export function readInjectedAgentSessionId( const value = env[ORCA_AGENT_SESSION_ID_ENV]?.trim() return value ? value : undefined } + +/** + * The address the host gives this session (`mailboxAddressOf` on its resolved party): a structured + * worker keeps the handle it was minted, any other session is `session:`. Only for text that + * must match what the host writes; the CLI spells it without the host resolver. + */ +export function injectedSessionAddress( + env: Readonly> = process.env +): string | undefined { + const sessionId = readInjectedAgentSessionId(env) + if (!sessionId) { + return undefined + } + const ownHandle = env.ORCA_TERMINAL_HANDLE + return isStructuredWorkerHandle(ownHandle) + ? ownHandle + : `${ORCA_SESSION_ADDRESS_PREFIX}${sessionId}` +}