@selesai/code 0.5.28 → 0.5.29
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/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +117 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +20 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +11 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/llama/provider.d.ts.map +1 -1
- package/dist/core/llama/provider.js +8 -3
- package/dist/core/llama/provider.js.map +1 -1
- package/dist/core/model-config.d.ts +30 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +20 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -4
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +13 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +24 -11
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +66 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +34 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +3 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
- package/dist/extensions/pi-intercom/README.md +102 -39
- package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
- package/dist/extensions/pi-intercom/broker/client.ts +315 -12
- package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
- package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
- package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
- package/dist/extensions/pi-intercom/config.test.ts +93 -0
- package/dist/extensions/pi-intercom/config.ts +55 -6
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
- package/dist/extensions/pi-intercom/cwd.ts +31 -0
- package/dist/extensions/pi-intercom/extension-api.ts +44 -0
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
- package/dist/extensions/pi-intercom/format-context.ts +32 -0
- package/dist/extensions/pi-intercom/index.ts +742 -145
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
- package/dist/extensions/pi-intercom/package.json +15 -5
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
- package/dist/extensions/pi-intercom/types.ts +94 -4
- package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
- package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
- package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +85 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +146 -52
- package/dist/extensions/pi-subagents/agents/architect.md +1 -0
- package/dist/extensions/pi-subagents/agents/builder.md +1 -0
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +3 -3
- package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +21 -989
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +282 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
- package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
- package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +63 -16
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +29 -4
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +143 -43
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
- package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +155 -10
- package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
- package/dist/extensions/pi-subagents/src/tui/render.ts +27 -28
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +118 -12
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +102 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +11 -11
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +10 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +46 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +34 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +3 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +43 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +3 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +10 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -46
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +4 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +72 -64
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +1 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +14 -5
- package/docs/environment-variables.md +86 -0
- package/docs/extensions.md +15 -5
- package/docs/index.md +1 -0
- package/docs/models.md +9 -2
- package/docs/providers.md +21 -2
- package/docs/rpc.md +23 -6
- package/docs/session-format.md +2 -0
- package/docs/settings.md +1 -1
- package/docs/usage.md +0 -13
- package/examples/extensions/custom-compaction.ts +5 -2
- package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +11 -3
- package/examples/extensions/message-renderer.ts +3 -3
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +5 -2
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/package.json +15 -6
- package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -64,7 +64,7 @@ interface TestParallelTask {
|
|
|
64
64
|
gateOn?: "execution" | "acceptance";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
type TestChainStep = TestSequentialStep | {
|
|
67
|
+
type TestChainStep = { checkpoint: string; message?: string; label?: string; phase?: string } | TestSequentialStep | {
|
|
68
68
|
parallel: TestParallelTask[];
|
|
69
69
|
concurrency?: number;
|
|
70
70
|
failFast?: boolean;
|
|
@@ -91,6 +91,7 @@ type TestChainStep = TestSequentialStep | {
|
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
interface ChainResultItem {
|
|
94
|
+
index: number;
|
|
94
95
|
agent: string;
|
|
95
96
|
exitCode: number;
|
|
96
97
|
finalOutput?: string;
|
|
@@ -118,6 +119,7 @@ interface ChainExecutionResult {
|
|
|
118
119
|
currentStepIndex?: number;
|
|
119
120
|
outputs?: Record<string, { text: string; structured?: unknown }>;
|
|
120
121
|
parallelHandoff?: { version: number; path: string; groupCount: number; childCount: number; cleanupState: string };
|
|
122
|
+
checkpoint?: { name?: string; status?: string; message?: string };
|
|
121
123
|
};
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -215,6 +217,27 @@ describe("chain execution — sequential", { skip: !available ? "pi packages not
|
|
|
215
217
|
);
|
|
216
218
|
}
|
|
217
219
|
|
|
220
|
+
it("pauses foreground chains at approval checkpoints without launching the next child", async () => {
|
|
221
|
+
mockPi.onCall({ output: "analysis done" });
|
|
222
|
+
mockPi.onCall({ output: "should not run" });
|
|
223
|
+
const agents = [makeAgent("analyst"), makeAgent("worker")];
|
|
224
|
+
|
|
225
|
+
const result = await executeChain!(makeChainParams([
|
|
226
|
+
{ agent: "analyst", task: "Analyze" },
|
|
227
|
+
{ checkpoint: "review", message: "Approve implementation?" },
|
|
228
|
+
{ agent: "worker", task: "Implement" },
|
|
229
|
+
], agents));
|
|
230
|
+
|
|
231
|
+
assert.equal(result.isError, undefined);
|
|
232
|
+
assert.match(result.content[0]!.text, /Chain paused at checkpoint 'review'/);
|
|
233
|
+
assert.equal(result.details.results.length, 1);
|
|
234
|
+
assert.equal(result.details.checkpoint?.name, "review");
|
|
235
|
+
assert.equal(result.details.checkpoint?.status, "pending");
|
|
236
|
+
assert.equal(result.details.workflowGraph?.nodes[1]?.kind, "checkpoint");
|
|
237
|
+
assert.equal(result.details.workflowGraph?.nodes[1]?.status, "paused");
|
|
238
|
+
assert.equal(mockPi.callCount(), 1);
|
|
239
|
+
});
|
|
240
|
+
|
|
218
241
|
it("runs a 2-step chain", async () => {
|
|
219
242
|
mockPi.onCall({ output: "Analysis complete: found 3 issues" });
|
|
220
243
|
const agents = [makeAgent("analyst"), makeAgent("reporter")];
|
|
@@ -1361,6 +1384,7 @@ describe("chain execution — parallel steps", { skip: !available ? "pi packages
|
|
|
1361
1384
|
|
|
1362
1385
|
assert.ok(!result.isError, `should succeed: ${JSON.stringify(result.content)}`);
|
|
1363
1386
|
assert.equal(result.details.results.length, 2);
|
|
1387
|
+
assert.deepEqual(result.details.results.map((row) => row.index), [0, 1]);
|
|
1364
1388
|
});
|
|
1365
1389
|
|
|
1366
1390
|
it("aggregates worktree handoffs across foreground chain groups", { skip: process.platform === "win32" ? "worktree paths differ on Windows" : undefined }, async () => {
|
|
@@ -1700,6 +1724,7 @@ describe("chain execution — parallel steps", { skip: !available ? "pi packages
|
|
|
1700
1724
|
|
|
1701
1725
|
assert.ok(!result.isError);
|
|
1702
1726
|
assert.equal(result.details.results.length, 4);
|
|
1727
|
+
assert.deepEqual(result.details.results.map((row) => row.index), [0, 1, 2, 3]);
|
|
1703
1728
|
assert.equal(result.details.totalSteps, 3);
|
|
1704
1729
|
});
|
|
1705
1730
|
});
|
|
@@ -53,6 +53,18 @@ function assistantToolCall(toolName: string): Record<string, unknown> {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function emptyTerminalAssistant(): Record<string, unknown> {
|
|
57
|
+
return {
|
|
58
|
+
role: "assistant",
|
|
59
|
+
content: [],
|
|
60
|
+
api: "test",
|
|
61
|
+
provider: "test",
|
|
62
|
+
model: "test",
|
|
63
|
+
stopReason: "stop",
|
|
64
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: { total: 0 } },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
describe("detectSubagentError", { skip: !available ? "utils not importable" : undefined }, () => {
|
|
57
69
|
// ---- Basic detection (must still work) ----
|
|
58
70
|
|
|
@@ -197,6 +209,42 @@ describe("detectSubagentError", { skip: !available ? "utils not importable" : un
|
|
|
197
209
|
"tool-call assistant message without text is not a recovery");
|
|
198
210
|
});
|
|
199
211
|
|
|
212
|
+
it("accepts a successful tool recovery followed by a clean empty terminal stop", () => {
|
|
213
|
+
const messages = [
|
|
214
|
+
toolResult("bash", "error: process exited with code 1", true),
|
|
215
|
+
assistantToolCall("edit"),
|
|
216
|
+
toolResult("edit", "Updated src/file.ts"),
|
|
217
|
+
emptyTerminalAssistant(),
|
|
218
|
+
];
|
|
219
|
+
const result = detectSubagentError(messages);
|
|
220
|
+
assert.equal(result.hasError, false,
|
|
221
|
+
"later successful work plus a clean terminal stop recovers the earlier tool error");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("keeps the final failed tool result despite a clean empty terminal stop", () => {
|
|
225
|
+
const messages = [
|
|
226
|
+
toolResult("edit", "Updated src/file.ts"),
|
|
227
|
+
assistantToolCall("bash"),
|
|
228
|
+
toolResult("bash", "error: process exited with code 2", true),
|
|
229
|
+
emptyTerminalAssistant(),
|
|
230
|
+
];
|
|
231
|
+
const result = detectSubagentError(messages);
|
|
232
|
+
assert.equal(result.hasError, true,
|
|
233
|
+
"a clean terminal stop alone cannot recover the final failed tool result");
|
|
234
|
+
assert.equal(result.exitCode, 2);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("does not recover an earlier tool error after an unsuccessful terminal stop", () => {
|
|
238
|
+
const messages = [
|
|
239
|
+
toolResult("bash", "error: process exited with code 1", true),
|
|
240
|
+
toolResult("edit", "Updated src/file.ts"),
|
|
241
|
+
{ ...emptyTerminalAssistant(), errorMessage: "provider transport failed" },
|
|
242
|
+
];
|
|
243
|
+
const result = detectSubagentError(messages);
|
|
244
|
+
assert.equal(result.hasError, true,
|
|
245
|
+
"the terminal assistant stop must itself be clean");
|
|
246
|
+
});
|
|
247
|
+
|
|
200
248
|
it("does not treat empty/whitespace assistant message as recovery", () => {
|
|
201
249
|
const messages = [
|
|
202
250
|
toolResult("read", "EISDIR: illegal operation on a directory", true),
|
|
@@ -1863,14 +1863,14 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
|
|
|
1863
1863
|
process.env.USERPROFILE = tempHome;
|
|
1864
1864
|
const worktreeDir = path.join(tempDir, "worktree");
|
|
1865
1865
|
fs.mkdirSync(worktreeDir, { recursive: true });
|
|
1866
|
-
writeProjectOverride(tempDir, "
|
|
1867
|
-
writeProjectOverride(worktreeDir, "
|
|
1866
|
+
writeProjectOverride(tempDir, "commentator", "openai/gpt-5-main");
|
|
1867
|
+
writeProjectOverride(worktreeDir, "commentator", "openai/gpt-5-worktree");
|
|
1868
1868
|
const executor = makeExecutor();
|
|
1869
1869
|
|
|
1870
1870
|
try {
|
|
1871
1871
|
const result = await executor.execute(
|
|
1872
1872
|
"id",
|
|
1873
|
-
{ action: "get", agent: "
|
|
1873
|
+
{ action: "get", agent: "commentator", cwd: "worktree" },
|
|
1874
1874
|
new AbortController().signal,
|
|
1875
1875
|
undefined,
|
|
1876
1876
|
makeCtx(makeSessionManagerRecorder().manager),
|
|
@@ -144,6 +144,14 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
async function waitForCallCount(minimum: number, timeoutMs = 10_000): Promise<void> {
|
|
148
|
+
const deadline = Date.now() + timeoutMs;
|
|
149
|
+
while (mockPi.callCount() < minimum) {
|
|
150
|
+
if (Date.now() > deadline) assert.fail(`Timed out waiting for ${minimum} mock pi calls; saw ${mockPi.callCount()}`);
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
async function waitForMissingPath(filePath: string, timeoutMs = 10_000): Promise<void> {
|
|
148
156
|
const deadline = Date.now() + timeoutMs;
|
|
149
157
|
while (fs.existsSync(filePath)) {
|
|
@@ -152,7 +160,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
function makeExecutor(options: { bridgeMode?: "always" | "off"; agents?: ReturnType<typeof makeAgent>[]; acknowledgeResults?: boolean; kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean } = {}) {
|
|
163
|
+
function makeExecutor(options: { bridgeMode?: "always" | "off"; resultDelivery?: boolean; agents?: ReturnType<typeof makeAgent>[]; acknowledgeResults?: boolean; kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean } = {}) {
|
|
156
164
|
const events = createRecordingEventBus({ acknowledgeResults: options.acknowledgeResults ?? true });
|
|
157
165
|
const state = {
|
|
158
166
|
baseCwd: tempDir,
|
|
@@ -180,7 +188,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
180
188
|
},
|
|
181
189
|
state,
|
|
182
190
|
config: {
|
|
183
|
-
intercomBridge: {
|
|
191
|
+
intercomBridge: {
|
|
192
|
+
mode: options.bridgeMode ?? "always",
|
|
193
|
+
...(options.resultDelivery === undefined ? {} : { resultDelivery: options.resultDelivery }),
|
|
194
|
+
},
|
|
184
195
|
},
|
|
185
196
|
asyncByDefault: false,
|
|
186
197
|
tempArtifactsDir: tempDir,
|
|
@@ -235,6 +246,22 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
235
246
|
assert.match(result.content[0]?.text ?? "", /Legacy foreground output/);
|
|
236
247
|
});
|
|
237
248
|
|
|
249
|
+
it("keeps native foreground output without attempting external grouped delivery when disabled", async () => {
|
|
250
|
+
mockPi.onCall({ output: "Native foreground output" });
|
|
251
|
+
const { executor, events } = makeExecutor({ resultDelivery: false });
|
|
252
|
+
|
|
253
|
+
const result = await executor.execute(
|
|
254
|
+
"single-native-delivery",
|
|
255
|
+
{ agent: "worker", task: "Summarize feature" },
|
|
256
|
+
new AbortController().signal,
|
|
257
|
+
undefined,
|
|
258
|
+
makeMinimalCtx(tempDir),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
|
|
262
|
+
assert.match(result.content[0]?.text ?? "", /Native foreground output/);
|
|
263
|
+
});
|
|
264
|
+
|
|
238
265
|
it("falls back to legacy foreground output when grouped delivery is not acknowledged", async () => {
|
|
239
266
|
mockPi.onCall({ output: "Unacknowledged foreground output" });
|
|
240
267
|
const { executor, events } = makeExecutor({ acknowledgeResults: false });
|
|
@@ -271,12 +298,145 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
271
298
|
assert.equal((payload.children ?? []).every((child) => /^subagent-[ab]-[a-f0-9]+-[12]$/.test(child.intercomTarget ?? "")), true);
|
|
272
299
|
assert.match(String(payload.message ?? ""), /Intercom targets below identify child sessions used while they were running/);
|
|
273
300
|
assert.match(String(payload.message ?? ""), /Run intercom target: subagent-a-[a-f0-9]+-1/);
|
|
274
|
-
assert.match(String(payload.message ?? ""), /1\. a — completed/);
|
|
275
|
-
assert.match(String(payload.message ?? ""), /2\. b — completed/);
|
|
301
|
+
assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
|
|
302
|
+
assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
|
|
276
303
|
assert.match(result.content[0]?.text ?? "", /Delivered parallel subagent results via intercom\./);
|
|
277
304
|
assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
|
|
278
305
|
});
|
|
279
306
|
|
|
307
|
+
it("keeps a queued top-level parallel child controlled after an early outer rejection", async () => {
|
|
308
|
+
mockPi.onCall({ matchArgIncludes: "task-a", output: "early child", delay: 100 });
|
|
309
|
+
mockPi.onCall({ matchArgIncludes: "task-b", output: "middle child", delay: 400 });
|
|
310
|
+
mockPi.onCall({ matchArgIncludes: "task-c", output: "too late", delay: 10_000 });
|
|
311
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
312
|
+
let rejected = false;
|
|
313
|
+
const result = await executor.execute(
|
|
314
|
+
"parallel-rejection-cleanup",
|
|
315
|
+
{ concurrency: 2, tasks: [{ agent: "a", task: "task-a" }, { agent: "b", task: "task-b" }, { agent: "c", task: "task-c" }] },
|
|
316
|
+
new AbortController().signal,
|
|
317
|
+
(update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
|
|
318
|
+
if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
|
|
319
|
+
rejected = true;
|
|
320
|
+
throw new Error("reject early parallel completion");
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
makeMinimalCtx(tempDir),
|
|
324
|
+
);
|
|
325
|
+
assert.equal(rejected, true);
|
|
326
|
+
assert.equal(result.isError, true);
|
|
327
|
+
assert.equal(state.foregroundControls.size, 1, "remaining worker still owns queued scheduling");
|
|
328
|
+
await waitForCallCount(3);
|
|
329
|
+
const liveControl = [...state.foregroundControls.values()][0];
|
|
330
|
+
assert.equal(liveControl?.currentAgent, "c", "later queued child must remain discoverable after launch");
|
|
331
|
+
assert.ok(liveControl?.interrupt, "later queued child must remain interruptible");
|
|
332
|
+
assert.equal(liveControl.interrupt(), true);
|
|
333
|
+
for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
|
|
334
|
+
assert.equal(state.foregroundControls.size, 0, "control must disappear after workers and children settle");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("keeps a queued chain-parallel child controlled after an early outer rejection", async () => {
|
|
338
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-a", output: "early chain child", delay: 100 });
|
|
339
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-b", output: "middle chain child", delay: 400 });
|
|
340
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-c", output: "too late", delay: 10_000 });
|
|
341
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
342
|
+
let rejected = false;
|
|
343
|
+
const result = await executor.execute(
|
|
344
|
+
"chain-parallel-rejection-cleanup",
|
|
345
|
+
{ chain: [{ concurrency: 2, parallel: [{ agent: "a", task: "chain-task-a" }, { agent: "b", task: "chain-task-b" }, { agent: "c", task: "chain-task-c" }] }] },
|
|
346
|
+
new AbortController().signal,
|
|
347
|
+
(update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
|
|
348
|
+
if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
|
|
349
|
+
rejected = true;
|
|
350
|
+
throw new Error("reject early chain completion");
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
makeMinimalCtx(tempDir),
|
|
354
|
+
);
|
|
355
|
+
assert.equal(rejected, true);
|
|
356
|
+
assert.equal(result.isError, true);
|
|
357
|
+
assert.equal(state.foregroundControls.size, 1, "remaining chain worker still owns queued scheduling");
|
|
358
|
+
await waitForCallCount(3);
|
|
359
|
+
const liveControl = [...state.foregroundControls.values()][0];
|
|
360
|
+
assert.equal(liveControl?.currentAgent, "c", "later queued chain child must remain discoverable after launch");
|
|
361
|
+
assert.ok(liveControl?.interrupt, "later queued chain child must remain interruptible");
|
|
362
|
+
assert.equal(liveControl.interrupt(), true);
|
|
363
|
+
for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
|
|
364
|
+
assert.equal(state.foregroundControls.size, 0, "chain control must disappear after workers and children settle");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("cleans a rejected sequential chain child and releases foreground ownership", async () => {
|
|
368
|
+
mockPi.onCall({ output: "sequential child output" });
|
|
369
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
370
|
+
let rejected = false;
|
|
371
|
+
let ownedControl: { schedulingOwners?: number; activeChildren?: Map<number, unknown> } | undefined;
|
|
372
|
+
const result = await executor.execute(
|
|
373
|
+
"chain-sequential-rejection-cleanup",
|
|
374
|
+
{ chain: [{ agent: "worker", task: "sequential task" }] },
|
|
375
|
+
new AbortController().signal,
|
|
376
|
+
(update: { details?: { progress?: Array<{ status?: string }> } }) => {
|
|
377
|
+
if (rejected || !update.details?.progress?.some((entry) => entry.status === "completed")) return;
|
|
378
|
+
rejected = true;
|
|
379
|
+
ownedControl = [...state.foregroundControls.values()][0];
|
|
380
|
+
throw new Error("reject sequential chain completion");
|
|
381
|
+
},
|
|
382
|
+
makeMinimalCtx(tempDir),
|
|
383
|
+
);
|
|
384
|
+
assert.equal(rejected, true);
|
|
385
|
+
assert.equal(result.isError, true);
|
|
386
|
+
assert.ok(ownedControl);
|
|
387
|
+
assert.equal(ownedControl.schedulingOwners, 0);
|
|
388
|
+
assert.equal(ownedControl.activeChildren?.size, 0);
|
|
389
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("cleans a sequential chain child after an early tool-budget validation return", async () => {
|
|
393
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
394
|
+
const execution = executor.execute(
|
|
395
|
+
"chain-sequential-budget-cleanup",
|
|
396
|
+
{ chain: [{ agent: "worker", task: "invalid budget task", toolBudget: { hard: 0 } }] },
|
|
397
|
+
new AbortController().signal,
|
|
398
|
+
undefined,
|
|
399
|
+
makeMinimalCtx(tempDir),
|
|
400
|
+
);
|
|
401
|
+
const ownedControl = [...state.foregroundControls.values()][0];
|
|
402
|
+
assert.ok(ownedControl, "outer scheduling owner should remain visible until execution settles");
|
|
403
|
+
const result = await execution;
|
|
404
|
+
assert.equal(result.isError, true);
|
|
405
|
+
assert.match(result.content[0]?.text ?? "", /toolBudget\.hard must be an integer >= 1/);
|
|
406
|
+
assert.equal(mockPi.callCount(), 0);
|
|
407
|
+
assert.equal(ownedControl.schedulingOwners, 0);
|
|
408
|
+
assert.equal(ownedControl.activeChildren?.size, 0);
|
|
409
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("cleans an attached single rejection and its temporary structured runtime", async () => {
|
|
413
|
+
mockPi.onCall({
|
|
414
|
+
stdoutRaw: [
|
|
415
|
+
{ type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
|
|
416
|
+
{ type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
|
|
417
|
+
{ type: "tool_execution_end", toolName: "structured_output" },
|
|
418
|
+
].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
|
|
419
|
+
structuredOutputCapture: { ok: true },
|
|
420
|
+
});
|
|
421
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
422
|
+
let structuredDir: string | undefined;
|
|
423
|
+
const result = await executor.execute(
|
|
424
|
+
"single-rejection-cleanup",
|
|
425
|
+
{ agent: "worker", task: "structured task", artifacts: false, outputSchema: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"] } },
|
|
426
|
+
new AbortController().signal,
|
|
427
|
+
(update: { details?: { results?: Array<{ structuredOutputPath?: string }>; progress?: Array<{ status?: string }> } }) => {
|
|
428
|
+
const outputPath = update.details?.results?.[0]?.structuredOutputPath;
|
|
429
|
+
if (outputPath) structuredDir = path.dirname(outputPath);
|
|
430
|
+
if (update.details?.progress?.[0]?.status === "completed") throw new Error("reject attached single completion");
|
|
431
|
+
},
|
|
432
|
+
makeMinimalCtx(tempDir),
|
|
433
|
+
);
|
|
434
|
+
assert.equal(result.isError, true);
|
|
435
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
436
|
+
assert.ok(structuredDir);
|
|
437
|
+
assert.equal(fs.existsSync(structuredDir), false, "temporary structured runtime must be removed on rejection");
|
|
438
|
+
});
|
|
439
|
+
|
|
280
440
|
it("chain runs emit one grouped event containing all executed children", async () => {
|
|
281
441
|
mockPi.onCall({ output: "Chain child output" });
|
|
282
442
|
const { executor, events } = makeExecutor({ agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
@@ -300,9 +460,9 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
300
460
|
assert.equal(payload.mode, "chain");
|
|
301
461
|
assert.deepEqual((payload.children ?? []).map((child) => child.agent).sort(), ["a", "b", "c"]);
|
|
302
462
|
assert.equal((payload.children ?? []).every((child) => /^subagent-[abc]-[a-f0-9]+-[123]$/.test(child.intercomTarget ?? "")), true);
|
|
303
|
-
assert.match(String(payload.message ?? ""), /1\. a — completed/);
|
|
304
|
-
assert.match(String(payload.message ?? ""), /2\. b — completed/);
|
|
305
|
-
assert.match(String(payload.message ?? ""), /3\. c — completed/);
|
|
463
|
+
assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
|
|
464
|
+
assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
|
|
465
|
+
assert.match(String(payload.message ?? ""), /3\. c — process completed · output present/);
|
|
306
466
|
assert.match(result.content[0]?.text ?? "", /Delivered chain subagent results via intercom\./);
|
|
307
467
|
assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
|
|
308
468
|
});
|
|
@@ -631,7 +791,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
631
791
|
lastUpdate: 200,
|
|
632
792
|
cwd: tempDir,
|
|
633
793
|
sessionFile,
|
|
634
|
-
steps: [{ agent: "worker", status: "complete" }],
|
|
794
|
+
steps: [{ agent: "worker", status: "complete", launchContractDigest: "source-launch-contract-digest" }],
|
|
635
795
|
}, null, 2), "utf-8");
|
|
636
796
|
const { executor, events } = makeExecutor();
|
|
637
797
|
|
|
@@ -651,6 +811,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
651
811
|
assert.doesNotMatch(result.content[0]?.text ?? "", /Follow:/);
|
|
652
812
|
const revivedId = result.details?.asyncId;
|
|
653
813
|
assert.ok(revivedId, "expected revived async id");
|
|
814
|
+
assert.equal(result.details?.sourceLaunchContractDigest, "source-launch-contract-digest");
|
|
654
815
|
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
816
|
assert.equal(startedEvent?.goal, "What changed?");
|
|
656
817
|
const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
|
|
@@ -785,7 +946,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
785
946
|
mockPi.onCall({ output: "first child done" });
|
|
786
947
|
mockPi.onCall({ output: "second child done" });
|
|
787
948
|
mockPi.onCall({ output: "revived foreground answer" });
|
|
788
|
-
const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b")] });
|
|
949
|
+
const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b", { model: "anthropic/claude-sonnet-4", thinking: "high" })] });
|
|
789
950
|
|
|
790
951
|
const original = await executor.execute(
|
|
791
952
|
"foreground-resume-original",
|
|
@@ -797,6 +958,17 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
797
958
|
const runId = original.details?.runId;
|
|
798
959
|
assert.ok(runId, "expected foreground run id");
|
|
799
960
|
|
|
961
|
+
const overridden = await executor.execute(
|
|
962
|
+
"foreground-resume-model-override",
|
|
963
|
+
{ action: "resume", id: runId, index: 1, message: "Follow up with b", model: "openai/gpt-5" },
|
|
964
|
+
new AbortController().signal,
|
|
965
|
+
undefined,
|
|
966
|
+
makeMinimalCtx(tempDir),
|
|
967
|
+
);
|
|
968
|
+
assert.equal(overridden.isError, true);
|
|
969
|
+
assert.match(overridden.content[0]?.text ?? "", /reuses the persisted child model/);
|
|
970
|
+
assert.equal(mockPi.callCount(), 2, "rejected model override must not spawn a revival");
|
|
971
|
+
|
|
800
972
|
const revived = await executor.execute(
|
|
801
973
|
"foreground-resume",
|
|
802
974
|
{ action: "resume", id: runId, index: 1, message: "Follow up with b" },
|
|
@@ -808,10 +980,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
808
980
|
assert.equal(revived.isError, undefined);
|
|
809
981
|
assert.match(revived.content[0]?.text ?? "", /Revived foreground subagent from/);
|
|
810
982
|
assert.match(revived.content[0]?.text ?? "", /Agent: b/);
|
|
983
|
+
assert.equal(revived.details?.sourceLaunchContractDigest, original.details?.results?.[1]?.launchContractDigest);
|
|
984
|
+
assert.ok(revived.details?.sourceLaunchContractDigest, "expected foreground source launch contract digest");
|
|
811
985
|
const reviveArgs = await readMockCallArgs(2);
|
|
812
986
|
const selectedSession = original.details?.results?.[1]?.sessionFile;
|
|
813
987
|
assert.ok(selectedSession, "expected selected child session file");
|
|
814
988
|
assert.equal(reviveArgs[reviveArgs.indexOf("--session") + 1], selectedSession);
|
|
989
|
+
assert.equal(reviveArgs[reviveArgs.indexOf("--model") + 1], original.details?.results?.[1]?.model);
|
|
815
990
|
const revivedId = revived.details?.asyncId;
|
|
816
991
|
assert.ok(revivedId, "expected revived async id");
|
|
817
992
|
const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
|
|
@@ -861,7 +1036,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
861
1036
|
);
|
|
862
1037
|
assert.equal(blocked.isError, true);
|
|
863
1038
|
assert.match(blocked.content[0]?.text ?? "", new RegExp(`already owned by run '${firstRevivedId}'`));
|
|
864
|
-
|
|
1039
|
+
// The original and the first revival each spawn Pi asynchronously, so wait for both markers
|
|
1040
|
+
// before asserting the contending revival added no third spawn.
|
|
1041
|
+
await waitForCallCount(2);
|
|
1042
|
+
assert.equal(mockPi.callCount(), 2, "contending foreground revival must not spawn Pi");
|
|
865
1043
|
|
|
866
1044
|
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
867
1045
|
await waitForFile(path.join(RESULTS_DIR, `${firstRevivedId}.json`));
|
|
@@ -1273,9 +1451,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
1273
1451
|
makeMinimalCtx(tempDir),
|
|
1274
1452
|
);
|
|
1275
1453
|
const fleetText = fleet.content[0]?.text ?? "";
|
|
1276
|
-
assert.match(fleetText, /
|
|
1454
|
+
assert.match(fleetText, /Foreground runs:/);
|
|
1277
1455
|
assert.ok(fleetText.includes(runId));
|
|
1278
|
-
assert.match(fleetText, /
|
|
1456
|
+
assert.match(fleetText, /running/);
|
|
1457
|
+
assert.match(fleetText, /contact_supervisor/);
|
|
1279
1458
|
|
|
1280
1459
|
const resumed = await executor.execute(
|
|
1281
1460
|
"foreground-detached-resume",
|
|
@@ -1534,7 +1713,28 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
1534
1713
|
const payload = intercomEvents[0]!.payload as { status?: string; summary?: string; message?: string };
|
|
1535
1714
|
assert.equal(payload.status, "failed");
|
|
1536
1715
|
assert.match(String(payload.summary ?? ""), /1 completed, 1 failed/);
|
|
1537
|
-
assert.match(String(payload.message ?? ""), /
|
|
1716
|
+
assert.match(String(payload.message ?? ""), /Process status: failed/);
|
|
1717
|
+
assert.match(String(payload.message ?? ""), /Outputs: 2 present \(semantic adequacy unassessed\)/);
|
|
1718
|
+
assert.match(String(payload.message ?? ""), /Inspect that output before retrying/);
|
|
1538
1719
|
assert.match(result.content[0]?.text ?? "", /Children: 1 completed, 1 failed/);
|
|
1539
1720
|
});
|
|
1721
|
+
|
|
1722
|
+
it("does not treat a synthetic foreground startup error as child output", async () => {
|
|
1723
|
+
mockPi.onCall({ output: "", stderr: "mock startup failure", exitCode: 1 });
|
|
1724
|
+
const { executor, events } = makeExecutor({ agents: [makeAgent("worker")] });
|
|
1725
|
+
|
|
1726
|
+
await executor.execute(
|
|
1727
|
+
"foreground-synthetic-error",
|
|
1728
|
+
{ agent: "worker", task: "synthetic-error-task" },
|
|
1729
|
+
new AbortController().signal,
|
|
1730
|
+
undefined,
|
|
1731
|
+
makeMinimalCtx(tempDir),
|
|
1732
|
+
);
|
|
1733
|
+
|
|
1734
|
+
const intercomEvents = events.emitted.filter((entry) => entry.channel === "subagent:result-intercom");
|
|
1735
|
+
assert.equal(intercomEvents.length, 1);
|
|
1736
|
+
const message = String((intercomEvents[0]!.payload as { message?: string }).message ?? "");
|
|
1737
|
+
assert.match(message, /process failed · output absent/);
|
|
1738
|
+
assert.doesNotMatch(message, /Inspect that output before retrying/);
|
|
1739
|
+
});
|
|
1540
1740
|
});
|
|
@@ -227,6 +227,40 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
227
227
|
assert.equal(state.foregroundControls.size, 0);
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
+
it("exposes stable child indexes for counted top-level parallel results", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
231
|
+
mockPi.onCall({
|
|
232
|
+
steps: [
|
|
233
|
+
{ jsonl: [{ type: "tool_execution_start", toolName: "read", args: { path: "a.md" } }], delay: 10 },
|
|
234
|
+
{ jsonl: [{ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "Counted A done" }], model: "mock/test-model", stopReason: "stop", usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: { total: 0.001 } } } }] },
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
mockPi.onCall({
|
|
238
|
+
steps: [
|
|
239
|
+
{ jsonl: [{ type: "tool_execution_start", toolName: "read", args: { path: "b.md" } }], delay: 10 },
|
|
240
|
+
{ jsonl: [{ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "Counted B done" }], model: "mock/test-model", stopReason: "stop", usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: { total: 0.001 } } } }] },
|
|
241
|
+
],
|
|
242
|
+
});
|
|
243
|
+
const executor = makeExecutor();
|
|
244
|
+
const updateIndexes: number[] = [];
|
|
245
|
+
|
|
246
|
+
const result = await executor.execute(
|
|
247
|
+
"counted-parallel-indexes",
|
|
248
|
+
{ tasks: [{ agent: "echo", task: "Counted inspection", count: 2 }] },
|
|
249
|
+
new AbortController().signal,
|
|
250
|
+
(update) => {
|
|
251
|
+
for (const row of update.details?.results ?? []) updateIndexes.push(row.index);
|
|
252
|
+
},
|
|
253
|
+
makeMinimalCtx(tempDir),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
assert.equal(result.isError, undefined);
|
|
257
|
+
assert.deepEqual(result.details?.results.map((row: { index: number }) => row.index), [0, 1]);
|
|
258
|
+
assert.deepEqual([...new Set(result.details?.results.map((row: { index: number }) => row.index))], [0, 1]);
|
|
259
|
+
assert.ok(updateIndexes.includes(0));
|
|
260
|
+
assert.ok(updateIndexes.includes(1));
|
|
261
|
+
assert.ok(updateIndexes.every((index) => Number.isInteger(index)));
|
|
262
|
+
});
|
|
263
|
+
|
|
230
264
|
it("publishes a durable handoff before cleaning foreground parallel worktrees", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
231
265
|
git(["init"]);
|
|
232
266
|
git(["config", "user.email", "test@example.com"]);
|
|
@@ -454,6 +454,50 @@ describe("renderSubagentResult fork indicator", () => {
|
|
|
454
454
|
assert.match(text, /output: \/tmp\/reviewer_output\.md/);
|
|
455
455
|
});
|
|
456
456
|
|
|
457
|
+
it("shows model and thinking on running compact single and multi rows", () => {
|
|
458
|
+
const single = renderSubagentResult!({
|
|
459
|
+
content: [{ type: "text", text: "(running...)" }],
|
|
460
|
+
details: {
|
|
461
|
+
mode: "single",
|
|
462
|
+
results: [{
|
|
463
|
+
agent: "reviewer",
|
|
464
|
+
task: "review",
|
|
465
|
+
exitCode: 0,
|
|
466
|
+
messages: [],
|
|
467
|
+
usage: emptyUsage,
|
|
468
|
+
progress: {
|
|
469
|
+
index: 0,
|
|
470
|
+
agent: "reviewer",
|
|
471
|
+
status: "running",
|
|
472
|
+
task: "review",
|
|
473
|
+
model: "openai-codex/gpt-5.5",
|
|
474
|
+
thinking: "high",
|
|
475
|
+
recentTools: [],
|
|
476
|
+
recentOutput: [],
|
|
477
|
+
toolCount: 1,
|
|
478
|
+
tokens: 42,
|
|
479
|
+
durationMs: 3_000,
|
|
480
|
+
},
|
|
481
|
+
}],
|
|
482
|
+
},
|
|
483
|
+
}, { expanded: false }, theme).render(160).join("\n");
|
|
484
|
+
assert.match(single, /reviewer \(gpt-5\.5 · thinking high\)/);
|
|
485
|
+
|
|
486
|
+
const multi = renderSubagentResult!({
|
|
487
|
+
content: [{ type: "text", text: "(running...)" }],
|
|
488
|
+
details: {
|
|
489
|
+
mode: "parallel",
|
|
490
|
+
totalSteps: 2,
|
|
491
|
+
results: [
|
|
492
|
+
{ agent: "scout", task: "scan", exitCode: 0, messages: [], usage: emptyUsage, model: "anthropic/claude-haiku-4-5", thinking: "low", progress: { index: 0, agent: "scout", status: "running", task: "scan", recentTools: [], recentOutput: [], toolCount: 0, tokens: 0, durationMs: 0 } },
|
|
493
|
+
{ agent: "worker", task: "fix", exitCode: 0, messages: [], usage: emptyUsage, progress: { index: 1, agent: "worker", status: "running", task: "fix", model: "openai/gpt-5-mini", recentTools: [], recentOutput: [], toolCount: 0, tokens: 0, durationMs: 0 } },
|
|
494
|
+
],
|
|
495
|
+
},
|
|
496
|
+
}, { expanded: false }, theme).render(160).join("\n");
|
|
497
|
+
assert.match(multi, /Agent 1\/2: scout \(claude-haiku-4-5 · thinking low\)/);
|
|
498
|
+
assert.match(multi, /Agent 2\/2: worker \(gpt-5-mini\)/);
|
|
499
|
+
});
|
|
500
|
+
|
|
457
501
|
it("keeps running compact result output stable when progress is unchanged", async () => {
|
|
458
502
|
const result = {
|
|
459
503
|
content: [{ type: "text" as const, text: "(running...)" }],
|