@selesai/code 0.8.2 → 0.8.4
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 +15 -0
- package/README.md +4 -2
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +18 -0
- package/dist/cli/config-selector.js +2 -2
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.js +2 -2
- package/dist/core/keybindings.d.ts +44 -4
- package/dist/core/llama/provider.js +22 -7
- package/dist/core/model-resolver.js +2 -0
- package/dist/core/model-runtime.d.ts +4 -2
- package/dist/core/model-runtime.js +23 -2
- package/dist/core/remote-catalog-provider.js +72 -67
- package/dist/core/settings-manager.d.ts +8 -0
- package/dist/core/settings-manager.js +18 -0
- package/dist/extensions/handoff-new.ts +1 -1
- package/dist/extensions/inline-skills.test.ts +1 -0
- package/dist/extensions/inline-skills.ts +1 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +25 -3
- package/dist/extensions/pi-intercom/README.md +34 -15
- package/dist/extensions/pi-intercom/broker/broker.ts +38 -141
- package/dist/extensions/pi-intercom/broker/client-liveness.test.ts +173 -0
- package/dist/extensions/pi-intercom/broker/client.ts +78 -150
- package/dist/extensions/pi-intercom/broker/extension-state.ts +18 -7
- package/dist/extensions/pi-intercom/broker/framing.ts +1 -1
- package/dist/extensions/pi-intercom/broker/protocol.ts +182 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +106 -7
- package/dist/extensions/pi-intercom/broker/spawn.ts +37 -11
- package/dist/extensions/pi-intercom/config.test.ts +8 -10
- package/dist/extensions/pi-intercom/config.ts +3 -3
- package/dist/extensions/pi-intercom/index.ts +218 -147
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +891 -103
- package/dist/extensions/pi-intercom/package.json +3 -2
- package/dist/extensions/pi-intercom/project-agent.test.ts +145 -0
- package/dist/extensions/pi-intercom/project-agent.ts +324 -0
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +86 -1
- package/dist/extensions/pi-intercom/reply-tracker.ts +41 -9
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +49 -9
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +9 -9
- package/dist/extensions/pi-intercom/types.ts +3 -1
- package/dist/extensions/pi-intercom/ui/inline-message.ts +7 -7
- package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +2 -2
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +15 -1
- package/dist/extensions/pi-powerline-footer/README.md +20 -37
- package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +40 -25
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +149 -18
- package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -0
- package/dist/extensions/pi-powerline-footer/editor-performance.ts +99 -0
- package/dist/extensions/pi-powerline-footer/git-status.ts +15 -0
- package/dist/extensions/pi-powerline-footer/index.ts +394 -586
- package/dist/extensions/pi-powerline-footer/package.json +5 -6
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -18
- package/dist/extensions/pi-powerline-footer/queue/store.ts +8 -56
- package/dist/extensions/pi-powerline-footer/queue/types.ts +1 -2
- package/dist/extensions/pi-powerline-footer/segments.ts +0 -3
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +231 -1
- package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +5 -19
- package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +43 -0
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +2 -2
- package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +64 -0
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +1 -1
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +86 -19
- package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +19 -149
- package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +6 -0
- package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +5 -5
- package/dist/extensions/pi-powerline-footer/tsconfig.json +1 -1
- package/dist/extensions/pi-powerline-footer/types.ts +1 -2
- package/dist/extensions/pi-subagents/CHANGELOG.md +202 -14
- package/dist/extensions/pi-subagents/README.md +65 -1614
- package/dist/extensions/pi-subagents/agents/builder.md +0 -1
- package/dist/extensions/pi-subagents/agents/delegate.md +14 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +74 -0
- package/dist/extensions/pi-subagents/agents/reviewer.md +78 -0
- package/dist/extensions/pi-subagents/agents/scout.md +50 -0
- package/dist/extensions/pi-subagents/agents/worker.md +58 -0
- package/dist/extensions/pi-subagents/docs/configuration.md +328 -0
- package/dist/extensions/pi-subagents/docs/extension-api.md +308 -0
- package/dist/extensions/pi-subagents/docs/missions.md +119 -0
- package/dist/extensions/pi-subagents/docs/models.md +192 -0
- package/dist/extensions/pi-subagents/docs/observability.md +174 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +343 -0
- package/dist/extensions/pi-subagents/docs/watchdog.md +176 -0
- package/dist/extensions/pi-subagents/docs/workflows.md +163 -0
- package/dist/extensions/pi-subagents/inspector-runner.mjs +11 -0
- package/dist/extensions/pi-subagents/package-lock.json +152 -194
- package/dist/extensions/pi-subagents/package.json +9 -10
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +12 -12
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -17
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +77 -72
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +177 -69
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +28 -8
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +88 -66
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +3 -3
- package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +624 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +29 -16
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +7 -3
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +2 -9
- package/dist/extensions/pi-subagents/src/api/delegation.ts +1 -2
- package/dist/extensions/pi-subagents/src/api/project-panes.ts +30 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +56 -8
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
- package/dist/extensions/pi-subagents/src/extension/index.ts +119 -50
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +72 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +15 -15
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +58 -21
- package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +39 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +47 -25
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +3 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +460 -64
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +102 -4
- package/dist/extensions/pi-subagents/src/missions/actions.ts +77 -16
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +162 -0
- package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +65 -14
- package/dist/extensions/pi-subagents/src/missions/store.ts +147 -5
- package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +256 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +41 -7
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +294 -134
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +19 -3
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +39 -6
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +267 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +145 -60
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +114 -6
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +33 -0
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +68 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +21 -2
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +42 -14
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +12 -24
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +152 -68
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +146 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +21 -5
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +43 -18
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +20 -19
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +55 -26
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +798 -112
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +165 -22
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +12 -2
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +465 -116
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +189 -32
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +3 -3
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +128 -3
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +100 -0
- package/dist/extensions/pi-subagents/src/shared/file-coalescer.ts +9 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +13 -0
- package/dist/extensions/pi-subagents/src/shared/formatters.ts +4 -6
- package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +51 -0
- package/dist/extensions/pi-subagents/src/shared/settings.ts +41 -11
- package/dist/extensions/pi-subagents/src/shared/types.ts +100 -3
- package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +43 -33
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -14
- package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +14 -12
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +176 -21
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +12 -6
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +52 -18
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +36 -5
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +30 -16
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +151 -31
- package/dist/extensions/pi-subagents/src/tui/render.ts +201 -68
- package/dist/extensions/pi-subagents/src/types/pi-runtime-compat.d.ts +0 -6
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +0 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +313 -25
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +57 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +96 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +119 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +85 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +620 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +56 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +49 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +93 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +128 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +38 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +171 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +1261 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +358 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +512 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +43 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +121 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +82 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +210 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +55 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +206 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +356 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +297 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +60 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +68 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +106 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +51 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +40 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +37 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +70 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +36 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +35 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +102 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +32 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +34 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +13 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +52 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +54 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +33 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +95 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +77 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +63 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +94 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +14 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +394 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +29 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +120 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +17 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +246 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +20 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +457 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +22 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +11 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +4 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +8 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +26 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +7 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +10 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +18 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +16 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +9 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +21 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +31 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +5 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +12 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +15 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +3 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/index.mjs +84 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/package.json +14 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +411 -300
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +309 -123
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +99 -56
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +159 -108
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +56 -56
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +114 -45
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +57 -19
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +106 -106
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +416 -106
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +287 -17
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +809 -87
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +107 -20
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +26 -6
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +1 -1
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +0 -8
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +163 -58
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +334 -248
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +52 -52
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +26 -26
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +99 -99
- package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +160 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +43 -1
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +36 -10
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +387 -30
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +94 -40
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +5 -3
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +19 -19
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +19 -19
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +20 -20
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +35 -5
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +52 -52
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +187 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +125 -71
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +18 -3
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +17 -23
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +25 -25
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +62 -44
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +329 -37
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +79 -2
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +102 -49
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +98 -32
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +5 -5
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +116 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +55 -7
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +289 -13
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +28 -1
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +11 -2
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +84 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +122 -8
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +24 -24
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +16 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +10 -10
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +26 -17
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +405 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +357 -92
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +22 -6
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +6 -6
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +43 -43
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +4 -4
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +19 -19
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +243 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +68 -168
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +58 -15
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +40 -0
- package/dist/extensions/pi-subagents/test/unit/reads-resolution.test.ts +66 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +47 -28
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +107 -27
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +56 -50
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +69 -28
- package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +59 -44
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +23 -1
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +32 -5
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +120 -91
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +85 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +35 -35
- package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +312 -22
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +150 -15
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -26
- package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -25
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +5 -5
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +13 -13
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +6 -6
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +138 -11
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +8 -8
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +6 -6
- package/dist/extensions/workflow/extension.ts +21 -19
- package/dist/extensions/workflow/modes.ts +65 -0
- package/dist/index.d.ts +1 -1
- package/dist/main.js +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +6 -2
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/interactive-mode.d.ts +28 -0
- package/dist/modes/interactive/interactive-mode.js +214 -28
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
- package/dist/modes/interactive/theme/theme-controller.js +10 -1
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.js +24 -6
- package/dist/skills/pi-subagents/SKILL.md +20 -15
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +77 -83
- package/dist/skills/pi-subagents/references/execution-controls.md +215 -106
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +29 -9
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +58 -43
- package/docs/keybindings.md +35 -7
- package/docs/plans/workflow-autoloop-reference.md +192 -0
- package/docs/plans/workflow-subagent-consolidation-planb.md +374 -0
- package/docs/plans/workflow-subagent-scripted-merge.md +406 -0
- package/docs/settings.md +16 -14
- package/docs/usage.md +5 -0
- package/package.json +5 -5
- package/dist/extensions/pi-powerline-footer/fixed-editor/cluster.ts +0 -113
- package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +0 -1080
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor.test.ts +0 -1452
- package/dist/extensions/workflow/adapter.ts +0 -949
- package/dist/extensions/workflow/modes/loop.ts +0 -74
- package/dist/extensions/workflow/modes/prototype.ts +0 -170
- package/dist/extensions/workflow/modes/quicktype.ts +0 -150
- package/dist/extensions/workflow/modes/task.ts +0 -105
- package/dist/extensions/workflow/run-state.ts +0 -124
- package/dist/extensions/workflow/state-machine.ts +0 -484
- package/dist/extensions/workflow/validators.ts +0 -42
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
tryImport,
|
|
28
28
|
} from "../support/helpers.ts";
|
|
29
29
|
import registerSubagentExtension from "../../src/extension/index.ts";
|
|
30
|
+
import { discoverAgents } from "../../src/agents/agents.ts";
|
|
30
31
|
import {
|
|
31
32
|
SUBAGENT_DELEGATION_REQUEST_EVENT,
|
|
32
33
|
SUBAGENT_DELEGATION_RESPONSE_EVENT,
|
|
@@ -35,7 +36,8 @@ import {
|
|
|
35
36
|
type SubagentDelegationResponse,
|
|
36
37
|
type SubagentDelegationStarted,
|
|
37
38
|
} from "../../src/api/delegation.ts";
|
|
38
|
-
import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type SubagentState } from "../../src/shared/types.ts";
|
|
39
|
+
import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, type AsyncStatus, type SubagentState } from "../../src/shared/types.ts";
|
|
40
|
+
import { ACTIVE_RUN_INDEX_DIR } from "../../src/runs/background/active-run-index.ts";
|
|
39
41
|
import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
|
|
40
42
|
import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
|
|
41
43
|
import { TOOL_BUDGET_ENV, TOOL_BUDGET_ZERO_AUTH_ENV } from "../../src/runs/shared/tool-budget.ts";
|
|
@@ -43,6 +45,9 @@ import { MainWatchdogRuntime } from "../../src/watchdog/runtime.ts";
|
|
|
43
45
|
import { MAX_CHILD_PENDING_LINE_BYTES, MAX_CHILD_STDERR_BYTES } from "../../src/runs/shared/child-protocol.ts";
|
|
44
46
|
import {
|
|
45
47
|
SUBAGENT_FANOUT_CHILD_ENV,
|
|
48
|
+
SUBAGENT_STEER_ACK_DIR_ENV,
|
|
49
|
+
SUBAGENT_STEER_CAPABILITY_ENV,
|
|
50
|
+
SUBAGENT_STEER_INBOX_ENV,
|
|
46
51
|
SUBAGENT_PARENT_CHILD_INDEX_ENV,
|
|
47
52
|
SUBAGENT_PARENT_CONTROL_INBOX_ENV,
|
|
48
53
|
SUBAGENT_PARENT_DEPTH_ENV,
|
|
@@ -142,6 +147,7 @@ interface RunSyncResult {
|
|
|
142
147
|
|
|
143
148
|
interface MockPiCallRecord {
|
|
144
149
|
args?: string[];
|
|
150
|
+
cwd?: string;
|
|
145
151
|
systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
|
|
146
152
|
}
|
|
147
153
|
|
|
@@ -235,6 +241,7 @@ interface ExecutorToolResult {
|
|
|
235
241
|
isError?: boolean;
|
|
236
242
|
details?: {
|
|
237
243
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
244
|
+
controlEvents?: Array<{ type?: string }>;
|
|
238
245
|
asyncId?: string;
|
|
239
246
|
timeoutMs?: number;
|
|
240
247
|
turnBudget?: { maxTurns: number; graceTurns: number };
|
|
@@ -300,7 +307,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
300
307
|
removeTempDir(tempDir);
|
|
301
308
|
});
|
|
302
309
|
|
|
303
|
-
function readCall(): { args: string[]; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
|
|
310
|
+
function readCall(): { args: string[]; cwd?: string; systemPrompts: NonNullable<MockPiCallRecord["systemPrompts"]> } {
|
|
304
311
|
const callFile = fs.readdirSync(mockPi.dir)
|
|
305
312
|
.filter((name) => name.startsWith("call-") && name.endsWith(".json"))
|
|
306
313
|
.sort()
|
|
@@ -308,7 +315,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
308
315
|
assert.ok(callFile, "expected a recorded mock pi call");
|
|
309
316
|
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
310
317
|
assert.ok(Array.isArray(payload.args), "expected recorded args");
|
|
311
|
-
return { args: payload.args, systemPrompts: payload.systemPrompts ?? [] };
|
|
318
|
+
return { args: payload.args, cwd: payload.cwd, systemPrompts: payload.systemPrompts ?? [] };
|
|
312
319
|
}
|
|
313
320
|
|
|
314
321
|
function readCallArgs(): string[] {
|
|
@@ -323,9 +330,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
323
330
|
allowMutatingManagementActions = true,
|
|
324
331
|
initialAsyncJobs: SubagentState["asyncJobs"] = new Map(),
|
|
325
332
|
workflowControllers?: Map<string, AbortController>,
|
|
333
|
+
handleScheduledRunAction?: Parameters<typeof createSubagentExecutor>[0]["handleScheduledRunAction"],
|
|
334
|
+
piEvents = createEventBus(),
|
|
326
335
|
) {
|
|
327
336
|
return createSubagentExecutor!({
|
|
328
|
-
pi: { events:
|
|
337
|
+
pi: { events: piEvents, getSessionName: () => undefined },
|
|
329
338
|
state: {
|
|
330
339
|
baseCwd: tempDir,
|
|
331
340
|
currentSessionId: initialSpawnState?.sessionId ?? null,
|
|
@@ -342,6 +351,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
342
351
|
expandTilde: (value: string) => value,
|
|
343
352
|
discoverAgents: () => ({ agents }),
|
|
344
353
|
allowMutatingManagementActions,
|
|
354
|
+
...(handleScheduledRunAction ? { handleScheduledRunAction } : {}),
|
|
345
355
|
});
|
|
346
356
|
}
|
|
347
357
|
|
|
@@ -361,11 +371,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
361
371
|
assert.equal(output, "Hello from mock agent");
|
|
362
372
|
});
|
|
363
373
|
|
|
364
|
-
it("
|
|
365
|
-
mockPi.onCall({ output: "single alias finished" });
|
|
374
|
+
it("rejects action='single' with execution fields", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
366
375
|
const executor = makeExecutor([makeAgent("echo")]);
|
|
367
376
|
|
|
368
|
-
const result = await executor.
|
|
377
|
+
const result = await executor.executePublic(
|
|
369
378
|
"single-alias",
|
|
370
379
|
{ action: "single", agent: "echo", task: "Run through alias" },
|
|
371
380
|
new AbortController().signal,
|
|
@@ -373,18 +382,46 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
373
382
|
makeMinimalCtx(tempDir),
|
|
374
383
|
);
|
|
375
384
|
|
|
385
|
+
assert.equal(result.isError, true);
|
|
386
|
+
assert.match(result.content[0]?.text ?? "", /is not a management action/);
|
|
387
|
+
assert.equal(mockPi.callCount(), 0);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("allows schedule.create to carry the required workflowScript target", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
391
|
+
let forwarded;
|
|
392
|
+
const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, async (params) => {
|
|
393
|
+
forwarded = params;
|
|
394
|
+
return { content: [{ type: "text", text: "created" }], details: { mode: "management", results: [] } };
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const result = await executor.execute(
|
|
398
|
+
"schedule-create",
|
|
399
|
+
{ action: "schedule.create", id: "nightly", every: "1h", workflowScript: "return runs.run('main', { agent: 'echo' })" },
|
|
400
|
+
new AbortController().signal,
|
|
401
|
+
undefined,
|
|
402
|
+
makeMinimalCtx(tempDir),
|
|
403
|
+
);
|
|
404
|
+
|
|
376
405
|
assert.equal(result.isError, undefined);
|
|
377
|
-
assert.
|
|
406
|
+
assert.equal(result.content[0]?.text, "created");
|
|
407
|
+
assert.equal(forwarded?.workflowScript, "return runs.run('main', { agent: 'echo' })");
|
|
378
408
|
});
|
|
379
409
|
|
|
380
|
-
it("starts workflow scripts asynchronously
|
|
381
|
-
mockPi.onCall({
|
|
382
|
-
const
|
|
383
|
-
const
|
|
410
|
+
it("starts workflow scripts asynchronously with a portable internal run id", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
411
|
+
mockPi.onCall({ echoEnv: [SUBAGENT_STEER_INBOX_ENV, SUBAGENT_STEER_CAPABILITY_ENV, SUBAGENT_STEER_ACK_DIR_ENV] });
|
|
412
|
+
const asyncJobs: SubagentState["asyncJobs"] = new Map();
|
|
413
|
+
const executor = makeExecutor([makeAgent("echo", { aliases: ["helper"] })], { missions: { globalIndex: false } }, false, undefined, true, asyncJobs);
|
|
414
|
+
const workflowCwd = path.join(tempDir, "workflow-cwd");
|
|
415
|
+
fs.mkdirSync(workflowCwd);
|
|
416
|
+
const toolCallId = "call_demo|fc_demo";
|
|
384
417
|
|
|
385
418
|
const result = await executor.execute(
|
|
386
|
-
|
|
387
|
-
{
|
|
419
|
+
toolCallId,
|
|
420
|
+
{
|
|
421
|
+
cwd: workflowCwd,
|
|
422
|
+
workflowScript: `emit("starting"); await runs.run("work", { agent: "helper", task: "Async work" }); return { answer: 42 };`,
|
|
423
|
+
mission: { summary: "Review the active backlog", labels: ["github-backlog", "review"] },
|
|
424
|
+
},
|
|
388
425
|
new AbortController().signal,
|
|
389
426
|
undefined,
|
|
390
427
|
makeMinimalCtx(tempDir),
|
|
@@ -392,25 +429,141 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
392
429
|
|
|
393
430
|
assert.equal(result.isError, undefined);
|
|
394
431
|
assert.equal(result.details.mode, "workflow");
|
|
395
|
-
assert.equal(result.details.
|
|
432
|
+
assert.equal(result.details.toolCallId, toolCallId);
|
|
433
|
+
assert.ok(result.details.asyncId);
|
|
434
|
+
const workflowRunId = result.details.asyncId;
|
|
435
|
+
assert.equal(result.details.runId, workflowRunId);
|
|
436
|
+
assert.notEqual(workflowRunId, toolCallId);
|
|
437
|
+
assert.match(workflowRunId, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
438
|
+
assert.equal(path.basename(result.details.asyncDir!), workflowRunId);
|
|
439
|
+
assert.equal(asyncJobs.has(workflowRunId), true);
|
|
440
|
+
assert.equal(asyncJobs.get(workflowRunId)?.cwd, workflowCwd);
|
|
441
|
+
assert.equal(asyncJobs.has(toolCallId), false);
|
|
442
|
+
assert.equal(fs.existsSync(path.join(DIRS.async, toolCallId)), false);
|
|
396
443
|
assert.match(result.content[0]?.text ?? "", /Async workflow/);
|
|
397
444
|
const statusPath = path.join(result.details.asyncDir!, "status.json");
|
|
398
|
-
let status: { state?: string; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; state?: string }> } } = {};
|
|
445
|
+
let status: { runId?: string; toolCallId?: string; cwd?: string; state?: string; steps?: Array<{ agent?: string; label?: string; workflowKey?: string; parentWorkflowRunId?: string }>; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; agent?: string; state?: string }> } } = {};
|
|
399
446
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
400
447
|
status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
|
|
401
448
|
if (status.state === "complete" || status.state === "failed") break;
|
|
402
449
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
403
450
|
}
|
|
404
451
|
assert.equal(status.state, "complete");
|
|
452
|
+
assert.equal(status.runId, workflowRunId);
|
|
453
|
+
assert.equal(status.toolCallId, toolCallId);
|
|
454
|
+
assert.equal(status.cwd, workflowCwd);
|
|
455
|
+
assert.equal(status.steps?.length, 1);
|
|
456
|
+
assert.deepEqual(status.steps?.map(({ agent, label, workflowKey }) => ({ agent, label, workflowKey })), [
|
|
457
|
+
{ agent: "echo", label: "work", workflowKey: "work" },
|
|
458
|
+
]);
|
|
459
|
+
assert.ok(status.steps?.every((step) => step.parentWorkflowRunId === workflowRunId));
|
|
405
460
|
assert.deepEqual(status.workflow?.value, { answer: 42 });
|
|
406
461
|
assert.deepEqual(status.workflow?.emits, ["starting"]);
|
|
407
|
-
assert.
|
|
408
|
-
|
|
462
|
+
assert.equal(mockPi.callCount(), 1);
|
|
463
|
+
assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.agent === "echo" && entry.state === "completed"));
|
|
464
|
+
const traceEvents = fs.readFileSync(path.join(result.details.asyncDir!, "events.jsonl"), "utf-8")
|
|
465
|
+
.trim()
|
|
466
|
+
.split("\n")
|
|
467
|
+
.map((line) => JSON.parse(line) as { type?: string; trace?: Array<{ key?: string; state?: string }> })
|
|
468
|
+
.filter((event) => event.type === "subagent.workflow.trace");
|
|
469
|
+
assert.equal(traceEvents.length, 2);
|
|
470
|
+
assert.deepEqual(traceEvents[0]?.trace?.map(({ key, state }) => ({ key, state })), [{ key: "work", state: "started" }]);
|
|
471
|
+
assert.deepEqual(traceEvents[1]?.trace?.map(({ key, state }) => ({ key, state })), [
|
|
472
|
+
{ key: "work", state: "started" },
|
|
473
|
+
{ key: "work", state: "completed" },
|
|
474
|
+
]);
|
|
475
|
+
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
476
|
+
const persistedResult = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { id?: string; runId?: string; toolCallId?: string; agent?: string; cwd?: string; summary?: string; workflow?: { value?: unknown }; results?: Array<{ agent?: string; workflowKey?: string; runId?: string; output?: string }> };
|
|
477
|
+
assert.equal(persistedResult.id, workflowRunId);
|
|
478
|
+
assert.equal(persistedResult.runId, workflowRunId);
|
|
479
|
+
assert.equal(persistedResult.toolCallId, toolCallId);
|
|
409
480
|
assert.equal(persistedResult.agent, "workflow");
|
|
481
|
+
assert.equal(persistedResult.cwd, workflowCwd);
|
|
482
|
+
assert.deepEqual(persistedResult.results?.map(({ agent, workflowKey }) => ({ agent, workflowKey })), [
|
|
483
|
+
{ agent: "echo", workflowKey: "work" },
|
|
484
|
+
]);
|
|
485
|
+
const steeringEnv = JSON.parse(persistedResult.results?.[0]?.output ?? "null") as Record<string, string | null>;
|
|
486
|
+
assert.match(steeringEnv[SUBAGENT_STEER_INBOX_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-targets[/\\]0$/);
|
|
487
|
+
assert.match(steeringEnv[SUBAGENT_STEER_CAPABILITY_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-capabilities[/\\]0\.json$/);
|
|
488
|
+
assert.match(steeringEnv[SUBAGENT_STEER_ACK_DIR_ENV] ?? "", /control[/\\]workflow-foreground[/\\].+[/\\]control[/\\]steer-acks[/\\]0$/);
|
|
489
|
+
assert.equal(fs.existsSync(path.join(result.details.asyncDir!, "control", "workflow-foreground", persistedResult.results?.[0]?.runId ?? "missing")), false);
|
|
410
490
|
assert.match(persistedResult.summary ?? "", /Return: \{\n "answer": 42\n\}/);
|
|
411
491
|
assert.deepEqual(persistedResult.workflow?.value, { answer: 42 });
|
|
492
|
+
assert.equal(fs.existsSync(path.join(DIRS.results, `${toolCallId}.json`)), false);
|
|
412
493
|
fs.rmSync(result.details.asyncDir!, { recursive: true, force: true });
|
|
413
|
-
fs.rmSync(
|
|
494
|
+
fs.rmSync(resultPath, { force: true });
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("projects live child activity into async workflow status", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
498
|
+
mockPi.onCall({
|
|
499
|
+
steps: [
|
|
500
|
+
{ jsonl: [events.toolStart("read", { path: "src/example.ts" })] },
|
|
501
|
+
{ delay: 2_500, jsonl: [events.toolEnd("read"), events.toolResult("read", "contents")] },
|
|
502
|
+
{ jsonl: [events.assistantMessage("Done")] },
|
|
503
|
+
],
|
|
504
|
+
});
|
|
505
|
+
const asyncJobs: SubagentState["asyncJobs"] = new Map();
|
|
506
|
+
const executor = makeExecutor([makeAgent("echo")], {
|
|
507
|
+
control: {
|
|
508
|
+
enabled: true,
|
|
509
|
+
needsAttentionAfterMs: 100,
|
|
510
|
+
activeNoticeAfterMs: 100,
|
|
511
|
+
activeNoticeAfterTurns: 999_999,
|
|
512
|
+
activeNoticeAfterTokens: 999_999,
|
|
513
|
+
notifyOn: ["active_long_running", "needs_attention"],
|
|
514
|
+
notifyChannels: ["event"],
|
|
515
|
+
},
|
|
516
|
+
}, false, undefined, true, asyncJobs);
|
|
517
|
+
|
|
518
|
+
const result = await executor.execute(
|
|
519
|
+
"workflow-live-activity",
|
|
520
|
+
{ workflowScript: `return runs.run("main", { agent: "echo", task: "Inspect the file" });` },
|
|
521
|
+
new AbortController().signal,
|
|
522
|
+
undefined,
|
|
523
|
+
makeMinimalCtx(tempDir),
|
|
524
|
+
);
|
|
525
|
+
const { asyncId: workflowRunId, asyncDir } = result.details;
|
|
526
|
+
assert.ok(workflowRunId);
|
|
527
|
+
assert.ok(asyncDir);
|
|
528
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
529
|
+
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
530
|
+
const activeMarkerPath = path.join(DIRS.async, ACTIVE_RUN_INDEX_DIR, workflowRunId);
|
|
531
|
+
assert.equal(fs.existsSync(activeMarkerPath), true);
|
|
532
|
+
let liveStatus: AsyncStatus | undefined;
|
|
533
|
+
const activityDeadline = Date.now() + 5_000;
|
|
534
|
+
while (Date.now() < activityDeadline && !fs.existsSync(resultPath)) {
|
|
535
|
+
const candidate = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatus;
|
|
536
|
+
if (candidate.activityState === "active_long_running" && candidate.steps?.[0]?.currentTool === "read") {
|
|
537
|
+
liveStatus = candidate;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
assert.ok(liveStatus, "expected workflow status to expose live child activity");
|
|
544
|
+
assert.equal(liveStatus.activityState, "active_long_running");
|
|
545
|
+
assert.equal(typeof liveStatus.lastActivityAt, "number");
|
|
546
|
+
assert.equal(liveStatus.currentTool, "read");
|
|
547
|
+
assert.match(liveStatus.currentPath ?? "", /src[/\\]example\.ts$/);
|
|
548
|
+
assert.equal(liveStatus.toolCount, 1);
|
|
549
|
+
assert.equal(liveStatus.steps?.[0]?.status, "running");
|
|
550
|
+
assert.equal(liveStatus.steps?.[0]?.agent, "echo");
|
|
551
|
+
assert.match(liveStatus.steps?.[0]?.sessionFile ?? "", /session\.jsonl$/);
|
|
552
|
+
assert.equal(fs.existsSync(liveStatus.steps?.[0]?.sessionFile ?? ""), true);
|
|
553
|
+
assert.equal(liveStatus.steps?.[0]?.activityState, "active_long_running");
|
|
554
|
+
assert.equal(typeof liveStatus.steps?.[0]?.lastActivityAt, "number");
|
|
555
|
+
assert.equal(liveStatus.steps?.[0]?.toolCount, 1);
|
|
556
|
+
assert.equal(asyncJobs.get(workflowRunId)?.activityState, "active_long_running");
|
|
557
|
+
assert.equal(asyncJobs.get(workflowRunId)?.steps?.[0]?.currentTool, "read");
|
|
558
|
+
|
|
559
|
+
const completionDeadline = Date.now() + 5_000;
|
|
560
|
+
while (!fs.existsSync(resultPath)) {
|
|
561
|
+
if (Date.now() > completionDeadline) assert.fail("Timed out waiting for async workflow completion");
|
|
562
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
563
|
+
}
|
|
564
|
+
assert.equal(fs.existsSync(activeMarkerPath), false);
|
|
565
|
+
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
566
|
+
fs.rmSync(resultPath, { force: true });
|
|
414
567
|
});
|
|
415
568
|
|
|
416
569
|
it("rejects an invalid async workflow usage budget before creating run state", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
@@ -449,8 +602,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
449
602
|
);
|
|
450
603
|
|
|
451
604
|
assert.equal(started.isError, undefined);
|
|
452
|
-
assert.
|
|
453
|
-
|
|
605
|
+
assert.ok(started.details.asyncId);
|
|
606
|
+
assert.notEqual(started.details.asyncId, runId);
|
|
607
|
+
const resultPath = path.join(DIRS.results, `${started.details.asyncId}.json`);
|
|
454
608
|
let persisted: { state?: string; summary?: string; results?: Array<{ success?: boolean; output?: string }> } = {};
|
|
455
609
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
456
610
|
if (fs.existsSync(resultPath)) persisted = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
|
|
@@ -467,6 +621,60 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
467
621
|
fs.rmSync(resultPath, { force: true });
|
|
468
622
|
});
|
|
469
623
|
|
|
624
|
+
it("keeps ordinary async workflow child results in the watcher-owned path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
625
|
+
mockPi.onCall({ output: "async child done" });
|
|
626
|
+
const executor = makeExecutor([makeAgent("echo")]);
|
|
627
|
+
const result = await executor.execute(
|
|
628
|
+
`scripted-workflow-async-child-${Date.now()}`,
|
|
629
|
+
{ workflowScript: `return await runs.run("background", { agent: "echo", task: "Async child", async: true });`, async: false },
|
|
630
|
+
new AbortController().signal,
|
|
631
|
+
undefined,
|
|
632
|
+
makeMinimalCtx(tempDir),
|
|
633
|
+
);
|
|
634
|
+
const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
|
|
635
|
+
assert.ok(childRunId);
|
|
636
|
+
const childDir = path.join(DIRS.async, childRunId);
|
|
637
|
+
const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
|
|
638
|
+
for (let attempt = 0; attempt < 200 && !fs.existsSync(childResultPath); attempt++) {
|
|
639
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
640
|
+
}
|
|
641
|
+
assert.equal(fs.existsSync(childResultPath), true);
|
|
642
|
+
assert.equal(fs.existsSync(path.join(childDir, "workflow-result.json")), false);
|
|
643
|
+
fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
644
|
+
fs.rmSync(childResultPath, { force: true });
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("applies an agent deadline to a workflow-launched async child", { skip: !createSubagentExecutor ? "executor not importable" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
648
|
+
mockPi.onCall({ delay: 5_000, output: "too late" });
|
|
649
|
+
const executor = makeExecutor([makeAgent("slow", { defaultTimeoutMs: 150 })]);
|
|
650
|
+
const result = await executor.execute(
|
|
651
|
+
`scripted-workflow-async-child-timeout-${Date.now()}`,
|
|
652
|
+
{
|
|
653
|
+
workflowScript: `return await runs.run("background", { agent: "slow", task: "Wait", async: true });`,
|
|
654
|
+
async: false,
|
|
655
|
+
},
|
|
656
|
+
new AbortController().signal,
|
|
657
|
+
undefined,
|
|
658
|
+
makeMinimalCtx(tempDir),
|
|
659
|
+
);
|
|
660
|
+
const childRunId = (result.details.workflow?.value as { runId?: string } | undefined)?.runId;
|
|
661
|
+
assert.ok(childRunId, JSON.stringify(result.details.workflow?.value ?? result.content));
|
|
662
|
+
const childDir = path.join(DIRS.async, childRunId);
|
|
663
|
+
const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
|
|
664
|
+
let persisted: { timeoutMs?: number; state?: string; results?: Array<{ timedOut?: boolean; error?: string }> } = {};
|
|
665
|
+
for (let attempt = 0; attempt < 200; attempt++) {
|
|
666
|
+
if (fs.existsSync(childResultPath)) persisted = JSON.parse(fs.readFileSync(childResultPath, "utf-8"));
|
|
667
|
+
if (persisted.state === "failed") break;
|
|
668
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
669
|
+
}
|
|
670
|
+
assert.equal(persisted.timeoutMs, 150);
|
|
671
|
+
assert.equal(persisted.state, "failed");
|
|
672
|
+
assert.deepEqual(persisted.results?.map((entry) => entry.timedOut), [true]);
|
|
673
|
+
assert.deepEqual(persisted.results?.map((entry) => entry.error), ["Subagent timed out after 150ms."]);
|
|
674
|
+
fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
675
|
+
fs.rmSync(childResultPath, { force: true });
|
|
676
|
+
});
|
|
677
|
+
|
|
470
678
|
it("persists workflow parent metadata in an async worktree child status and result", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
471
679
|
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
472
680
|
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
@@ -476,14 +684,16 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
476
684
|
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
477
685
|
mockPi.onCall({ output: "async child done" });
|
|
478
686
|
const executor = makeExecutor([makeAgent("echo")]);
|
|
479
|
-
const
|
|
687
|
+
const toolCallId = `scripted-workflow-parent-${Date.now()}`;
|
|
480
688
|
const started = await executor.execute(
|
|
481
|
-
|
|
689
|
+
toolCallId,
|
|
482
690
|
{ workflowScript: `const child = await runs.run("background", { agent: "echo", task: "Async child", async: true, worktree: true }); return child.runId;` },
|
|
483
691
|
new AbortController().signal,
|
|
484
692
|
undefined,
|
|
485
693
|
makeMinimalCtx(tempDir),
|
|
486
694
|
);
|
|
695
|
+
assert.ok(started.details.asyncId);
|
|
696
|
+
const workflowRunId = started.details.asyncId;
|
|
487
697
|
const workflowResultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
488
698
|
let childRunId: string | undefined;
|
|
489
699
|
for (let attempt = 0; attempt < 150; attempt++) {
|
|
@@ -494,6 +704,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
494
704
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
495
705
|
}
|
|
496
706
|
assert.ok(childRunId);
|
|
707
|
+
const workflowStatus = JSON.parse(fs.readFileSync(path.join(started.details.asyncDir!, "status.json"), "utf-8")) as AsyncStatus;
|
|
708
|
+
const workflowStepSessionFile = workflowStatus.steps?.[0]?.sessionFile ?? "";
|
|
709
|
+
assert.equal(workflowStatus.steps?.[0]?.agent, "echo");
|
|
710
|
+
assert.match(workflowStepSessionFile, /session\.jsonl$/);
|
|
497
711
|
const childDir = path.join(DIRS.async, childRunId);
|
|
498
712
|
const childStatusPath = path.join(childDir, "status.json");
|
|
499
713
|
let childStatus: { state?: string; mode?: string; parentWorkflowRunId?: string; workflowKey?: string; parallelHandoff?: { path?: string } } = {};
|
|
@@ -513,6 +727,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
513
727
|
const childResult = JSON.parse(fs.readFileSync(childResultPath, "utf-8")) as { parentWorkflowRunId?: string; workflowKey?: string };
|
|
514
728
|
assert.equal(childResult.parentWorkflowRunId, workflowRunId);
|
|
515
729
|
assert.equal(childResult.workflowKey, "background");
|
|
730
|
+
assert.equal(fs.existsSync(workflowStepSessionFile), true);
|
|
516
731
|
fs.rmSync(started.details.asyncDir!, { recursive: true, force: true });
|
|
517
732
|
fs.rmSync(workflowResultPath, { force: true });
|
|
518
733
|
fs.rmSync(childDir, { recursive: true, force: true });
|
|
@@ -575,19 +790,20 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
575
790
|
fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
576
791
|
});
|
|
577
792
|
|
|
578
|
-
it("routes workflow
|
|
793
|
+
it("routes workflow children through one automatic mission", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
579
794
|
mockPi.onCall({ output: "scanned auth" });
|
|
580
795
|
mockPi.onCall({ output: "reviewed auth" });
|
|
581
|
-
const executor = makeExecutor([makeAgent("echo")]);
|
|
796
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
|
|
582
797
|
|
|
583
798
|
const result = await executor.execute(
|
|
584
799
|
"scripted-workflow",
|
|
585
800
|
{
|
|
586
801
|
async: false,
|
|
587
802
|
workflowScript: `
|
|
803
|
+
const stateType = typeof state;
|
|
588
804
|
const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
|
|
589
805
|
const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
|
|
590
|
-
return review.output;
|
|
806
|
+
return { output: review.output, stateType };
|
|
591
807
|
`,
|
|
592
808
|
},
|
|
593
809
|
new AbortController().signal,
|
|
@@ -599,9 +815,88 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
599
815
|
assert.match(result.content[0]?.text ?? "", /reviewed auth/);
|
|
600
816
|
assert.equal(result.details.mode, "workflow");
|
|
601
817
|
assert.equal(result.details.results.length, 2);
|
|
818
|
+
assert.equal(result.details.workflow?.value && (result.details.workflow.value as { stateType?: unknown }).stateType, "object");
|
|
819
|
+
assert.ok(result.details.missionId);
|
|
820
|
+
const missionDir = path.join(tempDir, ".pi-subagents", "missions");
|
|
821
|
+
const missionFiles = fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json"));
|
|
822
|
+
assert.equal(missionFiles.length, 1);
|
|
823
|
+
const mission = JSON.parse(fs.readFileSync(path.join(missionDir, missionFiles[0]!), "utf-8")) as { objective?: string };
|
|
824
|
+
assert.equal(mission.objective, "Scan auth");
|
|
602
825
|
assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state === "completed").map((entry) => entry.key), ["scan", "review"]);
|
|
603
826
|
});
|
|
604
827
|
|
|
828
|
+
it("keeps workflow children mission-detached when automatic mission persistence fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
829
|
+
mockPi.onCall({ output: "scanned auth" });
|
|
830
|
+
mockPi.onCall({ output: "reviewed auth" });
|
|
831
|
+
const blockedIndex = path.join(tempDir, "blocked-mission-index");
|
|
832
|
+
fs.writeFileSync(blockedIndex, "not a directory", "utf-8");
|
|
833
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndexDir: blockedIndex } });
|
|
834
|
+
|
|
835
|
+
const result = await executor.execute(
|
|
836
|
+
"scripted-workflow-mission-warning",
|
|
837
|
+
{
|
|
838
|
+
async: false,
|
|
839
|
+
workflowScript: `
|
|
840
|
+
const scan = await runs.run("scan", { agent: "echo", task: "Scan auth" });
|
|
841
|
+
const review = await runs.run("review", { agent: "echo", task: "Review: " + scan.output });
|
|
842
|
+
return review.output;
|
|
843
|
+
`,
|
|
844
|
+
},
|
|
845
|
+
new AbortController().signal,
|
|
846
|
+
undefined,
|
|
847
|
+
makeMinimalCtx(tempDir),
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
assert.equal(result.isError, undefined);
|
|
851
|
+
assert.equal(result.details.missionId, undefined);
|
|
852
|
+
assert.match(result.details.missionWarning ?? "", /Mission tracking unavailable/);
|
|
853
|
+
assert.equal(result.details.results.length, 2);
|
|
854
|
+
const missionDir = path.join(tempDir, ".pi-subagents", "missions");
|
|
855
|
+
const missionFiles = fs.existsSync(missionDir) ? fs.readdirSync(missionDir).filter((entry) => entry.endsWith(".json")) : [];
|
|
856
|
+
assert.equal(missionFiles.length, 1);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it("shares durable workflow state across a mission and omits it for mission:false", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
860
|
+
const executor = makeExecutor([makeAgent("echo")], { missions: { globalIndex: false } });
|
|
861
|
+
const first = await executor.execute(
|
|
862
|
+
"mission-state-first",
|
|
863
|
+
{
|
|
864
|
+
async: false,
|
|
865
|
+
mission: { title: "Stateful workflow" },
|
|
866
|
+
workflowScript: `await state.set("review.stage", { count: 1 }); return await state.get("review.stage");`,
|
|
867
|
+
},
|
|
868
|
+
new AbortController().signal,
|
|
869
|
+
undefined,
|
|
870
|
+
makeMinimalCtx(tempDir),
|
|
871
|
+
);
|
|
872
|
+
assert.equal(first.isError, undefined, first.content[0]?.text ?? "first workflow failed");
|
|
873
|
+
assert.ok(first.details.missionId);
|
|
874
|
+
assert.deepEqual(first.details.workflow?.value, { count: 1 });
|
|
875
|
+
const statePath = path.join(tempDir, ".pi-subagents", "missions", first.details.missionId, "state.json");
|
|
876
|
+
assert.equal(fs.existsSync(statePath), true);
|
|
877
|
+
|
|
878
|
+
const second = await executor.execute(
|
|
879
|
+
"mission-state-second",
|
|
880
|
+
{ async: false, missionId: first.details.missionId, workflowScript: `return await state.get("review.stage");` },
|
|
881
|
+
new AbortController().signal,
|
|
882
|
+
undefined,
|
|
883
|
+
makeMinimalCtx(tempDir),
|
|
884
|
+
);
|
|
885
|
+
assert.equal(second.isError, undefined, second.content[0]?.text ?? "second workflow failed");
|
|
886
|
+
assert.deepEqual(second.details.workflow?.value, { count: 1 });
|
|
887
|
+
|
|
888
|
+
const ephemeral = await executor.execute(
|
|
889
|
+
"mission-state-off",
|
|
890
|
+
{ async: false, mission: false, workflowScript: `return typeof state;` },
|
|
891
|
+
new AbortController().signal,
|
|
892
|
+
undefined,
|
|
893
|
+
makeMinimalCtx(tempDir),
|
|
894
|
+
);
|
|
895
|
+
assert.equal(ephemeral.isError, undefined, ephemeral.content[0]?.text ?? "ephemeral workflow failed");
|
|
896
|
+
assert.equal(ephemeral.details.workflow?.value, "undefined");
|
|
897
|
+
assert.equal(ephemeral.details.missionId, undefined);
|
|
898
|
+
});
|
|
899
|
+
|
|
605
900
|
it("runs a direct single child in a managed worktree", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
606
901
|
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
607
902
|
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
@@ -610,11 +905,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
610
905
|
execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
|
|
611
906
|
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
612
907
|
mockPi.onCall({ output: "isolated feature", writeFiles: [{ path: "feature.txt", content: "feature\n" }] });
|
|
613
|
-
const executor = makeExecutor([makeAgent("
|
|
908
|
+
const executor = makeExecutor([makeAgent("worker")]);
|
|
614
909
|
|
|
615
910
|
const result = await executor.execute(
|
|
616
911
|
"direct-worktree",
|
|
617
|
-
{ async: false, agent: "
|
|
912
|
+
{ async: false, agent: "worker", task: "Implement feature", worktree: true },
|
|
618
913
|
new AbortController().signal,
|
|
619
914
|
undefined,
|
|
620
915
|
makeMinimalCtx(tempDir),
|
|
@@ -635,6 +930,57 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
635
930
|
|
|
636
931
|
});
|
|
637
932
|
|
|
933
|
+
it("aligns a forked workflow child session with its managed worktree cwd", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
934
|
+
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
935
|
+
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
936
|
+
execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
|
|
937
|
+
fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
|
|
938
|
+
execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
|
|
939
|
+
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
940
|
+
|
|
941
|
+
const parentSessionFile = path.join(mockPi.dir, "parent-session.jsonl");
|
|
942
|
+
const childSessionFile = path.join(mockPi.dir, "forked-child-session.jsonl");
|
|
943
|
+
fs.writeFileSync(parentSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "parent", cwd: tempDir })}\n`, "utf-8");
|
|
944
|
+
const ctx = makeMinimalCtx(tempDir);
|
|
945
|
+
Object.assign(ctx.sessionManager, {
|
|
946
|
+
getSessionFile: () => parentSessionFile,
|
|
947
|
+
getLeafId: () => "parent-leaf",
|
|
948
|
+
openSession: () => ({
|
|
949
|
+
createBranchedSession: () => {
|
|
950
|
+
fs.writeFileSync(childSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "child", cwd: tempDir })}\n`, "utf-8");
|
|
951
|
+
return childSessionFile;
|
|
952
|
+
},
|
|
953
|
+
}),
|
|
954
|
+
});
|
|
955
|
+
mockPi.onCall({ output: "isolated fork child" });
|
|
956
|
+
const executor = makeExecutor([makeAgent("worker", { defaultContext: "fork" })]);
|
|
957
|
+
|
|
958
|
+
const result = await executor.execute(
|
|
959
|
+
"forked-worktree-workflow",
|
|
960
|
+
{ async: false, workflowScript: `return runs.run("isolated", { agent: "worker", task: "Work in isolation", worktree: true });` },
|
|
961
|
+
new AbortController().signal,
|
|
962
|
+
undefined,
|
|
963
|
+
ctx,
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
|
|
967
|
+
const workflowValue = result.details.workflow?.value as { artifactPaths?: string[] } | undefined;
|
|
968
|
+
const handoffPath = workflowValue?.artifactPaths?.find((candidate) => candidate.endsWith(".json") && candidate.includes("handoffs"));
|
|
969
|
+
assert.ok(handoffPath, JSON.stringify(workflowValue));
|
|
970
|
+
const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
|
|
971
|
+
groups: Array<{ cleanup: { tasks: Array<{ path: string }> } }>;
|
|
972
|
+
};
|
|
973
|
+
const managedWorktreeCwd = handoff.groups[0]?.cleanup.tasks[0]?.path;
|
|
974
|
+
assert.ok(managedWorktreeCwd);
|
|
975
|
+
const callCwd = readCall().cwd;
|
|
976
|
+
assert.ok(callCwd);
|
|
977
|
+
assert.notEqual(path.resolve(callCwd), path.resolve(tempDir));
|
|
978
|
+
assert.equal(path.basename(callCwd), path.basename(managedWorktreeCwd));
|
|
979
|
+
const sessionHeader = JSON.parse(fs.readFileSync(childSessionFile, "utf-8").split("\n", 1)[0]!) as { cwd?: string };
|
|
980
|
+
assert.ok(sessionHeader.cwd);
|
|
981
|
+
assert.equal(path.basename(sessionHeader.cwd), path.basename(callCwd));
|
|
982
|
+
});
|
|
983
|
+
|
|
638
984
|
it("lets runs.all siblings settle when one child fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
639
985
|
mockPi.onCall({ exitCode: 1, stderr: "first child failed" });
|
|
640
986
|
mockPi.onCall({ output: "second child completed" });
|
|
@@ -669,6 +1015,88 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
669
1015
|
assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state !== "started").map(({ state }) => state).sort(), ["completed", "failed"]);
|
|
670
1016
|
});
|
|
671
1017
|
|
|
1018
|
+
it("runs a direct child gate as host-verified acceptance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1019
|
+
const markerFile = "direct-gate.txt";
|
|
1020
|
+
const markerPath = path.join(tempDir, markerFile);
|
|
1021
|
+
mockPi.onCall({ output: [
|
|
1022
|
+
"done",
|
|
1023
|
+
"```acceptance-report",
|
|
1024
|
+
JSON.stringify({
|
|
1025
|
+
criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
|
|
1026
|
+
changedFiles: ["src/file.ts"],
|
|
1027
|
+
testsAddedOrUpdated: ["test/file.test.ts"],
|
|
1028
|
+
commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
|
|
1029
|
+
validationOutput: ["tests passed"],
|
|
1030
|
+
residualRisks: [],
|
|
1031
|
+
noStagedFiles: true,
|
|
1032
|
+
}),
|
|
1033
|
+
"```",
|
|
1034
|
+
].join("\n") });
|
|
1035
|
+
const executor = makeExecutor([makeAgent("echo")]);
|
|
1036
|
+
|
|
1037
|
+
const result = await executor.execute(
|
|
1038
|
+
"direct-gate",
|
|
1039
|
+
{ async: false, agent: "echo", task: "Validate the result without edits", gate: `${process.execPath} -e "require('node:fs').writeFileSync('${markerFile}','verified')"` },
|
|
1040
|
+
new AbortController().signal,
|
|
1041
|
+
undefined,
|
|
1042
|
+
makeMinimalCtx(tempDir),
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "direct gate failed");
|
|
1046
|
+
assert.equal(fs.readFileSync(markerPath, "utf-8"), "verified");
|
|
1047
|
+
assert.equal(result.details.results[0]?.acceptance?.status, "verified");
|
|
1048
|
+
assert.equal(result.details.results[0]?.acceptance?.verifyRuns[0]?.id, "gate");
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it("lets runs.all siblings settle when one verified gate fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1052
|
+
const acceptedReport = [
|
|
1053
|
+
"done",
|
|
1054
|
+
"```acceptance-report",
|
|
1055
|
+
JSON.stringify({
|
|
1056
|
+
criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
|
|
1057
|
+
changedFiles: ["src/file.ts"],
|
|
1058
|
+
testsAddedOrUpdated: ["test/file.test.ts"],
|
|
1059
|
+
commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
|
|
1060
|
+
validationOutput: ["tests passed"],
|
|
1061
|
+
residualRisks: [],
|
|
1062
|
+
noStagedFiles: true,
|
|
1063
|
+
}),
|
|
1064
|
+
"```",
|
|
1065
|
+
].join("\n");
|
|
1066
|
+
mockPi.onCall({ output: acceptedReport });
|
|
1067
|
+
mockPi.onCall({ output: acceptedReport });
|
|
1068
|
+
const executor = makeExecutor([makeAgent("echo")]);
|
|
1069
|
+
|
|
1070
|
+
const result = await executor.execute(
|
|
1071
|
+
"scripted-workflow-gates",
|
|
1072
|
+
{
|
|
1073
|
+
async: false,
|
|
1074
|
+
workflowScript: `
|
|
1075
|
+
const children = await runs.all([
|
|
1076
|
+
{ key: "fails-gate", agent: "echo", task: "First task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(7)"`)} },
|
|
1077
|
+
{ key: "passes-gate", agent: "echo", task: "Second task", gate: ${JSON.stringify(`${process.execPath} -e "process.exit(0)"`)} }
|
|
1078
|
+
]);
|
|
1079
|
+
return children.map(({ key, ok }) => ({ key, ok }));
|
|
1080
|
+
`,
|
|
1081
|
+
},
|
|
1082
|
+
new AbortController().signal,
|
|
1083
|
+
undefined,
|
|
1084
|
+
makeMinimalCtx(tempDir),
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
|
|
1088
|
+
assert.equal(mockPi.callCount(), 2);
|
|
1089
|
+
assert.deepEqual(result.details.workflow?.value, [
|
|
1090
|
+
{ key: "fails-gate", ok: false },
|
|
1091
|
+
{ key: "passes-gate", ok: true },
|
|
1092
|
+
]);
|
|
1093
|
+
const [failed, passed] = result.details.results;
|
|
1094
|
+
assert.equal(failed?.acceptance?.status, "rejected");
|
|
1095
|
+
assert.equal(failed?.acceptance?.verifyRuns[0]?.status, "failed");
|
|
1096
|
+
assert.equal(passed?.acceptance?.status, "verified");
|
|
1097
|
+
assert.equal(passed?.acceptance?.verifyRuns[0]?.status, "passed");
|
|
1098
|
+
});
|
|
1099
|
+
|
|
672
1100
|
it("gives parallel workflow children separate managed worktrees and durable handoffs", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
673
1101
|
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
674
1102
|
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
@@ -678,7 +1106,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
678
1106
|
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
679
1107
|
mockPi.onCall({ output: "feature a", writeFiles: [{ path: "feature-a.txt", content: "a\n" }] });
|
|
680
1108
|
mockPi.onCall({ output: "feature b", writeFiles: [{ path: "feature-b.txt", content: "b\n" }] });
|
|
681
|
-
const executor = makeExecutor([makeAgent("
|
|
1109
|
+
const executor = makeExecutor([makeAgent("worker")]);
|
|
682
1110
|
|
|
683
1111
|
const result = await executor.execute(
|
|
684
1112
|
"scripted-workflow-worktrees",
|
|
@@ -686,8 +1114,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
686
1114
|
async: false,
|
|
687
1115
|
workflowScript: `
|
|
688
1116
|
const children = await runs.all([
|
|
689
|
-
{ key: "feature-a", agent: "
|
|
690
|
-
{ key: "feature-b", agent: "
|
|
1117
|
+
{ key: "feature-a", agent: "worker", task: "Implement A", worktree: true },
|
|
1118
|
+
{ key: "feature-b", agent: "worker", task: "Implement B", worktree: true }
|
|
691
1119
|
]);
|
|
692
1120
|
return children.map(({ key, artifactPaths }) => ({ key, artifactPaths }));
|
|
693
1121
|
`,
|
|
@@ -726,6 +1154,87 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
726
1154
|
assert.match(result.content[0]?.text ?? "", /handoffs/);
|
|
727
1155
|
});
|
|
728
1156
|
|
|
1157
|
+
it("preserves a workflow worktree when its child detaches for supervisor coordination", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
1158
|
+
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
1159
|
+
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
1160
|
+
execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
|
|
1161
|
+
fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
|
|
1162
|
+
execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
|
|
1163
|
+
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
1164
|
+
mockPi.onCall({
|
|
1165
|
+
steps: [
|
|
1166
|
+
{ jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
|
|
1167
|
+
{ delay: 500, jsonl: [events.assistantMessage("done after coordination")] },
|
|
1168
|
+
],
|
|
1169
|
+
});
|
|
1170
|
+
const piEvents = createEventBus();
|
|
1171
|
+
const executor = makeExecutor(
|
|
1172
|
+
[makeAgent("worker", { systemPrompt: "Intercom orchestration channel:" })],
|
|
1173
|
+
{},
|
|
1174
|
+
false,
|
|
1175
|
+
undefined,
|
|
1176
|
+
true,
|
|
1177
|
+
new Map(),
|
|
1178
|
+
undefined,
|
|
1179
|
+
undefined,
|
|
1180
|
+
piEvents,
|
|
1181
|
+
);
|
|
1182
|
+
let detachAccepted = false;
|
|
1183
|
+
piEvents.on(INTERCOM_DETACH_RESPONSE_EVENT, (payload) => {
|
|
1184
|
+
if ((payload as { requestId?: unknown }).requestId === "workflow-worktree-detach") {
|
|
1185
|
+
detachAccepted ||= (payload as { accepted?: unknown }).accepted === true;
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
const detachTimer = setInterval(() => {
|
|
1189
|
+
if (!detachAccepted) piEvents.emit(INTERCOM_DETACH_REQUEST_EVENT, { requestId: "workflow-worktree-detach" });
|
|
1190
|
+
}, 10);
|
|
1191
|
+
detachTimer.unref();
|
|
1192
|
+
|
|
1193
|
+
const result = await executor.execute(
|
|
1194
|
+
"scripted-workflow-detached-worktree",
|
|
1195
|
+
{
|
|
1196
|
+
async: false,
|
|
1197
|
+
workflowScript: `
|
|
1198
|
+
const children = await runs.all([
|
|
1199
|
+
{ key: "detaches", agent: "worker", task: "Ask then continue", worktree: true }
|
|
1200
|
+
]);
|
|
1201
|
+
return children.map(({ key, ok, artifactPaths }) => ({ key, ok, artifactPaths }));
|
|
1202
|
+
`,
|
|
1203
|
+
},
|
|
1204
|
+
new AbortController().signal,
|
|
1205
|
+
undefined,
|
|
1206
|
+
makeMinimalCtx(tempDir),
|
|
1207
|
+
);
|
|
1208
|
+
clearInterval(detachTimer);
|
|
1209
|
+
|
|
1210
|
+
assert.equal(detachAccepted, true);
|
|
1211
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
|
|
1212
|
+
assert.match(result.content[0]?.text ?? "", /run detaches: failed/);
|
|
1213
|
+
const workflowValue = result.details.workflow?.value as Array<{ ok: boolean; artifactPaths: string[] }>;
|
|
1214
|
+
assert.equal(workflowValue[0]?.ok, false);
|
|
1215
|
+
const handoffPath = workflowValue[0]?.artifactPaths.find((candidate) => candidate.endsWith(".json"));
|
|
1216
|
+
assert.ok(handoffPath, result.content[0]?.text ?? "missing pending handoff");
|
|
1217
|
+
const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
|
|
1218
|
+
groups: Array<{
|
|
1219
|
+
cleanup: { state: string; tasks: Array<{ path: string; branch: string; preserved: boolean; worktreeRemoved: boolean; branchRemoved: boolean }> };
|
|
1220
|
+
}>;
|
|
1221
|
+
};
|
|
1222
|
+
const cleanup = handoff.groups[0]?.cleanup;
|
|
1223
|
+
assert.equal(cleanup?.state, "partial");
|
|
1224
|
+
assert.equal(cleanup?.tasks[0]?.preserved, true);
|
|
1225
|
+
assert.equal(cleanup?.tasks[0]?.worktreeRemoved, false);
|
|
1226
|
+
assert.equal(cleanup?.tasks[0]?.branchRemoved, false);
|
|
1227
|
+
const worktreePath = cleanup?.tasks[0]?.path;
|
|
1228
|
+
const branch = cleanup?.tasks[0]?.branch;
|
|
1229
|
+
assert.ok(worktreePath);
|
|
1230
|
+
assert.ok(branch);
|
|
1231
|
+
assert.equal(fs.existsSync(worktreePath), true, "live detached worktree must remain present");
|
|
1232
|
+
|
|
1233
|
+
await new Promise((resolve) => setTimeout(resolve, 750));
|
|
1234
|
+
execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd: tempDir });
|
|
1235
|
+
execFileSync("git", ["branch", "-D", branch], { cwd: tempDir, stdio: "ignore" });
|
|
1236
|
+
});
|
|
1237
|
+
|
|
729
1238
|
it("inherits workflow-level worktree isolation and allows a child opt-out", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
730
1239
|
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
731
1240
|
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
@@ -735,7 +1244,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
735
1244
|
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
736
1245
|
mockPi.onCall({ output: "isolated", writeFiles: [{ path: "isolated.txt", content: "isolated\n" }] });
|
|
737
1246
|
mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
|
|
738
|
-
const executor = makeExecutor([makeAgent("
|
|
1247
|
+
const executor = makeExecutor([makeAgent("worker")]);
|
|
739
1248
|
|
|
740
1249
|
const result = await executor.execute(
|
|
741
1250
|
"scripted-workflow-worktree-default",
|
|
@@ -743,8 +1252,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
743
1252
|
async: false,
|
|
744
1253
|
worktree: true,
|
|
745
1254
|
workflowScript: `
|
|
746
|
-
const isolated = await runs.run("isolated", { agent: "
|
|
747
|
-
const shared = await runs.run("shared", { agent: "
|
|
1255
|
+
const isolated = await runs.run("isolated", { agent: "worker", task: "Isolated" });
|
|
1256
|
+
const shared = await runs.run("shared", { agent: "worker", task: "Shared", worktree: false });
|
|
748
1257
|
return { isolated: isolated.artifactPaths, shared: shared.artifactPaths };
|
|
749
1258
|
`,
|
|
750
1259
|
},
|
|
@@ -762,6 +1271,79 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
762
1271
|
assert.equal(fs.existsSync(handoffPaths[0]!), true);
|
|
763
1272
|
});
|
|
764
1273
|
|
|
1274
|
+
it("supports dynamic parallel phases followed by sequential worktree children", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
1275
|
+
execFileSync("git", ["init"], { cwd: tempDir, stdio: "ignore" });
|
|
1276
|
+
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir });
|
|
1277
|
+
execFileSync("git", ["config", "user.name", "Test User"], { cwd: tempDir });
|
|
1278
|
+
fs.writeFileSync(path.join(tempDir, "base.txt"), "base\n", "utf-8");
|
|
1279
|
+
execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
|
|
1280
|
+
execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
|
|
1281
|
+
mockPi.onCall({ output: "api built", writeFiles: [{ path: "api.txt", content: "api\n" }] });
|
|
1282
|
+
mockPi.onCall({ output: "ui built", writeFiles: [{ path: "ui.txt", content: "ui\n" }] });
|
|
1283
|
+
mockPi.onCall({ output: "joined", writeFiles: [{ path: "joined.txt", content: "joined\n" }] });
|
|
1284
|
+
mockPi.onCall({ output: "shared", writeFiles: [{ path: "shared.txt", content: "shared\n" }] });
|
|
1285
|
+
const executor = makeExecutor([makeAgent("worker")]);
|
|
1286
|
+
|
|
1287
|
+
const result = await executor.execute(
|
|
1288
|
+
"scripted-workflow-dynamic-worktree-phases",
|
|
1289
|
+
{
|
|
1290
|
+
async: false,
|
|
1291
|
+
worktree: true,
|
|
1292
|
+
workflowScript: `
|
|
1293
|
+
const targets = ["api", "ui"];
|
|
1294
|
+
const built = await runs.all(targets.map((target) => ({
|
|
1295
|
+
key: "build-" + target,
|
|
1296
|
+
agent: "worker",
|
|
1297
|
+
task: "Build " + target
|
|
1298
|
+
})));
|
|
1299
|
+
const joined = await runs.run("join", { agent: "worker", task: built.map((child) => child.key).join(",") });
|
|
1300
|
+
const shared = await runs.run("shared", { agent: "worker", task: joined.key, worktree: false });
|
|
1301
|
+
return {
|
|
1302
|
+
built: built.map((child) => ({ key: child.key, artifactPaths: child.artifactPaths })),
|
|
1303
|
+
joined: { key: joined.key, artifactPaths: joined.artifactPaths },
|
|
1304
|
+
shared: shared.key
|
|
1305
|
+
};
|
|
1306
|
+
`,
|
|
1307
|
+
},
|
|
1308
|
+
new AbortController().signal,
|
|
1309
|
+
undefined,
|
|
1310
|
+
makeMinimalCtx(tempDir),
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
|
|
1314
|
+
assert.equal(mockPi.callCount(), 4, result.content[0]?.text ?? "workflow produced no output");
|
|
1315
|
+
assert.equal(fs.existsSync(path.join(tempDir, "api.txt")), false);
|
|
1316
|
+
assert.equal(fs.existsSync(path.join(tempDir, "ui.txt")), false);
|
|
1317
|
+
assert.equal(fs.existsSync(path.join(tempDir, "joined.txt")), false);
|
|
1318
|
+
assert.equal(fs.readFileSync(path.join(tempDir, "shared.txt"), "utf-8"), "shared\n");
|
|
1319
|
+
|
|
1320
|
+
const output = result.content[0]?.text ?? "";
|
|
1321
|
+
assert.match(output, /build-api/);
|
|
1322
|
+
assert.match(output, /build-ui/);
|
|
1323
|
+
assert.match(output, /join/);
|
|
1324
|
+
assert.match(output, /shared/);
|
|
1325
|
+
const handoffPaths = [...output.matchAll(/"([^"\n]*\/handoffs\/[^"\n]+\.json)"/g)].map((match) => match[1]!);
|
|
1326
|
+
assert.equal(handoffPaths.length, 3, output);
|
|
1327
|
+
const worktreePaths = new Set<string>();
|
|
1328
|
+
for (const handoffPath of handoffPaths) {
|
|
1329
|
+
const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
|
|
1330
|
+
groups: Array<{
|
|
1331
|
+
children: Array<{ patch: { changed: boolean; path: string } }>;
|
|
1332
|
+
cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> };
|
|
1333
|
+
}>;
|
|
1334
|
+
};
|
|
1335
|
+
assert.equal(handoff.groups.length, 1);
|
|
1336
|
+
assert.equal(handoff.groups[0]?.children[0]?.patch.changed, true);
|
|
1337
|
+
assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
|
|
1338
|
+
assert.equal(handoff.groups[0]?.cleanup.state, "complete");
|
|
1339
|
+
assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.worktreeRemoved, true);
|
|
1340
|
+
assert.equal(handoff.groups[0]?.cleanup.tasks[0]?.branchRemoved, true);
|
|
1341
|
+
worktreePaths.add(handoff.groups[0]!.cleanup.tasks[0]!.path);
|
|
1342
|
+
}
|
|
1343
|
+
assert.equal(worktreePaths.size, 3);
|
|
1344
|
+
for (const worktreePath of worktreePaths) assert.equal(fs.existsSync(worktreePath), false);
|
|
1345
|
+
});
|
|
1346
|
+
|
|
765
1347
|
it("applies a workflow usage budget across scripted child launches", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
766
1348
|
mockPi.onCall({ output: "first result" });
|
|
767
1349
|
const executor = makeExecutor([makeAgent("echo")]);
|
|
@@ -884,11 +1466,27 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
884
1466
|
);
|
|
885
1467
|
|
|
886
1468
|
assert.equal(result.isError, true);
|
|
887
|
-
assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one command)/i);
|
|
1469
|
+
assert.match(result.content[0]?.text ?? "", /(?:verified.*object form|verify.*at least one runtime command)/i);
|
|
888
1470
|
}
|
|
889
1471
|
assert.equal(mockPi.callCount(), 0);
|
|
890
1472
|
});
|
|
891
1473
|
|
|
1474
|
+
it("rejects invalid verified async chain acceptance before spawning", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1475
|
+
const executor = makeExecutor([makeAgent("echo")]);
|
|
1476
|
+
|
|
1477
|
+
const result = await executor.execute(
|
|
1478
|
+
"invalid-verified-async-chain-acceptance",
|
|
1479
|
+
{ chain: [{ agent: "echo", task: "Do work", acceptance: { level: "verified", verify: [] } }], async: true },
|
|
1480
|
+
new AbortController().signal,
|
|
1481
|
+
undefined,
|
|
1482
|
+
makeMinimalCtx(tempDir),
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
assert.equal(result.isError, true);
|
|
1486
|
+
assert.match(result.content[0]?.text ?? "", /verify.*at least one runtime command/i);
|
|
1487
|
+
assert.equal(mockPi.callCount(), 0);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
892
1490
|
it("rejects unknown action strings at runtime", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
893
1491
|
const executor = makeExecutor([makeAgent("echo")]);
|
|
894
1492
|
|
|
@@ -1053,7 +1651,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1053
1651
|
requestId: "registered-a",
|
|
1054
1652
|
ownerRunId: "owner-delegation",
|
|
1055
1653
|
nodeId: "node-a",
|
|
1056
|
-
agent: "
|
|
1654
|
+
agent: "worker",
|
|
1057
1655
|
task: "Return literal JSON-looking text",
|
|
1058
1656
|
context: "fresh",
|
|
1059
1657
|
cwd: tempDir,
|
|
@@ -1065,7 +1663,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1065
1663
|
requestId: "registered-b",
|
|
1066
1664
|
ownerRunId: "owner-delegation",
|
|
1067
1665
|
nodeId: "node-b",
|
|
1068
|
-
agent: "
|
|
1666
|
+
agent: "reviewer",
|
|
1069
1667
|
task: "Run the second logical node",
|
|
1070
1668
|
context: "fresh",
|
|
1071
1669
|
cwd: tempDir,
|
|
@@ -1187,7 +1785,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1187
1785
|
|
|
1188
1786
|
const status = await executor.execute("status", { action: "status" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
|
|
1189
1787
|
|
|
1190
|
-
assert.match(status.content[0]?.text ?? "", /^
|
|
1788
|
+
assert.match(status.content[0]?.text ?? "", /^Status target: active runs\nSpawn budget: 3\/5 used, 2 remaining/);
|
|
1191
1789
|
assert.deepEqual(status.details?.spawnBudget, {
|
|
1192
1790
|
used: 3,
|
|
1193
1791
|
configuredLimit: 4,
|
|
@@ -1409,6 +2007,52 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1409
2007
|
assert.deepEqual(result.details?.totalCost, { inputTokens: 100, outputTokens: 50, costUsd: 0.001 });
|
|
1410
2008
|
});
|
|
1411
2009
|
|
|
2010
|
+
it("ignores stale foreground control notification contexts after reload", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
2011
|
+
mockPi.onCall({
|
|
2012
|
+
jsonl: [
|
|
2013
|
+
events.assistantMessage("first update"),
|
|
2014
|
+
events.assistantMessage("second update"),
|
|
2015
|
+
],
|
|
2016
|
+
});
|
|
2017
|
+
const state: SubagentState = {
|
|
2018
|
+
baseCwd: tempDir,
|
|
2019
|
+
currentSessionId: null,
|
|
2020
|
+
asyncJobs: new Map(),
|
|
2021
|
+
foregroundRuns: new Map(),
|
|
2022
|
+
foregroundControls: new Map(),
|
|
2023
|
+
lastForegroundControlId: null,
|
|
2024
|
+
};
|
|
2025
|
+
const staleEvents = {
|
|
2026
|
+
on: createEventBus().on,
|
|
2027
|
+
emit() { throw new Error("This extension ctx is stale after session replacement or reload."); },
|
|
2028
|
+
};
|
|
2029
|
+
const updates: ExecutorToolResult[] = [];
|
|
2030
|
+
const executor = createSubagentExecutor!({
|
|
2031
|
+
pi: { events: staleEvents, getSessionName: () => undefined },
|
|
2032
|
+
state,
|
|
2033
|
+
config: { control: { enabled: true, activeNoticeAfterTurns: 2, activeNoticeAfterMs: 999_999, activeNoticeAfterTokens: 999_999, notifyOn: ["active_long_running"], notifyChannels: ["event"] } },
|
|
2034
|
+
asyncByDefault: false,
|
|
2035
|
+
tempArtifactsDir: tempDir,
|
|
2036
|
+
getSubagentSessionRoot: () => path.join(tempDir, ".pi-subagents", "sessions"),
|
|
2037
|
+
expandTilde: (value: string) => value,
|
|
2038
|
+
discoverAgents: () => ({ agents: [makeAgent("echo")] }),
|
|
2039
|
+
allowMutatingManagementActions: true,
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
const result = await executor.execute(
|
|
2043
|
+
"stale-control-context",
|
|
2044
|
+
{ agent: "echo", task: "Investigate behavior", async: false },
|
|
2045
|
+
new AbortController().signal,
|
|
2046
|
+
(update: ExecutorToolResult) => updates.push(update),
|
|
2047
|
+
makeMinimalCtx(tempDir),
|
|
2048
|
+
);
|
|
2049
|
+
|
|
2050
|
+
assert.equal(result.isError, undefined, result.content[0]?.text ?? "foreground run failed");
|
|
2051
|
+
assert.equal(result.details.results[0]?.exitCode, 0);
|
|
2052
|
+
const controlEvents = updates.flatMap((update) => update.details?.controlEvents ?? []);
|
|
2053
|
+
assert.equal(controlEvents[0]?.type, "active_long_running");
|
|
2054
|
+
});
|
|
2055
|
+
|
|
1412
2056
|
it("emits resolved model and thinking for nested foreground starts", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1413
2057
|
mockPi.onCall({ output: "nested result" });
|
|
1414
2058
|
const route = createNestedRoute("root-nested-model");
|
|
@@ -1502,10 +2146,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1502
2146
|
|
|
1503
2147
|
it("fails implementation runs that complete without mutation attempts", async () => {
|
|
1504
2148
|
mockPi.onCall({ output: "Validation:\nlet rawFilename = params.filename.trim();" });
|
|
1505
|
-
const agents = [makeAgent("
|
|
2149
|
+
const agents = [makeAgent("worker")];
|
|
1506
2150
|
const controlEvents: Array<{ message: string }> = [];
|
|
1507
2151
|
|
|
1508
|
-
const result = await runSync(tempDir, agents, "
|
|
2152
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
|
|
1509
2153
|
runId: "guard-run",
|
|
1510
2154
|
onControlEvent: (event: { message: string }) => controlEvents.push(event),
|
|
1511
2155
|
});
|
|
@@ -1515,18 +2159,18 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1515
2159
|
assert.equal(result.finalOutput, "Validation:\nlet rawFilename = params.filename.trim();");
|
|
1516
2160
|
assert.equal(result.progress.status, "failed");
|
|
1517
2161
|
assert.deepEqual(controlEvents.map((event) => event.message), [
|
|
1518
|
-
"
|
|
2162
|
+
"worker completed without making edits for an implementation task",
|
|
1519
2163
|
]);
|
|
1520
2164
|
assert.deepEqual(result.controlEvents?.map((event) => event.message), [
|
|
1521
|
-
"
|
|
2165
|
+
"worker completed without making edits for an implementation task",
|
|
1522
2166
|
]);
|
|
1523
2167
|
});
|
|
1524
2168
|
|
|
1525
2169
|
it("agent contract v1 reports omitted acceptance separately without injecting a prompt", async () => {
|
|
1526
2170
|
mockPi.onCall({ output: "Plan only" });
|
|
1527
|
-
const agents = [makeAgent("
|
|
2171
|
+
const agents = [makeAgent("worker", { tools: ["read", "write"] })];
|
|
1528
2172
|
|
|
1529
|
-
const result = await runSync(tempDir, agents, "
|
|
2173
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
|
|
1530
2174
|
runId: "v1-no-acceptance",
|
|
1531
2175
|
agentContract: { version: 1 },
|
|
1532
2176
|
});
|
|
@@ -1543,9 +2187,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1543
2187
|
|
|
1544
2188
|
it("agent contract v1 keeps acceptance rejection out of execution status", async () => {
|
|
1545
2189
|
mockPi.onCall({ output: "Done\n```acceptance-report\n{\"criteriaSatisfied\":[{\"id\":\"criterion-1\",\"status\":\"not-satisfied\",\"evidence\":\"no proof\"}]}\n```" });
|
|
1546
|
-
const agents = [makeAgent("
|
|
2190
|
+
const agents = [makeAgent("worker", { tools: ["read"], completionGuard: false })];
|
|
1547
2191
|
|
|
1548
|
-
const result = await runSync(tempDir, agents, "
|
|
2192
|
+
const result = await runSync(tempDir, agents, "worker", "Summarize the fix", {
|
|
1549
2193
|
runId: "v1-acceptance-reject",
|
|
1550
2194
|
agentContract: { version: 1 },
|
|
1551
2195
|
acceptance: { level: "checked", criteria: ["Return required proof"] },
|
|
@@ -1561,9 +2205,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1561
2205
|
|
|
1562
2206
|
it("agent contract v1 records explicit completion guard as an effect", async () => {
|
|
1563
2207
|
mockPi.onCall({ output: "Plan only" });
|
|
1564
|
-
const agents = [makeAgent("
|
|
2208
|
+
const agents = [makeAgent("worker", { tools: ["read", "write"], completionGuard: true })];
|
|
1565
2209
|
|
|
1566
|
-
const result = await runSync(tempDir, agents, "
|
|
2210
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
|
|
1567
2211
|
runId: "v1-completion-effect",
|
|
1568
2212
|
agentContract: { version: 1 },
|
|
1569
2213
|
});
|
|
@@ -1601,6 +2245,36 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1601
2245
|
if (child?.artifactPaths?.outputPath) assert.match(fs.readFileSync(child.artifactPaths.outputPath, "utf-8"), /"note": "captured"/);
|
|
1602
2246
|
});
|
|
1603
2247
|
|
|
2248
|
+
it("accepts recovered tool errors before valid structured output but rejects later errors", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
2249
|
+
const recoveredError = { type: "tool_result_end", message: { role: "toolResult", toolName: "read", isError: true, content: [{ type: "text", text: "EISDIR" }] } };
|
|
2250
|
+
const structuredEvents = [
|
|
2251
|
+
{ type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
|
|
2252
|
+
{ type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
|
|
2253
|
+
{ type: "tool_execution_end", toolName: "structured_output" },
|
|
2254
|
+
];
|
|
2255
|
+
mockPi.onCall({
|
|
2256
|
+
stdoutRaw: [recoveredError, ...structuredEvents].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
|
|
2257
|
+
structuredOutputCapture: { ok: true },
|
|
2258
|
+
});
|
|
2259
|
+
const executor = makeExecutor([makeAgent("echo")]);
|
|
2260
|
+
const params = { agent: "echo", task: "Return structured data", outputSchema: { type: "object", required: ["ok"], properties: { ok: { type: "boolean" } } }, acceptance: false } as const;
|
|
2261
|
+
|
|
2262
|
+
const recovered = await executor.execute("single-schema-recovered-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
|
|
2263
|
+
|
|
2264
|
+
assert.equal(recovered.isError, undefined);
|
|
2265
|
+
assert.deepEqual(recovered.details?.results?.[0]?.structuredOutput, { ok: true });
|
|
2266
|
+
|
|
2267
|
+
mockPi.reset();
|
|
2268
|
+
mockPi.onCall({
|
|
2269
|
+
stdoutRaw: [...structuredEvents, recoveredError].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
|
|
2270
|
+
structuredOutputCapture: { ok: true },
|
|
2271
|
+
});
|
|
2272
|
+
const terminal = await executor.execute("single-schema-terminal-error", params, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
|
|
2273
|
+
|
|
2274
|
+
assert.equal(terminal.isError, true);
|
|
2275
|
+
assert.match(terminal.details?.results?.[0]?.error ?? "", /read failed/);
|
|
2276
|
+
});
|
|
2277
|
+
|
|
1604
2278
|
it("rejects structured output capture files that were not produced by the structured_output tool", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1605
2279
|
mockPi.onCall({ output: "spoofed", structuredOutputCapture: { ok: true } });
|
|
1606
2280
|
const executor = makeExecutor([makeAgent("echo")]);
|
|
@@ -1682,11 +2356,11 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1682
2356
|
|
|
1683
2357
|
it("returns captured output when the foreground executor fails an implementation run", async () => {
|
|
1684
2358
|
mockPi.onCall({ output: "Oracle review:\n- finding one\n- finding two" });
|
|
1685
|
-
const executor = makeExecutor([makeAgent("
|
|
2359
|
+
const executor = makeExecutor([makeAgent("oracle")]);
|
|
1686
2360
|
|
|
1687
2361
|
const result = await executor.execute(
|
|
1688
2362
|
"failed-single-output",
|
|
1689
|
-
{ agent: "
|
|
2363
|
+
{ agent: "oracle", task: "Implement the approved file changes" },
|
|
1690
2364
|
new AbortController().signal,
|
|
1691
2365
|
undefined,
|
|
1692
2366
|
makeMinimalCtx(tempDir),
|
|
@@ -1701,9 +2375,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1701
2375
|
|
|
1702
2376
|
it("fails future-tense implementation summaries when no mutation attempt occurred", async () => {
|
|
1703
2377
|
mockPi.onCall({ output: "I’ll do that now and report back after implementing." });
|
|
1704
|
-
const agents = [makeAgent("
|
|
2378
|
+
const agents = [makeAgent("worker")];
|
|
1705
2379
|
|
|
1706
|
-
const result = await runSync(tempDir, agents, "
|
|
2380
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
|
|
1707
2381
|
runId: "guard-future-tense",
|
|
1708
2382
|
});
|
|
1709
2383
|
|
|
@@ -1761,9 +2435,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1761
2435
|
events.assistantMessage("Applied edit"),
|
|
1762
2436
|
],
|
|
1763
2437
|
});
|
|
1764
|
-
const agents = [makeAgent("
|
|
2438
|
+
const agents = [makeAgent("worker")];
|
|
1765
2439
|
|
|
1766
|
-
const result = await runSync(tempDir, agents, "
|
|
2440
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved file changes", {
|
|
1767
2441
|
runId: "guard-success",
|
|
1768
2442
|
});
|
|
1769
2443
|
|
|
@@ -1774,12 +2448,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1774
2448
|
|
|
1775
2449
|
it("resolves explicit agent aliases to canonical execution names", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
1776
2450
|
mockPi.onCall({ output: "Implemented" });
|
|
1777
|
-
const executor = makeExecutor([makeAgent("
|
|
2451
|
+
const executor = makeExecutor([makeAgent("worker", { aliases: ["developer"], completionGuard: false })]);
|
|
1778
2452
|
|
|
1779
2453
|
const result = await executor.execute("single", { agent: "developer", task: "Implement" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
|
|
1780
2454
|
|
|
1781
2455
|
assert.equal(result.isError, undefined);
|
|
1782
|
-
assert.equal(result.details?.results[0]?.agent, "
|
|
2456
|
+
assert.equal(result.details?.results[0]?.agent, "worker");
|
|
1783
2457
|
assert.match(result.content[0]?.text ?? "", /Implemented/);
|
|
1784
2458
|
});
|
|
1785
2459
|
|
|
@@ -1832,10 +2506,10 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1832
2506
|
events.assistantMessage("I need to retry the same edit."),
|
|
1833
2507
|
],
|
|
1834
2508
|
});
|
|
1835
|
-
const agents = [makeAgent("
|
|
2509
|
+
const agents = [makeAgent("worker")];
|
|
1836
2510
|
const controlEvents: NonNullable<RunSyncResult["controlEvents"]> = [];
|
|
1837
2511
|
|
|
1838
|
-
const result = await runSync(tempDir, agents, "
|
|
2512
|
+
const result = await runSync(tempDir, agents, "worker", "Implement the approved fixes", {
|
|
1839
2513
|
runId: "run-failures",
|
|
1840
2514
|
controlConfig: { enabled: true, failedToolAttemptsBeforeAttention: 3, notifyOn: ["active_long_running", "needs_attention"] },
|
|
1841
2515
|
onControlEvent: (event: NonNullable<RunSyncResult["controlEvents"]>[number]) => controlEvents.push(event),
|
|
@@ -1884,12 +2558,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1884
2558
|
it("retries a zero-activity startup exit on the same model", async () => {
|
|
1885
2559
|
mockPi.onCall({ exitCode: 1 });
|
|
1886
2560
|
mockPi.onCall({ output: "Recovered after startup race" });
|
|
1887
|
-
const agents = [makeAgent("
|
|
2561
|
+
const agents = [makeAgent("worker", {
|
|
1888
2562
|
model: "openai/gpt-5-mini",
|
|
1889
2563
|
fallbackModels: ["anthropic/claude-sonnet-4"],
|
|
1890
2564
|
})];
|
|
1891
2565
|
|
|
1892
|
-
const result = await runSync(tempDir, agents, "
|
|
2566
|
+
const result = await runSync(tempDir, agents, "worker", "Do work", {
|
|
1893
2567
|
runId: "startup-retry-sync",
|
|
1894
2568
|
acceptance: false,
|
|
1895
2569
|
});
|
|
@@ -1907,12 +2581,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1907
2581
|
it("does not retry a non-zero exit after tool activity", async () => {
|
|
1908
2582
|
mockPi.onCall({ jsonl: [events.toolStart("read", { path: "package.json" })], exitCode: 1 });
|
|
1909
2583
|
mockPi.onCall({ output: "must not run" });
|
|
1910
|
-
const agents = [makeAgent("
|
|
2584
|
+
const agents = [makeAgent("worker", {
|
|
1911
2585
|
model: "openai/gpt-5-mini",
|
|
1912
2586
|
fallbackModels: ["anthropic/claude-sonnet-4"],
|
|
1913
2587
|
})];
|
|
1914
2588
|
|
|
1915
|
-
const result = await runSync(tempDir, agents, "
|
|
2589
|
+
const result = await runSync(tempDir, agents, "worker", "Read a file", {
|
|
1916
2590
|
runId: "startup-no-retry-after-tool",
|
|
1917
2591
|
acceptance: false,
|
|
1918
2592
|
});
|
|
@@ -1925,9 +2599,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1925
2599
|
it("does not retry non-SIGKILL signaled child exits", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
|
|
1926
2600
|
mockPi.onCall({ signal: "SIGTERM" });
|
|
1927
2601
|
mockPi.onCall({ output: "must not run" });
|
|
1928
|
-
const agents = [makeAgent("
|
|
2602
|
+
const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
|
|
1929
2603
|
|
|
1930
|
-
const result = await runSync(tempDir, agents, "
|
|
2604
|
+
const result = await runSync(tempDir, agents, "worker", "Do work", {
|
|
1931
2605
|
runId: "startup-no-retry-after-signal",
|
|
1932
2606
|
acceptance: false,
|
|
1933
2607
|
});
|
|
@@ -1941,9 +2615,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1941
2615
|
|
|
1942
2616
|
it("prefers signal termination errors over stderr tails", { skip: process.platform === "win32" ? "POSIX child signal reporting is unavailable on Windows" : false }, async () => {
|
|
1943
2617
|
mockPi.onCall({ stderr: "INFO benign startup line\n", signal: "SIGTERM" });
|
|
1944
|
-
const agents = [makeAgent("
|
|
2618
|
+
const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
|
|
1945
2619
|
|
|
1946
|
-
const result = await runSync(tempDir, agents, "
|
|
2620
|
+
const result = await runSync(tempDir, agents, "worker", "Do work", {
|
|
1947
2621
|
runId: "signal-error-over-stderr",
|
|
1948
2622
|
acceptance: false,
|
|
1949
2623
|
});
|
|
@@ -1957,9 +2631,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1957
2631
|
it("does not retry a child exit with raw stdout diagnostics", async () => {
|
|
1958
2632
|
mockPi.onCall({ stdoutRaw: "configuration failed before protocol startup\n", exitCode: 1 });
|
|
1959
2633
|
mockPi.onCall({ output: "must not run" });
|
|
1960
|
-
const agents = [makeAgent("
|
|
2634
|
+
const agents = [makeAgent("worker", { model: "openai/gpt-5-mini" })];
|
|
1961
2635
|
|
|
1962
|
-
const result = await runSync(tempDir, agents, "
|
|
2636
|
+
const result = await runSync(tempDir, agents, "worker", "Do work", {
|
|
1963
2637
|
runId: "startup-no-retry-after-stdout-diagnostic",
|
|
1964
2638
|
acceptance: false,
|
|
1965
2639
|
});
|
|
@@ -1972,12 +2646,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
1972
2646
|
|
|
1973
2647
|
it("reports an actionable error after startup retries are exhausted", async () => {
|
|
1974
2648
|
mockPi.onCall({ exitCode: 1 });
|
|
1975
|
-
const agents = [makeAgent("
|
|
2649
|
+
const agents = [makeAgent("worker", {
|
|
1976
2650
|
model: "openai/gpt-5-mini",
|
|
1977
2651
|
fallbackModels: ["anthropic/claude-sonnet-4"],
|
|
1978
2652
|
})];
|
|
1979
2653
|
|
|
1980
|
-
const result = await runSync(tempDir, agents, "
|
|
2654
|
+
const result = await runSync(tempDir, agents, "worker", "Do work", {
|
|
1981
2655
|
runId: "startup-retry-exhausted-sync",
|
|
1982
2656
|
acceptance: false,
|
|
1983
2657
|
});
|
|
@@ -2487,6 +3161,32 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2487
3161
|
assert.ok(result.progress.durationMs > 0, "should track duration");
|
|
2488
3162
|
});
|
|
2489
3163
|
|
|
3164
|
+
it("streams progress while a foreground child has not emitted output", async () => {
|
|
3165
|
+
const updates: Array<{ text: string; durationMs: number | undefined }> = [];
|
|
3166
|
+
const releasePath = path.join(tempDir, "release-foreground-progress");
|
|
3167
|
+
mockPi.onCall({ output: "Done", waitForPath: releasePath });
|
|
3168
|
+
|
|
3169
|
+
const runPromise = runSync(tempDir, makeAgentConfigs(["echo"]), "echo", "Task", {
|
|
3170
|
+
onUpdate: (update: { content: Array<{ type: string; text?: string }>; details?: { progress?: ProgressSummary[] } }) => {
|
|
3171
|
+
updates.push({
|
|
3172
|
+
text: update.content[0]?.text ?? "",
|
|
3173
|
+
durationMs: update.details?.progress?.[0]?.durationMs,
|
|
3174
|
+
});
|
|
3175
|
+
},
|
|
3176
|
+
});
|
|
3177
|
+
const deadline = Date.now() + 5_000;
|
|
3178
|
+
while (updates.filter((update) => update.text === "(running...)").length < 2 && Date.now() < deadline) {
|
|
3179
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
3180
|
+
}
|
|
3181
|
+
fs.writeFileSync(releasePath, "release", "utf-8");
|
|
3182
|
+
const result = await runPromise;
|
|
3183
|
+
|
|
3184
|
+
const runningUpdates = updates.filter((update) => update.text === "(running...)");
|
|
3185
|
+
assert.equal(result.exitCode, 0);
|
|
3186
|
+
assert.ok(runningUpdates.length >= 2, "expected an initial update and a heartbeat before child output");
|
|
3187
|
+
assert.ok((runningUpdates.at(-1)?.durationMs ?? 0) > (runningUpdates[0]?.durationMs ?? 0), "expected heartbeat duration to advance");
|
|
3188
|
+
});
|
|
3189
|
+
|
|
2490
3190
|
it("tracks live activity updates and exposes artifact paths while running", async () => {
|
|
2491
3191
|
const updates: Array<{ details?: { results?: Array<{ artifactPaths?: ArtifactPaths }>; progress?: ProgressSummary[] } }> = [];
|
|
2492
3192
|
mockPi.onCall({
|
|
@@ -2563,9 +3263,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2563
3263
|
events.assistantMessage("Found 2 files: file1.txt and file2.txt"),
|
|
2564
3264
|
],
|
|
2565
3265
|
});
|
|
2566
|
-
const agents = makeAgentConfigs(["
|
|
3266
|
+
const agents = makeAgentConfigs(["scout"]);
|
|
2567
3267
|
|
|
2568
|
-
const result = await runSync(tempDir, agents, "
|
|
3268
|
+
const result = await runSync(tempDir, agents, "scout", "List files", {});
|
|
2569
3269
|
|
|
2570
3270
|
assert.equal(result.exitCode, 0);
|
|
2571
3271
|
const output = getFinalOutput(result.messages);
|
|
@@ -2592,13 +3292,13 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2592
3292
|
|
|
2593
3293
|
it("injects an agent-file-relative local skill into the foreground child prompt", async () => {
|
|
2594
3294
|
mockPi.onCall({ output: "Done" });
|
|
2595
|
-
const agentFile = path.join(tempDir, "agents", "nested", "
|
|
3295
|
+
const agentFile = path.join(tempDir, "agents", "nested", "worker.md");
|
|
2596
3296
|
const skillFile = path.join(path.dirname(agentFile), "skills", "local", "SKILL.md");
|
|
2597
3297
|
fs.mkdirSync(path.dirname(skillFile), { recursive: true });
|
|
2598
3298
|
fs.writeFileSync(skillFile, "---\ndescription: local skill description\n---\nLocal skill body\n", "utf-8");
|
|
2599
|
-
const agents = [makeAgent("
|
|
3299
|
+
const agents = [makeAgent("worker", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] })];
|
|
2600
3300
|
|
|
2601
|
-
const result = await runSync(tempDir, agents, "
|
|
3301
|
+
const result = await runSync(tempDir, agents, "worker", "Task", {});
|
|
2602
3302
|
|
|
2603
3303
|
assert.equal(result.exitCode, 0);
|
|
2604
3304
|
assert.deepEqual(result.skills, ["local"]);
|
|
@@ -2622,9 +3322,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2622
3322
|
});
|
|
2623
3323
|
|
|
2624
3324
|
it("fails foreground runs on explicit unavailable pi-subagents skill requests without spawning", async () => {
|
|
2625
|
-
const agents = [makeAgent("
|
|
3325
|
+
const agents = [makeAgent("worker")];
|
|
2626
3326
|
|
|
2627
|
-
const result = await runSync(tempDir, agents, "
|
|
3327
|
+
const result = await runSync(tempDir, agents, "worker", "Task", { skills: ["pi-subagents"] });
|
|
2628
3328
|
|
|
2629
3329
|
assert.equal(result.exitCode, 1);
|
|
2630
3330
|
assert.equal(result.error, "Skills not found: pi-subagents");
|
|
@@ -2632,9 +3332,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2632
3332
|
});
|
|
2633
3333
|
|
|
2634
3334
|
it("fails foreground runs when an agent default requests pi-subagents skill", async () => {
|
|
2635
|
-
const agents = [makeAgent("
|
|
3335
|
+
const agents = [makeAgent("worker", { skills: ["pi-subagents"] })];
|
|
2636
3336
|
|
|
2637
|
-
const result = await runSync(tempDir, agents, "
|
|
3337
|
+
const result = await runSync(tempDir, agents, "worker", "Task", {});
|
|
2638
3338
|
|
|
2639
3339
|
assert.equal(result.exitCode, 1);
|
|
2640
3340
|
assert.equal(result.error, "Skills not found: pi-subagents");
|
|
@@ -2806,6 +3506,28 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
2806
3506
|
assert.equal(fs.readFileSync(outputPath, "utf-8"), "fresh assistant output");
|
|
2807
3507
|
});
|
|
2808
3508
|
|
|
3509
|
+
it("top-level reviewer runs do not inherit bundled chain artifact reads", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
3510
|
+
fs.writeFileSync(path.join(tempDir, "plan.md"), "chain plan");
|
|
3511
|
+
fs.writeFileSync(path.join(tempDir, "progress.md"), "chain progress");
|
|
3512
|
+
mockPi.onCall({ output: "Review done" });
|
|
3513
|
+
const reviewer = discoverAgents(tempDir, "project").agents.find((agent) => agent.name === "reviewer");
|
|
3514
|
+
assert.ok(reviewer, "expected bundled reviewer");
|
|
3515
|
+
assert.equal(reviewer.defaultReads, undefined);
|
|
3516
|
+
const executor = makeExecutor([reviewer]);
|
|
3517
|
+
|
|
3518
|
+
await executor.execute(
|
|
3519
|
+
"single-reviewer-without-chain-artifacts",
|
|
3520
|
+
{ agent: "reviewer", task: "Review the supplied files." },
|
|
3521
|
+
new AbortController().signal,
|
|
3522
|
+
undefined,
|
|
3523
|
+
makeMinimalCtx(tempDir),
|
|
3524
|
+
);
|
|
3525
|
+
|
|
3526
|
+
const taskArg = readCallArgs().at(-1) ?? "";
|
|
3527
|
+
assert.doesNotMatch(taskArg, /\[Read from:/);
|
|
3528
|
+
assert.doesNotMatch(taskArg, /plan\.md|progress\.md/);
|
|
3529
|
+
});
|
|
3530
|
+
|
|
2809
3531
|
it("routes foreground single relative outputs to the run output artifact directory by default", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
2810
3532
|
mockPi.onCall({ output: "default report" });
|
|
2811
3533
|
const executor = makeExecutor([makeAgent("researcher", { output: "context.md" })]);
|
|
@@ -3310,8 +4032,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3310
4032
|
});
|
|
3311
4033
|
|
|
3312
4034
|
mockPi.onCall({ echoEnv: envKeys });
|
|
3313
|
-
const nonFanoutAgents = [makeAgent("
|
|
3314
|
-
const nonFanout = await runSync(tempDir, nonFanoutAgents, "
|
|
4035
|
+
const nonFanoutAgents = [makeAgent("worker", { tools: ["read"] })];
|
|
4036
|
+
const nonFanout = await runSync(tempDir, nonFanoutAgents, "worker", "Task", { runId: "non-fanout-run" });
|
|
3315
4037
|
assert.equal(nonFanout.exitCode, 0);
|
|
3316
4038
|
assert.deepEqual(JSON.parse(nonFanout.finalOutput ?? "{}"), {
|
|
3317
4039
|
SELESAI_SUBAGENT_FANOUT_CHILD: "0",
|
|
@@ -3357,9 +4079,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3357
4079
|
|
|
3358
4080
|
it("fails with actionable diagnostics when a requested extension tool is not loaded", async () => {
|
|
3359
4081
|
mockPi.onCall({ output: "Model incorrectly claimed success", missingTools: ["fixture_search"] });
|
|
3360
|
-
const agents = [makeAgent("extension-
|
|
4082
|
+
const agents = [makeAgent("extension-worker", { tools: ["read", "fixture_search"], fallbackModels: ["mock/fallback-model"] })];
|
|
3361
4083
|
|
|
3362
|
-
const result = await runSync(tempDir, agents, "extension-
|
|
4084
|
+
const result = await runSync(tempDir, agents, "extension-worker", "Use fixture search", { runId: "missing-extension-tool" });
|
|
3363
4085
|
|
|
3364
4086
|
assert.equal(result.exitCode, 1);
|
|
3365
4087
|
assert.match(result.error ?? "", /requested unavailable child tools: fixture_search/);
|
|
@@ -3585,9 +4307,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3585
4307
|
mockAssistantMessage("final wrapped output", "stop"),
|
|
3586
4308
|
],
|
|
3587
4309
|
});
|
|
3588
|
-
const agents = makeAgentConfigs(["
|
|
4310
|
+
const agents = makeAgentConfigs(["worker"]);
|
|
3589
4311
|
|
|
3590
|
-
const result = await runSync(tempDir, agents, "
|
|
4312
|
+
const result = await runSync(tempDir, agents, "worker", "Use the final grace turn to wrap up.", {
|
|
3591
4313
|
turnBudget: { maxTurns: 1, graceTurns: 1 },
|
|
3592
4314
|
runId: "foreground-turn-budget-soft",
|
|
3593
4315
|
});
|
|
@@ -3620,7 +4342,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3620
4342
|
},
|
|
3621
4343
|
],
|
|
3622
4344
|
});
|
|
3623
|
-
const agents = makeAgentConfigs(["
|
|
4345
|
+
const agents = makeAgentConfigs(["worker"]);
|
|
3624
4346
|
const snapshots: Array<{
|
|
3625
4347
|
turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number };
|
|
3626
4348
|
turnBudgetExceeded?: boolean;
|
|
@@ -3629,7 +4351,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
|
|
|
3629
4351
|
status?: string;
|
|
3630
4352
|
}> = [];
|
|
3631
4353
|
|
|
3632
|
-
const result = await runSync(tempDir, agents, "
|
|
4354
|
+
const result = await runSync(tempDir, agents, "worker", "Finish active tool work before enforcing the hard limit.", {
|
|
3633
4355
|
turnBudget: { maxTurns: 1, graceTurns: 0 },
|
|
3634
4356
|
runId: "foreground-turn-budget-deferred",
|
|
3635
4357
|
onUpdate(update: { details?: { results?: Array<{ turnBudget?: { outcome?: string; terminationDeferredAtTurn?: number }; turnBudgetExceeded?: boolean; error?: string }>; progress?: Array<{ currentTool?: string; status?: string }> } }) {
|