@selesai/code 0.5.20 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { after, afterEach, before, beforeEach, describe, it } from "node:test";
|
|
6
|
-
import { ASYNC_DIR, INTERCOM_DETACH_REQUEST_EVENT, RESULTS_DIR, SUBAGENT_ASYNC_STARTED_EVENT } from "../../src/shared/types.ts";
|
|
6
|
+
import { ASYNC_DIR, INTERCOM_DETACH_REQUEST_EVENT, RESULTS_DIR, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT } from "../../src/shared/types.ts";
|
|
7
|
+
import { waitForSubagents } from "../../src/runs/background/subagent-wait.ts";
|
|
8
|
+
import { sessionLeaseDir } from "../../src/runs/shared/session-lease.ts";
|
|
7
9
|
import type { MockPi } from "../support/helpers.ts";
|
|
8
10
|
import {
|
|
9
11
|
createMockPi,
|
|
@@ -21,7 +23,7 @@ interface ExecutorResult {
|
|
|
21
23
|
details?: {
|
|
22
24
|
mode?: string;
|
|
23
25
|
runId?: string;
|
|
24
|
-
results?: Array<{ agent?: string; finalOutput?: string }>;
|
|
26
|
+
results?: Array<{ agent?: string; finalOutput?: string; sessionFile?: string; acceptance?: { status?: string }; artifactPaths?: { metadataPath?: string } }>;
|
|
25
27
|
asyncId?: string;
|
|
26
28
|
};
|
|
27
29
|
}
|
|
@@ -113,15 +115,25 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
113
115
|
async function readMockCallArgs(index: number): Promise<string[]> {
|
|
114
116
|
const deadline = Date.now() + 10_000;
|
|
115
117
|
let callFile: string | undefined;
|
|
116
|
-
|
|
118
|
+
let parseError: SyntaxError | undefined;
|
|
119
|
+
while (Date.now() <= deadline) {
|
|
117
120
|
callFile = fs.readdirSync(mockPi.dir)
|
|
118
121
|
.filter((name) => name.startsWith("call-") && name.endsWith(".json"))
|
|
119
122
|
.sort()[index];
|
|
120
|
-
if (callFile
|
|
123
|
+
if (callFile) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")).args as string[];
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
128
|
+
parseError = error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
121
131
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
122
132
|
}
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
if (callFile && parseError) {
|
|
134
|
+
throw new Error(`Mock Pi call record '${callFile}' remained incomplete after 10s.`, { cause: parseError });
|
|
135
|
+
}
|
|
136
|
+
assert.fail(`expected mock pi call at index ${index}`);
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
async function waitForFile(filePath: string, timeoutMs = 10_000): Promise<void> {
|
|
@@ -132,6 +144,14 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
async function waitForMissingPath(filePath: string, timeoutMs = 10_000): Promise<void> {
|
|
148
|
+
const deadline = Date.now() + timeoutMs;
|
|
149
|
+
while (fs.existsSync(filePath)) {
|
|
150
|
+
if (Date.now() > deadline) assert.fail(`Timed out waiting for path cleanup: ${filePath}`);
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
135
155
|
function makeExecutor(options: { bridgeMode?: "always" | "off"; agents?: ReturnType<typeof makeAgent>[]; acknowledgeResults?: boolean; kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean } = {}) {
|
|
136
156
|
const events = createRecordingEventBus({ acknowledgeResults: options.acknowledgeResults ?? true });
|
|
137
157
|
const state = {
|
|
@@ -317,12 +337,12 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
317
337
|
|
|
318
338
|
assert.equal(detachEmitted, true);
|
|
319
339
|
assert.match(result.content[0]?.text ?? "", /Chain detached for intercom coordination/);
|
|
320
|
-
assert.
|
|
340
|
+
assert.match(result.content[0]?.text ?? "", /do not resume or launch a replacement/);
|
|
321
341
|
assert.equal(bus.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
|
|
322
342
|
assert.equal(mockPi.callCount(), 1);
|
|
323
343
|
});
|
|
324
344
|
|
|
325
|
-
it("resume action
|
|
345
|
+
it("resume action rejects a live async child without side effects", async () => {
|
|
326
346
|
const runId = `resume-live-${Date.now()}`;
|
|
327
347
|
const asyncDir = path.join(ASYNC_DIR, runId);
|
|
328
348
|
const kills: Array<{ pid: number; signal?: NodeJS.Signals | 0 }> = [];
|
|
@@ -330,6 +350,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
330
350
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
331
351
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
332
352
|
runId,
|
|
353
|
+
sessionId: "session-123",
|
|
333
354
|
mode: "single",
|
|
334
355
|
state: "running",
|
|
335
356
|
pid: process.pid,
|
|
@@ -352,20 +373,72 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
352
373
|
makeMinimalCtx(tempDir),
|
|
353
374
|
);
|
|
354
375
|
|
|
355
|
-
assert.equal(result.isError,
|
|
356
|
-
assert.match(result.content[0]?.text ?? "",
|
|
357
|
-
assert.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const payload = events.emitted.find((entry) => entry.channel === "subagent:result-intercom")?.payload as { to?: string; message?: string } | undefined;
|
|
362
|
-
assert.equal(payload?.to, `subagent-worker-${runId}-1`);
|
|
363
|
-
assert.match(payload?.message ?? "", /Can you clarify the last change\?/);
|
|
376
|
+
assert.equal(result.isError, true);
|
|
377
|
+
assert.match(result.content[0]?.text ?? "", new RegExp(`Async child '${runId}' index 0 is still running`));
|
|
378
|
+
assert.match(result.content[0]?.text ?? "", new RegExp(`subagent\\(\\{ action: "steer", id: "${runId}", index: 0, message: "\\.\\.\\." \\}\\)`));
|
|
379
|
+
assert.deepEqual(kills, []);
|
|
380
|
+
assert.equal(fs.existsSync(path.join(asyncDir, "control", "interrupt.json")), false);
|
|
381
|
+
assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
|
|
364
382
|
} finally {
|
|
365
383
|
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
366
384
|
}
|
|
367
385
|
});
|
|
368
386
|
|
|
387
|
+
it("resume action rejects async runs from another or missing parent session", async () => {
|
|
388
|
+
for (const [suffix, sessionId] of [["other", "another-session"], ["missing", undefined]] as const) {
|
|
389
|
+
const runId = `resume-${suffix}-session-${Date.now()}`;
|
|
390
|
+
const asyncDir = path.join(ASYNC_DIR, runId);
|
|
391
|
+
try {
|
|
392
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
393
|
+
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
394
|
+
runId,
|
|
395
|
+
...(sessionId ? { sessionId } : {}),
|
|
396
|
+
mode: "single",
|
|
397
|
+
state: "running",
|
|
398
|
+
pid: process.pid,
|
|
399
|
+
startedAt: 100,
|
|
400
|
+
lastUpdate: Date.now(),
|
|
401
|
+
steps: [{ agent: "worker", status: "running" }],
|
|
402
|
+
}, null, 2), "utf-8");
|
|
403
|
+
const { executor } = makeExecutor();
|
|
404
|
+
|
|
405
|
+
const result = await executor.execute(
|
|
406
|
+
`resume-${suffix}-session`,
|
|
407
|
+
{ action: "resume", id: runId, message: "Continue" },
|
|
408
|
+
new AbortController().signal,
|
|
409
|
+
undefined,
|
|
410
|
+
makeMinimalCtx(tempDir),
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
assert.equal(result.isError, true);
|
|
414
|
+
assert.match(result.content[0]?.text ?? "", /not found in the active session/);
|
|
415
|
+
assert.equal(mockPi.callCount(), 0);
|
|
416
|
+
} finally {
|
|
417
|
+
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("resume action rejects explicit reviewed acceptance before launching", async () => {
|
|
423
|
+
for (const [index, params] of [
|
|
424
|
+
{ message: "Continue", acceptance: "reviewed" },
|
|
425
|
+
{ chain: [{ agent: "reviewer", task: "Review {previous}", acceptance: { level: "reviewed" } }] },
|
|
426
|
+
].entries()) {
|
|
427
|
+
const { executor, events } = makeExecutor({ agents: [makeAgent("reviewer")] });
|
|
428
|
+
const result = await executor.execute(
|
|
429
|
+
`resume-invalid-acceptance-${index}`,
|
|
430
|
+
{ action: "resume", id: "missing-source-run", ...params },
|
|
431
|
+
new AbortController().signal,
|
|
432
|
+
undefined,
|
|
433
|
+
makeMinimalCtx(tempDir),
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
assert.equal(result.isError, true);
|
|
437
|
+
assert.match(result.content[0]?.text ?? "", /Cannot resume:.*achieved status.*acceptance\.review\.required/i);
|
|
438
|
+
assert.equal(events.emitted.some((entry) => entry.channel === SUBAGENT_ASYNC_STARTED_EVENT), false);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
369
442
|
it("resume action can attach a live async child as the first step of a new chain", async () => {
|
|
370
443
|
const sourceRunId = `resume-chain-root-${Date.now()}`;
|
|
371
444
|
const sourceAsyncDir = path.join(ASYNC_DIR, sourceRunId);
|
|
@@ -377,6 +450,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
377
450
|
fs.writeFileSync(sourceSession, "", "utf-8");
|
|
378
451
|
fs.writeFileSync(path.join(sourceAsyncDir, "status.json"), JSON.stringify({
|
|
379
452
|
runId: sourceRunId,
|
|
453
|
+
sessionId: "session-123",
|
|
380
454
|
mode: "single",
|
|
381
455
|
state: "running",
|
|
382
456
|
pid: process.pid,
|
|
@@ -387,6 +461,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
387
461
|
}, null, 2), "utf-8");
|
|
388
462
|
fs.writeFileSync(sourceResultPath, JSON.stringify({
|
|
389
463
|
id: sourceRunId,
|
|
464
|
+
sessionId: "session-123",
|
|
390
465
|
agent: "worker",
|
|
391
466
|
mode: "single",
|
|
392
467
|
success: true,
|
|
@@ -410,11 +485,12 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
410
485
|
|
|
411
486
|
assert.equal(result.isError, undefined);
|
|
412
487
|
assert.match(result.content[0]?.text ?? "", /Attached async subagent/);
|
|
413
|
-
const startedEvent = events.emitted.find((entry) => entry.channel === SUBAGENT_ASYNC_STARTED_EVENT)?.payload as { agent?: string; agents?: string[]; chain?: string[]; chainStepCount?: number } | undefined;
|
|
488
|
+
const startedEvent = events.emitted.find((entry) => entry.channel === SUBAGENT_ASYNC_STARTED_EVENT)?.payload as { agent?: string; agents?: string[]; chain?: string[]; chainStepCount?: number; goal?: string } | undefined;
|
|
414
489
|
assert.equal(startedEvent?.agent, "worker");
|
|
415
490
|
assert.deepEqual(startedEvent?.agents, ["worker", "reviewer"]);
|
|
416
491
|
assert.deepEqual(startedEvent?.chain, ["worker", "reviewer"]);
|
|
417
492
|
assert.equal(startedEvent?.chainStepCount, 2);
|
|
493
|
+
assert.equal(startedEvent?.goal, "Review this root result: {previous}");
|
|
418
494
|
const attachedId = result.details?.asyncId;
|
|
419
495
|
assert.ok(attachedId, "expected attached chain async id");
|
|
420
496
|
assert.match(result.details?.asyncDir ?? "", new RegExp(`${attachedId}$`));
|
|
@@ -441,6 +517,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
441
517
|
fs.mkdirSync(RESULTS_DIR, { recursive: true });
|
|
442
518
|
fs.writeFileSync(path.join(sourceAsyncDir, "status.json"), JSON.stringify({
|
|
443
519
|
runId: sourceRunId,
|
|
520
|
+
sessionId: "session-123",
|
|
444
521
|
mode: "single",
|
|
445
522
|
state: "complete",
|
|
446
523
|
startedAt: 100,
|
|
@@ -450,6 +527,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
450
527
|
}, null, 2), "utf-8");
|
|
451
528
|
fs.writeFileSync(sourceResultPath, JSON.stringify({
|
|
452
529
|
id: sourceRunId,
|
|
530
|
+
sessionId: "session-123",
|
|
453
531
|
agent: "worker",
|
|
454
532
|
mode: "single",
|
|
455
533
|
success: true,
|
|
@@ -503,6 +581,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
503
581
|
fs.writeFileSync(secondSession, "", "utf-8");
|
|
504
582
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
505
583
|
runId,
|
|
584
|
+
sessionId: "session-123",
|
|
506
585
|
mode: "parallel",
|
|
507
586
|
state: "complete",
|
|
508
587
|
startedAt: 100,
|
|
@@ -510,7 +589,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
510
589
|
cwd: tempDir,
|
|
511
590
|
steps: [
|
|
512
591
|
{ agent: "a", status: "complete", sessionFile: firstSession },
|
|
513
|
-
{ agent: "b", status: "complete", sessionFile: secondSession },
|
|
592
|
+
{ agent: "b", status: "complete", sessionFile: secondSession, model: "anthropic/claude-sonnet-4", thinking: "high" },
|
|
514
593
|
],
|
|
515
594
|
}, null, 2), "utf-8");
|
|
516
595
|
const { executor } = makeExecutor({ agents: [makeAgent("a"), makeAgent("b")] });
|
|
@@ -529,6 +608,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
529
608
|
assert.match(result.content[0]?.text ?? "", new RegExp(secondSession.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
530
609
|
const args = await readMockCallArgs(0);
|
|
531
610
|
assert.equal(args[args.indexOf("--session") + 1], secondSession);
|
|
611
|
+
assert.equal(args[args.indexOf("--model") + 1], "anthropic/claude-sonnet-4:high");
|
|
532
612
|
} finally {
|
|
533
613
|
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
534
614
|
}
|
|
@@ -544,6 +624,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
544
624
|
fs.writeFileSync(sessionFile, "", "utf-8");
|
|
545
625
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
546
626
|
runId,
|
|
627
|
+
sessionId: "session-123",
|
|
547
628
|
mode: "single",
|
|
548
629
|
state: "complete",
|
|
549
630
|
startedAt: 100,
|
|
@@ -552,7 +633,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
552
633
|
sessionFile,
|
|
553
634
|
steps: [{ agent: "worker", status: "complete" }],
|
|
554
635
|
}, null, 2), "utf-8");
|
|
555
|
-
const { executor } = makeExecutor();
|
|
636
|
+
const { executor, events } = makeExecutor();
|
|
556
637
|
|
|
557
638
|
const result = await executor.execute(
|
|
558
639
|
"resume-revive",
|
|
@@ -565,11 +646,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
565
646
|
assert.equal(result.isError, undefined);
|
|
566
647
|
assert.match(result.content[0]?.text ?? "", /Revived async subagent from/);
|
|
567
648
|
assert.match(result.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
|
|
568
|
-
assert.match(result.content[0]?.text ?? "", /call
|
|
649
|
+
assert.match(result.content[0]?.text ?? "", /call subagent_wait\(\)/);
|
|
569
650
|
assert.match(result.content[0]?.text ?? "", /Status if needed: subagent\(\{ action: "status"/);
|
|
570
651
|
assert.doesNotMatch(result.content[0]?.text ?? "", /Follow:/);
|
|
571
652
|
const revivedId = result.details?.asyncId;
|
|
572
653
|
assert.ok(revivedId, "expected revived async id");
|
|
654
|
+
const startedEvent = events.emitted.find((entry) => entry.channel === SUBAGENT_ASYNC_STARTED_EVENT && (entry.payload as { id?: string }).id === revivedId)?.payload as { goal?: string } | undefined;
|
|
655
|
+
assert.equal(startedEvent?.goal, "What changed?");
|
|
573
656
|
const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
|
|
574
657
|
const deadline = Date.now() + 10_000;
|
|
575
658
|
while (!fs.existsSync(resultPath)) {
|
|
@@ -581,6 +664,123 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
581
664
|
}
|
|
582
665
|
});
|
|
583
666
|
|
|
667
|
+
it("revives from the persisted contract when the agent definition was removed", async () => {
|
|
668
|
+
mockPi.onCall({ output: "descriptor-backed answer" });
|
|
669
|
+
const runId = `resume-descriptor-${Date.now()}`;
|
|
670
|
+
const asyncDir = path.join(ASYNC_DIR, runId);
|
|
671
|
+
const sessionFile = path.join(tempDir, "descriptor-child.jsonl");
|
|
672
|
+
let revivedId: string | undefined;
|
|
673
|
+
try {
|
|
674
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
675
|
+
fs.writeFileSync(sessionFile, "", "utf-8");
|
|
676
|
+
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
677
|
+
runId, sessionId: "session-123", mode: "single", state: "paused", startedAt: 100, lastUpdate: 200, cwd: tempDir,
|
|
678
|
+
steps: [{ agent: "removed-worker", status: "paused", sessionFile }],
|
|
679
|
+
}, null, 2), "utf-8");
|
|
680
|
+
fs.writeFileSync(path.join(asyncDir, "recovery-descriptor.json"), JSON.stringify({
|
|
681
|
+
version: 1,
|
|
682
|
+
sourceRunId: runId,
|
|
683
|
+
agent: "removed-worker",
|
|
684
|
+
cwd: tempDir,
|
|
685
|
+
model: "anthropic/claude-sonnet-4:high",
|
|
686
|
+
tools: ["read"],
|
|
687
|
+
systemPrompt: "Original persisted prompt",
|
|
688
|
+
systemPromptMode: "replace",
|
|
689
|
+
inheritProjectContext: false,
|
|
690
|
+
inheritSkills: false,
|
|
691
|
+
outputMode: "inline",
|
|
692
|
+
maxSubagentDepth: 1,
|
|
693
|
+
share: false,
|
|
694
|
+
}, null, 2), "utf-8");
|
|
695
|
+
const { executor } = makeExecutor({ agents: [] });
|
|
696
|
+
|
|
697
|
+
const result = await executor.execute(
|
|
698
|
+
"resume-descriptor",
|
|
699
|
+
{ action: "resume", id: runId, message: "Continue safely." },
|
|
700
|
+
new AbortController().signal,
|
|
701
|
+
undefined,
|
|
702
|
+
makeMinimalCtx(tempDir),
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
assert.equal(result.isError, undefined);
|
|
706
|
+
revivedId = result.details.asyncId;
|
|
707
|
+
assert.ok(revivedId);
|
|
708
|
+
const args = await readMockCallArgs(0);
|
|
709
|
+
assert.equal(args[args.indexOf("--session") + 1], sessionFile);
|
|
710
|
+
assert.equal(args[args.indexOf("--model") + 1], "anthropic/claude-sonnet-4:high");
|
|
711
|
+
assert.equal(args[args.indexOf("--tools") + 1], "read");
|
|
712
|
+
assert.equal(args.includes("--system-prompt"), true);
|
|
713
|
+
assert.equal(args.includes("--append-system-prompt"), false);
|
|
714
|
+
await waitForFile(path.join(RESULTS_DIR, `${revivedId}.json`));
|
|
715
|
+
} finally {
|
|
716
|
+
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("rejects concurrent direct revival of the same completed async session and releases ownership", async () => {
|
|
721
|
+
const releasePath = path.join(tempDir, "release-async-revival");
|
|
722
|
+
mockPi.onCall({ waitForPath: releasePath, output: "first revived answer" });
|
|
723
|
+
mockPi.onCall({ output: "answer after release" });
|
|
724
|
+
const sourceRunId = `resume-lease-async-${Date.now()}`;
|
|
725
|
+
const sourceAsyncDir = path.join(ASYNC_DIR, sourceRunId);
|
|
726
|
+
const sessionFile = path.join(tempDir, "leased-async-child.jsonl");
|
|
727
|
+
fs.mkdirSync(sourceAsyncDir, { recursive: true });
|
|
728
|
+
fs.writeFileSync(sessionFile, "", "utf-8");
|
|
729
|
+
fs.writeFileSync(path.join(sourceAsyncDir, "status.json"), JSON.stringify({
|
|
730
|
+
runId: sourceRunId,
|
|
731
|
+
sessionId: "session-123",
|
|
732
|
+
mode: "single",
|
|
733
|
+
state: "complete",
|
|
734
|
+
startedAt: 100,
|
|
735
|
+
lastUpdate: 200,
|
|
736
|
+
cwd: tempDir,
|
|
737
|
+
sessionFile,
|
|
738
|
+
steps: [{ agent: "worker", status: "complete" }],
|
|
739
|
+
}, null, 2), "utf-8");
|
|
740
|
+
try {
|
|
741
|
+
const { executor } = makeExecutor();
|
|
742
|
+
const first = await executor.execute(
|
|
743
|
+
"resume-lease-async-first",
|
|
744
|
+
{ action: "resume", id: sourceRunId, message: "First follow-up" },
|
|
745
|
+
new AbortController().signal,
|
|
746
|
+
undefined,
|
|
747
|
+
makeMinimalCtx(tempDir),
|
|
748
|
+
);
|
|
749
|
+
assert.equal(first.isError, undefined);
|
|
750
|
+
const firstRevivedId = first.details?.asyncId;
|
|
751
|
+
assert.ok(firstRevivedId);
|
|
752
|
+
await readMockCallArgs(0);
|
|
753
|
+
|
|
754
|
+
const blocked = await executor.execute(
|
|
755
|
+
"resume-lease-async-blocked",
|
|
756
|
+
{ action: "resume", id: sourceRunId, message: "Conflicting follow-up" },
|
|
757
|
+
new AbortController().signal,
|
|
758
|
+
undefined,
|
|
759
|
+
makeMinimalCtx(tempDir),
|
|
760
|
+
);
|
|
761
|
+
assert.equal(blocked.isError, true);
|
|
762
|
+
assert.match(blocked.content[0]?.text ?? "", new RegExp(`already owned by run '${firstRevivedId}'`));
|
|
763
|
+
assert.equal(fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).length, 1, "contending revival must not spawn Pi");
|
|
764
|
+
|
|
765
|
+
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
766
|
+
await waitForFile(path.join(RESULTS_DIR, `${firstRevivedId}.json`));
|
|
767
|
+
await waitForMissingPath(sessionLeaseDir(sessionFile));
|
|
768
|
+
const afterRelease = await executor.execute(
|
|
769
|
+
"resume-lease-async-after-release",
|
|
770
|
+
{ action: "resume", id: sourceRunId, message: "Follow-up after release" },
|
|
771
|
+
new AbortController().signal,
|
|
772
|
+
undefined,
|
|
773
|
+
makeMinimalCtx(tempDir),
|
|
774
|
+
);
|
|
775
|
+
assert.equal(afterRelease.isError, undefined);
|
|
776
|
+
assert.ok(afterRelease.details?.asyncId);
|
|
777
|
+
await waitForFile(path.join(RESULTS_DIR, `${afterRelease.details.asyncId}.json`));
|
|
778
|
+
} finally {
|
|
779
|
+
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
780
|
+
fs.rmSync(sourceAsyncDir, { recursive: true, force: true });
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
584
784
|
it("resume action revives a completed foreground child by index", async () => {
|
|
585
785
|
mockPi.onCall({ output: "first child done" });
|
|
586
786
|
mockPi.onCall({ output: "second child done" });
|
|
@@ -622,6 +822,65 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
622
822
|
}
|
|
623
823
|
});
|
|
624
824
|
|
|
825
|
+
it("applies the same exclusive lease to remembered foreground revival", async () => {
|
|
826
|
+
const releasePath = path.join(tempDir, "release-foreground-revival");
|
|
827
|
+
mockPi.onCall({ output: "original foreground answer" });
|
|
828
|
+
mockPi.onCall({ waitForPath: releasePath, output: "first foreground revival" });
|
|
829
|
+
mockPi.onCall({ output: "foreground revival after release" });
|
|
830
|
+
const { executor } = makeExecutor({ bridgeMode: "off" });
|
|
831
|
+
try {
|
|
832
|
+
const original = await executor.execute(
|
|
833
|
+
"foreground-lease-original",
|
|
834
|
+
{ agent: "worker", task: "Original task" },
|
|
835
|
+
new AbortController().signal,
|
|
836
|
+
undefined,
|
|
837
|
+
makeMinimalCtx(tempDir),
|
|
838
|
+
);
|
|
839
|
+
const sourceRunId = original.details?.runId;
|
|
840
|
+
const sessionFile = original.details?.results?.[0]?.sessionFile;
|
|
841
|
+
assert.ok(sourceRunId);
|
|
842
|
+
assert.ok(sessionFile);
|
|
843
|
+
|
|
844
|
+
const first = await executor.execute(
|
|
845
|
+
"foreground-lease-first",
|
|
846
|
+
{ action: "resume", id: sourceRunId, message: "First follow-up" },
|
|
847
|
+
new AbortController().signal,
|
|
848
|
+
undefined,
|
|
849
|
+
makeMinimalCtx(tempDir),
|
|
850
|
+
);
|
|
851
|
+
assert.equal(first.isError, undefined);
|
|
852
|
+
const firstRevivedId = first.details?.asyncId;
|
|
853
|
+
assert.ok(firstRevivedId);
|
|
854
|
+
|
|
855
|
+
const blocked = await executor.execute(
|
|
856
|
+
"foreground-lease-blocked",
|
|
857
|
+
{ action: "resume", id: sourceRunId, message: "Conflicting follow-up" },
|
|
858
|
+
new AbortController().signal,
|
|
859
|
+
undefined,
|
|
860
|
+
makeMinimalCtx(tempDir),
|
|
861
|
+
);
|
|
862
|
+
assert.equal(blocked.isError, true);
|
|
863
|
+
assert.match(blocked.content[0]?.text ?? "", new RegExp(`already owned by run '${firstRevivedId}'`));
|
|
864
|
+
assert.equal(fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).length, 2, "contending foreground revival must not spawn Pi");
|
|
865
|
+
|
|
866
|
+
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
867
|
+
await waitForFile(path.join(RESULTS_DIR, `${firstRevivedId}.json`));
|
|
868
|
+
await waitForMissingPath(sessionLeaseDir(sessionFile));
|
|
869
|
+
const afterRelease = await executor.execute(
|
|
870
|
+
"foreground-lease-after-release",
|
|
871
|
+
{ action: "resume", id: sourceRunId, message: "Follow-up after release" },
|
|
872
|
+
new AbortController().signal,
|
|
873
|
+
undefined,
|
|
874
|
+
makeMinimalCtx(tempDir),
|
|
875
|
+
);
|
|
876
|
+
assert.equal(afterRelease.isError, undefined);
|
|
877
|
+
assert.ok(afterRelease.details?.asyncId);
|
|
878
|
+
await waitForFile(path.join(RESULTS_DIR, `${afterRelease.details.asyncId}.json`));
|
|
879
|
+
} finally {
|
|
880
|
+
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
625
884
|
it("status recovers remembered detached foreground output after child exit", async () => {
|
|
626
885
|
mockPi.onCall({
|
|
627
886
|
steps: [
|
|
@@ -680,6 +939,152 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
680
939
|
assert.match(transcriptText, /final recovered answer/);
|
|
681
940
|
});
|
|
682
941
|
|
|
942
|
+
it("keeps detached acceptance pending and emits recovered foreground completion to the originating session", async () => {
|
|
943
|
+
const acceptanceReport = [
|
|
944
|
+
"final recovered answer",
|
|
945
|
+
"```acceptance-report",
|
|
946
|
+
JSON.stringify({
|
|
947
|
+
criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "review completed" }],
|
|
948
|
+
changedFiles: ["src/review.ts"],
|
|
949
|
+
testsAddedOrUpdated: ["test/review.test.ts"],
|
|
950
|
+
commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
|
|
951
|
+
validationOutput: ["npm test passed"],
|
|
952
|
+
residualRisks: [],
|
|
953
|
+
noStagedFiles: true,
|
|
954
|
+
}),
|
|
955
|
+
"```",
|
|
956
|
+
].join("\n");
|
|
957
|
+
mockPi.onCall({
|
|
958
|
+
steps: [
|
|
959
|
+
{ jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
|
|
960
|
+
{ delay: 75, jsonl: [events.assistantMessage(acceptanceReport)] },
|
|
961
|
+
],
|
|
962
|
+
});
|
|
963
|
+
const { executor, events: bus, state } = makeExecutor({ agents: [makeAgent("a", { systemPrompt: "Intercom orchestration channel:" })] });
|
|
964
|
+
let detachEmitted = false;
|
|
965
|
+
const original = await executor.execute(
|
|
966
|
+
"foreground-detached-completion-original",
|
|
967
|
+
{ agent: "a", task: "ask supervisor", acceptance: { level: "checked", criteria: ["Review completed"] } },
|
|
968
|
+
new AbortController().signal,
|
|
969
|
+
(update: { details?: { progress?: Array<{ currentTool?: string }> } }) => {
|
|
970
|
+
if (detachEmitted || !update.details?.progress?.some((entry) => entry.currentTool === "contact_supervisor")) return;
|
|
971
|
+
detachEmitted = true;
|
|
972
|
+
bus.emit(INTERCOM_DETACH_REQUEST_EVENT, { requestId: "single-detached-completion" });
|
|
973
|
+
},
|
|
974
|
+
makeMinimalCtx(tempDir),
|
|
975
|
+
);
|
|
976
|
+
const runId = original.details?.runId;
|
|
977
|
+
assert.ok(runId, "expected foreground run id");
|
|
978
|
+
assert.equal(original.details?.results?.[0]?.acceptance?.status, "pending");
|
|
979
|
+
assert.match(original.content[0]?.text ?? "", /subagent_wait\(\{ id:/);
|
|
980
|
+
const metadataPath = original.details?.results?.[0]?.artifactPaths?.metadataPath;
|
|
981
|
+
assert.ok(metadataPath);
|
|
982
|
+
const pendingMetadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8")) as { acceptance?: { status?: string } };
|
|
983
|
+
assert.equal(pendingMetadata.acceptance?.status, "pending");
|
|
984
|
+
|
|
985
|
+
const waited = await waitForSubagents({ id: runId, timeoutMs: 5000 }, undefined, {
|
|
986
|
+
state: state as never,
|
|
987
|
+
events: bus,
|
|
988
|
+
asyncDirRoot: path.join(tempDir, "async-runs"),
|
|
989
|
+
resultsDir: path.join(tempDir, "results"),
|
|
990
|
+
});
|
|
991
|
+
assert.equal(waited.isError, undefined);
|
|
992
|
+
assert.match(waited.content[0]?.text ?? "", /remembered detached foreground run/);
|
|
993
|
+
|
|
994
|
+
const completion = bus.emitted.find((event) => event.channel === SUBAGENT_FOREGROUND_COMPLETE_EVENT);
|
|
995
|
+
assert.ok(completion, "expected a foreground completion event");
|
|
996
|
+
const payload = completion.payload as {
|
|
997
|
+
runId?: string;
|
|
998
|
+
sessionId?: string;
|
|
999
|
+
source?: string;
|
|
1000
|
+
summary?: string;
|
|
1001
|
+
success?: boolean;
|
|
1002
|
+
};
|
|
1003
|
+
assert.equal(payload.runId, runId);
|
|
1004
|
+
assert.equal(payload.sessionId, "session-123");
|
|
1005
|
+
assert.equal(payload.source, "foreground");
|
|
1006
|
+
assert.equal(payload.success, true);
|
|
1007
|
+
assert.match(payload.summary ?? "", /final recovered answer/);
|
|
1008
|
+
const recoveredMetadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8")) as { exitCode?: number; acceptance?: { status?: string; childReport?: unknown } };
|
|
1009
|
+
assert.equal(recoveredMetadata.exitCode, 0);
|
|
1010
|
+
assert.equal(recoveredMetadata.acceptance?.status, "checked");
|
|
1011
|
+
assert.ok(recoveredMetadata.acceptance?.childReport);
|
|
1012
|
+
|
|
1013
|
+
const status = await executor.execute(
|
|
1014
|
+
"foreground-detached-completion-status",
|
|
1015
|
+
{ action: "status", id: runId },
|
|
1016
|
+
new AbortController().signal,
|
|
1017
|
+
undefined,
|
|
1018
|
+
makeMinimalCtx(tempDir),
|
|
1019
|
+
);
|
|
1020
|
+
assert.match(status.content[0]?.text ?? "", /acceptance: checked/);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("preserves deferred acceptance failure details in foreground completion and status", async () => {
|
|
1024
|
+
const rejectedReport = [
|
|
1025
|
+
"final answer with rejected acceptance evidence",
|
|
1026
|
+
"```acceptance-report",
|
|
1027
|
+
JSON.stringify({
|
|
1028
|
+
criteriaSatisfied: [{ id: "criterion-1", status: "not-satisfied", evidence: "review found a blocker" }],
|
|
1029
|
+
changedFiles: ["src/review.ts"],
|
|
1030
|
+
testsAddedOrUpdated: ["test/review.test.ts"],
|
|
1031
|
+
commandsRun: [{ command: "npm test", result: "failed", summary: "blocker remains" }],
|
|
1032
|
+
validationOutput: ["blocker remains"],
|
|
1033
|
+
residualRisks: ["review blocker"],
|
|
1034
|
+
noStagedFiles: true,
|
|
1035
|
+
}),
|
|
1036
|
+
"```",
|
|
1037
|
+
].join("\n");
|
|
1038
|
+
mockPi.onCall({
|
|
1039
|
+
steps: [
|
|
1040
|
+
{ jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
|
|
1041
|
+
{ delay: 75, jsonl: [events.assistantMessage(rejectedReport)] },
|
|
1042
|
+
],
|
|
1043
|
+
});
|
|
1044
|
+
const { executor, events: bus, state } = makeExecutor({ agents: [makeAgent("a", { systemPrompt: "Intercom orchestration channel:" })] });
|
|
1045
|
+
let detachEmitted = false;
|
|
1046
|
+
const original = await executor.execute(
|
|
1047
|
+
"foreground-detached-failed-acceptance",
|
|
1048
|
+
{ agent: "a", task: "ask supervisor", acceptance: { level: "checked", criteria: ["Review completed"] } },
|
|
1049
|
+
new AbortController().signal,
|
|
1050
|
+
(update: { details?: { progress?: Array<{ currentTool?: string }> } }) => {
|
|
1051
|
+
if (detachEmitted || !update.details?.progress?.some((entry) => entry.currentTool === "contact_supervisor")) return;
|
|
1052
|
+
detachEmitted = true;
|
|
1053
|
+
bus.emit(INTERCOM_DETACH_REQUEST_EVENT, { requestId: "single-detached-failed-acceptance" });
|
|
1054
|
+
},
|
|
1055
|
+
makeMinimalCtx(tempDir),
|
|
1056
|
+
);
|
|
1057
|
+
const runId = original.details?.runId;
|
|
1058
|
+
assert.ok(runId);
|
|
1059
|
+
assert.equal(original.details?.results?.[0]?.acceptance?.status, "pending");
|
|
1060
|
+
|
|
1061
|
+
const waited = await waitForSubagents({ id: runId, timeoutMs: 5000 }, undefined, {
|
|
1062
|
+
state: state as never,
|
|
1063
|
+
events: bus,
|
|
1064
|
+
asyncDirRoot: path.join(tempDir, "async-runs"),
|
|
1065
|
+
resultsDir: path.join(tempDir, "results"),
|
|
1066
|
+
});
|
|
1067
|
+
assert.equal(waited.isError, undefined);
|
|
1068
|
+
assert.match(waited.content[0]?.text ?? "", /1 failed/);
|
|
1069
|
+
|
|
1070
|
+
const completion = bus.emitted.find((event) => event.channel === SUBAGENT_FOREGROUND_COMPLETE_EVENT);
|
|
1071
|
+
assert.ok(completion);
|
|
1072
|
+
const payload = completion.payload as { success?: boolean; summary?: string };
|
|
1073
|
+
assert.equal(payload.success, false);
|
|
1074
|
+
assert.match(payload.summary ?? "", /Acceptance rejected/);
|
|
1075
|
+
assert.match(payload.summary ?? "", /final answer with rejected acceptance evidence/);
|
|
1076
|
+
|
|
1077
|
+
const status = await executor.execute(
|
|
1078
|
+
"foreground-detached-failed-status",
|
|
1079
|
+
{ action: "status", id: runId },
|
|
1080
|
+
new AbortController().signal,
|
|
1081
|
+
undefined,
|
|
1082
|
+
makeMinimalCtx(tempDir),
|
|
1083
|
+
);
|
|
1084
|
+
assert.match(status.content[0]?.text ?? "", /acceptance: rejected/);
|
|
1085
|
+
assert.match(status.content[0]?.text ?? "", /error: Acceptance rejected/);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
683
1088
|
it("status recovers remembered detached chain output after child exit", async () => {
|
|
684
1089
|
mockPi.onCall({
|
|
685
1090
|
steps: [
|
|
@@ -796,6 +1201,45 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
796
1201
|
assert.match(transcript.content[0]?.text ?? "", /second recovered answer/);
|
|
797
1202
|
});
|
|
798
1203
|
|
|
1204
|
+
it("blocks sibling resume while any remembered foreground child remains detached", async () => {
|
|
1205
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b")] });
|
|
1206
|
+
const siblingSession = path.join(tempDir, "completed-sibling.jsonl");
|
|
1207
|
+
fs.writeFileSync(siblingSession, "", "utf-8");
|
|
1208
|
+
state.foregroundRuns.set("mixed-detached-run", {
|
|
1209
|
+
runId: "mixed-detached-run",
|
|
1210
|
+
mode: "parallel",
|
|
1211
|
+
cwd: tempDir,
|
|
1212
|
+
sessionId: "session-123",
|
|
1213
|
+
updatedAt: Date.now(),
|
|
1214
|
+
children: [
|
|
1215
|
+
{ agent: "a", index: 0, status: "detached", updatedAt: Date.now() },
|
|
1216
|
+
{ agent: "b", index: 1, status: "completed", sessionFile: siblingSession, updatedAt: Date.now() },
|
|
1217
|
+
],
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
const status = await executor.execute(
|
|
1221
|
+
"mixed-detached-status",
|
|
1222
|
+
{ action: "status", id: "mixed-detached-run" },
|
|
1223
|
+
new AbortController().signal,
|
|
1224
|
+
undefined,
|
|
1225
|
+
makeMinimalCtx(tempDir),
|
|
1226
|
+
);
|
|
1227
|
+
const statusText = status.content[0]?.text ?? "";
|
|
1228
|
+
assert.match(statusText, /do not resume or launch a replacement while any child remains detached/);
|
|
1229
|
+
assert.doesNotMatch(statusText, /Revive child:/);
|
|
1230
|
+
|
|
1231
|
+
const resumed = await executor.execute(
|
|
1232
|
+
"mixed-detached-resume",
|
|
1233
|
+
{ action: "resume", id: "mixed-detached-run", index: 1, message: "replace sibling" },
|
|
1234
|
+
new AbortController().signal,
|
|
1235
|
+
undefined,
|
|
1236
|
+
makeMinimalCtx(tempDir),
|
|
1237
|
+
);
|
|
1238
|
+
assert.equal(resumed.isError, true);
|
|
1239
|
+
assert.match(resumed.content[0]?.text ?? "", /cannot be revived safely while any child may still be live/);
|
|
1240
|
+
assert.match(resumed.content[0]?.text ?? "", /do not launch a replacement/);
|
|
1241
|
+
});
|
|
1242
|
+
|
|
799
1243
|
it("resume action rejects detached foreground children that may still be live", async () => {
|
|
800
1244
|
mockPi.onCall({
|
|
801
1245
|
steps: [
|
|
@@ -821,6 +1265,18 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
821
1265
|
const runId = original.details?.runId;
|
|
822
1266
|
assert.ok(runId, "expected foreground run id");
|
|
823
1267
|
|
|
1268
|
+
const fleet = await executor.execute(
|
|
1269
|
+
"foreground-detached-fleet",
|
|
1270
|
+
{ action: "status", view: "fleet" },
|
|
1271
|
+
new AbortController().signal,
|
|
1272
|
+
undefined,
|
|
1273
|
+
makeMinimalCtx(tempDir),
|
|
1274
|
+
);
|
|
1275
|
+
const fleetText = fleet.content[0]?.text ?? "";
|
|
1276
|
+
assert.match(fleetText, /Detached foreground runs:/);
|
|
1277
|
+
assert.ok(fleetText.includes(runId));
|
|
1278
|
+
assert.match(fleetText, /recovery: reply to the supervisor request first/);
|
|
1279
|
+
|
|
824
1280
|
const resumed = await executor.execute(
|
|
825
1281
|
"foreground-detached-resume",
|
|
826
1282
|
{ action: "resume", id: runId, message: "Follow up" },
|
|
@@ -835,6 +1291,50 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
835
1291
|
assert.doesNotMatch(resumed.content[0]?.text ?? "", /revive only/);
|
|
836
1292
|
});
|
|
837
1293
|
|
|
1294
|
+
it("does not inspect or resume remembered foreground runs from another parent session", async () => {
|
|
1295
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a")] });
|
|
1296
|
+
const sessionFile = path.join(tempDir, "other-session-child.jsonl");
|
|
1297
|
+
fs.writeFileSync(sessionFile, "", "utf-8");
|
|
1298
|
+
state.foregroundRuns.set("other-session-run", {
|
|
1299
|
+
runId: "other-session-run",
|
|
1300
|
+
mode: "single",
|
|
1301
|
+
cwd: tempDir,
|
|
1302
|
+
sessionId: "session-other",
|
|
1303
|
+
updatedAt: Date.now(),
|
|
1304
|
+
children: [{ agent: "a", index: 0, status: "completed", sessionFile }],
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
const status = await executor.execute(
|
|
1308
|
+
"other-session-status",
|
|
1309
|
+
{ action: "status", id: "other-session-run" },
|
|
1310
|
+
new AbortController().signal,
|
|
1311
|
+
undefined,
|
|
1312
|
+
makeMinimalCtx(tempDir),
|
|
1313
|
+
);
|
|
1314
|
+
assert.equal(status.isError, true);
|
|
1315
|
+
assert.doesNotMatch(status.content[0]?.text ?? "", /State: remembered foreground/);
|
|
1316
|
+
|
|
1317
|
+
const fleet = await executor.execute(
|
|
1318
|
+
"other-session-fleet",
|
|
1319
|
+
{ action: "status", view: "fleet" },
|
|
1320
|
+
new AbortController().signal,
|
|
1321
|
+
undefined,
|
|
1322
|
+
makeMinimalCtx(tempDir),
|
|
1323
|
+
);
|
|
1324
|
+
assert.doesNotMatch(fleet.content[0]?.text ?? "", /other-session-run/);
|
|
1325
|
+
|
|
1326
|
+
const resumed = await executor.execute(
|
|
1327
|
+
"other-session-resume",
|
|
1328
|
+
{ action: "resume", id: "other-session-run", message: "Follow up" },
|
|
1329
|
+
new AbortController().signal,
|
|
1330
|
+
undefined,
|
|
1331
|
+
makeMinimalCtx(tempDir),
|
|
1332
|
+
);
|
|
1333
|
+
assert.equal(resumed.isError, true);
|
|
1334
|
+
assert.doesNotMatch(resumed.content[0]?.text ?? "", /Revived foreground subagent/);
|
|
1335
|
+
assert.equal(mockPi.callCount(), 0);
|
|
1336
|
+
});
|
|
1337
|
+
|
|
838
1338
|
it("resume action keeps exact foreground validation errors over async prefix matches", async () => {
|
|
839
1339
|
const base = `exact-invalid-${Date.now()}`;
|
|
840
1340
|
const asyncSession = path.join(tempDir, "async-exact-prefix.jsonl");
|
|
@@ -856,6 +1356,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
856
1356
|
runId: base,
|
|
857
1357
|
mode: "single",
|
|
858
1358
|
cwd: tempDir,
|
|
1359
|
+
sessionId: "session-123",
|
|
859
1360
|
updatedAt: Date.now(),
|
|
860
1361
|
children: [{ agent: "a", index: 0, status: "completed" }],
|
|
861
1362
|
});
|
|
@@ -885,6 +1386,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
885
1386
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
886
1387
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
887
1388
|
runId: base,
|
|
1389
|
+
sessionId: "session-123",
|
|
888
1390
|
mode: "single",
|
|
889
1391
|
state: "complete",
|
|
890
1392
|
startedAt: 100,
|
|
@@ -897,6 +1399,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
897
1399
|
runId: `${base}-foreground`,
|
|
898
1400
|
mode: "single",
|
|
899
1401
|
cwd: tempDir,
|
|
1402
|
+
sessionId: "session-123",
|
|
900
1403
|
updatedAt: Date.now(),
|
|
901
1404
|
children: [{ agent: "a", index: 0, status: "completed", sessionFile: foregroundSession }],
|
|
902
1405
|
});
|
|
@@ -932,6 +1435,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
932
1435
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
933
1436
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
934
1437
|
runId,
|
|
1438
|
+
sessionId: "session-123",
|
|
935
1439
|
mode: "single",
|
|
936
1440
|
state: "complete",
|
|
937
1441
|
startedAt: 100,
|
|
@@ -945,6 +1449,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
945
1449
|
runId: `${base}-foreground`,
|
|
946
1450
|
mode: "single",
|
|
947
1451
|
cwd: tempDir,
|
|
1452
|
+
sessionId: "session-123",
|
|
948
1453
|
updatedAt: Date.now(),
|
|
949
1454
|
children: [{ agent: "a", index: 0, status: "completed", sessionFile: foregroundSession }],
|
|
950
1455
|
});
|
|
@@ -978,6 +1483,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
978
1483
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
979
1484
|
fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify({
|
|
980
1485
|
runId: asyncId,
|
|
1486
|
+
sessionId: "session-123",
|
|
981
1487
|
mode: "single",
|
|
982
1488
|
state: "complete",
|
|
983
1489
|
startedAt: 100,
|
|
@@ -990,6 +1496,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
990
1496
|
runId: foregroundId,
|
|
991
1497
|
mode: "single",
|
|
992
1498
|
cwd: tempDir,
|
|
1499
|
+
sessionId: "session-123",
|
|
993
1500
|
updatedAt: Date.now(),
|
|
994
1501
|
children: [{ agent: "a", index: 0, status: "completed", sessionFile: foregroundSession }],
|
|
995
1502
|
});
|