diff --git a/README.md b/README.md
index 5f02638d9c4..f0b3f1df6a3 100644
--- a/README.md
+++ b/README.md
@@ -194,7 +194,6 @@ Works with **any CLI agent** — if it runs in a terminal, it runs in Orca.
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/config/scripts/locale-translation-policy.mjs b/config/scripts/locale-translation-policy.mjs
index 75f50d15ff6..af88805b8c4 100644
--- a/config/scripts/locale-translation-policy.mjs
+++ b/config/scripts/locale-translation-policy.mjs
@@ -38,7 +38,6 @@ export const NEVER_TRANSLATE_VALUES = new Set([
'Claude Agent Teams',
'Cline',
'Codebuff',
- 'Freebuff',
'Codex',
'Command Code',
'Cursor',
diff --git a/config/scripts/mobile-web-app-session-terminal-closure.test.mjs b/config/scripts/mobile-web-app-session-terminal-closure.test.mjs
index 8fd2842687e..9c677111ba3 100644
--- a/config/scripts/mobile-web-app-session-terminal-closure.test.mjs
+++ b/config/scripts/mobile-web-app-session-terminal-closure.test.mjs
@@ -432,8 +432,21 @@ const MERMAID_PACKAGE = 'node_modules/mermaid/'
*
* modules 4216 -> 4218 (+2)
* local modules 1030 -> 1032 (+2)
+ *
+ * #22452 (`80f5aae0f9`) then added `src/shared/main-agent-status.ts` and
+ * `src/shared/agent-turn-outcome.ts`, which `agent-status-types.ts` on this route imports. Two local
+ * modules; the change was src/shared-only, so its own CI never ran this suite and main read two over.
+ *
+ * modules 4218 -> 4220 (+2)
+ * local modules 1032 -> 1034 (+2)
+ *
+ * Reverting #18790 then took `src/shared/agent-icons/freebuff.png` back out of
+ * `mobile-agent-icon-assets.ts`, undoing the one module #22119 pinned for it. Measured on the revert.
+ *
+ * modules 4220 -> 4219 (-1)
+ * local modules 1034 -> 1033 (-1)
*/
-const SESSION_ROUTE_MODULES = 4218
+const SESSION_ROUTE_MODULES = 4219
/** What the page enters this route through once the route is a switch with a `.web.tsx` sibling. */
const ROUTE_ENTRY = [
diff --git a/docs/readme/README.es.md b/docs/readme/README.es.md
index 27075756236..65fbd425a92 100644
--- a/docs/readme/README.es.md
+++ b/docs/readme/README.es.md
@@ -192,7 +192,6 @@ Funciona con **cualquier agente CLI** — si corre en una terminal, corre en Orc
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/readme/README.fr.md b/docs/readme/README.fr.md
index b9e91dc9cea..4d1e439d8b5 100644
--- a/docs/readme/README.fr.md
+++ b/docs/readme/README.fr.md
@@ -198,7 +198,6 @@ Fonctionne avec **n'importe quel agent CLI** — s'il tourne dans un terminal, i
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/readme/README.ja.md b/docs/readme/README.ja.md
index ab5ea12e5fb..324605b87fb 100644
--- a/docs/readme/README.ja.md
+++ b/docs/readme/README.ja.md
@@ -192,7 +192,6 @@ PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意の
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/readme/README.ko.md b/docs/readme/README.ko.md
index da25a1c948e..451f0d88399 100644
--- a/docs/readme/README.ko.md
+++ b/docs/readme/README.ko.md
@@ -194,7 +194,6 @@ diff의 어느 줄에든 코멘트를 남기고 에이전트에게 바로 보내
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/readme/README.pt.md b/docs/readme/README.pt.md
index 53524927393..9d0cb70a793 100644
--- a/docs/readme/README.pt.md
+++ b/docs/readme/README.pt.md
@@ -194,7 +194,6 @@ Funciona com **qualquer agente CLI** — se roda em um terminal, roda no Orca.
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/readme/README.zh-CN.md b/docs/readme/README.zh-CN.md
index 9cbced81b21..d1125d4410a 100644
--- a/docs/readme/README.zh-CN.md
+++ b/docs/readme/README.zh-CN.md
@@ -192,7 +192,6 @@ VS Code 的编辑器,处处自动保存 — 把文件或图片直接拖入智
Charm
Cline
Codebuff
-
Freebuff
Command Code
Continue
Droid
diff --git a/docs/site/content/docs/agents/supported.mdx b/docs/site/content/docs/agents/supported.mdx
index f5774c11f18..8f93ec6bb4e 100644
--- a/docs/site/content/docs/agents/supported.mdx
+++ b/docs/site/content/docs/agents/supported.mdx
@@ -46,7 +46,6 @@ To restore prompts for one agent only, edit that agent's default arguments or en
| Autohand | Auto-setup | [Autohand](https://github.com/autohandai/code-cli) |
| Cline | Auto-setup | [Cline](https://docs.cline.bot/cline-cli/overview) |
| Codebuff | Auto-setup | [Codebuff](https://www.codebuff.com/docs/help/quick-start) |
-| Freebuff | Auto-setup | [Freebuff](https://freebuff.com/cli) |
| Command Code | Auto-setup, status | [Command Code](https://commandcode.ai/docs/quickstart) |
| Muse | macOS/Linux; trusts the workspace at launch | [Meta](https://dev.meta.ai/docs/muse-code) |
| Continue | Auto-setup | [Continue](https://docs.continue.dev/guides/cli) |
diff --git a/mobile/src/components/mobile-agent-icon-assets.ts b/mobile/src/components/mobile-agent-icon-assets.ts
index ee07291b3b6..791364bf5e2 100644
--- a/mobile/src/components/mobile-agent-icon-assets.ts
+++ b/mobile/src/components/mobile-agent-icon-assets.ts
@@ -29,7 +29,6 @@ export const MOBILE_AGENT_ICON_ASSETS: Partial>
autohand: 'autohand.ai',
cline: 'cline.bot',
codebuff: 'codebuff.com',
- freebuff: 'freebuff.com',
'command-code': 'commandcode.ai',
continue: 'continue.dev',
cursor: 'cursor.com',
diff --git a/src/main/runtime/orca-runtime-get-orchestration-dispatch-authority.ts b/src/main/runtime/orca-runtime-get-orchestration-dispatch-authority.ts
index 5eb0bffb8e8..d61374c3466 100644
--- a/src/main/runtime/orca-runtime-get-orchestration-dispatch-authority.ts
+++ b/src/main/runtime/orca-runtime-get-orchestration-dispatch-authority.ts
@@ -22,7 +22,6 @@ import { getAppEnvironment } from '../../shared/app-environment'
import type { FleetAgentStatusEvidence } from '../../shared/orchestration-fleet-agent-status-evidence'
import { readOrchestrationFleetAgentStatusSnapshot } from './orchestration-fleet-agent-status-snapshot'
import { resolveStructuredWorkerAuthority } from './structured-worker-authority'
-import { matchesProcessIncarnation } from './orchestration/worker-terminal-process-liveness'
export class OrcaRuntimeWithGetOrchestrationDispatchAuthority extends OrcaRuntimeWithVerifyOrchestrationCompatibilityCaller {
/** Every pane key this PTY could be addressed by, including restored receipts. */
@@ -271,38 +270,4 @@ export class OrcaRuntimeWithGetOrchestrationDispatchAuthority extends OrcaRuntim
: undefined
})
}
- /**
- * Recover a live terminal handle for a worker whose durable handle stopped resolving
- * (renderer graph epoch bump / handle invalidation) while its PTY is still tracked. Fences on
- * the recorded process incarnation EXACTLY — never a bare ptyId, worktree, or pane — so a
- * reused ptyId belonging to a different process can never be closed, and fails closed on an
- * unknown host scope (this is also consumed by workerShow, which does no lease re-check).
- * Returns a freshly minted live handle, or null when no live PTY carries that exact incarnation.
- */
- resolveTerminalHandleByProcessIncarnation(
- processIncarnation: string,
- serializedHostScope: string | null
- ): string | null {
- if (!processIncarnation || !serializedHostScope) {
- return null
- }
- // Scan by the incarnation itself (startsWith + exact equality, mirroring
- // classifyWorkerTerminalProcessIncarnation) rather than splitting on a colon, so relay/SSH
- // ptyIds and colon-bearing incarnationIds still match. A pty with no incarnationId can never
- // match, so the legacy `${runtimeId}:${ptyId}:${ptyGeneration}` fence stays fail-closed.
- for (const [ptyId, pty] of this.ptysById) {
- if (!matchesProcessIncarnation(ptyId, pty.incarnationId, processIncarnation)) {
- continue
- }
- const hostScope = this.getOrchestrationCompatibilityHostScope(pty)
- if (!hostScope || JSON.stringify(hostScope) !== serializedHostScope) {
- // Keep scanning: a colon-ambiguous decoy pty in a different host scope that this
- // incarnation string happens to prefix-match must not suppress the genuine same-scope
- // pty later in ptysById. The scope check still fences the real match below.
- continue
- }
- return this.issuePtyHandle(pty)
- }
- return null
- }
}
diff --git a/src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts b/src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts
index 66b07f0b3d8..bac59e614b2 100644
--- a/src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts
+++ b/src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts
@@ -1,13 +1,11 @@
import { describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
-import { makePaneKey } from '../../shared/stable-pane-id'
const PTY_ID = 'ssh:target@@relay-pty'
const WORKTREE_ID = 'repo::/worktree'
const TAB_ID = 'tab-terminal'
const LEAF_ID = '11111111-1111-4111-8111-111111111111'
-/** A runtime whose pty controller captures every write for assertion. */
function makeRuntime(): { runtime: OrcaRuntimeService; writes: string[] } {
const writes: string[] = []
const runtime = new OrcaRuntimeService(null)
@@ -22,7 +20,6 @@ function makeRuntime(): { runtime: OrcaRuntimeService; writes: string[] } {
return { runtime, writes }
}
-/** Attach a window and publish a one-tab, one-leaf terminal graph. */
function syncGraph(runtime: OrcaRuntimeService): void {
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
@@ -47,7 +44,6 @@ function syncGraph(runtime: OrcaRuntimeService): void {
})
}
-/** (Re)register the fixture pty leaf under the given incarnation id. */
function register(runtime: OrcaRuntimeService, incarnationId: string): void {
runtime.registerPty(PTY_ID, WORKTREE_ID, 'target', {
tabId: TAB_ID,
@@ -70,8 +66,6 @@ describe('runtime terminal handle incarnation fencing', () => {
inspectProcess
})
expect(runtime.markRendererReloading(1)).not.toBeNull()
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
expect((runtime as unknown as { handles: Map }).handles.has(handle)).toBe(
false
)
@@ -95,8 +89,6 @@ describe('runtime terminal handle incarnation fencing', () => {
})
})
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
it('treats a null-to-known incarnation as the same un-fenced PTY', async () => {
const { runtime } = makeRuntime()
const handle = runtime.preAllocateHandleForPty(PTY_ID)
@@ -140,8 +132,6 @@ describe('runtime terminal handle incarnation fencing', () => {
// Rotate the record directly so reconcile is the only fence exercised.
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: test reaches the runtime's protected pty record map to bypass the registerPty fence.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
const internals = runtime as unknown as {
ptysById: Map
}
@@ -225,8 +215,6 @@ describe('runtime terminal handle incarnation fencing', () => {
runtime.registerPreAllocatedHandleForPty(PTY_ID, replacementHandle)
syncGraph(runtime)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
const internals = runtime as unknown as {
handles: Map
handleByLeafKey: Map
@@ -249,140 +237,3 @@ describe('runtime terminal handle incarnation fencing', () => {
})
})
})
-
-describe('resolveTerminalHandleByProcessIncarnation direct fencing', () => {
- const LOCAL_SCOPE = JSON.stringify({ kind: 'local', hostId: 'local' })
- // PTY_ID ('ssh:target@@relay-pty') derives connectionId 'target' via parseAppSshPtyId, so its
- // host scope is ssh:target rather than local.
- const SSH_TARGET_SCOPE = JSON.stringify({ kind: 'ssh', targetId: 'target' })
-
- /** Register a pty leaf directly, optionally under a connection and incarnation. */
- function seedPty(
- runtime: OrcaRuntimeService,
- ptyId: string,
- incarnationId: string | null,
- connectionId: string | null = null
- ): void {
- runtime.registerPty(ptyId, WORKTREE_ID, connectionId, {
- tabId: TAB_ID,
- leafId: LEAF_ID,
- ...(incarnationId ? { incarnationId } : {})
- })
- }
-
- /** Call the private resolveTerminalHandleByProcessIncarnation on the runtime. */
- function resolve(
- runtime: OrcaRuntimeService,
- processIncarnation: string,
- serializedHostScope: string | null
- ): string | null {
- return (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation(
- processIncarnation: string,
- serializedHostScope: string | null
- ): string | null
- }
- ).resolveTerminalHandleByProcessIncarnation(processIncarnation, serializedHostScope)
- )
- }
-
- it('mints a live handle for a pty whose exact incarnation and host scope match', () => {
- const { runtime } = makeRuntime()
- seedPty(runtime, PTY_ID, 'incarnation-1')
- const handle = resolve(runtime, `${PTY_ID}:incarnation-1`, SSH_TARGET_SCOPE)
- expect(handle).toMatch(/^term_/)
- // Re-minting the same live pty is idempotent.
- expect(resolve(runtime, `${PTY_ID}:incarnation-1`, SSH_TARGET_SCOPE)).toBe(handle)
- })
-
- it('returns null when the recorded incarnationId no longer matches the live pty', () => {
- const { runtime } = makeRuntime()
- seedPty(runtime, PTY_ID, 'incarnation-1')
- expect(resolve(runtime, `${PTY_ID}:incarnation-2`, LOCAL_SCOPE)).toBeNull()
- })
-
- it('returns null when the live pty has no incarnationId (legacy runtimeId:ptyId:gen never reaps)', () => {
- const { runtime } = makeRuntime()
- seedPty(runtime, PTY_ID, null)
- // A modern-shaped probe cannot match a pty that carries no incarnationId...
- expect(resolve(runtime, `${PTY_ID}:incarnation-1`, LOCAL_SCOPE)).toBeNull()
- // ...and the legacy `${runtimeId}:${ptyId}:${ptyGeneration}` shape stays fail-closed rather
- // than reap on a ptyGeneration guess.
- expect(resolve(runtime, `runtime_test:${PTY_ID}:0`, LOCAL_SCOPE)).toBeNull()
- })
-
- it('returns null when the host scope does not match', () => {
- const { runtime } = makeRuntime()
- seedPty(runtime, PTY_ID, 'incarnation-1')
- expect(
- resolve(runtime, `${PTY_ID}:incarnation-1`, JSON.stringify({ kind: 'ssh', targetId: 'nope' }))
- ).toBeNull()
- })
-
- it('returns null when no host scope is supplied (fails closed)', () => {
- const { runtime } = makeRuntime()
- seedPty(runtime, PTY_ID, 'incarnation-1')
- expect(resolve(runtime, `${PTY_ID}:incarnation-1`, null)).toBeNull()
- })
-
- it('resolves a colon-bearing SSH/relay incarnationId that lastIndexOf would mis-split', () => {
- const { runtime } = makeRuntime()
- // PTY_ID already carries ':' and '@@'; the incarnationId itself also carries colons.
- seedPty(runtime, PTY_ID, 'relay:conn-3:incarnation-9', 'target')
- const handle = resolve(
- runtime,
- `${PTY_ID}:relay:conn-3:incarnation-9`,
- JSON.stringify({ kind: 'ssh', targetId: 'target' })
- )
- expect(handle).toMatch(/^term_/)
- })
-
- it('resolves a Windows repo::C:\\path@@1 ptyId', () => {
- const { runtime } = makeRuntime()
- const windowsPtyId = 'repo::C:\\path@@1'
- seedPty(runtime, windowsPtyId, 'incarnation-win')
- const handle = resolve(runtime, `${windowsPtyId}:incarnation-win`, LOCAL_SCOPE)
- expect(handle).toMatch(/^term_/)
- })
-
- it('keeps scanning past a colon-ambiguous decoy in another host scope to the genuine match', () => {
- const { runtime } = makeRuntime()
- // One process-incarnation string, two colon-ambiguous pty ids that both satisfy the exact
- // matcher: 'relay' + 'conn:incarnation-1' AND 'relay:conn' + 'incarnation-1' both stringify to
- // 'relay:conn:incarnation-1'. The decoy is registered FIRST and lives in a different host scope
- // (ssh:decoy); the genuine pty is local and registered later.
- const processIncarnation = 'relay:conn:incarnation-1'
- const DECOY_TAB_ID = 'tab-decoy'
- const DECOY_LEAF_ID = '22222222-2222-4222-8222-222222222222'
- const GENUINE_TAB_ID = 'tab-genuine'
- const GENUINE_LEAF_ID = '33333333-3333-4333-8333-333333333333'
- runtime.registerPty('relay', WORKTREE_ID, 'decoy', {
- tabId: DECOY_TAB_ID,
- leafId: DECOY_LEAF_ID,
- incarnationId: 'conn:incarnation-1'
- })
- runtime.registerPty('relay:conn', WORKTREE_ID, null, {
- tabId: GENUINE_TAB_ID,
- leafId: GENUINE_LEAF_ID,
- incarnationId: 'incarnation-1'
- })
-
- // Before the fix, the earlier decoy's host-scope mismatch returned null and suppressed the
- // genuine same-scope pty entirely. `continue` lets the scan reach it.
- const handle = resolve(runtime, processIncarnation, LOCAL_SCOPE)
- expect(handle).toMatch(/^term_/)
- // getTerminalProcessIncarnation cannot separate the two (both stringify to
- // 'relay:conn:incarnation-1'), so pin the remint to the GENUINE pane. The paneKey is the only
- // terminal-specific observable that distinguishes the genuine leaf from the same-incarnation
- // decoy; this fails if the resolver ever mints the decoy's handle instead of the genuine pty's.
- const genuinePaneKey = makePaneKey(GENUINE_TAB_ID, GENUINE_LEAF_ID)
- const decoyPaneKey = makePaneKey(DECOY_TAB_ID, DECOY_LEAF_ID)
- expect(runtime.getTerminalPaneKey(handle!)).toBe(genuinePaneKey)
- expect(runtime.getTerminalPaneKey(handle!)).not.toBe(decoyPaneKey)
- })
-})
diff --git a/src/main/runtime/orchestration/worker-terminal-process-liveness.test.ts b/src/main/runtime/orchestration/worker-terminal-process-liveness.test.ts
deleted file mode 100644
index e2bc2cc7d63..00000000000
--- a/src/main/runtime/orchestration/worker-terminal-process-liveness.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { matchesProcessIncarnation } from './worker-terminal-process-liveness'
-
-describe('matchesProcessIncarnation', () => {
- it.each([
- {
- name: 'an exact ptyId:incarnation match',
- ptyId: 'pty-1',
- incarnationId: 'incarnation-1',
- processIncarnation: 'pty-1:incarnation-1',
- expected: true
- },
- {
- name: 'a Windows repo::C:\\path@@1 ptyId whose incarnation matches',
- ptyId: 'repo::C:\\path@@1',
- incarnationId: 'incarnation-win',
- processIncarnation: 'repo::C:\\path@@1:incarnation-win',
- expected: true
- },
- {
- name: 'a colon-bearing relay incarnationId that a lastIndexOf split would mangle',
- ptyId: 'relay-pty',
- incarnationId: 'relay:conn-3:incarnation-9',
- processIncarnation: 'relay-pty:relay:conn-3:incarnation-9',
- expected: true
- },
- {
- name: 'a whitespace-dirty incarnationId (lost contact is never a match)',
- ptyId: 'pty-1',
- incarnationId: ' incarnation-1',
- processIncarnation: 'pty-1: incarnation-1',
- expected: false
- },
- {
- name: 'an empty-string incarnationId',
- ptyId: 'pty-1',
- incarnationId: '',
- processIncarnation: 'pty-1:',
- expected: false
- },
- {
- name: 'an absent (null) incarnationId',
- ptyId: 'pty-1',
- incarnationId: null,
- processIncarnation: 'pty-1:incarnation-1',
- expected: false
- },
- {
- name: 'an absent (undefined) incarnationId',
- ptyId: 'pty-1',
- incarnationId: undefined,
- processIncarnation: 'pty-1:incarnation-1',
- expected: false
- },
- {
- name: 'a prefix-decoy ptyId (@@1) against a longer live pty (@@10)',
- ptyId: 'repo::C:\\path@@1',
- incarnationId: 'inc-1',
- processIncarnation: 'repo::C:\\path@@10:inc-1',
- expected: false
- }
- ])('returns $expected for $name', ({ ptyId, incarnationId, processIncarnation, expected }) => {
- expect(matchesProcessIncarnation(ptyId, incarnationId, processIncarnation)).toBe(expected)
- })
-
- it('rejects a partial prefix that is not colon-delimited', () => {
- // 'pty-10' is not the pty 'pty-1'; the `${ptyId}:` fence keeps them distinct.
- expect(matchesProcessIncarnation('pty-1', 'inc', 'pty-10:inc')).toBe(false)
- })
-})
diff --git a/src/main/runtime/orchestration/worker-terminal-process-liveness.ts b/src/main/runtime/orchestration/worker-terminal-process-liveness.ts
index 4e652d6286a..72c5ce07666 100644
--- a/src/main/runtime/orchestration/worker-terminal-process-liveness.ts
+++ b/src/main/runtime/orchestration/worker-terminal-process-liveness.ts
@@ -5,24 +5,6 @@ import type { PtyProcessInfo } from '../../providers/pty-process-info'
export type { WorkerTerminalHostScope } from '../../../shared/worker-terminal-host-scope'
export { parseWorkerTerminalHostScope } from '../../../shared/worker-terminal-host-scope'
-/**
- * Does `processIncarnation` name exactly this pty's live incarnation? Requires exact
- * `${ptyId}:${incarnationId}` equality, so it is immune to colons on either side (relay/SSH
- * ptyIds, colon-bearing relay incarnationIds). A pty with no (or a whitespace-dirty)
- * incarnationId can never match — the exact-incarnation fence stays intact.
- */
-export function matchesProcessIncarnation(
- ptyId: string,
- incarnationId: string | null | undefined,
- processIncarnation: string
-): boolean {
- if (!incarnationId || incarnationId !== incarnationId.trim()) {
- return false
- }
- return `${ptyId}:${incarnationId}` === processIncarnation
-}
-
-/** Classify a recorded incarnation against live sessions: live on exact match, unverifiable when a candidate pty has a dirty or absent incarnationId (lost contact is never a death certificate), else exited. */
export function classifyWorkerTerminalProcessIncarnation(
processIncarnation: string,
sessions: readonly PtyProcessInfo[]
@@ -31,9 +13,13 @@ export function classifyWorkerTerminalProcessIncarnation(
processIncarnation.startsWith(`${session.id}:`)
)
if (
- possibleMatches.some((session) =>
- matchesProcessIncarnation(session.id, session.incarnationId, processIncarnation)
- )
+ possibleMatches.some((session) => {
+ const incarnationId = session.incarnationId
+ if (!incarnationId || incarnationId !== incarnationId.trim()) {
+ return false
+ }
+ return `${session.id}:${incarnationId}` === processIncarnation
+ })
) {
return 'live'
}
diff --git a/src/main/runtime/rpc/methods/orchestration-worker-release-incarnation-fallback.test.ts b/src/main/runtime/rpc/methods/orchestration-worker-release-incarnation-fallback.test.ts
deleted file mode 100644
index a9e9ceb165c..00000000000
--- a/src/main/runtime/rpc/methods/orchestration-worker-release-incarnation-fallback.test.ts
+++ /dev/null
@@ -1,622 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest'
-import { ORCHESTRATION_METHODS } from './orchestration'
-import { eraseRpcMethods, type RpcContext } from '../core'
-import { OrchestrationDb } from '../../orchestration/db'
-import { OrcaRuntimeService } from '../../orca-runtime'
-import { completeWorkerTerminalRelease } from './orchestration/worker/worker-release-completion'
-
-describe('orchestration worker release incarnation fallback', () => {
- let db: OrchestrationDb
- let dbOpen = false
- let runtime: OrcaRuntimeService
- let ctx: RpcContext
- let activeRunId: string
- let inspectProcessLiveness: ReturnType
-
- const coordinatorPaneKey = 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
- const workerPaneKey = 'tab_worker:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
-
- /** Fresh in-memory db and a fully-stubbed runtime for one worker-release scenario. */
- function setup(): void {
- db = new OrchestrationDb(':memory:')
- dbOpen = true
- runtime = new OrcaRuntimeService()
- runtime.setOrchestrationDb(db)
- inspectProcessLiveness = vi.fn().mockResolvedValue('live')
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- inspectTerminalProcessIncarnationLiveness: typeof inspectProcessLiveness
- }
- ).inspectTerminalProcessIncarnationLiveness = inspectProcessLiveness
- vi.spyOn(runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
- handle === 'term_coord'
- ? coordinatorPaneKey
- : handle === 'term_worker' || handle === 'term_reminted'
- ? workerPaneKey
- : null
- )
- vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
- handle === 'term_worker' || handle === 'term_reminted' ? 'runtime_test:term_worker:1' : null
- )
- vi.spyOn(runtime, 'getOrchestrationDispatchAuthority').mockImplementation((handle) =>
- handle === 'term_worker' || handle === 'term_reminted'
- ? // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- ({
- terminalHandle: handle,
- paneKey: workerPaneKey,
- processIncarnation: 'runtime_test:term_worker:1',
- hostScope: { kind: 'local', hostId: 'local' }
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- } as never)
- : null
- )
- vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
- vi.spyOn(runtime, 'showTerminal').mockImplementation(
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- async (handle) => ({ handle, worktreeId: 'repo::worktree', status: 'running' }) as never
- )
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- vi.spyOn(runtime, 'showManagedTerminalWorkspace').mockResolvedValue({
- id: 'repo::worktree'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- } as never)
- vi.spyOn(runtime, 'createTerminal').mockResolvedValue({
- handle: 'term_worker',
- worktreeId: 'repo::worktree',
- title: 'worker'
- })
- vi.spyOn(runtime, 'waitForTerminal').mockResolvedValue({
- handle: 'term_worker',
- condition: 'tui-idle',
- satisfied: true,
- status: 'running',
- exitCode: null
- })
- vi.spyOn(runtime, 'getTerminalOrchestrationCliCommand').mockReturnValue('orca')
- vi.spyOn(runtime, 'sendTerminalAgentPrompt').mockResolvedValue({
- handle: 'term_worker',
- accepted: true,
- bytesWritten: 1
- })
- vi.spyOn(runtime, 'isTerminalRunningAgent').mockResolvedValue(true)
- vi.spyOn(runtime, 'getExactWorkerProviderSession').mockReturnValue(null)
- vi.spyOn(runtime, 'readTerminal').mockResolvedValue({
- handle: 'term_worker',
- status: 'running',
- tail: ['worker output line 1', 'worker output line 2'],
- truncated: false,
- nextCursor: '2'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- })
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- vi.spyOn(runtime, 'closeTerminal').mockResolvedValue({
- handle: 'term_worker',
- tabId: 'tab-worker',
- ptyKilled: true
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- } as never)
- vi.spyOn(runtime, 'notifyMessageArrived').mockImplementation(() => {})
- activeRunId = db.createRun({
- objective: 'Release test Run',
- coordinatorHandle: 'term_coord',
- coordinatorPaneKey
- }).id
- ctx = { runtime }
- }
-
- afterEach(() => {
- if (dbOpen) {
- dbOpen = false
- db.close()
- }
- vi.restoreAllMocks()
- })
-
- /** Look up a registered orchestration RPC method by name. */
- function findMethod(name: string) {
- const method = eraseRpcMethods(ORCHESTRATION_METHODS).find((m) => m.name === name)
- if (!method) {
- throw new Error(`Method not found: ${name}`)
- }
- return method
- }
-
- /** Parse a method's params and invoke its handler against the shared ctx. */
- async function call(name: string, params: Record) {
- const method = findMethod(name)
- const parsed = method.params ? method.params.parse(params) : undefined
- return method.handler(parsed, ctx)
- }
-
- /** Start a ready worker on a fresh task off the coordinator terminal. */
- async function startWorker(options: { terminal?: string } = {}): Promise<{
- taskId: string
- dispatchId: string
- }> {
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const task = db.createTask({ spec: 'release fixture task', runId: activeRunId })
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const result = (await call('orchestration.workerStart', {
- task: task.id,
- from: 'term_coord',
- ...(options.terminal ? { terminal: options.terminal } : { agent: 'codex' })
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- })) as { dispatchId: string; state: string }
- expect(result.state).toBe('ready')
- return { taskId: task.id, dispatchId: result.dispatchId }
- }
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- /** Record the worker's report as settled — the precondition for release. */
- function settle(taskId: string, dispatchId: string, outcome: 'succeeded' | 'failed'): void {
- const settlement = db.settleWorkerReport({
- taskId,
- dispatchId,
- outcome,
- result: `worker ${outcome}`
- })
- expect(settlement.action).toBe('settled')
- }
-
- /** Start a worker and settle its report, the state a release acts on. */
- async function startSettledWorker(
- outcome: 'succeeded' | 'failed' = 'succeeded',
- options: { terminal?: string } = {}
- ): Promise<{ taskId: string; dispatchId: string }> {
- const worker = await startWorker(options)
- settle(worker.taskId, worker.dispatchId, outcome)
- return worker
- }
-
- it('closes a live worker terminal whose durable handle no longer resolves but whose process incarnation still matches', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- // The durable handle stops resolving (renderer graph epoch bump / handle invalidation)...
- vi.mocked(runtime.showTerminal).mockImplementation(async (handle) =>
- handle === 'term_worker'
- ? Promise.reject(new Error('terminal_handle_stale'))
- : // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ({ handle, worktreeId: 'repo::worktree', status: 'running' } as never)
- )
- // ...but the recorded process incarnation still names a live PTY, re-minted to a fresh handle.
- const resolveByIncarnation = vi.fn().mockReturnValue('term_reminted')
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- expect(receipt).toMatchObject({ state: 'released', processAction: 'closed_agent_terminal' })
- expect(resolveByIncarnation).toHaveBeenCalledWith(
- 'runtime_test:term_worker:1',
- JSON.stringify({ kind: 'local', hostId: 'local' })
- )
- // The close targeted exactly the re-minted live handle for that PTY, never the stale one.
- expect(runtime.closeTerminal).toHaveBeenCalledTimes(1)
- expect(runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'released',
- release_state: 'released'
- })
- })
-
- it('stays release_unknown and closes nothing when the recorded incarnation no longer matches a live pty', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- // A reused ptyId now belongs to a different process: the incarnation mismatch refuses a close.
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- }
-
- expect(receipt.state).toBe('release_unknown')
- expect(resolveByIncarnation).toHaveBeenCalledWith(
- 'runtime_test:term_worker:1',
- JSON.stringify({ kind: 'local', hostId: 'local' })
- )
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).toBe('unknown')
- })
-
- it('does not plain-settle an exited missing worker when the archive was never committed', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- inspectProcessLiveness.mockResolvedValue('exited')
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- expect(receipt).toMatchObject({ state: 'release_unknown', processAction: 'none' })
- expect(inspectProcessLiveness).toHaveBeenCalled()
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'owned',
- release_state: 'unknown'
- })
- })
-
- it('settles released without a close when exited missing and an archive is already committed', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- const requested = db.requestWorkerTerminalRelease(dispatchId)
- if (requested.disposition !== 'requested') {
- throw new Error(`expected requested, got ${requested.disposition}`)
- }
- db.commitWorkerTerminalArchiveForRelease({
- dispatchId,
- resourceId: requested.resource.id,
- kind: 'terminal_tail',
- content: JSON.stringify({ lines: [] }),
- archiveSource: 'terminal',
- archiveStatus: 'empty'
- })
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- inspectProcessLiveness.mockResolvedValue('exited')
-
- const receipt = await completeWorkerTerminalRelease({
- runtime,
- db,
- dispatchId,
- resource: db.getWorkerTerminalResource(requested.resource.id)!,
- mode: 'interactive'
- })
-
- expect(receipt).toMatchObject({ state: 'released', processAction: 'none' })
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'released',
- release_state: 'released'
- })
- })
-
- it('reaches settleDead before the lease check when a gone worker is exited with no live authority', async () => {
- // Without a committed archive settleDead retains; lease must not run first and force
- // retained/identity_unproven. Disposition is release_unknown (interactive) after the retain.
- setup()
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- vi.mocked(runtime.getOrchestrationDispatchAuthority).mockReturnValue(null)
- inspectProcessLiveness.mockResolvedValue('exited')
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- expect(receipt).toMatchObject({ state: 'release_unknown', processAction: 'none' })
- expect(receipt.state).not.toBe('retained')
- expect(inspectProcessLiveness).toHaveBeenCalled()
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- })
-
- it('concedes release_unknown before the lease check when a gone worker has no live authority and liveness is unproven', async () => {
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- setup()
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- vi.mocked(runtime.getOrchestrationDispatchAuthority).mockReturnValue(null)
- // Liveness is unresolvable/not-exited: the process may have been re-homed, so concede rather
- // than retain or guess at a live process.
- inspectProcessLiveness.mockResolvedValue('live')
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- }
-
- expect(receipt.state).toBe('release_unknown')
- expect(receipt.state).not.toBe('retained')
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).toBe('unknown')
- })
-
- it('workerStop closes a live worker via the reminted handle when the durable handle is stale', async () => {
- setup()
- const { dispatchId } = await startWorker()
- // The durable handle stops resolving, but the recorded incarnation still names a live PTY.
- vi.mocked(runtime.showTerminal).mockImplementation(async (handle) =>
- handle === 'term_worker'
- ? Promise.reject(new Error('terminal_handle_stale'))
- : // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ({ handle, worktreeId: 'repo::worktree', status: 'running' } as never)
- )
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const resolveByIncarnation = vi.fn().mockReturnValue('term_reminted')
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerStop', { dispatch: dispatchId })) as {
- processAction: string
- }
-
- expect(receipt.processAction).toBe('closed_agent_terminal')
- // The kill targeted exactly the reminted live handle, never the stale durable one — closing
- // the stale handle would throw terminal_handle_stale and leak the PTY.
- expect(runtime.closeTerminal).toHaveBeenCalledTimes(1)
- expect(runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
- })
-
- it('workerRead reads a live worker via the reminted handle when the durable handle is stale', async () => {
- setup()
- const { dispatchId } = await startWorker()
- vi.mocked(runtime.showTerminal).mockImplementation(async (handle) =>
- handle === 'term_worker'
- ? Promise.reject(new Error('terminal_handle_stale'))
- : // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ({ handle, worktreeId: 'repo::worktree', status: 'running' } as never)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- )
- const resolveByIncarnation = vi.fn().mockReturnValue('term_reminted')
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const output = (await call('orchestration.workerRead', { dispatch: dispatchId })) as {
- terminal?: { tail: string[] }
- }
-
- // Both the exact-session probe and the terminal read addressed the reminted handle.
- expect(runtime.getExactWorkerProviderSession).toHaveBeenCalledWith(
- 'term_reminted',
- expect.any(Number)
- )
- expect(runtime.readTerminal).toHaveBeenCalledWith('term_reminted', expect.anything())
- expect(output.terminal?.tail).toEqual(['worker output line 1', 'worker output line 2'])
- })
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- it('recovery-mode: defers when exited missing has no archive rather than plain-settling', async () => {
- // Proof of death still runs settleDead first; when it retains (no archive), recovery must
- // stay release_pending — not plain-settle, not unknown.
- setup()
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- inspectProcessLiveness.mockResolvedValue('exited')
- const requested = db.requestWorkerTerminalRelease(dispatchId)
- if (requested.disposition !== 'requested') {
- throw new Error(`expected a requested release, got ${requested.disposition}`)
- }
-
- const receipt = await completeWorkerTerminalRelease({
- runtime,
- db,
- dispatchId,
- resource: requested.resource,
- mode: 'recovery'
- })
-
- expect(receipt).toMatchObject({ state: 'release_pending', processAction: 'none' })
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'owned',
- release_state: 'requested'
- })
- })
-
- it('recovery-mode: settles released before the defer when exited missing already has an archive', async () => {
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- setup()
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const { dispatchId } = await startSettledWorker()
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- inspectProcessLiveness.mockResolvedValue('exited')
- const requested = db.requestWorkerTerminalRelease(dispatchId)
- if (requested.disposition !== 'requested') {
- throw new Error(`expected a requested release, got ${requested.disposition}`)
- }
- db.commitWorkerTerminalArchiveForRelease({
- dispatchId,
- resourceId: requested.resource.id,
- kind: 'terminal_tail',
- content: JSON.stringify({ lines: [] }),
- archiveSource: 'terminal',
- archiveStatus: 'empty'
- })
-
- const receipt = await completeWorkerTerminalRelease({
- runtime,
- db,
- dispatchId,
- resource: db.getWorkerTerminalResource(requested.resource.id)!,
- mode: 'recovery'
- })
-
- expect(receipt).toMatchObject({ state: 'released', processAction: 'none' })
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'released',
- release_state: 'released'
- })
- })
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- it('recovery-mode: defers release_pending when liveness is unverifiable rather than provably exited', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- vi.mocked(runtime.showTerminal).mockRejectedValue(new Error('terminal_handle_stale'))
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const resolveByIncarnation = vi.fn().mockReturnValue(null)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: typeof resolveByIncarnation
- }
- ).resolveTerminalHandleByProcessIncarnation = resolveByIncarnation
- // Not a death certificate: inventory may still be incomplete, so recovery must defer.
- inspectProcessLiveness.mockResolvedValue('unverifiable')
- const requested = db.requestWorkerTerminalRelease(dispatchId)
- if (requested.disposition !== 'requested') {
- throw new Error(`expected a requested release, got ${requested.disposition}`)
- }
-
- const receipt = await completeWorkerTerminalRelease({
- runtime,
- db,
- dispatchId,
- resource: requested.resource,
- mode: 'recovery'
- })
-
- expect(receipt.state).toBe('release_pending')
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- expect(db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).not.toBe('released')
- })
-})
diff --git a/src/main/runtime/rpc/methods/orchestration-worker-release-reap-fixed.func.test.ts b/src/main/runtime/rpc/methods/orchestration-worker-release-reap-fixed.func.test.ts
deleted file mode 100644
index d4c7884b1a4..00000000000
--- a/src/main/runtime/rpc/methods/orchestration-worker-release-reap-fixed.func.test.ts
+++ /dev/null
@@ -1,381 +0,0 @@
-// PRB-0219 / upstream #18737 — FUNCTIONAL (integration) reproduction of the steady-state
-// worker-release reap LEAK.
-//
-// This is the "functional/integration" tier of the PRB-0219 test strategy. It wires the REAL
-// orchestration RPC surface (orchestration.workerStart / workerRelease / workerList), the REAL
-// OrchestrationDb, and the REAL release completion + observation modules against a fake runtime
-// that faithfully models the two identity planes involved in the bug:
-//
-// * VOLATILE, epoch-fenced handle table — `handleTable` keyed by terminal handle, each entry
-// stamped with the `rendererGraphEpoch` at which it was issued. `showTerminal` resolves a
-// handle ONLY while its stamped epoch matches the current epoch (mirrors getLiveLeafForHandle
-// throwing 'terminal_handle_stale' on a rendererGraphEpoch bump). A relay reconnect / renderer
-// remount on headless serve bumps the epoch.
-// * DURABLE, incarnation-addressed process table — `ptysById` keyed by the ptyId embedded in the
-// worker's persisted process_incarnation (`${ptyId}:${incarnationId}`). The pty stays LIVE
-// across an epoch bump; nothing about the graph epoch kills the process.
-//
-// The bug (mechanism): when the epoch bumps, the durable db handle stops resolving through
-// showTerminal WHILE THE PTY IS STILL ALIVE. inspectWorkerTerminal swallows the throw and reports
-// `missing`; completeWorkerTerminalRelease then commits `release_unknown` and returns WITHOUT ever
-// calling runtime.closeTerminal — so the process/PTY leaks. On mtl-02 those orphans accumulate in
-// the orca-serve@factory cgroup until TasksMax=4096 is hit and Bun/omp abort() on EAGAIN.
-//
-// The observable leak signal asserted here: state 'release_unknown' + processAction 'none' +
-// closeTerminal NEVER called + the pty STILL alive in ptysById + worker-list terminalState stuck
-// on 'release_unknown' (never 'released').
-
-import { afterEach, describe, expect, it, vi } from 'vitest'
-import { ORCHESTRATION_METHODS } from './orchestration'
-import { eraseRpcMethods, type RpcContext } from '../core'
-import { OrchestrationDb } from '../../orchestration/db'
-import { OrcaRuntimeService } from '../../orca-runtime'
-
-describe('PRB-0219 worker-release reap FIX (functional verification)', () => {
- let db: OrchestrationDb
- let dbOpen = false
- let runtime: OrcaRuntimeService
- let ctx: RpcContext
- let activeRunId: string
-
- const coordinatorPaneKey = 'tab_coord:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
- const workerPaneKey = 'tab_worker:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
-
- // Dispatch/automation ptyId shape: `${repoId}::${worktreePath}@@${suffix}` (executionHostId:null).
- const PTY_ID = 'repo-7f3a::/data/wt/factory-task-1@@a1b2c3d4'
- const INCARNATION_ID = 1
- const PROCESS_INCARNATION = `${PTY_ID}:${INCARNATION_ID}`
-
- // ---- fake runtime process/handle planes (module-level so spies can mutate them) ----
- let ptysById: Map
- let handleTable: Map
- let rendererGraphEpoch: number
- let closedPtyIds: string[]
-
- /** Resolve a handle to its live pty only while its stamped graph epoch is current. */
- function resolveHandleToLivePty(
- handle: string
- ): { ptyId: string; pty: { incarnationId: number; alive: boolean } } | null {
- const entry = handleTable.get(handle)
- if (!entry) {
- return null
- }
- if (entry.epoch !== rendererGraphEpoch) {
- // rendererGraphEpoch fence: the durable handle no longer resolves to a live leaf.
- return null
- }
- const pty = ptysById.get(entry.ptyId)
- if (!pty || !pty.alive) {
- return null
- }
- return { ptyId: entry.ptyId, pty }
- }
-
- /** Wire the real RPC surface and db against the two-plane fake runtime. */
- function setup(): void {
- ptysById = new Map([
- ['coord-pty', { incarnationId: 1, alive: true }],
- [PTY_ID, { incarnationId: INCARNATION_ID, alive: true }]
- ])
- handleTable = new Map([
- ['term_coord', { ptyId: 'coord-pty', epoch: 0 }],
- ['term_worker', { ptyId: PTY_ID, epoch: 0 }]
- ])
- rendererGraphEpoch = 0
- closedPtyIds = []
-
- db = new OrchestrationDb(':memory:')
- dbOpen = true
- runtime = new OrcaRuntimeService()
- runtime.setOrchestrationDb(db)
-
- // Incarnation-addressed liveness probe: reads the DURABLE plane, so it stays 'live' across the
- // epoch bump (the process really is still running). Matches the real asymmetry.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- inspectTerminalProcessIncarnationLiveness: (
- incarnation: string
- ) => Promise<'live' | 'exited'>
- }
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ).inspectTerminalProcessIncarnationLiveness = vi.fn(async (incarnation: string) => {
- const idx = incarnation.lastIndexOf(':')
- const ptyId = incarnation.slice(0, idx)
- const pty = ptysById.get(ptyId)
- return pty?.alive ? 'live' : 'exited'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- }) as never
-
- // The incarnation-addressed re-resolution primitive the fix adds. Present here as a spy so the
- // same harness proves BOTH tiers: pre-fix completion never calls it (leak); post-fix completion
- // calls it to remint a live handle (reap). Fence: EXACT incarnationId match only.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ;// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- (
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- runtime as unknown as {
- resolveTerminalHandleByProcessIncarnation: (
- incarnation: string,
- hostScope: string | null
- ) => string | null
- }
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- ).resolveTerminalHandleByProcessIncarnation = vi.fn((incarnation: string): string | null => {
- const idx = incarnation.lastIndexOf(':')
- const ptyId = incarnation.slice(0, idx)
- const inc = Number(incarnation.slice(idx + 1))
- const pty = ptysById.get(ptyId)
- if (!pty || !pty.alive) {
- return null
- }
- if (pty.incarnationId !== inc) {
- // Fence: a reused ptyId with a different incarnation must NOT resolve.
- return null
- }
- // Remint a live handle at the current graph epoch.
- handleTable.set('term_reminted', { ptyId, epoch: rendererGraphEpoch })
- return 'term_reminted'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- }) as never
-
- // Identity plane (durable) — answers for the original AND any reminted handle. Independent of
- // the graph epoch, exactly like the real getTerminal* accessors that read dispatch authority.
- const knownWorkerHandle = (handle: string): boolean =>
- handle === 'term_worker' || handle === 'term_reminted'
- vi.spyOn(runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
- handle === 'term_coord'
- ? coordinatorPaneKey
- : knownWorkerHandle(handle)
- ? workerPaneKey
- : null
- )
- vi.spyOn(runtime, 'getTerminalProcessIncarnation').mockImplementation((handle) =>
- knownWorkerHandle(handle) ? PROCESS_INCARNATION : null
- )
- vi.spyOn(runtime, 'getOrchestrationDispatchAuthority').mockImplementation((handle) =>
- knownWorkerHandle(handle)
- ? // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: This test fixture is deliberately shaped to exercise the private/runtime boundary.
- ({
- terminalHandle: handle,
- paneKey: workerPaneKey,
- processIncarnation: PROCESS_INCARNATION,
- hostScope: { kind: 'local', hostId: 'local' }
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- } as never)
- : null
- )
- vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {})
-
- // Volatile handle resolution — epoch-fenced. Throws 'terminal_handle_stale' once the epoch
- // moves past the epoch at which the handle was issued.
- vi.spyOn(runtime, 'showTerminal').mockImplementation(async (handle) => {
- if (!resolveHandleToLivePty(handle)) {
- throw new Error('terminal_handle_stale')
- }
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- return { handle, worktreeId: 'repo::worktree', status: 'running' } as never
- })
-
- // The reap: closing a handle kills exactly the pty it resolves to.
- vi.spyOn(runtime, 'closeTerminal').mockImplementation(async (handle) => {
- const live = resolveHandleToLivePty(handle)
- if (!live) {
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- return { handle, tabId: null, ptyKilled: false } as never
- }
- live.pty.alive = false
- ptysById.delete(live.ptyId)
- closedPtyIds.push(live.ptyId)
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- return { handle, tabId: `tab:${live.ptyId}`, ptyKilled: true } as never
- })
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // Remaining runtime surface required to start + settle a worker (mirrors the unit harness).
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- vi.spyOn(runtime, 'showManagedTerminalWorkspace').mockResolvedValue({
- id: 'repo::worktree'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- } as never)
- vi.spyOn(runtime, 'createTerminal').mockResolvedValue({
- handle: 'term_worker',
- worktreeId: 'repo::worktree',
- title: 'worker'
- })
- vi.spyOn(runtime, 'waitForTerminal').mockResolvedValue({
- handle: 'term_worker',
- condition: 'tui-idle',
- satisfied: true,
- status: 'running',
- exitCode: null
- })
- vi.spyOn(runtime, 'getTerminalOrchestrationCliCommand').mockReturnValue('orca')
- vi.spyOn(runtime, 'sendTerminalAgentPrompt').mockResolvedValue({
- handle: 'term_worker',
- accepted: true,
- bytesWritten: 1
- })
- vi.spyOn(runtime, 'isTerminalRunningAgent').mockResolvedValue(true)
- vi.spyOn(runtime, 'getExactWorkerProviderSession').mockReturnValue(null)
- vi.spyOn(runtime, 'readTerminal').mockResolvedValue({
- handle: 'term_worker',
- status: 'running',
- tail: ['worker output line 1', 'worker output line 2'],
- truncated: false,
- nextCursor: '2'
- })
- vi.spyOn(runtime, 'notifyMessageArrived').mockImplementation(() => {})
-
- activeRunId = db.createRun({
- objective: 'PRB-0219 reap leak fixture',
- coordinatorHandle: 'term_coord',
- coordinatorPaneKey
- }).id
- ctx = { runtime }
- }
-
- afterEach(() => {
- if (dbOpen) {
- dbOpen = false
- db.close()
- }
- vi.restoreAllMocks()
- })
-
- /** Look up a registered orchestration RPC method by name. */
- function findMethod(name: string) {
- const method = eraseRpcMethods(ORCHESTRATION_METHODS).find((m) => m.name === name)
- if (!method) {
- throw new Error(`Method not found: ${name}`)
- }
- return method
- }
-
- /** Parse a method's params and invoke its handler against the shared ctx. */
- async function call(name: string, params: Record) {
- const method = findMethod(name)
- const parsed = method.params ? method.params.parse(params) : undefined
- return method.handler(parsed, ctx)
- }
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- /** Start a worker and settle its report, the state a release acts on. */
- async function startSettledWorker(): Promise<{ taskId: string; dispatchId: string }> {
- const task = db.createTask({ spec: 'reap-leak fixture task', runId: activeRunId })
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const result = (await call('orchestration.workerStart', {
- task: task.id,
- from: 'term_coord',
- agent: 'codex'
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- })) as { dispatchId: string; state: string }
- expect(result.state).toBe('ready')
- const settlement = db.settleWorkerReport({
- taskId: task.id,
- dispatchId: result.dispatchId,
- outcome: 'succeeded',
- result: 'worker succeeded'
- })
- expect(settlement.action).toBe('settled')
- return { taskId: task.id, dispatchId: result.dispatchId }
- }
-
- /** The terminalState workerList projects for a dispatch, or null. */
- async function workerTerminalState(dispatchId: string): Promise {
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const listed = (await call('orchestration.workerList', { run: activeRunId })) as {
- workers: { dispatchId: string; terminalState: string | null }[]
- }
- return listed.workers.find((w) => w.dispatchId === dispatchId)?.terminalState ?? null
- }
-
- it('REAP (fixed): a rendererGraphEpoch bump strands the durable handle, but the incarnation fallback remints a live handle and reaps the process', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
- const resource = db.getWorkerTerminalResourceByOwner(dispatchId)
- expect(resource?.process_incarnation).toBe(PROCESS_INCARNATION)
-
- // Relay reconnect / renderer remount bumps the graph epoch: the durable handle goes stale
- // while the PTY stays alive.
- rendererGraphEpoch = 1
- await expect(runtime.showTerminal('term_worker')).rejects.toThrow('terminal_handle_stale')
- expect(ptysById.get(PTY_ID)?.alive).toBe(true)
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- // The fix: inspectWorkerTerminal re-resolved the live PTY by process incarnation, reminted a
- // handle, and completion closed THAT handle — the process is actually reaped.
- expect(receipt.state).toBe('released')
- expect(receipt.processAction).toBe('closed_agent_terminal')
- expect(runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
- expect(closedPtyIds).toEqual([PTY_ID])
- expect(ptysById.has(PTY_ID)).toBe(false)
- expect(await workerTerminalState(dispatchId)).toBe('released')
- })
-
- it('CONTROL: with the graph epoch intact the same release reaps exactly that PTY', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- expect(receipt.state).toBe('released')
- expect(receipt.processAction).toBe('closed_agent_terminal')
- expect(closedPtyIds).toEqual([PTY_ID])
- expect(ptysById.has(PTY_ID)).toBe(false)
- expect(await workerTerminalState(dispatchId)).toBe('released')
- })
-
- it('FENCE (fixed): a reused ptyId carrying a different incarnation must NOT remint or close — stays release_unknown', async () => {
- setup()
- const { dispatchId } = await startSettledWorker()
-
- // The graph epoch bumps AND the ptyId has been reused by a different process (incarnation 2),
- // while the worker's recorded incarnation is still 1. The exact-incarnation fence must refuse.
- rendererGraphEpoch = 1
- const reused = ptysById.get(PTY_ID)
- if (reused) {
- reused.incarnationId = 2
- }
-
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Test fixture crosses a private/runtime boundary with a verified shape.
- const receipt = (await call('orchestration.workerRelease', { dispatch: dispatchId })) as {
- state: string
- processAction: string
- }
-
- expect(receipt.state).toBe('release_unknown')
- expect(receipt.processAction).toBe('none')
- expect(runtime.closeTerminal).not.toHaveBeenCalled()
- // The other lane's live process is left untouched (never reaped by an over-broad match).
- expect(ptysById.get(PTY_ID)?.alive).toBe(true)
- expect(await workerTerminalState(dispatchId)).toBe('release_unknown')
- })
-})
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/structured-worker-release-stop.ts b/src/main/runtime/rpc/methods/orchestration/worker/structured-worker-release-stop.ts
index 91e524e4bf1..4a32147d1a6 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/structured-worker-release-stop.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/structured-worker-release-stop.ts
@@ -4,15 +4,15 @@ import type { WorkerTerminalResourceRow } from '../../../../orchestration/worker
import { stopStructuredWorker } from '../../orchestration-structured-worker-lifecycle'
import type { StructuredWorkerIdentity } from '../../../../structured-worker-identity'
import { archiveSummary } from './worker-terminal-resource-presentation'
-import { releaseUnknownRecovery, type WorkerReleaseReceipt } from './worker-release-completion'
+import type { WorkerReleaseReceipt } from './worker-release-completion'
/**
* The close half of a release for a worker that IS a structured session.
*
* Separate from the PTY close for the same reason the delivery lane is: there is no terminal to
* close and no exit to observe, so the host's own settlement is the only proof available. Only a
- * proven close may settle; an unproven one reports `release_unknown` and stays retryable, but only
- * under a fresh request id — replaying the prior one just returns this same receipt.
+ * proven close may settle; an unproven one reports `release_unknown` and stays retryable under the
+ * same request id.
*/
export async function stopStructuredWorkerForRelease(args: {
structured: StructuredWorkerIdentity
@@ -36,7 +36,7 @@ export async function stopStructuredWorkerForRelease(args: {
processAction: stop.closeAttempted ? 'closed_agent_terminal' : 'none',
archive: { source: args.archiveSource, status: args.archiveStatus },
lastError: unknown.release_error ?? stop.reason,
- recovery: releaseUnknownRecovery(dispatchId)
+ recovery: `Inspect with: orca orchestration worker-show --dispatch ${dispatchId} --json — then repeat worker-release with the same --retry-request.`
}
}
const settled = db.settleWorkerTerminalRelease(resource.id)
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-control.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-control.ts
index 2930abcd63f..5e8babf3c5e 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-control.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-control.ts
@@ -143,9 +143,6 @@ export const ORCHESTRATION_WORKER_CONTROL_METHODS = [
`Worker Dispatch ${params.dispatch} no longer resolves to its exact process.`
)
}
- // Read via the handle inspectWorkerTerminal proved live: the durable one, or a handle
- // re-minted from the recorded incarnation after the durable handle went stale.
- const liveHandle = observation.terminalHandle ?? terminalHandle
const structured = readStructuredWorkerOutput({
db,
dispatchId: params.dispatch,
@@ -166,7 +163,7 @@ export const ORCHESTRATION_WORKER_CONTROL_METHODS = [
const output = await readExactWorkerOutput({
runtime,
dispatchId: params.dispatch,
- terminalHandle: liveHandle,
+ terminalHandle,
workerState: worker?.state ?? 'unsupervised',
terminalStatus:
observation.status === 'exited'
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-observation.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-observation.ts
index 2fae12d6608..83b3d020f4c 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-observation.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-observation.ts
@@ -15,7 +15,6 @@ import type {
WorkerDispatchRow
} from '../../../../orchestration/types'
-/** Observe a worker terminal, re-minting a live handle from the recorded process incarnation when the durable handle went stale, so a still-running worker is never reported missing and leaked. */
export async function inspectWorkerTerminal(
runtime: OrcaRuntimeService,
db: OrchestrationDb,
@@ -28,15 +27,12 @@ export async function inspectWorkerTerminal(
reason?: string
/** Set only on a proven-exact worker parked on a prompt that needs a human. */
agentWait?: RuntimeTerminalInteractiveWait | null
- /** The handle that actually resolved: the durable one, or a live handle re-minted from the
- * recorded process incarnation after the durable handle went stale. Null when none resolved. */
- terminalHandle: string | null
}> {
const worker = db.getWorkerDispatch(dispatchId)
const terminalHandle =
worker?.agent_terminal_handle ?? db.getDispatchContextById(dispatchId)?.assignee_handle
if (!terminalHandle) {
- return { terminal: null, exact: false, status: 'unattached', terminalHandle: null }
+ return { terminal: null, exact: false, status: 'unattached' }
}
const structured = resolveStructuredWorkerForDispatch(db, dispatchId)
if (structured) {
@@ -57,44 +53,20 @@ export async function inspectWorkerTerminal(
terminal: null,
exact,
status: exact ? observation.status : 'identity_changed',
- ...(exact && observation.reason ? { reason: observation.reason } : {}),
- terminalHandle: null
+ ...(exact && observation.reason ? { reason: observation.reason } : {})
}
}
- let effectiveHandle = terminalHandle
- let terminal = await runtime.showTerminal(effectiveHandle).catch(() => null)
+ const terminal = await runtime.showTerminal(terminalHandle).catch(() => null)
if (!terminal) {
- // Why: the durable handle resolves nowhere after a renderer graph epoch bump or handle
- // invalidation, yet the recorded process incarnation may still name a live PTY. Re-mint a
- // live handle (incarnation-fenced) so worker-show and release act on the still-running
- // process instead of reporting it missing — which would leak the agent process tree.
- // (workerList is a pure DB projection and never calls inspectWorkerTerminal.)
- const resource = db.getWorkerTerminalResourceByOwner(dispatchId)
- const reminted = resource?.process_incarnation
- ? runtime.resolveTerminalHandleByProcessIncarnation(
- resource.process_incarnation,
- resource.host_scope
- )
- : null
- if (reminted) {
- const remintedTerminal = await runtime.showTerminal(reminted).catch(() => null)
- if (remintedTerminal) {
- effectiveHandle = reminted
- terminal = remintedTerminal
- }
- }
- }
- if (!terminal) {
- // The re-mint above failed, so no live handle resolved; report the durable handle unresolved.
- return { terminal: null, exact: false, status: 'missing', terminalHandle: null }
+ return { terminal: null, exact: false, status: 'missing' }
}
const exact = db.isDispatchProcessCurrent({
dispatchId,
- paneKey: runtime.getTerminalPaneKey(effectiveHandle),
- processIncarnation: runtime.getTerminalProcessIncarnation(effectiveHandle)
+ paneKey: runtime.getTerminalPaneKey(terminalHandle),
+ processIncarnation: runtime.getTerminalProcessIncarnation(terminalHandle)
})
if (!exact) {
- return { terminal, exact, status: 'identity_changed', terminalHandle: effectiveHandle }
+ return { terminal, exact, status: 'identity_changed' }
}
// Why: the aggregate inventory only iterates registered providers, so a dropped
// relay clears `connected` for every remote PTY at once. Lost contact is not a
@@ -104,48 +76,38 @@ export async function inspectWorkerTerminal(
// Exact-gated by the early return above: a replaced process's prompt would attribute another
// lane's blocker to this worker.
const agentWait = terminal.agentWait
- const verdict = runtime.getTerminalLivenessVerdict?.(effectiveHandle) ?? null
+ const verdict = runtime.getTerminalLivenessVerdict?.(terminalHandle) ?? null
if (verdict?.status === 'unverifiable') {
- return {
- terminal,
- exact,
- status: 'unverifiable',
- reason: verdict.reason,
- agentWait,
- terminalHandle: effectiveHandle
- }
+ return { terminal, exact, status: 'unverifiable', reason: verdict.reason, agentWait }
}
if (verdict?.status === 'live') {
- return { terminal, exact, status: 'live', agentWait, terminalHandle: effectiveHandle }
+ return { terminal, exact, status: 'live', agentWait }
}
if (!verdict) {
const dispatch = db.getDispatchContextById?.(dispatchId)
const persistedHostScope = parseWorkerTerminalHostScope(dispatch?.host_scope ?? null)
- const currentHostScope = runtime.getOrchestrationDispatchAuthority?.(effectiveHandle)?.hostScope
+ const currentHostScope = runtime.getOrchestrationDispatchAuthority?.(terminalHandle)?.hostScope
if (persistedHostScope?.kind === 'ssh' || currentHostScope?.kind === 'ssh') {
return {
terminal,
exact,
status: 'unverifiable',
reason: 'missing_liveness_verdict',
- agentWait,
- terminalHandle: effectiveHandle
+ agentWait
}
}
return {
terminal,
exact,
status: terminal.connected === false ? 'exited' : 'live',
- agentWait,
- terminalHandle: effectiveHandle
+ agentWait
}
}
return {
terminal,
exact,
status: 'exited',
- agentWait,
- terminalHandle: effectiveHandle
+ agentWait
}
}
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-release-completion.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-release-completion.ts
index 5076d607e7b..b686cdfe6cd 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-release-completion.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-release-completion.ts
@@ -121,9 +121,6 @@ async function completeWorkerTerminalReleaseOnce(
}
}
const observation = await inspectWorkerTerminal(runtime, db, dispatchId)
- // The live handle to act on: the durable one, or a handle re-minted from the recorded process
- // incarnation when the durable handle went stale (inspectWorkerTerminal proved it live).
- const terminalHandle = observation.terminalHandle ?? resource.terminal_handle
if (observation.status === 'identity_changed') {
const retained = db.revertWorkerTerminalReleaseToRetained(resource.id, 'identity_unproven')
return {
@@ -135,39 +132,33 @@ async function completeWorkerTerminalReleaseOnce(
}
}
if (observation.status === 'missing' || observation.status === 'unattached') {
- // Re-resolution by process incarnation (inspectWorkerTerminal) already failed, so no live PTY
- // carries this worker's exact incarnation. If that incarnation is provably gone, settle
- // released BEFORE the recovery defer: proof of death outranks deferral, so a provably-exited
- // worker never languishes in release_pending across recovery passes.
- if (resource.process_incarnation) {
- const processLiveness = await runtime.inspectTerminalProcessIncarnationLiveness(
- resource.process_incarnation,
- resource.host_scope
- )
- if (processLiveness === 'exited') {
- // Prefer incarnation-fenced settle (dispatch relation + process_incarnation CAS).
- const reconciled = db.settleDeadWorkerTerminalRelease({
- requestingDispatchId: dispatchId,
- resourceId: resource.id,
- processIncarnation: resource.process_incarnation
- })
- if (reconciled.disposition === 'released') {
- runtime.notifyMessageArrived(`dispatch:${dispatchId}`, 'status')
- return {
- dispatchId,
- state: 'released',
- processAction: 'none',
- archive: archiveSummary(reconciled.resource)
+ if (args.mode === 'recovery') {
+ // A close can succeed before the process crashes, leaving `releasing` durable state while
+ // terminal inventory no longer resolves the handle. Only a positive host liveness verdict
+ // may settle that exact incarnation; contact loss remains pending/unverifiable.
+ if (resource.process_incarnation) {
+ const processLiveness = await runtime.inspectTerminalProcessIncarnationLiveness(
+ resource.process_incarnation,
+ resource.host_scope
+ )
+ if (processLiveness === 'exited') {
+ const reconciled = db.settleDeadWorkerTerminalRelease({
+ requestingDispatchId: dispatchId,
+ resourceId: resource.id,
+ processIncarnation: resource.process_incarnation
+ })
+ if (reconciled.disposition === 'released') {
+ runtime.notifyMessageArrived(`dispatch:${dispatchId}`, 'status')
+ return {
+ dispatchId,
+ state: 'released',
+ processAction: 'closed_exited_terminal',
+ archive: archiveSummary(reconciled.resource)
+ }
}
}
- // settleDead retains when the archive is still mandatory and missing (e.g. requested but
- // never committed). Do NOT plain-settle: that would discard output and break recovery's
- // "archive is mandatory" invariant. Fall through to recovery pending / unknown instead.
}
- }
- if (args.mode === 'recovery') {
- // No death certificate yet: inventory may still be incomplete during startup/reconnect
- // discovery, so defer instead of guessing.
+ // Inventory may still be incomplete during startup/reconnect discovery; defer.
return {
dispatchId,
state: 'release_pending',
@@ -193,7 +184,7 @@ async function completeWorkerTerminalReleaseOnce(
}
}
- if (!workerTerminalLeaseIsCurrent(runtime, db, dispatchId, resource, terminalHandle)) {
+ if (!workerTerminalLeaseIsCurrent(runtime, db, dispatchId, resource)) {
const retained = db.revertWorkerTerminalReleaseToRetained(resource.id, 'identity_unproven')
return {
dispatchId,
@@ -212,7 +203,7 @@ async function completeWorkerTerminalReleaseOnce(
const captured = await captureWorkerOutputArchive({
runtime,
dispatchId,
- terminalHandle,
+ terminalHandle: resource.terminal_handle,
attachedAtMs: orchestrationTimestampToMs(worker.created_at),
structuredWorker: structured
})
@@ -240,7 +231,7 @@ async function completeWorkerTerminalReleaseOnce(
archive: archiveSummary(releasing)
}
}
- if (!workerTerminalLeaseIsCurrent(runtime, db, dispatchId, releasing, terminalHandle)) {
+ if (!workerTerminalLeaseIsCurrent(runtime, db, dispatchId, releasing)) {
const retained = db.revertWorkerTerminalReleaseToRetained(resource.id, 'identity_unproven')
return {
dispatchId,
@@ -263,7 +254,7 @@ async function completeWorkerTerminalReleaseOnce(
archiveStatus
})
}
- const close = await runtime.closeTerminal(terminalHandle)
+ const close = await runtime.closeTerminal(resource.terminal_handle)
if (!close.ptyKilled) {
const reason = describeUnconfirmedAgentStop(close)
const unknown = db.markWorkerTerminalReleaseUnknown(resource.id, reason)
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-release-runtime-incarnation.test.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-release-runtime-incarnation.test.ts
deleted file mode 100644
index 4caac62f5da..00000000000
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-release-runtime-incarnation.test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest'
-import { inspectWorkerTerminal } from './worker-observation'
-import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
-import { makePaneKey } from '../../../../../../shared/stable-pane-id'
-
-const PTY_ID = 'runtime_test:term_worker'
-const LEAF_ID = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
-
-describe('worker release through runtime incarnation recovery', () => {
- const harness = createOrchestrationWorkerReleaseHarness()
- afterEach(() => harness.cleanup())
-
- it.each(['release', 'stop', 'read', 'replacement', 'disconnected'] as const)(
- '%s uses runtime identity and close paths',
- async (scenario) => {
- harness.setup()
- const { dispatchId } =
- scenario === 'stop' ? await harness.startWorker() : await harness.startSettledWorker()
- const runtime = harness.runtime
- vi.mocked(runtime.showTerminal).mockRestore()
- vi.mocked(runtime.getTerminalPaneKey).mockRestore()
- vi.mocked(runtime.getTerminalProcessIncarnation).mockRestore()
- vi.mocked(runtime.getOrchestrationDispatchAuthority).mockRestore()
- vi.mocked(runtime.readTerminal).mockRestore()
- vi.mocked(runtime.closeTerminal).mockRestore()
- const kill = vi.fn(() => true)
- runtime.setPtyController({ write: () => true, kill, getForegroundProcess: async () => null })
- runtime.registerPty(PTY_ID, 'repo::worktree', null, {
- tabId: 'tab_worker',
- leafId: LEAF_ID,
- incarnationId: scenario === 'replacement' ? '2' : '1'
- })
- await expect(runtime.showTerminal('term_worker')).rejects.toThrow()
- if (scenario === 'disconnected') {
- vi.spyOn(runtime, 'getTerminalLivenessVerdict').mockReturnValue({
- status: 'unverifiable',
- reason: 'transport unavailable'
- })
- const observed = await inspectWorkerTerminal(runtime, harness.db, dispatchId)
- expect(observed).toMatchObject({ exact: true, status: 'unverifiable' })
- expect(kill).not.toHaveBeenCalled()
- return
- }
- if (scenario === 'stop') {
- const receipt = await harness.call('orchestration.workerStop', { dispatch: dispatchId })
- expect(receipt).toMatchObject({ state: 'stopped', processAction: 'closed_agent_terminal' })
- expect(kill).toHaveBeenCalledExactlyOnceWith(PTY_ID)
- return
- }
- if (scenario === 'read') {
- const readTerminal = vi.spyOn(runtime, 'readTerminal')
- const output = await harness.call('orchestration.workerRead', { dispatch: dispatchId })
- // Pins the read to the reminted terminal: the handle it reached must resolve to the
- // registered pane and incarnation, which the stale durable handle never does.
- const [[readHandle]] = readTerminal.mock.calls
- expect(runtime.getTerminalPaneKey(readHandle)).toBe(makePaneKey('tab_worker', LEAF_ID))
- expect(runtime.getTerminalProcessIncarnation(readHandle)).toBe(`${PTY_ID}:1`)
- expect(output).toMatchObject({
- source: 'terminal',
- fallbackReason: 'session_not_reported'
- })
- expect(kill).not.toHaveBeenCalled()
- return
- }
- const receipt = await harness.call('orchestration.workerRelease', { dispatch: dispatchId })
- if (scenario === 'replacement') {
- expect(receipt).toMatchObject({ state: 'release_unknown', processAction: 'none' })
- expect(kill).not.toHaveBeenCalled()
- return
- }
- expect(receipt).toMatchObject({ state: 'released', processAction: 'closed_agent_terminal' })
- expect(kill).toHaveBeenCalledExactlyOnceWith(PTY_ID)
- expect(harness.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
- ownership_state: 'released',
- release_state: 'released'
- })
- }
- )
-})
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-stop.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-stop.ts
index ee0e73c904f..98d3c376f61 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-stop.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-stop.ts
@@ -141,7 +141,6 @@ export const ORCHESTRATION_WORKER_STOP_METHODS = [
})
}
const observation = await inspectWorkerTerminal(runtime, db, params.dispatch)
- const liveHandle = observation.terminalHandle ?? handle
// The host exit can settle this stop while terminal inspection is awaiting inventory.
if (db.getWorkerDispatch(params.dispatch)?.state === 'stopped') {
runtime.notifyMessageArrived(`dispatch:${params.dispatch}`, 'status')
@@ -202,7 +201,7 @@ export const ORCHESTRATION_WORKER_STOP_METHODS = [
}
}
const closed = await runtime
- .closeTerminal(liveHandle)
+ .closeTerminal(handle)
.then((close) => ({ close }) as const)
.catch(
(error: unknown) =>
diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-terminal-release-lease.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-terminal-release-lease.ts
index cad0ceb4128..50a97c9de4b 100644
--- a/src/main/runtime/rpc/methods/orchestration/worker/worker-terminal-release-lease.ts
+++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-terminal-release-lease.ts
@@ -9,27 +9,23 @@ export function workerTerminalLeaseIsCurrent(
runtime: OrcaRuntimeService,
db: OrchestrationDb,
dispatchId: string,
- resource: WorkerTerminalResourceRow,
- // Live (possibly reminted) handle for runtime probes. Durable identity still compares
- // worker.agent_terminal_handle to resource.terminal_handle; after beginGraphReload the durable
- // string is absent from the handle table, so authority/pane/incarnation must use the live one.
- liveTerminalHandle: string
+ resource: WorkerTerminalResourceRow
): boolean {
const worker = db.getWorkerDispatch(dispatchId)
if (isStructuredWorkerHandle(resource.terminal_handle)) {
return structuredWorkerTerminalLeaseIsCurrent(db, dispatchId, worker, resource)
}
- const authority = runtime.getOrchestrationDispatchAuthority(liveTerminalHandle)
+ const authority = runtime.getOrchestrationDispatchAuthority(resource.terminal_handle)
// Exited PTYs retain identity and host evidence but no longer mint launch authority.
return Boolean(
worker?.agent_terminal_handle === resource.terminal_handle &&
(authority
? resource.host_scope === JSON.stringify(authority.hostScope)
- : runtime.getTerminalLivenessVerdict(liveTerminalHandle)?.status === 'exited') &&
+ : runtime.getTerminalLivenessVerdict(resource.terminal_handle)?.status === 'exited') &&
db.isDispatchProcessCurrent({
dispatchId,
- paneKey: runtime.getTerminalPaneKey(liveTerminalHandle),
- processIncarnation: runtime.getTerminalProcessIncarnation(liveTerminalHandle)
+ paneKey: runtime.getTerminalPaneKey(resource.terminal_handle),
+ processIncarnation: runtime.getTerminalProcessIncarnation(resource.terminal_handle)
}) &&
!db.workerTerminalResourceHasIdentityConflict(resource.id)
)
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index 2ddea555dab..42c829a5e01 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -584,7 +584,6 @@
"9e2a9bb87b": "Continue",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index 887ba198003..cfce4be274b 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -358,7 +358,6 @@
"9e2a9bb87b": "Continue",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json
index d9c5dd5004d..3bb3ca43922 100644
--- a/src/renderer/src/i18n/locales/fr.json
+++ b/src/renderer/src/i18n/locales/fr.json
@@ -435,7 +435,6 @@
"9e2a9bb87b": "Continuer",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 4fe965ac48e..db72c535817 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -358,7 +358,6 @@
"9e2a9bb87b": "Continue",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 88ba5814432..55893501e49 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -361,7 +361,6 @@
"9e2a9bb87b": "Continue",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index 592a734e077..2f5c569a099 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -361,7 +361,6 @@
"9e2a9bb87b": "Continue",
"6f8056a565": "Command Code",
"4238b771b5": "Codebuff",
- "b0b350de65": "Freebuff",
"cbaf0c2e0b": "Cline",
"1f8a19e9ad": "Autohand Code",
"5e8eff11b3": "Auggie",
diff --git a/src/renderer/src/lib/agent-catalog.tsx b/src/renderer/src/lib/agent-catalog.tsx
index 32193c7e7b0..6ea1de86787 100644
--- a/src/renderer/src/lib/agent-catalog.tsx
+++ b/src/renderer/src/lib/agent-catalog.tsx
@@ -235,13 +235,6 @@ export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] =>
faviconDomain: 'codebuff.com',
homepageUrl: 'https://www.codebuff.com/docs/help/quick-start'
},
- {
- id: 'freebuff',
- label: translate('auto.lib.agent.catalog.b0b350de65', 'Freebuff'),
- cmd: 'freebuff',
- faviconDomain: 'freebuff.com',
- homepageUrl: 'https://freebuff.com/cli'
- },
{
id: 'command-code',
label: translate('auto.lib.agent.catalog.6f8056a565', 'Command Code'),
diff --git a/src/renderer/src/lib/agent-favicon-assets.ts b/src/renderer/src/lib/agent-favicon-assets.ts
index 98ed4c95b0e..caeea46bc43 100644
--- a/src/renderer/src/lib/agent-favicon-assets.ts
+++ b/src/renderer/src/lib/agent-favicon-assets.ts
@@ -14,7 +14,6 @@ import augUrl from '../../../shared/agent-icons/aug.png?url'
import autohandUrl from '../../../shared/agent-icons/autohand.png?url'
import clineUrl from '../../../shared/agent-icons/cline.png?url'
import codebuffUrl from '../../../shared/agent-icons/codebuff.png?url'
-import freebuffUrl from '../../../shared/agent-icons/freebuff.png?url'
import commandCodeUrl from '../../../shared/agent-icons/command-code.png?url'
import continueUrl from '../../../shared/agent-icons/continue.png?url'
import cursorUrl from '../../../shared/agent-icons/cursor.png?url'
@@ -50,7 +49,6 @@ export const AGENT_FAVICON_ASSETS: Partial> = {
autohand: autohandUrl,
cline: clineUrl,
codebuff: codebuffUrl,
- freebuff: freebuffUrl,
'command-code': commandCodeUrl,
continue: continueUrl,
cursor: cursorUrl,
diff --git a/src/renderer/src/lib/agent-status.ts b/src/renderer/src/lib/agent-status.ts
index 8a642c1dd50..b88018b4294 100644
--- a/src/renderer/src/lib/agent-status.ts
+++ b/src/renderer/src/lib/agent-status.ts
@@ -120,7 +120,6 @@ const ICONABLE_AGENT_TYPES: Record = {
aug: true,
cline: true,
codebuff: true,
- freebuff: true,
'command-code': true,
continue: true,
cursor: true,
diff --git a/src/shared/agent-icons/freebuff.png b/src/shared/agent-icons/freebuff.png
deleted file mode 100644
index b81a6fa9c21..00000000000
Binary files a/src/shared/agent-icons/freebuff.png and /dev/null differ
diff --git a/src/shared/agent-kind.ts b/src/shared/agent-kind.ts
index fd1e369c767..13e123fbc88 100644
--- a/src/shared/agent-kind.ts
+++ b/src/shared/agent-kind.ts
@@ -36,7 +36,6 @@ const TUI_AGENT_KIND_BY_AGENT = {
aug: 'aug',
cline: 'cline',
codebuff: 'codebuff',
- freebuff: 'freebuff',
'command-code': 'command-code',
continue: 'continue',
cursor: 'cursor',
diff --git a/src/shared/skills-cli-agent-keys.ts b/src/shared/skills-cli-agent-keys.ts
index f99b1f1c248..275f96b9fae 100644
--- a/src/shared/skills-cli-agent-keys.ts
+++ b/src/shared/skills-cli-agent-keys.ts
@@ -35,7 +35,6 @@ export const SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT = {
aug: 'augment',
cline: 'cline',
codebuff: null,
- freebuff: null,
'command-code': 'command-code',
continue: 'continue',
cursor: 'cursor',
diff --git a/src/shared/telemetry-property-schemas.ts b/src/shared/telemetry-property-schemas.ts
index 81613deff8f..4e868a3de33 100644
--- a/src/shared/telemetry-property-schemas.ts
+++ b/src/shared/telemetry-property-schemas.ts
@@ -30,7 +30,6 @@ export const AGENT_KIND_VALUES = [
'aug',
'cline',
'codebuff',
- 'freebuff',
'command-code',
'continue',
'cursor',
diff --git a/src/shared/tui-agent-config.ts b/src/shared/tui-agent-config.ts
index fa53d2c0f7d..40986189f32 100644
--- a/src/shared/tui-agent-config.ts
+++ b/src/shared/tui-agent-config.ts
@@ -234,10 +234,6 @@ const TUI_AGENT_CONFIG_SOURCE: Record = {
detectCmd: 'codebuff',
promptInjectionMode: 'stdin-after-start'
},
- freebuff: {
- detectCmd: 'freebuff',
- promptInjectionMode: 'stdin-after-start'
- },
'command-code': {
// Why: use the full name (not its `cmd` alias) so detection doesn't collide with Windows' built-in cmd.exe.
detectCmd: 'command-code',
diff --git a/src/shared/tui-agent-display-names.ts b/src/shared/tui-agent-display-names.ts
index 4acb76e61af..b886cfb1490 100644
--- a/src/shared/tui-agent-display-names.ts
+++ b/src/shared/tui-agent-display-names.ts
@@ -32,7 +32,6 @@ export const TUI_AGENT_DISPLAY_NAMES: Record = {
aug: 'Auggie',
cline: 'Cline',
codebuff: 'Codebuff',
- freebuff: 'Freebuff',
'command-code': 'Command Code',
continue: 'Continue',
cursor: 'Cursor',
diff --git a/src/shared/tui-agent-selection.ts b/src/shared/tui-agent-selection.ts
index 4afacfd9e21..85863618228 100644
--- a/src/shared/tui-agent-selection.ts
+++ b/src/shared/tui-agent-selection.ts
@@ -31,7 +31,6 @@ export const TUI_AGENT_AUTO_PICK_ORDER = [
'autohand',
'cline',
'codebuff',
- 'freebuff',
'command-code',
'continue',
'cursor',
diff --git a/src/shared/tui-agent.ts b/src/shared/tui-agent.ts
index 6bf80af5002..226060d1d23 100644
--- a/src/shared/tui-agent.ts
+++ b/src/shared/tui-agent.ts
@@ -22,7 +22,6 @@ export type TuiAgent =
| 'aug' // Augment/Auggie
| 'cline' // Cline
| 'codebuff' // Codebuff
- | 'freebuff' // Freebuff
| 'command-code' // Command Code
| 'continue' // Continue
| 'cursor' // Cursor