@selesai/code 0.8.3 → 0.8.5
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 +7 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/defaults/models.json +41 -55
- package/dist/defaults/settings.json +7 -8
- 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 +14 -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 -317
- package/dist/extensions/pi-powerline-footer/package.json +1 -1
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -12
- 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/git-optional-locks.test.ts +64 -0
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +116 -0
- 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/types.ts +1 -2
- package/dist/extensions/pi-subagents/CHANGELOG.md +225 -14
- package/dist/extensions/pi-subagents/README.md +66 -1613
- 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 +372 -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 +38 -19
- 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 +62 -8
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +2 -2
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +51 -26
- package/dist/extensions/pi-subagents/src/extension/index.ts +140 -50
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +77 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +18 -24
- 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/intercom-bridge.ts +4 -1
- 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 -17
- package/dist/extensions/pi-subagents/src/missions/store.ts +151 -9
- package/dist/extensions/pi-subagents/src/missions/types.ts +58 -3
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +260 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -8
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +46 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +158 -36
- 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 +26 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -6
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
- 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/owned-process-tree.ts +104 -0
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
- 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 +26 -3
- 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 +233 -101
- 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 +40 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +188 -46
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +137 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1365 -252
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +178 -26
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
- 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 +32 -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 +3 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +509 -117
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +10 -10
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
- 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 +21 -5
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +20 -8
- 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 +192 -3
- package/dist/extensions/pi-subagents/src/shared/utf8.ts +11 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +46 -34
- 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 +37 -21
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +375 -42
- 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 +332 -27
- 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 +102 -15
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +507 -306
- 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 +124 -58
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +62 -16
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +167 -113
- 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 +139 -58
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +324 -59
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +98 -22
- 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 +1122 -96
- 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 +194 -59
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
- 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 +118 -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 +18 -3
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +99 -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 +94 -23
- 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 +70 -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 +30 -2
- 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 +91 -47
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +468 -38
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +14 -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 +7 -6
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
- 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 +81 -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 +42 -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/owned-process-tree.test.ts +69 -0
- 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 +440 -92
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +24 -8
- 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 +59 -6
- 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 +51 -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 +111 -31
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
- 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 +65 -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 +60 -44
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +424 -49
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
- 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 +130 -92
- 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 +65 -26
- package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +138 -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 +68 -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/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +48 -1
- 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/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/quickstart.md +27 -43
- package/docs/settings.md +4 -0
- package/docs/usage.md +1 -0
- package/package.json +2 -1
- 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
|
@@ -25,6 +25,7 @@ import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceil
|
|
|
25
25
|
import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
|
|
26
26
|
import { discoverAgents } from "../../src/agents/agents.ts";
|
|
27
27
|
import { runSync } from "../../src/runs/foreground/execution.ts";
|
|
28
|
+
import { ACTIVE_ASYNC_CAPACITY_DIR, acquireActiveAsyncCapacity, activeAsyncCapacitySessionKey } from "../../src/runs/background/active-async-capacity.ts";
|
|
28
29
|
|
|
29
30
|
interface LaunchResolvedExtensions {
|
|
30
31
|
version?: number;
|
|
@@ -80,7 +81,7 @@ interface AsyncResultPayload {
|
|
|
80
81
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
81
82
|
usageBudget?: UsageBudgetState;
|
|
82
83
|
checkpoint?: { name?: string; status?: string };
|
|
83
|
-
results: Array<{ agent?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
|
|
84
|
+
results: Array<{ agent?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; output?: string; outputState?: "present" | "absent" | "unknown"; success?: boolean; error?: string; protocolError?: { code?: string; stream?: string; limitBytes?: number; observedBytes?: number }; timedOut?: boolean; stopped?: boolean; turnBudget?: { maxTurns: number; graceTurns: number; outcome: string; turnCount: number; wrapUpRequestedAtTurn?: number; terminationDeferredAtTurn?: number; exceededAtTurn?: number }; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean; model?: string; attemptedModels?: string[]; modelAttempts?: Array<{ success?: boolean; error?: string }>; totalCost?: { inputTokens: number; outputTokens: number; costUsd: number }; structuredOutput?: unknown; agentContract?: { version: 1 }; execution?: { status?: string; success?: boolean; exitCode?: number }; effects?: { fileMutation?: { status?: string; expected?: boolean; attempted?: boolean } }; intercomTarget?: string; acceptance?: { status?: string; effectiveAcceptance?: { level?: string }; childReport?: unknown; runtimeChecks?: Array<{ id?: string; status?: string; message?: string }> }; artifactPaths?: { outputPath?: string; inputPath?: string; metadataPath?: string; transcriptPath?: string }; outputSaveError?: string; metadataSaveError?: string; capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] }; capabilityAudit?: { effectiveTools?: string[]; removedTools?: string[]; extensionsDenied?: boolean } }>;
|
|
84
85
|
outputs?: Record<string, { text?: string; structured?: unknown }>;
|
|
85
86
|
workflowGraph?: { nodes?: Array<{ kind?: string; label?: string; phase?: string; status?: string; acceptanceStatus?: string; error?: string; outputName?: string; structured?: boolean; children?: Array<{ label?: string; outputName?: string; itemKey?: string; status?: string; acceptanceStatus?: string; error?: string }> }> };
|
|
86
87
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
@@ -96,6 +97,7 @@ interface AsyncStatusPayload {
|
|
|
96
97
|
currentTool?: string;
|
|
97
98
|
currentPath?: string;
|
|
98
99
|
state?: string;
|
|
100
|
+
endedAt?: number;
|
|
99
101
|
launchContractDigest?: string;
|
|
100
102
|
launchResolvedExtensions?: LaunchResolvedExtensions;
|
|
101
103
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions;
|
|
@@ -191,7 +193,7 @@ function childWatchdogStatus(runId: string, phase: "idle" | "reviewing" | "autof
|
|
|
191
193
|
return {
|
|
192
194
|
type: CHILD_WATCHDOG_STATUS_EVENT,
|
|
193
195
|
runId,
|
|
194
|
-
agent: "
|
|
196
|
+
agent: "worker",
|
|
195
197
|
childIndex: 0,
|
|
196
198
|
stepIndex: 0,
|
|
197
199
|
seq,
|
|
@@ -230,6 +232,7 @@ interface AsyncExecutionModule {
|
|
|
230
232
|
|
|
231
233
|
interface UtilsModule {
|
|
232
234
|
readStatus(dir: string): { runId: string; state: string; mode: string } | null;
|
|
235
|
+
pruneStatusCacheForAsyncRoot(root: string, runIds: Iterable<string>): number;
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
interface TypesModule {
|
|
@@ -254,6 +257,7 @@ const isAsyncAvailable = asyncMod?.isAsyncAvailable;
|
|
|
254
257
|
const executeAsyncSingle = asyncMod?.executeAsyncSingle;
|
|
255
258
|
const executeAsyncChain = asyncMod?.executeAsyncChain;
|
|
256
259
|
const readStatus = utils?.readStatus;
|
|
260
|
+
const pruneStatusCacheForAsyncRoot = utils?.pruneStatusCacheForAsyncRoot;
|
|
257
261
|
const ASYNC_DIR = typesMod?.ASYNC_DIR;
|
|
258
262
|
const RESULTS_DIR = typesMod?.RESULTS_DIR;
|
|
259
263
|
const TEMP_ROOT_DIR = typesMod?.TEMP_ROOT_DIR;
|
|
@@ -455,9 +459,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
455
459
|
|
|
456
460
|
function launchProtocolTest(id: string): void {
|
|
457
461
|
executeAsyncSingle(id, {
|
|
458
|
-
agent: "
|
|
462
|
+
agent: "worker",
|
|
459
463
|
task: "Exercise child protocol",
|
|
460
|
-
agentConfig: makeAgent("
|
|
464
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
461
465
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
462
466
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
463
467
|
shareEnabled: false,
|
|
@@ -483,6 +487,16 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
483
487
|
assert.equal(payload.results[0]?.output, "你好 from fragmented async JSON");
|
|
484
488
|
});
|
|
485
489
|
|
|
490
|
+
it("persists terminal status before creating the result artifact", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
491
|
+
mockPi.onCall({ output: "completed output" });
|
|
492
|
+
const id = `async-terminal-status-${Date.now().toString(36)}`;
|
|
493
|
+
launchProtocolTest(id);
|
|
494
|
+
await waitForAsyncResultFile(id);
|
|
495
|
+
const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
496
|
+
assert.equal(status.state, "complete");
|
|
497
|
+
assert.equal(status.endedAt !== undefined, true);
|
|
498
|
+
});
|
|
499
|
+
|
|
486
500
|
it("persists absent output provenance when async lifecycle text is synthetic", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
487
501
|
mockPi.onCall({ jsonl: [mockAssistantMessage("", "tool_use")], stderr: "mock child failure", exitCode: 1 });
|
|
488
502
|
const id = `async-output-absent-${Date.now().toString(36)}`;
|
|
@@ -504,12 +518,12 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
504
518
|
});
|
|
505
519
|
|
|
506
520
|
it("matches preflight launch digest in equivalent foreground and async execution", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
507
|
-
const agentName = `contract-
|
|
521
|
+
const agentName = `contract-worker-${Date.now().toString(36)}`;
|
|
508
522
|
const task = "Compare the resolved launch inputs.";
|
|
509
523
|
const turnBudget = { maxTurns: 2, graceTurns: 1 } as const;
|
|
510
524
|
const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
|
|
511
525
|
fs.mkdirSync(path.dirname(agentPath), { recursive: true });
|
|
512
|
-
fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison
|
|
526
|
+
fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison worker\n---\n`, "utf-8");
|
|
513
527
|
const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
|
|
514
528
|
assert.ok(discovered, "expected temporary agent definition to be discovered");
|
|
515
529
|
const preflight = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, turnBudget, runId: "contract-preflight" });
|
|
@@ -548,9 +562,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
548
562
|
const id = `async-launch-digest-${Date.now().toString(36)}`;
|
|
549
563
|
const privateExtension = path.join(tempDir, "extensions", "private-extension.ts");
|
|
550
564
|
const launch = executeAsyncSingle(id, {
|
|
551
|
-
agent: "
|
|
565
|
+
agent: "worker",
|
|
552
566
|
task: "Exercise launch digest reporting",
|
|
553
|
-
agentConfig: makeAgent("
|
|
567
|
+
agentConfig: makeAgent("worker", { completionGuard: false, extensions: [privateExtension] }),
|
|
554
568
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
555
569
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
556
570
|
shareEnabled: false,
|
|
@@ -559,8 +573,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
559
573
|
acceptance: false,
|
|
560
574
|
});
|
|
561
575
|
assert.match(launch.details.launchContractDigest ?? "", /^[a-f0-9]{64}$/);
|
|
576
|
+
const recovery = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "recovery-descriptor.json"), "utf-8")) as { runFanoutBudget?: { rootRunId?: string; limit?: number } };
|
|
577
|
+
assert.deepEqual(recovery.runFanoutBudget && { rootRunId: recovery.runFanoutBudget.rootRunId, limit: recovery.runFanoutBudget.limit }, { rootRunId: id, limit: 64 });
|
|
562
578
|
const payload = await readAsyncPayload(id);
|
|
563
|
-
const status =
|
|
579
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete" && candidate.runtimeAcknowledgedExtensions !== undefined);
|
|
564
580
|
assert.equal(payload.launchContractDigest, launch.details.launchContractDigest);
|
|
565
581
|
assert.equal(payload.results[0]?.launchContractDigest, launch.details.launchContractDigest);
|
|
566
582
|
assert.equal(status.launchContractDigest, launch.details.launchContractDigest);
|
|
@@ -612,7 +628,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
612
628
|
|
|
613
629
|
it("foreground checkpoint decisions use current-session control actions", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
|
|
614
630
|
mockPi.onCall({ output: "analysis done" });
|
|
615
|
-
const executor = makeAsyncExecutor([makeAgent("analyst"), makeAgent("
|
|
631
|
+
const executor = makeAsyncExecutor([makeAgent("analyst"), makeAgent("worker")]);
|
|
616
632
|
const ctx = makeMinimalCtx(tempDir);
|
|
617
633
|
ctx.sessionManager.getSessionId = () => "session-foreground-checkpoint";
|
|
618
634
|
const id = `foreground-checkpoint-${Date.now().toString(36)}`;
|
|
@@ -621,7 +637,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
621
637
|
chain: [
|
|
622
638
|
{ agent: "analyst", task: "Analyze" },
|
|
623
639
|
{ checkpoint: "review", message: "Approve implementation?" },
|
|
624
|
-
{ agent: "
|
|
640
|
+
{ agent: "worker", task: "Implement" },
|
|
625
641
|
],
|
|
626
642
|
}, new AbortController().signal, undefined, ctx) as AsyncExecutionResult;
|
|
627
643
|
|
|
@@ -646,9 +662,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
646
662
|
chain: [
|
|
647
663
|
{ agent: "analyst", task: "Analyze" },
|
|
648
664
|
{ checkpoint: "review" },
|
|
649
|
-
{ agent: "
|
|
665
|
+
{ agent: "worker", task: "Implement" },
|
|
650
666
|
],
|
|
651
|
-
agents: [makeAgent("analyst"), makeAgent("
|
|
667
|
+
agents: [makeAgent("analyst"), makeAgent("worker")],
|
|
652
668
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
653
669
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
654
670
|
shareEnabled: false,
|
|
@@ -717,11 +733,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
717
733
|
const sessionFile = path.join(tempDir, "sessions", "parent-session", "session.jsonl");
|
|
718
734
|
const ctx = makeMinimalCtx(tempDir);
|
|
719
735
|
ctx.sessionManager.getSessionFile = () => sessionFile;
|
|
720
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
736
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })], { artifactDir: "session" });
|
|
721
737
|
|
|
722
738
|
const launch = await executor.execute(
|
|
723
739
|
"async-session-artifact-dir",
|
|
724
|
-
{ agent: "
|
|
740
|
+
{ agent: "worker", task: "Write async session artifacts", async: true, runId: "async-session-artifacts", acceptance: false },
|
|
725
741
|
new AbortController().signal,
|
|
726
742
|
undefined,
|
|
727
743
|
ctx,
|
|
@@ -746,13 +762,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
746
762
|
const sessionId = `session-capability-${Date.now().toString(36)}`;
|
|
747
763
|
const handle = registerSubagentCapabilityCeiling({ sessionId, ceiling: { allowedTools: ["read"], denyExtensions: true }, source: "test" });
|
|
748
764
|
try {
|
|
749
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
765
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { tools: ["read", "write"], completionGuard: false })]);
|
|
750
766
|
const id = `async-capability-${Date.now().toString(36)}`;
|
|
751
767
|
const ctx = makeMinimalCtx(tempDir);
|
|
752
768
|
ctx.sessionManager.getSessionId = () => sessionId;
|
|
753
769
|
const launch = await executor.execute(
|
|
754
770
|
id,
|
|
755
|
-
{ agent: "
|
|
771
|
+
{ agent: "worker", task: "Run with a restricted capability ceiling", async: true, runId: id, acceptance: false, artifacts: true },
|
|
756
772
|
new AbortController().signal,
|
|
757
773
|
undefined,
|
|
758
774
|
ctx,
|
|
@@ -783,13 +799,51 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
783
799
|
}
|
|
784
800
|
});
|
|
785
801
|
|
|
802
|
+
it("redacts async task text from durable artifacts, transcripts, and metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
803
|
+
mockPi.onCall({ output: "async redaction complete" });
|
|
804
|
+
const id = `async-prompt-redaction-${Date.now().toString(36)}`;
|
|
805
|
+
const sentinel = "ASYNC_RAW_PROMPT_SENTINEL_1021";
|
|
806
|
+
executeAsyncSingle(id, {
|
|
807
|
+
agent: "worker",
|
|
808
|
+
task: `Handle ${sentinel} without persisting it`,
|
|
809
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
810
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
811
|
+
artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeTranscript: true, includeMetadata: true, cleanupDays: 7 },
|
|
812
|
+
artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
|
|
813
|
+
shareEnabled: false,
|
|
814
|
+
maxSubagentDepth: 2,
|
|
815
|
+
acceptance: false,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
await waitForMockPiCall(mockPi, 0);
|
|
819
|
+
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.endsWith(".json"));
|
|
820
|
+
assert.ok(callFile);
|
|
821
|
+
const call = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as { args?: string[] };
|
|
822
|
+
assert.match(call.args?.join("\n") ?? "", new RegExp(sentinel));
|
|
823
|
+
|
|
824
|
+
const payload = await readAsyncPayload(id);
|
|
825
|
+
const artifactPaths = payload.results[0]?.artifactPaths;
|
|
826
|
+
assert.ok(artifactPaths?.inputPath);
|
|
827
|
+
assert.ok(artifactPaths.metadataPath);
|
|
828
|
+
assert.ok(artifactPaths.transcriptPath);
|
|
829
|
+
const inputText = fs.readFileSync(artifactPaths.inputPath, "utf-8");
|
|
830
|
+
const transcriptText = fs.readFileSync(artifactPaths.transcriptPath, "utf-8");
|
|
831
|
+
const metadataText = fs.readFileSync(artifactPaths.metadataPath, "utf-8");
|
|
832
|
+
assert.doesNotMatch(inputText, new RegExp(sentinel));
|
|
833
|
+
assert.doesNotMatch(transcriptText, new RegExp(sentinel));
|
|
834
|
+
assert.doesNotMatch(metadataText, new RegExp(sentinel));
|
|
835
|
+
assert.match(inputText, /\[prompt redacted\]/);
|
|
836
|
+
assert.match(transcriptText, /\[prompt redacted\]/);
|
|
837
|
+
assert.equal((JSON.parse(metadataText) as { task?: string }).task, "[prompt redacted]");
|
|
838
|
+
});
|
|
839
|
+
|
|
786
840
|
it("background writes a failure stub to output artifacts when no output was produced", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
787
841
|
mockPi.onCall({ output: "", stderr: "model unavailable", exitCode: 1 });
|
|
788
842
|
const id = `async-empty-failure-artifact-${Date.now().toString(36)}`;
|
|
789
843
|
executeAsyncSingle(id, {
|
|
790
|
-
agent: "
|
|
844
|
+
agent: "worker",
|
|
791
845
|
task: "Fail before output",
|
|
792
|
-
agentConfig: makeAgent("
|
|
846
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
793
847
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
794
848
|
artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeMetadata: true, cleanupDays: 7 },
|
|
795
849
|
artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
|
|
@@ -814,9 +868,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
814
868
|
const id = `async-artifact-dir-recovery-${Date.now().toString(36)}`;
|
|
815
869
|
const artifactsDir = path.join(tempDir, ".pi-subagents", "artifacts");
|
|
816
870
|
executeAsyncSingle(id, {
|
|
817
|
-
agent: "
|
|
871
|
+
agent: "worker",
|
|
818
872
|
task: "Complete after generated artifacts are cleaned",
|
|
819
|
-
agentConfig: makeAgent("
|
|
873
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
820
874
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
821
875
|
artifactConfig: { enabled: true, includeInput: true, includeOutput: true, includeJsonl: false, includeMetadata: true, cleanupDays: 7 },
|
|
822
876
|
artifactsDir,
|
|
@@ -899,9 +953,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
899
953
|
mockPi.onCall({ output: "OK" });
|
|
900
954
|
const id = `async-single-literal-output-ref-${Date.now().toString(36)}`;
|
|
901
955
|
const result = executeAsyncSingle(id, {
|
|
902
|
-
agent: "
|
|
956
|
+
agent: "worker",
|
|
903
957
|
task,
|
|
904
|
-
agentConfig: makeAgent("
|
|
958
|
+
agentConfig: makeAgent("worker"),
|
|
905
959
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
906
960
|
artifactConfig: {
|
|
907
961
|
enabled: false,
|
|
@@ -931,9 +985,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
931
985
|
mockPi.onCall({ output: "non-node exec async done" });
|
|
932
986
|
const id = `async-non-node-exec-${Date.now().toString(36)}`;
|
|
933
987
|
const result = executeAsyncSingle(id, {
|
|
934
|
-
agent: "
|
|
988
|
+
agent: "worker",
|
|
935
989
|
task: "Say non-node exec async done. Do not edit files.",
|
|
936
|
-
agentConfig: makeAgent("
|
|
990
|
+
agentConfig: makeAgent("worker"),
|
|
937
991
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
938
992
|
artifactConfig: {
|
|
939
993
|
enabled: false,
|
|
@@ -965,9 +1019,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
965
1019
|
mockPi.onCall({ output: "stale node exec async done" });
|
|
966
1020
|
const id = `async-stale-node-exec-${Date.now().toString(36)}`;
|
|
967
1021
|
const result = executeAsyncSingle(id, {
|
|
968
|
-
agent: "
|
|
1022
|
+
agent: "worker",
|
|
969
1023
|
task: "Say stale node exec async done. Do not edit files.",
|
|
970
|
-
agentConfig: makeAgent("
|
|
1024
|
+
agentConfig: makeAgent("worker"),
|
|
971
1025
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
972
1026
|
artifactConfig: {
|
|
973
1027
|
enabled: false,
|
|
@@ -1095,7 +1149,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1095
1149
|
await waitForMockPiCall(mockPi, 1, 10_000);
|
|
1096
1150
|
const resultPath = await waitForAsyncResultFile(id, 8_000);
|
|
1097
1151
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1098
|
-
const status =
|
|
1152
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1099
1153
|
assert.equal(payload.state, "failed");
|
|
1100
1154
|
assert.equal(payload.success, false);
|
|
1101
1155
|
assert.equal(payload.exitCode, 1);
|
|
@@ -1113,6 +1167,69 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1113
1167
|
assert.equal(mockPi.callCount(), 2);
|
|
1114
1168
|
});
|
|
1115
1169
|
|
|
1170
|
+
it("enforces an agent-level timeout on an async serial child without a composite deadline", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1171
|
+
mockPi.onCall({ delay: 5_000, output: "too late" });
|
|
1172
|
+
const id = `async-child-timeout-chain-${Date.now().toString(36)}`;
|
|
1173
|
+
executeAsyncChain(id, {
|
|
1174
|
+
chain: [{ agent: "slow", task: "Wait" }],
|
|
1175
|
+
agents: [makeAgent("slow", { defaultTimeoutMs: 150 })],
|
|
1176
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1177
|
+
artifactConfig: {
|
|
1178
|
+
enabled: false,
|
|
1179
|
+
includeInput: false,
|
|
1180
|
+
includeOutput: false,
|
|
1181
|
+
includeJsonl: false,
|
|
1182
|
+
includeMetadata: false,
|
|
1183
|
+
cleanupDays: 7,
|
|
1184
|
+
},
|
|
1185
|
+
shareEnabled: false,
|
|
1186
|
+
maxSubagentDepth: 2,
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
const payload = await readAsyncPayload(id);
|
|
1190
|
+
assert.equal(payload.timeoutMs, undefined, "composite parent must remain unbounded by default");
|
|
1191
|
+
assert.equal(payload.state, "failed");
|
|
1192
|
+
assert.equal(payload.results[0]?.timedOut, true);
|
|
1193
|
+
assert.equal(payload.results[0]?.error, "Subagent timed out after 150ms.");
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
it("enforces child timeouts on async parallel tasks without a composite deadline", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1197
|
+
mockPi.onCall({ delay: 5_000, output: "one too late" });
|
|
1198
|
+
mockPi.onCall({ delay: 5_000, output: "two too late" });
|
|
1199
|
+
const id = `async-child-timeout-parallel-${Date.now().toString(36)}`;
|
|
1200
|
+
executeAsyncChain(id, {
|
|
1201
|
+
chain: [{
|
|
1202
|
+
parallel: [
|
|
1203
|
+
{ agent: "slow-one", task: "Wait" },
|
|
1204
|
+
{ agent: "slow-two", task: "Wait" },
|
|
1205
|
+
],
|
|
1206
|
+
concurrency: 2,
|
|
1207
|
+
}],
|
|
1208
|
+
resultMode: "parallel",
|
|
1209
|
+
agents: [
|
|
1210
|
+
makeAgent("slow-one", { defaultTimeoutMs: 150 }),
|
|
1211
|
+
makeAgent("slow-two", { defaultTimeoutMs: 200 }),
|
|
1212
|
+
],
|
|
1213
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1214
|
+
artifactConfig: {
|
|
1215
|
+
enabled: false,
|
|
1216
|
+
includeInput: false,
|
|
1217
|
+
includeOutput: false,
|
|
1218
|
+
includeJsonl: false,
|
|
1219
|
+
includeMetadata: false,
|
|
1220
|
+
cleanupDays: 7,
|
|
1221
|
+
},
|
|
1222
|
+
shareEnabled: false,
|
|
1223
|
+
maxSubagentDepth: 2,
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
const payload = await readAsyncPayload(id);
|
|
1227
|
+
assert.equal(payload.timeoutMs, undefined, "composite parent must remain unbounded by default");
|
|
1228
|
+
assert.equal(payload.state, "failed");
|
|
1229
|
+
assert.deepEqual(payload.results.map((result) => result.timedOut), [true, true]);
|
|
1230
|
+
assert.deepEqual(payload.results.map((result) => result.error), ["Subagent timed out after 150ms.", "Subagent timed out after 200ms."]);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1116
1233
|
it("hard-kills async children that ignore timeout SIGTERM", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1117
1234
|
mockPi.onCall({ delay: 60_000, ignoreSigterm: true, output: "too late" });
|
|
1118
1235
|
const id = `async-timeout-hard-kill-${Date.now().toString(36)}`;
|
|
@@ -1140,7 +1257,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1140
1257
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1141
1258
|
const elapsedMs = Date.now() - startedAt;
|
|
1142
1259
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1143
|
-
const status =
|
|
1260
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1144
1261
|
assert.equal(payload.state, "failed");
|
|
1145
1262
|
assert.equal(payload.timedOut, true);
|
|
1146
1263
|
assert.equal(payload.results[0]?.timedOut, true);
|
|
@@ -1151,15 +1268,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1151
1268
|
assert.equal(mockPi.callCount(), 1);
|
|
1152
1269
|
});
|
|
1153
1270
|
|
|
1154
|
-
it("cancels async acceptance verification when the run times out", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1271
|
+
it("cancels async acceptance verification when the run times out", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1155
1272
|
mockPi.onCall({ output: "implementation complete" });
|
|
1156
1273
|
const id = `async-timeout-acceptance-${Date.now().toString(36)}`;
|
|
1157
1274
|
const timeoutMs = 1_000;
|
|
1158
1275
|
const startedAt = Date.now();
|
|
1159
1276
|
executeAsyncSingle(id, {
|
|
1160
|
-
agent: "
|
|
1277
|
+
agent: "worker",
|
|
1161
1278
|
task: "Implement with verified acceptance",
|
|
1162
|
-
agentConfig: makeAgent("
|
|
1279
|
+
agentConfig: makeAgent("worker"),
|
|
1163
1280
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1164
1281
|
artifactConfig: {
|
|
1165
1282
|
enabled: true,
|
|
@@ -1182,7 +1299,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1182
1299
|
const resultPath = await waitForAsyncResultFile(id, 5_000);
|
|
1183
1300
|
const elapsedMs = Date.now() - startedAt;
|
|
1184
1301
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1185
|
-
const status =
|
|
1302
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1186
1303
|
assert.equal(payload.state, "failed");
|
|
1187
1304
|
assert.equal(payload.timedOut, true);
|
|
1188
1305
|
assert.equal(payload.results[0]?.timedOut, true);
|
|
@@ -1206,9 +1323,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1206
1323
|
});
|
|
1207
1324
|
const id = `async-turn-budget-soft-${Date.now().toString(36)}`;
|
|
1208
1325
|
executeAsyncSingle(id, {
|
|
1209
|
-
agent: "
|
|
1326
|
+
agent: "worker",
|
|
1210
1327
|
task: "Use the final grace turn to wrap up.",
|
|
1211
|
-
agentConfig: makeAgent("
|
|
1328
|
+
agentConfig: makeAgent("worker"),
|
|
1212
1329
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1213
1330
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1214
1331
|
shareEnabled: false,
|
|
@@ -1218,7 +1335,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1218
1335
|
|
|
1219
1336
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1220
1337
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1221
|
-
const status =
|
|
1338
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
1222
1339
|
assert.equal(payload.success, true);
|
|
1223
1340
|
assert.equal(payload.state, "complete");
|
|
1224
1341
|
assert.equal(payload.turnBudgetExceeded, undefined);
|
|
@@ -1245,9 +1362,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1245
1362
|
});
|
|
1246
1363
|
const id = `async-turn-budget-hard-${Date.now().toString(36)}`;
|
|
1247
1364
|
executeAsyncSingle(id, {
|
|
1248
|
-
agent: "
|
|
1365
|
+
agent: "worker",
|
|
1249
1366
|
task: "Exceed the turn budget.",
|
|
1250
|
-
agentConfig: makeAgent("
|
|
1367
|
+
agentConfig: makeAgent("worker"),
|
|
1251
1368
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1252
1369
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1253
1370
|
shareEnabled: false,
|
|
@@ -1257,7 +1374,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1257
1374
|
|
|
1258
1375
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1259
1376
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1260
|
-
const status =
|
|
1377
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
1261
1378
|
assert.equal(payload.success, true);
|
|
1262
1379
|
assert.equal(payload.state, "complete");
|
|
1263
1380
|
assert.equal(payload.exitCode, 0);
|
|
@@ -1295,9 +1412,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1295
1412
|
],
|
|
1296
1413
|
});
|
|
1297
1414
|
executeAsyncSingle(id, {
|
|
1298
|
-
agent: "
|
|
1415
|
+
agent: "worker",
|
|
1299
1416
|
task: "Finish active tool work before enforcing the hard limit.",
|
|
1300
|
-
agentConfig: makeAgent("
|
|
1417
|
+
agentConfig: makeAgent("worker"),
|
|
1301
1418
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1302
1419
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1303
1420
|
shareEnabled: false,
|
|
@@ -1319,7 +1436,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1319
1436
|
|
|
1320
1437
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1321
1438
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1322
|
-
const status =
|
|
1439
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
1323
1440
|
assert.equal(payload.state, "complete");
|
|
1324
1441
|
assert.equal(payload.turnBudgetExceeded, undefined);
|
|
1325
1442
|
assert.equal(payload.turnBudget?.outcome, "wrap-up-requested");
|
|
@@ -1345,9 +1462,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1345
1462
|
});
|
|
1346
1463
|
const id = `async-turn-budget-timeout-${Date.now().toString(36)}`;
|
|
1347
1464
|
executeAsyncSingle(id, {
|
|
1348
|
-
agent: "
|
|
1465
|
+
agent: "worker",
|
|
1349
1466
|
task: "Timeout while hard termination is deferred.",
|
|
1350
|
-
agentConfig: makeAgent("
|
|
1467
|
+
agentConfig: makeAgent("worker"),
|
|
1351
1468
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1352
1469
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1353
1470
|
shareEnabled: false,
|
|
@@ -1361,7 +1478,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1361
1478
|
deliverTimeoutRequest({ asyncDir, pid: pending.pid, source: "test" });
|
|
1362
1479
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1363
1480
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1364
|
-
const status =
|
|
1481
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1365
1482
|
assert.equal(payload.state, "failed");
|
|
1366
1483
|
assert.equal(payload.timedOut, true);
|
|
1367
1484
|
assert.equal(payload.turnBudgetExceeded, undefined);
|
|
@@ -1387,9 +1504,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1387
1504
|
});
|
|
1388
1505
|
const id = `async-turn-budget-stop-${Date.now().toString(36)}`;
|
|
1389
1506
|
executeAsyncSingle(id, {
|
|
1390
|
-
agent: "
|
|
1507
|
+
agent: "worker",
|
|
1391
1508
|
task: "Stop while hard termination is deferred.",
|
|
1392
|
-
agentConfig: makeAgent("
|
|
1509
|
+
agentConfig: makeAgent("worker"),
|
|
1393
1510
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1394
1511
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1395
1512
|
shareEnabled: false,
|
|
@@ -1402,7 +1519,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1402
1519
|
deliverStopRequest({ asyncDir, pid: pending.pid, source: "test" });
|
|
1403
1520
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1404
1521
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1405
|
-
const status =
|
|
1522
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "stopped");
|
|
1406
1523
|
assert.equal(payload.state, "stopped");
|
|
1407
1524
|
assert.equal(payload.stopped, true);
|
|
1408
1525
|
assert.equal(payload.turnBudgetExceeded, undefined);
|
|
@@ -1445,26 +1562,26 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1445
1562
|
const wrappedTask = `Fork preamble: ${"execution ".repeat(20)}`;
|
|
1446
1563
|
const rawGoal = `Caller-facing goal: ${"raw ".repeat(40)}`;
|
|
1447
1564
|
const singleResult = executeAsyncSingle(singleId, {
|
|
1448
|
-
agent: "
|
|
1565
|
+
agent: "worker",
|
|
1449
1566
|
task: wrappedTask,
|
|
1450
1567
|
goal: rawGoal,
|
|
1451
|
-
agentConfig: makeAgent("
|
|
1568
|
+
agentConfig: makeAgent("worker"),
|
|
1452
1569
|
...commonParams,
|
|
1453
1570
|
});
|
|
1454
|
-
assert.match(singleResult.content[0]?.text ?? "", /Async:
|
|
1571
|
+
assert.match(singleResult.content[0]?.text ?? "", /Async: worker \[/);
|
|
1455
1572
|
assert.match(singleResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
|
|
1456
1573
|
assert.match(singleResult.content[0]?.text ?? "", /call subagent_wait\(\)/i);
|
|
1457
1574
|
assert.match(singleResult.content[0]?.text ?? "", /non-interactive run: Pi auto-drains current-session background work at agent_end/);
|
|
1458
|
-
assert.equal(startedEvent(singleId).task,
|
|
1459
|
-
assert.equal(startedEvent(singleId).goal,
|
|
1575
|
+
assert.equal(startedEvent(singleId).task, "[prompt redacted]");
|
|
1576
|
+
assert.equal(startedEvent(singleId).goal, "[prompt redacted]");
|
|
1460
1577
|
await waitForAsyncResultFile(singleId, 30_000);
|
|
1461
1578
|
|
|
1462
1579
|
mockPi.onCall({ output: "interactive done" });
|
|
1463
1580
|
const interactiveId = `async-handoff-interactive-${Date.now().toString(36)}`;
|
|
1464
1581
|
const interactiveResult = executeAsyncSingle(interactiveId, {
|
|
1465
|
-
agent: "
|
|
1582
|
+
agent: "worker",
|
|
1466
1583
|
task: "Interactive handoff",
|
|
1467
|
-
agentConfig: makeAgent("
|
|
1584
|
+
agentConfig: makeAgent("worker"),
|
|
1468
1585
|
...commonParams,
|
|
1469
1586
|
ctx: { ...commonParams.ctx, interactive: true },
|
|
1470
1587
|
});
|
|
@@ -1478,19 +1595,19 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1478
1595
|
mockPi.onCall({ output: "parallel two done" });
|
|
1479
1596
|
const parallelId = `async-handoff-parallel-${Date.now().toString(36)}`;
|
|
1480
1597
|
const parallelResult = executeAsyncChain(parallelId, {
|
|
1481
|
-
chain: [{ parallel: [{ agent: "
|
|
1598
|
+
chain: [{ parallel: [{ agent: "worker", task: "Do one" }, { agent: "reviewer", task: "Do two" }] }],
|
|
1482
1599
|
resultMode: "parallel",
|
|
1483
|
-
agents: [makeAgent("
|
|
1600
|
+
agents: [makeAgent("worker"), makeAgent("reviewer")],
|
|
1484
1601
|
...commonParams,
|
|
1485
1602
|
});
|
|
1486
1603
|
assert.match(parallelResult.content[0]?.text ?? "", /Async parallel:/);
|
|
1487
1604
|
assert.match(parallelResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
|
|
1488
1605
|
assert.match(parallelResult.content[0]?.text ?? "", /call subagent_wait\(\)/i);
|
|
1489
|
-
assert.equal(startedEvent(parallelId).goal, "
|
|
1606
|
+
assert.equal(startedEvent(parallelId).goal, "[prompt redacted]");
|
|
1490
1607
|
const parallelResultPath = await waitForAsyncResultFile(parallelId, 10_000);
|
|
1491
1608
|
const parallelPayload = JSON.parse(fs.readFileSync(parallelResultPath, "utf-8")) as { agent?: string; mode?: string };
|
|
1492
1609
|
assert.equal(parallelPayload.mode, "parallel");
|
|
1493
|
-
assert.equal(parallelPayload.agent, "parallel:
|
|
1610
|
+
assert.equal(parallelPayload.agent, "parallel:worker+reviewer");
|
|
1494
1611
|
|
|
1495
1612
|
mockPi.onCall({ output: "chain done" });
|
|
1496
1613
|
const chainId = `async-handoff-chain-${Date.now().toString(36)}`;
|
|
@@ -1498,15 +1615,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1498
1615
|
const chainChildTask = `Do chained work ${"child ".repeat(15)}`;
|
|
1499
1616
|
const chainResult = executeAsyncChain(chainId, {
|
|
1500
1617
|
task: chainGoal,
|
|
1501
|
-
chain: [{ agent: "
|
|
1502
|
-
agents: [makeAgent("
|
|
1618
|
+
chain: [{ agent: "worker", task: chainChildTask }],
|
|
1619
|
+
agents: [makeAgent("worker")],
|
|
1503
1620
|
...commonParams,
|
|
1504
1621
|
});
|
|
1505
1622
|
assert.match(chainResult.content[0]?.text ?? "", /Async chain:/);
|
|
1506
1623
|
assert.match(chainResult.content[0]?.text ?? "", /Do not run sleep timers or polling loops/);
|
|
1507
1624
|
const chainEvent = startedEvent(chainId);
|
|
1508
|
-
assert.equal(chainEvent.task,
|
|
1509
|
-
assert.equal(chainEvent.goal,
|
|
1625
|
+
assert.equal(chainEvent.task, "[prompt redacted]");
|
|
1626
|
+
assert.equal(chainEvent.goal, "[prompt redacted]");
|
|
1510
1627
|
await waitForAsyncResultFile(chainId, 10_000);
|
|
1511
1628
|
});
|
|
1512
1629
|
|
|
@@ -1515,8 +1632,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1515
1632
|
const id = `async-missing-extension-tool-${Date.now().toString(36)}`;
|
|
1516
1633
|
|
|
1517
1634
|
executeAsyncChain(id, {
|
|
1518
|
-
chain: [{ agent: "extension-
|
|
1519
|
-
agents: [makeAgent("extension-
|
|
1635
|
+
chain: [{ agent: "extension-worker", task: "Use fixture search" }],
|
|
1636
|
+
agents: [makeAgent("extension-worker", { tools: ["read", "fixture_search"] })],
|
|
1520
1637
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1521
1638
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1522
1639
|
shareEnabled: false,
|
|
@@ -1533,11 +1650,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1533
1650
|
|
|
1534
1651
|
it("applies agent acceptance roles to inferred async acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1535
1652
|
mockPi.onCall({ output: "exploration complete" });
|
|
1536
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
1653
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
|
|
1537
1654
|
|
|
1538
1655
|
const result = await executor.execute(
|
|
1539
1656
|
"async-agent-acceptance-role",
|
|
1540
|
-
{ agent: "
|
|
1657
|
+
{ agent: "worker", task: "Explore the authentication flow", async: true, clarify: false },
|
|
1541
1658
|
new AbortController().signal,
|
|
1542
1659
|
undefined,
|
|
1543
1660
|
makeMinimalCtx(tempDir),
|
|
@@ -1551,11 +1668,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1551
1668
|
|
|
1552
1669
|
it("applies agent acceptance roles to inferred async parallel acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1553
1670
|
mockPi.onCall({ output: "parallel exploration complete" });
|
|
1554
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
1671
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
|
|
1555
1672
|
|
|
1556
1673
|
const result = await executor.execute(
|
|
1557
1674
|
"async-parallel-agent-acceptance-role",
|
|
1558
|
-
{ tasks: [{ agent: "
|
|
1675
|
+
{ tasks: [{ agent: "worker", task: "Explore the authentication flow" }], async: true, clarify: false },
|
|
1559
1676
|
new AbortController().signal,
|
|
1560
1677
|
undefined,
|
|
1561
1678
|
makeMinimalCtx(tempDir),
|
|
@@ -1599,6 +1716,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1599
1716
|
});
|
|
1600
1717
|
|
|
1601
1718
|
it("top-level async parallel conversion preserves output, reads, and progress", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1719
|
+
fs.writeFileSync(path.join(tempDir, "input.md"), "context");
|
|
1602
1720
|
mockPi.onCall({ output: "Async top-level report" });
|
|
1603
1721
|
const executor = createSubagentExecutor!({
|
|
1604
1722
|
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
@@ -1608,13 +1726,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1608
1726
|
tempArtifactsDir: tempDir,
|
|
1609
1727
|
getSubagentSessionRoot: () => tempDir,
|
|
1610
1728
|
expandTilde: (p: string) => p,
|
|
1611
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
1729
|
+
discoverAgents: () => ({ agents: [makeAgent("worker", { defaultProgress: true })] }),
|
|
1612
1730
|
});
|
|
1613
1731
|
|
|
1614
1732
|
const result = await executor.execute(
|
|
1615
1733
|
"async-parallel-fields",
|
|
1616
1734
|
{
|
|
1617
|
-
tasks: [{ agent: "
|
|
1735
|
+
tasks: [{ agent: "worker", task: "Do async work", output: "async-top-output.md", reads: ["input.md"] }],
|
|
1618
1736
|
async: true,
|
|
1619
1737
|
clarify: false,
|
|
1620
1738
|
},
|
|
@@ -1667,11 +1785,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1667
1785
|
it(`async top-level parallel isolates inherited output (${outputOverride === true ? "output:true" : "omitted output"})`, { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1668
1786
|
mockPi.onCall({ matchArgIncludes: "Write the first report", output: "first async report" });
|
|
1669
1787
|
mockPi.onCall({ matchArgIncludes: "Write the second report", output: "second async report" });
|
|
1670
|
-
const agent = makeAgent("
|
|
1788
|
+
const agent = makeAgent("worker", { output: "context.md", tools: ["read", "grep", "find", "ls"] });
|
|
1671
1789
|
const executor = makeAsyncExecutor([agent]);
|
|
1672
1790
|
const tasks = [
|
|
1673
|
-
{ agent: "
|
|
1674
|
-
{ agent: "
|
|
1791
|
+
{ agent: "worker", task: "Write the first report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
1792
|
+
{ agent: "worker", task: "Write the second report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
1675
1793
|
];
|
|
1676
1794
|
const launch = await executor.execute(
|
|
1677
1795
|
`async-inherited-output-${outputOverride === true ? "true" : "omitted"}`,
|
|
@@ -1688,8 +1806,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1688
1806
|
assert.equal(payload.results[1]?.output?.split("\n\nOutput saved to:")[0], "second async report");
|
|
1689
1807
|
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
|
|
1690
1808
|
const authoritativePaths = [
|
|
1691
|
-
path.join(outputDir, "parallel-0", "0-
|
|
1692
|
-
path.join(outputDir, "parallel-0", "1-
|
|
1809
|
+
path.join(outputDir, "parallel-0", "0-worker", "context.md"),
|
|
1810
|
+
path.join(outputDir, "parallel-0", "1-worker", "context.md"),
|
|
1693
1811
|
];
|
|
1694
1812
|
assert.equal(fs.readFileSync(authoritativePaths[0]!, "utf-8"), "first async report");
|
|
1695
1813
|
assert.equal(fs.readFileSync(authoritativePaths[1]!, "utf-8"), "second async report");
|
|
@@ -1702,8 +1820,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1702
1820
|
const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
|
|
1703
1821
|
const firstTask = taskArgs.find((task) => task.includes("Write the first report")) ?? "";
|
|
1704
1822
|
const secondTask = taskArgs.find((task) => task.includes("Write the second report")) ?? "";
|
|
1705
|
-
assert.ok(firstTask.includes(path.join("parallel-0", "0-
|
|
1706
|
-
assert.ok(secondTask.includes(path.join("parallel-0", "1-
|
|
1823
|
+
assert.ok(firstTask.includes(path.join("parallel-0", "0-worker", "context.md")));
|
|
1824
|
+
assert.ok(secondTask.includes(path.join("parallel-0", "1-worker", "context.md")));
|
|
1707
1825
|
for (const taskArg of [firstTask, secondTask]) {
|
|
1708
1826
|
assert.match(taskArg, /Return the complete artifact in your final response\./);
|
|
1709
1827
|
assert.match(taskArg, /Do not call contact_supervisor merely because no write-capable tool is available\./);
|
|
@@ -1713,13 +1831,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1713
1831
|
}
|
|
1714
1832
|
|
|
1715
1833
|
it("async top-level parallel rejects duplicate explicit output paths before spawning", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1716
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
1834
|
+
const executor = makeAsyncExecutor([makeAgent("worker")]);
|
|
1717
1835
|
const result = await executor.execute(
|
|
1718
1836
|
"async-duplicate-output",
|
|
1719
1837
|
{
|
|
1720
1838
|
tasks: [
|
|
1721
|
-
{ agent: "
|
|
1722
|
-
{ agent: "
|
|
1839
|
+
{ agent: "worker", task: "Write A", output: "same.md" },
|
|
1840
|
+
{ agent: "worker", task: "Write B", output: "same.md" },
|
|
1723
1841
|
],
|
|
1724
1842
|
async: true,
|
|
1725
1843
|
clarify: false,
|
|
@@ -1730,20 +1848,20 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1730
1848
|
);
|
|
1731
1849
|
|
|
1732
1850
|
assert.equal(result.isError, true);
|
|
1733
|
-
assert.match(result.content[0]?.text ?? "", /Parallel tasks 1 \(
|
|
1851
|
+
assert.match(result.content[0]?.text ?? "", /Parallel tasks 1 \(worker\) and 2 \(worker\).*same\.md/);
|
|
1734
1852
|
assert.equal(mockPi.callCount(), 0);
|
|
1735
1853
|
});
|
|
1736
1854
|
|
|
1737
1855
|
it("async top-level parallel preserves distinct explicit output destinations", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1738
1856
|
mockPi.onCall({ matchArgIncludes: "first.md", output: "first explicit report" });
|
|
1739
1857
|
mockPi.onCall({ matchArgIncludes: "second.md", output: "second explicit report" });
|
|
1740
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
1858
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { output: "context.md" })]);
|
|
1741
1859
|
const launch = await executor.execute(
|
|
1742
1860
|
"async-distinct-explicit-output",
|
|
1743
1861
|
{
|
|
1744
1862
|
tasks: [
|
|
1745
|
-
{ agent: "
|
|
1746
|
-
{ agent: "
|
|
1863
|
+
{ agent: "worker", task: "Write A", output: "first.md" },
|
|
1864
|
+
{ agent: "worker", task: "Write B", output: "second.md" },
|
|
1747
1865
|
],
|
|
1748
1866
|
async: true,
|
|
1749
1867
|
clarify: false,
|
|
@@ -1758,8 +1876,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1758
1876
|
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
|
|
1759
1877
|
assert.equal(fs.readFileSync(path.join(outputDir, "first.md"), "utf-8"), "first explicit report");
|
|
1760
1878
|
assert.equal(fs.readFileSync(path.join(outputDir, "second.md"), "utf-8"), "second explicit report");
|
|
1761
|
-
assert.ok(payload.results[0]?.artifactPaths?.outputPath?.endsWith("
|
|
1762
|
-
assert.ok(payload.results[1]?.artifactPaths?.outputPath?.endsWith("
|
|
1879
|
+
assert.ok(payload.results[0]?.artifactPaths?.outputPath?.endsWith("_worker_0_output.md"));
|
|
1880
|
+
assert.ok(payload.results[1]?.artifactPaths?.outputPath?.endsWith("_worker_1_output.md"));
|
|
1763
1881
|
});
|
|
1764
1882
|
|
|
1765
1883
|
it("async chain static parallel namespaces inherited default outputs", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -1767,8 +1885,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1767
1885
|
mockPi.onCall({ matchArgIncludes: "Write second", output: "chain second report" });
|
|
1768
1886
|
const id = `async-chain-parallel-output-${Date.now().toString(36)}`;
|
|
1769
1887
|
const launch = executeAsyncChain(id, {
|
|
1770
|
-
chain: [{ parallel: [{ agent: "
|
|
1771
|
-
agents: [makeAgent("
|
|
1888
|
+
chain: [{ parallel: [{ agent: "worker", task: "Write first" }, { agent: "worker", task: "Write second" }] }],
|
|
1889
|
+
agents: [makeAgent("worker", { output: "context.md" })],
|
|
1772
1890
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-chain-output" },
|
|
1773
1891
|
artifactConfig: { enabled: true, includeInput: false, includeOutput: true, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1774
1892
|
artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
|
|
@@ -1781,8 +1899,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1781
1899
|
assert.equal(payload.success, true);
|
|
1782
1900
|
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", id);
|
|
1783
1901
|
const authoritativePaths = [
|
|
1784
|
-
path.join(outputDir, "parallel-0", "0-
|
|
1785
|
-
path.join(outputDir, "parallel-0", "1-
|
|
1902
|
+
path.join(outputDir, "parallel-0", "0-worker", "context.md"),
|
|
1903
|
+
path.join(outputDir, "parallel-0", "1-worker", "context.md"),
|
|
1786
1904
|
];
|
|
1787
1905
|
assert.equal(fs.readFileSync(authoritativePaths[0]!, "utf-8"), "chain first report");
|
|
1788
1906
|
assert.equal(fs.readFileSync(authoritativePaths[1]!, "utf-8"), "chain second report");
|
|
@@ -1793,8 +1911,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1793
1911
|
assert.equal(fs.readFileSync(artifactPaths[1], "utf-8"), "chain second report");
|
|
1794
1912
|
const calls = fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).sort();
|
|
1795
1913
|
const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
|
|
1796
|
-
assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-
|
|
1797
|
-
assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-
|
|
1914
|
+
assert.ok(taskArgs.find((task) => task.includes("Write first"))?.includes(path.join("parallel-0", "0-worker", "context.md")));
|
|
1915
|
+
assert.ok(taskArgs.find((task) => task.includes("Write second"))?.includes(path.join("parallel-0", "1-worker", "context.md")));
|
|
1798
1916
|
});
|
|
1799
1917
|
|
|
1800
1918
|
it("async single preserves checked evidence while independent review is pending", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -1825,14 +1943,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1825
1943
|
};
|
|
1826
1944
|
const id = `async-acceptance-${Date.now().toString(36)}`;
|
|
1827
1945
|
executeAsyncSingle(id, {
|
|
1828
|
-
agent: "
|
|
1946
|
+
agent: "worker",
|
|
1829
1947
|
task: "Implement acceptance-covered fix",
|
|
1830
|
-
agentConfig: makeAgent("
|
|
1948
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
1831
1949
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-acceptance" },
|
|
1832
1950
|
artifactConfig,
|
|
1833
1951
|
shareEnabled: false,
|
|
1834
1952
|
maxSubagentDepth: 2,
|
|
1835
|
-
acceptance: { level: "checked", criteria: ["Patch bug"], review: { agent: "
|
|
1953
|
+
acceptance: { level: "checked", criteria: ["Patch bug"], review: { agent: "reviewer", required: true } },
|
|
1836
1954
|
});
|
|
1837
1955
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1838
1956
|
const result = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
@@ -1856,13 +1974,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1856
1974
|
tempArtifactsDir: tempDir,
|
|
1857
1975
|
getSubagentSessionRoot: () => tempDir,
|
|
1858
1976
|
expandTilde: (p: string) => p,
|
|
1859
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
1977
|
+
discoverAgents: () => ({ agents: [makeAgent("reviewer", { defaultProgress: true })] }),
|
|
1860
1978
|
});
|
|
1861
1979
|
|
|
1862
1980
|
const result = await executor.execute(
|
|
1863
1981
|
"async-chain-read-only-progress",
|
|
1864
1982
|
{
|
|
1865
|
-
chain: [{ agent: "
|
|
1983
|
+
chain: [{ agent: "reviewer" }],
|
|
1866
1984
|
task: "Review-only. Do not edit files. Return findings.",
|
|
1867
1985
|
async: true,
|
|
1868
1986
|
clarify: false,
|
|
@@ -1934,7 +2052,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1934
2052
|
assert.ok(!result.isError);
|
|
1935
2053
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1936
2054
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1937
|
-
const status =
|
|
2055
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
1938
2056
|
assert.deepEqual(payload.results[0]?.structuredOutput, { value: "Alpha structured" });
|
|
1939
2057
|
assert.deepEqual(payload.outputs?.data?.structured, { value: "Alpha structured" });
|
|
1940
2058
|
assert.match(readMockPiArgs(mockPi, 1).at(-1) ?? "", /Alpha structured/);
|
|
@@ -1952,15 +2070,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1952
2070
|
mockPi.onCall({ matchArgIncludes: "Scout API", output: "Scout A async findings" });
|
|
1953
2071
|
mockPi.onCall({ matchArgIncludes: "Scout UI", output: "Scout B async findings" });
|
|
1954
2072
|
mockPi.onCall({ matchArgIncludes: "Synthesize:", output: "Async funnel synthesis" });
|
|
1955
|
-
mockPi.onCall({ matchArgIncludes: "Review funnel A:", output: "Async
|
|
1956
|
-
mockPi.onCall({ matchArgIncludes: "Review funnel B:", output: "Async
|
|
2073
|
+
mockPi.onCall({ matchArgIncludes: "Review funnel A:", output: "Async reviewer A done" });
|
|
2074
|
+
mockPi.onCall({ matchArgIncludes: "Review funnel B:", output: "Async reviewer B done" });
|
|
1957
2075
|
const id = `async-parallel-funnel-fanout-${Date.now().toString(36)}`;
|
|
1958
2076
|
const result = executeAsyncChain(id, {
|
|
1959
2077
|
chain: [
|
|
1960
2078
|
{
|
|
1961
2079
|
parallel: [
|
|
1962
|
-
{ agent: "
|
|
1963
|
-
{ agent: "
|
|
2080
|
+
{ agent: "scout-a", task: "Scout API" },
|
|
2081
|
+
{ agent: "scout-b", task: "Scout UI" },
|
|
1964
2082
|
],
|
|
1965
2083
|
concurrency: 2,
|
|
1966
2084
|
},
|
|
@@ -1973,7 +2091,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1973
2091
|
concurrency: 2,
|
|
1974
2092
|
},
|
|
1975
2093
|
],
|
|
1976
|
-
agents: [makeAgent("
|
|
2094
|
+
agents: [makeAgent("scout-a"), makeAgent("scout-b"), makeAgent("synthesizer"), makeAgent("review-a"), makeAgent("review-b")],
|
|
1977
2095
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-parallel-funnel-fanout" },
|
|
1978
2096
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
1979
2097
|
shareEnabled: false,
|
|
@@ -1983,14 +2101,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1983
2101
|
assert.ok(!result.isError, `should launch: ${JSON.stringify(result.content)}`);
|
|
1984
2102
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
1985
2103
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1986
|
-
const status =
|
|
2104
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
1987
2105
|
assert.equal(payload.success, true);
|
|
1988
2106
|
assert.deepEqual(payload.results.map((entry) => entry.output), [
|
|
1989
2107
|
"Scout A async findings",
|
|
1990
2108
|
"Scout B async findings",
|
|
1991
2109
|
"Async funnel synthesis",
|
|
1992
|
-
"Async
|
|
1993
|
-
"Async
|
|
2110
|
+
"Async reviewer A done",
|
|
2111
|
+
"Async reviewer B done",
|
|
1994
2112
|
]);
|
|
1995
2113
|
assert.deepEqual(status.steps?.map((step) => step.status), ["complete", "complete", "complete", "complete", "complete"]);
|
|
1996
2114
|
assert.deepEqual(status.parallelGroups, [
|
|
@@ -1998,9 +2116,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1998
2116
|
{ start: 3, count: 2, stepIndex: 2 },
|
|
1999
2117
|
]);
|
|
2000
2118
|
const funnelTask = readMockPiArgsMatching(mockPi, "Synthesize:").at(-1) ?? "";
|
|
2001
|
-
assert.match(funnelTask, /=== Parallel Task 1 \(
|
|
2119
|
+
assert.match(funnelTask, /=== Parallel Task 1 \(scout-a\) ===/);
|
|
2002
2120
|
assert.match(funnelTask, /Scout A async findings/);
|
|
2003
|
-
assert.match(funnelTask, /=== Parallel Task 2 \(
|
|
2121
|
+
assert.match(funnelTask, /=== Parallel Task 2 \(scout-b\) ===/);
|
|
2004
2122
|
assert.match(funnelTask, /Scout B async findings/);
|
|
2005
2123
|
assert.match(readMockPiArgsMatching(mockPi, "Review funnel A:").at(-1) ?? "", /Review funnel A:\nAsync funnel synthesis/);
|
|
2006
2124
|
assert.match(readMockPiArgsMatching(mockPi, "Review funnel B:").at(-1) ?? "", /Review funnel B:\nAsync funnel synthesis/);
|
|
@@ -2023,13 +2141,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2023
2141
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2024
2142
|
{
|
|
2025
2143
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2026
|
-
parallel: { agent: "
|
|
2144
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", label: "Review {target.path}", outputSchema: { type: "object" } },
|
|
2027
2145
|
collect: { as: "reviews" },
|
|
2028
2146
|
concurrency: 1,
|
|
2029
2147
|
},
|
|
2030
2148
|
{ agent: "consumer", task: "Use {outputs.reviews}" },
|
|
2031
2149
|
],
|
|
2032
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2150
|
+
agents: [makeAgent("producer"), makeAgent("reviewer"), makeAgent("consumer")],
|
|
2033
2151
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-placeholder" },
|
|
2034
2152
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2035
2153
|
shareEnabled: false,
|
|
@@ -2045,7 +2163,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2045
2163
|
if (fs.existsSync(statusPath)) status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
2046
2164
|
else await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2047
2165
|
}
|
|
2048
|
-
assert.deepEqual(status.steps?.map((step) => step.agent), ["producer", "expand:
|
|
2166
|
+
assert.deepEqual(status.steps?.map((step) => step.agent), ["producer", "expand:reviewer", "consumer"]);
|
|
2049
2167
|
assert.equal(status.steps?.[1]?.label, "Review {target.path}");
|
|
2050
2168
|
assert.equal(status.steps?.[1]?.outputName, "reviews");
|
|
2051
2169
|
assert.deepEqual(status.parallelGroups, [{ start: 1, count: 1, stepIndex: 1 }]);
|
|
@@ -2054,7 +2172,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2054
2172
|
const finalStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
2055
2173
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2056
2174
|
assert.equal(payload.success, true);
|
|
2057
|
-
assert.deepEqual(finalStatus.steps?.map((step) => step.agent), ["producer", "
|
|
2175
|
+
assert.deepEqual(finalStatus.steps?.map((step) => step.agent), ["producer", "reviewer", "reviewer", "consumer"]);
|
|
2058
2176
|
assert.deepEqual(finalStatus.parallelGroups, [{ start: 1, count: 2, stepIndex: 1 }]);
|
|
2059
2177
|
});
|
|
2060
2178
|
|
|
@@ -2070,7 +2188,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2070
2188
|
{
|
|
2071
2189
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2072
2190
|
parallel: {
|
|
2073
|
-
agent: "
|
|
2191
|
+
agent: "reviewer",
|
|
2074
2192
|
task: "Review {target.path}",
|
|
2075
2193
|
label: "Review {target.path}",
|
|
2076
2194
|
outputSchema: { type: "object" },
|
|
@@ -2080,7 +2198,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2080
2198
|
},
|
|
2081
2199
|
{ agent: "consumer", task: "Use {outputs.reviews}" },
|
|
2082
2200
|
],
|
|
2083
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2201
|
+
agents: [makeAgent("producer"), makeAgent("reviewer", { output: "context.md" }), makeAgent("consumer")],
|
|
2084
2202
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic" },
|
|
2085
2203
|
artifactConfig: { enabled: true, includeInput: false, includeOutput: true, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2086
2204
|
artifactsDir: path.join(tempDir, ".pi-subagents", "artifacts"),
|
|
@@ -2102,8 +2220,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2102
2220
|
assert.deepEqual(collected.map((item) => item.structured), [{ ok: "a" }, { ok: "b" }]);
|
|
2103
2221
|
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", id);
|
|
2104
2222
|
const dynamicOutputPaths = [
|
|
2105
|
-
path.join(outputDir, "dynamic-1", "0-
|
|
2106
|
-
path.join(outputDir, "dynamic-1", "1-
|
|
2223
|
+
path.join(outputDir, "dynamic-1", "0-reviewer", "context.md"),
|
|
2224
|
+
path.join(outputDir, "dynamic-1", "1-reviewer", "context.md"),
|
|
2107
2225
|
];
|
|
2108
2226
|
assert.equal(fs.readFileSync(dynamicOutputPaths[0]!, "utf-8"), "review-a");
|
|
2109
2227
|
assert.equal(fs.readFileSync(dynamicOutputPaths[1]!, "utf-8"), "review-b");
|
|
@@ -2130,13 +2248,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2130
2248
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2131
2249
|
{
|
|
2132
2250
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2133
|
-
parallel: { agent: "
|
|
2251
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
|
|
2134
2252
|
collect: { as: "reviews" },
|
|
2135
2253
|
concurrency: 1,
|
|
2136
2254
|
},
|
|
2137
2255
|
],
|
|
2138
2256
|
usageBudget: { tokens: { hard: 200 } },
|
|
2139
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2257
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2140
2258
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-budget" },
|
|
2141
2259
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2142
2260
|
shareEnabled: false,
|
|
@@ -2151,7 +2269,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2151
2269
|
assert.equal(status.steps?.[1]?.status, "complete");
|
|
2152
2270
|
assert.equal(status.steps?.[2]?.status, "failed");
|
|
2153
2271
|
assert.match(status.steps?.[2]?.error ?? "", /Usage budget exhausted/);
|
|
2154
|
-
assert.equal(payload.results.find((result) => result.agent === "
|
|
2272
|
+
assert.equal(payload.results.find((result) => result.agent === "reviewer" && result.skipped)?.skipped, true);
|
|
2155
2273
|
});
|
|
2156
2274
|
|
|
2157
2275
|
it("rejects a shared explicit output before dynamic fanout children start", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -2162,12 +2280,12 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2162
2280
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2163
2281
|
{
|
|
2164
2282
|
expand: { from: { output: "targets", path: "/items" }, item: "target", maxItems: 2 },
|
|
2165
|
-
parallel: { agent: "
|
|
2283
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", output: "shared.md" },
|
|
2166
2284
|
collect: { as: "reviews" },
|
|
2167
2285
|
concurrency: 2,
|
|
2168
2286
|
},
|
|
2169
2287
|
],
|
|
2170
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2288
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2171
2289
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-explicit-output" },
|
|
2172
2290
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2173
2291
|
shareEnabled: false,
|
|
@@ -2196,7 +2314,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2196
2314
|
{
|
|
2197
2315
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 2 },
|
|
2198
2316
|
parallel: {
|
|
2199
|
-
agent: "
|
|
2317
|
+
agent: "reviewer",
|
|
2200
2318
|
task: "Review {target.path}",
|
|
2201
2319
|
label: "Review {target.path}",
|
|
2202
2320
|
outputSchema: { type: "object" },
|
|
@@ -2205,7 +2323,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2205
2323
|
concurrency: 1,
|
|
2206
2324
|
},
|
|
2207
2325
|
],
|
|
2208
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2326
|
+
agents: [makeAgent("producer"), makeAgent("reviewer", { model: "anthropic/claude-sonnet-4-5:high", thinking: "high" })],
|
|
2209
2327
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic" },
|
|
2210
2328
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2211
2329
|
shareEnabled: false,
|
|
@@ -2330,7 +2448,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2330
2448
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2331
2449
|
{
|
|
2332
2450
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2333
|
-
parallel: { agent: "
|
|
2451
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" }, acceptance: { level: "checked" } },
|
|
2334
2452
|
collect: { as: "reviews" },
|
|
2335
2453
|
acceptance: {
|
|
2336
2454
|
level: "verified",
|
|
@@ -2338,7 +2456,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2338
2456
|
},
|
|
2339
2457
|
},
|
|
2340
2458
|
],
|
|
2341
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2459
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2342
2460
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-acceptance-timeout" },
|
|
2343
2461
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2344
2462
|
shareEnabled: false,
|
|
@@ -2349,7 +2467,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2349
2467
|
const resultPath = await waitForAsyncResultFile(id, 5_000);
|
|
2350
2468
|
const elapsedMs = Date.now() - startedAt;
|
|
2351
2469
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2352
|
-
const status =
|
|
2470
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
2353
2471
|
const dynamicNode = payload.workflowGraph?.nodes?.[1] as { status?: string; error?: string; acceptanceStatus?: string } | undefined;
|
|
2354
2472
|
assert.equal(payload.state, "failed");
|
|
2355
2473
|
assert.equal(payload.timedOut, true);
|
|
@@ -2373,13 +2491,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2373
2491
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2374
2492
|
{
|
|
2375
2493
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2376
|
-
parallel: { agent: "
|
|
2494
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
|
|
2377
2495
|
collect: { as: "reviews" },
|
|
2378
2496
|
concurrency: 1,
|
|
2379
2497
|
},
|
|
2380
2498
|
{ agent: "consumer", task: "Use {outputs.reviews}" },
|
|
2381
2499
|
],
|
|
2382
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2500
|
+
agents: [makeAgent("producer"), makeAgent("reviewer"), makeAgent("consumer")],
|
|
2383
2501
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-targets" },
|
|
2384
2502
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2385
2503
|
shareEnabled: false,
|
|
@@ -2405,11 +2523,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2405
2523
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2406
2524
|
{
|
|
2407
2525
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 1 },
|
|
2408
|
-
parallel: { agent: "
|
|
2526
|
+
parallel: { agent: "reviewer", task: "Review {target.path}" },
|
|
2409
2527
|
collect: { as: "reviews" },
|
|
2410
2528
|
},
|
|
2411
2529
|
],
|
|
2412
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2530
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2413
2531
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-fail" },
|
|
2414
2532
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2415
2533
|
shareEnabled: false,
|
|
@@ -2419,7 +2537,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2419
2537
|
assert.ok(!result.isError);
|
|
2420
2538
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
2421
2539
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2422
|
-
const status =
|
|
2540
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed") as AsyncStatusPayload & { workflowGraph?: AsyncResultPayload["workflowGraph"]; error?: string };
|
|
2423
2541
|
assert.equal(payload.success, false);
|
|
2424
2542
|
assert.match(payload.results.at(-1)?.error ?? "", /exceeding maxItems 1/);
|
|
2425
2543
|
assert.equal(payload.workflowGraph?.nodes?.[1]?.status, "failed");
|
|
@@ -2438,11 +2556,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2438
2556
|
{ agent: "producer", task: "Produce targets", as: "targets", outputSchema: { type: "object" } },
|
|
2439
2557
|
{
|
|
2440
2558
|
expand: { from: { output: "targets", path: "/items" }, item: "target", key: "/path", maxItems: 4 },
|
|
2441
|
-
parallel: { agent: "
|
|
2559
|
+
parallel: { agent: "reviewer", task: "Review {target.path}", outputSchema: { type: "object" } },
|
|
2442
2560
|
collect: { as: "reviews", outputSchema: { type: "object" } },
|
|
2443
2561
|
},
|
|
2444
2562
|
],
|
|
2445
|
-
agents: [makeAgent("producer"), makeAgent("
|
|
2563
|
+
agents: [makeAgent("producer"), makeAgent("reviewer")],
|
|
2446
2564
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-dynamic-collect-fail" },
|
|
2447
2565
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2448
2566
|
shareEnabled: false,
|
|
@@ -2459,7 +2577,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2459
2577
|
assert.match(payload.workflowGraph?.nodes?.[1]?.error ?? "", /Collected output validation failed/);
|
|
2460
2578
|
});
|
|
2461
2579
|
|
|
2462
|
-
it("top-level async worktree parallel
|
|
2580
|
+
it("top-level async worktree parallel keeps existing reads and writes output under project artifacts", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : process.platform === "win32" ? "worktree path separators unreliable on Windows CI" : undefined }, async () => {
|
|
2463
2581
|
const repoDir = createRepo("pi-subagent-async-worktree-");
|
|
2464
2582
|
try {
|
|
2465
2583
|
mockPi.onCall({ output: "Worktree report" });
|
|
@@ -2471,13 +2589,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2471
2589
|
tempArtifactsDir: repoDir,
|
|
2472
2590
|
getSubagentSessionRoot: () => repoDir,
|
|
2473
2591
|
expandTilde: (p: string) => p,
|
|
2474
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
2592
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
2475
2593
|
});
|
|
2476
2594
|
|
|
2477
2595
|
const result = await executor.execute(
|
|
2478
2596
|
"async-parallel-worktree-fields",
|
|
2479
2597
|
{
|
|
2480
|
-
tasks: [{ agent: "
|
|
2598
|
+
tasks: [{ agent: "worker", task: "Do worktree work", output: "report.md", reads: ["input.md"] }],
|
|
2481
2599
|
async: true,
|
|
2482
2600
|
clarify: false,
|
|
2483
2601
|
worktree: true,
|
|
@@ -2493,11 +2611,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2493
2611
|
const worktreeCwd = path.join(os.tmpdir(), `pi-worktree-${asyncId}-s0-0`);
|
|
2494
2612
|
const args = await waitForMockPiArgs(mockPi, 0);
|
|
2495
2613
|
const taskArg = args.at(-1) ?? "";
|
|
2496
|
-
assert.
|
|
2614
|
+
assert.match(taskArg, new RegExp(`\\[Read from: ${escapeRegExp(path.join(worktreeCwd, "input.md"))}\\]`));
|
|
2497
2615
|
assert.ok(taskArg.includes(`Write your findings to exactly this path: ${path.join(repoDir, ".pi-subagents", "artifacts", "outputs", asyncId, "report.md")}`));
|
|
2498
2616
|
const resultPath = await waitForAsyncResultFile(asyncId, 90_000);
|
|
2499
2617
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2500
|
-
const status =
|
|
2618
|
+
const status = await waitForAsyncState(asyncId, (candidate) => candidate.state === "complete");
|
|
2501
2619
|
assert.equal(payload.parallelHandoff?.version, 1);
|
|
2502
2620
|
assert.equal(payload.parallelHandoff?.path, path.join(result.details!.asyncDir!, "handoff.json"));
|
|
2503
2621
|
assert.deepEqual(status.parallelHandoff, payload.parallelHandoff);
|
|
@@ -2509,7 +2627,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2509
2627
|
groups: Array<{ children: Array<{ agent: string; status: string; patch: { path: string } }>; cleanup: { state: string } }>;
|
|
2510
2628
|
};
|
|
2511
2629
|
assert.equal(handoff.version, 1);
|
|
2512
|
-
assert.equal(handoff.groups[0]!.children[0]!.agent, "
|
|
2630
|
+
assert.equal(handoff.groups[0]!.children[0]!.agent, "worker");
|
|
2513
2631
|
assert.equal(handoff.groups[0]!.children[0]!.status, "completed");
|
|
2514
2632
|
assert.equal(handoff.groups[0]!.cleanup.state, "complete");
|
|
2515
2633
|
assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true, "patch artifact should outlive cleanup");
|
|
@@ -2518,33 +2636,49 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2518
2636
|
}
|
|
2519
2637
|
});
|
|
2520
2638
|
|
|
2521
|
-
it("readStatus caches
|
|
2522
|
-
const
|
|
2639
|
+
it("readStatus caches ordered sweeps above 50 files and invalidates same-mtime replacements", () => {
|
|
2640
|
+
const root = createTempDir();
|
|
2523
2641
|
try {
|
|
2524
|
-
const statusPath = path.join(dir, "status.json");
|
|
2525
2642
|
const fixedTimestamp = new Date(1_700_000_000_000);
|
|
2526
|
-
const
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2643
|
+
const dirs = Array.from({ length: 51 }, (_, index) => {
|
|
2644
|
+
const dir = path.join(root, `run-${index}`);
|
|
2645
|
+
const statusPath = path.join(dir, "status.json");
|
|
2646
|
+
fs.mkdirSync(dir);
|
|
2647
|
+
fs.writeFileSync(statusPath, JSON.stringify({
|
|
2648
|
+
runId: `cache-test-${index}`,
|
|
2649
|
+
state: "running",
|
|
2650
|
+
mode: "single",
|
|
2651
|
+
startedAt: fixedTimestamp.getTime(),
|
|
2652
|
+
}));
|
|
2653
|
+
fs.utimesSync(statusPath, fixedTimestamp, fixedTimestamp);
|
|
2654
|
+
return dir;
|
|
2655
|
+
});
|
|
2534
2656
|
|
|
2535
|
-
const cached = readStatus(dir);
|
|
2536
|
-
assert.ok(
|
|
2537
|
-
assert.strictEqual(readStatus(dir), cached);
|
|
2657
|
+
const cached = dirs.map((dir) => readStatus(dir));
|
|
2658
|
+
cached.forEach((status) => assert.ok(status));
|
|
2659
|
+
dirs.forEach((dir, index) => assert.strictEqual(readStatus(dir), cached[index]));
|
|
2538
2660
|
|
|
2539
|
-
|
|
2661
|
+
const replacedDir = dirs[25]!;
|
|
2662
|
+
const cachedStatus = cached[25];
|
|
2663
|
+
assert.ok(cachedStatus);
|
|
2664
|
+
const statusPath = path.join(replacedDir, "status.json");
|
|
2665
|
+
writeAtomicJson(statusPath, { ...cachedStatus, state: "stopped" });
|
|
2540
2666
|
fs.utimesSync(statusPath, fixedTimestamp, fixedTimestamp);
|
|
2541
2667
|
assert.equal(fs.statSync(statusPath).mtimeMs, fixedTimestamp.getTime());
|
|
2542
|
-
const replaced = readStatus(
|
|
2668
|
+
const replaced = readStatus(replacedDir);
|
|
2543
2669
|
assert.ok(replaced);
|
|
2544
2670
|
assert.equal(replaced.state, "stopped");
|
|
2545
|
-
assert.notStrictEqual(replaced,
|
|
2671
|
+
assert.notStrictEqual(replaced, cachedStatus);
|
|
2672
|
+
|
|
2673
|
+
fs.rmSync(statusPath);
|
|
2674
|
+
assert.equal(readStatus(replacedDir), null);
|
|
2675
|
+
|
|
2676
|
+
const removedDir = dirs[50]!;
|
|
2677
|
+
assert.ok(readStatus(removedDir));
|
|
2678
|
+
fs.rmSync(removedDir, { recursive: true, force: true });
|
|
2679
|
+
assert.equal(pruneStatusCacheForAsyncRoot(root, dirs.slice(0, 50).map((dir) => path.basename(dir))), 1);
|
|
2546
2680
|
} finally {
|
|
2547
|
-
removeTempDir(
|
|
2681
|
+
removeTempDir(root);
|
|
2548
2682
|
}
|
|
2549
2683
|
});
|
|
2550
2684
|
|
|
@@ -2578,10 +2712,11 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2578
2712
|
const asyncDir = path.join(ASYNC_DIR, id);
|
|
2579
2713
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
2580
2714
|
const run = executeAsyncSingle(id, {
|
|
2581
|
-
agent: "
|
|
2715
|
+
agent: "worker",
|
|
2582
2716
|
task: "Do work",
|
|
2583
2717
|
acceptance: { level: "none", reason: "descriptor persistence coverage" },
|
|
2584
|
-
|
|
2718
|
+
turnBudget: { maxTurns: 8, graceTurns: 2 },
|
|
2719
|
+
agentConfig: makeAgent("worker", {
|
|
2585
2720
|
model: "openai/gpt-5-mini:high",
|
|
2586
2721
|
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2587
2722
|
}),
|
|
@@ -2616,12 +2751,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2616
2751
|
const descriptorPath = path.join(asyncDir, "recovery-descriptor.json");
|
|
2617
2752
|
const descriptor = JSON.parse(fs.readFileSync(descriptorPath, "utf-8"));
|
|
2618
2753
|
assert.equal(descriptor.sourceRunId, id);
|
|
2619
|
-
assert.equal(descriptor.agent, "
|
|
2754
|
+
assert.equal(descriptor.agent, "worker");
|
|
2620
2755
|
assert.equal(descriptor.model, "openai/gpt-5-mini:high");
|
|
2621
2756
|
assert.deepEqual(descriptor.fallbackModels, ["anthropic/claude-sonnet-4:low"]);
|
|
2622
2757
|
assert.equal(descriptor.cwd, tempDir);
|
|
2623
2758
|
assert.equal(descriptor.sessionDir, path.join(sessionRoot, `async-${id}`));
|
|
2624
2759
|
assert.deepEqual(descriptor.acceptance, { level: "none", reason: "descriptor persistence coverage" });
|
|
2760
|
+
assert.deepEqual(descriptor.initialTurnBudget, { maxTurns: 8, graceTurns: 2 });
|
|
2625
2761
|
assert.equal(Object.hasOwn(descriptor.acceptance, "explicit"), false);
|
|
2626
2762
|
assert.equal(Object.hasOwn(descriptor.acceptance, "inferredReason"), false);
|
|
2627
2763
|
assert.equal(Object.hasOwn(descriptor, "task"), false);
|
|
@@ -2635,7 +2771,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2635
2771
|
assert.equal(payload.results[0].modelAttempts.length, 2);
|
|
2636
2772
|
assert.deepEqual(payload.results[0].totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2637
2773
|
assert.deepEqual(payload.totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2638
|
-
const statusPayload =
|
|
2774
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete" && candidate.totalCost !== undefined);
|
|
2639
2775
|
assert.equal(statusPayload.lifecycleArtifactVersion, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION);
|
|
2640
2776
|
assert.equal(statusPayload.steps[0]?.model, "anthropic/claude-sonnet-4:low");
|
|
2641
2777
|
assert.equal(statusPayload.steps[0]?.thinking, "low");
|
|
@@ -2657,9 +2793,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2657
2793
|
mockPi.onCall({ output: "Recovered asynchronously after startup race" });
|
|
2658
2794
|
const id = `async-startup-retry-${Date.now().toString(36)}`;
|
|
2659
2795
|
executeAsyncSingle(id, {
|
|
2660
|
-
agent: "
|
|
2796
|
+
agent: "worker",
|
|
2661
2797
|
task: "Do work",
|
|
2662
|
-
agentConfig: makeAgent("
|
|
2798
|
+
agentConfig: makeAgent("worker", {
|
|
2663
2799
|
model: "openai/gpt-5-mini:high",
|
|
2664
2800
|
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2665
2801
|
}),
|
|
@@ -2706,9 +2842,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2706
2842
|
mockPi.onCall({ output: "Recovered after stream failure" });
|
|
2707
2843
|
const id = `async-fallback-stream-${Date.now().toString(36)}`;
|
|
2708
2844
|
executeAsyncSingle(id, {
|
|
2709
|
-
agent: "
|
|
2845
|
+
agent: "worker",
|
|
2710
2846
|
task: "Do work",
|
|
2711
|
-
agentConfig: makeAgent("
|
|
2847
|
+
agentConfig: makeAgent("worker", {
|
|
2712
2848
|
model: "openai/gpt-5-mini:high",
|
|
2713
2849
|
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2714
2850
|
}),
|
|
@@ -2747,9 +2883,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2747
2883
|
mockPi.onCall({ output: "fallback must not run" });
|
|
2748
2884
|
const id = `async-fallback-toolfail-${Date.now().toString(36)}`;
|
|
2749
2885
|
executeAsyncSingle(id, {
|
|
2750
|
-
agent: "
|
|
2886
|
+
agent: "worker",
|
|
2751
2887
|
task: "Do work",
|
|
2752
|
-
agentConfig: makeAgent("
|
|
2888
|
+
agentConfig: makeAgent("worker", {
|
|
2753
2889
|
model: "openai/gpt-5-mini:high",
|
|
2754
2890
|
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2755
2891
|
}),
|
|
@@ -2795,9 +2931,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2795
2931
|
mockPi.onCall({ output: "Recovered asynchronously" });
|
|
2796
2932
|
const id = `async-fallback-thinking-off-${Date.now().toString(36)}`;
|
|
2797
2933
|
const run = executeAsyncSingle(id, {
|
|
2798
|
-
agent: "
|
|
2934
|
+
agent: "worker",
|
|
2799
2935
|
task: "Do work",
|
|
2800
|
-
agentConfig: makeAgent("
|
|
2936
|
+
agentConfig: makeAgent("worker", {
|
|
2801
2937
|
model: "openai/gpt-5-mini:high",
|
|
2802
2938
|
fallbackModels: ["anthropic/claude-sonnet-4:low"],
|
|
2803
2939
|
thinking: "high",
|
|
@@ -2850,9 +2986,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2850
2986
|
mockPi.onCall({ output: "Recovered asynchronously from empty output" });
|
|
2851
2987
|
const id = `async-empty-output-fallback-${Date.now().toString(36)}`;
|
|
2852
2988
|
executeAsyncSingle(id, {
|
|
2853
|
-
agent: "
|
|
2989
|
+
agent: "worker",
|
|
2854
2990
|
task: "Do work",
|
|
2855
|
-
agentConfig: makeAgent("
|
|
2991
|
+
agentConfig: makeAgent("worker", {
|
|
2856
2992
|
model: "openai/gpt-5-mini",
|
|
2857
2993
|
fallbackModels: ["anthropic/claude-sonnet-4"],
|
|
2858
2994
|
}),
|
|
@@ -2900,9 +3036,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2900
3036
|
mockPi.onCall({ output: "Recovered asynchronously on fallback" });
|
|
2901
3037
|
const id = `async-empty-output-after-tool-error-${Date.now().toString(36)}`;
|
|
2902
3038
|
executeAsyncSingle(id, {
|
|
2903
|
-
agent: "
|
|
3039
|
+
agent: "worker",
|
|
2904
3040
|
task: "Do work",
|
|
2905
|
-
agentConfig: makeAgent("
|
|
3041
|
+
agentConfig: makeAgent("worker", {
|
|
2906
3042
|
model: "openai/gpt-5-mini",
|
|
2907
3043
|
fallbackModels: ["anthropic/claude-sonnet-4"],
|
|
2908
3044
|
}),
|
|
@@ -2942,11 +3078,10 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2942
3078
|
exitCode: 0,
|
|
2943
3079
|
});
|
|
2944
3080
|
const id = `async-zero-exit-provider-error-${Date.now().toString(36)}`;
|
|
2945
|
-
const asyncDir = path.join(ASYNC_DIR, id);
|
|
2946
3081
|
executeAsyncSingle(id, {
|
|
2947
|
-
agent: "
|
|
3082
|
+
agent: "worker",
|
|
2948
3083
|
task: "Do work",
|
|
2949
|
-
agentConfig: makeAgent("
|
|
3084
|
+
agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
|
|
2950
3085
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
2951
3086
|
artifactConfig: {
|
|
2952
3087
|
enabled: false,
|
|
@@ -2964,8 +3099,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2964
3099
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2965
3100
|
assert.equal(payload.success, false);
|
|
2966
3101
|
assert.match(payload.results[0]?.error ?? "", /429 quota exceeded/);
|
|
2967
|
-
const statusPayload =
|
|
2968
|
-
assert.equal(statusPayload.state, "failed");
|
|
3102
|
+
const statusPayload = await waitForAsyncState(id, (status) => status.state === "failed");
|
|
2969
3103
|
assert.match(statusPayload.steps?.[0]?.error ?? "", /429 quota exceeded/);
|
|
2970
3104
|
});
|
|
2971
3105
|
|
|
@@ -2990,9 +3124,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2990
3124
|
const id = `async-recovered-child-error-${Date.now().toString(36)}`;
|
|
2991
3125
|
const asyncDir = path.join(ASYNC_DIR, id);
|
|
2992
3126
|
executeAsyncSingle(id, {
|
|
2993
|
-
agent: "
|
|
3127
|
+
agent: "worker",
|
|
2994
3128
|
task: "Do work",
|
|
2995
|
-
agentConfig: makeAgent("
|
|
3129
|
+
agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
|
|
2996
3130
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
2997
3131
|
artifactConfig: {
|
|
2998
3132
|
enabled: false,
|
|
@@ -3014,7 +3148,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3014
3148
|
assert.equal(payload.results[0]?.success, true);
|
|
3015
3149
|
assert.equal(payload.results[0]?.error, undefined);
|
|
3016
3150
|
assert.equal(payload.results[0]?.output, "Recovered asynchronously");
|
|
3017
|
-
const statusPayload =
|
|
3151
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
3018
3152
|
assert.equal(statusPayload.state, "complete");
|
|
3019
3153
|
assert.equal(statusPayload.steps?.[0]?.status, "complete");
|
|
3020
3154
|
assert.equal(statusPayload.steps?.[0]?.exitCode, 0);
|
|
@@ -3040,9 +3174,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3040
3174
|
const id = `async-provider-error-empty-stop-${Date.now().toString(36)}`;
|
|
3041
3175
|
const asyncDir = path.join(ASYNC_DIR, id);
|
|
3042
3176
|
executeAsyncSingle(id, {
|
|
3043
|
-
agent: "
|
|
3177
|
+
agent: "worker",
|
|
3044
3178
|
task: "Do work",
|
|
3045
|
-
agentConfig: makeAgent("
|
|
3179
|
+
agentConfig: makeAgent("worker", { model: "openai/gpt-5-mini" }),
|
|
3046
3180
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3047
3181
|
artifactConfig: {
|
|
3048
3182
|
enabled: false,
|
|
@@ -3064,7 +3198,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3064
3198
|
assert.equal(payload.results[0]?.success, false);
|
|
3065
3199
|
assert.match(payload.results[0]?.error ?? "", /provider transport failed/);
|
|
3066
3200
|
assert.equal(payload.results[0]?.output, "");
|
|
3067
|
-
const statusPayload =
|
|
3201
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
3068
3202
|
assert.equal(statusPayload.state, "failed");
|
|
3069
3203
|
assert.equal(statusPayload.steps?.[0]?.status, "failed");
|
|
3070
3204
|
assert.equal(statusPayload.steps?.[0]?.exitCode, 1);
|
|
@@ -3199,9 +3333,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3199
3333
|
mockPi.onCall({ output: "async inline report" });
|
|
3200
3334
|
const id = `async-string-false-output-${Date.now().toString(36)}`;
|
|
3201
3335
|
const run = executeAsyncSingle(id, {
|
|
3202
|
-
agent: "
|
|
3336
|
+
agent: "worker",
|
|
3203
3337
|
task: "Do work",
|
|
3204
|
-
agentConfig: makeAgent("
|
|
3338
|
+
agentConfig: makeAgent("worker", { output: "default-report.md" }),
|
|
3205
3339
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3206
3340
|
artifactConfig: {
|
|
3207
3341
|
enabled: false,
|
|
@@ -3238,9 +3372,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3238
3372
|
const sessionRoot = path.join(tempDir, "sessions");
|
|
3239
3373
|
|
|
3240
3374
|
executeAsyncSingle(id, {
|
|
3241
|
-
agent: "
|
|
3375
|
+
agent: "worker",
|
|
3242
3376
|
task: "Deploy app",
|
|
3243
|
-
agentConfig: makeAgent("
|
|
3377
|
+
agentConfig: makeAgent("worker"),
|
|
3244
3378
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3245
3379
|
artifactConfig: {
|
|
3246
3380
|
enabled: false,
|
|
@@ -3277,9 +3411,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3277
3411
|
const sessionRoot = path.join(tempDir, "sessions");
|
|
3278
3412
|
|
|
3279
3413
|
executeAsyncSingle(id, {
|
|
3280
|
-
agent: "
|
|
3414
|
+
agent: "worker",
|
|
3281
3415
|
task: "Implement the approved fixes",
|
|
3282
|
-
agentConfig: makeAgent("
|
|
3416
|
+
agentConfig: makeAgent("worker"),
|
|
3283
3417
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3284
3418
|
artifactConfig: {
|
|
3285
3419
|
enabled: false,
|
|
@@ -3312,7 +3446,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3312
3446
|
const eventsPath = path.join(ASYNC_DIR, id, "events.jsonl");
|
|
3313
3447
|
const eventsText = fs.readFileSync(eventsPath, "utf-8");
|
|
3314
3448
|
assert.match(eventsText, /"reason":"completion_guard"/);
|
|
3315
|
-
assert.match(eventsText, /Subagent failed:
|
|
3449
|
+
assert.match(eventsText, /Subagent failed: worker/);
|
|
3316
3450
|
assert.doesNotMatch(eventsText, /Status:/);
|
|
3317
3451
|
assert.doesNotMatch(eventsText, /Interrupt:/);
|
|
3318
3452
|
});
|
|
@@ -3322,9 +3456,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3322
3456
|
const id = `async-v1-separate-${Date.now().toString(36)}`;
|
|
3323
3457
|
|
|
3324
3458
|
executeAsyncSingle(id, {
|
|
3325
|
-
agent: "
|
|
3459
|
+
agent: "worker",
|
|
3326
3460
|
task: "Implement the approved fixes",
|
|
3327
|
-
agentConfig: makeAgent("
|
|
3461
|
+
agentConfig: makeAgent("worker", { completionGuard: true }),
|
|
3328
3462
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3329
3463
|
artifactConfig: {
|
|
3330
3464
|
enabled: false,
|
|
@@ -3343,7 +3477,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3343
3477
|
|
|
3344
3478
|
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
3345
3479
|
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
3346
|
-
const statusPayload =
|
|
3480
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete");
|
|
3347
3481
|
|
|
3348
3482
|
assert.equal(payload.success, true);
|
|
3349
3483
|
assert.equal(payload.state, "complete");
|
|
@@ -3364,9 +3498,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3364
3498
|
const id = `async-single-schema-${Date.now().toString(36)}`;
|
|
3365
3499
|
|
|
3366
3500
|
executeAsyncSingle(id, {
|
|
3367
|
-
agent: "
|
|
3501
|
+
agent: "worker",
|
|
3368
3502
|
task: "Return structured data",
|
|
3369
|
-
agentConfig: makeAgent("
|
|
3503
|
+
agentConfig: makeAgent("worker", { completionGuard: false }),
|
|
3370
3504
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3371
3505
|
artifactConfig: {
|
|
3372
3506
|
enabled: false,
|
|
@@ -3432,9 +3566,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3432
3566
|
const sessionRoot = path.join(tempDir, "sessions");
|
|
3433
3567
|
|
|
3434
3568
|
executeAsyncSingle(id, {
|
|
3435
|
-
agent: "
|
|
3569
|
+
agent: "worker",
|
|
3436
3570
|
task: "Do work",
|
|
3437
|
-
agentConfig: makeAgent("
|
|
3571
|
+
agentConfig: makeAgent("worker", { model: "gpt-5-mini" }),
|
|
3438
3572
|
ctx: {
|
|
3439
3573
|
pi: { events: { emit() {} } },
|
|
3440
3574
|
cwd: tempDir,
|
|
@@ -3472,6 +3606,55 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3472
3606
|
assert.deepEqual(payload.results[0].attemptedModels, ["github-copilot/gpt-5-mini"]);
|
|
3473
3607
|
});
|
|
3474
3608
|
|
|
3609
|
+
it("rejects an over-cap top-level async launch before creating run artifacts", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
3610
|
+
fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey("session-cap")), { recursive: true, force: true });
|
|
3611
|
+
const state = {
|
|
3612
|
+
baseCwd: tempDir,
|
|
3613
|
+
currentSessionId: "session-cap",
|
|
3614
|
+
asyncJobs: new Map(),
|
|
3615
|
+
fleetJobs: new Map(),
|
|
3616
|
+
foregroundControls: new Map(),
|
|
3617
|
+
lastForegroundControlId: null,
|
|
3618
|
+
};
|
|
3619
|
+
const occupied = acquireActiveAsyncCapacity({
|
|
3620
|
+
sessionId: "session-cap",
|
|
3621
|
+
limit: 1,
|
|
3622
|
+
runId: "held-run",
|
|
3623
|
+
kind: "runner",
|
|
3624
|
+
asyncDir: path.join(tempDir, "held-run"),
|
|
3625
|
+
});
|
|
3626
|
+
assert.ok(occupied);
|
|
3627
|
+
occupied.markStarted("held-runner");
|
|
3628
|
+
const rejectedAsyncDir = path.join(ASYNC_DIR, "cap-rejected");
|
|
3629
|
+
const rejectedResultPath = path.join(RESULTS_DIR, "cap-rejected.json");
|
|
3630
|
+
fs.rmSync(rejectedAsyncDir, { recursive: true, force: true });
|
|
3631
|
+
fs.rmSync(rejectedResultPath, { force: true });
|
|
3632
|
+
const executor = createSubagentExecutor!({
|
|
3633
|
+
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
3634
|
+
state,
|
|
3635
|
+
config: { maxActiveAsyncRunsPerSession: 1, artifactDir: "project" },
|
|
3636
|
+
asyncByDefault: false,
|
|
3637
|
+
tempArtifactsDir: tempDir,
|
|
3638
|
+
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
3639
|
+
expandTilde: (p: string) => p,
|
|
3640
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
3641
|
+
});
|
|
3642
|
+
const context = makeMinimalCtx(tempDir);
|
|
3643
|
+
context.sessionManager.getSessionFile = () => null;
|
|
3644
|
+
context.sessionManager.getSessionId = () => "session-cap";
|
|
3645
|
+
const previousDepth = process.env.SELESAI_SUBAGENT_DEPTH;
|
|
3646
|
+
process.env.SELESAI_SUBAGENT_DEPTH = "0";
|
|
3647
|
+
const result = await executor.execute("cap-rejected", { agent: "worker", task: "Must not start", async: true }, new AbortController().signal, undefined, context);
|
|
3648
|
+
if (previousDepth === undefined) delete process.env.SELESAI_SUBAGENT_DEPTH;
|
|
3649
|
+
else process.env.SELESAI_SUBAGENT_DEPTH = previousDepth;
|
|
3650
|
+
assert.equal(result.isError, true);
|
|
3651
|
+
assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /Active async run capacity exhausted: 1\/1 used/);
|
|
3652
|
+
assert.equal(fs.existsSync(rejectedAsyncDir), false);
|
|
3653
|
+
assert.equal(fs.existsSync(rejectedResultPath), false);
|
|
3654
|
+
assert.equal(fs.existsSync(path.join(tempDir, ".pi-subagents")), false);
|
|
3655
|
+
fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey("session-cap")), { recursive: true, force: true });
|
|
3656
|
+
});
|
|
3657
|
+
|
|
3475
3658
|
it("scheduled executor launches retain the live active session ownership", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
3476
3659
|
mockPi.onCall({ output: "Scheduled work completed" });
|
|
3477
3660
|
const liveCwd = path.join(tempDir, "live-project");
|
|
@@ -3494,14 +3677,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3494
3677
|
tempArtifactsDir: tempDir,
|
|
3495
3678
|
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
3496
3679
|
expandTilde: (p: string) => p,
|
|
3497
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
3680
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
3498
3681
|
});
|
|
3499
3682
|
const retainedCtx = makeMinimalCtx(tempDir);
|
|
3500
3683
|
retainedCtx.sessionManager.getSessionId = () => "session-a";
|
|
3501
3684
|
retainedCtx.model = { provider: "deepseek", id: "scheduled-model" };
|
|
3502
3685
|
const launch = await executor.executeScheduled(
|
|
3503
3686
|
`scheduled-owner-${Date.now().toString(36)}`,
|
|
3504
|
-
{ agent: "
|
|
3687
|
+
{ agent: "worker", task: "Run retained project timer", async: true, acceptance: false },
|
|
3505
3688
|
new AbortController().signal,
|
|
3506
3689
|
retainedCtx,
|
|
3507
3690
|
) as AsyncExecutionResult;
|
|
@@ -3514,7 +3697,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3514
3697
|
assert.deepEqual(state.subagentSpawns, { sessionId: "session-b", count: 1, configuredLimit: 1, granted: 1, grantHistory: [] });
|
|
3515
3698
|
const blocked = await executor.executeScheduled(
|
|
3516
3699
|
`scheduled-owner-blocked-${Date.now().toString(36)}`,
|
|
3517
|
-
{ agent: "
|
|
3700
|
+
{ agent: "worker", task: "Exceed the retained owner budget", async: true, acceptance: false },
|
|
3518
3701
|
new AbortController().signal,
|
|
3519
3702
|
retainedCtx,
|
|
3520
3703
|
);
|
|
@@ -3544,7 +3727,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3544
3727
|
tempArtifactsDir: tempDir,
|
|
3545
3728
|
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
3546
3729
|
expandTilde: (p: string) => p,
|
|
3547
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
3730
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
3548
3731
|
});
|
|
3549
3732
|
const retainedCtx = makeMinimalCtx(tempDir);
|
|
3550
3733
|
retainedCtx.sessionManager.getSessionId = () => "session-a";
|
|
@@ -3584,7 +3767,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3584
3767
|
tempArtifactsDir: tempDir,
|
|
3585
3768
|
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
3586
3769
|
expandTilde: (p: string) => p,
|
|
3587
|
-
discoverAgents: () => ({ agents: [makeAgent("
|
|
3770
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
3588
3771
|
});
|
|
3589
3772
|
const initialCtx = makeMinimalCtx(tempDir);
|
|
3590
3773
|
initialCtx.sessionManager.getSessionId = () => "session-continued";
|
|
@@ -3595,7 +3778,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3595
3778
|
continuedCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3596
3779
|
const launch = await executor.execute(
|
|
3597
3780
|
"continued-async-child",
|
|
3598
|
-
{ agent: "
|
|
3781
|
+
{ agent: "worker", task: "Do work", async: true, acceptance: false },
|
|
3599
3782
|
new AbortController().signal,
|
|
3600
3783
|
undefined,
|
|
3601
3784
|
continuedCtx,
|
|
@@ -3612,7 +3795,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3612
3795
|
|
|
3613
3796
|
it("foreground chains keep the last parent session model after continuation drops ctx.model", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
|
|
3614
3797
|
mockPi.onCall({ output: "Done foreground" });
|
|
3615
|
-
const executor = makeAsyncExecutor([makeAgent("
|
|
3798
|
+
const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })]);
|
|
3616
3799
|
const initialCtx = makeMinimalCtx(tempDir);
|
|
3617
3800
|
initialCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3618
3801
|
initialCtx.model = { provider: "deepseek", id: "deepseek-v4-flash" };
|
|
@@ -3622,7 +3805,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3622
3805
|
continuedCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3623
3806
|
const result = await executor.execute(
|
|
3624
3807
|
"continued-foreground-chain",
|
|
3625
|
-
{ chain: [{ agent: "
|
|
3808
|
+
{ chain: [{ agent: "worker", task: "Do work" }], acceptance: false },
|
|
3626
3809
|
new AbortController().signal,
|
|
3627
3810
|
undefined,
|
|
3628
3811
|
continuedCtx,
|
|
@@ -3637,9 +3820,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3637
3820
|
|
|
3638
3821
|
const id = `async-single-parent-model-${Date.now().toString(36)}`;
|
|
3639
3822
|
executeAsyncSingle(id, {
|
|
3640
|
-
agent: "
|
|
3823
|
+
agent: "worker",
|
|
3641
3824
|
task: "Do work",
|
|
3642
|
-
agentConfig: makeAgent("
|
|
3825
|
+
agentConfig: makeAgent("worker"),
|
|
3643
3826
|
ctx: {
|
|
3644
3827
|
pi: { events: { emit() {} } },
|
|
3645
3828
|
cwd: tempDir,
|
|
@@ -3674,8 +3857,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3674
3857
|
|
|
3675
3858
|
const id = `async-chain-parent-model-${Date.now().toString(36)}`;
|
|
3676
3859
|
executeAsyncChain(id, {
|
|
3677
|
-
chain: [{ agent: "
|
|
3678
|
-
agents: [makeAgent("
|
|
3860
|
+
chain: [{ agent: "worker", task: "Do work" }],
|
|
3861
|
+
agents: [makeAgent("worker")],
|
|
3679
3862
|
ctx: {
|
|
3680
3863
|
pi: { events: { emit() {} } },
|
|
3681
3864
|
cwd: tempDir,
|
|
@@ -3710,8 +3893,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3710
3893
|
|
|
3711
3894
|
const id = `async-chain-empty-model-${Date.now().toString(36)}`;
|
|
3712
3895
|
executeAsyncChain(id, {
|
|
3713
|
-
chain: [{ agent: "
|
|
3714
|
-
agents: [makeAgent("
|
|
3896
|
+
chain: [{ agent: "worker", task: "Do work", model: "" }],
|
|
3897
|
+
agents: [makeAgent("worker", { model: "anthropic/claude-sonnet-4-5", thinking: "high" })],
|
|
3715
3898
|
ctx: {
|
|
3716
3899
|
pi: { events: { emit() {} } },
|
|
3717
3900
|
cwd: tempDir,
|
|
@@ -3754,8 +3937,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3754
3937
|
for (const [index, requestedModel] of [undefined, "", "inherit"].entries()) {
|
|
3755
3938
|
const id = `async-chain-no-parent-${index}-${Date.now().toString(36)}`;
|
|
3756
3939
|
executeAsyncChain(id, {
|
|
3757
|
-
chain: [{ agent: "
|
|
3758
|
-
agents: [makeAgent("
|
|
3940
|
+
chain: [{ agent: "worker", task: "Do work", ...(requestedModel !== undefined ? { model: requestedModel } : {}) }],
|
|
3941
|
+
agents: [makeAgent("worker", { model: "openai/gpt-5-mini", thinking: "high" })],
|
|
3759
3942
|
ctx: {
|
|
3760
3943
|
pi: { events: { emit() {} } },
|
|
3761
3944
|
cwd: tempDir,
|
|
@@ -3804,9 +3987,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3804
3987
|
try {
|
|
3805
3988
|
writePackageSkill(taskCwd, "async-task-cwd-skill");
|
|
3806
3989
|
executeAsyncSingle(id, {
|
|
3807
|
-
agent: "
|
|
3990
|
+
agent: "worker",
|
|
3808
3991
|
task: "Do work",
|
|
3809
|
-
agentConfig: makeAgent("
|
|
3992
|
+
agentConfig: makeAgent("worker", { skills: ["async-task-cwd-skill"] }),
|
|
3810
3993
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3811
3994
|
cwd: taskCwd,
|
|
3812
3995
|
artifactConfig: {
|
|
@@ -3842,15 +4025,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3842
4025
|
it("injects agent-file-relative local skills into background single child prompts", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3843
4026
|
mockPi.onCall({ output: "Done asynchronously" });
|
|
3844
4027
|
const id = `async-local-skill-${Date.now().toString(36)}`;
|
|
3845
|
-
const agentFile = path.join(tempDir, "agents", "
|
|
4028
|
+
const agentFile = path.join(tempDir, "agents", "worker", "worker.md");
|
|
3846
4029
|
const skillFile = path.join(path.dirname(agentFile), "skills", "local", "SKILL.md");
|
|
3847
4030
|
fs.mkdirSync(path.dirname(skillFile), { recursive: true });
|
|
3848
4031
|
fs.writeFileSync(skillFile, "---\ndescription: async local skill\n---\nLocal skill body\n", "utf-8");
|
|
3849
4032
|
|
|
3850
4033
|
executeAsyncSingle(id, {
|
|
3851
|
-
agent: "
|
|
4034
|
+
agent: "worker",
|
|
3852
4035
|
task: "Do work",
|
|
3853
|
-
agentConfig: makeAgent("
|
|
4036
|
+
agentConfig: makeAgent("worker", { filePath: agentFile, skills: ["local"], skillPath: ["./skills"] }),
|
|
3854
4037
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3855
4038
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
3856
4039
|
shareEnabled: false,
|
|
@@ -3896,9 +4079,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3896
4079
|
it("background single runs report unavailable pi-subagents skill requests", () => {
|
|
3897
4080
|
const id = `async-pi-subagents-skill-${Date.now().toString(36)}`;
|
|
3898
4081
|
const result = executeAsyncSingle(id, {
|
|
3899
|
-
agent: "
|
|
4082
|
+
agent: "worker",
|
|
3900
4083
|
task: "Do work",
|
|
3901
|
-
agentConfig: makeAgent("
|
|
4084
|
+
agentConfig: makeAgent("worker"),
|
|
3902
4085
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3903
4086
|
cwd: tempDir,
|
|
3904
4087
|
artifactConfig: {
|
|
@@ -3922,8 +4105,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3922
4105
|
it("background chains report unavailable pi-subagents skill requests", () => {
|
|
3923
4106
|
const id = `async-chain-pi-subagents-skill-${Date.now().toString(36)}`;
|
|
3924
4107
|
const result = executeAsyncChain(id, {
|
|
3925
|
-
chain: [{ agent: "
|
|
3926
|
-
agents: [makeAgent("
|
|
4108
|
+
chain: [{ agent: "worker", task: "Do work", skill: ["pi-subagents"] }],
|
|
4109
|
+
agents: [makeAgent("worker")],
|
|
3927
4110
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3928
4111
|
cwd: tempDir,
|
|
3929
4112
|
artifactConfig: {
|
|
@@ -3954,8 +4137,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3954
4137
|
try {
|
|
3955
4138
|
writePackageSkill(path.join(chainCwd, "packages", "app"), "async-chain-step-skill");
|
|
3956
4139
|
executeAsyncChain(id, {
|
|
3957
|
-
chain: [{ agent: "
|
|
3958
|
-
agents: [makeAgent("
|
|
4140
|
+
chain: [{ agent: "worker", task: "Do work", cwd: "packages/app", skill: ["async-chain-step-skill"] }],
|
|
4141
|
+
agents: [makeAgent("worker")],
|
|
3959
4142
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3960
4143
|
cwd: chainCwd,
|
|
3961
4144
|
artifactConfig: {
|
|
@@ -4053,9 +4236,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4053
4236
|
fs.mkdirSync(path.join(TEMP_ROOT_DIR, `async-cfg-${id}.json`), { recursive: true });
|
|
4054
4237
|
|
|
4055
4238
|
const result = executeAsyncSingle(id, {
|
|
4056
|
-
agent: "
|
|
4239
|
+
agent: "worker",
|
|
4057
4240
|
task: "Do work",
|
|
4058
|
-
agentConfig: makeAgent("
|
|
4241
|
+
agentConfig: makeAgent("worker"),
|
|
4059
4242
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4060
4243
|
artifactConfig: {
|
|
4061
4244
|
enabled: false,
|
|
@@ -4080,9 +4263,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4080
4263
|
const missingCwd = path.join(tempDir, "missing-cwd");
|
|
4081
4264
|
|
|
4082
4265
|
const singleResult = executeAsyncSingle(id, {
|
|
4083
|
-
agent: "
|
|
4266
|
+
agent: "worker",
|
|
4084
4267
|
task: "Do work",
|
|
4085
|
-
agentConfig: makeAgent("
|
|
4268
|
+
agentConfig: makeAgent("worker"),
|
|
4086
4269
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4087
4270
|
cwd: missingCwd,
|
|
4088
4271
|
artifactConfig: {
|
|
@@ -4104,8 +4287,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4104
4287
|
|
|
4105
4288
|
const chainId = `async-missing-cwd-chain-${Date.now().toString(36)}`;
|
|
4106
4289
|
const chainResult = executeAsyncChain(chainId, {
|
|
4107
|
-
chain: [{ agent: "
|
|
4108
|
-
agents: [makeAgent("
|
|
4290
|
+
chain: [{ agent: "worker", task: "Do work" }],
|
|
4291
|
+
agents: [makeAgent("worker")],
|
|
4109
4292
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4110
4293
|
cwd: missingCwd,
|
|
4111
4294
|
artifactConfig: {
|
|
@@ -4135,9 +4318,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4135
4318
|
try {
|
|
4136
4319
|
const id = `async-spawn-fail-${Date.now().toString(36)}`;
|
|
4137
4320
|
const result = executeAsyncSingle(id, {
|
|
4138
|
-
agent: "
|
|
4321
|
+
agent: "worker",
|
|
4139
4322
|
task: "Do work",
|
|
4140
|
-
agentConfig: makeAgent("
|
|
4323
|
+
agentConfig: makeAgent("worker"),
|
|
4141
4324
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4142
4325
|
artifactConfig: {
|
|
4143
4326
|
enabled: false,
|
|
@@ -4172,8 +4355,8 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4172
4355
|
fs.mkdirSync(path.join(TEMP_ROOT_DIR, `async-cfg-${id}.json`), { recursive: true });
|
|
4173
4356
|
|
|
4174
4357
|
const result = executeAsyncChain(id, {
|
|
4175
|
-
chain: [{ agent: "
|
|
4176
|
-
agents: [makeAgent("
|
|
4358
|
+
chain: [{ agent: "worker", task: "Do work" }],
|
|
4359
|
+
agents: [makeAgent("worker")],
|
|
4177
4360
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4178
4361
|
artifactConfig: {
|
|
4179
4362
|
enabled: false,
|
|
@@ -4203,9 +4386,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4203
4386
|
|
|
4204
4387
|
const start = Date.now();
|
|
4205
4388
|
executeAsyncSingle(id, {
|
|
4206
|
-
agent: "
|
|
4389
|
+
agent: "worker",
|
|
4207
4390
|
task: "Do work",
|
|
4208
|
-
agentConfig: makeAgent("
|
|
4391
|
+
agentConfig: makeAgent("worker"),
|
|
4209
4392
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4210
4393
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4211
4394
|
shareEnabled: false,
|
|
@@ -4237,9 +4420,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4237
4420
|
|
|
4238
4421
|
const start = Date.now();
|
|
4239
4422
|
executeAsyncSingle(id, {
|
|
4240
|
-
agent: "
|
|
4423
|
+
agent: "worker",
|
|
4241
4424
|
task: "Do work",
|
|
4242
|
-
agentConfig: makeAgent("
|
|
4425
|
+
agentConfig: makeAgent("worker"),
|
|
4243
4426
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4244
4427
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4245
4428
|
shareEnabled: false,
|
|
@@ -4269,9 +4452,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4269
4452
|
|
|
4270
4453
|
const start = Date.now();
|
|
4271
4454
|
executeAsyncSingle(id, {
|
|
4272
|
-
agent: "
|
|
4455
|
+
agent: "worker",
|
|
4273
4456
|
task: "Do work",
|
|
4274
|
-
agentConfig: makeAgent("
|
|
4457
|
+
agentConfig: makeAgent("worker"),
|
|
4275
4458
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4276
4459
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4277
4460
|
shareEnabled: false,
|
|
@@ -4304,9 +4487,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4304
4487
|
|
|
4305
4488
|
const start = Date.now();
|
|
4306
4489
|
executeAsyncSingle(id, {
|
|
4307
|
-
agent: "
|
|
4490
|
+
agent: "worker",
|
|
4308
4491
|
task: "Do work",
|
|
4309
|
-
agentConfig: makeAgent("
|
|
4492
|
+
agentConfig: makeAgent("worker"),
|
|
4310
4493
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4311
4494
|
artifactConfig: {
|
|
4312
4495
|
enabled: false,
|
|
@@ -4349,9 +4532,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4349
4532
|
|
|
4350
4533
|
const start = Date.now();
|
|
4351
4534
|
executeAsyncSingle(id, {
|
|
4352
|
-
agent: "
|
|
4535
|
+
agent: "scout",
|
|
4353
4536
|
task: "Inspect something",
|
|
4354
|
-
agentConfig: makeAgent("
|
|
4537
|
+
agentConfig: makeAgent("scout"),
|
|
4355
4538
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4356
4539
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4357
4540
|
shareEnabled: false,
|
|
@@ -4394,9 +4577,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4394
4577
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
4395
4578
|
|
|
4396
4579
|
executeAsyncSingle(id, {
|
|
4397
|
-
agent: "
|
|
4580
|
+
agent: "worker",
|
|
4398
4581
|
task: "Do work",
|
|
4399
|
-
agentConfig: makeAgent("
|
|
4582
|
+
agentConfig: makeAgent("worker"),
|
|
4400
4583
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4401
4584
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4402
4585
|
shareEnabled: false,
|
|
@@ -4431,9 +4614,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4431
4614
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
4432
4615
|
|
|
4433
4616
|
executeAsyncSingle(id, {
|
|
4434
|
-
agent: "
|
|
4617
|
+
agent: "scout",
|
|
4435
4618
|
task: "Investigate behavior",
|
|
4436
|
-
agentConfig: makeAgent("
|
|
4619
|
+
agentConfig: makeAgent("scout"),
|
|
4437
4620
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4438
4621
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4439
4622
|
shareEnabled: false,
|
|
@@ -4499,9 +4682,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4499
4682
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4500
4683
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
4501
4684
|
executeAsyncSingle(id, {
|
|
4502
|
-
agent: "
|
|
4685
|
+
agent: "worker",
|
|
4503
4686
|
task: "Run the command",
|
|
4504
|
-
agentConfig: makeAgent("
|
|
4687
|
+
agentConfig: makeAgent("worker"),
|
|
4505
4688
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4506
4689
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4507
4690
|
shareEnabled: false,
|
|
@@ -4541,23 +4724,25 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4541
4724
|
});
|
|
4542
4725
|
|
|
4543
4726
|
it("subagent_wait wakes when an async child is waiting on contact_supervisor", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4727
|
+
const id = `async-supervisor-attention-${Date.now().toString(36)}`;
|
|
4728
|
+
const replyReleasePath = path.join(tempDir, `${id}.reply`);
|
|
4729
|
+
const finalReleasePath = path.join(tempDir, `${id}.final`);
|
|
4544
4730
|
mockPi.onCall({
|
|
4545
4731
|
steps: [
|
|
4546
4732
|
{ jsonl: [events.toolStart("contact_supervisor", { reason: "need_decision", message: "Need a decision" })] },
|
|
4547
|
-
{
|
|
4548
|
-
{
|
|
4733
|
+
{ waitForPath: replyReleasePath, jsonl: [events.toolEnd("contact_supervisor"), events.toolResult("contact_supervisor", "**Reply from supervisor:**\nProceed")] },
|
|
4734
|
+
{ waitForPath: finalReleasePath, jsonl: [events.assistantMessage("Done")] },
|
|
4549
4735
|
],
|
|
4550
4736
|
});
|
|
4551
4737
|
|
|
4552
|
-
const id = `async-supervisor-attention-${Date.now().toString(36)}`;
|
|
4553
4738
|
const asyncDir = path.join(ASYNC_DIR, id);
|
|
4554
4739
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4555
4740
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
4556
4741
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4557
4742
|
executeAsyncSingle(id, {
|
|
4558
|
-
agent: "
|
|
4743
|
+
agent: "worker",
|
|
4559
4744
|
task: "Ask the supervisor for a blocking decision",
|
|
4560
|
-
agentConfig: makeAgent("
|
|
4745
|
+
agentConfig: makeAgent("worker"),
|
|
4561
4746
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4562
4747
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4563
4748
|
shareEnabled: false,
|
|
@@ -4573,55 +4758,71 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4573
4758
|
},
|
|
4574
4759
|
});
|
|
4575
4760
|
|
|
4576
|
-
const
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4761
|
+
const releaseMockChild = () => {
|
|
4762
|
+
if (!fs.existsSync(replyReleasePath)) fs.writeFileSync(replyReleasePath, "release", "utf-8");
|
|
4763
|
+
if (!fs.existsSync(finalReleasePath)) fs.writeFileSync(finalReleasePath, "release", "utf-8");
|
|
4764
|
+
};
|
|
4765
|
+
const releaseSupervisorReply = () => {
|
|
4766
|
+
if (!fs.existsSync(replyReleasePath)) fs.writeFileSync(replyReleasePath, "release", "utf-8");
|
|
4767
|
+
};
|
|
4768
|
+
try {
|
|
4769
|
+
const attentionDeadline = Date.now() + 10_000;
|
|
4770
|
+
let statusDuringAttention: AsyncStatusPayload | undefined;
|
|
4771
|
+
while (Date.now() < attentionDeadline && !fs.existsSync(resultPath)) {
|
|
4772
|
+
if (fs.existsSync(statusPath)) {
|
|
4773
|
+
const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
4774
|
+
if (nextStatus.currentTool === "contact_supervisor" && nextStatus.activityState === "needs_attention") {
|
|
4775
|
+
statusDuringAttention = nextStatus;
|
|
4776
|
+
break;
|
|
4777
|
+
}
|
|
4584
4778
|
}
|
|
4779
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4585
4780
|
}
|
|
4586
|
-
|
|
4587
|
-
}
|
|
4588
|
-
assert.ok(statusDuringAttention, "expected status.json to expose the blocking supervisor request");
|
|
4781
|
+
assert.ok(statusDuringAttention, "expected status.json to expose the blocking supervisor request");
|
|
4589
4782
|
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4783
|
+
try {
|
|
4784
|
+
const waitResult = await waitForSubagents({ id, timeoutMs: 3_500 }, undefined, {
|
|
4785
|
+
state: { currentSessionId: "session-1", foregroundRuns: new Map(), asyncJobs: new Map(), cleanupTimers: new Map(), resultFileCoalescer: new Map() },
|
|
4786
|
+
pollIntervalMs: 100,
|
|
4787
|
+
events: createEventBus(),
|
|
4788
|
+
});
|
|
4789
|
+
const waitText = waitResult.content[0]?.text ?? "";
|
|
4790
|
+
assert.equal(waitResult.isError, undefined);
|
|
4791
|
+
assert.match(waitText, /attention required/i);
|
|
4792
|
+
assert.match(waitText, new RegExp(id));
|
|
4793
|
+
assert.match(waitText, /intercom\(\{ action: "pending" \}\)/);
|
|
4794
|
+
assert.equal(fs.existsSync(resultPath), false, "wait should return before the child completes");
|
|
4795
|
+
} finally {
|
|
4796
|
+
releaseSupervisorReply();
|
|
4797
|
+
}
|
|
4601
4798
|
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4799
|
+
const eventText = fs.existsSync(eventsPath) ? fs.readFileSync(eventsPath, "utf-8") : "";
|
|
4800
|
+
assert.match(eventText, /"type":"needs_attention"/);
|
|
4801
|
+
assert.match(eventText, /"reason":"supervisor_request"/);
|
|
4802
|
+
assert.equal(statusDuringAttention.activityState, "needs_attention");
|
|
4803
|
+
assert.equal(statusDuringAttention.steps?.[0]?.activityState, "needs_attention");
|
|
4804
|
+
assert.equal(statusDuringAttention.currentTool, "contact_supervisor");
|
|
4805
|
+
assert.equal(statusDuringAttention.steps?.[0]?.currentTool, "contact_supervisor");
|
|
4806
|
+
|
|
4807
|
+
const clearDeadline = Date.now() + 10_000;
|
|
4808
|
+
let statusAfterReply: AsyncStatusPayload | undefined;
|
|
4809
|
+
while (Date.now() < clearDeadline && !fs.existsSync(resultPath)) {
|
|
4810
|
+
const nextStatus = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
4811
|
+
if (nextStatus.state === "running" && !nextStatus.currentTool && !nextStatus.steps?.[0]?.currentTool) {
|
|
4812
|
+
statusAfterReply = nextStatus;
|
|
4813
|
+
break;
|
|
4814
|
+
}
|
|
4815
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4617
4816
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
assert.equal(statusAfterReply.activityState, undefined);
|
|
4622
|
-
assert.equal(statusAfterReply.steps?.[0]?.activityState, undefined);
|
|
4817
|
+
assert.ok(statusAfterReply, "expected the child to keep running after the supervisor reply");
|
|
4818
|
+
assert.equal(statusAfterReply.activityState, undefined);
|
|
4819
|
+
assert.equal(statusAfterReply.steps?.[0]?.activityState, undefined);
|
|
4623
4820
|
|
|
4624
|
-
|
|
4821
|
+
fs.writeFileSync(finalReleasePath, "release", "utf-8");
|
|
4822
|
+
await waitForAsyncResultFile(id);
|
|
4823
|
+
} finally {
|
|
4824
|
+
releaseMockChild();
|
|
4825
|
+
}
|
|
4625
4826
|
});
|
|
4626
4827
|
|
|
4627
4828
|
it("background runs escalate repeated mutating tool failures", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -4640,9 +4841,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4640
4841
|
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
4641
4842
|
|
|
4642
4843
|
executeAsyncSingle(id, {
|
|
4643
|
-
agent: "
|
|
4844
|
+
agent: "worker",
|
|
4644
4845
|
task: "Implement the approved fixes",
|
|
4645
|
-
agentConfig: makeAgent("
|
|
4846
|
+
agentConfig: makeAgent("worker"),
|
|
4646
4847
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4647
4848
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4648
4849
|
shareEnabled: false,
|
|
@@ -4724,9 +4925,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4724
4925
|
const sessionRoot = path.join(tempDir, "sessions");
|
|
4725
4926
|
|
|
4726
4927
|
executeAsyncSingle(id, {
|
|
4727
|
-
agent: "
|
|
4928
|
+
agent: "worker",
|
|
4728
4929
|
task: "Stream noisy diagnostics",
|
|
4729
|
-
agentConfig: makeAgent("
|
|
4930
|
+
agentConfig: makeAgent("worker"),
|
|
4730
4931
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4731
4932
|
artifactConfig: {
|
|
4732
4933
|
enabled: false,
|
|
@@ -4775,9 +4976,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4775
4976
|
const sessionRoot = path.join(tempDir, "sessions");
|
|
4776
4977
|
|
|
4777
4978
|
executeAsyncSingle(id, {
|
|
4778
|
-
agent: "
|
|
4979
|
+
agent: "worker",
|
|
4779
4980
|
task: "Stream detailed progress",
|
|
4780
|
-
agentConfig: makeAgent("
|
|
4981
|
+
agentConfig: makeAgent("worker"),
|
|
4781
4982
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
4782
4983
|
artifactConfig: {
|
|
4783
4984
|
enabled: false,
|