@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
|
+
import { MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES, isRuntimeAcknowledgedExtensionId, projectRuntimeAcknowledgedExtensions, readRuntimeAcknowledgedExtensions, sanitizeRuntimeAcknowledgedExtensions } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
|
|
7
|
+
|
|
8
|
+
describe("runtime acknowledged extension projection", () => {
|
|
9
|
+
it("validates opaque ids without path-like content", () => {
|
|
10
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("com.example-extension:v1"), true);
|
|
11
|
+
assert.equal(isRuntimeAcknowledgedExtensionId(""), false);
|
|
12
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("has space"), false);
|
|
13
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("../secret"), false);
|
|
14
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("path/to/ext"), false);
|
|
15
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("path\\to\\ext"), false);
|
|
16
|
+
assert.equal(isRuntimeAcknowledgedExtensionId("x".repeat(129)), false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("deduplicates, caps ids, and counts omitted valid ids", () => {
|
|
20
|
+
const ids = ["dup", "dup", ...Array.from({ length: 35 }, (_, index) => `ext-${index}`), "bad id"];
|
|
21
|
+
const projected = projectRuntimeAcknowledgedExtensions(ids);
|
|
22
|
+
|
|
23
|
+
assert.equal(projected?.source, "child-runtime");
|
|
24
|
+
assert.equal(projected?.ids.length, 32);
|
|
25
|
+
assert.equal(projected?.ids[0], "dup");
|
|
26
|
+
assert.equal(projected?.omitted, 4);
|
|
27
|
+
assert.equal(projected?.ids.includes("bad id"), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("sanitizes persisted projections and rejects invalid shapes", () => {
|
|
31
|
+
assert.deepEqual(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "child-runtime", ids: ["ok", "ok", "bad/path"], omitted: 2 }), {
|
|
32
|
+
version: 1,
|
|
33
|
+
source: "child-runtime",
|
|
34
|
+
ids: ["ok"],
|
|
35
|
+
omitted: 2,
|
|
36
|
+
});
|
|
37
|
+
assert.equal(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "launch-resolved", ids: ["ok"] }), undefined);
|
|
38
|
+
assert.equal(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "child-runtime", ids: ["bad/path"] }), undefined);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("rejects oversized capture files without reading them", (t) => {
|
|
42
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-ack-"));
|
|
43
|
+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
44
|
+
const filePath = path.join(dir, "ack.json");
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(filePath, "x".repeat(MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES + 1));
|
|
47
|
+
assert.equal(readRuntimeAcknowledgedExtensions(filePath), undefined);
|
|
48
|
+
|
|
49
|
+
fs.writeFileSync(filePath, JSON.stringify({ version: 1, source: "child-runtime", ids: ["ok"], omitted: 0 }));
|
|
50
|
+
assert.deepEqual(readRuntimeAcknowledgedExtensions(filePath), { version: 1, source: "child-runtime", ids: ["ok"], omitted: 0 });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -38,6 +38,13 @@ interface SubagentParamsSchema {
|
|
|
38
38
|
graceTurns?: { minimum?: number };
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
|
+
usageBudget?: {
|
|
42
|
+
properties?: {
|
|
43
|
+
tokens?: { properties?: { soft?: { exclusiveMinimum?: number }; hard?: { exclusiveMinimum?: number } } };
|
|
44
|
+
costUsd?: { properties?: { soft?: { exclusiveMinimum?: number }; hard?: { exclusiveMinimum?: number } } };
|
|
45
|
+
};
|
|
46
|
+
description?: string;
|
|
47
|
+
};
|
|
41
48
|
id?: {
|
|
42
49
|
type?: string;
|
|
43
50
|
description?: string;
|
|
@@ -187,6 +194,18 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
|
|
|
187
194
|
assert.doesNotMatch(description, /orchestration\./);
|
|
188
195
|
});
|
|
189
196
|
|
|
197
|
+
it("documents list task as an optional advisory intent and keeps action a free string", () => {
|
|
198
|
+
const taskSchema = (SubagentParams?.properties as Record<string, JsonSchemaNode> | undefined)?.task;
|
|
199
|
+
assert.ok(taskSchema, "task schema should exist");
|
|
200
|
+
const description = String(taskSchema?.description ?? "");
|
|
201
|
+
assert.match(description, /action:'list'/);
|
|
202
|
+
assert.match(description, /never launches/);
|
|
203
|
+
assert.match(description, /explicitly call subagent/);
|
|
204
|
+
const actionSchema = SubagentParams?.properties?.action;
|
|
205
|
+
assert.equal(actionSchema?.type, "string");
|
|
206
|
+
assert.equal(actionSchema?.enum, undefined);
|
|
207
|
+
});
|
|
208
|
+
|
|
190
209
|
it("includes foreground timeout aliases and turn budget", () => {
|
|
191
210
|
const timeoutSchema = SubagentParams?.properties?.timeoutMs;
|
|
192
211
|
const maxRuntimeSchema = SubagentParams?.properties?.maxRuntimeMs;
|
|
@@ -206,6 +225,19 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
|
|
|
206
225
|
assert.equal(toolBudgetSchema?.properties?.hard?.minimum, 1);
|
|
207
226
|
});
|
|
208
227
|
|
|
228
|
+
it("includes root-only reported usage budget", () => {
|
|
229
|
+
const usageBudgetSchema = SubagentParams?.properties?.usageBudget;
|
|
230
|
+
assert.ok(usageBudgetSchema, "usageBudget schema should exist");
|
|
231
|
+
assert.equal(usageBudgetSchema.properties?.tokens?.properties?.soft?.exclusiveMinimum, 0);
|
|
232
|
+
assert.equal(usageBudgetSchema.properties?.tokens?.properties?.hard?.exclusiveMinimum, 0);
|
|
233
|
+
assert.equal(usageBudgetSchema.properties?.costUsd?.properties?.soft?.exclusiveMinimum, 0);
|
|
234
|
+
assert.equal(usageBudgetSchema.properties?.costUsd?.properties?.hard?.exclusiveMinimum, 0);
|
|
235
|
+
assert.match(String(usageBudgetSchema.description ?? ""), /root-only/);
|
|
236
|
+
assert.match(String(usageBudgetSchema.description ?? ""), /running children are not stopped/i);
|
|
237
|
+
assert.equal(getPropertySchema(SubagentParams?.properties?.tasks?.items as JsonSchemaNode | undefined, ["usageBudget"]), undefined);
|
|
238
|
+
assert.equal(getPropertySchema(SubagentParams?.properties?.chain?.items as JsonSchemaNode | undefined, ["usageBudget"]), undefined);
|
|
239
|
+
});
|
|
240
|
+
|
|
209
241
|
it("includes subagent control fields", () => {
|
|
210
242
|
const idSchema = SubagentParams?.properties?.id;
|
|
211
243
|
assert.ok(idSchema, "id schema should exist");
|
|
@@ -214,6 +246,12 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
|
|
|
214
246
|
assert.match(String(idSchema.description ?? ""), /interrupt/i);
|
|
215
247
|
assert.match(String(idSchema.description ?? ""), /steer/i);
|
|
216
248
|
assert.match(String(idSchema.description ?? ""), /append-step/i);
|
|
249
|
+
assert.match(String(idSchema.description ?? ""), /approve-checkpoint/i);
|
|
250
|
+
assert.match(String(idSchema.description ?? ""), /reject-checkpoint/i);
|
|
251
|
+
|
|
252
|
+
const chainItemSchema = SubagentParams?.properties?.chain?.items;
|
|
253
|
+
assert.equal(chainItemSchema?.properties?.checkpoint?.type, "string");
|
|
254
|
+
assert.equal(chainItemSchema?.properties?.message?.type, "string");
|
|
217
255
|
|
|
218
256
|
const runIdSchema = SubagentParams?.properties?.runId;
|
|
219
257
|
assert.ok(runIdSchema, "runId schema should exist");
|
|
@@ -318,12 +356,18 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
|
|
|
318
356
|
assert.ok(SubagentParams, "SubagentParams schema should exist");
|
|
319
357
|
const schema = SubagentParams as unknown as JsonSchemaNode;
|
|
320
358
|
const serialized = JSON.stringify(schema);
|
|
321
|
-
assert.ok(serialized.length <
|
|
359
|
+
assert.ok(serialized.length < 16_000, `expected compact schema under 16k chars, got ${serialized.length}`);
|
|
322
360
|
assert.equal(serialized.includes('"$ref"'), false);
|
|
323
361
|
assert.equal(serialized.includes('"$defs"'), false);
|
|
324
362
|
assert.equal(serialized.split("Optional acceptance policy.").length - 1, 1);
|
|
325
363
|
assert.match(String((schema.properties as Record<string, JsonSchemaNode> | undefined)?.agent?.description ?? ""), /SINGLE mode/);
|
|
326
|
-
|
|
364
|
+
const acceptanceDescription = String((schema.properties as Record<string, JsonSchemaNode> | undefined)?.acceptance?.description ?? "");
|
|
365
|
+
assert.match(acceptanceDescription, /acceptance policy/);
|
|
366
|
+
assert.match(acceptanceDescription, /Supported evidence kinds:/);
|
|
367
|
+
assert.match(acceptanceDescription, /commands-run/);
|
|
368
|
+
assert.match(acceptanceDescription, /changed-files/);
|
|
369
|
+
assert.match(acceptanceDescription, /manual-notes/);
|
|
370
|
+
assert.match(acceptanceDescription, /\{ level: "checked", evidence: \["commands-run", "changed-files"\] \}/);
|
|
327
371
|
|
|
328
372
|
const nestedDescriptionPaths: string[] = [];
|
|
329
373
|
const stack: Array<{ path: string; value: unknown }> = [{ path: "SubagentParams", value: schema }];
|
|
@@ -6,8 +6,10 @@ import * as path from "node:path";
|
|
|
6
6
|
import type { Message, Usage } from "@earendil-works/pi-ai";
|
|
7
7
|
import {
|
|
8
8
|
captureSingleOutputSnapshot,
|
|
9
|
+
CONTEXT_FALLBACK_LIMIT,
|
|
9
10
|
extractChildWrittenOutput,
|
|
10
11
|
finalizeSingleOutput,
|
|
12
|
+
formatBoundedPersistenceFallback,
|
|
11
13
|
formatSavedOutputReference,
|
|
12
14
|
injectOutputPathSystemPrompt,
|
|
13
15
|
injectSingleOutputInstruction,
|
|
@@ -269,6 +271,49 @@ describe("validateFileOnlyOutputMode", () => {
|
|
|
269
271
|
});
|
|
270
272
|
});
|
|
271
273
|
|
|
274
|
+
describe("CONTEXT_FALLBACK_LIMIT and formatBoundedPersistenceFallback", () => {
|
|
275
|
+
it("exports the fixed internal 80-line/4-KiB context fallback cap", () => {
|
|
276
|
+
assert.equal(CONTEXT_FALLBACK_LIMIT.lines, 80);
|
|
277
|
+
assert.equal(CONTEXT_FALLBACK_LIMIT.bytes, 4096);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("includes error, process status, intended path, and a bounded slice", () => {
|
|
281
|
+
const fallback = formatBoundedPersistenceFallback({
|
|
282
|
+
error: "EACCES: permission denied",
|
|
283
|
+
outputPath: "/tmp/report.md",
|
|
284
|
+
fullOutput: "line one\nline two",
|
|
285
|
+
exitCode: 1,
|
|
286
|
+
processError: "boom",
|
|
287
|
+
});
|
|
288
|
+
assert.match(fallback, /\[Full output unavailable\] EACCES: permission denied/);
|
|
289
|
+
assert.match(fallback, /Process status: boom/);
|
|
290
|
+
assert.match(fallback, /Intended output path: \/tmp\/report\.md/);
|
|
291
|
+
assert.match(fallback, /Full output is unavailable; showing a bounded excerpt \(first 80 lines \/ 4 KiB\)\./);
|
|
292
|
+
assert.match(fallback, /line one\nline two/);
|
|
293
|
+
assert.doesNotMatch(fallback, /Output saved to:/);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("never returns unbounded output when the source exceeds the cap", () => {
|
|
297
|
+
const longOutput = Array.from({ length: 500 }, (_, i) => `line ${i} ${`x`.repeat(50)}`).join("\n");
|
|
298
|
+
const fallback = formatBoundedPersistenceFallback({
|
|
299
|
+
error: "disk full",
|
|
300
|
+
fullOutput: longOutput,
|
|
301
|
+
exitCode: 0,
|
|
302
|
+
});
|
|
303
|
+
const body = fallback.split("\n").filter((line) => /^line \d+/.test(line)).length;
|
|
304
|
+
assert.ok(body <= 80, `fallback kept ${body} lines, expected at most 80`);
|
|
305
|
+
assert.ok(Buffer.byteLength(fallback, "utf-8") <= 12 * 1024, "fallback header plus 4-KiB slice stays bounded");
|
|
306
|
+
assert.doesNotMatch(fallback, /line 400/);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("states completed status for exit code 0 and failed status otherwise", () => {
|
|
310
|
+
const completed = formatBoundedPersistenceFallback({ error: "read failed", fullOutput: "x", exitCode: 0 });
|
|
311
|
+
assert.match(completed, /Process status: completed/);
|
|
312
|
+
const failed = formatBoundedPersistenceFallback({ error: "read failed", fullOutput: "x", exitCode: 2 });
|
|
313
|
+
assert.match(failed, /Process status: failed \(exit 2\)/);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
272
317
|
describe("finalizeSingleOutput", () => {
|
|
273
318
|
it("formats saved-path messaging around the already-resolved output", () => {
|
|
274
319
|
const result = finalizeSingleOutput({
|
|
@@ -298,13 +343,58 @@ describe("finalizeSingleOutput", () => {
|
|
|
298
343
|
assert.match(result.displayOutput, /3 lines/);
|
|
299
344
|
});
|
|
300
345
|
|
|
301
|
-
it("
|
|
346
|
+
it("returns error/status plus the saved-output reference on failed runs with a persisted result", () => {
|
|
302
347
|
const result = finalizeSingleOutput({
|
|
303
348
|
fullOutput: "full output",
|
|
304
349
|
truncatedOutput: "truncated output",
|
|
305
350
|
outputPath: "/tmp/review.md",
|
|
306
351
|
savedPath: "/tmp/review.md",
|
|
307
352
|
exitCode: 1,
|
|
353
|
+
error: "exploded",
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
assert.match(result.displayOutput, /^exploded\n\nOutput saved to: \/tmp\/review\.md/);
|
|
357
|
+
assert.doesNotMatch(result.displayOutput, /truncated output/);
|
|
358
|
+
assert.equal(result.savedPath, "/tmp/review.md");
|
|
359
|
+
assert.ok(result.outputReference);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("returns only the bounded persistence-failure fallback when saving failed", () => {
|
|
363
|
+
const result = finalizeSingleOutput({
|
|
364
|
+
fullOutput: "full output text",
|
|
365
|
+
truncatedOutput: "truncated output",
|
|
366
|
+
outputPath: "/tmp/review.md",
|
|
367
|
+
exitCode: 0,
|
|
368
|
+
saveError: "EACCES: permission denied",
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
assert.match(result.displayOutput, /\[Full output unavailable\]/);
|
|
372
|
+
assert.match(result.displayOutput, /Intended output path: \/tmp\/review\.md/);
|
|
373
|
+
assert.match(result.displayOutput, /full output text/);
|
|
374
|
+
assert.doesNotMatch(result.displayOutput, /Output saved to:/);
|
|
375
|
+
assert.equal(result.savedPath, undefined);
|
|
376
|
+
assert.equal(result.saveError, "EACCES: permission denied");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("returns only the bounded persistence-failure fallback on failed runs with a save error", () => {
|
|
380
|
+
const result = finalizeSingleOutput({
|
|
381
|
+
fullOutput: "partial child output",
|
|
382
|
+
outputPath: "/tmp/review.md",
|
|
383
|
+
exitCode: 1,
|
|
384
|
+
saveError: "Failed to read changed output file: boom",
|
|
385
|
+
error: "step failed",
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
assert.match(result.displayOutput, /\[Full output unavailable\] Failed to read changed output file: boom/);
|
|
389
|
+
assert.match(result.displayOutput, /Process status: step failed/);
|
|
390
|
+
assert.doesNotMatch(result.displayOutput, /Output saved to:/);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("keeps the legacy full inline output for successful inline runs without a saved path", () => {
|
|
394
|
+
const result = finalizeSingleOutput({
|
|
395
|
+
fullOutput: "legacy inline output",
|
|
396
|
+
truncatedOutput: "truncated output",
|
|
397
|
+
exitCode: 0,
|
|
308
398
|
});
|
|
309
399
|
|
|
310
400
|
assert.equal(result.displayOutput, "truncated output");
|
|
@@ -340,7 +340,7 @@ describe("skills filesystem fallback", () => {
|
|
|
340
340
|
`;
|
|
341
341
|
execFileSync(
|
|
342
342
|
process.execPath,
|
|
343
|
-
["--experimental-
|
|
343
|
+
["--experimental-strip-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
|
|
344
344
|
{
|
|
345
345
|
cwd: projectRoot,
|
|
346
346
|
env: {
|
|
@@ -60,7 +60,7 @@ function projectRequest(status: AsyncStatus, request: SteerRequest, states: Stee
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
async function waitUntil<T>(read: () => T | undefined, timeoutMs =
|
|
63
|
+
async function waitUntil<T>(read: () => T | undefined, timeoutMs = 30_000): Promise<T> {
|
|
64
64
|
const deadline = Date.now() + timeoutMs;
|
|
65
65
|
while (true) {
|
|
66
66
|
const value = read();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
4
|
+
import {
|
|
5
|
+
boundStreamedRecentOutput,
|
|
6
|
+
boundStreamedRecentTools,
|
|
7
|
+
boundStreamedToolCalls,
|
|
8
|
+
MAX_STREAMED_OUTPUT_LINE_CHARS,
|
|
9
|
+
MAX_STREAMED_RECENT_TOOLS,
|
|
10
|
+
MAX_STREAMED_TOOL_CALLS,
|
|
11
|
+
} from "../../src/shared/utils.ts";
|
|
12
|
+
|
|
13
|
+
describe("streamed progress snapshot bounds", () => {
|
|
14
|
+
it("keeps only the most recent tool-history entries and clones them", () => {
|
|
15
|
+
const recentTools = Array.from({ length: MAX_STREAMED_RECENT_TOOLS + 40 }, (_, i) => ({
|
|
16
|
+
tool: "read",
|
|
17
|
+
args: `file-${i}.ts`,
|
|
18
|
+
endMs: i,
|
|
19
|
+
}));
|
|
20
|
+
const bounded = boundStreamedRecentTools(recentTools);
|
|
21
|
+
assert.equal(bounded.length, MAX_STREAMED_RECENT_TOOLS);
|
|
22
|
+
// most-recent retained (chronological)
|
|
23
|
+
assert.equal(bounded.at(-1)?.endMs, recentTools.at(-1)?.endMs);
|
|
24
|
+
assert.equal(bounded[0]?.endMs, recentTools.at(-MAX_STREAMED_RECENT_TOOLS)?.endMs);
|
|
25
|
+
// cloned, not aliased
|
|
26
|
+
bounded[0]!.args = "mutated";
|
|
27
|
+
assert.notEqual(recentTools.at(-MAX_STREAMED_RECENT_TOOLS)?.args, "mutated");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("truncates long recent-output lines but leaves short lines intact", () => {
|
|
31
|
+
const shortLine = "ok";
|
|
32
|
+
const longLine = "x".repeat(MAX_STREAMED_OUTPUT_LINE_CHARS + 5000);
|
|
33
|
+
const bounded = boundStreamedRecentOutput([shortLine, longLine]);
|
|
34
|
+
assert.equal(bounded[0], shortLine);
|
|
35
|
+
assert.ok(bounded[1]!.length < longLine.length);
|
|
36
|
+
assert.ok(bounded[1]!.startsWith("x".repeat(MAX_STREAMED_OUTPUT_LINE_CHARS)));
|
|
37
|
+
assert.match(bounded[1]!, /\[truncated\]$/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("prefers an existing toolCalls summary and caps it", () => {
|
|
41
|
+
const toolCalls = Array.from({ length: MAX_STREAMED_TOOL_CALLS + 20 }, (_, i) => ({
|
|
42
|
+
text: `read(${i})`,
|
|
43
|
+
expandedText: `read(file-${i}.ts)`,
|
|
44
|
+
}));
|
|
45
|
+
const bounded = boundStreamedToolCalls({ toolCalls, messages: undefined });
|
|
46
|
+
assert.equal(bounded?.length, MAX_STREAMED_TOOL_CALLS);
|
|
47
|
+
assert.equal(bounded?.at(-1)?.text, toolCalls.at(-1)?.text);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("derives tool-call summaries from messages when none are present", () => {
|
|
51
|
+
const messages = [
|
|
52
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "a.ts" } }] },
|
|
53
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "grep", arguments: { pattern: "foo" } }] },
|
|
54
|
+
{ role: "user", content: [{ type: "text", text: "ignored" }] },
|
|
55
|
+
] as never;
|
|
56
|
+
const bounded = boundStreamedToolCalls({ toolCalls: undefined, messages });
|
|
57
|
+
assert.equal(bounded?.length, 2);
|
|
58
|
+
assert.match(bounded![0]!.text, /read/);
|
|
59
|
+
assert.match(bounded![1]!.text, /grep/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns undefined when there are no tool calls", () => {
|
|
63
|
+
assert.equal(boundStreamedToolCalls({ toolCalls: [], messages: [] as never }), undefined);
|
|
64
|
+
assert.equal(boundStreamedToolCalls({ toolCalls: undefined, messages: undefined }), undefined);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("bounds a pathological running snapshot well under the child-stdout protocol cap", () => {
|
|
68
|
+
// Simulates a long, deep nested fan-out: many tool calls + long output lines.
|
|
69
|
+
const recentTools = Array.from({ length: 5000 }, (_, i) => ({ tool: "read", args: `services/pkg/file-${i}.ts`, endMs: i }));
|
|
70
|
+
const recentOutput = Array.from({ length: 50 }, () => "y".repeat(60_000));
|
|
71
|
+
const snapshot = {
|
|
72
|
+
recentTools: boundStreamedRecentTools(recentTools),
|
|
73
|
+
recentOutput: boundStreamedRecentOutput(recentOutput),
|
|
74
|
+
};
|
|
75
|
+
const bytes = Buffer.byteLength(JSON.stringify(snapshot), "utf8");
|
|
76
|
+
assert.ok(bytes < 256 * 1024, `bounded snapshot should be small, was ${bytes} bytes`);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
SUBAGENT_STEER_INBOX_ENV,
|
|
17
17
|
SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
|
|
18
18
|
} from "../../src/runs/shared/pi-args.ts";
|
|
19
|
+
import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
|
|
19
20
|
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "../../src/runs/shared/structured-output.ts";
|
|
20
21
|
import { TOOL_BUDGET_ENV } from "../../src/runs/shared/tool-budget.ts";
|
|
21
22
|
import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, formatChildToolDiagnostic, MCP_DIRECT_CHILD_TOOLS_ENV, readChildToolDiagnostic, REQUIRED_CHILD_TOOLS_ENV } from "../../src/runs/shared/tool-availability.ts";
|
|
@@ -128,6 +129,42 @@ function setSupervisorEnv(): void {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
describe("subagent prompt runtime", () => {
|
|
132
|
+
it("collects runtime extension acknowledgements until terminal serialization", () => {
|
|
133
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-runtime-ack-"));
|
|
134
|
+
try {
|
|
135
|
+
const outputPath = path.join(dir, "acks.json");
|
|
136
|
+
process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = outputPath;
|
|
137
|
+
const runtimeHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
|
|
138
|
+
const extensionHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
|
|
139
|
+
const pushHandler = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, handler: (payload?: unknown) => unknown): void => {
|
|
140
|
+
target.set(event, [...(target.get(event) ?? []), handler]);
|
|
141
|
+
};
|
|
142
|
+
const emitAll = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, payload?: unknown): void => {
|
|
143
|
+
for (const handler of target.get(event) ?? []) handler(payload);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
registerSubagentPromptRuntime({
|
|
147
|
+
events: { on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(extensionHandlers, event, handler); } },
|
|
148
|
+
on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(runtimeHandlers, event, handler); },
|
|
149
|
+
} as never);
|
|
150
|
+
|
|
151
|
+
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
|
|
152
|
+
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
|
|
153
|
+
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "bad/path" });
|
|
154
|
+
runtimeHandlers.get("agent_end")?.[0]?.({});
|
|
155
|
+
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "late" });
|
|
156
|
+
|
|
157
|
+
assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), {
|
|
158
|
+
version: 1,
|
|
159
|
+
source: "child-runtime",
|
|
160
|
+
ids: ["ext.one"],
|
|
161
|
+
omitted: 0,
|
|
162
|
+
});
|
|
163
|
+
} finally {
|
|
164
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
131
168
|
it("nudges after the tool budget soft limit and blocks configured tools after hard", () => {
|
|
132
169
|
const handlers = new Map<string, (payload: { toolName?: string }) => unknown>();
|
|
133
170
|
const sent: string[] = [];
|
|
@@ -299,6 +336,10 @@ describe("subagent prompt runtime", () => {
|
|
|
299
336
|
});
|
|
300
337
|
|
|
301
338
|
it("registers child watchdog lifecycle handlers only when enabled by env", () => {
|
|
339
|
+
delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
|
|
340
|
+
// Clear the ack capture env explicitly: when this test suite itself runs inside a
|
|
341
|
+
// pi-subagents child, the runner sets it and an extra agent_end handler registers.
|
|
342
|
+
delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
|
|
302
343
|
const handlersWithout = new Map<string, unknown[]>();
|
|
303
344
|
registerSubagentPromptRuntime({
|
|
304
345
|
on(event: string, handler: unknown) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { Usage } from "../../src/shared/types.ts";
|
|
4
|
+
import {
|
|
5
|
+
MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS,
|
|
6
|
+
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
7
|
+
formatSubagentStartupRetryExhaustedError,
|
|
8
|
+
formatSubagentStartupRetryNote,
|
|
9
|
+
isRetryableSubagentStartupFailure,
|
|
10
|
+
waitForSubagentStartupRetry,
|
|
11
|
+
type SubagentStartupFailureEvidence,
|
|
12
|
+
} from "../../src/runs/shared/subagent-startup-retry.ts";
|
|
13
|
+
|
|
14
|
+
function emptyUsage(): Usage {
|
|
15
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function startupFailure(overrides: Partial<SubagentStartupFailureEvidence> = {}): SubagentStartupFailureEvidence {
|
|
19
|
+
return {
|
|
20
|
+
exitCode: 1,
|
|
21
|
+
messageCount: 0,
|
|
22
|
+
toolCount: 0,
|
|
23
|
+
usage: emptyUsage(),
|
|
24
|
+
durationMs: 20,
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("subagent startup retry", () => {
|
|
30
|
+
it("uses a bounded retry schedule", () => {
|
|
31
|
+
assert.deepEqual(SUBAGENT_STARTUP_RETRY_DELAYS_MS, [250, 750, 1500]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("classifies a short non-zero exit with no child activity", () => {
|
|
35
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure()), true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects successful, long-running, and diagnosed exits", () => {
|
|
39
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ exitCode: 0 })), false);
|
|
40
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ durationMs: MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS + 1 })), false);
|
|
41
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ error: "authentication failed" })), false);
|
|
42
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ finalOutput: "partial response" })), false);
|
|
43
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ protocolError: { code: "protocol_output_limit" } })), false);
|
|
44
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ processSignal: "SIGKILL" })), false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("rejects any model, tool, usage, mutation, or lifecycle activity", () => {
|
|
48
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ messageCount: 1 })), false);
|
|
49
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ toolCount: 1 })), false);
|
|
50
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ usage: { ...emptyUsage(), input: 1 } })), false);
|
|
51
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ usage: { ...emptyUsage(), turns: 1 } })), false);
|
|
52
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ observedMutationAttempt: true })), false);
|
|
53
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ detached: true })), false);
|
|
54
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ interrupted: true })), false);
|
|
55
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ timedOut: true })), false);
|
|
56
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ stopped: true })), false);
|
|
57
|
+
assert.equal(isRetryableSubagentStartupFailure(startupFailure({ turnBudgetExceeded: true })), false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("formats retry and exhaustion diagnostics without task content", () => {
|
|
61
|
+
assert.equal(
|
|
62
|
+
formatSubagentStartupRetryNote({ model: "openai/test", attempt: 1, maxAttempts: 4, delayMs: 250 }),
|
|
63
|
+
"[startup-retry] openai/test exited before model or tool activity (attempt 1/4). Retrying the same model in 250ms.",
|
|
64
|
+
);
|
|
65
|
+
assert.match(
|
|
66
|
+
formatSubagentStartupRetryExhaustedError({ model: "openai/test", attempts: 4 }),
|
|
67
|
+
/failed to start after 4 attempts.*concurrent Pi startup race.*lower subagent concurrency/i,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("cancels a pending retry delay", async () => {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const wait = waitForSubagentStartupRetry(5000, [controller.signal]);
|
|
74
|
+
controller.abort();
|
|
75
|
+
assert.equal(await wait, false);
|
|
76
|
+
});
|
|
77
|
+
});
|