[CI] Reuse costly test setup and report an explicit full result (#29228)

* ci: publish an explicit result from the PR aggregate

* test(codex): preserve review text across OSC terminators

* perf(ci): reuse worker fixtures and validated launcher output

* fix(test): use typed orchestration database access

* fix(ci): exclude test fixtures from focus ratchet
This commit is contained in:
Neil
2026-10-15 16:27:26 -07:00
committed by GitHub
parent 5b7845e36f
commit 83dcc7dcb7
40 changed files with 1656 additions and 1732 deletions
+12 -3
View File
@@ -10,10 +10,19 @@ export function verifyRequiredPrContext(parseYaml) {
const workflow = parseYaml(readFileSync(join(directory, file), 'utf8'))
return Object.entries(workflow.jobs)
.filter(([id, job]) => (job.name ?? id) === 'PR checks passed')
.map(([id]) => `${file}:${id}`)
.map(([id, job]) => ({ owner: `${file}:${id}`, job }))
})
if (providers.length !== 1 || providers[0] !== 'pr.yml:verify') {
throw new Error(`Required PR context must belong only to pr.yml:verify; found ${providers}`)
const owners = providers.map((provider) => provider.owner)
if (providers.length !== 1 || owners[0] !== 'pr.yml:verify') {
throw new Error(`Required PR context must belong only to pr.yml:verify; found ${owners}`)
}
const gate = providers[0].job
const condition = gate.if?.replace(/^\$\{\{\s*|\s*\}\}$/g, '').trim()
if (condition !== '!cancelled()') {
throw new Error('Required PR aggregate must run unless the workflow is cancelled.')
}
if (gate['continue-on-error'] || gate.steps?.some((step) => step['continue-on-error'])) {
throw new Error('Required PR aggregate must not ignore failures.')
}
}
+12 -86
View File
@@ -51,7 +51,6 @@ jobs:
package: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package }}
package_windows: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package_windows }}
layout_oracle: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.layout_oracle }}
verify_fast_path: ${{ steps.verify_fast_path.outputs.canonical }}
e2e_should_run: ${{ steps.e2e_filter.outputs.should_run }}
test_files: ${{ steps.e2e_filter.outputs.test_files }}
e2e_run_changed: ${{ steps.e2e_filter.outputs.e2e_run_changed }}
@@ -137,33 +136,6 @@ jobs:
fi
printf '%s\n' "$CHANGED" | node config/scripts/pr-code-change-scope.mjs "${MOBILE_CI_ARGS[@]}" | tee -a "$GITHUB_OUTPUT"
# Actions comparisons ignore case; validate exact routing before allowing a skipped gate.
- name: Validate aggregate routing
id: verify_fast_path
env:
STATIC_ANALYSIS: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.static_analysis }}
TYPECHECK: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.typecheck }}
GIT_COMPATIBILITY: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.git_compatibility }}
CODEX_INDEX_HEAL_CONTRACT: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.codex_index_heal_contract }}
XTERM_PATCH_SYNC: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.xterm_patch_sync }}
SHELL_CONTRACTS: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.shell_contracts }}
TEST: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.test }}
MOBILE_WEB_APP: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.mobile_web_app }}
CROSS_VERSION_WIRE: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.cross-version-wire }}
MANAGED_HOOK_NODE18: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.managed_hook_node18 }}
PACKAGE: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package }}
PACKAGE_WINDOWS: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.package_windows }}
LAYOUT_ORACLE: ${{ steps.readiness.outputs.reused != 'true' && steps.filter.outputs.layout_oracle }}
run: |
canonical=1
for selected in "$STATIC_ANALYSIS" "$TYPECHECK" "$GIT_COMPATIBILITY" "$CODEX_INDEX_HEAL_CONTRACT" "$XTERM_PATCH_SYNC" "$SHELL_CONTRACTS" "$TEST" "$MOBILE_WEB_APP" "$CROSS_VERSION_WIRE" "$MANAGED_HOOK_NODE18" "$PACKAGE" "$PACKAGE_WINDOWS" "$LAYOUT_ORACLE"; do
case "$selected" in
true|false) ;;
*) canonical=0 ;;
esac
done
echo "canonical=$canonical" >> "$GITHUB_OUTPUT"
# Reuse the path-detector checkout instead of queuing another runner.
- name: Filter changed E2E specs
id: e2e_filter
@@ -1528,62 +1500,8 @@ jobs:
verify:
name: PR checks passed
# GitHub accepts skipped required jobs; only mismatches need a runner to fail with details.
if: >-
!cancelled() && !(
needs.code_paths.result == 'success' &&
needs.code_paths.outputs.verify_fast_path == '1' &&
(needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.static_analysis == 'false') &&
(needs.code_paths.outputs.typecheck == 'true' || needs.code_paths.outputs.typecheck == 'false') &&
(
((needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.typecheck == 'true') && needs.preflight.result == 'success') ||
(needs.code_paths.outputs.static_analysis == 'false' && needs.code_paths.outputs.typecheck == 'false' && needs.preflight.result == 'skipped')
) &&
(
(needs.code_paths.outputs.git_compatibility == 'true' && needs.git_compatibility.result == 'success') ||
(needs.code_paths.outputs.git_compatibility == 'false' && needs.git_compatibility.result == 'skipped')
) &&
(
(needs.code_paths.outputs.codex_index_heal_contract == 'true' && needs.codex_index_heal_contract.result == 'success') ||
(needs.code_paths.outputs.codex_index_heal_contract == 'false' && needs.codex_index_heal_contract.result == 'skipped')
) &&
(
(needs.code_paths.outputs.xterm_patch_sync == 'true' && needs.xterm_patch_sync.result == 'success') ||
(needs.code_paths.outputs.xterm_patch_sync == 'false' && needs.xterm_patch_sync.result == 'skipped')
) &&
(
(needs.code_paths.outputs.shell_contracts == 'true' && needs.shell_contracts.result == 'success') ||
(needs.code_paths.outputs.shell_contracts == 'false' && needs.shell_contracts.result == 'skipped')
) &&
(
(needs.code_paths.outputs.test == 'true' && needs.test.result == 'success') ||
(needs.code_paths.outputs.test == 'false' && needs.test.result == 'skipped')
) &&
(
(needs.code_paths.outputs.mobile_web_app == 'true' && needs.mobile_web_app.result == 'success') ||
(needs.code_paths.outputs.mobile_web_app == 'false' && needs.mobile_web_app.result == 'skipped')
) &&
(
(needs.code_paths.outputs['cross-version-wire'] == 'true' && needs['cross-version-wire'].result == 'success') ||
(needs.code_paths.outputs['cross-version-wire'] == 'false' && needs['cross-version-wire'].result == 'skipped')
) &&
(
(needs.code_paths.outputs.managed_hook_node18 == 'true' && needs.managed_hook_node18.result == 'success') ||
(needs.code_paths.outputs.managed_hook_node18 == 'false' && needs.managed_hook_node18.result == 'skipped')
) &&
(
(needs.code_paths.outputs.package == 'true' && needs.package.result == 'success') ||
(needs.code_paths.outputs.package == 'false' && needs.package.result == 'skipped')
) &&
(
(needs.code_paths.outputs.package_windows == 'true' && needs.package_windows.result == 'success') ||
(needs.code_paths.outputs.package_windows == 'false' && needs.package_windows.result == 'skipped')
) &&
(
(needs.code_paths.outputs.layout_oracle == 'true' && needs.layout_oracle.result == 'success') ||
(needs.code_paths.outputs.layout_oracle == 'false' && needs.layout_oracle.result == 'skipped')
)
)
# Publish an explicit result after every required dependency settles.
if: ${{ !cancelled() }}
needs:
- code_paths
- preflight
@@ -1598,7 +1516,6 @@ jobs:
- package
- package_windows
- layout_oracle
# Only failures and noncanonical routing allocate this runner.
runs-on: ubuntu-slim
timeout-minutes: 5
@@ -1616,6 +1533,8 @@ jobs:
CODE_PATHS: ${{ needs.code_paths.result }}
SHOULD_RUN: ${{ needs.code_paths.outputs.should_run }}
PREFLIGHT: ${{ needs.preflight.result }}
STATIC_ANALYSIS_SHOULD_RUN: ${{ needs.code_paths.outputs.static_analysis }}
TYPECHECK_SHOULD_RUN: ${{ needs.code_paths.outputs.typecheck }}
PREFLIGHT_SHOULD_RUN: ${{ needs.code_paths.outputs.static_analysis == 'true' || needs.code_paths.outputs.typecheck == 'true' }}
GIT_COMPATIBILITY: ${{ needs.git_compatibility.result }}
GIT_COMPATIBILITY_SHOULD_RUN: ${{ needs.code_paths.outputs.git_compatibility }}
@@ -1643,6 +1562,10 @@ jobs:
if [ "$CODE_PATHS" != "success" ]; then
exit 1
fi
case "$STATIC_ANALYSIS_SHOULD_RUN:$TYPECHECK_SHOULD_RUN" in
true:true|true:false|false:true|false:false) ;;
*) echo "preflight: missing or invalid selection"; exit 1 ;;
esac
if [ "$SHOULD_RUN" != "true" ]; then
echo "Docs-only change; expensive PR checks skipped."
fi
@@ -1654,11 +1577,14 @@ jobs:
echo "$name: expected success, got $result"
failed=1
fi
else
elif [ "$should" = "false" ]; then
if [ "$result" != "skipped" ]; then
echo "$name: expected skipped, got $result"
failed=1
fi
else
echo "$name: missing or invalid selection"
failed=1
fi
}
# Require success when the PR has code-relevant changes
@@ -59,7 +59,7 @@ beforeAll(() => {
sharedLauncherPath = join(sharedLauncherRoot, 'orca.exe')
const result = runProcessSync({
program: process.execPath,
args: ['config/scripts/build-windows-cli-launcher.mjs', '--output', sharedLauncherPath],
args: ['config/scripts/build-windows-cli-launcher.mjs'],
cwd: projectRoot,
timeoutMs: 300_000
})
@@ -67,6 +67,10 @@ beforeAll(() => {
if (result.code !== 0) {
throw new Error(`${result.stdout}\n${result.stderr}`)
}
copyFileSync(
join(projectRoot, 'native', 'windows-cli-launcher', '.build', 'orca.exe'),
sharedLauncherPath
)
}, 300_000)
afterAll(() => {
+4 -1
View File
@@ -355,7 +355,10 @@ const ADVICE =
"Route by the resource's owner. Only a creation flow with no source row may use defaultCreationHost."
export function isScannedPath(rel) {
return /\.(ts|tsx)$/.test(rel) && !/\.(test|spec)\.tsx?$/.test(rel)
return (
/\.(ts|tsx)$/.test(rel) &&
!/\.(test|spec)(?:-fixture)?\.tsx?$/.test(rel)
)
}
// One read and one discovery per root per run; `--prune` then `main` reuse them.
@@ -241,5 +241,6 @@ describe('isScannedPath', () => {
expect(isScannedPath('src/renderer/src/a.tsx')).toBe(true)
expect(isScannedPath('src/renderer/src/a.test.ts')).toBe(false)
expect(isScannedPath('src/renderer/src/a.spec.tsx')).toBe(false)
expect(isScannedPath('src/renderer/src/a.test-fixture.ts')).toBe(false)
})
})
@@ -8,9 +8,6 @@ export const aggregateJob = aggregateWorkflow.jobs.verify
export const aggregateShell = aggregateJob.steps.find(
(step) => step.name === 'Require successful checks'
)
export const aggregateMarker = aggregateWorkflow.jobs.code_paths.steps.find(
(step) => step.id === 'verify_fast_path'
)
export const aggregateSelectors = PR_CHECK_JOBS.filter((job) => job !== 'orcad_browser')
export const aggregateRequiredJobs = aggregateJob.needs.filter((job) => job !== 'code_paths')
export const aggregateVariable = (job) => job.replaceAll('-', '_').toUpperCase()
@@ -47,7 +44,6 @@ export function aggregateEnvironment(needs) {
export function aggregateNeeds(selected = 'true', overrides = {}) {
const outputs = {
should_run: 'true',
verify_fast_path: '1',
...Object.fromEntries(aggregateSelectors.map((job) => [job, selected])),
...overrides
}
+24 -156
View File
@@ -1,36 +1,20 @@
import { describe, expect, it } from 'vitest'
import {
aggregateJob,
aggregateMarker,
aggregateNeeds,
aggregateRequiredJobs,
aggregateSelectors,
aggregateShell,
aggregateShouldRun,
aggregateVariable,
aggregateWorkflow,
compileAggregateExpression
} from './pr-aggregate-gate-context.mjs'
const results = ['success', 'failure', 'cancelled', 'skipped', '', 'unknown', undefined, null]
const malformedSelectors = [
'',
'TRUE',
'True',
'FALSE',
'0',
'1',
'null',
'unknown',
' true',
'true\n',
undefined,
null
]
const admitted = (needs, cancelled = false) =>
aggregateShouldRun({ needs }, cancelled).coerceString() === 'true'
describe('runner-free PR aggregate success', () => {
describe('explicit PR aggregate result', () => {
it('keeps every required dependency and the existing failure reporter', () => {
expect(aggregateJob.name).toBe('PR checks passed')
expect(aggregateJob.needs).toEqual([
@@ -43,160 +27,44 @@ describe('runner-free PR aggregate success', () => {
}
expect(aggregateJob['continue-on-error']).toBeUndefined()
expect(aggregateShell['continue-on-error']).toBeUndefined()
expect(aggregateJob.if).toMatch(/^!cancelled\(\) && !\(/)
expect(aggregateJob.if).toBe('${{ !cancelled() }}')
expect(aggregateShell.run).toContain('"$CODE_PATHS" != "success"')
const checked = [...aggregateShell.run.matchAll(/^check_job (\S+) /gm)].map((match) => match[1])
expect(checked).toEqual(aggregateRequiredJobs)
})
it('validates the same effective selectors the detector publishes, after readiness and classification', () => {
const detector = aggregateWorkflow.jobs.code_paths
expect(Object.keys(aggregateMarker.env).sort()).toEqual(
aggregateSelectors.map(aggregateVariable).sort()
)
for (const job of aggregateSelectors) {
expect(aggregateMarker.env[aggregateVariable(job)], job).toBe(detector.outputs[job])
}
expect(detector.outputs.verify_fast_path).toBe(
'${{ steps.verify_fast_path.outputs.canonical }}'
)
const position = detector.steps.indexOf(aggregateMarker)
for (const id of ['readiness', 'filter']) {
expect(position).toBeGreaterThan(detector.steps.findIndex((step) => step.id === id))
}
expect(aggregateMarker.if).toBeUndefined()
expect(aggregateMarker['continue-on-error']).toBeUndefined()
})
it('accepts every canonical routing combination without allocating the aggregate runner', () => {
expect(aggregateSelectors).toHaveLength(13)
const failures = []
for (let mask = 0; mask < 2 ** aggregateSelectors.length; mask += 1) {
const selectors = Object.fromEntries(
aggregateSelectors.map((job, index) => [job, mask & (1 << index) ? 'true' : 'false'])
)
if (admitted(aggregateNeeds('false', selectors))) {
failures.push(mask)
}
}
expect(failures).toEqual([])
})
it('requires each job to match its selected or unselected result', () => {
for (const job of aggregateRequiredJobs) {
for (const selected of ['true', 'false']) {
it('runs for code, docs, reused and mixed plans after every dependency outcome', () => {
for (const selected of ['true', 'false']) {
const plan = aggregateNeeds(selected)
expect(admitted(plan)).toBe(true)
for (const job of aggregateJob.needs) {
for (const result of results) {
const needs = aggregateNeeds(selected)
needs[job].result = result
const expected = selected === 'true' ? 'success' : 'skipped'
expect(admitted(needs), `${job}/${selected}/${result}`).toBe(result !== expected)
expect(admitted(needs), `${selected}/${job}/${result}`).toBe(true)
expect(admitted(needs, true), `${selected}/${job}/${result}`).toBe(false)
}
const missing = aggregateNeeds(selected)
delete missing[job]
expect(admitted(missing), `${selected}/${job}/missing`).toBe(true)
expect(admitted(missing, true), `${selected}/${job}/missing`).toBe(false)
}
}
expect(admitted(aggregateNeeds('false', { test: 'true', typecheck: 'true' }))).toBe(true)
expect(admitted({})).toBe(true)
expect(admitted({}, true)).toBe(false)
})
it('requires preflight success if either phase is selected', () => {
for (const static_analysis of ['true', 'false']) {
for (const typecheck of ['true', 'false']) {
for (const result of results) {
const needs = aggregateNeeds('false', { static_analysis, typecheck })
needs.preflight.result = result
const selected = static_analysis === 'true' || typecheck === 'true'
const expected = selected ? 'success' : 'skipped'
expect(admitted(needs), `${static_analysis}/${typecheck}/${result}`).toBe(
result !== expected
)
}
}
it('passes raw preflight selectors to the shell before case-insensitive Actions comparisons', () => {
const outputs = aggregateWorkflow.jobs.code_paths.outputs
for (const [variable, output] of [
['STATIC_ANALYSIS_SHOULD_RUN', 'static_analysis'],
['TYPECHECK_SHOULD_RUN', 'typecheck']
]) {
expect(aggregateShell.env[variable]).toBe(`\${{ needs.code_paths.outputs.${output} }}`)
expect(outputs[output]).toContain(`steps.filter.outputs.${output}`)
}
})
it('sends every malformed selector through the unchanged shell', () => {
for (const job of aggregateSelectors) {
for (const selected of malformedSelectors) {
for (const result of results) {
const needs = aggregateNeeds('false', { [job]: selected, verify_fast_path: '0' })
const affected = ['static_analysis', 'typecheck'].includes(job) ? 'preflight' : job
needs[affected].result = result
expect(admitted(needs), `${job}/${selected}/${result}`).toBe(true)
}
}
}
})
it('rejects outputs masked after the canonical-routing marker was emitted', () => {
for (const job of aggregateSelectors) {
for (const selected of ['true', 'false']) {
for (const masked of ['', undefined, null]) {
const needs = aggregateNeeds(selected)
needs.code_paths.outputs[job] = masked
expect(admitted(needs), `${job}/${selected}/${masked}`).toBe(true)
}
}
}
})
it('requires successful detection and a numeric canonical marker', () => {
for (const selected of ['true', 'false']) {
for (const result of results) {
const needs = aggregateNeeds(selected)
needs.code_paths.result = result
expect(admitted(needs), `${selected}/${result}`).toBe(result !== 'success')
}
for (const marker of ['', '0', 'true', 'TRUE', 'unknown', undefined, null]) {
expect(admitted(aggregateNeeds(selected, { verify_fast_path: marker }))).toBe(true)
}
const missing = aggregateNeeds(selected)
delete missing.code_paths.outputs
expect(admitted(missing)).toBe(true)
delete missing.code_paths
expect(admitted(missing)).toBe(true)
}
})
it('preserves workflow cancellation while detecting individually cancelled dependencies', () => {
for (const job of aggregateJob.needs) {
for (const result of results) {
const needs = aggregateNeeds()
needs[job].result = result
expect(admitted(needs, true), `${job}/${result}`).toBe(false)
if (result === 'cancelled') {
expect(admitted(needs, false), job).toBe(true)
}
}
const missing = aggregateNeeds()
delete missing[job]
expect(admitted(missing, false), job).toBe(true)
expect(admitted(missing, true), job).toBe(false)
}
})
it('keeps the informational overall selector independent of the gate', () => {
for (const should_run of ['true', 'false', ...malformedSelectors]) {
for (const selected of ['true', 'false']) {
expect(admitted(aggregateNeeds(selected, { should_run }))).toBe(false)
}
}
})
it('uses Actions case-insensitive equality and preserves readiness output masking', () => {
const compare = compileAggregateExpression("needs.value == 'true'")
expect(compare({ needs: { value: 'TRUE' } }).coerceString()).toBe('true')
for (const job of aggregateSelectors) {
const effective = compileAggregateExpression(aggregateMarker.env[aggregateVariable(job)], [
'steps'
])
for (const reused of ['true', 'TRUE', 'false', '', undefined]) {
for (const selected of ['true', 'false', ...malformedSelectors]) {
const context = {
steps: { readiness: { outputs: { reused } }, filter: { outputs: { [job]: selected } } }
}
const masked = reused?.toLowerCase() === 'true'
expect(effective(context).coerceString(), `${job}/${reused}/${selected}`).toBe(
masked ? 'false' : (selected ?? '')
)
}
}
}
})
})
+73 -125
View File
@@ -1,43 +1,16 @@
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { runProcess } from '@orca/process-host'
import {
aggregateEnvironment,
aggregateMarker,
aggregateNeeds,
aggregateRequiredJobs as requiredJobs,
aggregateSelectors,
aggregateShell as gate,
aggregateShouldRun,
aggregateVariable as variable
aggregateShell as gate
} from './pr-aggregate-gate-context.mjs'
function requiredResults(shouldRun) {
return Object.fromEntries(
requiredJobs.flatMap((job) => [
[variable(job), shouldRun ? 'success' : 'skipped'],
[`${variable(job)}_SHOULD_RUN`, String(shouldRun)]
])
)
}
async function verify(results) {
return runProcess({
program: 'bash',
args: ['-c', gate.run],
env: {
...process.env,
ORCA_BACKGROUND_LAUNCH: '1',
CODE_PATHS: 'success',
SHOULD_RUN: 'true',
...results
},
timeoutMs: 10_000
})
}
async function verifyCases(environments, script, env = {}) {
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`
const cases = environments.map((environment, index) => {
@@ -65,116 +38,91 @@ async function verifyCases(environments, script, env = {}) {
}
}
const malformedSelectors = [
'',
'TRUE',
'True',
'FALSE',
'0',
'1',
'null',
'unknown',
' true',
'true\n',
undefined,
null
]
const outcomes = ['success', 'skipped', 'failure', 'cancelled', '', 'unknown', undefined, null]
// The aggregate runs as Bash on Linux; Windows has no required Bash installation.
describe.skipIf(process.platform === 'win32')(
'readiness reuse through the real aggregate gate',
'required PR checks through the real aggregate shell',
() => {
it('accepts completed required checks or skips authorized by exact-source evidence', async () => {
expect((await verify(requiredResults(true))).code).toBe(0)
expect((await verify(requiredResults(false))).code).toBe(0)
})
it('rejects every missing, failed or cancelled required result when reuse is unavailable', async () => {
for (const job of requiredJobs) {
for (const result of ['', 'skipped', 'failure', 'cancelled']) {
const verdict = await verify({ ...requiredResults(true), [variable(job)]: result })
expect(verdict.code, `${job}: ${result}`).toBe(1)
it('accepts code, docs, reuse and mixed plans with their required outcomes', async () => {
const plans = [aggregateNeeds('true'), aggregateNeeds('false')]
for (const static_analysis of ['true', 'false']) {
for (const typecheck of ['true', 'false']) {
plans.push(aggregateNeeds('false', { static_analysis, typecheck }))
}
}
plans.push(aggregateNeeds('false', { test: 'true', package: 'true', typecheck: 'true' }))
for (const should_run of ['true', 'false', ...malformedSelectors]) {
plans.push(aggregateNeeds('true', { should_run }))
}
expect(await verifyCases(plans.map(aggregateEnvironment), gate.run)).toEqual(
plans.map(() => 0)
)
})
it('requires the detector to succeed even when every downstream job is skipped', async () => {
for (const result of ['', 'skipped', 'failure', 'cancelled']) {
expect((await verify({ ...requiredResults(false), CODE_PATHS: result })).code).toBe(1)
}
})
it('rejects unexpected downstream execution when the proven plan requires skips', async () => {
for (const job of requiredJobs) {
const verdict = await verify({ ...requiredResults(false), [variable(job)]: 'success' })
expect(verdict.code, job).toBe(1)
}
})
it('emits the numeric fast-path marker only for exact lowercase routing values', async () => {
const directory = mkdtempSync(join(tmpdir(), 'orca-aggregate-marker-'))
const output = join(directory, 'output')
const valid = Object.fromEntries(aggregateSelectors.map((job) => [variable(job), 'true']))
const cases = [
valid,
Object.fromEntries(aggregateSelectors.map((job) => [variable(job), 'false']))
]
const expected = ['canonical=1', 'canonical=1']
for (const job of aggregateSelectors) {
for (const value of [
'true',
'false',
'',
'TRUE',
'True',
'FALSE',
'0',
'1',
'null',
'unknown',
' true',
'true\n'
]) {
cases.push({ ...valid, [variable(job)]: value })
expected.push(value === 'true' || value === 'false' ? 'canonical=1' : 'canonical=0')
}
}
try {
expect(await verifyCases(cases, aggregateMarker.run, { GITHUB_OUTPUT: output })).toEqual(
cases.map(() => 0)
)
expect(readFileSync(output, 'utf8').trimEnd().split('\n')).toEqual(expected)
} finally {
rmSync(directory, { recursive: true, force: true })
}
})
it('matches the original shell for successful routing, all dependency mismatches and malformed selectors', async () => {
const cases = [aggregateNeeds('true'), aggregateNeeds('false')]
it('rejects missing, failed, cancelled or incorrectly skipped required results', async () => {
const cases = []
const expected = []
for (const selected of ['true', 'false']) {
for (const job of ['code_paths', ...requiredJobs]) {
for (const result of ['success', 'skipped', 'failure', 'cancelled', '', 'unknown']) {
for (const result of outcomes) {
const needs = aggregateNeeds(selected)
needs[job].result = result
cases.push(needs)
cases.push(aggregateEnvironment(needs))
const correct = job === 'code_paths' || selected === 'true' ? 'success' : 'skipped'
expected.push(result === correct ? 0 : 1)
}
const missing = aggregateNeeds(selected)
delete missing[job]
cases.push(aggregateEnvironment(missing))
expected.push(1)
}
}
expect(await verifyCases(cases, gate.run)).toEqual(expected)
})
it('rejects every malformed or masked raw selector even when downstream jobs were skipped', async () => {
const cases = []
for (const job of aggregateSelectors) {
for (const selected of malformedSelectors) {
cases.push(aggregateEnvironment(aggregateNeeds('false', { [job]: selected })))
const missing = aggregateNeeds('false')
delete missing.code_paths.outputs[job]
cases.push(aggregateEnvironment(missing))
}
}
expect(await verifyCases(cases, gate.run)).toEqual(cases.map(() => 1))
})
it('rejects malformed preflight phases even when the other phase selects preflight', async () => {
const cases = []
for (const phase of ['static_analysis', 'typecheck']) {
const other = phase === 'static_analysis' ? 'typecheck' : 'static_analysis'
for (const selected of malformedSelectors) {
for (const otherSelected of ['true', 'false']) {
for (const result of ['success', 'skipped']) {
const needs = aggregateNeeds('false', { [phase]: selected, [other]: otherSelected })
needs.preflight.result = result
cases.push(aggregateEnvironment(needs))
}
}
}
}
for (const job of aggregateSelectors) {
for (const selected of [
'',
'TRUE',
'FALSE',
'0',
'unknown',
' true',
'true\n',
undefined
]) {
const matching = aggregateNeeds('false', { [job]: selected, verify_fast_path: '0' })
cases.push(matching)
const mismatched = structuredClone(matching)
const affected = ['static_analysis', 'typecheck'].includes(job) ? 'preflight' : job
mismatched[affected].result =
matching[affected].result === 'success' ? 'skipped' : 'success'
cases.push(mismatched)
}
}
const results = await verifyCases(cases.map(aggregateEnvironment), gate.run)
expect(results).toHaveLength(cases.length)
for (const [index, needs] of cases.entries()) {
const runs = aggregateShouldRun({ needs }).coerceString() === 'true'
if (needs.code_paths.outputs.verify_fast_path === '0') {
expect(runs, `fallback case ${index}`).toBe(true)
} else {
expect(runs, `canonical case ${index}`).toBe(results[index] !== 0)
}
}
expect(await verifyCases(cases, gate.run)).toEqual(cases.map(() => 1))
})
}
)
+1 -1
View File
@@ -151,7 +151,7 @@ describe('ready-for-review required check reuse', () => {
expect(detector.steps.find((step) => step.id === 'e2e_filter').if).toBe(
"github.event.pull_request.draft != true && steps.filter.outputs.should_run == 'true'"
)
expect(workflow.jobs.verify.if).toMatch(/^!cancelled\(\) && !\(/)
expect(workflow.jobs.verify.if).toBe('${{ !cancelled() }}')
expect(workflow.jobs.verify.needs).toEqual([
'code_paths',
'preflight',
@@ -34,3 +34,34 @@ it('checks context names even when workflow-only changes skip root tests', () =>
expect(checkout.with['sparse-checkout'].split('\n')).toContain(path)
}
})
it('rejects aggregate conditions that hide a successful result', () => {
for (const condition of [undefined, 'success()', 'always()', '!cancelled() && !(true)']) {
const parseChanged = (source) => {
const workflow = parse(source)
if (workflow.jobs.verify?.name === 'PR checks passed') {
workflow.jobs.verify.if = condition
}
return workflow
}
expect(() => verifyRequiredPrContext(parseChanged), String(condition)).toThrow(
'Required PR aggregate must run unless the workflow is cancelled.'
)
}
})
it('rejects aggregate jobs and steps that ignore failures', () => {
for (const location of ['job', 'step']) {
const parseChanged = (source) => {
const workflow = parse(source)
if (workflow.jobs.verify?.name === 'PR checks passed') {
const target = location === 'job' ? workflow.jobs.verify : workflow.jobs.verify.steps[0]
target['continue-on-error'] = true
}
return workflow
}
expect(() => verifyRequiredPrContext(parseChanged), location).toThrow(
'Required PR aggregate must not ignore failures.'
)
}
})
@@ -13,6 +13,7 @@ import { createServer, type Server } from 'node:http'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { StringDecoder } from 'node:string_decoder'
import { stripAnsiEscapeSequences } from '../../shared/ansi-escape-sequences'
import { createCodexComposerReadyScanner } from '../../shared/codex-composer-ready-scanner'
import { createTerminalEscapeScanner } from '../../shared/terminal-escape-scanner'
import { promisify } from 'node:util'
@@ -500,14 +501,14 @@ describe.runIf(binary)('codex hook file-entry binary contract', { timeout: 180_0
model.close()
receiver.close()
}
const screen = readFileSync(out, 'utf-8').replace(/\s+/g, '')
const screen = stripAnsiEscapeSequences(readFileSync(out, 'utf-8')).replace(/\s+/g, '')
return { screen, posts }
}
})
// Why a pty: Codex's review screen only exists in the TUI, which `codex exec` never shows.
const PTY_DRIVER = `
import os, pty, re, sys, time, select, signal, struct, fcntl, termios
import os, pty, sys, time, select, signal, struct, fcntl, termios
out, prompt, cwd = sys.argv[1], sys.argv[2], sys.argv[3]
control, expected = sys.argv[4], sys.argv[5]
cmd = sys.argv[sys.argv.index('--') + 1:]
@@ -557,8 +558,6 @@ finally:
except ProcessLookupError: pass
finally:
waited_pid, status = os.waitpid(pid, 0)
buf = re.sub(rb'\\x1b\\[[0-9;?<>=]*[A-Za-z]', b'', buf)
buf = re.sub(rb'\\x1b\\][^\\x07]*\\x07', b'', buf)
open(out, 'wb').write(buf)
`
@@ -0,0 +1,347 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import type { OrchestrationFleetWorker } from '../../../../../../shared/orchestration-fleet-projection'
import { createRootDispatch } from '../../../../orchestration/db/root-dispatch-test-fixture'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
export function registerWorkerListRunScopeRpcTests(): void {
type WorkerListReceipt = { workers: { dispatchId: string; runId: string }[] }
/** The runtime half of the worker-list scope seam: the two RPC questions the CLI handler asks
* (`cli/handlers/orchestration/worker-list-run-scope.ts`) over a real OrchestrationDb. The CLI
* half lives beside the handler; the two cannot share one file across tsconfig projects. */
describe('orchestration worker-list Run scope (runtime)', () => {
const h = createOrchestrationWorkerReleaseHarness()
beforeEach(() => h.setup())
afterEach(() => h.cleanup())
function createDispatchInRun(runId: string, handle: string): string {
const task = h.db.createTask({ spec: `task for ${handle}`, runId })
return createRootDispatch(h.db, task.id, handle).id
}
function createOtherRun(): string {
return h.db.createRun({
objective: 'Another Run',
coordinatorHandle: 'term_other',
coordinatorPaneKey: 'tab_other:cccccccc-cccc-4ccc-8ccc-cccccccccccc'
}).id
}
it('resolves the bound Run from the coordinator handle and lists only its dispatches', async () => {
const boundDispatch = createDispatchInRun(h.activeRunId, 'term_bound')
const otherDispatch = createDispatchInRun(createOtherRun(), 'term_unbound')
const current = (await h.call('orchestration.runCurrent', { from: 'term_coord' })) as {
run: { id: string } | null
}
expect(current.run?.id).toBe(h.activeRunId)
const listed = (await h.call('orchestration.workerList', {
paginate: true,
run: current.run!.id
})) as WorkerListReceipt
expect(listed.workers.map((worker) => worker.dispatchId)).toEqual([boundDispatch])
expect(listed.workers.map((worker) => worker.dispatchId)).not.toContain(otherDispatch)
})
it('refuses runCurrent for an unbound handle, and an unscoped list spans every Run', async () => {
const boundDispatch = createDispatchInRun(h.activeRunId, 'term_bound')
const otherDispatch = createDispatchInRun(createOtherRun(), 'term_unbound')
// The CLI's catch turns this refusal into `scope.source = 'all'`.
await expect(
h.call('orchestration.runCurrent', { from: 'term_unbound_shell' })
).rejects.toThrow(/no stable pane identity/)
const listed = (await h.call('orchestration.workerList', {
paginate: true
})) as WorkerListReceipt
expect(listed.workers.map((worker) => worker.dispatchId).sort()).toEqual(
[boundDispatch, otherDispatch].sort()
)
})
})
// A plain orchestration.dispatch attempt has no worker_dispatches row, so the retry precondition
// used to reject it and its abandoned Task had no documented route back.
describe('worker-start --retry-of a context-only Dispatch', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
async function dispatchContextOnly(
spec: string
): Promise<{ taskId: string; dispatchId: string }> {
const task = harness.db.createTask({ spec, runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { id: string } }
expect(harness.db.getWorkerDispatch(result.dispatch.id)).toBeUndefined()
return { taskId: task.id, dispatchId: result.dispatch.id }
}
it('restarts the Task after the attempt is abandoned', async () => {
const { taskId, dispatchId } = await dispatchContextOnly('unsupervised attempt')
await expect(
harness.call('orchestration.workerAbandon', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'abandoned', alreadySettled: false })
expect(harness.db.getTask(taskId)?.status).toBe('blocked')
const retried = (await harness.call('orchestration.workerStart', {
task: taskId,
from: 'term_coord',
terminal: 'term_worker',
retryOf: dispatchId
})) as { dispatchId: string; state: string }
expect(retried.state).toBe('ready')
expect(harness.db.getDispatchContextById(retried.dispatchId)?.retry_of_dispatch_id).toBe(
dispatchId
)
expect(harness.db.getTask(taskId)?.status).toBe('dispatched')
})
it('still refuses to retry an attempt that has not settled', async () => {
const { taskId, dispatchId } = await dispatchContextOnly('live attempt')
await expect(
harness.call('orchestration.workerStart', {
task: taskId,
from: 'term_coord',
terminal: 'term_worker',
retryOf: dispatchId
})
).rejects.toMatchObject({ code: 'task_not_startable' })
})
})
// A coordinator that records context against its own terminal delegated nothing, so the row it
// leaves behind must not read back as the coordinator's own parent Attempt.
describe('context-only self-dispatch and nesting depth', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
async function selfDispatch(): Promise<string> {
const task = harness.db.createTask({ spec: 'self bookkeeping', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord'
})) as { dispatch: { id: string } }
return result.dispatch.id
}
it('leaves the coordinator able to start a worker', async () => {
const selfDispatchId = await selfDispatch()
expect(harness.db.getDispatchContextById(selfDispatchId)).toMatchObject({
creator_handle: 'term_coord',
creator_pane_key: harness.coordinatorPaneKey
})
const started = await harness.startWorker({ terminal: 'term_worker' })
expect(harness.db.getDispatchContextById(started.dispatchId)).toMatchObject({
depth: 1,
creator_dispatch_id: null
})
})
it('still counts a real assignment to another pane as a nesting parent', async () => {
const task = harness.db.createTask({ spec: 'real delegation', runId: harness.activeRunId })
const delegated = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { id: string } }
expect(
harness.db.resolveCreatorDepth({
kind: 'terminal',
handle: 'term_worker',
paneKey: harness.workerPaneKey
})
).toBe(1)
expect(
harness.db.resolveCreatorDispatchId({
kind: 'terminal',
handle: 'term_worker',
paneKey: harness.workerPaneKey
})
).toBe(delegated.dispatch.id)
})
it('reports the self-dispatching coordinator as a root', async () => {
await selfDispatch()
const creator = {
kind: 'terminal',
handle: 'term_coord',
paneKey: harness.coordinatorPaneKey
} as const
expect(harness.db.resolveCreatorDepth(creator)).toBe(0)
expect(harness.db.resolveCreatorDispatchId(creator)).toBeNull()
})
})
type ReadWithProjection = { projection?: OrchestrationFleetWorker | null }
describe('orchestration worker-read fleet projection', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('publishes the fleet agent verdict beside the PTY verdict on a live read', async () => {
h.setup()
const { dispatchId } = await h.startWorker()
const read = (await h.call('orchestration.workerRead', {
dispatch: dispatchId
})) as ReadWithProjection & { status: { liveness?: string } }
expect(read.projection?.dispatchId).toBe(dispatchId)
// The agent verdict is not the PTY verdict; worker-read must carry both.
expect(read.status.liveness).toBe('live')
expect(read.projection?.liveness.verdict).toBe('unverifiable')
})
it('carries the projection on an archived read after release', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
const read = (await h.call('orchestration.workerRead', {
dispatch: dispatchId
})) as ReadWithProjection
expect(read.projection?.dispatchId).toBe(dispatchId)
expect(read.projection?.liveness.verdict).toBe('exited')
})
})
type ListedWorker = {
dispatchId: string
workerState: string
dispatchStatus: string
projection: {
outcome: string
liveness: { verdict: string; reason?: string }
nextAction: { kind: string; argv: string[] }
attention: { categories: string[]; requiresAction: boolean }
}
}
describe('pre-v3 dispatch rows in worker-list', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
/** A pre-v3 dispatch: a real dispatch_contexts row settled through the real lifecycle with no
* worker_dispatches row, which is what every dispatch made before supervised workers looks like. */
function createLegacyDispatch(status: 'completed' | 'failed' | 'dispatched'): string {
const task = h.db.createTask({ spec: `legacy ${status} task`, runId: h.activeRunId })
const dispatch = createRootDispatch(h.db, task.id, `term_legacy_${status}`)
if (status === 'completed') {
h.db.completeDispatch(dispatch.id)
}
if (status === 'failed') {
h.db.failDispatch(dispatch.id, 'legacy failure')
}
return dispatch.id
}
async function listWorkers(): Promise<Map<string, ListedWorker>> {
const listed = (await h.call('orchestration.workerList', {
paginate: true,
run: h.activeRunId
})) as { workers: ListedWorker[] }
return new Map(listed.workers.map((worker) => [worker.dispatchId, worker]))
}
it('projects a settled legacy dispatch as settled with nothing to act on', async () => {
h.setup()
const completed = createLegacyDispatch('completed')
const worker = (await listWorkers()).get(completed)!
expect(worker.workerState).toBe('unsupervised')
expect(worker.dispatchStatus).toBe('completed')
// `dispatch_contexts.status = 'completed'` is only written from an accepted `succeeded`
// report or a task completion, so the durable record is the whole settlement.
expect(worker.projection.outcome).toBe('succeeded')
// Absence is not a death certificate, so the verdict stays unverifiable — but a dispatch
// that never had a worker row has no process whose absence could require action.
expect(worker.projection.liveness).toEqual({
verdict: 'unverifiable',
reason: 'unsupervised_settled'
})
expect(worker.projection.attention.categories).not.toContain('unverifiable')
expect(worker.projection.attention.requiresAction).toBe(false)
expect(worker.projection.nextAction.kind).toBe('none')
})
it.each(['completed', 'failed'] as const)(
'closes a pending question when a legacy dispatch settles as %s',
async (status) => {
h.setup()
const task = h.db.createTask({
spec: `legacy ${status} with question`,
runId: h.activeRunId
})
const dispatch = createRootDispatch(h.db, task.id, `term_legacy_q_${status}`)
const asked = h.db.createQuestion({
runId: h.activeRunId,
dispatchId: dispatch.id,
askerHandle: `term_legacy_q_${status}`,
question: 'Which branch?'
})
// Both settlement paths a pre-v3 dispatch can take: the task-status path and failDispatch.
if (status === 'completed') {
h.db.updateTaskStatus(task.id, 'completed', 'done')
} else {
h.db.failDispatch(dispatch.id, 'legacy failure')
}
const worker = (await listWorkers()).get(dispatch.id)!
expect(h.db.getQuestion(asked.question.message_id)?.status).toBe('closed')
expect(worker.dispatchStatus).toBe(status)
expect(worker.projection.attention.categories).not.toContain('input')
// Nothing can answer a question on a settled Dispatch, so `input` must not outlive it.
expect(worker.projection.attention.requiresAction).toBe(status === 'failed')
}
)
it('keeps a legacy failed dispatch actionable on the failure, not on absence', async () => {
h.setup()
const failed = createLegacyDispatch('failed')
const worker = (await listWorkers()).get(failed)!
expect(worker.dispatchStatus).toBe('failed')
expect(worker.projection.outcome).toBe('failed')
expect(worker.projection.attention.categories).toEqual(['failure'])
expect(worker.projection.attention.requiresAction).toBe(true)
})
it('leaves an unsettled legacy dispatch genuinely unknown', async () => {
h.setup()
const dispatched = createLegacyDispatch('dispatched')
const worker = (await listWorkers()).get(dispatched)!
expect(worker.projection.outcome).toBe('in_progress')
expect(worker.projection.liveness).toEqual({
verdict: 'unverifiable',
reason: 'missing_status'
})
expect(worker.projection.attention.categories).toContain('unverifiable')
expect(worker.projection.attention.requiresAction).toBe(true)
expect(worker.projection.nextAction).toEqual({ kind: 'none', argv: [] })
})
})
}
@@ -1,342 +0,0 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import type { OrchestrationFleetWorker } from '../../../../../../shared/orchestration-fleet-projection'
import { createRootDispatch } from '../../../../orchestration/db/root-dispatch-test-fixture'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
type WorkerListReceipt = { workers: { dispatchId: string; runId: string }[] }
/** The runtime half of the worker-list scope seam: the two RPC questions the CLI handler asks
* (`cli/handlers/orchestration/worker-list-run-scope.ts`) over a real OrchestrationDb. The CLI
* half lives beside the handler; the two cannot share one file across tsconfig projects. */
describe('orchestration worker-list Run scope (runtime)', () => {
const h = createOrchestrationWorkerReleaseHarness()
beforeEach(() => h.setup())
afterEach(() => h.cleanup())
function createDispatchInRun(runId: string, handle: string): string {
const task = h.db.createTask({ spec: `task for ${handle}`, runId })
return createRootDispatch(h.db, task.id, handle).id
}
function createOtherRun(): string {
return h.db.createRun({
objective: 'Another Run',
coordinatorHandle: 'term_other',
coordinatorPaneKey: 'tab_other:cccccccc-cccc-4ccc-8ccc-cccccccccccc'
}).id
}
it('resolves the bound Run from the coordinator handle and lists only its dispatches', async () => {
const boundDispatch = createDispatchInRun(h.activeRunId, 'term_bound')
const otherDispatch = createDispatchInRun(createOtherRun(), 'term_unbound')
const current = (await h.call('orchestration.runCurrent', { from: 'term_coord' })) as {
run: { id: string } | null
}
expect(current.run?.id).toBe(h.activeRunId)
const listed = (await h.call('orchestration.workerList', {
paginate: true,
run: current.run!.id
})) as WorkerListReceipt
expect(listed.workers.map((worker) => worker.dispatchId)).toEqual([boundDispatch])
expect(listed.workers.map((worker) => worker.dispatchId)).not.toContain(otherDispatch)
})
it('refuses runCurrent for an unbound handle, and an unscoped list spans every Run', async () => {
const boundDispatch = createDispatchInRun(h.activeRunId, 'term_bound')
const otherDispatch = createDispatchInRun(createOtherRun(), 'term_unbound')
// The CLI's catch turns this refusal into `scope.source = 'all'`.
await expect(
h.call('orchestration.runCurrent', { from: 'term_unbound_shell' })
).rejects.toThrow(/no stable pane identity/)
const listed = (await h.call('orchestration.workerList', {
paginate: true
})) as WorkerListReceipt
expect(listed.workers.map((worker) => worker.dispatchId).sort()).toEqual(
[boundDispatch, otherDispatch].sort()
)
})
})
// A plain orchestration.dispatch attempt has no worker_dispatches row, so the retry precondition
// used to reject it and its abandoned Task had no documented route back.
describe('worker-start --retry-of a context-only Dispatch', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
async function dispatchContextOnly(
spec: string
): Promise<{ taskId: string; dispatchId: string }> {
const task = harness.db.createTask({ spec, runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { id: string } }
expect(harness.db.getWorkerDispatch(result.dispatch.id)).toBeUndefined()
return { taskId: task.id, dispatchId: result.dispatch.id }
}
it('restarts the Task after the attempt is abandoned', async () => {
const { taskId, dispatchId } = await dispatchContextOnly('unsupervised attempt')
await expect(
harness.call('orchestration.workerAbandon', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'abandoned', alreadySettled: false })
expect(harness.db.getTask(taskId)?.status).toBe('blocked')
const retried = (await harness.call('orchestration.workerStart', {
task: taskId,
from: 'term_coord',
terminal: 'term_worker',
retryOf: dispatchId
})) as { dispatchId: string; state: string }
expect(retried.state).toBe('ready')
expect(harness.db.getDispatchContextById(retried.dispatchId)?.retry_of_dispatch_id).toBe(
dispatchId
)
expect(harness.db.getTask(taskId)?.status).toBe('dispatched')
})
it('still refuses to retry an attempt that has not settled', async () => {
const { taskId, dispatchId } = await dispatchContextOnly('live attempt')
await expect(
harness.call('orchestration.workerStart', {
task: taskId,
from: 'term_coord',
terminal: 'term_worker',
retryOf: dispatchId
})
).rejects.toMatchObject({ code: 'task_not_startable' })
})
})
// A coordinator that records context against its own terminal delegated nothing, so the row it
// leaves behind must not read back as the coordinator's own parent Attempt.
describe('context-only self-dispatch and nesting depth', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
async function selfDispatch(): Promise<string> {
const task = harness.db.createTask({ spec: 'self bookkeeping', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord'
})) as { dispatch: { id: string } }
return result.dispatch.id
}
it('leaves the coordinator able to start a worker', async () => {
const selfDispatchId = await selfDispatch()
expect(harness.db.getDispatchContextById(selfDispatchId)).toMatchObject({
creator_handle: 'term_coord',
creator_pane_key: harness.coordinatorPaneKey
})
const started = await harness.startWorker({ terminal: 'term_worker' })
expect(harness.db.getDispatchContextById(started.dispatchId)).toMatchObject({
depth: 1,
creator_dispatch_id: null
})
})
it('still counts a real assignment to another pane as a nesting parent', async () => {
const task = harness.db.createTask({ spec: 'real delegation', runId: harness.activeRunId })
const delegated = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { id: string } }
expect(
harness.db.resolveCreatorDepth({
kind: 'terminal',
handle: 'term_worker',
paneKey: harness.workerPaneKey
})
).toBe(1)
expect(
harness.db.resolveCreatorDispatchId({
kind: 'terminal',
handle: 'term_worker',
paneKey: harness.workerPaneKey
})
).toBe(delegated.dispatch.id)
})
it('reports the self-dispatching coordinator as a root', async () => {
await selfDispatch()
const creator = {
kind: 'terminal',
handle: 'term_coord',
paneKey: harness.coordinatorPaneKey
} as const
expect(harness.db.resolveCreatorDepth(creator)).toBe(0)
expect(harness.db.resolveCreatorDispatchId(creator)).toBeNull()
})
})
type ReadWithProjection = { projection?: OrchestrationFleetWorker | null }
describe('orchestration worker-read fleet projection', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('publishes the fleet agent verdict beside the PTY verdict on a live read', async () => {
h.setup()
const { dispatchId } = await h.startWorker()
const read = (await h.call('orchestration.workerRead', {
dispatch: dispatchId
})) as ReadWithProjection & { status: { liveness?: string } }
expect(read.projection?.dispatchId).toBe(dispatchId)
// The agent verdict is not the PTY verdict; worker-read must carry both.
expect(read.status.liveness).toBe('live')
expect(read.projection?.liveness.verdict).toBe('unverifiable')
})
it('carries the projection on an archived read after release', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
const read = (await h.call('orchestration.workerRead', {
dispatch: dispatchId
})) as ReadWithProjection
expect(read.projection?.dispatchId).toBe(dispatchId)
expect(read.projection?.liveness.verdict).toBe('exited')
})
})
type ListedWorker = {
dispatchId: string
workerState: string
dispatchStatus: string
projection: {
outcome: string
liveness: { verdict: string; reason?: string }
nextAction: { kind: string; argv: string[] }
attention: { categories: string[]; requiresAction: boolean }
}
}
describe('pre-v3 dispatch rows in worker-list', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
/** A pre-v3 dispatch: a real dispatch_contexts row settled through the real lifecycle with no
* worker_dispatches row, which is what every dispatch made before supervised workers looks like. */
function createLegacyDispatch(status: 'completed' | 'failed' | 'dispatched'): string {
const task = h.db.createTask({ spec: `legacy ${status} task`, runId: h.activeRunId })
const dispatch = createRootDispatch(h.db, task.id, `term_legacy_${status}`)
if (status === 'completed') {
h.db.completeDispatch(dispatch.id)
}
if (status === 'failed') {
h.db.failDispatch(dispatch.id, 'legacy failure')
}
return dispatch.id
}
async function listWorkers(): Promise<Map<string, ListedWorker>> {
const listed = (await h.call('orchestration.workerList', {
paginate: true,
run: h.activeRunId
})) as { workers: ListedWorker[] }
return new Map(listed.workers.map((worker) => [worker.dispatchId, worker]))
}
it('projects a settled legacy dispatch as settled with nothing to act on', async () => {
h.setup()
const completed = createLegacyDispatch('completed')
const worker = (await listWorkers()).get(completed)!
expect(worker.workerState).toBe('unsupervised')
expect(worker.dispatchStatus).toBe('completed')
// `dispatch_contexts.status = 'completed'` is only written from an accepted `succeeded`
// report or a task completion, so the durable record is the whole settlement.
expect(worker.projection.outcome).toBe('succeeded')
// Absence is not a death certificate, so the verdict stays unverifiable — but a dispatch
// that never had a worker row has no process whose absence could require action.
expect(worker.projection.liveness).toEqual({
verdict: 'unverifiable',
reason: 'unsupervised_settled'
})
expect(worker.projection.attention.categories).not.toContain('unverifiable')
expect(worker.projection.attention.requiresAction).toBe(false)
expect(worker.projection.nextAction.kind).toBe('none')
})
it.each(['completed', 'failed'] as const)(
'closes a pending question when a legacy dispatch settles as %s',
async (status) => {
h.setup()
const task = h.db.createTask({ spec: `legacy ${status} with question`, runId: h.activeRunId })
const dispatch = createRootDispatch(h.db, task.id, `term_legacy_q_${status}`)
const asked = h.db.createQuestion({
runId: h.activeRunId,
dispatchId: dispatch.id,
askerHandle: `term_legacy_q_${status}`,
question: 'Which branch?'
})
// Both settlement paths a pre-v3 dispatch can take: the task-status path and failDispatch.
if (status === 'completed') {
h.db.updateTaskStatus(task.id, 'completed', 'done')
} else {
h.db.failDispatch(dispatch.id, 'legacy failure')
}
const worker = (await listWorkers()).get(dispatch.id)!
expect(h.db.getQuestion(asked.question.message_id)?.status).toBe('closed')
expect(worker.dispatchStatus).toBe(status)
expect(worker.projection.attention.categories).not.toContain('input')
// Nothing can answer a question on a settled Dispatch, so `input` must not outlive it.
expect(worker.projection.attention.requiresAction).toBe(status === 'failed')
}
)
it('keeps a legacy failed dispatch actionable on the failure, not on absence', async () => {
h.setup()
const failed = createLegacyDispatch('failed')
const worker = (await listWorkers()).get(failed)!
expect(worker.dispatchStatus).toBe('failed')
expect(worker.projection.outcome).toBe('failed')
expect(worker.projection.attention.categories).toEqual(['failure'])
expect(worker.projection.attention.requiresAction).toBe(true)
})
it('leaves an unsettled legacy dispatch genuinely unknown', async () => {
h.setup()
const dispatched = createLegacyDispatch('dispatched')
const worker = (await listWorkers()).get(dispatched)!
expect(worker.projection.outcome).toBe('in_progress')
expect(worker.projection.liveness).toEqual({
verdict: 'unverifiable',
reason: 'missing_status'
})
expect(worker.projection.attention.categories).toContain('unverifiable')
expect(worker.projection.attention.requiresAction).toBe(true)
expect(worker.projection.nextAction).toEqual({ kind: 'none', argv: [] })
})
})
@@ -0,0 +1,249 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
export function registerWorkerReleaseArchiveTests(): void {
function codexMessage(id: string, text: string): string {
return JSON.stringify({
timestamp: '2026-08-03T12:00:00.000Z',
type: 'event_msg',
payload: { id, type: 'agent_message', message: text }
})
}
describe('orchestration worker release archive', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('records an explicitly empty archive for an already-exited worker process', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
vi.mocked(h.runtime.showTerminal).mockImplementation(
async (handle) => ({ handle, worktreeId: 'repo::worktree', connected: false }) as never
)
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'exited',
tail: [],
truncated: false,
nextCursor: null
})
const receipt = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
state: string
processAction: string
archive: { status: string | null } | null
}
expect(receipt).toMatchObject({
state: 'released',
processAction: 'closed_exited_terminal',
archive: { status: 'empty' }
})
})
it('keeps a bounded tail when one terminal line exceeds the archive budget', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const suffix = 'meaningful-tail'
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'running',
tail: [`${'x'.repeat(300_000)}${suffix}`],
truncated: false,
nextCursor: '1'
})
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
archive: { status: string | null } | null
}
const read = (await h.call('orchestration.workerRead', { dispatch: dispatchId })) as {
terminal: { tail: string[]; truncated: boolean }
warnings: string[]
}
expect(release.archive?.status).toBe('captured')
expect(read.terminal.tail).toHaveLength(1)
expect(read.terminal.tail[0]).toMatch(new RegExp(`${suffix}$`))
expect(read.terminal.truncated).toBe(true)
expect(read.warnings).not.toContain(
'The live terminal buffer was empty at release; structured transcript output was unavailable.'
)
})
it('serves the frozen redacted archive through worker-read after release, with cursors', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'running',
tail: ['first line', `capability dcap_${'a'.repeat(24)} leaked`, 'last line'],
draft: `send --dispatch-capability dcap_${'b'.repeat(24)}`,
truncated: false,
nextCursor: '3'
})
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
vi.mocked(h.runtime.readTerminal).mockClear()
const page1 = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
limit: 2
})) as {
archived?: boolean
terminal: { tail: string[]; draft?: string }
cursor: string | null
}
expect(page1.terminal.tail).toEqual([
'first line',
'capability [dispatch capability redacted] leaked'
])
expect(page1.terminal.draft).toBe('send --dispatch-capability [dispatch capability redacted]')
expect(page1.cursor).not.toBeNull()
const page2 = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
cursor: page1.cursor as string
})) as { terminal: { tail: string[]; draft?: string }; cursor: string | null }
expect(page2.terminal.tail).toEqual(['last line'])
expect(page2.terminal.draft).toBeUndefined()
expect(page2.cursor).toBeNull()
// The live terminal is never consulted after release.
expect(h.runtime.readTerminal).not.toHaveBeenCalled()
})
it('reads an immutable transcript snapshot after the provider file disappears', async () => {
h.setup()
const directory = await mkdtemp(join(tmpdir(), 'orca-worker-release-snapshot-'))
const transcriptPath = join(directory, 'rollout.jsonl')
try {
await writeFile(
transcriptPath,
`${codexMessage('snapshot-one', 'frozen first')}\n${codexMessage('snapshot-two', 'frozen second')}\n`
)
vi.mocked(h.runtime.getExactWorkerProviderSession).mockReturnValue({
agent: 'codex',
processIncarnation: 'runtime_test:term_worker:1',
providerSession: {
key: 'codex:snapshot-session',
id: 'snapshot-session',
transcriptPath
}
} as never)
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
await rm(transcriptPath)
const page = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
limit: 1
})) as { cursor: string }
expect(page).toMatchObject({
archived: true,
source: 'transcript',
transcript: {
messages: [{ id: 'snapshot-one', blocks: [{ type: 'text', text: 'frozen first' }] }]
}
})
await expect(
h.call('orchestration.workerRead', { dispatch: dispatchId, cursor: page.cursor })
).resolves.toMatchObject({
transcript: {
messages: [{ id: 'snapshot-two', blocks: [{ type: 'text', text: 'frozen second' }] }]
}
})
} finally {
await rm(directory, { recursive: true, force: true })
}
})
it('preserves payload clipping metadata in the released transcript snapshot', async () => {
h.setup()
const directory = await mkdtemp(join(tmpdir(), 'orca-worker-release-clipped-snapshot-'))
const transcriptPath = join(directory, 'rollout.jsonl')
try {
await writeFile(
transcriptPath,
`${JSON.stringify({
timestamp: '2026-08-03T12:00:00.000Z',
type: 'event_msg',
payload: { id: 'clipped-message', type: 'agent_message', message: 'x'.repeat(5_000) }
})}\n`
)
vi.mocked(h.runtime.getExactWorkerProviderSession).mockReturnValue({
agent: 'codex',
processIncarnation: 'runtime_test:term_worker:1',
providerSession: {
key: 'codex:clipped-session',
id: 'clipped-session',
transcriptPath
}
} as never)
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
const read = (await h.call('orchestration.workerRead', { dispatch: dispatchId })) as {
transcript: { limited: boolean }
cursor: string
contentComplete: boolean
clipping: string[]
warnings: string[]
}
expect(read).toMatchObject({
transcript: { limited: true },
contentComplete: false,
clipping: ['transcript_payload'],
warnings: ['Oversized transcript text was clipped.']
})
expect(read.cursor).toMatch(/^owr1_/)
expect(read.warnings).not.toContain(
'Older transcript messages were omitted from the bounded archive.'
)
} finally {
await rm(directory, { recursive: true, force: true })
}
})
it('rejects a legacy live-terminal cursor after output moves to the archive', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
await expect(
h.call('orchestration.workerRead', { dispatch: dispatchId, cursor: 1 })
).rejects.toThrow(/source changed/i)
})
it('recovers archive metadata when a prior attempt committed only the archive row', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const requested = h.db.requestWorkerTerminalRelease(dispatchId)
expect(requested.disposition).toBe('requested')
if (requested.disposition !== 'requested') {
throw new Error('release request was not recorded')
}
h.db.storeWorkerTerminalArchive({
dispatchId,
resourceId: requested.resource.id,
kind: 'terminal_tail',
content: JSON.stringify({
lines: ['archive survived the interrupted attempt'],
truncated: false,
terminalStatus: 'running',
warnings: []
})
})
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
archive: { source: string | null; status: string | null } | null
}
expect(release.archive).toEqual({ source: 'terminal', status: 'captured' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
archive_source: 'terminal',
archive_status: 'captured'
})
})
})
}
@@ -1,247 +0,0 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
function codexMessage(id: string, text: string): string {
return JSON.stringify({
timestamp: '2026-08-03T12:00:00.000Z',
type: 'event_msg',
payload: { id, type: 'agent_message', message: text }
})
}
describe('orchestration worker release archive', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('records an explicitly empty archive for an already-exited worker process', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
vi.mocked(h.runtime.showTerminal).mockImplementation(
async (handle) => ({ handle, worktreeId: 'repo::worktree', connected: false }) as never
)
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'exited',
tail: [],
truncated: false,
nextCursor: null
})
const receipt = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
state: string
processAction: string
archive: { status: string | null } | null
}
expect(receipt).toMatchObject({
state: 'released',
processAction: 'closed_exited_terminal',
archive: { status: 'empty' }
})
})
it('keeps a bounded tail when one terminal line exceeds the archive budget', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const suffix = 'meaningful-tail'
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'running',
tail: [`${'x'.repeat(300_000)}${suffix}`],
truncated: false,
nextCursor: '1'
})
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
archive: { status: string | null } | null
}
const read = (await h.call('orchestration.workerRead', { dispatch: dispatchId })) as {
terminal: { tail: string[]; truncated: boolean }
warnings: string[]
}
expect(release.archive?.status).toBe('captured')
expect(read.terminal.tail).toHaveLength(1)
expect(read.terminal.tail[0]).toMatch(new RegExp(`${suffix}$`))
expect(read.terminal.truncated).toBe(true)
expect(read.warnings).not.toContain(
'The live terminal buffer was empty at release; structured transcript output was unavailable.'
)
})
it('serves the frozen redacted archive through worker-read after release, with cursors', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
vi.mocked(h.runtime.readTerminal).mockResolvedValue({
handle: 'term_worker',
status: 'running',
tail: ['first line', `capability dcap_${'a'.repeat(24)} leaked`, 'last line'],
draft: `send --dispatch-capability dcap_${'b'.repeat(24)}`,
truncated: false,
nextCursor: '3'
})
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
vi.mocked(h.runtime.readTerminal).mockClear()
const page1 = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
limit: 2
})) as {
archived?: boolean
terminal: { tail: string[]; draft?: string }
cursor: string | null
}
expect(page1.terminal.tail).toEqual([
'first line',
'capability [dispatch capability redacted] leaked'
])
expect(page1.terminal.draft).toBe('send --dispatch-capability [dispatch capability redacted]')
expect(page1.cursor).not.toBeNull()
const page2 = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
cursor: page1.cursor as string
})) as { terminal: { tail: string[]; draft?: string }; cursor: string | null }
expect(page2.terminal.tail).toEqual(['last line'])
expect(page2.terminal.draft).toBeUndefined()
expect(page2.cursor).toBeNull()
// The live terminal is never consulted after release.
expect(h.runtime.readTerminal).not.toHaveBeenCalled()
})
it('reads an immutable transcript snapshot after the provider file disappears', async () => {
h.setup()
const directory = await mkdtemp(join(tmpdir(), 'orca-worker-release-snapshot-'))
const transcriptPath = join(directory, 'rollout.jsonl')
try {
await writeFile(
transcriptPath,
`${codexMessage('snapshot-one', 'frozen first')}\n${codexMessage('snapshot-two', 'frozen second')}\n`
)
vi.mocked(h.runtime.getExactWorkerProviderSession).mockReturnValue({
agent: 'codex',
processIncarnation: 'runtime_test:term_worker:1',
providerSession: {
key: 'codex:snapshot-session',
id: 'snapshot-session',
transcriptPath
}
} as never)
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
await rm(transcriptPath)
const page = (await h.call('orchestration.workerRead', {
dispatch: dispatchId,
limit: 1
})) as { cursor: string }
expect(page).toMatchObject({
archived: true,
source: 'transcript',
transcript: {
messages: [{ id: 'snapshot-one', blocks: [{ type: 'text', text: 'frozen first' }] }]
}
})
await expect(
h.call('orchestration.workerRead', { dispatch: dispatchId, cursor: page.cursor })
).resolves.toMatchObject({
transcript: {
messages: [{ id: 'snapshot-two', blocks: [{ type: 'text', text: 'frozen second' }] }]
}
})
} finally {
await rm(directory, { recursive: true, force: true })
}
})
it('preserves payload clipping metadata in the released transcript snapshot', async () => {
h.setup()
const directory = await mkdtemp(join(tmpdir(), 'orca-worker-release-clipped-snapshot-'))
const transcriptPath = join(directory, 'rollout.jsonl')
try {
await writeFile(
transcriptPath,
`${JSON.stringify({
timestamp: '2026-08-03T12:00:00.000Z',
type: 'event_msg',
payload: { id: 'clipped-message', type: 'agent_message', message: 'x'.repeat(5_000) }
})}\n`
)
vi.mocked(h.runtime.getExactWorkerProviderSession).mockReturnValue({
agent: 'codex',
processIncarnation: 'runtime_test:term_worker:1',
providerSession: {
key: 'codex:clipped-session',
id: 'clipped-session',
transcriptPath
}
} as never)
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
const read = (await h.call('orchestration.workerRead', { dispatch: dispatchId })) as {
transcript: { limited: boolean }
cursor: string
contentComplete: boolean
clipping: string[]
warnings: string[]
}
expect(read).toMatchObject({
transcript: { limited: true },
contentComplete: false,
clipping: ['transcript_payload'],
warnings: ['Oversized transcript text was clipped.']
})
expect(read.cursor).toMatch(/^owr1_/)
expect(read.warnings).not.toContain(
'Older transcript messages were omitted from the bounded archive.'
)
} finally {
await rm(directory, { recursive: true, force: true })
}
})
it('rejects a legacy live-terminal cursor after output moves to the archive', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
await h.call('orchestration.workerRelease', { dispatch: dispatchId })
await expect(
h.call('orchestration.workerRead', { dispatch: dispatchId, cursor: 1 })
).rejects.toThrow(/source changed/i)
})
it('recovers archive metadata when a prior attempt committed only the archive row', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const requested = h.db.requestWorkerTerminalRelease(dispatchId)
expect(requested.disposition).toBe('requested')
if (requested.disposition !== 'requested') {
throw new Error('release request was not recorded')
}
h.db.storeWorkerTerminalArchive({
dispatchId,
resourceId: requested.resource.id,
kind: 'terminal_tail',
content: JSON.stringify({
lines: ['archive survived the interrupted attempt'],
truncated: false,
terminalStatus: 'running',
warnings: []
})
})
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
archive: { source: string | null; status: string | null } | null
}
expect(release.archive).toEqual({ source: 'terminal', status: 'captured' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
archive_source: 'terminal',
archive_status: 'captured'
})
})
})
@@ -0,0 +1,5 @@
import { registerWorkerReleaseOwnershipGuardTests } from './worker-release-ownership-guard.spec'
import { registerWorkerReleaseRuntimeIncarnationTests } from './worker-release-runtime-incarnation.spec'
registerWorkerReleaseOwnershipGuardTests()
registerWorkerReleaseRuntimeIncarnationTests()
@@ -0,0 +1,5 @@
import { registerWorkerReleaseArchiveTests } from './worker-release-archive.spec'
import { registerWorkerReleaseInventoryTests } from './worker-release-inventory.spec'
registerWorkerReleaseArchiveTests()
registerWorkerReleaseInventoryTests()
@@ -0,0 +1,194 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
export function registerWorkerReleaseInventoryTests(): void {
describe('orchestration worker release inventory', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('transfers ownership on exact reuse and fences release through the old Dispatch', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
const originalResource = h.db.getWorkerTerminalResourceByOwner(first.dispatchId)
expect(originalResource?.ownership_state).toBe('owned')
const second = await h.startWorker({ terminal: 'term_reminted' })
const transferred = h.db.getWorkerTerminalResourceByOwner(second.dispatchId)
expect(transferred?.id).toBe(originalResource?.id)
expect(transferred?.terminal_handle).toBe('term_reminted')
expect(h.db.getWorkerTerminalResourceByOwner(first.dispatchId)).toBeUndefined()
h.inspectProcessLiveness.mockResolvedValueOnce('exited')
const oldRelease = (await h.call('orchestration.workerRelease', {
dispatch: first.dispatchId
})) as { state: string; reason?: string }
expect(oldRelease).toMatchObject({ state: 'retained', reason: 'ownership_transferred' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
h.settle(second.taskId, second.dispatchId, 'succeeded')
const newRelease = (await h.call('orchestration.workerRelease', {
dispatch: second.dispatchId
})) as { state: string }
expect(newRelease.state).toBe('released')
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(1)
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
})
it('refuses to settle dead transferred ownership with no durable archive', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
const second = await h.startWorker({ terminal: 'term_reminted' })
h.settle(second.taskId, second.dispatchId, 'succeeded')
h.inspectProcessLiveness.mockResolvedValue('exited')
await expect(
h.call('orchestration.workerRelease', { dispatch: first.dispatchId })
).resolves.toMatchObject({
state: 'retained',
reason: 'ownership_transferred',
processAction: 'none'
})
expect(h.inspectProcessLiveness).toHaveBeenCalledWith(
'runtime_test:term_worker:1',
JSON.stringify({ kind: 'local', hostId: 'local' })
)
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
expect(h.db.getWorkerTerminalResourceByOwner(second.dispatchId)?.release_state).not.toBe(
'released'
)
})
it('rejects exact reuse after release intent instead of closing the new worker', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
expect(h.db.requestWorkerTerminalRelease(first.dispatchId).disposition).toBe('requested')
const nextTask = h.db.createTask({ spec: 'racing reuse', runId: h.activeRunId })
const attempted = (await h.call('orchestration.workerStart', {
task: nextTask.id,
from: 'term_coord',
terminal: 'term_worker'
})) as { state: string; lastError?: string }
expect(attempted).toMatchObject({ state: 'failed' })
expect(attempted.lastError).toMatch(/release.*progress/i)
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
await expect(
h.call('orchestration.workerRelease', { dispatch: first.dispatchId })
).resolves.toMatchObject({ state: 'released' })
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(1)
})
it('retains when persisted state has another resource for the exact terminal identity', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const raw = h.db.db
raw
.prepare(
`INSERT INTO worker_terminal_resources (
id, origin_dispatch_id, owner_dispatch_id, terminal_handle, pane_key,
process_incarnation, host_scope, ownership_state, release_state, retained_reason
) VALUES (
'wtr_conflict', 'ctx_conflict', 'ctx_conflict', 'term_reminted', ?, ?, ?,
'external', 'retained', 'legacy_ambiguous'
)`
)
.run(
h.workerPaneKey,
'runtime_test:term_worker:1',
JSON.stringify({ kind: 'local', hostId: 'local' })
)
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'retained', reason: 'identity_unproven' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
})
it('worker-retain records a durable user exception that release can later replace', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const retained = (await h.call('orchestration.workerRetain', { dispatch: dispatchId })) as {
state: string
reason?: string
}
expect(retained).toMatchObject({ state: 'retained', reason: 'user_requested' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).toBe('retained')
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
state: string
}
expect(release.state).toBe('released')
})
it('worker-list separates terminal accounting from Task outcome', async () => {
h.setup()
const active = await h.startWorker()
const perWorkerLookup = vi.spyOn(h.db, 'getWorkerTerminalResourceByOwner')
perWorkerLookup.mockClear()
const result1 = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { dispatchId: string; terminalState: string | null; workerState: string }[]
counts: Record<string, number>
}
expect(result1.workers).toHaveLength(1)
expect(result1.workers[0]).toMatchObject({
dispatchId: active.dispatchId,
terminalState: 'active',
workerState: 'ready'
})
expect(perWorkerLookup).not.toHaveBeenCalled()
h.settle(active.taskId, active.dispatchId, 'succeeded')
const result2 = (await h.call('orchestration.workerList', {
run: h.activeRunId,
terminalState: 'reclaimable'
})) as { workers: { dispatchId: string }[]; counts: Record<string, number> }
expect(result2.workers.map((worker) => worker.dispatchId)).toEqual([active.dispatchId])
expect(result2.counts).toMatchObject({ reclaimable: 1 })
await h.call('orchestration.workerRelease', { dispatch: active.dispatchId })
const result3 = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { terminalState: string | null; workerState: string }[]
}
expect(result3.workers[0]).toMatchObject({
terminalState: 'released',
workerState: 'succeeded'
})
})
it('reports abandoned workers as retained instead of reclaimable', async () => {
h.setup()
const { dispatchId } = await h.startWorker()
await h.call('orchestration.workerAbandon', { dispatch: dispatchId })
const listed = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { dispatchId: string; terminalState: string | null }[]
}
expect(listed.workers).toContainEqual(
expect.objectContaining({ dispatchId, terminalState: 'retained' })
)
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({
state: 'retained',
reason: 'identity_unproven',
processAction: 'none'
})
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
})
it('worker-show exposes the terminal resource', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const shown = (await h.call('orchestration.workerShow', { dispatch: dispatchId })) as {
terminalResource: { ownershipState: string; releaseState: string } | null
}
expect(shown.terminalResource).toMatchObject({
ownershipState: 'owned',
releaseState: 'not_requested'
})
})
})
}
@@ -1,194 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
describe('orchestration worker release inventory', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('transfers ownership on exact reuse and fences release through the old Dispatch', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
const originalResource = h.db.getWorkerTerminalResourceByOwner(first.dispatchId)
expect(originalResource?.ownership_state).toBe('owned')
const second = await h.startWorker({ terminal: 'term_reminted' })
const transferred = h.db.getWorkerTerminalResourceByOwner(second.dispatchId)
expect(transferred?.id).toBe(originalResource?.id)
expect(transferred?.terminal_handle).toBe('term_reminted')
expect(h.db.getWorkerTerminalResourceByOwner(first.dispatchId)).toBeUndefined()
h.inspectProcessLiveness.mockResolvedValueOnce('exited')
const oldRelease = (await h.call('orchestration.workerRelease', {
dispatch: first.dispatchId
})) as { state: string; reason?: string }
expect(oldRelease).toMatchObject({ state: 'retained', reason: 'ownership_transferred' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
h.settle(second.taskId, second.dispatchId, 'succeeded')
const newRelease = (await h.call('orchestration.workerRelease', {
dispatch: second.dispatchId
})) as { state: string }
expect(newRelease.state).toBe('released')
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(1)
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_reminted')
})
it('refuses to settle dead transferred ownership with no durable archive', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
const second = await h.startWorker({ terminal: 'term_reminted' })
h.settle(second.taskId, second.dispatchId, 'succeeded')
h.inspectProcessLiveness.mockResolvedValue('exited')
await expect(
h.call('orchestration.workerRelease', { dispatch: first.dispatchId })
).resolves.toMatchObject({
state: 'retained',
reason: 'ownership_transferred',
processAction: 'none'
})
expect(h.inspectProcessLiveness).toHaveBeenCalledWith(
'runtime_test:term_worker:1',
JSON.stringify({ kind: 'local', hostId: 'local' })
)
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
expect(h.db.getWorkerTerminalResourceByOwner(second.dispatchId)?.release_state).not.toBe(
'released'
)
})
it('rejects exact reuse after release intent instead of closing the new worker', async () => {
h.setup()
const first = await h.startSettledWorker('succeeded')
expect(h.db.requestWorkerTerminalRelease(first.dispatchId).disposition).toBe('requested')
const nextTask = h.db.createTask({ spec: 'racing reuse', runId: h.activeRunId })
const attempted = (await h.call('orchestration.workerStart', {
task: nextTask.id,
from: 'term_coord',
terminal: 'term_worker'
})) as { state: string; lastError?: string }
expect(attempted).toMatchObject({ state: 'failed' })
expect(attempted.lastError).toMatch(/release.*progress/i)
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
await expect(
h.call('orchestration.workerRelease', { dispatch: first.dispatchId })
).resolves.toMatchObject({ state: 'released' })
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(1)
})
it('retains when persisted state has another resource for the exact terminal identity', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const raw = (
h.db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }
).db
raw
.prepare(
`INSERT INTO worker_terminal_resources (
id, origin_dispatch_id, owner_dispatch_id, terminal_handle, pane_key,
process_incarnation, host_scope, ownership_state, release_state, retained_reason
) VALUES (
'wtr_conflict', 'ctx_conflict', 'ctx_conflict', 'term_reminted', ?, ?, ?,
'external', 'retained', 'legacy_ambiguous'
)`
)
.run(
h.workerPaneKey,
'runtime_test:term_worker:1',
JSON.stringify({ kind: 'local', hostId: 'local' })
)
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'retained', reason: 'identity_unproven' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
})
it('worker-retain records a durable user exception that release can later replace', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const retained = (await h.call('orchestration.workerRetain', { dispatch: dispatchId })) as {
state: string
reason?: string
}
expect(retained).toMatchObject({ state: 'retained', reason: 'user_requested' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).toBe('retained')
const release = (await h.call('orchestration.workerRelease', { dispatch: dispatchId })) as {
state: string
}
expect(release.state).toBe('released')
})
it('worker-list separates terminal accounting from Task outcome', async () => {
h.setup()
const active = await h.startWorker()
const perWorkerLookup = vi.spyOn(h.db, 'getWorkerTerminalResourceByOwner')
perWorkerLookup.mockClear()
const result1 = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { dispatchId: string; terminalState: string | null; workerState: string }[]
counts: Record<string, number>
}
expect(result1.workers).toHaveLength(1)
expect(result1.workers[0]).toMatchObject({
dispatchId: active.dispatchId,
terminalState: 'active',
workerState: 'ready'
})
expect(perWorkerLookup).not.toHaveBeenCalled()
h.settle(active.taskId, active.dispatchId, 'succeeded')
const result2 = (await h.call('orchestration.workerList', {
run: h.activeRunId,
terminalState: 'reclaimable'
})) as { workers: { dispatchId: string }[]; counts: Record<string, number> }
expect(result2.workers.map((worker) => worker.dispatchId)).toEqual([active.dispatchId])
expect(result2.counts).toMatchObject({ reclaimable: 1 })
await h.call('orchestration.workerRelease', { dispatch: active.dispatchId })
const result3 = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { terminalState: string | null; workerState: string }[]
}
expect(result3.workers[0]).toMatchObject({
terminalState: 'released',
workerState: 'succeeded'
})
})
it('reports abandoned workers as retained instead of reclaimable', async () => {
h.setup()
const { dispatchId } = await h.startWorker()
await h.call('orchestration.workerAbandon', { dispatch: dispatchId })
const listed = (await h.call('orchestration.workerList', { run: h.activeRunId })) as {
workers: { dispatchId: string; terminalState: string | null }[]
}
expect(listed.workers).toContainEqual(
expect.objectContaining({ dispatchId, terminalState: 'retained' })
)
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({
state: 'retained',
reason: 'identity_unproven',
processAction: 'none'
})
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
})
it('worker-show exposes the terminal resource', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker()
const shown = (await h.call('orchestration.workerShow', { dispatch: dispatchId })) as {
terminalResource: { ownershipState: string; releaseState: string } | null
}
expect(shown.terminalResource).toMatchObject({
ownershipState: 'owned',
releaseState: 'not_requested'
})
})
})
@@ -0,0 +1,210 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RuntimeTerminalWait } from '../../../../../../shared/runtime-types'
import { reconcileRequestedWorkerTerminalReleases } from '../../../../orchestration/worker-terminal-release-reconciliation'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
export function registerWorkerReleaseOwnershipGuardTests(): void {
describe('workerRelease on a retained resource whose process exited', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('does not release a terminal the user took over', async () => {
const { dispatchId } = await harness.startSettledWorker('succeeded')
const takeover = (await harness.call('orchestration.workerTerminalUserInput', {
paneKey: harness.workerPaneKey
})) as { changed: number }
expect(takeover.changed).toBe(1)
expect(harness.db.getWorkerTerminalResourceByOwner(dispatchId)?.ownership_state).toBe(
'user_owned'
)
// The agent process later exits on its own; the user's pane and scrollback remain.
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string; reason?: string; archive: unknown }
expect(receipt.state).toBe('retained')
expect(receipt.reason).toBe('user_takeover')
const after = harness.db.getWorkerTerminalResourceByOwner(dispatchId)
expect(after?.ownership_state).toBe('user_owned')
expect(after?.release_state).not.toBe('released')
})
it.each(['transferred', 'external'] as const)(
'does not release a %s resource on an exited process',
async (ownershipState) => {
const { dispatchId } = await harness.startSettledWorker('succeeded')
const resource = harness.db.getWorkerTerminalResourceByOwner(dispatchId)!
harness.db.db
.prepare('UPDATE worker_terminal_resources SET ownership_state = ? WHERE id = ?')
.run(ownershipState, resource.id)
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('retained')
const after = harness.db.getWorkerTerminalResourceByOwner(dispatchId)
expect(after?.ownership_state).toBe(ownershipState)
expect(after?.release_state).not.toBe('released')
}
)
it('records the archive as unavailable rather than retaining the pane forever', async () => {
const { dispatchId } = await harness.startWorker()
// Abandoned workers never reach `requested`, the only state that writes an archive.
expect(harness.db.abandonWorkerDispatch(dispatchId, 'epoch_test').disposition).toBe(
'abandoned'
)
expect(harness.db.getWorkerTerminalArchive(dispatchId)).toBeFalsy()
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string; archive: { status: string | null } | null }
expect(receipt.state).toBe('released')
expect(receipt.archive?.status).toBe('unavailable')
})
it('exits retention after a recovery abandon even once the user retained it', async () => {
const { dispatchId } = await harness.startWorker()
harness.db.reconcileMissingWorkerTerminal(dispatchId, 'terminal gone')
expect(harness.db.getWorkerDispatch(dispatchId)?.state).toBe('abandoned')
harness.inspectProcessLiveness.mockResolvedValue('exited')
// retain deletes the archive and parks the row in `retained`: still no route back to `requested`.
await harness.call('orchestration.workerRetain', { dispatch: dispatchId })
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('released')
})
it('still refuses when an archive names a different resource', async () => {
const { dispatchId } = await harness.startWorker()
const resource = harness.db.getWorkerTerminalResourceByOwner(dispatchId)!
expect(harness.db.abandonWorkerDispatch(dispatchId, 'epoch_test').disposition).toBe(
'abandoned'
)
harness.db.storeWorkerTerminalArchive({
dispatchId,
resourceId: `${resource.id}-other`,
kind: 'terminal_tail',
content: 'tail'
})
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('retained')
expect(harness.db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).not.toBe(
'released'
)
})
})
const READY_WAIT = {
handle: 'term_worker',
condition: 'tui-idle',
satisfied: true,
status: 'running',
exitCode: null
} satisfies RuntimeTerminalWait
describe('Antigravity orchestration worker lifecycle', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('owns the terminal immediately and delays prompt delivery until AGY is ready', async () => {
h.setup()
const readiness = h.deferred<RuntimeTerminalWait>()
vi.spyOn(h.runtime, 'waitForTerminal').mockReturnValue(readiness.promise)
const pending = h.startWorker({ agent: 'antigravity' })
await vi.waitFor(() => expect(h.runtime.waitForTerminal).toHaveBeenCalled())
expect(h.runtime.createTerminal).toHaveBeenCalledWith(
'id:repo::worktree',
expect.objectContaining({ startupAgent: 'antigravity', surfaceOwner: false })
)
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
expect(h.db.listWorkerTerminalResources({})[0]?.resource).toMatchObject({
ownership_state: 'owned',
terminal_handle: 'term_worker'
})
readiness.resolve(READY_WAIT)
await expect(pending).resolves.toEqual(
expect.objectContaining({ dispatchId: expect.any(String) })
)
expect(h.runtime.sendTerminalAgentPrompt).toHaveBeenCalledTimes(1)
})
it('stops only the owned AGY terminal', async () => {
h.setup()
const { dispatchId } = await h.startWorker({ agent: 'antigravity' })
await expect(
h.call('orchestration.workerStop', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'stopped', processAction: 'closed_agent_terminal' })
expect(h.runtime.closeTerminal).toHaveBeenCalledOnce()
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_worker')
})
it('releases an owned AGY terminal and recovers a transient stale endpoint', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker('succeeded', {
agent: 'antigravity'
})
vi.mocked(h.runtime.closeTerminal).mockRejectedValueOnce(new Error('Multiplexer disposed'))
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'release_pending', processAction: 'none' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
ownership_state: 'owned',
release_state: 'releasing'
})
await expect(reconcileRequestedWorkerTerminalReleases(h.runtime)).resolves.toMatchObject({
attempted: 1,
released: 1
})
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
release_state: 'released'
})
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(2)
expect(h.runtime.closeTerminal).toHaveBeenNthCalledWith(2, 'term_worker')
})
it('fails closed on a stale AGY handle and releases it on a fresh retry', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker('succeeded', {
agent: 'antigravity'
})
vi.mocked(h.runtime.showTerminal).mockRejectedValueOnce(new Error('terminal_handle_stale'))
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'release_unknown' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
ownership_state: 'owned',
release_state: 'unknown'
})
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'released' })
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_worker')
})
})
}
@@ -1,204 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RuntimeTerminalWait } from '../../../../../../shared/runtime-types'
import { reconcileRequestedWorkerTerminalReleases } from '../../../../orchestration/worker-terminal-release-reconciliation'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
describe('workerRelease on a retained resource whose process exited', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('does not release a terminal the user took over', async () => {
const { dispatchId } = await harness.startSettledWorker('succeeded')
const takeover = (await harness.call('orchestration.workerTerminalUserInput', {
paneKey: harness.workerPaneKey
})) as { changed: number }
expect(takeover.changed).toBe(1)
expect(harness.db.getWorkerTerminalResourceByOwner(dispatchId)?.ownership_state).toBe(
'user_owned'
)
// The agent process later exits on its own; the user's pane and scrollback remain.
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string; reason?: string; archive: unknown }
expect(receipt.state).toBe('retained')
expect(receipt.reason).toBe('user_takeover')
const after = harness.db.getWorkerTerminalResourceByOwner(dispatchId)
expect(after?.ownership_state).toBe('user_owned')
expect(after?.release_state).not.toBe('released')
})
it.each(['transferred', 'external'] as const)(
'does not release a %s resource on an exited process',
async (ownershipState) => {
const { dispatchId } = await harness.startSettledWorker('succeeded')
const resource = harness.db.getWorkerTerminalResourceByOwner(dispatchId)!
harness.db.db
.prepare('UPDATE worker_terminal_resources SET ownership_state = ? WHERE id = ?')
.run(ownershipState, resource.id)
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('retained')
const after = harness.db.getWorkerTerminalResourceByOwner(dispatchId)
expect(after?.ownership_state).toBe(ownershipState)
expect(after?.release_state).not.toBe('released')
}
)
it('records the archive as unavailable rather than retaining the pane forever', async () => {
const { dispatchId } = await harness.startWorker()
// Abandoned workers never reach `requested`, the only state that writes an archive.
expect(harness.db.abandonWorkerDispatch(dispatchId, 'epoch_test').disposition).toBe('abandoned')
expect(harness.db.getWorkerTerminalArchive(dispatchId)).toBeFalsy()
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string; archive: { status: string | null } | null }
expect(receipt.state).toBe('released')
expect(receipt.archive?.status).toBe('unavailable')
})
it('exits retention after a recovery abandon even once the user retained it', async () => {
const { dispatchId } = await harness.startWorker()
harness.db.reconcileMissingWorkerTerminal(dispatchId, 'terminal gone')
expect(harness.db.getWorkerDispatch(dispatchId)?.state).toBe('abandoned')
harness.inspectProcessLiveness.mockResolvedValue('exited')
// retain deletes the archive and parks the row in `retained`: still no route back to `requested`.
await harness.call('orchestration.workerRetain', { dispatch: dispatchId })
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('released')
})
it('still refuses when an archive names a different resource', async () => {
const { dispatchId } = await harness.startWorker()
const resource = harness.db.getWorkerTerminalResourceByOwner(dispatchId)!
expect(harness.db.abandonWorkerDispatch(dispatchId, 'epoch_test').disposition).toBe('abandoned')
harness.db.storeWorkerTerminalArchive({
dispatchId,
resourceId: `${resource.id}-other`,
kind: 'terminal_tail',
content: 'tail'
})
harness.inspectProcessLiveness.mockResolvedValue('exited')
const receipt = (await harness.call('orchestration.workerRelease', {
dispatch: dispatchId
})) as { state: string }
expect(receipt.state).toBe('retained')
expect(harness.db.getWorkerTerminalResourceByOwner(dispatchId)?.release_state).not.toBe(
'released'
)
})
})
const READY_WAIT = {
handle: 'term_worker',
condition: 'tui-idle',
satisfied: true,
status: 'running',
exitCode: null
} satisfies RuntimeTerminalWait
describe('Antigravity orchestration worker lifecycle', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it('owns the terminal immediately and delays prompt delivery until AGY is ready', async () => {
h.setup()
const readiness = h.deferred<RuntimeTerminalWait>()
vi.spyOn(h.runtime, 'waitForTerminal').mockReturnValue(readiness.promise)
const pending = h.startWorker({ agent: 'antigravity' })
await vi.waitFor(() => expect(h.runtime.waitForTerminal).toHaveBeenCalled())
expect(h.runtime.createTerminal).toHaveBeenCalledWith(
'id:repo::worktree',
expect.objectContaining({ startupAgent: 'antigravity', surfaceOwner: false })
)
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
expect(h.db.listWorkerTerminalResources({})[0]?.resource).toMatchObject({
ownership_state: 'owned',
terminal_handle: 'term_worker'
})
readiness.resolve(READY_WAIT)
await expect(pending).resolves.toEqual(
expect.objectContaining({ dispatchId: expect.any(String) })
)
expect(h.runtime.sendTerminalAgentPrompt).toHaveBeenCalledTimes(1)
})
it('stops only the owned AGY terminal', async () => {
h.setup()
const { dispatchId } = await h.startWorker({ agent: 'antigravity' })
await expect(
h.call('orchestration.workerStop', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'stopped', processAction: 'closed_agent_terminal' })
expect(h.runtime.closeTerminal).toHaveBeenCalledOnce()
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_worker')
})
it('releases an owned AGY terminal and recovers a transient stale endpoint', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker('succeeded', {
agent: 'antigravity'
})
vi.mocked(h.runtime.closeTerminal).mockRejectedValueOnce(new Error('Multiplexer disposed'))
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'release_pending', processAction: 'none' })
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
ownership_state: 'owned',
release_state: 'releasing'
})
await expect(reconcileRequestedWorkerTerminalReleases(h.runtime)).resolves.toMatchObject({
attempted: 1,
released: 1
})
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
release_state: 'released'
})
expect(h.runtime.closeTerminal).toHaveBeenCalledTimes(2)
expect(h.runtime.closeTerminal).toHaveBeenNthCalledWith(2, 'term_worker')
})
it('fails closed on a stale AGY handle and releases it on a fresh retry', async () => {
h.setup()
const { dispatchId } = await h.startSettledWorker('succeeded', {
agent: 'antigravity'
})
vi.mocked(h.runtime.showTerminal).mockRejectedValueOnce(new Error('terminal_handle_stale'))
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'release_unknown' })
expect(h.runtime.closeTerminal).not.toHaveBeenCalled()
expect(h.db.getWorkerTerminalResourceByOwner(dispatchId)).toMatchObject({
ownership_state: 'owned',
release_state: 'unknown'
})
await expect(
h.call('orchestration.workerRelease', { dispatch: dispatchId })
).resolves.toMatchObject({ state: 'released' })
expect(h.runtime.closeTerminal).toHaveBeenCalledWith('term_worker')
})
})
@@ -0,0 +1,88 @@
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'
export function registerWorkerReleaseRuntimeIncarnationTests(): void {
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'
})
}
)
})
}
@@ -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'
})
}
)
})
@@ -0,0 +1,5 @@
import { registerWorkerListRunScopeRpcTests } from './worker-list-run-scope-rpc.spec'
import { registerWorkerStartTerminalTargetTests } from './worker-start-terminal-target.spec'
registerWorkerListRunScopeRpcTests()
registerWorkerStartTerminalTargetTests()
@@ -0,0 +1,267 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { LaunchedAgentForeground } from '../../../../launched-agent-foreground'
import type { RuntimeTerminalWait } from '../../../../../../shared/runtime-terminal-contracts'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
export function registerWorkerStartTerminalTargetTests(): void {
describe('worker-start --terminal target', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('refuses the coordinator terminal by handle', async () => {
const task = harness.db.createTask({ spec: 'self adoption', runId: harness.activeRunId })
await expect(
harness.call('orchestration.workerStart', {
task: task.id,
from: 'term_coord',
terminal: 'term_coord'
})
).rejects.toMatchObject({
code: 'terminal_is_coordinator',
message: expect.stringContaining("coordinator's own terminal")
})
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
})
it('refuses a different handle that resolves to the coordinator pane', async () => {
const task = harness.db.createTask({
spec: 'self adoption alias',
runId: harness.activeRunId
})
vi.spyOn(harness.runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
handle === 'term_coord' || handle === 'term_coord_alias' ? harness.coordinatorPaneKey : null
)
await expect(
harness.call('orchestration.workerStart', {
task: task.id,
from: 'term_coord',
terminal: 'term_coord_alias'
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
})
it('still accepts a separate agent terminal in the same worktree', async () => {
const started = await harness.startWorker({ terminal: 'term_worker' })
expect(started.dispatchId).toEqual(expect.any(String))
})
})
// The other door into the same self-adoption: manual dispatch never compared `to` to the caller.
describe('orchestration.dispatch --to the caller', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('refuses an injected dispatch aimed at the coordinator handle', async () => {
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
({
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}) as never
)
const task = harness.db.createTask({ spec: 'self dispatch', runId: harness.activeRunId })
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord',
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
expect(harness.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
it('refuses an injected dispatch to a different handle that resolves to the coordinator pane', async () => {
vi.spyOn(harness.runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
handle === 'term_coord' || handle === 'term_coord_alias' ? harness.coordinatorPaneKey : null
)
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
({
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}) as never
)
const task = harness.db.createTask({
spec: 'self dispatch alias',
runId: harness.activeRunId
})
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord_alias',
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
})
// Low-level topologies (and the e2e specs that drive them from one pane) dispatch context
// to the caller's own terminal; nothing is written into the pane, so nothing self-adopts.
it('still records a context-only dispatch aimed at the coordinator handle', async () => {
const task = harness.db.createTask({ spec: 'self context', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord'
})) as { dispatch: { id: string; status: string } }
expect(result.dispatch.status).toBe('dispatched')
expect(harness.db.getDispatchContextById(result.dispatch.id)?.assignee_handle).toBe(
'term_coord'
)
expect(harness.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
// The rejection for a missing agent tells the caller to dispatch without --inject, which the
// coordinator guard forbids; the self-target answer must not depend on agent presence.
it.each([
['the coordinator handle', 'term_coord'],
['an alias of the coordinator pane', 'term_coord_alias']
])('refuses %s even when no agent is detected', async (_label, target) => {
vi.spyOn(harness.runtime, 'isTerminalRunningAgent').mockResolvedValue(false)
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
(handle === 'term_coord_alias'
? {
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}
: null) as never
)
const task = harness.db.createTask({
spec: `self inject ${target}`,
runId: harness.activeRunId
})
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: target,
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
})
it('still dispatches to a different pane', async () => {
const task = harness.db.createTask({ spec: 'peer dispatch', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { assignee_pane_key: string } }
expect(result.dispatch.assignee_pane_key).toBe(harness.workerPaneKey)
})
})
const PTY_ID = 'pty_worker'
// Why: a shell back at its prompt after the agent exits reads as ready too, so the brief needs the
// launched agent found in front, or the shell runs it.
describe('a worker start writes its brief only into the agent it launched', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
function launchedPane(foreground: LaunchedAgentForeground): string[] {
h.setup()
const writes: string[] = []
vi.spyOn(h.runtime, 'readLaunchedAgentForeground').mockResolvedValue(foreground)
vi.spyOn(h.runtime, 'launchedAgentHostProvesAgent').mockReturnValue(true)
vi.spyOn(h.runtime, 'subscribeToTerminalData').mockReturnValue(() => {})
vi.mocked(h.runtime.sendTerminalAgentPrompt).mockImplementation(
async (handle, text, options) => {
await options?.beforeWrite?.(PTY_ID)
writes.push(text)
return { handle, accepted: true, bytesWritten: text.length }
}
)
return writes
}
it('types nothing when the agent exited and its shell is in front', async () => {
const writes = launchedPane('shell')
await expect(h.startWorker({ agent: 'claude' })).rejects.toThrow()
expect(writes).toEqual([])
})
it('writes the brief once into the agent found in front', async () => {
const writes = launchedPane('agent')
await h.startWorker({ agent: 'claude' })
expect(writes).toHaveLength(1)
})
it('leaves a terminal the caller supplied to its own idle wait', async () => {
const writes = launchedPane('shell')
await h.startWorker({ terminal: 'term_worker' })
expect(h.runtime.readLaunchedAgentForeground).not.toHaveBeenCalled()
expect(writes).toHaveLength(1)
})
})
describe('composer-marker first dispatch readiness', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it.each(['zcode', 'opencode', 'opencode2'] as const)(
'%s waits for the new composer before dispatch',
async (agent) => {
h.setup()
const gate = h.deferred<RuntimeTerminalWait>()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer').mockReturnValue(gate.promise)
const pending = h.startWorker({ agent })
await vi.waitFor(() =>
expect(h.runtime.waitForFreshWorkerComposer).toHaveBeenCalledWith(
'term_worker',
agent,
60_000
)
)
expect(h.runtime.waitForTerminal).not.toHaveBeenCalled()
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
gate.resolve({
handle: 'term_worker',
condition: 'tui-idle',
satisfied: true,
status: 'running',
exitCode: null
})
await pending
expect(h.runtime.sendTerminalAgentPrompt).toHaveBeenCalledOnce()
}
)
it('keeps reused terminals on the normal idle wait', async () => {
h.setup()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer')
await h.startWorker({ terminal: 'term_worker' })
expect(h.runtime.waitForFreshWorkerComposer).not.toHaveBeenCalled()
expect(h.runtime.waitForTerminal).toHaveBeenCalledWith(
'term_worker',
expect.objectContaining({ condition: 'tui-idle' })
)
})
it('never delivers a task after a startup timeout', async () => {
h.setup()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer').mockRejectedValue(new Error('timeout'))
await expect(h.startWorker({ agent: 'zcode' })).rejects.toThrow('Expected worker-start')
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
})
}
@@ -1,259 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { LaunchedAgentForeground } from '../../../../launched-agent-foreground'
import type { RuntimeTerminalWait } from '../../../../../../shared/runtime-terminal-contracts'
import { createOrchestrationWorkerReleaseHarness } from './worker-release.test-support'
describe('worker-start --terminal target', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('refuses the coordinator terminal by handle', async () => {
const task = harness.db.createTask({ spec: 'self adoption', runId: harness.activeRunId })
await expect(
harness.call('orchestration.workerStart', {
task: task.id,
from: 'term_coord',
terminal: 'term_coord'
})
).rejects.toMatchObject({
code: 'terminal_is_coordinator',
message: expect.stringContaining("coordinator's own terminal")
})
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
})
it('refuses a different handle that resolves to the coordinator pane', async () => {
const task = harness.db.createTask({ spec: 'self adoption alias', runId: harness.activeRunId })
vi.spyOn(harness.runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
handle === 'term_coord' || handle === 'term_coord_alias' ? harness.coordinatorPaneKey : null
)
await expect(
harness.call('orchestration.workerStart', {
task: task.id,
from: 'term_coord',
terminal: 'term_coord_alias'
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
})
it('still accepts a separate agent terminal in the same worktree', async () => {
const started = await harness.startWorker({ terminal: 'term_worker' })
expect(started.dispatchId).toEqual(expect.any(String))
})
})
// The other door into the same self-adoption: manual dispatch never compared `to` to the caller.
describe('orchestration.dispatch --to the caller', () => {
const harness = createOrchestrationWorkerReleaseHarness()
beforeEach(() => harness.setup())
afterEach(() => harness.cleanup())
it('refuses an injected dispatch aimed at the coordinator handle', async () => {
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
({
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}) as never
)
const task = harness.db.createTask({ spec: 'self dispatch', runId: harness.activeRunId })
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord',
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
expect(harness.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
it('refuses an injected dispatch to a different handle that resolves to the coordinator pane', async () => {
vi.spyOn(harness.runtime, 'getTerminalPaneKey').mockImplementation((handle) =>
handle === 'term_coord' || handle === 'term_coord_alias' ? harness.coordinatorPaneKey : null
)
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
({
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}) as never
)
const task = harness.db.createTask({ spec: 'self dispatch alias', runId: harness.activeRunId })
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord_alias',
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
})
// Low-level topologies (and the e2e specs that drive them from one pane) dispatch context
// to the caller's own terminal; nothing is written into the pane, so nothing self-adopts.
it('still records a context-only dispatch aimed at the coordinator handle', async () => {
const task = harness.db.createTask({ spec: 'self context', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_coord'
})) as { dispatch: { id: string; status: string } }
expect(result.dispatch.status).toBe('dispatched')
expect(harness.db.getDispatchContextById(result.dispatch.id)?.assignee_handle).toBe(
'term_coord'
)
expect(harness.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
// The rejection for a missing agent tells the caller to dispatch without --inject, which the
// coordinator guard forbids; the self-target answer must not depend on agent presence.
it.each([
['the coordinator handle', 'term_coord'],
['an alias of the coordinator pane', 'term_coord_alias']
])('refuses %s even when no agent is detected', async (_label, target) => {
vi.spyOn(harness.runtime, 'isTerminalRunningAgent').mockResolvedValue(false)
vi.spyOn(harness.runtime, 'getOrchestrationDispatchAuthority').mockImplementation(
(handle) =>
(handle === 'term_coord_alias'
? {
terminalHandle: handle,
paneKey: harness.coordinatorPaneKey,
processIncarnation: 'runtime_test:term_coord:1'
}
: null) as never
)
const task = harness.db.createTask({
spec: `self inject ${target}`,
runId: harness.activeRunId
})
await expect(
harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: target,
inject: true
})
).rejects.toMatchObject({ code: 'terminal_is_coordinator' })
expect(harness.db.getDispatchContext(task.id)).toBeUndefined()
})
it('still dispatches to a different pane', async () => {
const task = harness.db.createTask({ spec: 'peer dispatch', runId: harness.activeRunId })
const result = (await harness.call('orchestration.dispatch', {
task: task.id,
from: 'term_coord',
to: 'term_worker'
})) as { dispatch: { assignee_pane_key: string } }
expect(result.dispatch.assignee_pane_key).toBe(harness.workerPaneKey)
})
})
const PTY_ID = 'pty_worker'
// Why: a shell back at its prompt after the agent exits reads as ready too, so the brief needs the
// launched agent found in front, or the shell runs it.
describe('a worker start writes its brief only into the agent it launched', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
function launchedPane(foreground: LaunchedAgentForeground): string[] {
h.setup()
const writes: string[] = []
vi.spyOn(h.runtime, 'readLaunchedAgentForeground').mockResolvedValue(foreground)
vi.spyOn(h.runtime, 'launchedAgentHostProvesAgent').mockReturnValue(true)
vi.spyOn(h.runtime, 'subscribeToTerminalData').mockReturnValue(() => {})
vi.mocked(h.runtime.sendTerminalAgentPrompt).mockImplementation(
async (handle, text, options) => {
await options?.beforeWrite?.(PTY_ID)
writes.push(text)
return { handle, accepted: true, bytesWritten: text.length }
}
)
return writes
}
it('types nothing when the agent exited and its shell is in front', async () => {
const writes = launchedPane('shell')
await expect(h.startWorker({ agent: 'claude' })).rejects.toThrow()
expect(writes).toEqual([])
})
it('writes the brief once into the agent found in front', async () => {
const writes = launchedPane('agent')
await h.startWorker({ agent: 'claude' })
expect(writes).toHaveLength(1)
})
it('leaves a terminal the caller supplied to its own idle wait', async () => {
const writes = launchedPane('shell')
await h.startWorker({ terminal: 'term_worker' })
expect(h.runtime.readLaunchedAgentForeground).not.toHaveBeenCalled()
expect(writes).toHaveLength(1)
})
})
describe('composer-marker first dispatch readiness', () => {
const h = createOrchestrationWorkerReleaseHarness()
afterEach(() => h.cleanup())
it.each(['zcode', 'opencode', 'opencode2'] as const)(
'%s waits for the new composer before dispatch',
async (agent) => {
h.setup()
const gate = h.deferred<RuntimeTerminalWait>()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer').mockReturnValue(gate.promise)
const pending = h.startWorker({ agent })
await vi.waitFor(() =>
expect(h.runtime.waitForFreshWorkerComposer).toHaveBeenCalledWith(
'term_worker',
agent,
60_000
)
)
expect(h.runtime.waitForTerminal).not.toHaveBeenCalled()
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
gate.resolve({
handle: 'term_worker',
condition: 'tui-idle',
satisfied: true,
status: 'running',
exitCode: null
})
await pending
expect(h.runtime.sendTerminalAgentPrompt).toHaveBeenCalledOnce()
}
)
it('keeps reused terminals on the normal idle wait', async () => {
h.setup()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer')
await h.startWorker({ terminal: 'term_worker' })
expect(h.runtime.waitForFreshWorkerComposer).not.toHaveBeenCalled()
expect(h.runtime.waitForTerminal).toHaveBeenCalledWith(
'term_worker',
expect.objectContaining({ condition: 'tui-idle' })
)
})
it('never delivers a task after a startup timeout', async () => {
h.setup()
vi.spyOn(h.runtime, 'waitForFreshWorkerComposer').mockRejectedValue(new Error('timeout'))
await expect(h.startWorker({ agent: 'zcode' })).rejects.toThrow('Expected worker-start')
expect(h.runtime.sendTerminalAgentPrompt).not.toHaveBeenCalled()
})
})
@@ -1,3 +1,4 @@
import './unused-default-rpc-methods.test-fixture'
import { afterEach, describe, expect, it } from 'vitest'
import {
cleanupLegacyCompatibilityDispatcherHarnesses,
@@ -50,7 +50,7 @@ vi.mock('@/runtime/runtime-rpc-client', async (importOriginal) => {
// @ts-expect-error -- minimal window.api stub for the store under test
globalThis.window = { api: {} }
import { createTestStore } from './store-test-helpers'
import { createRuntimeDetectedAgentsPairingTestStore as createTestStore } from './runtime-detected-agents-pairing.test-fixture'
function env(id: string): PublicKnownRuntimeEnvironment {
return { id } as unknown as PublicKnownRuntimeEnvironment
@@ -0,0 +1,22 @@
import { createStore } from 'zustand/vanilla'
import {
createRuntimeDetectedAgentsSlice,
type RuntimeDetectedAgentsStoreState
} from './runtime-detected-agents'
import { createRuntimeAgentOwnerTestState } from './runtime-detected-agents.test-fixture'
import { createRuntimeStatusSlice } from './runtime-status'
import type { RuntimeStatusStoreState } from './runtime-status-types'
type RuntimeAgentPairingStoreState = RuntimeStatusStoreState & RuntimeDetectedAgentsStoreState
export function createRuntimeDetectedAgentsPairingTestStore() {
return createStore<RuntimeAgentPairingStoreState>()((...args) => ({
...createRuntimeStatusSlice(...args),
...createRuntimeDetectedAgentsSlice(...args),
...createRuntimeAgentOwnerTestState(),
clientHostedBrowserCloseIntentsByEnvironment: {},
clearClientHostedBrowserCloseIntents: () => {
throw new Error('Agent-detection pairing must not replay browser close intents')
}
}))
}
@@ -21,7 +21,7 @@ vi.mock('@/components/terminal-pane/pty-dispatcher', () => ({
// @ts-expect-error -- minimal API stub for the store under test
globalThis.window = { api: {} }
import { createTestStore } from './store-test-helpers'
import { createRuntimeDetectedAgentsPairingTestStore as createTestStore } from './runtime-detected-agents-pairing.test-fixture'
import { getRuntimeAgentInventoryKey } from './runtime-agent-inventory-key'
import { makeWorktree, TEST_REPO } from './worktrees-slice-test-fixtures'
@@ -0,0 +1,17 @@
import { createStore } from 'zustand/vanilla'
import type { getConnectionIdFromState } from '@/lib/connection-owner-resolution'
import {
createRuntimeDetectedAgentsSlice,
type RuntimeDetectedAgentsStoreState
} from './runtime-detected-agents'
export function createRuntimeAgentOwnerTestState(): Parameters<typeof getConnectionIdFromState>[0] {
return { repos: [], worktreesByRepo: {}, folderWorkspaces: [], projectGroups: [] }
}
export function createRuntimeDetectedAgentsTestStore() {
return createStore<RuntimeDetectedAgentsStoreState>()((...args) => ({
...createRuntimeDetectedAgentsSlice(...args),
...createRuntimeAgentOwnerTestState()
}))
}
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createTestStore } from './store-test-helpers'
import { createRuntimeDetectedAgentsTestStore as createTestStore } from './runtime-detected-agents.test-fixture'
import { makeWorktree, TEST_REPO } from './worktrees-slice-test-fixtures'
import { getRuntimeAgentInventoryKey } from './runtime-agent-inventory-key'
import {
@@ -1,5 +1,3 @@
import type { StateCreator } from 'zustand'
import type { AppState } from '../types'
import type { TuiAgent } from '../../../../shared/tui-agent'
import {
callRuntimeRpc,
@@ -129,12 +127,20 @@ function withoutKeys<T>(record: Record<string, T>, drop: (key: string) => boolea
return changed ? next : record
}
export const createRuntimeDetectedAgentsSlice: StateCreator<
AppState,
[],
[],
RuntimeDetectedAgentsSlice
> = (set, get) => {
export type RuntimeDetectedAgentsStoreState = RuntimeDetectedAgentsSlice &
Parameters<typeof getConnectionIdFromState>[0]
export type RuntimeDetectedAgentsStoreSetState = (
partial:
| Partial<RuntimeDetectedAgentsStoreState>
| ((state: RuntimeDetectedAgentsStoreState) => Partial<RuntimeDetectedAgentsStoreState>)
) => void
export const createRuntimeDetectedAgentsSlice: (
set: RuntimeDetectedAgentsStoreSetState,
get: () => RuntimeDetectedAgentsStoreState,
store?: unknown
) => RuntimeDetectedAgentsSlice = (set, get) => {
// Drops every workspace's inventory for the environments `drop` selects.
const dropEnvironments = (drop: (environmentId: string) => boolean): void => {
const dropKey = (key: string) => drop(getRuntimeAgentInventoryEnvironmentId(key))
@@ -1,6 +1,6 @@
import { toast } from 'sonner'
import { translate } from '@/i18n/i18n'
import type { AppState } from '../types'
import type { RuntimeStatusSlice } from './runtime-status-types'
const activeRuntimeDisconnectedToasts = new Map<string, symbol>()
const RUNTIME_DISCONNECTED_TOAST_DURATION_MS = 4_000
@@ -11,7 +11,10 @@ function getRuntimeDisconnectedToastId(environmentId: string): string {
export function showRuntimeDisconnectedToast(
environmentId: string,
getState: () => AppState
getState: () => Pick<
RuntimeStatusSlice,
'runtimeEnvironments' | 'refreshRuntimeEnvironmentStatus'
>
): void {
const environment = getState().runtimeEnvironments.find((entry) => entry.id === environmentId)
const toastId = getRuntimeDisconnectedToastId(environmentId)
@@ -8,7 +8,7 @@ import {
clearRuntimeEnvironmentConnectionGenerationsForTests,
getRuntimeEnvironmentConnectionGeneration
} from './runtime-status'
import { createTestStore } from './store-test-helpers'
import { createRuntimeStatusTestStore as createTestStore } from './runtime-status.test-fixture'
vi.mock('sonner', () => ({
toast: { warning: vi.fn(), dismiss: vi.fn() }
@@ -25,7 +25,7 @@ import {
resetSkillDiscoveryCacheForTests
} from '@/hooks/installed-agent-skill-discovery'
import { getInstalledAgentSkillDiscoveryCacheSizeForTests } from '@/hooks/installed-agent-skill-discovery-cache'
import { createTestStore } from './store-test-helpers'
import { createRuntimeStatusTestStore as createTestStore } from './runtime-status.test-fixture'
function environment(id: string, pairingRevision = 1): PublicKnownRuntimeEnvironment {
return {
@@ -2,8 +2,11 @@ import {
runtimeEnvironmentStatusFromSnapshot,
type RuntimeHostStatusSnapshot
} from '../../../../shared/runtime-host-status'
import type { AppState } from '../types'
import type { RuntimeEnvironmentStatus } from './runtime-status-types'
import type {
RuntimeEnvironmentStatus,
RuntimeStatusSlice,
RuntimeStatusStoreState
} from './runtime-status-types'
import { ensureBrowserClientHostsForRestoredPages } from '@/runtime/restored-client-hosted-browser-host-attach'
import { replayClientHostedBrowserCloseIntents } from '@/runtime/client-hosted-browser-close-intent-replay'
@@ -13,7 +16,7 @@ import { replayClientHostedBrowserCloseIntents } from '@/runtime/client-hosted-b
*/
export function snapshotOutrunsCatalog(
snapshot: RuntimeHostStatusSnapshot,
state: Pick<AppState, 'runtimeEnvironments'>
state: Pick<RuntimeStatusSlice, 'runtimeEnvironments'>
): boolean {
const environment = state.runtimeEnvironments.find((entry) => entry.id === snapshot.environmentId)
return (
@@ -24,7 +27,7 @@ export function snapshotOutrunsCatalog(
export function applyRuntimeHostStatusSnapshot(
snapshot: RuntimeHostStatusSnapshot,
state: AppState,
state: RuntimeStatusStoreState,
publishEvidence: (entry: RuntimeEnvironmentStatus) => void
): void {
const environment = state.runtimeEnvironments.find((entry) => entry.id === snapshot.environmentId)
@@ -3,6 +3,7 @@ import type {
RuntimeHostStatusSnapshot
} from '../../../../shared/runtime-host-status'
import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments'
import type { AppState } from '../types'
export type { RuntimeEnvironmentStatus }
@@ -25,3 +26,28 @@ export type RuntimeStatusSlice = {
refreshRuntimeEnvironmentStatus: (environmentId: string, timeoutMs?: number) => Promise<boolean>
hydrateRuntimeEnvironmentStatuses: () => Promise<void>
}
export type RuntimeStatusStoreState = RuntimeStatusSlice &
Pick<
AppState,
'clientHostedBrowserCloseIntentsByEnvironment' | 'clearClientHostedBrowserCloseIntents'
> &
Partial<
Pick<
AppState,
| 'settings'
| 'remoteBrowserPageHandlesByPageId'
| 'retainRuntimeDetectedAgents'
| 'retainRuntimeTerminalQuickCommands'
| 'retainEnvironmentSshState'
| 'clearRuntimeDetectedAgents'
| 'markEnvironmentSshStateStale'
| 'purgeStaleRuntimeHostState'
>
>
export type RuntimeStatusStoreSetState = (
partial:
| Partial<RuntimeStatusStoreState>
| ((state: RuntimeStatusStoreState) => Partial<RuntimeStatusStoreState>)
) => void
@@ -0,0 +1,13 @@
import { createStore } from 'zustand/vanilla'
import { createRuntimeStatusSlice } from './runtime-status'
import type { RuntimeStatusStoreState } from './runtime-status-types'
export function createRuntimeStatusTestStore() {
return createStore<RuntimeStatusStoreState>()((...args) => ({
...createRuntimeStatusSlice(...args),
clientHostedBrowserCloseIntentsByEnvironment: {},
clearClientHostedBrowserCloseIntents: () => {
throw new Error('Runtime-status catalog tests must not replay browser close intents')
}
}))
}
@@ -1,6 +1,8 @@
import type { StateCreator } from 'zustand'
import type { AppState } from '../types'
import type { RuntimeStatusSlice } from './runtime-status-types'
import type {
RuntimeStatusSlice,
RuntimeStatusStoreSetState,
RuntimeStatusStoreState
} from './runtime-status-types'
export type { RuntimeEnvironmentStatus, RuntimeStatusSlice } from './runtime-status-types'
import { lastVerifiedRuntimeStatus } from '../../../../shared/runtime-host-status'
import { runtimeEnvironmentStatusesEqual } from './runtime-environment-status-equality'
@@ -40,10 +42,11 @@ export {
setRuntimeEnvironmentConnectionGenerationForTests
} from './runtime-status-connection-generation'
export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeStatusSlice> = (
set,
get
) => ({
export const createRuntimeStatusSlice: (
set: RuntimeStatusStoreSetState,
get: () => RuntimeStatusStoreState,
store?: unknown
) => RuntimeStatusSlice = (set, get) => ({
runtimeEnvironments: [],
runtimeEnvironmentCatalogHydrated: false,
runtimeEnvironmentCatalogSettled: false,