@selesai/code 0.5.28 → 0.6.1
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/CHANGELOG.md +24 -0
- package/README.md +1 -1
- 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 +19 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/system-prompt.test.d.ts +2 -0
- package/dist/core/system-prompt.test.d.ts.map +1 -0
- package/dist/core/system-prompt.test.js +89 -0
- package/dist/core/system-prompt.test.js.map +1 -0
- 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/defaults/models.json +13 -45
- package/dist/defaults/settings.json +1 -2
- package/dist/extensions/copy-turn.test.ts +131 -0
- package/dist/extensions/copy-turn.ts +6 -1
- package/dist/extensions/package.json +0 -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 +88 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +158 -69
- package/dist/extensions/pi-subagents/agents/architect.md +5 -4
- package/dist/extensions/pi-subagents/agents/builder.md +6 -4
- package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
- package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
- package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
- 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 +22 -988
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -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 +281 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
- 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/agents/task-aware-routing.ts +125 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
- 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 +86 -21
- 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 +56 -5
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
- 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 +214 -60
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
- 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/single-output.ts +63 -9
- 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 +22 -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 +196 -12
- package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
- 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 +55 -34
- 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 +113 -8
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
- 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 +62 -22
- 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 +8 -6
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
- 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 +199 -17
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
- 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/real-session-runner.ts +18 -2
- 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-disabled.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
- 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/builtin-agent-documentation.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -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 +28 -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 +17 -12
- 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 +39 -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 +48 -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 +46 -2
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
- 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-aware-routing.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
- 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/skills/ponytail/SKILL.md +1 -3
- 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/plans/subagent-delegation/phase-0-correctness.md +265 -0
- package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
- package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
- package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
- package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
- 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 +16 -7
- package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
- package/dist/extensions/caveman/index.js +0 -118
- package/dist/extensions/caveman/package.json +0 -8
- package/dist/extensions/caveman/test/extension.test.js +0 -203
- package/dist/extensions/caveman/test/helpers.test.js +0 -58
- package/dist/skills/caveman/SKILL.md +0 -50
|
@@ -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,
|
|
@@ -216,10 +227,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
216
227
|
assert.match(result.content[0]?.text ?? "", /Delivered single subagent result via intercom\./);
|
|
217
228
|
assert.doesNotMatch(result.content[0]?.text ?? "", /Full child output from worker/);
|
|
218
229
|
assert.equal(result.details?.results?.[0]?.finalOutput, undefined);
|
|
219
|
-
|
|
230
|
+
// Reference-first intercom delivery: the child summary is the saved-output
|
|
231
|
+
// reference (error/status first for failed children), never raw child prose.
|
|
232
|
+
assert.match(String(payload.message ?? ""), /Output saved to: /);
|
|
233
|
+
assert.doesNotMatch(String(payload.message ?? ""), /Full child output from worker/);
|
|
220
234
|
});
|
|
221
235
|
|
|
222
|
-
it("
|
|
236
|
+
it("returns the native saved-output receipt when the bridge is inactive", async () => {
|
|
223
237
|
mockPi.onCall({ output: "Legacy foreground output" });
|
|
224
238
|
const { executor, events } = makeExecutor({ bridgeMode: "off" });
|
|
225
239
|
|
|
@@ -232,10 +246,31 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
232
246
|
);
|
|
233
247
|
|
|
234
248
|
assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
|
|
235
|
-
assert.match(result.content[0]?.text ?? "", /
|
|
249
|
+
assert.match(result.content[0]?.text ?? "", /Output saved to: /);
|
|
250
|
+
assert.doesNotMatch(result.content[0]?.text ?? "", /Legacy foreground output/);
|
|
251
|
+
const runId = result.details?.runId;
|
|
252
|
+
assert.ok(runId);
|
|
253
|
+
assert.equal(fs.readFileSync(path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId, "result.md"), "utf-8"), "Legacy foreground output");
|
|
236
254
|
});
|
|
237
255
|
|
|
238
|
-
it("
|
|
256
|
+
it("keeps the native saved-output receipt without attempting external grouped delivery when disabled", async () => {
|
|
257
|
+
mockPi.onCall({ output: "Native foreground output" });
|
|
258
|
+
const { executor, events } = makeExecutor({ resultDelivery: false });
|
|
259
|
+
|
|
260
|
+
const result = await executor.execute(
|
|
261
|
+
"single-native-delivery",
|
|
262
|
+
{ agent: "worker", task: "Summarize feature" },
|
|
263
|
+
new AbortController().signal,
|
|
264
|
+
undefined,
|
|
265
|
+
makeMinimalCtx(tempDir),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
|
|
269
|
+
assert.match(result.content[0]?.text ?? "", /Output saved to: /);
|
|
270
|
+
assert.doesNotMatch(result.content[0]?.text ?? "", /Native foreground output/);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("falls back to the compact native saved-output receipt when grouped delivery is not acknowledged", async () => {
|
|
239
274
|
mockPi.onCall({ output: "Unacknowledged foreground output" });
|
|
240
275
|
const { executor, events } = makeExecutor({ acknowledgeResults: false });
|
|
241
276
|
|
|
@@ -248,7 +283,8 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
248
283
|
);
|
|
249
284
|
|
|
250
285
|
assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), true);
|
|
251
|
-
assert.match(result.content[0]?.text ?? "", /
|
|
286
|
+
assert.match(result.content[0]?.text ?? "", /Output saved to: /);
|
|
287
|
+
assert.doesNotMatch(result.content[0]?.text ?? "", /Unacknowledged foreground output/);
|
|
252
288
|
});
|
|
253
289
|
|
|
254
290
|
it("top-level parallel runs emit one grouped event containing all children", async () => {
|
|
@@ -271,12 +307,145 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
271
307
|
assert.equal((payload.children ?? []).every((child) => /^subagent-[ab]-[a-f0-9]+-[12]$/.test(child.intercomTarget ?? "")), true);
|
|
272
308
|
assert.match(String(payload.message ?? ""), /Intercom targets below identify child sessions used while they were running/);
|
|
273
309
|
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/);
|
|
310
|
+
assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
|
|
311
|
+
assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
|
|
276
312
|
assert.match(result.content[0]?.text ?? "", /Delivered parallel subagent results via intercom\./);
|
|
277
313
|
assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
|
|
278
314
|
});
|
|
279
315
|
|
|
316
|
+
it("keeps a queued top-level parallel child controlled after an early outer rejection", async () => {
|
|
317
|
+
mockPi.onCall({ matchArgIncludes: "task-a", output: "early child", delay: 100 });
|
|
318
|
+
mockPi.onCall({ matchArgIncludes: "task-b", output: "middle child", delay: 400 });
|
|
319
|
+
mockPi.onCall({ matchArgIncludes: "task-c", output: "too late", delay: 10_000 });
|
|
320
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
321
|
+
let rejected = false;
|
|
322
|
+
const result = await executor.execute(
|
|
323
|
+
"parallel-rejection-cleanup",
|
|
324
|
+
{ concurrency: 2, tasks: [{ agent: "a", task: "task-a" }, { agent: "b", task: "task-b" }, { agent: "c", task: "task-c" }] },
|
|
325
|
+
new AbortController().signal,
|
|
326
|
+
(update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
|
|
327
|
+
if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
|
|
328
|
+
rejected = true;
|
|
329
|
+
throw new Error("reject early parallel completion");
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
makeMinimalCtx(tempDir),
|
|
333
|
+
);
|
|
334
|
+
assert.equal(rejected, true);
|
|
335
|
+
assert.equal(result.isError, true);
|
|
336
|
+
assert.equal(state.foregroundControls.size, 1, "remaining worker still owns queued scheduling");
|
|
337
|
+
await waitForCallCount(3);
|
|
338
|
+
const liveControl = [...state.foregroundControls.values()][0];
|
|
339
|
+
assert.equal(liveControl?.currentAgent, "c", "later queued child must remain discoverable after launch");
|
|
340
|
+
assert.ok(liveControl?.interrupt, "later queued child must remain interruptible");
|
|
341
|
+
assert.equal(liveControl.interrupt(), true);
|
|
342
|
+
for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
|
|
343
|
+
assert.equal(state.foregroundControls.size, 0, "control must disappear after workers and children settle");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("keeps a queued chain-parallel child controlled after an early outer rejection", async () => {
|
|
347
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-a", output: "early chain child", delay: 100 });
|
|
348
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-b", output: "middle chain child", delay: 400 });
|
|
349
|
+
mockPi.onCall({ matchArgIncludes: "chain-task-c", output: "too late", delay: 10_000 });
|
|
350
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
351
|
+
let rejected = false;
|
|
352
|
+
const result = await executor.execute(
|
|
353
|
+
"chain-parallel-rejection-cleanup",
|
|
354
|
+
{ chain: [{ concurrency: 2, parallel: [{ agent: "a", task: "chain-task-a" }, { agent: "b", task: "chain-task-b" }, { agent: "c", task: "chain-task-c" }] }] },
|
|
355
|
+
new AbortController().signal,
|
|
356
|
+
(update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
|
|
357
|
+
if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
|
|
358
|
+
rejected = true;
|
|
359
|
+
throw new Error("reject early chain completion");
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
makeMinimalCtx(tempDir),
|
|
363
|
+
);
|
|
364
|
+
assert.equal(rejected, true);
|
|
365
|
+
assert.equal(result.isError, true);
|
|
366
|
+
assert.equal(state.foregroundControls.size, 1, "remaining chain worker still owns queued scheduling");
|
|
367
|
+
await waitForCallCount(3);
|
|
368
|
+
const liveControl = [...state.foregroundControls.values()][0];
|
|
369
|
+
assert.equal(liveControl?.currentAgent, "c", "later queued chain child must remain discoverable after launch");
|
|
370
|
+
assert.ok(liveControl?.interrupt, "later queued chain child must remain interruptible");
|
|
371
|
+
assert.equal(liveControl.interrupt(), true);
|
|
372
|
+
for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
|
|
373
|
+
assert.equal(state.foregroundControls.size, 0, "chain control must disappear after workers and children settle");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("cleans a rejected sequential chain child and releases foreground ownership", async () => {
|
|
377
|
+
mockPi.onCall({ output: "sequential child output" });
|
|
378
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
379
|
+
let rejected = false;
|
|
380
|
+
let ownedControl: { schedulingOwners?: number; activeChildren?: Map<number, unknown> } | undefined;
|
|
381
|
+
const result = await executor.execute(
|
|
382
|
+
"chain-sequential-rejection-cleanup",
|
|
383
|
+
{ chain: [{ agent: "worker", task: "sequential task" }] },
|
|
384
|
+
new AbortController().signal,
|
|
385
|
+
(update: { details?: { progress?: Array<{ status?: string }> } }) => {
|
|
386
|
+
if (rejected || !update.details?.progress?.some((entry) => entry.status === "completed")) return;
|
|
387
|
+
rejected = true;
|
|
388
|
+
ownedControl = [...state.foregroundControls.values()][0];
|
|
389
|
+
throw new Error("reject sequential chain completion");
|
|
390
|
+
},
|
|
391
|
+
makeMinimalCtx(tempDir),
|
|
392
|
+
);
|
|
393
|
+
assert.equal(rejected, true);
|
|
394
|
+
assert.equal(result.isError, true);
|
|
395
|
+
assert.ok(ownedControl);
|
|
396
|
+
assert.equal(ownedControl.schedulingOwners, 0);
|
|
397
|
+
assert.equal(ownedControl.activeChildren?.size, 0);
|
|
398
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("cleans a sequential chain child after an early tool-budget validation return", async () => {
|
|
402
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
403
|
+
const execution = executor.execute(
|
|
404
|
+
"chain-sequential-budget-cleanup",
|
|
405
|
+
{ chain: [{ agent: "worker", task: "invalid budget task", toolBudget: { hard: 0 } }] },
|
|
406
|
+
new AbortController().signal,
|
|
407
|
+
undefined,
|
|
408
|
+
makeMinimalCtx(tempDir),
|
|
409
|
+
);
|
|
410
|
+
const ownedControl = [...state.foregroundControls.values()][0];
|
|
411
|
+
assert.ok(ownedControl, "outer scheduling owner should remain visible until execution settles");
|
|
412
|
+
const result = await execution;
|
|
413
|
+
assert.equal(result.isError, true);
|
|
414
|
+
assert.match(result.content[0]?.text ?? "", /toolBudget\.hard must be an integer >= 1/);
|
|
415
|
+
assert.equal(mockPi.callCount(), 0);
|
|
416
|
+
assert.equal(ownedControl.schedulingOwners, 0);
|
|
417
|
+
assert.equal(ownedControl.activeChildren?.size, 0);
|
|
418
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("cleans an attached single rejection and its temporary structured runtime", async () => {
|
|
422
|
+
mockPi.onCall({
|
|
423
|
+
stdoutRaw: [
|
|
424
|
+
{ type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
|
|
425
|
+
{ type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
|
|
426
|
+
{ type: "tool_execution_end", toolName: "structured_output" },
|
|
427
|
+
].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
|
|
428
|
+
structuredOutputCapture: { ok: true },
|
|
429
|
+
});
|
|
430
|
+
const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
|
|
431
|
+
let structuredDir: string | undefined;
|
|
432
|
+
const result = await executor.execute(
|
|
433
|
+
"single-rejection-cleanup",
|
|
434
|
+
{ agent: "worker", task: "structured task", artifacts: false, outputSchema: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"] } },
|
|
435
|
+
new AbortController().signal,
|
|
436
|
+
(update: { details?: { results?: Array<{ structuredOutputPath?: string }>; progress?: Array<{ status?: string }> } }) => {
|
|
437
|
+
const outputPath = update.details?.results?.[0]?.structuredOutputPath;
|
|
438
|
+
if (outputPath) structuredDir = path.dirname(outputPath);
|
|
439
|
+
if (update.details?.progress?.[0]?.status === "completed") throw new Error("reject attached single completion");
|
|
440
|
+
},
|
|
441
|
+
makeMinimalCtx(tempDir),
|
|
442
|
+
);
|
|
443
|
+
assert.equal(result.isError, true);
|
|
444
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
445
|
+
assert.ok(structuredDir);
|
|
446
|
+
assert.equal(fs.existsSync(structuredDir), false, "temporary structured runtime must be removed on rejection");
|
|
447
|
+
});
|
|
448
|
+
|
|
280
449
|
it("chain runs emit one grouped event containing all executed children", async () => {
|
|
281
450
|
mockPi.onCall({ output: "Chain child output" });
|
|
282
451
|
const { executor, events } = makeExecutor({ agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
|
|
@@ -300,9 +469,9 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
300
469
|
assert.equal(payload.mode, "chain");
|
|
301
470
|
assert.deepEqual((payload.children ?? []).map((child) => child.agent).sort(), ["a", "b", "c"]);
|
|
302
471
|
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/);
|
|
472
|
+
assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
|
|
473
|
+
assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
|
|
474
|
+
assert.match(String(payload.message ?? ""), /3\. c — process completed · output present/);
|
|
306
475
|
assert.match(result.content[0]?.text ?? "", /Delivered chain subagent results via intercom\./);
|
|
307
476
|
assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
|
|
308
477
|
});
|
|
@@ -631,7 +800,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
631
800
|
lastUpdate: 200,
|
|
632
801
|
cwd: tempDir,
|
|
633
802
|
sessionFile,
|
|
634
|
-
steps: [{ agent: "worker", status: "complete" }],
|
|
803
|
+
steps: [{ agent: "worker", status: "complete", launchContractDigest: "source-launch-contract-digest" }],
|
|
635
804
|
}, null, 2), "utf-8");
|
|
636
805
|
const { executor, events } = makeExecutor();
|
|
637
806
|
|
|
@@ -651,6 +820,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
651
820
|
assert.doesNotMatch(result.content[0]?.text ?? "", /Follow:/);
|
|
652
821
|
const revivedId = result.details?.asyncId;
|
|
653
822
|
assert.ok(revivedId, "expected revived async id");
|
|
823
|
+
assert.equal(result.details?.sourceLaunchContractDigest, "source-launch-contract-digest");
|
|
654
824
|
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
825
|
assert.equal(startedEvent?.goal, "What changed?");
|
|
656
826
|
const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
|
|
@@ -785,7 +955,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
785
955
|
mockPi.onCall({ output: "first child done" });
|
|
786
956
|
mockPi.onCall({ output: "second child done" });
|
|
787
957
|
mockPi.onCall({ output: "revived foreground answer" });
|
|
788
|
-
const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b")] });
|
|
958
|
+
const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b", { model: "anthropic/claude-sonnet-4", thinking: "high" })] });
|
|
789
959
|
|
|
790
960
|
const original = await executor.execute(
|
|
791
961
|
"foreground-resume-original",
|
|
@@ -797,6 +967,17 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
797
967
|
const runId = original.details?.runId;
|
|
798
968
|
assert.ok(runId, "expected foreground run id");
|
|
799
969
|
|
|
970
|
+
const overridden = await executor.execute(
|
|
971
|
+
"foreground-resume-model-override",
|
|
972
|
+
{ action: "resume", id: runId, index: 1, message: "Follow up with b", model: "openai/gpt-5" },
|
|
973
|
+
new AbortController().signal,
|
|
974
|
+
undefined,
|
|
975
|
+
makeMinimalCtx(tempDir),
|
|
976
|
+
);
|
|
977
|
+
assert.equal(overridden.isError, true);
|
|
978
|
+
assert.match(overridden.content[0]?.text ?? "", /reuses the persisted child model/);
|
|
979
|
+
assert.equal(mockPi.callCount(), 2, "rejected model override must not spawn a revival");
|
|
980
|
+
|
|
800
981
|
const revived = await executor.execute(
|
|
801
982
|
"foreground-resume",
|
|
802
983
|
{ action: "resume", id: runId, index: 1, message: "Follow up with b" },
|
|
@@ -808,10 +989,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
808
989
|
assert.equal(revived.isError, undefined);
|
|
809
990
|
assert.match(revived.content[0]?.text ?? "", /Revived foreground subagent from/);
|
|
810
991
|
assert.match(revived.content[0]?.text ?? "", /Agent: b/);
|
|
992
|
+
assert.equal(revived.details?.sourceLaunchContractDigest, original.details?.results?.[1]?.launchContractDigest);
|
|
993
|
+
assert.ok(revived.details?.sourceLaunchContractDigest, "expected foreground source launch contract digest");
|
|
811
994
|
const reviveArgs = await readMockCallArgs(2);
|
|
812
995
|
const selectedSession = original.details?.results?.[1]?.sessionFile;
|
|
813
996
|
assert.ok(selectedSession, "expected selected child session file");
|
|
814
997
|
assert.equal(reviveArgs[reviveArgs.indexOf("--session") + 1], selectedSession);
|
|
998
|
+
assert.equal(reviveArgs[reviveArgs.indexOf("--model") + 1], original.details?.results?.[1]?.model);
|
|
815
999
|
const revivedId = revived.details?.asyncId;
|
|
816
1000
|
assert.ok(revivedId, "expected revived async id");
|
|
817
1001
|
const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
|
|
@@ -861,7 +1045,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
861
1045
|
);
|
|
862
1046
|
assert.equal(blocked.isError, true);
|
|
863
1047
|
assert.match(blocked.content[0]?.text ?? "", new RegExp(`already owned by run '${firstRevivedId}'`));
|
|
864
|
-
|
|
1048
|
+
// The original and the first revival each spawn Pi asynchronously, so wait for both markers
|
|
1049
|
+
// before asserting the contending revival added no third spawn.
|
|
1050
|
+
await waitForCallCount(2);
|
|
1051
|
+
assert.equal(mockPi.callCount(), 2, "contending foreground revival must not spawn Pi");
|
|
865
1052
|
|
|
866
1053
|
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
867
1054
|
await waitForFile(path.join(RESULTS_DIR, `${firstRevivedId}.json`));
|
|
@@ -1072,7 +1259,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
1072
1259
|
const payload = completion.payload as { success?: boolean; summary?: string };
|
|
1073
1260
|
assert.equal(payload.success, false);
|
|
1074
1261
|
assert.match(payload.summary ?? "", /Acceptance rejected/);
|
|
1075
|
-
|
|
1262
|
+
// Reference-first foreground completion: the recovery summary carries the
|
|
1263
|
+
// saved-output reference (with error/status), never raw child prose.
|
|
1264
|
+
assert.match(payload.summary ?? "", /Output saved to: /);
|
|
1265
|
+
assert.doesNotMatch(payload.summary ?? "", /final answer with rejected acceptance evidence/);
|
|
1076
1266
|
|
|
1077
1267
|
const status = await executor.execute(
|
|
1078
1268
|
"foreground-detached-failed-status",
|
|
@@ -1273,9 +1463,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
1273
1463
|
makeMinimalCtx(tempDir),
|
|
1274
1464
|
);
|
|
1275
1465
|
const fleetText = fleet.content[0]?.text ?? "";
|
|
1276
|
-
assert.match(fleetText, /
|
|
1466
|
+
assert.match(fleetText, /Foreground runs:/);
|
|
1277
1467
|
assert.ok(fleetText.includes(runId));
|
|
1278
|
-
assert.match(fleetText, /
|
|
1468
|
+
assert.match(fleetText, /running/);
|
|
1469
|
+
assert.match(fleetText, /contact_supervisor/);
|
|
1279
1470
|
|
|
1280
1471
|
const resumed = await executor.execute(
|
|
1281
1472
|
"foreground-detached-resume",
|
|
@@ -1534,7 +1725,28 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
|
|
|
1534
1725
|
const payload = intercomEvents[0]!.payload as { status?: string; summary?: string; message?: string };
|
|
1535
1726
|
assert.equal(payload.status, "failed");
|
|
1536
1727
|
assert.match(String(payload.summary ?? ""), /1 completed, 1 failed/);
|
|
1537
|
-
assert.match(String(payload.message ?? ""), /
|
|
1728
|
+
assert.match(String(payload.message ?? ""), /Process status: failed/);
|
|
1729
|
+
assert.match(String(payload.message ?? ""), /Outputs: 2 present \(semantic adequacy unassessed\)/);
|
|
1730
|
+
assert.match(String(payload.message ?? ""), /Inspect that output before retrying/);
|
|
1538
1731
|
assert.match(result.content[0]?.text ?? "", /Children: 1 completed, 1 failed/);
|
|
1539
1732
|
});
|
|
1733
|
+
|
|
1734
|
+
it("does not treat a synthetic foreground startup error as child output", async () => {
|
|
1735
|
+
mockPi.onCall({ output: "", stderr: "mock startup failure", exitCode: 1 });
|
|
1736
|
+
const { executor, events } = makeExecutor({ agents: [makeAgent("worker")] });
|
|
1737
|
+
|
|
1738
|
+
await executor.execute(
|
|
1739
|
+
"foreground-synthetic-error",
|
|
1740
|
+
{ agent: "worker", task: "synthetic-error-task" },
|
|
1741
|
+
new AbortController().signal,
|
|
1742
|
+
undefined,
|
|
1743
|
+
makeMinimalCtx(tempDir),
|
|
1744
|
+
);
|
|
1745
|
+
|
|
1746
|
+
const intercomEvents = events.emitted.filter((entry) => entry.channel === "subagent:result-intercom");
|
|
1747
|
+
assert.equal(intercomEvents.length, 1);
|
|
1748
|
+
const message = String((intercomEvents[0]!.payload as { message?: string }).message ?? "");
|
|
1749
|
+
assert.match(message, /process failed · output absent/);
|
|
1750
|
+
assert.doesNotMatch(message, /Inspect that output before retrying/);
|
|
1751
|
+
});
|
|
1540
1752
|
});
|
|
@@ -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"]);
|
|
@@ -253,7 +287,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
253
287
|
groups: Array<{ children: Array<{ agent: string; summary: string; patch: { path: string } }>; cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> } }>;
|
|
254
288
|
};
|
|
255
289
|
assert.equal(handoff.groups[0]!.children[0]!.agent, "echo");
|
|
256
|
-
|
|
290
|
+
// Reference-first handoff: the summary carries the saved-output reference.
|
|
291
|
+
assert.match(handoff.groups[0]!.children[0]!.summary, /Output saved to: /);
|
|
292
|
+
assert.doesNotMatch(handoff.groups[0]!.children[0]!.summary, /Worktree task complete/);
|
|
257
293
|
assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
|
|
258
294
|
assert.ok(result.details?.runId);
|
|
259
295
|
assert.ok(handoff.groups[0]!.children[0]!.patch.path.includes(`${path.sep}worktree-diffs${path.sep}${result.details.runId}${path.sep}`));
|
|
@@ -317,7 +353,8 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
317
353
|
|
|
318
354
|
assert.equal(result.isError, undefined);
|
|
319
355
|
assert.equal(result.details?.mode, "parallel");
|
|
320
|
-
assert.match(result.content[0]?.text ?? "",
|
|
356
|
+
assert.match(result.content[0]?.text ?? "", /Output saved to: /);
|
|
357
|
+
assert.doesNotMatch(result.content[0]?.text ?? "", new RegExp(`${action} alias finished`));
|
|
321
358
|
}
|
|
322
359
|
});
|
|
323
360
|
|
|
@@ -431,7 +468,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
431
468
|
assert.equal(result.details?.results?.[0]?.timedOut, true);
|
|
432
469
|
assert.equal(result.details?.results?.[0]?.error, "Subagent timed out after 300ms.");
|
|
433
470
|
assert.equal(result.details?.results?.[1]?.exitCode, 0);
|
|
434
|
-
assert.equal(result.details?.results?.[1]?.finalOutput,
|
|
471
|
+
assert.equal(result.details?.results?.[1]?.finalOutput, undefined);
|
|
472
|
+
assert.ok(result.details?.results?.[1]?.savedOutputPath);
|
|
473
|
+
assert.equal(fs.readFileSync(result.details?.results?.[1]?.savedOutputPath as string, "utf-8"), "fast done");
|
|
435
474
|
assert.match(result.content[0]?.text ?? "", /1\/2 succeeded/);
|
|
436
475
|
assert.match(result.content[0]?.text ?? "", /TIMED OUT: Subagent timed out after 300ms\./);
|
|
437
476
|
});
|
|
@@ -456,8 +495,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
456
495
|
assert.match(text, /Output saved to:/);
|
|
457
496
|
assert.match(text, /2 lines/);
|
|
458
497
|
assert.doesNotMatch(text, /Parallel full report/);
|
|
459
|
-
assert.
|
|
460
|
-
assert.
|
|
498
|
+
assert.equal(result.details?.results?.[0]?.finalOutput, undefined);
|
|
499
|
+
assert.match(result.details?.results?.[0]?.outputReference?.message ?? "", /Output saved to:/);
|
|
500
|
+
assert.doesNotMatch(result.details?.results?.[0]?.outputReference?.message ?? "", /Parallel full report/);
|
|
461
501
|
assert.equal(fs.readFileSync(outputPath, "utf-8"), "Parallel full report\nwith details");
|
|
462
502
|
});
|
|
463
503
|
|
|
@@ -466,7 +506,7 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
466
506
|
|
|
467
507
|
const result = await executor.execute(
|
|
468
508
|
"parallel-file-only-missing-output",
|
|
469
|
-
{ tasks: [{ agent: "echo", task: "Write report", outputMode: "file-only" }] },
|
|
509
|
+
{ tasks: [{ agent: "echo", task: "Write report", output: false, outputMode: "file-only" }] },
|
|
470
510
|
new AbortController().signal,
|
|
471
511
|
undefined,
|
|
472
512
|
makeMinimalCtx(tempDir),
|
|
@@ -570,7 +610,8 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
570
610
|
|
|
571
611
|
Inspect
|
|
572
612
|
|
|
573
|
-
|
|
613
|
+
---\n**Output:**`));
|
|
614
|
+
assert.match(taskArg, /## Acceptance Contract/);
|
|
574
615
|
});
|
|
575
616
|
|
|
576
617
|
it("top-level parallel defaultProgress uses isolated run storage", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
@@ -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...)" }],
|