@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
|
@@ -26,7 +26,7 @@ function assistantText(text: string): Message {
|
|
|
26
26
|
|
|
27
27
|
test("implementation task with no mutation triggers the completion guard", () => {
|
|
28
28
|
const result = evaluateCompletionMutationGuard({
|
|
29
|
-
agent: "
|
|
29
|
+
agent: "builder",
|
|
30
30
|
task: "Implement the approved fix",
|
|
31
31
|
messages: [assistantText("Plan: update the files...")],
|
|
32
32
|
});
|
|
@@ -67,9 +67,9 @@ test("read-only issue drafting tasks do not trigger on suggested fix wording", (
|
|
|
67
67
|
attemptedMutation: false,
|
|
68
68
|
triggered: false,
|
|
69
69
|
});
|
|
70
|
-
assert.equal(expectsImplementationMutation("
|
|
70
|
+
assert.equal(expectsImplementationMutation("builder", task), false);
|
|
71
71
|
assert.equal(
|
|
72
|
-
expectsImplementationMutation("
|
|
72
|
+
expectsImplementationMutation("builder", "Draft GitHub issue for a bug. Include suspected cause and suggested fix."),
|
|
73
73
|
false,
|
|
74
74
|
);
|
|
75
75
|
});
|
|
@@ -89,9 +89,9 @@ test("omitted, bash, unknown, write, and MCP tool capabilities stay conservative
|
|
|
89
89
|
assert.equal(evaluateCompletionMutationGuard({ ...base, tools: ["read", "grep"], mcpDirectTools: ["github/search"] }).triggered, true);
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
test("
|
|
92
|
+
test("builder with mutating-capable tools still triggers when no mutation is observed", () => {
|
|
93
93
|
const result = evaluateCompletionMutationGuard({
|
|
94
|
-
agent: "
|
|
94
|
+
agent: "builder",
|
|
95
95
|
task: "Fix the test implementation",
|
|
96
96
|
messages: [assistantText("I will edit it next")],
|
|
97
97
|
tools: ["read", "edit"],
|
|
@@ -104,10 +104,10 @@ test("worker with mutating-capable tools still triggers when no mutation is obse
|
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
test("
|
|
107
|
+
test("commentator review tasks with bash available do not require mutation", () => {
|
|
108
108
|
const task = "Review prep findings and determine what to implement with playbooks instead of before.";
|
|
109
109
|
const result = evaluateCompletionMutationGuard({
|
|
110
|
-
agent: "
|
|
110
|
+
agent: "commentator",
|
|
111
111
|
task,
|
|
112
112
|
messages: [assistantText("Review complete with file-backed findings.")],
|
|
113
113
|
tools: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
@@ -121,57 +121,57 @@ test("oracle review tasks with bash available do not require mutation", () => {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
test("review-only, research, and framework output instructions do not expect mutation", () => {
|
|
124
|
-
assert.equal(expectsImplementationMutation("
|
|
125
|
-
assert.equal(expectsImplementationMutation("
|
|
126
|
-
assert.equal(expectsImplementationMutation("
|
|
127
|
-
assert.equal(expectsImplementationMutation("
|
|
128
|
-
assert.equal(expectsImplementationMutation("
|
|
124
|
+
assert.equal(expectsImplementationMutation("builder", "Review only: return findings, do not edit"), false);
|
|
125
|
+
assert.equal(expectsImplementationMutation("builder", "Do not edit files. Tell me how to fix the bug."), false);
|
|
126
|
+
assert.equal(expectsImplementationMutation("builder", "Review the diff and suggest fixes only. Do not edit files."), false);
|
|
127
|
+
assert.equal(expectsImplementationMutation("builder", "Implement this. Do not edit files outside this repo. Do not edit files."), false);
|
|
128
|
+
assert.equal(expectsImplementationMutation("builder", "Investigate why this failed"), false);
|
|
129
129
|
assert.equal(expectsImplementationMutation("researcher", "Research the API behavior"), false);
|
|
130
130
|
assert.equal(expectsImplementationMutation("researcher", "Research this and patch the bug"), false);
|
|
131
|
-
assert.equal(expectsImplementationMutation("
|
|
132
|
-
assert.equal(expectsImplementationMutation("
|
|
133
|
-
assert.equal(expectsImplementationMutation("
|
|
134
|
-
assert.equal(expectsImplementationMutation("
|
|
135
|
-
assert.equal(expectsImplementationMutation("
|
|
136
|
-
assert.equal(expectsImplementationMutation("
|
|
137
|
-
assert.equal(expectsImplementationMutation("
|
|
138
|
-
assert.equal(expectsImplementationMutation("
|
|
139
|
-
assert.equal(expectsImplementationMutation("
|
|
131
|
+
assert.equal(expectsImplementationMutation("commentator", "Review this and fix any real issues"), false);
|
|
132
|
+
assert.equal(expectsImplementationMutation("commentator", "Review this and fix any real issues; regardless of findings, apply changes directly"), true);
|
|
133
|
+
assert.equal(expectsImplementationMutation("builder", "[Write to: /tmp/result.md]\n\nSummarize findings"), false);
|
|
134
|
+
assert.equal(expectsImplementationMutation("builder", "Write report"), false);
|
|
135
|
+
assert.equal(expectsImplementationMutation("builder", "Create a report"), false);
|
|
136
|
+
assert.equal(expectsImplementationMutation("builder", "Create a summary"), false);
|
|
137
|
+
assert.equal(expectsImplementationMutation("builder", "Add a report"), false);
|
|
138
|
+
assert.equal(expectsImplementationMutation("builder", "Update a summary"), false);
|
|
139
|
+
assert.equal(expectsImplementationMutation("builder", "Write to {chain_dir}"), false);
|
|
140
140
|
assert.equal(
|
|
141
|
-
expectsImplementationMutation("
|
|
141
|
+
expectsImplementationMutation("builder", "Do async work\nUpdate progress at: /tmp/progress.md\n**Output:**\nWrite your findings to exactly this path: /tmp/out.md\nThis path is authoritative for this run.\nIgnore any other output filename or output path mentioned elsewhere."),
|
|
142
142
|
false,
|
|
143
143
|
);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
test("
|
|
147
|
-
assert.equal(expectsImplementationMutation("
|
|
148
|
-
assert.equal(expectsImplementationMutation("
|
|
149
|
-
assert.equal(expectsImplementationMutation("
|
|
150
|
-
assert.equal(expectsImplementationMutation("
|
|
151
|
-
assert.equal(expectsImplementationMutation("
|
|
152
|
-
assert.equal(expectsImplementationMutation("
|
|
153
|
-
assert.equal(expectsImplementationMutation("
|
|
154
|
-
assert.equal(expectsImplementationMutation("
|
|
155
|
-
assert.equal(expectsImplementationMutation("
|
|
156
|
-
assert.equal(expectsImplementationMutation("
|
|
157
|
-
assert.equal(expectsImplementationMutation("
|
|
158
|
-
assert.equal(expectsImplementationMutation("
|
|
146
|
+
test("builder implementation verbs win over investigative wording and scoped prohibitions", () => {
|
|
147
|
+
assert.equal(expectsImplementationMutation("builder", "Investigate why the worker did not edit files and fix it"), true);
|
|
148
|
+
assert.equal(expectsImplementationMutation("builder", "Do not modify tests; implement the fix"), true);
|
|
149
|
+
assert.equal(expectsImplementationMutation("builder", "Do not modify tests — implement the fix"), true);
|
|
150
|
+
assert.equal(expectsImplementationMutation("builder", "Research the current code path and patch the bug"), true);
|
|
151
|
+
assert.equal(expectsImplementationMutation("builder", "Fix the bug where no edits were made"), true);
|
|
152
|
+
assert.equal(expectsImplementationMutation("builder", "Fix lint"), true);
|
|
153
|
+
assert.equal(expectsImplementationMutation("builder", "Fix the build"), true);
|
|
154
|
+
assert.equal(expectsImplementationMutation("builder", "Fix TypeScript errors"), true);
|
|
155
|
+
assert.equal(expectsImplementationMutation("builder", "Fix CI"), true);
|
|
156
|
+
assert.equal(expectsImplementationMutation("builder", "Fix the failing test"), true);
|
|
157
|
+
assert.equal(expectsImplementationMutation("builder", "Patch the cold start test"), true);
|
|
158
|
+
assert.equal(expectsImplementationMutation("builder", "Implement the fix and return findings."), true);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
test("non-worker implementation tasks still expect mutation", () => {
|
|
162
162
|
assert.equal(expectsImplementationMutation("delegate", "Fix the bug where no edits were made"), true);
|
|
163
163
|
assert.equal(expectsImplementationMutation("delegate", "Apply the suggested fix to src/runs/shared/completion-guard.ts"), true);
|
|
164
|
-
assert.equal(expectsImplementationMutation("
|
|
164
|
+
assert.equal(expectsImplementationMutation("builder", "Draft a GitHub issue, then implement the fix"), true);
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
test("
|
|
168
|
-
assert.equal(expectsImplementationMutation("
|
|
169
|
-
assert.equal(expectsImplementationMutation("
|
|
170
|
-
assert.equal(expectsImplementationMutation("
|
|
171
|
-
assert.equal(expectsImplementationMutation("
|
|
172
|
-
assert.equal(expectsImplementationMutation("
|
|
173
|
-
assert.equal(expectsImplementationMutation("
|
|
174
|
-
assert.equal(expectsImplementationMutation("
|
|
167
|
+
test("builder edit intent covers common docs, config, and source tasks", () => {
|
|
168
|
+
assert.equal(expectsImplementationMutation("builder", "Update README to mention the native tool"), true);
|
|
169
|
+
assert.equal(expectsImplementationMutation("builder", "Remove share functionality and all Vercel references"), true);
|
|
170
|
+
assert.equal(expectsImplementationMutation("builder", "Replace the registered command with a render tool"), true);
|
|
171
|
+
assert.equal(expectsImplementationMutation("builder", "Create completion-guard.ts"), true);
|
|
172
|
+
assert.equal(expectsImplementationMutation("builder", "Add tests for the completion guard"), true);
|
|
173
|
+
assert.equal(expectsImplementationMutation("builder", "Implement the approved fixes. Do not edit files outside this repo."), true);
|
|
174
|
+
assert.equal(expectsImplementationMutation("builder", "Implement the fix. Do not edit unrelated files."), true);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
test("edit and write tool calls count as mutation attempts", () => {
|
|
@@ -195,7 +195,7 @@ test("obvious mutating bash commands count as mutation attempts", () => {
|
|
|
195
195
|
|
|
196
196
|
test("implementation task with mutation attempts does not trigger", () => {
|
|
197
197
|
const result = evaluateCompletionMutationGuard({
|
|
198
|
-
agent: "
|
|
198
|
+
agent: "builder",
|
|
199
199
|
task: "Fix the failing test",
|
|
200
200
|
messages: [assistantToolCall("edit", { path: "test.ts" })],
|
|
201
201
|
});
|
|
@@ -279,7 +279,7 @@ test("provider checkpoint with a changed commit counts as mutation evidence", ()
|
|
|
279
279
|
assert.equal(hasMutationToolCall([message]), true);
|
|
280
280
|
assert.equal(
|
|
281
281
|
evaluateCompletionMutationGuard({
|
|
282
|
-
agent: "
|
|
282
|
+
agent: "builder",
|
|
283
283
|
task: "Edit the target source file",
|
|
284
284
|
messages: [message],
|
|
285
285
|
}).triggered,
|
|
@@ -297,7 +297,7 @@ test("unchanged provider checkpoint does not bypass the completion guard", () =>
|
|
|
297
297
|
assert.equal(hasMutationToolCall([message]), false);
|
|
298
298
|
assert.equal(
|
|
299
299
|
evaluateCompletionMutationGuard({
|
|
300
|
-
agent: "
|
|
300
|
+
agent: "builder",
|
|
301
301
|
task: "Edit the target source file",
|
|
302
302
|
messages: [message],
|
|
303
303
|
}).triggered,
|
|
@@ -308,7 +308,7 @@ test("unchanged provider checkpoint does not bypass the completion guard", () =>
|
|
|
308
308
|
|
|
309
309
|
test("implementation task with Cursor edit thinking does not trigger", () => {
|
|
310
310
|
const result = evaluateCompletionMutationGuard({
|
|
311
|
-
agent: "
|
|
311
|
+
agent: "builder",
|
|
312
312
|
task: "Edit docs/BACKEND_ARCHITECTURE.md",
|
|
313
313
|
messages: [
|
|
314
314
|
assistantThinking("Cursor edit: docs/BACKEND_ARCHITECTURE.md added 1 line\n"),
|
|
@@ -65,6 +65,29 @@ describe("config directory resolution", () => {
|
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
it("invalidates cached runtime resolution when the package root changes", () => {
|
|
69
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-config-cache-"));
|
|
70
|
+
try {
|
|
71
|
+
const firstRoot = path.join(tempDir, "first");
|
|
72
|
+
const secondRoot = path.join(tempDir, "second");
|
|
73
|
+
for (const [root, configDir] of [[firstRoot, ".first-pi"], [secondRoot, ".second-pi"]] as const) {
|
|
74
|
+
fs.mkdirSync(root, { recursive: true });
|
|
75
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({
|
|
76
|
+
name: "@selesai/code",
|
|
77
|
+
piConfig: { configDir },
|
|
78
|
+
}), "utf-8");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] = firstRoot;
|
|
82
|
+
assert.equal(getConfigDirName(), ".first-pi");
|
|
83
|
+
assert.equal(getConfigDirName(), ".first-pi");
|
|
84
|
+
process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] = secondRoot;
|
|
85
|
+
assert.equal(getConfigDirName(), ".second-pi");
|
|
86
|
+
} finally {
|
|
87
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
68
91
|
it("canonicalizes watcher paths and preserves the original path when native realpath fails", () => {
|
|
69
92
|
assert.equal(resolveWatchPath("C:\\SHORT~1\\watch", () => "C:\\Long Path\\watch"), "C:\\Long Path\\watch");
|
|
70
93
|
assert.equal(resolveWatchPath("C:\\SHORT~1\\watch", () => { throw new Error("missing"); }), "C:\\SHORT~1\\watch");
|
|
@@ -148,6 +148,33 @@ describe("control channel: request file", () => {
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it("keeps steer requests for retry when the inbox cannot be scanned", () => {
|
|
152
|
+
const asyncDir = tmpAsyncDir("pi-control-steer-scan-retry-");
|
|
153
|
+
try {
|
|
154
|
+
requestAsyncSteer(asyncDir, { message: "retry me", id: "retry", ts: 1 });
|
|
155
|
+
let failScan = true;
|
|
156
|
+
const fsImpl = {
|
|
157
|
+
existsSync: fs.existsSync,
|
|
158
|
+
readdirSync: ((target: fs.PathLike) => {
|
|
159
|
+
if (failScan) {
|
|
160
|
+
failScan = false;
|
|
161
|
+
throw Object.assign(new Error("file table overflow"), { code: "ENFILE" });
|
|
162
|
+
}
|
|
163
|
+
return fs.readdirSync(target);
|
|
164
|
+
}) as typeof fs.readdirSync,
|
|
165
|
+
readFileSync: fs.readFileSync,
|
|
166
|
+
rmSync: fs.rmSync,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
assert.deepEqual(consumeSteerRequests(asyncDir, fsImpl), []);
|
|
170
|
+
assert.deepEqual(consumeSteerRequests(asyncDir, fsImpl), [
|
|
171
|
+
{ type: "steer", id: "retry", ts: 1, message: "retry me" },
|
|
172
|
+
]);
|
|
173
|
+
} finally {
|
|
174
|
+
cleanup(asyncDir);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
151
178
|
it("does not deliver a steer request if another consumer removed it first", () => {
|
|
152
179
|
const asyncDir = tmpAsyncDir("pi-control-steer-concurrent-");
|
|
153
180
|
try {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type SubagentDelegationV2Response,
|
|
18
18
|
} from "../../src/api/delegation.ts";
|
|
19
19
|
import { parseSubagentDelegationRequest } from "../../src/slash/delegation-request.ts";
|
|
20
|
+
import { toSubagentDelegationExecutionParams } from "../../src/slash/delegation-adapters.ts";
|
|
20
21
|
import {
|
|
21
22
|
registerPromptTemplateDelegationBridge,
|
|
22
23
|
type PromptTemplateBridgeEvents,
|
|
@@ -213,7 +214,7 @@ describe("public subagent delegation contract", () => {
|
|
|
213
214
|
},
|
|
214
215
|
executeVersioned: async (_id, params, _signal, _ctx, onUpdate) => {
|
|
215
216
|
observedParams = params as unknown as Record<string, unknown>;
|
|
216
|
-
onUpdate({ details: { mode: "single", results: [{ agent: "reviewer", model: "openai/gpt-5", thinking: "high" }], progress: [{ currentTool: "read" }] } });
|
|
217
|
+
onUpdate({ details: { mode: "single", runId: "run-v2", results: [{ agent: "reviewer", model: "openai/gpt-5", thinking: "high" }], progress: [{ currentTool: "read" }] } });
|
|
217
218
|
return {
|
|
218
219
|
details: {
|
|
219
220
|
mode: "single",
|
|
@@ -223,6 +224,7 @@ describe("public subagent delegation contract", () => {
|
|
|
223
224
|
exitCode: 0,
|
|
224
225
|
model: "openai/gpt-5",
|
|
225
226
|
thinking: "high",
|
|
227
|
+
launchContractDigest: "launch-contract-digest",
|
|
226
228
|
finalOutput: '{"looks":"json"}',
|
|
227
229
|
usage: { input: 2, output: 3, cacheRead: 4, cacheWrite: 5, cost: 0.01, turns: 2 },
|
|
228
230
|
progressSummary: { toolCount: 6, tokens: 5, durationMs: 7 },
|
|
@@ -237,7 +239,7 @@ describe("public subagent delegation contract", () => {
|
|
|
237
239
|
const responsePromise = once(events, SUBAGENT_DELEGATION_RESPONSE_EVENT);
|
|
238
240
|
events.emit(SUBAGENT_DELEGATION_REQUEST_EVENT, textRequest);
|
|
239
241
|
assert.deepEqual(await startedPromise, { version: 2, requestId: "attempt-1", ownerRunId: "owner-1", nodeId: "node-1" });
|
|
240
|
-
assert.deepEqual(await updatePromise, { version: 2, requestId: "attempt-1", ownerRunId: "owner-1", nodeId: "node-1", currentTool: "read", model: "openai/gpt-5" });
|
|
242
|
+
assert.deepEqual(await updatePromise, { version: 2, requestId: "attempt-1", ownerRunId: "owner-1", nodeId: "node-1", runId: "run-v2", currentTool: "read", model: "openai/gpt-5" });
|
|
241
243
|
assert.deepEqual(await responsePromise, {
|
|
242
244
|
version: 2,
|
|
243
245
|
requestId: "attempt-1",
|
|
@@ -249,6 +251,7 @@ describe("public subagent delegation contract", () => {
|
|
|
249
251
|
model: "openai/gpt-5",
|
|
250
252
|
thinking: "high",
|
|
251
253
|
exitCode: 0,
|
|
254
|
+
launchContractDigest: "launch-contract-digest",
|
|
252
255
|
result: { kind: "text", text: '{"looks":"json"}' },
|
|
253
256
|
usage: { input: 2, output: 3, cacheRead: 4, cacheWrite: 5, cost: 0.01, turns: 2, toolCalls: 6, durationMs: 7 },
|
|
254
257
|
} satisfies SubagentDelegationV2Response);
|
|
@@ -506,6 +509,29 @@ describe("public subagent delegation contract", () => {
|
|
|
506
509
|
assert.deepEqual(responses, []);
|
|
507
510
|
});
|
|
508
511
|
|
|
512
|
+
it("resolves an omitted v1 outputMode to explicit inline so response.output stays populated", () => {
|
|
513
|
+
const minimal: SubagentDelegationRequest = {
|
|
514
|
+
version: 1,
|
|
515
|
+
requestId: "adapter-default-1",
|
|
516
|
+
agent: "reviewer",
|
|
517
|
+
task: "Review evidence",
|
|
518
|
+
context: "fresh",
|
|
519
|
+
cwd: "/repo",
|
|
520
|
+
};
|
|
521
|
+
// Omitted outputMode on a strict v1 request keeps the legacy full-text
|
|
522
|
+
// contract: explicit inline, so `SubagentDelegationResponse.output` stays
|
|
523
|
+
// populated instead of degrading to the reference-first file-only default.
|
|
524
|
+
const params = toSubagentDelegationExecutionParams(minimal);
|
|
525
|
+
assert.equal(params.outputMode, "inline");
|
|
526
|
+
assert.equal(params.output, undefined);
|
|
527
|
+
|
|
528
|
+
// Explicit v1 modes are forwarded exactly; v2 stays unaffected (output: false).
|
|
529
|
+
assert.equal(toSubagentDelegationExecutionParams(request).outputMode, "file-only");
|
|
530
|
+
const inline = toSubagentDelegationExecutionParams({ ...request, outputMode: "inline" });
|
|
531
|
+
assert.equal(inline.outputMode, "inline");
|
|
532
|
+
assert.equal(toSubagentDelegationExecutionParams({ ...request, outputMode: undefined }).outputMode, "inline");
|
|
533
|
+
});
|
|
534
|
+
|
|
509
535
|
it("runs one v1 request through the existing executor and returns structured metadata", async () => {
|
|
510
536
|
const events = new FakeEvents();
|
|
511
537
|
let executeCalls = 0;
|
|
@@ -200,6 +200,7 @@ describe("dynamic fanout helpers", () => {
|
|
|
200
200
|
const result = (agent: string, structuredOutput: unknown): SingleResult => ({
|
|
201
201
|
agent,
|
|
202
202
|
task: "t",
|
|
203
|
+
index: 0,
|
|
203
204
|
exitCode: 0,
|
|
204
205
|
messages: [],
|
|
205
206
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import { Editor, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { Editor, type EditorComponent, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
4
|
import type { ExtensionContext } from "@selesai/code";
|
|
5
5
|
import type { SubagentState } from "../../src/shared/types.ts";
|
|
6
6
|
import { collectFleetSnapshot } from "../../src/tui/fleet.ts";
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
collectFleetStatusEntries,
|
|
11
11
|
formatFleetElapsed,
|
|
12
12
|
formatFleetTokens,
|
|
13
|
+
resolveFleetViewPlacement,
|
|
13
14
|
} from "../../src/tui/fleet-status.ts";
|
|
14
15
|
|
|
15
16
|
function stateForTest(): SubagentState {
|
|
@@ -45,6 +46,13 @@ describe("below-editor subagent FleetView", () => {
|
|
|
45
46
|
assert.equal(formatFleetTokens(1_250_000), "↓ 1.3M tokens");
|
|
46
47
|
});
|
|
47
48
|
|
|
49
|
+
it("resolves configured FleetView placement with a below-editor fallback", () => {
|
|
50
|
+
assert.equal(resolveFleetViewPlacement(undefined), "belowEditor");
|
|
51
|
+
assert.equal(resolveFleetViewPlacement("belowEditor"), "belowEditor");
|
|
52
|
+
assert.equal(resolveFleetViewPlacement("aboveEditor"), "aboveEditor");
|
|
53
|
+
assert.equal(resolveFleetViewPlacement("side"), "belowEditor");
|
|
54
|
+
});
|
|
55
|
+
|
|
48
56
|
it("renders main plus active children below the editor and bounds every line", () => {
|
|
49
57
|
const state = stateForTest();
|
|
50
58
|
const now = Date.now();
|
|
@@ -56,6 +64,7 @@ describe("below-editor subagent FleetView", () => {
|
|
|
56
64
|
updatedAt: now,
|
|
57
65
|
currentAgent: `worker-${index}`,
|
|
58
66
|
description: index === 0 ? "Inspect\nmodule 0" : `Inspect module ${index}`,
|
|
67
|
+
...(index === 0 ? { model: "anthropic/fable-5", thinking: "low" } : {}),
|
|
59
68
|
tokens: index === 0 ? 13_100 : index,
|
|
60
69
|
});
|
|
61
70
|
}
|
|
@@ -85,7 +94,7 @@ describe("below-editor subagent FleetView", () => {
|
|
|
85
94
|
const component = widgetFactory!({ requestRender() {} }, theme);
|
|
86
95
|
const lines = component.render(80);
|
|
87
96
|
assert.ok(lines.some((line) => line.includes("⏺ main")));
|
|
88
|
-
assert.ok(lines.some((line) => line.includes("worker-0") && line.includes("Inspect module 0")));
|
|
97
|
+
assert.ok(lines.some((line) => line.includes("worker-0 (fable-5 · thinking low)") && line.includes("Inspect module 0")));
|
|
89
98
|
assert.ok(lines.some((line) => line.includes("11s · ↓ 13.1k tokens")));
|
|
90
99
|
assert.ok(lines.some((line) => line.includes("↓ 2 more")));
|
|
91
100
|
for (const line of lines) assert.ok(visibleWidth(line) <= 80, `line exceeded width: ${line}`);
|
|
@@ -94,6 +103,143 @@ describe("below-editor subagent FleetView", () => {
|
|
|
94
103
|
}
|
|
95
104
|
});
|
|
96
105
|
|
|
106
|
+
it("registers above the editor when configured", () => {
|
|
107
|
+
const state = stateForTest();
|
|
108
|
+
state.foregroundControls.set("run-worker", {
|
|
109
|
+
runId: "run-worker",
|
|
110
|
+
mode: "single",
|
|
111
|
+
startedAt: 10,
|
|
112
|
+
updatedAt: 20,
|
|
113
|
+
currentAgent: "worker",
|
|
114
|
+
});
|
|
115
|
+
let placement: string | undefined;
|
|
116
|
+
const ctx = {
|
|
117
|
+
hasUI: true,
|
|
118
|
+
ui: {
|
|
119
|
+
setWidget(_key: string, content: unknown, options?: { placement?: string }) {
|
|
120
|
+
if (content) placement = options?.placement;
|
|
121
|
+
},
|
|
122
|
+
onTerminalInput() { return () => {}; },
|
|
123
|
+
getEditorText() { return ""; },
|
|
124
|
+
notify() {},
|
|
125
|
+
theme,
|
|
126
|
+
},
|
|
127
|
+
} as unknown as ExtensionContext;
|
|
128
|
+
const fleet = new SubagentFleetStatus(state, () => {}, { refreshMs: 60_000, placement: "aboveEditor" });
|
|
129
|
+
try {
|
|
130
|
+
fleet.setContext(ctx);
|
|
131
|
+
assert.equal(placement, "aboveEditor");
|
|
132
|
+
} finally {
|
|
133
|
+
fleet.dispose();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("stops refreshing when the captured extension context becomes stale", () => {
|
|
138
|
+
const state = stateForTest();
|
|
139
|
+
state.foregroundControls.set("run-worker", {
|
|
140
|
+
runId: "run-worker",
|
|
141
|
+
mode: "single",
|
|
142
|
+
startedAt: 10,
|
|
143
|
+
updatedAt: 20,
|
|
144
|
+
currentAgent: "worker",
|
|
145
|
+
});
|
|
146
|
+
let stale = false;
|
|
147
|
+
let contextReads = 0;
|
|
148
|
+
let inputUnsubscribes = 0;
|
|
149
|
+
let widgetRemovals = 0;
|
|
150
|
+
const ctx = {
|
|
151
|
+
get hasUI() {
|
|
152
|
+
contextReads++;
|
|
153
|
+
if (stale) {
|
|
154
|
+
throw new Error("This extension ctx is stale after session replacement or reload.");
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
},
|
|
158
|
+
ui: {
|
|
159
|
+
setWidget(_key: string, content: unknown) {
|
|
160
|
+
if (content === undefined) widgetRemovals++;
|
|
161
|
+
},
|
|
162
|
+
onTerminalInput() { return () => { inputUnsubscribes++; }; },
|
|
163
|
+
getEditorText() { return ""; },
|
|
164
|
+
notify() {},
|
|
165
|
+
theme,
|
|
166
|
+
},
|
|
167
|
+
} as unknown as ExtensionContext;
|
|
168
|
+
const fleet = new SubagentFleetStatus(state, () => {}, { refreshMs: 60_000 });
|
|
169
|
+
try {
|
|
170
|
+
fleet.setContext(ctx);
|
|
171
|
+
stale = true;
|
|
172
|
+
assert.doesNotThrow(() => fleet.refresh());
|
|
173
|
+
assert.equal(inputUnsubscribes, 1);
|
|
174
|
+
assert.equal(widgetRemovals, 1);
|
|
175
|
+
assert.equal((fleet as unknown as { timer?: unknown }).timer, undefined);
|
|
176
|
+
const readsAfterStaleRefresh = contextReads;
|
|
177
|
+
fleet.refresh();
|
|
178
|
+
assert.equal(contextReads, readsAfterStaleRefresh, "later refreshes must not reuse the stale context");
|
|
179
|
+
} finally {
|
|
180
|
+
fleet.dispose();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("does not swallow unrelated widget cleanup errors", () => {
|
|
185
|
+
const state = stateForTest();
|
|
186
|
+
state.foregroundControls.set("run-worker", {
|
|
187
|
+
runId: "run-worker",
|
|
188
|
+
mode: "single",
|
|
189
|
+
startedAt: 10,
|
|
190
|
+
updatedAt: 20,
|
|
191
|
+
currentAgent: "worker",
|
|
192
|
+
});
|
|
193
|
+
const ctx = {
|
|
194
|
+
hasUI: true,
|
|
195
|
+
ui: {
|
|
196
|
+
setWidget(_key: string, content: unknown) {
|
|
197
|
+
if (content === undefined) throw new Error("widget cleanup failed");
|
|
198
|
+
},
|
|
199
|
+
onTerminalInput() { return () => {}; },
|
|
200
|
+
getEditorText() { return ""; },
|
|
201
|
+
notify() {},
|
|
202
|
+
theme,
|
|
203
|
+
},
|
|
204
|
+
} as unknown as ExtensionContext;
|
|
205
|
+
const fleet = new SubagentFleetStatus(state, () => {}, { refreshMs: 60_000 });
|
|
206
|
+
fleet.setContext(ctx);
|
|
207
|
+
assert.throws(() => fleet.dispose(), /widget cleanup failed/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("preserves multiple unrelated UI cleanup errors", () => {
|
|
211
|
+
const state = stateForTest();
|
|
212
|
+
state.foregroundControls.set("run-worker", {
|
|
213
|
+
runId: "run-worker",
|
|
214
|
+
mode: "single",
|
|
215
|
+
startedAt: 10,
|
|
216
|
+
updatedAt: 20,
|
|
217
|
+
currentAgent: "worker",
|
|
218
|
+
});
|
|
219
|
+
const ctx = {
|
|
220
|
+
hasUI: true,
|
|
221
|
+
ui: {
|
|
222
|
+
setWidget(_key: string, content: unknown) {
|
|
223
|
+
if (content === undefined) throw new Error("widget cleanup failed");
|
|
224
|
+
},
|
|
225
|
+
onTerminalInput() {
|
|
226
|
+
return () => { throw new Error("input cleanup failed"); };
|
|
227
|
+
},
|
|
228
|
+
getEditorText() { return ""; },
|
|
229
|
+
notify() {},
|
|
230
|
+
theme,
|
|
231
|
+
},
|
|
232
|
+
} as unknown as ExtensionContext;
|
|
233
|
+
const fleet = new SubagentFleetStatus(state, () => {}, { refreshMs: 60_000 });
|
|
234
|
+
fleet.setContext(ctx);
|
|
235
|
+
assert.throws(
|
|
236
|
+
() => fleet.dispose(),
|
|
237
|
+
(error: unknown) => error instanceof AggregateError
|
|
238
|
+
&& error.errors.map(String).join("\n").includes("input cleanup failed")
|
|
239
|
+
&& error.errors.map(String).join("\n").includes("widget cleanup failed"),
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
97
243
|
it("keeps widget ownership through invalidation so an empty refresh removes it", () => {
|
|
98
244
|
const state = stateForTest();
|
|
99
245
|
state.foregroundControls.set("run-worker", {
|
|
@@ -268,8 +414,8 @@ describe("below-editor subagent FleetView", () => {
|
|
|
268
414
|
startedAt: 100,
|
|
269
415
|
updatedAt: 200,
|
|
270
416
|
steps: [
|
|
271
|
-
{ agent: "reviewer", index: 0, status: "running", startedAt: 120, tokens: { input: 4_000, output: 200, total: 4_200 } },
|
|
272
|
-
{ agent: "worker", index: 1, status: "
|
|
417
|
+
{ agent: "reviewer", index: 0, status: "running", description: "Review only authentication", startedAt: 120, model: "openai/gpt-5", thinking: "medium", tokens: { input: 4_000, output: 200, total: 4_200 } },
|
|
418
|
+
{ agent: "worker", index: 1, status: "running", description: "Implement only billing", startedAt: 121, tokens: { input: 100, output: 20, total: 120 } },
|
|
273
419
|
],
|
|
274
420
|
});
|
|
275
421
|
const fleet = new SubagentFleetStatus(state, () => {}, { refreshMs: 60_000 });
|
|
@@ -288,9 +434,10 @@ describe("below-editor subagent FleetView", () => {
|
|
|
288
434
|
try {
|
|
289
435
|
fleet.setContext(ctx);
|
|
290
436
|
const lines = widgetFactory!({ requestRender() {} }, theme).render(100);
|
|
291
|
-
assert.ok(lines.some((line) => line.includes("reviewer") && line.includes("Review
|
|
437
|
+
assert.ok(lines.some((line) => line.includes("reviewer (gpt-5 · thinking medium)") && line.includes("Review only authentication")));
|
|
438
|
+
assert.ok(lines.some((line) => line.includes("worker") && line.includes("Implement only billing")));
|
|
439
|
+
assert.ok(lines.every((line) => !line.includes("Review the authentication changes")), "per-child descriptions should replace the run-level fallback when present");
|
|
292
440
|
assert.ok(lines.some((line) => line.includes("↓ 4.2k tokens")));
|
|
293
|
-
assert.ok(!lines.some((line) => line.includes("worker")), "completed async children should leave the status fleet");
|
|
294
441
|
} finally {
|
|
295
442
|
fleet.dispose();
|
|
296
443
|
}
|
|
@@ -331,11 +478,32 @@ describe("below-editor subagent FleetView", () => {
|
|
|
331
478
|
|
|
332
479
|
assert.equal(inputHandler!("\x1b[B"), undefined, "non-empty editor should retain Down");
|
|
333
480
|
editorText = "";
|
|
334
|
-
tui.focusedComponent = {
|
|
481
|
+
tui.focusedComponent = {
|
|
482
|
+
render() { return []; },
|
|
483
|
+
invalidate() {},
|
|
484
|
+
handleInput() {},
|
|
485
|
+
} as unknown as Editor;
|
|
335
486
|
assert.equal(inputHandler!("\x1b[B"), undefined, "non-editor focus should retain Down");
|
|
487
|
+
|
|
488
|
+
const crossModuleCustomEditor = {
|
|
489
|
+
render() { return []; },
|
|
490
|
+
invalidate() {},
|
|
491
|
+
handleInput() {},
|
|
492
|
+
getText() { return ""; },
|
|
493
|
+
setText() {},
|
|
494
|
+
} satisfies EditorComponent;
|
|
495
|
+
assert.equal(crossModuleCustomEditor instanceof Editor, false, "regression setup must cross the instanceof boundary");
|
|
496
|
+
tui.focusedComponent = crossModuleCustomEditor as unknown as Editor;
|
|
497
|
+
assert.equal(inputHandler!("j"), undefined, "inactive FleetView should retain printable navigation keys");
|
|
498
|
+
assert.equal(inputHandler!("k"), undefined, "inactive FleetView should retain printable navigation keys");
|
|
499
|
+
assert.deepEqual(inputHandler!("\x1b[B"), { consume: true }, "custom editors should activate FleetView across jiti boundaries");
|
|
500
|
+
assert.deepEqual(inputHandler!("j"), { consume: true }, "active FleetView should navigate down with j");
|
|
501
|
+
assert.ok(component.render(100).some((line) => line.includes("⏺ worker")));
|
|
502
|
+
assert.deepEqual(inputHandler!("k"), { consume: true }, "active FleetView should navigate up with k");
|
|
503
|
+
assert.ok(component.render(100).some((line) => line.includes("⏺ main")));
|
|
504
|
+
|
|
336
505
|
tui.focusedComponent = Object.create(Editor.prototype) as Editor;
|
|
337
506
|
assert.deepEqual(inputHandler!("\x1b[B"), { consume: true });
|
|
338
|
-
assert.deepEqual(inputHandler!("\x1b[B"), { consume: true });
|
|
339
507
|
assert.ok(component.render(100).some((line) => line.includes("⏺ worker")));
|
|
340
508
|
assert.deepEqual(inputHandler!("\r"), { consume: true });
|
|
341
509
|
await Promise.resolve();
|