@selesai/code 0.13.15 → 0.13.18
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 +37 -0
- package/README.md +2 -14
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
- package/dist/bun/sandbox-env-setup.d.ts +1 -0
- package/dist/bun/sandbox-env-setup.js +3 -0
- package/dist/cli/auth-command.d.ts +23 -0
- package/dist/cli/auth-command.js +102 -0
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +11 -0
- package/dist/cli.js +2 -14
- package/dist/config.d.ts +3 -2
- package/dist/config.js +9 -4
- package/dist/core/agent-session-runtime.js +15 -7
- package/dist/core/agent-session.d.ts +2 -6
- package/dist/core/agent-session.js +15 -4
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/types.d.ts +16 -2
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/keybindings.d.ts +5 -0
- package/dist/core/keybindings.js +4 -0
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/model-config.d.ts +15 -0
- package/dist/core/model-config.js +3 -0
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.js +5 -2
- package/dist/core/provider-composer.js +8 -2
- package/dist/core/session-export.d.ts +3 -0
- package/dist/core/session-export.js +31 -0
- package/dist/core/session-manager.d.ts +3 -2
- package/dist/core/session-manager.js +40 -13
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.js +4 -2
- package/dist/core/system-prompt.js +10 -10
- package/dist/core/tools/bash.js +3 -117
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.js +5 -163
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.js +10 -122
- package/dist/core/tools/renderers/bash.d.ts +11 -0
- package/dist/core/tools/renderers/bash.js +129 -0
- package/dist/core/tools/renderers/edit.d.ts +22 -0
- package/dist/core/tools/renderers/edit.js +166 -0
- package/dist/core/tools/renderers/find.d.ts +9 -0
- package/dist/core/tools/renderers/find.js +63 -0
- package/dist/core/tools/renderers/grep.d.ts +9 -0
- package/dist/core/tools/renderers/grep.js +68 -0
- package/dist/core/tools/renderers/index.d.ts +27 -0
- package/dist/core/tools/renderers/index.js +46 -0
- package/dist/core/tools/renderers/ls.d.ts +9 -0
- package/dist/core/tools/renderers/ls.js +57 -0
- package/dist/core/tools/renderers/read.d.ts +10 -0
- package/dist/core/tools/renderers/read.js +128 -0
- package/dist/core/tools/renderers/write.d.ts +9 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/write.js +5 -146
- package/dist/extensions/capability-gateway/index.ts +12 -1
- package/dist/extensions/capability-gateway/integration.test.ts +23 -0
- package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
- package/dist/extensions/pi-hermes-memory/README.md +16 -12
- package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
- package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
- package/dist/extensions/pi-hermes-memory/package.json +4 -4
- package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
- package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
- package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
- package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
- package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
- package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
- package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
- package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
- package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
- package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
- package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
- package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
- package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
- package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
- package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
- package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
- package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
- package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
- package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
- package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
- package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
- package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
- package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
- package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
- package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
- package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
- package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +16 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
- package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
- package/dist/extensions/pi-intercom/index.ts +1 -1
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +1 -6
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
- package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
- package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
- package/dist/extensions/pi-subagents/README.md +3 -3
- package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/docs/agents.md +32 -16
- package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
- package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
- package/dist/extensions/pi-subagents/docs/missions.md +2 -0
- package/dist/extensions/pi-subagents/docs/models.md +58 -1
- package/dist/extensions/pi-subagents/docs/observability.md +50 -12
- package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
- package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
- package/dist/extensions/pi-subagents/package-lock.json +747 -177
- package/dist/extensions/pi-subagents/package.json +6 -4
- package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
- package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
- package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
- package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
- package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
- package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
- package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
- package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
- package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
- package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
- package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
- package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
- package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
- package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
- package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
- package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
- package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
- package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
- package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
- package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
- package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
- package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
- package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
- package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
- package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
- package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
- package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
- package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
- package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
- package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
- package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
- package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
- package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
- package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
- package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
- package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
- package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
- package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
- package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
- package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
- package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
- package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
- package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
- package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
- package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
- package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
- package/dist/extensions/pi-web-agent/src/types.ts +1 -1
- package/dist/extensions/pi-zentui/README.md +17 -0
- package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
- package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
- package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
- package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
- package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
- package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
- package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
- package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
- package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
- package/dist/extensions/pi-zentui/package-lock.json +277 -517
- package/dist/extensions/pi-zentui/package.json +7 -4
- package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
- package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
- package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
- package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
- package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
- package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
- package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
- package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
- package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
- package/dist/index.d.ts +1 -1
- package/dist/main.js +4 -1
- package/dist/modes/interactive/chat-viewport.d.ts +19 -0
- package/dist/modes/interactive/chat-viewport.js +27 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
- package/dist/modes/interactive/components/assistant-message.js +30 -13
- package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
- package/dist/modes/interactive/components/custom-editor.js +39 -1
- package/dist/modes/interactive/components/first-time-setup.js +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
- package/dist/modes/interactive/components/markdown-transform.js +18 -0
- package/dist/modes/interactive/components/model-selector.js +19 -17
- package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
- package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -20
- package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
- package/dist/modes/interactive/components/settings-submenu.js +164 -0
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.js +75 -18
- package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
- package/dist/modes/interactive/components/tool-execution.js +35 -33
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/interactive-mode.d.ts +13 -15
- package/dist/modes/interactive/interactive-mode.js +144 -80
- package/dist/modes/interactive/session-share.d.ts +15 -0
- package/dist/modes/interactive/session-share.js +183 -0
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
- package/dist/modes/interactive/theme/theme-json.js +129 -0
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/tui-renderer.d.ts +20 -0
- package/dist/modes/interactive/tui-renderer.js +65 -0
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/management-http.d.ts +24 -0
- package/dist/utils/management-http.js +53 -0
- package/dist/utils/text.d.ts +7 -0
- package/dist/utils/text.js +8 -0
- package/dist/utils/tools-manager.d.ts +1 -0
- package/dist/utils/tools-manager.js +43 -21
- package/docs/docs.json +0 -4
- package/package.json +4 -4
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
- package/dist/skills/implanger/SKILL.md +0 -70
- package/dist/skills/output/SKILL.md +0 -49
- package/dist/skills/planger/SKILL.md +0 -167
- package/dist/skills/workflow/SKILL.md +0 -95
- package/docs/workflows.md +0 -18
- /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
|
@@ -2,34 +2,18 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
SUBAGENT_CHILD_AGENT_ENV,
|
|
9
|
-
SUBAGENT_CHILD_INDEX_ENV,
|
|
10
|
-
SUBAGENT_FANOUT_CHILD_ENV,
|
|
11
|
-
SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV,
|
|
12
|
-
SUBAGENT_ORCHESTRATOR_TARGET_ENV,
|
|
13
|
-
SUBAGENT_RUN_ID_ENV,
|
|
14
|
-
SUBAGENT_STEER_ACK_DIR_ENV,
|
|
15
|
-
SUBAGENT_STEER_CAPABILITY_ENV,
|
|
16
|
-
SUBAGENT_STEER_INBOX_ENV,
|
|
17
|
-
SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
|
|
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";
|
|
20
|
-
import { clearStructuredOutputCaptures, STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV, STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "../../src/runs/shared/structured-output.ts";
|
|
21
|
-
import { TOOL_BUDGET_ENV } from "../../src/runs/shared/tool-budget.ts";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
|
+
import { RUNTIME_EXTENSION_ACK_EVENT } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
|
|
7
|
+
import { clearStructuredOutputCaptures } from "../../src/runs/shared/structured-output.ts";
|
|
22
8
|
import { getAgentDir } from "../../src/shared/utils.ts";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
9
|
+
import { formatChildToolDiagnostic, type ChildToolDiagnostic } from "../../src/runs/shared/tool-availability.ts";
|
|
10
|
+
import type { ChildRuntimeConfig } from "../../src/runs/shared/child-runtime-config.ts";
|
|
11
|
+
import type { ChildWatchdogConfig } from "../../src/watchdog/child-status.ts";
|
|
26
12
|
import { SUBAGENT_WATCHDOG_WARNING_TYPE } from "../../src/watchdog/types.ts";
|
|
27
13
|
import registerSubagentPromptRuntime, {
|
|
28
14
|
CHILD_FANOUT_BOUNDARY_INSTRUCTIONS,
|
|
29
15
|
CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS,
|
|
30
|
-
SUBAGENT_INTERCOM_SESSION_NAME_ENV,
|
|
31
16
|
registerPermissionGate,
|
|
32
|
-
registerSteeringInbox,
|
|
33
17
|
rewriteSubagentPrompt,
|
|
34
18
|
stripGlobalContext,
|
|
35
19
|
stripInheritedSkills,
|
|
@@ -38,34 +22,34 @@ import registerSubagentPromptRuntime, {
|
|
|
38
22
|
stripSubagentOrchestrationSkill,
|
|
39
23
|
} from "../../src/runs/shared/subagent-prompt-runtime.ts";
|
|
40
24
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
25
|
+
function childConfig(overrides: Partial<ChildRuntimeConfig> = {}): ChildRuntimeConfig {
|
|
26
|
+
return { fanoutChild: false, depth: 1, waitTool: { enabled: true }, fast: false, ...overrides };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function supervisorConfig(overrides: Partial<ChildRuntimeConfig> = {}): ChildRuntimeConfig {
|
|
30
|
+
return childConfig({
|
|
31
|
+
orchestratorTarget: "subagent-chat-parent",
|
|
32
|
+
orchestratorSessionId: "session-parent",
|
|
33
|
+
supervisorChannelDir: path.join(os.tmpdir(), "subagent-supervisor-runtime-test"),
|
|
34
|
+
runId: "run-123",
|
|
35
|
+
agent: "worker",
|
|
36
|
+
childIndex: 0,
|
|
37
|
+
...overrides,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const watchdogConfig: ChildWatchdogConfig = {
|
|
42
|
+
enabled: true,
|
|
43
|
+
runId: "run-1",
|
|
44
|
+
agent: "worker",
|
|
45
|
+
childIndex: 0,
|
|
46
|
+
watchdogTailTimeoutMs: 1000,
|
|
47
|
+
agentEndTimeoutMs: 500,
|
|
48
|
+
maxWarnings: null,
|
|
49
|
+
lsp: { enabled: false, timeoutMs: 3000, maxFiles: 20, maxDiagnostics: 50 },
|
|
50
|
+
stalemateRepeats: 2,
|
|
51
|
+
cadence: { everyNTools: null },
|
|
52
|
+
} as ChildWatchdogConfig;
|
|
69
53
|
|
|
70
54
|
const SKILLS_SECTION = "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>safe-bash</name>\n <description>desc</description>\n <location>/tmp/SKILL.md</location>\n </skill>\n <skill>\n <name>pi-subagents</name>\n <description>delegate to subagents</description>\n <location>/tmp/pi-subagents/SKILL.md</location>\n </skill>\n</available_skills>";
|
|
71
55
|
|
|
@@ -86,79 +70,18 @@ const PROMPT_WITH_EXPLICIT_SKILL = [
|
|
|
86
70
|
|
|
87
71
|
const CONFIGURED_SKILLS_SECTION = "\n\nThe following configured skills are available to this subagent.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n<available_skills>\n <skill>\n <name>configured-skill</name>\n <description>explicit agent skill</description>\n <location>/tmp/configured-skill/SKILL.md</location>\n </skill>\n</available_skills>";
|
|
88
72
|
|
|
89
|
-
afterEach(() => {
|
|
90
|
-
if (envSnapshot.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT;
|
|
91
|
-
else process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = envSnapshot.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT;
|
|
92
|
-
if (envSnapshot.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT;
|
|
93
|
-
else process.env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT = envSnapshot.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT;
|
|
94
|
-
if (envSnapshot.SELESAI_SUBAGENT_INHERIT_SKILLS === undefined) delete process.env.SELESAI_SUBAGENT_INHERIT_SKILLS;
|
|
95
|
-
else process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = envSnapshot.SELESAI_SUBAGENT_INHERIT_SKILLS;
|
|
96
|
-
if (envSnapshot.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME === undefined) delete process.env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME;
|
|
97
|
-
else process.env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME = envSnapshot.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME;
|
|
98
|
-
if (envSnapshot.SELESAI_SUBAGENT_FANOUT_CHILD === undefined) delete process.env.SELESAI_SUBAGENT_FANOUT_CHILD;
|
|
99
|
-
else process.env.SELESAI_SUBAGENT_FANOUT_CHILD = envSnapshot.SELESAI_SUBAGENT_FANOUT_CHILD;
|
|
100
|
-
if (envSnapshot.SELESAI_SUBAGENT_STEER_INBOX === undefined) delete process.env[SUBAGENT_STEER_INBOX_ENV];
|
|
101
|
-
else process.env[SUBAGENT_STEER_INBOX_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_INBOX;
|
|
102
|
-
if (envSnapshot.SELESAI_SUBAGENT_STEER_CAPABILITY === undefined) delete process.env[SUBAGENT_STEER_CAPABILITY_ENV];
|
|
103
|
-
else process.env[SUBAGENT_STEER_CAPABILITY_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_CAPABILITY;
|
|
104
|
-
if (envSnapshot.SELESAI_SUBAGENT_STEER_ACK_DIR === undefined) delete process.env[SUBAGENT_STEER_ACK_DIR_ENV];
|
|
105
|
-
else process.env[SUBAGENT_STEER_ACK_DIR_ENV] = envSnapshot.SELESAI_SUBAGENT_STEER_ACK_DIR;
|
|
106
|
-
if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE === undefined) delete process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
|
|
107
|
-
else process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE;
|
|
108
|
-
if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA === undefined) delete process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
109
|
-
else process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA;
|
|
110
|
-
if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE === undefined) delete process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV];
|
|
111
|
-
else process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE;
|
|
112
|
-
if (envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED === undefined) delete process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV];
|
|
113
|
-
else process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = envSnapshot.SELESAI_SUBAGENT_STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED;
|
|
114
|
-
if (envSnapshot.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS === undefined) delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
|
|
115
|
-
else process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = envSnapshot.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS;
|
|
116
|
-
if (envSnapshot.SELESAI_SUBAGENT_TOOL_BUDGET === undefined) delete process.env[TOOL_BUDGET_ENV];
|
|
117
|
-
else process.env[TOOL_BUDGET_ENV] = envSnapshot.SELESAI_SUBAGENT_TOOL_BUDGET;
|
|
118
|
-
if (envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY === undefined) delete process.env[PERMISSION_POLICY_ENV];
|
|
119
|
-
else process.env[PERMISSION_POLICY_ENV] = envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY;
|
|
120
|
-
if (envSnapshot.SELESAI_SUBAGENT_REQUIRED_TOOLS === undefined) delete process.env[REQUIRED_CHILD_TOOLS_ENV];
|
|
121
|
-
else process.env[REQUIRED_CHILD_TOOLS_ENV] = envSnapshot.SELESAI_SUBAGENT_REQUIRED_TOOLS;
|
|
122
|
-
if (envSnapshot.SELESAI_SUBAGENT_MCP_DIRECT_TOOLS === undefined) delete process.env[MCP_DIRECT_CHILD_TOOLS_ENV];
|
|
123
|
-
else process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = envSnapshot.SELESAI_SUBAGENT_MCP_DIRECT_TOOLS;
|
|
124
|
-
if (envSnapshot.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH === undefined) delete process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV];
|
|
125
|
-
else process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = envSnapshot.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH;
|
|
126
|
-
if (envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET === undefined) delete process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV];
|
|
127
|
-
else process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET;
|
|
128
|
-
if (envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID === undefined) delete process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV];
|
|
129
|
-
else process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV] = envSnapshot.SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID;
|
|
130
|
-
if (envSnapshot.SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR === undefined) delete process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV];
|
|
131
|
-
else process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] = envSnapshot.SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR;
|
|
132
|
-
if (envSnapshot.SELESAI_SUBAGENT_RUN_ID === undefined) delete process.env[SUBAGENT_RUN_ID_ENV];
|
|
133
|
-
else process.env[SUBAGENT_RUN_ID_ENV] = envSnapshot.SELESAI_SUBAGENT_RUN_ID;
|
|
134
|
-
if (envSnapshot.SELESAI_SUBAGENT_CHILD_AGENT === undefined) delete process.env[SUBAGENT_CHILD_AGENT_ENV];
|
|
135
|
-
else process.env[SUBAGENT_CHILD_AGENT_ENV] = envSnapshot.SELESAI_SUBAGENT_CHILD_AGENT;
|
|
136
|
-
if (envSnapshot.SELESAI_SUBAGENT_CHILD_INDEX === undefined) delete process.env[SUBAGENT_CHILD_INDEX_ENV];
|
|
137
|
-
else process.env[SUBAGENT_CHILD_INDEX_ENV] = envSnapshot.SELESAI_SUBAGENT_CHILD_INDEX;
|
|
138
|
-
if (envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG === undefined) delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
|
|
139
|
-
else process.env[CHILD_WATCHDOG_CONFIG_ENV] = envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG;
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
function setSupervisorEnv(): void {
|
|
144
|
-
process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = "subagent-chat-parent";
|
|
145
|
-
process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV] = "session-parent";
|
|
146
|
-
process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] = path.join(os.tmpdir(), "subagent-supervisor-runtime-test");
|
|
147
|
-
process.env[SUBAGENT_RUN_ID_ENV] = "run-123";
|
|
148
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
|
|
149
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
150
|
-
}
|
|
151
|
-
|
|
152
73
|
describe("subagent prompt runtime", () => {
|
|
74
|
+
it("ignores an unconfigured path-based load", () => {
|
|
75
|
+
assert.doesNotThrow(() => registerSubagentPromptRuntime({} as never));
|
|
76
|
+
});
|
|
77
|
+
|
|
153
78
|
it("registers no permission hook by default and routes ask only to the watchdog arbiter", async () => {
|
|
154
79
|
const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown> = [];
|
|
155
80
|
const pi = { on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx?: unknown) => unknown) { if (event === "tool_call") handlers.push(handler); } };
|
|
156
|
-
|
|
157
|
-
registerPermissionGate(pi as never);
|
|
81
|
+
registerPermissionGate(pi as never, undefined, undefined);
|
|
158
82
|
assert.equal(handlers.length, 0);
|
|
159
83
|
|
|
160
|
-
|
|
161
|
-
registerPermissionGate(pi as never);
|
|
84
|
+
registerPermissionGate(pi as never, { rules: { write: "deny" } }, undefined);
|
|
162
85
|
assert.equal(handlers.length, 1);
|
|
163
86
|
assert.equal(await handlers[0]!({ toolName: "bash", input: { command: "rm -rf /" } }), undefined);
|
|
164
87
|
assert.equal(await handlers[0]!({ toolName: "contact_supervisor", input: {} }), undefined);
|
|
@@ -167,10 +90,9 @@ describe("subagent prompt runtime", () => {
|
|
|
167
90
|
reason: "Blocked by pi-subagents permission rule: 'write' is denied.",
|
|
168
91
|
});
|
|
169
92
|
|
|
170
|
-
process.env[PERMISSION_POLICY_ENV] = JSON.stringify({ write: "ask" });
|
|
171
93
|
const askHandlers: Array<(event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown> = [];
|
|
172
94
|
const requests: Array<{ toolName: string; args: unknown }> = [];
|
|
173
|
-
registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown) { if (event === "tool_call") askHandlers.push(handler); } } as never, async (request) => {
|
|
95
|
+
registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: unknown) => unknown) { if (event === "tool_call") askHandlers.push(handler); } } as never, { rules: { write: "ask" } }, undefined, async (request) => {
|
|
174
96
|
requests.push({ toolName: request.toolName, args: request.args });
|
|
175
97
|
return { approved: true, reason: "approved by watchdog", source: "watchdog" };
|
|
176
98
|
});
|
|
@@ -179,19 +101,10 @@ describe("subagent prompt runtime", () => {
|
|
|
179
101
|
});
|
|
180
102
|
|
|
181
103
|
it("fails closed when an ask permission decision stalls", async () => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
process.env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify({
|
|
185
|
-
enabled: true,
|
|
186
|
-
watchdogTailTimeoutMs: 1_000,
|
|
187
|
-
agentEndTimeoutMs: 5,
|
|
188
|
-
maxWarnings: null,
|
|
189
|
-
lsp: { enabled: false, timeoutMs: 100, maxFiles: 1, maxDiagnostics: 1 },
|
|
190
|
-
stalemateRepeats: 2,
|
|
191
|
-
cadence: { everyNTools: null },
|
|
192
|
-
});
|
|
104
|
+
{
|
|
105
|
+
const stallingWatchdog = { ...watchdogConfig, agentEndTimeoutMs: 5, lsp: { enabled: false, timeoutMs: 100, maxFiles: 1, maxDiagnostics: 1 } } as ChildWatchdogConfig;
|
|
193
106
|
const handlers: Array<(event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown> = [];
|
|
194
|
-
registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown) { if (event === "tool_call") handlers.push(handler); } } as never, async () => new Promise(() => undefined));
|
|
107
|
+
registerPermissionGate({ on(event: string, handler: (event: { toolName?: string; input?: unknown }, ctx: { signal?: AbortSignal }) => unknown) { if (event === "tool_call") handlers.push(handler); } } as never, { rules: { write: "ask" } }, stallingWatchdog, async () => new Promise(() => undefined));
|
|
195
108
|
|
|
196
109
|
const result = await Promise.race([
|
|
197
110
|
handlers[0]!({ toolName: "write", input: { path: "out.txt" } }, { signal: undefined }),
|
|
@@ -203,18 +116,11 @@ describe("subagent prompt runtime", () => {
|
|
|
203
116
|
block: true,
|
|
204
117
|
reason: "Blocked by pi-subagents permission rule: Watchdog permission arbiter failed closed: Watchdog permission decision timed out after 5ms.",
|
|
205
118
|
});
|
|
206
|
-
} finally {
|
|
207
|
-
if (envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY === undefined) delete process.env[PERMISSION_POLICY_ENV];
|
|
208
|
-
else process.env[PERMISSION_POLICY_ENV] = envSnapshot.SELESAI_SUBAGENT_PERMISSION_POLICY;
|
|
209
|
-
if (envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG === undefined) delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
|
|
210
|
-
else process.env[CHILD_WATCHDOG_CONFIG_ENV] = envSnapshot.SELESAI_SUBAGENT_WATCHDOG_CHILD_CONFIG;
|
|
211
119
|
}
|
|
212
120
|
});
|
|
213
121
|
it("collects runtime extension acknowledgements until terminal serialization", () => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const outputPath = path.join(dir, "acks.json");
|
|
217
|
-
process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = outputPath;
|
|
122
|
+
{
|
|
123
|
+
const acknowledged: string[][] = [];
|
|
218
124
|
const runtimeHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
|
|
219
125
|
const extensionHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
|
|
220
126
|
const pushHandler = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, handler: (payload?: unknown) => unknown): void => {
|
|
@@ -227,7 +133,7 @@ describe("subagent prompt runtime", () => {
|
|
|
227
133
|
registerSubagentPromptRuntime({
|
|
228
134
|
events: { on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(extensionHandlers, event, handler); } },
|
|
229
135
|
on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(runtimeHandlers, event, handler); },
|
|
230
|
-
} as never);
|
|
136
|
+
} as never, childConfig({ runtimeAcknowledgements: (ids) => acknowledged.push(ids) }));
|
|
231
137
|
|
|
232
138
|
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
|
|
233
139
|
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
|
|
@@ -235,21 +141,13 @@ describe("subagent prompt runtime", () => {
|
|
|
235
141
|
runtimeHandlers.get("agent_end")?.[0]?.({});
|
|
236
142
|
emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "late" });
|
|
237
143
|
|
|
238
|
-
assert.deepEqual(
|
|
239
|
-
version: 1,
|
|
240
|
-
source: "child-runtime",
|
|
241
|
-
ids: ["ext.one"],
|
|
242
|
-
omitted: 0,
|
|
243
|
-
});
|
|
244
|
-
} finally {
|
|
245
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
144
|
+
assert.deepEqual(acknowledged, [["ext.one", "ext.one"]]);
|
|
246
145
|
}
|
|
247
146
|
});
|
|
248
147
|
|
|
249
148
|
it("nudges after the tool budget soft limit and blocks configured tools after hard", () => {
|
|
250
149
|
const handlers = new Map<string, (payload: { toolName?: string }) => unknown>();
|
|
251
150
|
const sent: string[] = [];
|
|
252
|
-
process.env[TOOL_BUDGET_ENV] = JSON.stringify({ soft: 2, hard: 2, block: ["read"] });
|
|
253
151
|
|
|
254
152
|
registerSubagentPromptRuntime({
|
|
255
153
|
on(event: string, handler: (payload: { toolName?: string }) => unknown) {
|
|
@@ -258,7 +156,7 @@ describe("subagent prompt runtime", () => {
|
|
|
258
156
|
sendUserMessage(content: string) {
|
|
259
157
|
sent.push(content);
|
|
260
158
|
},
|
|
261
|
-
} as { on(event: string, handler: (payload: { toolName?: string }) => unknown): void; sendUserMessage(content: string): void });
|
|
159
|
+
} as { on(event: string, handler: (payload: { toolName?: string }) => unknown): void; sendUserMessage(content: string): void }, childConfig({ toolBudget: { soft: 2, hard: 2, block: ["read"] } }));
|
|
262
160
|
|
|
263
161
|
const toolCall = handlers.get("tool_call");
|
|
264
162
|
assert.ok(toolCall, "tool_call handler should be registered");
|
|
@@ -273,436 +171,15 @@ describe("subagent prompt runtime", () => {
|
|
|
273
171
|
assert.equal(toolCall({ toolName: "write" }), undefined);
|
|
274
172
|
});
|
|
275
173
|
|
|
276
|
-
it("registers
|
|
277
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-watch-runtime-"));
|
|
278
|
-
try {
|
|
279
|
-
const inbox = path.join(dir, "steer");
|
|
280
|
-
const nativeInbox = path.join(dir, "native-steer");
|
|
281
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
282
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
283
|
-
let watchedDir: fs.PathLike | undefined;
|
|
284
|
-
const intervalDelays: number[] = [];
|
|
285
|
-
const fakeWatcher = { on() { return fakeWatcher; }, close() {} } as fs.FSWatcher;
|
|
286
|
-
|
|
287
|
-
registerSteeringInbox({
|
|
288
|
-
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
289
|
-
handlers.set(event, handler);
|
|
290
|
-
},
|
|
291
|
-
sendUserMessage() {},
|
|
292
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(): void }, {
|
|
293
|
-
platform: "linux",
|
|
294
|
-
nativeRealpath(target) {
|
|
295
|
-
assert.equal(target, inbox);
|
|
296
|
-
return nativeInbox;
|
|
297
|
-
},
|
|
298
|
-
watch: ((target: fs.PathLike) => {
|
|
299
|
-
watchedDir = target;
|
|
300
|
-
return fakeWatcher;
|
|
301
|
-
}) as typeof fs.watch,
|
|
302
|
-
timers: {
|
|
303
|
-
setInterval: ((_handler: Parameters<typeof setInterval>[0], delay?: number) => {
|
|
304
|
-
intervalDelays.push(delay ?? 0);
|
|
305
|
-
return { unref() {} };
|
|
306
|
-
}) as typeof setInterval,
|
|
307
|
-
clearInterval: (() => {}) as typeof clearInterval,
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
handlers.get("session_start")?.({});
|
|
312
|
-
assert.equal(watchedDir, nativeInbox);
|
|
313
|
-
assert.deepEqual(intervalDelays, [5000]);
|
|
314
|
-
handlers.get("session_shutdown")?.({});
|
|
315
|
-
} finally {
|
|
316
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("uses polling without native steering watchers on Darwin", () => {
|
|
321
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-darwin-runtime-"));
|
|
322
|
-
try {
|
|
323
|
-
const inbox = path.join(dir, "steer");
|
|
324
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
325
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
326
|
-
const intervalDelays: number[] = [];
|
|
327
|
-
let watchCalls = 0;
|
|
328
|
-
|
|
329
|
-
registerSteeringInbox({
|
|
330
|
-
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
331
|
-
handlers.set(event, handler);
|
|
332
|
-
},
|
|
333
|
-
sendUserMessage() {},
|
|
334
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(): void }, {
|
|
335
|
-
platform: "darwin",
|
|
336
|
-
watch: (() => { watchCalls += 1; throw new Error("Darwin must not use fs.watch."); }) as typeof fs.watch,
|
|
337
|
-
timers: {
|
|
338
|
-
setInterval: ((_handler: Parameters<typeof setInterval>[0], delay?: number) => {
|
|
339
|
-
intervalDelays.push(delay ?? 0);
|
|
340
|
-
return { unref() {} };
|
|
341
|
-
}) as typeof setInterval,
|
|
342
|
-
clearInterval: (() => {}) as typeof clearInterval,
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
handlers.get("session_start")?.({});
|
|
347
|
-
assert.equal(watchCalls, 0);
|
|
348
|
-
assert.deepEqual(intervalDelays, [250]);
|
|
349
|
-
handlers.get("session_shutdown")?.({});
|
|
350
|
-
} finally {
|
|
351
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it("delivers steering inbox requests as mid-run user messages", () => {
|
|
356
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-runtime-"));
|
|
357
|
-
try {
|
|
358
|
-
const inbox = path.join(dir, "steer");
|
|
359
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
360
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
361
|
-
const sent: Array<{ content: string; options: { deliverAs: string } }> = [];
|
|
362
|
-
|
|
363
|
-
registerSubagentPromptRuntime({
|
|
364
|
-
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
365
|
-
handlers.set(event, handler);
|
|
366
|
-
},
|
|
367
|
-
sendUserMessage(content: string, options: { deliverAs: string }) {
|
|
368
|
-
sent.push({ content, options });
|
|
369
|
-
},
|
|
370
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; sendUserMessage(content: string, options: { deliverAs: string }): void });
|
|
371
|
-
|
|
372
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "steer-1", ts: 1, message: "Focus on tests." });
|
|
373
|
-
handlers.get("message_start")?.({});
|
|
374
|
-
handlers.get("session_shutdown")?.({});
|
|
375
|
-
|
|
376
|
-
assert.equal(sent.length, 1);
|
|
377
|
-
assert.equal(sent[0]?.options.deliverAs, "steer");
|
|
378
|
-
assert.match(sent[0]?.content ?? "", /Mid-run steering/);
|
|
379
|
-
assert.match(sent[0]?.content ?? "", /Focus on tests\./);
|
|
380
|
-
assert.deepEqual(fs.readdirSync(inbox).filter((entry) => entry.endsWith(".json")), []);
|
|
381
|
-
} finally {
|
|
382
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it("queues follow-ups and acknowledges delivery at the next turn boundary", () => {
|
|
387
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-follow-up-runtime-"));
|
|
388
|
-
try {
|
|
389
|
-
const inbox = path.join(dir, "inbox");
|
|
390
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
391
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
|
|
392
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
393
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
394
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
395
|
-
const sent: Array<{ content: string; deliverAs?: string }> = [];
|
|
396
|
-
registerSteeringInbox({
|
|
397
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
398
|
-
sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
|
|
399
|
-
} as never);
|
|
400
|
-
handlers.get("session_start")?.({});
|
|
401
|
-
handlers.get("agent_start")?.({});
|
|
402
|
-
handlers.get("turn_start")?.({});
|
|
403
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "follow", ts: 1, message: "Check docs.", mode: "follow_up" });
|
|
404
|
-
handlers.get("message_start")?.({});
|
|
405
|
-
assert.equal(sent[0]?.deliverAs, "followUp");
|
|
406
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
|
|
407
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
408
|
-
handlers.get("turn_end")?.({});
|
|
409
|
-
handlers.get("turn_start")?.({});
|
|
410
|
-
const delivered = consumeSteerAcks(dir)[0];
|
|
411
|
-
assert.equal(delivered?.state, "delivered");
|
|
412
|
-
assert.equal(delivered?.deliveryStatus, "delivered");
|
|
413
|
-
|
|
414
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "auto-mid", ts: 2, message: "Mid-turn auto.", mode: "auto" });
|
|
415
|
-
handlers.get("message_start")?.({});
|
|
416
|
-
assert.equal(sent[1]?.deliverAs, "followUp");
|
|
417
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[1]?.content });
|
|
418
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
419
|
-
handlers.get("turn_end")?.({});
|
|
420
|
-
handlers.get("turn_start")?.({});
|
|
421
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "delivered");
|
|
422
|
-
|
|
423
|
-
handlers.get("turn_end")?.({});
|
|
424
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "auto-idle", ts: 3, message: "Between-turn auto.", mode: "auto" });
|
|
425
|
-
handlers.get("message_start")?.({});
|
|
426
|
-
assert.equal(sent[2]?.deliverAs, "steer");
|
|
427
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[2]?.content });
|
|
428
|
-
assert.equal(consumeSteerAcks(dir)[0]?.deliveryStatus, "delivered");
|
|
429
|
-
|
|
430
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "undelivered", ts: 4, message: "Never reached.", mode: "follow_up" });
|
|
431
|
-
handlers.get("message_start")?.({});
|
|
432
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[3]?.content });
|
|
433
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
434
|
-
handlers.get("session_shutdown")?.({});
|
|
435
|
-
const failed = consumeSteerAcks(dir)[0];
|
|
436
|
-
assert.equal(failed?.state, "failed");
|
|
437
|
-
assert.match(failed?.message ?? "", /ended before queued follow-up/);
|
|
438
|
-
} finally {
|
|
439
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it("queues auto steering after agent_end until agent_settled", () => {
|
|
444
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-settled-steering-runtime-"));
|
|
445
|
-
try {
|
|
446
|
-
const inbox = path.join(dir, "inbox");
|
|
447
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
448
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
449
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
450
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
451
|
-
const sent: Array<{ content: string; deliverAs?: string }> = [];
|
|
452
|
-
registerSteeringInbox({
|
|
453
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
454
|
-
sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
|
|
455
|
-
} as never);
|
|
456
|
-
handlers.get("session_start")?.({});
|
|
457
|
-
handlers.get("agent_start")?.({});
|
|
458
|
-
handlers.get("turn_start")?.({});
|
|
459
|
-
handlers.get("turn_end")?.({});
|
|
460
|
-
handlers.get("agent_end")?.({ willRetry: false });
|
|
461
|
-
|
|
462
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "settling-auto", ts: 1, message: "Wait for settled.", mode: "auto" });
|
|
463
|
-
handlers.get("message_start")?.({});
|
|
464
|
-
assert.equal(sent[0]?.deliverAs, "followUp");
|
|
465
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
|
|
466
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
467
|
-
|
|
468
|
-
handlers.get("agent_settled")?.({});
|
|
469
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "settled-auto", ts: 2, message: "Now idle.", mode: "auto" });
|
|
470
|
-
handlers.get("message_start")?.({});
|
|
471
|
-
assert.equal(sent[1]?.deliverAs, undefined);
|
|
472
|
-
handlers.get("input")?.({ source: "extension", text: sent[1]?.content });
|
|
473
|
-
assert.equal(consumeSteerAcks(dir)[0]?.deliveryStatus, "delivered");
|
|
474
|
-
handlers.get("session_shutdown")?.({});
|
|
475
|
-
} finally {
|
|
476
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
it("keeps auto steering queued while agent_end will retry", () => {
|
|
481
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-retry-steering-runtime-"));
|
|
482
|
-
try {
|
|
483
|
-
const inbox = path.join(dir, "inbox");
|
|
484
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
485
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
486
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
487
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
488
|
-
const sent: Array<{ content: string; deliverAs?: string }> = [];
|
|
489
|
-
registerSteeringInbox({
|
|
490
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
491
|
-
sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
|
|
492
|
-
} as never);
|
|
493
|
-
handlers.get("session_start")?.({});
|
|
494
|
-
handlers.get("agent_start")?.({});
|
|
495
|
-
handlers.get("turn_start")?.({});
|
|
496
|
-
handlers.get("turn_end")?.({});
|
|
497
|
-
handlers.get("agent_end")?.({ willRetry: true });
|
|
498
|
-
|
|
499
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "retry-auto", ts: 1, message: "Keep this guidance.", mode: "auto" });
|
|
500
|
-
handlers.get("message_start")?.({});
|
|
501
|
-
assert.equal(sent[0]?.deliverAs, "followUp");
|
|
502
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "followUp", text: sent[0]?.content });
|
|
503
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
504
|
-
|
|
505
|
-
handlers.get("turn_start")?.({});
|
|
506
|
-
const delivered = consumeSteerAcks(dir)[0];
|
|
507
|
-
assert.equal(delivered?.requestId, "retry-auto");
|
|
508
|
-
assert.equal(delivered?.deliveryStatus, "delivered");
|
|
509
|
-
handlers.get("session_shutdown")?.({});
|
|
510
|
-
} finally {
|
|
511
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it("falls back to idle delivery for runtimes without agent_settled", async () => {
|
|
516
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-legacy-steering-runtime-"));
|
|
517
|
-
try {
|
|
518
|
-
const inbox = path.join(dir, "inbox");
|
|
519
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
520
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
521
|
-
const sent: Array<{ content: string; deliverAs?: string }> = [];
|
|
522
|
-
registerSteeringInbox({
|
|
523
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
524
|
-
sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
|
|
525
|
-
} as never, { legacySettleFallbackMs: 5 });
|
|
526
|
-
handlers.get("session_start")?.({});
|
|
527
|
-
handlers.get("agent_start")?.({});
|
|
528
|
-
handlers.get("turn_start")?.({});
|
|
529
|
-
handlers.get("turn_end")?.({});
|
|
530
|
-
handlers.get("agent_end")?.({ willRetry: false });
|
|
531
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
532
|
-
|
|
533
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "legacy-auto", ts: 1, message: "Legacy idle.", mode: "auto" });
|
|
534
|
-
handlers.get("message_start")?.({});
|
|
535
|
-
assert.equal(sent[0]?.deliverAs, undefined);
|
|
536
|
-
handlers.get("session_shutdown")?.({});
|
|
537
|
-
} finally {
|
|
538
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it("does not acknowledge sendUserMessage until the correlated Pi input event arrives", () => {
|
|
543
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-ack-runtime-"));
|
|
544
|
-
try {
|
|
545
|
-
const inbox = path.join(dir, "inbox");
|
|
546
|
-
const capability = path.join(dir, "capability.json");
|
|
547
|
-
const ackDir = path.join(dir, "control", "steer-acks", "0");
|
|
548
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
549
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = capability;
|
|
550
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = ackDir;
|
|
551
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
552
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
553
|
-
const sent: string[] = [];
|
|
554
|
-
registerSteeringInbox({
|
|
555
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
556
|
-
sendUserMessage(content: string) { sent.push(content); },
|
|
557
|
-
} as never);
|
|
558
|
-
handlers.get("session_start")?.({});
|
|
559
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "first", ts: 1, message: "Focus on tests." });
|
|
560
|
-
handlers.get("message_start")?.({});
|
|
561
|
-
assert.equal(sent.length, 1);
|
|
562
|
-
assert.deepEqual(fs.existsSync(ackDir) ? fs.readdirSync(ackDir) : [], []);
|
|
563
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[0] });
|
|
564
|
-
const acks = consumeSteerAcks(dir);
|
|
565
|
-
assert.equal(acks.length, 1);
|
|
566
|
-
assert.equal(acks[0]?.requestId, "first");
|
|
567
|
-
assert.equal(acks[0]?.state, "delivered");
|
|
568
|
-
assert.equal(acks[0]?.message, "Pi accepted the correlated steering input.");
|
|
569
|
-
handlers.get("session_shutdown")?.({});
|
|
570
|
-
} finally {
|
|
571
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it("retries pending correlation once as a follow-up after compaction", () => {
|
|
576
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-compaction-runtime-"));
|
|
577
|
-
try {
|
|
578
|
-
const inbox = path.join(dir, "inbox");
|
|
579
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
580
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
|
|
581
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
582
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
583
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
584
|
-
const sent: Array<{ content: string; deliverAs?: string }> = [];
|
|
585
|
-
registerSteeringInbox({
|
|
586
|
-
on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); },
|
|
587
|
-
sendUserMessage(content: string, options?: { deliverAs?: string }) { sent.push({ content, deliverAs: options?.deliverAs }); },
|
|
588
|
-
} as never);
|
|
589
|
-
handlers.get("session_start")?.({});
|
|
590
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "compact", ts: 1, message: "Keep this guidance." });
|
|
591
|
-
handlers.get("message_start")?.({});
|
|
592
|
-
assert.equal(sent.length, 1);
|
|
593
|
-
assert.deepEqual(consumeSteerAcks(dir), []);
|
|
594
|
-
|
|
595
|
-
handlers.get("session_compact")?.({ reason: "manual" });
|
|
596
|
-
assert.equal(sent.length, 2);
|
|
597
|
-
assert.equal(sent[1]?.deliverAs, "followUp");
|
|
598
|
-
handlers.get("input")?.({ source: "extension", text: sent[1]?.content });
|
|
599
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "queued");
|
|
600
|
-
handlers.get("turn_start")?.({});
|
|
601
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "delivered");
|
|
602
|
-
handlers.get("session_compact")?.({ reason: "manual" });
|
|
603
|
-
assert.equal(sent.length, 2);
|
|
604
|
-
handlers.get("session_shutdown")?.({});
|
|
605
|
-
} finally {
|
|
606
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
it("fails pending correlation when the session shuts down", () => {
|
|
611
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-pending-shutdown-runtime-"));
|
|
612
|
-
try {
|
|
613
|
-
const inbox = path.join(dir, "inbox");
|
|
614
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
615
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
|
|
616
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
617
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
618
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
619
|
-
registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, sendUserMessage() {} } as never);
|
|
620
|
-
handlers.get("session_start")?.({});
|
|
621
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "pending", ts: 1, message: "Unconfirmed guidance." });
|
|
622
|
-
handlers.get("message_start")?.({});
|
|
623
|
-
handlers.get("session_shutdown")?.({});
|
|
624
|
-
const ack = consumeSteerAcks(dir)[0];
|
|
625
|
-
assert.equal(ack?.requestId, "pending");
|
|
626
|
-
assert.equal(ack?.state, "failed");
|
|
627
|
-
assert.match(ack?.message ?? "", /before Pi confirmed steering input delivery/);
|
|
628
|
-
} finally {
|
|
629
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it("correlates duplicate guidance FIFO without a visible marker", () => {
|
|
634
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-fifo-runtime-"));
|
|
635
|
-
try {
|
|
636
|
-
const inbox = path.join(dir, "inbox");
|
|
637
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
638
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
|
|
639
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
640
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
641
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
642
|
-
const sent: string[] = [];
|
|
643
|
-
registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); }, sendUserMessage(content: string) { sent.push(content); } } as never);
|
|
644
|
-
handlers.get("session_start")?.({});
|
|
645
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "one", ts: 1, message: "same guidance" });
|
|
646
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "two", ts: 2, message: "same guidance" });
|
|
647
|
-
handlers.get("message_start")?.({});
|
|
648
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[0] });
|
|
649
|
-
handlers.get("input")?.({ source: "extension", streamingBehavior: "steer", text: sent[1] });
|
|
650
|
-
assert.deepEqual(consumeSteerAcks(dir).map((ack) => ack.requestId), ["one", "two"]);
|
|
651
|
-
handlers.get("session_shutdown")?.({});
|
|
652
|
-
} finally {
|
|
653
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
it("publishes an unsupported capability and failed acknowledgments without sendUserMessage", () => {
|
|
658
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-steering-unsupported-runtime-"));
|
|
659
|
-
try {
|
|
660
|
-
const inbox = path.join(dir, "inbox");
|
|
661
|
-
process.env[SUBAGENT_STEER_INBOX_ENV] = inbox;
|
|
662
|
-
process.env[SUBAGENT_STEER_CAPABILITY_ENV] = path.join(dir, "capability.json");
|
|
663
|
-
process.env[SUBAGENT_STEER_ACK_DIR_ENV] = path.join(dir, "control", "steer-acks", "0");
|
|
664
|
-
process.env[SUBAGENT_CHILD_INDEX_ENV] = "0";
|
|
665
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
666
|
-
registerSteeringInbox({ on(event: string, handler: (payload?: unknown) => unknown) { handlers.set(event, handler); } } as never);
|
|
667
|
-
handlers.get("session_start")?.({});
|
|
668
|
-
assert.equal(JSON.parse(fs.readFileSync(path.join(dir, "capability.json"), "utf-8")).supported, false);
|
|
669
|
-
writeSteerRequestToDir(inbox, { type: "steer", id: "unsupported", ts: 1, message: "guidance" });
|
|
670
|
-
handlers.get("message_start")?.({});
|
|
671
|
-
assert.equal(consumeSteerAcks(dir)[0]?.state, "failed");
|
|
672
|
-
handlers.get("session_shutdown")?.({});
|
|
673
|
-
} finally {
|
|
674
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
it("registers child watchdog lifecycle handlers only when enabled by env", () => {
|
|
679
|
-
delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
|
|
680
|
-
// Clear the ack capture env explicitly: when this test suite itself runs inside a
|
|
681
|
-
// pi-subagents child, the runner sets it and an extra agent_end handler registers.
|
|
682
|
-
delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
|
|
683
|
-
delete process.env[SUBAGENT_STEER_INBOX_ENV];
|
|
684
|
-
delete process.env[SUBAGENT_STEER_CAPABILITY_ENV];
|
|
685
|
-
delete process.env[SUBAGENT_STEER_ACK_DIR_ENV];
|
|
174
|
+
it("registers child watchdog lifecycle handlers only when the config enables them", () => {
|
|
686
175
|
const handlersWithout = new Map<string, unknown[]>();
|
|
687
176
|
registerSubagentPromptRuntime({
|
|
688
177
|
on(event: string, handler: unknown) {
|
|
689
178
|
handlersWithout.set(event, [...(handlersWithout.get(event) ?? []), handler]);
|
|
690
179
|
},
|
|
691
|
-
} as { on(event: string, handler: unknown): void });
|
|
180
|
+
} as { on(event: string, handler: unknown): void }, childConfig());
|
|
692
181
|
assert.equal(handlersWithout.get("agent_end")?.length ?? 0, 1, "headless auto-drain is always registered");
|
|
693
182
|
|
|
694
|
-
process.env[CHILD_WATCHDOG_CONFIG_ENV] = JSON.stringify({
|
|
695
|
-
enabled: true,
|
|
696
|
-
runId: "run-1",
|
|
697
|
-
agent: "worker",
|
|
698
|
-
childIndex: 0,
|
|
699
|
-
watchdogTailTimeoutMs: 1000,
|
|
700
|
-
agentEndTimeoutMs: 500,
|
|
701
|
-
maxWarnings: null,
|
|
702
|
-
lsp: { enabled: false, timeoutMs: 3000, maxFiles: 20, maxDiagnostics: 50 },
|
|
703
|
-
stalemateRepeats: 2,
|
|
704
|
-
cadence: { everyNTools: null },
|
|
705
|
-
});
|
|
706
183
|
const handlersWith = new Map<string, unknown[]>();
|
|
707
184
|
registerSubagentPromptRuntime({
|
|
708
185
|
on(event: string, handler: unknown) {
|
|
@@ -712,21 +189,16 @@ describe("subagent prompt runtime", () => {
|
|
|
712
189
|
return "off";
|
|
713
190
|
},
|
|
714
191
|
sendMessage() {},
|
|
715
|
-
} as { on(event: string, handler: unknown): void; getThinkingLevel(): string; sendMessage(): void });
|
|
192
|
+
} as { on(event: string, handler: unknown): void; getThinkingLevel(): string; sendMessage(): void }, childConfig({ childWatchdog: watchdogConfig, watchdogStatus: () => {} }));
|
|
716
193
|
|
|
717
194
|
assert.ok((handlersWith.get("before_agent_start")?.length ?? 0) >= 2);
|
|
718
195
|
assert.ok((handlersWith.get("turn_end")?.length ?? 0) >= 1);
|
|
719
196
|
assert.ok((handlersWith.get("agent_end")?.length ?? 0) >= 2, "watchdog and auto-drain both observe agent_end");
|
|
720
197
|
});
|
|
721
198
|
|
|
722
|
-
it("registered structured_output tool accepts valid schema output and
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const schemaPath = path.join(dir, "schema.json");
|
|
726
|
-
const outputPath = path.join(dir, "output.json");
|
|
727
|
-
fs.writeFileSync(schemaPath, JSON.stringify({ type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }), "utf-8");
|
|
728
|
-
process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
|
|
729
|
-
process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
|
|
199
|
+
it("registered structured_output tool accepts valid schema output and captures it", async () => {
|
|
200
|
+
{
|
|
201
|
+
const captured: unknown[] = [];
|
|
730
202
|
let execute: ((_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }>) | undefined;
|
|
731
203
|
let parameters: unknown;
|
|
732
204
|
|
|
@@ -738,7 +210,9 @@ describe("subagent prompt runtime", () => {
|
|
|
738
210
|
}
|
|
739
211
|
},
|
|
740
212
|
on() {},
|
|
741
|
-
} as { registerTool(tool: { name: string; parameters: unknown; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }): void; on(): void }
|
|
213
|
+
} as { registerTool(tool: { name: string; parameters: unknown; execute: (_id: string, params: { value: unknown }) => Promise<{ terminate?: boolean }> }): void; on(): void }, childConfig({
|
|
214
|
+
structuredOutput: { schema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, capture: (value) => captured.push(value) },
|
|
215
|
+
}));
|
|
742
216
|
|
|
743
217
|
assert.ok(execute, "structured_output tool should be registered");
|
|
744
218
|
assert.deepEqual(parameters, {
|
|
@@ -749,23 +223,13 @@ describe("subagent prompt runtime", () => {
|
|
|
749
223
|
});
|
|
750
224
|
const result = await execute("tool-1", { value: { ok: true } });
|
|
751
225
|
assert.equal(result.terminate, true);
|
|
752
|
-
assert.deepEqual(
|
|
753
|
-
} finally {
|
|
754
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
226
|
+
assert.deepEqual(captured, [{ ok: true }]);
|
|
755
227
|
}
|
|
756
228
|
});
|
|
757
229
|
|
|
758
230
|
it("requires and validates acceptanceReport when structured capture is required", async () => {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const schemaPath = path.join(dir, "schema.json");
|
|
762
|
-
const outputPath = path.join(dir, "output.json");
|
|
763
|
-
const acceptancePath = path.join(dir, "acceptance.json");
|
|
764
|
-
fs.writeFileSync(schemaPath, JSON.stringify({ type: "object" }), "utf-8");
|
|
765
|
-
process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
|
|
766
|
-
process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
|
|
767
|
-
process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = acceptancePath;
|
|
768
|
-
process.env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV] = "1";
|
|
231
|
+
{
|
|
232
|
+
const captured: Array<{ value: unknown; acceptanceReport: unknown }> = [];
|
|
769
233
|
let execute: ((_id: string, params: { value: unknown; acceptanceReport?: unknown }) => Promise<unknown>) | undefined;
|
|
770
234
|
let parameters: { required?: string[] } | undefined;
|
|
771
235
|
|
|
@@ -777,29 +241,21 @@ describe("subagent prompt runtime", () => {
|
|
|
777
241
|
}
|
|
778
242
|
},
|
|
779
243
|
on() {},
|
|
780
|
-
} as { registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }): void; on(): void }
|
|
244
|
+
} as { registerTool(tool: { name: string; parameters: unknown; execute: typeof execute }): void; on(): void }, childConfig({
|
|
245
|
+
structuredOutput: { schema: { type: "object" }, acceptanceReport: "required", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) },
|
|
246
|
+
}));
|
|
781
247
|
|
|
782
248
|
assert.deepEqual(parameters?.required, ["value", "acceptanceReport"]);
|
|
783
249
|
await assert.rejects(execute!("missing", { value: {} }), /Missing acceptanceReport/);
|
|
784
250
|
await assert.rejects(execute!("empty", { value: {}, acceptanceReport: {} }), /expected at least one acceptance report field/);
|
|
785
251
|
await execute!("valid", { value: {}, acceptanceReport: { manualNotes: "validated evidence" } });
|
|
786
|
-
assert.deepEqual(
|
|
787
|
-
} finally {
|
|
788
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
252
|
+
assert.deepEqual(captured, [{ value: {}, acceptanceReport: { manualNotes: "validated evidence" } }]);
|
|
789
253
|
}
|
|
790
254
|
});
|
|
791
255
|
|
|
792
|
-
it("
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const schemaPath = path.join(dir, "schema.json");
|
|
796
|
-
const outputPath = path.join(dir, "output.json");
|
|
797
|
-
const acceptancePath = path.join(dir, "acceptance.json");
|
|
798
|
-
fs.writeFileSync(schemaPath, JSON.stringify({ type: "object" }), "utf-8");
|
|
799
|
-
fs.writeFileSync(acceptancePath, JSON.stringify({ manualNotes: "stale" }), "utf-8");
|
|
800
|
-
process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
|
|
801
|
-
process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
|
|
802
|
-
process.env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV] = acceptancePath;
|
|
256
|
+
it("captures an omitted optional acceptance report as undefined", async () => {
|
|
257
|
+
{
|
|
258
|
+
const captured: Array<{ value: unknown; acceptanceReport: unknown }> = [];
|
|
803
259
|
let execute: ((_id: string, params: { value: unknown }) => Promise<unknown>) | undefined;
|
|
804
260
|
|
|
805
261
|
registerSubagentPromptRuntime({
|
|
@@ -807,13 +263,12 @@ describe("subagent prompt runtime", () => {
|
|
|
807
263
|
if (tool.name === "structured_output") execute = tool.execute;
|
|
808
264
|
},
|
|
809
265
|
on() {},
|
|
810
|
-
} as { registerTool(tool: { name: string; execute: typeof execute }): void; on(): void }
|
|
266
|
+
} as { registerTool(tool: { name: string; execute: typeof execute }): void; on(): void }, childConfig({
|
|
267
|
+
structuredOutput: { schema: { type: "object" }, acceptanceReport: "optional", capture: (value, acceptanceReport) => captured.push({ value, acceptanceReport }) },
|
|
268
|
+
}));
|
|
811
269
|
|
|
812
270
|
await execute!("without-report", { value: {} });
|
|
813
|
-
assert.
|
|
814
|
-
assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), {});
|
|
815
|
-
} finally {
|
|
816
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
271
|
+
assert.deepEqual(captured, [{ value: {}, acceptanceReport: undefined }]);
|
|
817
272
|
}
|
|
818
273
|
});
|
|
819
274
|
|
|
@@ -836,11 +291,8 @@ describe("subagent prompt runtime", () => {
|
|
|
836
291
|
});
|
|
837
292
|
|
|
838
293
|
it("scopes local structured_output schema refs under the value parameter", () => {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const schemaPath = path.join(dir, "schema.json");
|
|
842
|
-
const outputPath = path.join(dir, "output.json");
|
|
843
|
-
fs.writeFileSync(schemaPath, JSON.stringify({
|
|
294
|
+
{
|
|
295
|
+
const schema = {
|
|
844
296
|
$defs: { item: { type: "string" } },
|
|
845
297
|
type: "object",
|
|
846
298
|
properties: {
|
|
@@ -850,9 +302,7 @@ describe("subagent prompt runtime", () => {
|
|
|
850
302
|
properties: { label: { $ref: "#/$defs/item" } },
|
|
851
303
|
},
|
|
852
304
|
},
|
|
853
|
-
}
|
|
854
|
-
process.env[STRUCTURED_OUTPUT_SCHEMA_ENV] = schemaPath;
|
|
855
|
-
process.env[STRUCTURED_OUTPUT_CAPTURE_ENV] = outputPath;
|
|
305
|
+
};
|
|
856
306
|
let parameters = {} as { properties?: { value?: { properties?: { name?: { $ref?: string }; nested?: { properties?: { label?: { $ref?: string } } } } } } };
|
|
857
307
|
|
|
858
308
|
registerSubagentPromptRuntime({
|
|
@@ -860,12 +310,10 @@ describe("subagent prompt runtime", () => {
|
|
|
860
310
|
if (tool.name === "structured_output") parameters = tool.parameters as typeof parameters;
|
|
861
311
|
},
|
|
862
312
|
on() {},
|
|
863
|
-
} as { registerTool(tool: { name: string; parameters: unknown }): void; on(): void });
|
|
313
|
+
} as { registerTool(tool: { name: string; parameters: unknown }): void; on(): void }, childConfig({ structuredOutput: { schema, capture: () => {} } }));
|
|
864
314
|
|
|
865
315
|
assert.equal(parameters.properties?.value?.properties?.name?.$ref, "#/properties/value/$defs/item");
|
|
866
316
|
assert.equal(parameters.properties?.value?.properties?.nested?.properties?.label?.$ref, "#/properties/value/$defs/item");
|
|
867
|
-
} finally {
|
|
868
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
869
317
|
}
|
|
870
318
|
});
|
|
871
319
|
|
|
@@ -1187,13 +635,10 @@ describe("subagent prompt runtime", () => {
|
|
|
1187
635
|
const subagentResult = { role: "toolResult", toolName: "subagent", content: "OK" };
|
|
1188
636
|
const subagentCall = { role: "assistant", content: [{ type: "toolCall", name: "subagent", input: { agent: "delegate" } }] };
|
|
1189
637
|
const instruction = { role: "custom", customType: "subagent-orchestration-instructions", content: "Subagent orchestration is enabled." };
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
assert.deepEqual(stripParentOnlySubagentMessages([user, subagentCall, subagentResult, instruction]), [user, subagentCall, subagentResult]);
|
|
638
|
+
assert.deepEqual(stripParentOnlySubagentMessages([user, subagentCall, subagentResult, instruction], { preserveFanoutToolHistory: true }), [user, subagentCall, subagentResult]);
|
|
1193
639
|
});
|
|
1194
640
|
|
|
1195
641
|
it("defers native supervisor registration until runtime events and respects installed pi-intercom tools", async () => {
|
|
1196
|
-
setSupervisorEnv();
|
|
1197
642
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1198
643
|
const registered: string[] = [];
|
|
1199
644
|
|
|
@@ -1205,7 +650,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1205
650
|
registerTool(tool: { name: string }) {
|
|
1206
651
|
registered.push(tool.name);
|
|
1207
652
|
},
|
|
1208
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
|
|
653
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
|
|
1209
654
|
|
|
1210
655
|
assert.deepEqual(registered, ["bg_wait", "subagent_wait"]);
|
|
1211
656
|
handlers.get("session_start")?.({});
|
|
@@ -1214,15 +659,10 @@ describe("subagent prompt runtime", () => {
|
|
|
1214
659
|
});
|
|
1215
660
|
|
|
1216
661
|
it("does not satisfy strict allowlists with native generic intercom", () => {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
try {
|
|
1220
|
-
const diagnosticPath = path.join(dir, "tools.json");
|
|
662
|
+
{
|
|
663
|
+
const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
|
|
1221
664
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1222
665
|
const registered: string[] = [];
|
|
1223
|
-
process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"]);
|
|
1224
|
-
process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
|
|
1225
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "scout";
|
|
1226
666
|
|
|
1227
667
|
registerSubagentPromptRuntime({
|
|
1228
668
|
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
@@ -1232,30 +672,28 @@ describe("subagent prompt runtime", () => {
|
|
|
1232
672
|
registerTool(tool: { name: string }) {
|
|
1233
673
|
registered.push(tool.name);
|
|
1234
674
|
},
|
|
1235
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }
|
|
675
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig({
|
|
676
|
+
agent: "scout",
|
|
677
|
+
requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
|
|
678
|
+
toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
679
|
+
}));
|
|
1236
680
|
|
|
1237
681
|
handlers.get("session_start")?.({});
|
|
1238
682
|
assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
|
|
1239
683
|
assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: read, grep, find, ls, bash, edit, write, intercom/);
|
|
1240
|
-
assert.deepEqual(
|
|
684
|
+
assert.deepEqual(diagnostics, [{
|
|
1241
685
|
agent: "scout",
|
|
1242
686
|
required: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
|
|
1243
687
|
available: ["bg_wait", "subagent_wait", "contact_supervisor"],
|
|
1244
688
|
missing: ["read", "grep", "find", "ls", "bash", "edit", "write", "intercom"],
|
|
1245
|
-
});
|
|
1246
|
-
} finally {
|
|
1247
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
689
|
+
}]);
|
|
1248
690
|
}
|
|
1249
691
|
});
|
|
1250
692
|
|
|
1251
693
|
it("records missing core write tools from the actual child registry", () => {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
const diagnosticPath = path.join(dir, "tools.json");
|
|
694
|
+
{
|
|
695
|
+
const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
|
|
1255
696
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1256
|
-
process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "grep", "find", "ls", "bash", "edit", "write"]);
|
|
1257
|
-
process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
|
|
1258
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
|
|
1259
697
|
|
|
1260
698
|
registerSubagentPromptRuntime({
|
|
1261
699
|
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
@@ -1263,22 +701,23 @@ describe("subagent prompt runtime", () => {
|
|
|
1263
701
|
},
|
|
1264
702
|
getAllTools: () => ["read", "grep", "find", "ls", "contact_supervisor"].map((name) => ({ name })),
|
|
1265
703
|
registerTool() {},
|
|
1266
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }
|
|
704
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
|
|
705
|
+
agent: "worker",
|
|
706
|
+
requiredTools: ["read", "grep", "find", "ls", "bash", "edit", "write"],
|
|
707
|
+
toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
708
|
+
}));
|
|
1267
709
|
|
|
1268
710
|
assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: bash, edit, write/);
|
|
1269
|
-
assert.deepEqual(
|
|
711
|
+
assert.deepEqual(diagnostics, [{
|
|
1270
712
|
agent: "worker",
|
|
1271
713
|
required: ["read", "grep", "find", "ls", "bash", "edit", "write"],
|
|
1272
714
|
available: ["read", "grep", "find", "ls", "contact_supervisor"],
|
|
1273
715
|
missing: ["bash", "edit", "write"],
|
|
1274
|
-
});
|
|
1275
|
-
} finally {
|
|
1276
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
716
|
+
}]);
|
|
1277
717
|
}
|
|
1278
718
|
});
|
|
1279
719
|
|
|
1280
720
|
it("keeps installed pi-intercom while filling only a missing child contact_supervisor tool", async () => {
|
|
1281
|
-
setSupervisorEnv();
|
|
1282
721
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1283
722
|
const registered: string[] = [];
|
|
1284
723
|
|
|
@@ -1290,7 +729,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1290
729
|
registerTool(tool: { name: string }) {
|
|
1291
730
|
registered.push(tool.name);
|
|
1292
731
|
},
|
|
1293
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
|
|
732
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
|
|
1294
733
|
|
|
1295
734
|
handlers.get("session_start")?.({});
|
|
1296
735
|
await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT });
|
|
@@ -1299,13 +738,10 @@ describe("subagent prompt runtime", () => {
|
|
|
1299
738
|
});
|
|
1300
739
|
|
|
1301
740
|
it("registers only native supervisor tools at runtime when pi-intercom is absent", async () => {
|
|
1302
|
-
setSupervisorEnv();
|
|
1303
|
-
const previousRequiredTools = process.env[REQUIRED_CHILD_TOOLS_ENV];
|
|
1304
|
-
delete process.env[REQUIRED_CHILD_TOOLS_ENV];
|
|
1305
741
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1306
742
|
const registered: string[] = [];
|
|
1307
743
|
|
|
1308
|
-
|
|
744
|
+
{
|
|
1309
745
|
registerSubagentPromptRuntime({
|
|
1310
746
|
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
1311
747
|
handlers.set(event, handler);
|
|
@@ -1314,28 +750,21 @@ describe("subagent prompt runtime", () => {
|
|
|
1314
750
|
registerTool(tool: { name: string }) {
|
|
1315
751
|
registered.push(tool.name);
|
|
1316
752
|
},
|
|
1317
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void });
|
|
753
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(tool: { name: string }): void }, supervisorConfig());
|
|
1318
754
|
|
|
1319
755
|
handlers.get("session_start")?.({});
|
|
1320
756
|
assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
|
|
1321
757
|
|
|
1322
758
|
await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT });
|
|
1323
759
|
assert.deepEqual(registered, ["bg_wait", "subagent_wait", "contact_supervisor"]);
|
|
1324
|
-
} finally {
|
|
1325
|
-
if (previousRequiredTools === undefined) delete process.env[REQUIRED_CHILD_TOOLS_ENV];
|
|
1326
|
-
else process.env[REQUIRED_CHILD_TOOLS_ENV] = previousRequiredTools;
|
|
1327
760
|
}
|
|
1328
761
|
});
|
|
1329
762
|
|
|
1330
763
|
it("records requested tools missing from the child registry after startup hooks settle", async () => {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
const diagnosticPath = path.join(dir, "tools.json");
|
|
764
|
+
{
|
|
765
|
+
const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
|
|
1334
766
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1335
767
|
const available = ["read"];
|
|
1336
|
-
process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "fixture_search"]);
|
|
1337
|
-
process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
|
|
1338
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "extension-worker";
|
|
1339
768
|
|
|
1340
769
|
registerSubagentPromptRuntime({
|
|
1341
770
|
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
@@ -1343,67 +772,34 @@ describe("subagent prompt runtime", () => {
|
|
|
1343
772
|
},
|
|
1344
773
|
getAllTools: () => available.map((name) => ({ name })),
|
|
1345
774
|
registerTool() {},
|
|
1346
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }
|
|
775
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
|
|
776
|
+
agent: "extension-worker",
|
|
777
|
+
requiredTools: ["read", "fixture_search"],
|
|
778
|
+
toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
779
|
+
}));
|
|
1347
780
|
|
|
1348
781
|
const promptRewrite = await handlers.get("before_agent_start")?.({ systemPrompt: BASE_PROMPT }) as { systemPrompt?: string } | undefined;
|
|
1349
|
-
assert.
|
|
782
|
+
assert.deepEqual(diagnostics, []);
|
|
1350
783
|
assert.doesNotMatch(promptRewrite?.systemPrompt ?? "", /requested unavailable child tools/);
|
|
1351
784
|
|
|
1352
785
|
assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/);
|
|
1353
|
-
assert.deepEqual(
|
|
786
|
+
assert.deepEqual(diagnostics, [{
|
|
1354
787
|
agent: "extension-worker",
|
|
1355
788
|
required: ["read", "fixture_search"],
|
|
1356
789
|
available: ["read"],
|
|
1357
790
|
missing: ["fixture_search"],
|
|
1358
|
-
});
|
|
791
|
+
}]);
|
|
1359
792
|
|
|
1360
793
|
available.push("fixture_search");
|
|
1361
794
|
handlers.get("agent_start")?.({});
|
|
1362
|
-
assert.equal(
|
|
1363
|
-
} finally {
|
|
1364
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
1365
|
-
}
|
|
1366
|
-
});
|
|
1367
|
-
|
|
1368
|
-
it("ignores malformed inherited MCP metadata before strict availability diagnostics", () => {
|
|
1369
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-stale-mcp-tool-diagnostic-"));
|
|
1370
|
-
try {
|
|
1371
|
-
const diagnosticPath = path.join(dir, "tools.json");
|
|
1372
|
-
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1373
|
-
process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "fixture_search"]);
|
|
1374
|
-
process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = "not-json";
|
|
1375
|
-
process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
|
|
1376
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
|
|
1377
|
-
|
|
1378
|
-
registerSubagentPromptRuntime({
|
|
1379
|
-
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
1380
|
-
handlers.set(event, handler);
|
|
1381
|
-
},
|
|
1382
|
-
getAllTools: () => [{ name: "read" }],
|
|
1383
|
-
registerTool() {},
|
|
1384
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void });
|
|
1385
|
-
|
|
1386
|
-
assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: fixture_search/);
|
|
1387
|
-
assert.deepEqual(readChildToolDiagnostic(diagnosticPath), {
|
|
1388
|
-
agent: "worker",
|
|
1389
|
-
required: ["read", "fixture_search"],
|
|
1390
|
-
available: ["read"],
|
|
1391
|
-
missing: ["fixture_search"],
|
|
1392
|
-
});
|
|
1393
|
-
} finally {
|
|
1394
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
795
|
+
assert.equal(diagnostics.at(-1), undefined);
|
|
1395
796
|
}
|
|
1396
797
|
});
|
|
1397
798
|
|
|
1398
799
|
it("classifies missing resolved MCP direct tools without softening strict diagnostics", () => {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
const diagnosticPath = path.join(dir, "tools.json");
|
|
800
|
+
{
|
|
801
|
+
const diagnostics: Array<ChildToolDiagnostic | undefined> = [];
|
|
1402
802
|
const handlers = new Map<string, (payload?: unknown) => unknown>();
|
|
1403
|
-
process.env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(["read", "rust_symbols_workspace_symbols", "fixture_search"]);
|
|
1404
|
-
process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = JSON.stringify(["rust_symbols_workspace_symbols"]);
|
|
1405
|
-
process.env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = diagnosticPath;
|
|
1406
|
-
process.env[SUBAGENT_CHILD_AGENT_ENV] = "worker";
|
|
1407
803
|
|
|
1408
804
|
registerSubagentPromptRuntime({
|
|
1409
805
|
on(event: string, handler: (payload?: unknown) => unknown) {
|
|
@@ -1411,10 +807,15 @@ describe("subagent prompt runtime", () => {
|
|
|
1411
807
|
},
|
|
1412
808
|
getAllTools: () => [{ name: "read" }],
|
|
1413
809
|
registerTool() {},
|
|
1414
|
-
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }
|
|
810
|
+
} as { on(event: string, handler: (payload?: unknown) => unknown): void; getAllTools(): Array<{ name: string }>; registerTool(): void }, childConfig({
|
|
811
|
+
agent: "worker",
|
|
812
|
+
requiredTools: ["read", "rust_symbols_workspace_symbols", "fixture_search"],
|
|
813
|
+
mcpDirectTools: ["rust_symbols_workspace_symbols"],
|
|
814
|
+
toolDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
|
|
815
|
+
}));
|
|
1415
816
|
|
|
1416
817
|
assert.throws(() => handlers.get("agent_start")?.({}), /requested unavailable child tools: rust_symbols_workspace_symbols, fixture_search/);
|
|
1417
|
-
const diagnostic =
|
|
818
|
+
const diagnostic = diagnostics[0];
|
|
1418
819
|
assert.deepEqual(diagnostic, {
|
|
1419
820
|
agent: "worker",
|
|
1420
821
|
required: ["read", "rust_symbols_workspace_symbols", "fixture_search"],
|
|
@@ -1424,16 +825,12 @@ describe("subagent prompt runtime", () => {
|
|
|
1424
825
|
});
|
|
1425
826
|
assert.match(formatChildToolDiagnostic(diagnostic!), /host\/pi-mcp-adapter registration problem/);
|
|
1426
827
|
assert.match(formatChildToolDiagnostic(diagnostic!), /fixture_search/);
|
|
1427
|
-
assert.equal(fs.existsSync(diagnosticPath), true);
|
|
1428
|
-
} finally {
|
|
1429
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
1430
828
|
}
|
|
1431
829
|
});
|
|
1432
830
|
|
|
1433
|
-
it("sets the child intercom session name from
|
|
831
|
+
it("sets the child intercom session name from the config during agent startup", async () => {
|
|
1434
832
|
let sessionName: string | undefined;
|
|
1435
833
|
let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined;
|
|
1436
|
-
process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV] = "subagent-worker-78f659a3";
|
|
1437
834
|
|
|
1438
835
|
registerSubagentPromptRuntime({
|
|
1439
836
|
on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) {
|
|
@@ -1443,7 +840,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1443
840
|
setSessionName(name: string) {
|
|
1444
841
|
sessionName = name;
|
|
1445
842
|
},
|
|
1446
|
-
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }>; setSessionName(name: string): void });
|
|
843
|
+
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }>; setSessionName(name: string): void }, childConfig({ intercomSessionName: "subagent-worker-78f659a3", sessionName: "worker: display name" }));
|
|
1447
844
|
|
|
1448
845
|
await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
|
|
1449
846
|
|
|
@@ -1457,11 +854,9 @@ describe("subagent prompt runtime", () => {
|
|
|
1457
854
|
if (event === "before_agent_start") beforeAgentStart = handler;
|
|
1458
855
|
},
|
|
1459
856
|
getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }],
|
|
1460
|
-
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> });
|
|
857
|
+
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ inheritProjectContext: false, inheritGlobalContext: true, inheritSkills: false }));
|
|
1461
858
|
|
|
1462
859
|
assert.ok(beforeAgentStart, "expected before_agent_start handler");
|
|
1463
|
-
process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = "0";
|
|
1464
|
-
process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = "0";
|
|
1465
860
|
|
|
1466
861
|
const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
|
|
1467
862
|
assert.ok(rewritten);
|
|
@@ -1470,18 +865,14 @@ describe("subagent prompt runtime", () => {
|
|
|
1470
865
|
assert.ok(rewritten.systemPrompt.includes("Current date: 2026-04-16"));
|
|
1471
866
|
});
|
|
1472
867
|
|
|
1473
|
-
it("uses the fanout boundary through before_agent_start
|
|
868
|
+
it("uses the fanout boundary through before_agent_start for a fanout child", async () => {
|
|
1474
869
|
let beforeAgentStart: ((event: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) | undefined;
|
|
1475
870
|
registerSubagentPromptRuntime({
|
|
1476
871
|
on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>) {
|
|
1477
872
|
if (event === "before_agent_start") beforeAgentStart = handler;
|
|
1478
873
|
},
|
|
1479
874
|
getAllTools: () => [{ name: "intercom" }, { name: "contact_supervisor" }],
|
|
1480
|
-
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> });
|
|
1481
|
-
|
|
1482
|
-
process.env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT = "1";
|
|
1483
|
-
process.env.SELESAI_SUBAGENT_INHERIT_SKILLS = "1";
|
|
1484
|
-
process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
|
|
875
|
+
} as { on(event: string, handler: (payload: { systemPrompt: string }) => Promise<{ systemPrompt: string } | undefined>): void; getAllTools(): Array<{ name: string }> }, childConfig({ fanoutChild: true, inheritProjectContext: true, inheritGlobalContext: true, inheritSkills: true }));
|
|
1485
876
|
|
|
1486
877
|
const rewritten = await beforeAgentStart?.({ systemPrompt: BASE_PROMPT });
|
|
1487
878
|
assert.ok(rewritten);
|
|
@@ -1494,7 +885,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1494
885
|
on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined) {
|
|
1495
886
|
if (event === "context") contextHandler = handler;
|
|
1496
887
|
},
|
|
1497
|
-
} as { on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined): void });
|
|
888
|
+
} as { on(event: string, handler: (payload: { messages: unknown[] }) => { messages: unknown[] } | undefined): void }, childConfig());
|
|
1498
889
|
|
|
1499
890
|
const priorParentTurn = { role: "user", content: "Earlier we said planner → worker → reviewers → worker." };
|
|
1500
891
|
const currentTask = { role: "user", content: "Now implement only the assigned fix." };
|
|
@@ -1517,7 +908,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1517
908
|
on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
|
|
1518
909
|
if (event === "context") contextHandler = handler;
|
|
1519
910
|
},
|
|
1520
|
-
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
|
|
911
|
+
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
|
|
1521
912
|
|
|
1522
913
|
const toolCallId = "call_N7iYNRPXLl9czpXh3bDyMpIL|fc_0e76718634eca88f016a76fdc89aec81919763fa7858f67a0d";
|
|
1523
914
|
const messages = [
|
|
@@ -1542,7 +933,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1542
933
|
on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
|
|
1543
934
|
if (event === "context") contextHandler = handler;
|
|
1544
935
|
},
|
|
1545
|
-
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
|
|
936
|
+
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
|
|
1546
937
|
|
|
1547
938
|
const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY|fc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa";
|
|
1548
939
|
const messages = [
|
|
@@ -1560,7 +951,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1560
951
|
on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
|
|
1561
952
|
if (event === "context") contextHandler = handler;
|
|
1562
953
|
},
|
|
1563
|
-
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
|
|
954
|
+
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
|
|
1564
955
|
|
|
1565
956
|
const toolCallId = "call_7XJjvAJfk07117JO8LgBCZjY\nfc_0e92b09b28010bac016a756e9e79cc8197b01825a5dc3d9eaa";
|
|
1566
957
|
const messages = [
|
|
@@ -1580,7 +971,7 @@ describe("subagent prompt runtime", () => {
|
|
|
1580
971
|
on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined) {
|
|
1581
972
|
if (event === "context") contextHandler = handler;
|
|
1582
973
|
},
|
|
1583
|
-
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void });
|
|
974
|
+
} as { on(event: string, handler: (payload: { messages: unknown[] }, ctx: { model?: { api: string } }) => { messages: unknown[] } | undefined): void }, childConfig());
|
|
1584
975
|
|
|
1585
976
|
const messages = [
|
|
1586
977
|
{ role: "user", content: "Task" },
|
|
@@ -1590,4 +981,4 @@ describe("subagent prompt runtime", () => {
|
|
|
1590
981
|
|
|
1591
982
|
assert.equal(contextHandler?.({ messages }, {}), undefined);
|
|
1592
983
|
});
|
|
1593
|
-
});
|
|
984
|
+
});
|