@selesai/code 0.9.10 → 0.9.12
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 +271 -255
- package/dist/cli/args.js +13 -2
- package/dist/cli/args.test.js +8 -0
- package/dist/cli/credential-print.js +4 -4
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +4 -6
- package/dist/config.js +10 -14
- package/dist/core/agent-session-auto-handoff.test.js +24 -1
- package/dist/core/agent-session.d.ts +10 -4
- package/dist/core/agent-session.js +61 -46
- package/dist/core/compaction/branch-summarization.js +30 -30
- package/dist/core/compaction/compaction.js +81 -81
- package/dist/core/compaction/utils.js +2 -2
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.js +9 -0
- package/dist/core/export-html/template.css +1066 -1066
- package/dist/core/export-html/template.html +55 -55
- package/dist/core/export-html/template.js +1864 -1864
- package/dist/core/export-html/vendor/highlight.min.js +1212 -1212
- package/dist/core/export-html/vendor/marked.min.js +78 -78
- package/dist/core/extensions/loader.js +108 -35
- package/dist/core/extensions/loader.test.js +21 -0
- package/dist/core/extensions/types.d.ts +12 -4
- package/dist/core/keybindings.d.ts +2 -2
- package/dist/core/messages.js +7 -7
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.js +10 -4
- package/dist/core/package-manager.js +4 -4
- package/dist/core/sdk.d.ts +5 -5
- package/dist/core/sdk.js +16 -4
- package/dist/core/settings-manager.d.ts +6 -2
- package/dist/core/settings-manager.js +22 -3
- package/dist/core/tools/bash.d.ts +18 -1
- package/dist/core/tools/bash.js +41 -23
- package/dist/core/tools/index.d.ts +4 -2
- package/dist/core/tools/index.js +9 -2
- package/dist/core/tools/powershell.d.ts +15 -0
- package/dist/core/tools/powershell.js +38 -0
- package/dist/core/tools/powershell.test.d.ts +1 -0
- package/dist/core/tools/powershell.test.js +20 -0
- package/dist/defaults/settings.json +0 -1
- package/dist/extensions/context-compaction-reminder.test.ts +82 -82
- package/dist/extensions/context-compaction-reminder.ts +28 -28
- package/dist/extensions/model-prompt-injector/index.test.ts +0 -10
- package/dist/extensions/model-prompt-injector/index.ts +0 -3
- package/dist/extensions/pi-intercom/CHANGELOG.md +53 -16
- package/dist/extensions/pi-intercom/LICENSE +21 -21
- package/dist/extensions/pi-intercom/README.md +41 -8
- package/dist/extensions/pi-intercom/banner.png +0 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +498 -215
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -83
- package/dist/extensions/pi-intercom/broker/client.ts +39 -12
- package/dist/extensions/pi-intercom/broker/extension.test.ts +486 -387
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -114
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -153
- package/dist/extensions/pi-intercom/broker/paths.ts +134 -134
- package/dist/extensions/pi-intercom/broker/protocol.ts +26 -0
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -34
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -21
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +5 -20
- package/dist/extensions/pi-intercom/config.test.ts +41 -0
- package/dist/extensions/pi-intercom/config.ts +21 -0
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -40
- package/dist/extensions/pi-intercom/cwd.ts +31 -31
- package/dist/extensions/pi-intercom/extension-api.ts +84 -44
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -31
- package/dist/extensions/pi-intercom/format-context.ts +32 -32
- package/dist/extensions/pi-intercom/index.ts +362 -26
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +819 -13
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/project-agent.ts +2 -2
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +8 -110
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -66
- package/dist/extensions/pi-intercom/types.ts +32 -5
- package/dist/extensions/pi-intercom/ui/compose.ts +143 -143
- package/dist/extensions/pi-intercom/ui/inline-message.ts +6 -0
- package/dist/extensions/pi-intercom/ui/session-list.ts +166 -166
- package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +286 -286
- package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +108 -108
- package/dist/extensions/pi-powerline-footer/separators.ts +57 -57
- package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +47 -47
- package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +39 -39
- package/dist/extensions/pi-powerline-footer/theme.example.json +24 -24
- package/dist/extensions/pi-powerline-footer/theme.json +12 -12
- package/dist/extensions/pi-powerline-footer/tps.ts +345 -345
- package/dist/extensions/pi-rewind-hook/README.md +245 -245
- package/dist/extensions/pi-rewind-hook/index.ts +1445 -1445
- package/dist/extensions/pi-rewind-hook/package.json +29 -29
- package/dist/extensions/pi-subagents/.github/assets/subagent-fleet-inspector.png +0 -0
- package/dist/extensions/pi-subagents/.github/workflows/release.yml +28 -0
- package/dist/extensions/pi-subagents/.github/workflows/test.yml +51 -0
- package/dist/extensions/pi-subagents/.greptile/config.json +33 -0
- package/dist/extensions/pi-subagents/.greptile/files.json +8 -0
- package/dist/extensions/pi-subagents/.greptile/rules.md +16 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +309 -87
- package/dist/extensions/pi-subagents/README.md +15 -17
- package/dist/extensions/pi-subagents/agents/oracle.md +3 -1
- package/dist/extensions/pi-subagents/agents/researcher.md +32 -13
- package/dist/extensions/pi-subagents/agents/reviewer.md +13 -2
- package/dist/extensions/pi-subagents/agents/scout.md +2 -2
- package/dist/extensions/pi-subagents/agents/worker.md +2 -1
- package/dist/extensions/pi-subagents/async-retention-discovery-worker.mjs +180 -0
- package/dist/extensions/pi-subagents/docs/configuration.md +106 -27
- package/dist/extensions/pi-subagents/docs/extension-api.md +85 -21
- package/dist/extensions/pi-subagents/docs/missions.md +41 -4
- package/dist/extensions/pi-subagents/docs/models.md +52 -9
- package/dist/extensions/pi-subagents/docs/observability.md +103 -16
- package/dist/extensions/pi-subagents/docs/tool-reference.md +60 -45
- package/dist/extensions/pi-subagents/docs/watchdog.md +60 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +181 -7
- package/dist/extensions/pi-subagents/index.ts +10 -1
- package/dist/extensions/pi-subagents/install.mjs +4 -4
- package/dist/extensions/pi-subagents/package-lock.json +1483 -1470
- package/dist/extensions/pi-subagents/package.json +6 -3
- package/dist/extensions/pi-subagents/prompts/council.md +60 -0
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +5 -1
- package/dist/extensions/pi-subagents/prompts/review-loop.md +9 -5
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +254 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +18 -17
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +107 -140
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +44 -92
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +261 -368
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +6 -2
- package/dist/extensions/pi-subagents/src/agents/agents.ts +388 -85
- package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +14 -0
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +418 -0
- package/dist/extensions/pi-subagents/src/agents/skills.ts +1 -1
- package/dist/extensions/pi-subagents/src/api/agents.ts +7 -0
- package/dist/extensions/pi-subagents/src/api/external-job-provider.ts +195 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +88 -27
- package/dist/extensions/pi-subagents/src/api/project-panes.ts +2 -0
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +20 -11
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +14 -6
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -4
- package/dist/extensions/pi-subagents/src/extension/index.ts +270 -187
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +65 -28
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +120 -11
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +46 -64
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +51 -37
- package/dist/extensions/pi-subagents/src/extension/tool-result.ts +19 -0
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -2
- package/dist/extensions/pi-subagents/src/inspectors/herdr/client.ts +3 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/focus.ts +55 -0
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +230 -46
- package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +26 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +32 -10
- package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +6 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +4 -9
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/policy/authority.ts +2 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +31 -8
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +210 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +63 -33
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +71 -17
- package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +886 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +14 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +49 -56
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +3 -33
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +22 -12
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +105 -71
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +68 -19
- package/dist/extensions/pi-subagents/src/runs/background/index-segment.ts +59 -0
- package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +443 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +94 -10
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +158 -90
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +141 -53
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +8 -5
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +14 -4
- package/dist/extensions/pi-subagents/src/runs/background/run-id-query.ts +7 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +13 -14
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +46 -25
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +823 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +9 -3
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +594 -216
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +17 -13
- package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +138 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +23 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +82 -4
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +5 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +21 -14
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +23 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +153 -58
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -9
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1283 -1657
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +274 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +59 -10
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +52 -2
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/extension-bindings.ts +78 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-job-bridge.ts +460 -0
- package/dist/extensions/pi-subagents/src/runs/shared/external-job-runner.ts +389 -0
- package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +10 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +243 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +201 -85
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +106 -39
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +145 -0
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +150 -27
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +46 -4
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +19 -15
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +86 -2
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +9 -9
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +0 -6
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +18 -4
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +51 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +62 -21
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +1 -23
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +14 -2
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +2 -2
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +22 -2
- package/dist/extensions/pi-subagents/src/shared/capacity-resilient-json.ts +102 -0
- package/dist/extensions/pi-subagents/src/shared/completion-owner.ts +14 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +49 -1
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +59 -1
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +12 -5
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +20 -0
- package/dist/extensions/pi-subagents/src/shared/prompt-resources.ts +0 -40
- package/dist/extensions/pi-subagents/src/shared/settings.ts +11 -30
- package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +228 -49
- package/dist/extensions/pi-subagents/src/shared/utils.ts +11 -3
- package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +10 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +0 -162
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +2 -5
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +105 -13
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +6 -36
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +829 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +1415 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +78 -7
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +1 -1
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +1 -0
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +59 -51
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +8 -2
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +2 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +4 -4
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +471 -86
- package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +140 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
- 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 +1 -1
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +0 -54
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1053 -414
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +194 -4
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +59 -4
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +0 -77
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +107 -6
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +211 -897
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +265 -375
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +77 -7
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +10 -10
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +547 -30
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +2112 -310
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +274 -34
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +9 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +30 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +4 -2
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +1 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +197 -0
- package/dist/extensions/pi-subagents/test/unit/agent-contract.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +204 -27
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +444 -145
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +256 -4
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +9 -7
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +152 -22
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +29 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +205 -23
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/capacity-resilient-json.test.ts +155 -0
- package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +81 -3
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +109 -7
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -2
- package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/external-job-runner.test.ts +961 -0
- package/dist/extensions/pi-subagents/test/unit/file-system-retry.test.ts +64 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +1110 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +1664 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +81 -1
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +138 -11
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +41 -2
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +210 -79
- package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +95 -0
- package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +596 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +3 -5
- package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +167 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +319 -18
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +19 -5
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +129 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +231 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +28 -1
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +106 -2
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +204 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +59 -4
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +34 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -6
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +12 -3
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +56 -61
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +416 -1
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +9 -2
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +42 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +21 -30
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +86 -11
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +233 -5
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +28 -5
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +323 -20
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +66 -10
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -5
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +174 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +679 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +67 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +55 -44
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +399 -84
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +15 -10
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +11 -7
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +40 -1
- package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +10 -2
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +38 -3
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +110 -12
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +204 -19
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +25 -0
- package/dist/extensions/pi-subagents/test/unit/thinking-ceiling.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +90 -50
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +234 -7
- package/dist/extensions/pi-subagents/test/unit/watch-strategy.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +37 -9
- package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +302 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +167 -7
- package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +6 -6
- package/dist/extensions/pi-web-agent/package.json +31 -31
- package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -205
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -136
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -152
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -25
- package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -28
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +136 -136
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -946
- package/dist/extensions/pi-web-agent/src/extension.ts +126 -126
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -118
- package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -199
- package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -100
- package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -117
- package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -67
- package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -60
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -50
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -52
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -105
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -45
- package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -28
- package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -47
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -376
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -64
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -181
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -101
- package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -81
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +210 -210
- package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -75
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -61
- package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -54
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -41
- package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -20
- package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -63
- package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -114
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -72
- package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -96
- package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -71
- package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -31
- package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -31
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -157
- package/dist/extensions/pi-web-agent/src/types.ts +88 -88
- package/dist/extensions/ponytail/index.js +31 -25
- package/dist/extensions/ponytail/package.json +8 -8
- package/dist/extensions/ponytail/ponytail-config.cjs +34 -7
- package/dist/extensions/ponytail/ponytail-instructions.cjs +7 -9
- package/dist/extensions/ponytail/test/extension.test.js +7 -8
- package/dist/extensions/ponytail/test/helpers.test.js +1 -1
- package/dist/extensions/question/batch.ts +103 -103
- package/dist/extensions/question/constants.ts +30 -30
- package/dist/extensions/question/helpers.ts +58 -58
- package/dist/extensions/question/navigation.ts +14 -14
- package/dist/extensions/question/package.json +19 -19
- package/dist/extensions/question/schemas.ts +43 -43
- package/dist/extensions/question/selection-mode.ts +46 -46
- package/dist/extensions/question/shortcuts.ts +44 -44
- package/dist/extensions/question/types.ts +132 -132
- package/dist/extensions/test-resolve-hook-impl.mjs +6 -6
- package/dist/extensions/test-resolve-hook.mjs +6 -6
- package/dist/extensions/web-agent-onboarding.ts +222 -222
- package/dist/extensions/workflow/package.json +17 -17
- package/dist/package-manager-cli.js +71 -71
- package/dist/rpc-entry.js +0 -0
- package/dist/skills/agent-browser/SKILL.md +52 -52
- package/dist/skills/batch-grill-me/SKILL.md +19 -19
- package/dist/skills/brandkit/SKILL.md +799 -0
- package/dist/skills/design-taste-frontend/SKILL.md +1207 -0
- package/dist/skills/design-taste-frontend-v1/SKILL.md +227 -0
- package/dist/skills/full-output-enforcement/SKILL.md +50 -0
- package/dist/skills/gpt-taste/SKILL.md +75 -0
- package/dist/skills/grill-me/SKILL.md +10 -10
- package/dist/skills/handoff/SKILL.md +16 -16
- package/dist/skills/handoff-text/SKILL.md +14 -14
- package/dist/skills/high-end-visual-design/SKILL.md +99 -0
- package/dist/skills/image-to-code/SKILL.md +1229 -0
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +1466 -0
- package/dist/skills/imagegen-frontend-web/SKILL.md +988 -0
- package/dist/skills/implanger/SKILL.md +69 -69
- package/dist/skills/improve-codebase/REFERENCE.md +78 -78
- package/dist/skills/improve-codebase/SKILL.md +178 -178
- package/dist/skills/industrial-brutalist-ui/SKILL.md +93 -0
- package/dist/skills/minimalist-ui/SKILL.md +86 -0
- package/dist/skills/pi-subagents/SKILL.md +18 -17
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +31 -9
- package/dist/skills/pi-subagents/references/execution-controls.md +95 -143
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +9 -12
- package/dist/skills/pi-subagents/references/multi-lane-orchestration.md +39 -0
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +45 -94
- package/dist/skills/planger/SKILL.md +166 -166
- package/dist/skills/ponytail/SKILL.md +120 -116
- package/dist/skills/ponytail-audit/SKILL.md +41 -42
- package/dist/skills/ponytail-debt/SKILL.md +44 -45
- package/dist/skills/ponytail-gain/SKILL.md +50 -51
- package/dist/skills/ponytail-help/SKILL.md +71 -70
- package/dist/skills/ponytail-review/SKILL.md +57 -58
- package/dist/skills/redesign-existing-projects/SKILL.md +179 -0
- package/dist/skills/selesai-handoff/SKILL.md +20 -20
- package/dist/skills/stitch-design-taste/SKILL.md +185 -0
- package/dist/skills/unlazy/CHANGELOG.md +100 -0
- package/dist/skills/unlazy/CONTRIBUTING.md +66 -0
- package/dist/skills/unlazy/LICENSE +21 -0
- package/dist/skills/unlazy/README.md +242 -0
- package/dist/skills/unlazy/SECURITY.md +70 -0
- package/dist/skills/unlazy/SKILL.md +100 -0
- package/dist/skills/unlazy/package.json +13 -0
- package/dist/skills/unlazy/references/dispatch.md +82 -0
- package/dist/skills/unlazy/references/gates.md +132 -0
- package/dist/skills/unlazy/references/method.md +49 -0
- package/dist/skills/unlazy/references/orchestration.md +99 -0
- package/dist/skills/unlazy/references/parallel.md +123 -0
- package/dist/skills/unlazy/references/token-economy.md +39 -0
- package/dist/skills/unlazy/research/validation-protocol.md +81 -0
- package/dist/skills/unlazy/scripts/dispatch-check.mjs +108 -0
- package/dist/skills/unlazy/scripts/gate-check.mjs +894 -0
- package/dist/skills/unlazy/scripts/gate-lint.mjs +178 -0
- package/dist/skills/unlazy/scripts/install-hooks.mjs +206 -0
- package/dist/skills/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/dist/skills/unlazy/scripts/lib/dispatch.mjs +283 -0
- package/dist/skills/unlazy/scripts/lib/gates.mjs +629 -0
- package/dist/skills/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/dist/skills/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/dist/skills/unlazy/scripts/stop-hook.mjs +174 -0
- package/dist/skills/unlazy/templates/PLAN.md +65 -0
- package/dist/skills/unlazy/templates/gates-leaf.md +51 -0
- package/dist/skills/unlazy/templates/gates-node.md +50 -0
- package/dist/skills/unlazy/tests/contract-tests.mjs +127 -0
- package/dist/skills/unlazy/tests/dispatch-tests.mjs +481 -0
- package/dist/skills/unlazy/tests/hardening-tests.mjs +903 -0
- package/dist/skills/unlazy/tests/lint-tests.mjs +242 -0
- package/dist/skills/unlazy/tests/run-tests.mjs +554 -0
- package/dist/skills/unlazy/tests/self-check.mjs +165 -0
- package/dist/skills/unlazy/tests/stress-tests.mjs +356 -0
- package/dist/skills/web-design-guidelines/SKILL.md +40 -0
- package/dist/skills/workflow-creation/SKILL.md +73 -73
- package/dist/themes/powerline-footer/theme.json +33 -33
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.js +17 -5
- package/docs/compaction.md +396 -396
- package/docs/containerization.md +111 -111
- package/docs/development.md +71 -71
- package/docs/docs.json +164 -164
- package/docs/environment-variables.md +86 -86
- package/docs/index.md +83 -83
- package/docs/json.md +82 -82
- package/docs/models.md +502 -502
- package/docs/packages.md +227 -227
- package/docs/plans/subagent-delegation/phase-0-correctness.md +264 -264
- package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +485 -485
- package/docs/plans/subagent-delegation/phase-2-context-controls.md +281 -281
- package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +361 -361
- package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +380 -380
- package/docs/prompt-templates.md +95 -95
- package/docs/providers.md +293 -293
- package/docs/sdk.md +1144 -1143
- package/docs/security.md +59 -59
- package/docs/session-format.md +414 -414
- package/docs/sessions.md +145 -145
- package/docs/settings.md +0 -5
- package/docs/shared-host-extensions.md +109 -109
- package/docs/shell-aliases.md +13 -13
- package/docs/skills.md +231 -231
- package/docs/terminal-setup.md +142 -142
- package/docs/termux.md +127 -127
- package/docs/themes.md +295 -295
- package/docs/tmux.md +63 -63
- package/docs/tui.md +927 -927
- package/docs/windows.md +17 -17
- package/examples/README.md +25 -25
- package/examples/extensions/README.md +211 -211
- package/examples/extensions/auto-commit-on-exit.ts +49 -49
- package/examples/extensions/bash-spawn-hook.ts +30 -30
- package/examples/extensions/bookmark.ts +50 -50
- package/examples/extensions/border-status-editor.ts +150 -150
- package/examples/extensions/built-in-tool-renderer.ts +249 -249
- package/examples/extensions/claude-rules.ts +86 -86
- package/examples/extensions/commands.ts +72 -72
- package/examples/extensions/confirm-destructive.ts +59 -59
- package/examples/extensions/custom-compaction.ts +130 -130
- package/examples/extensions/custom-footer.ts +64 -64
- package/examples/extensions/custom-header.ts +73 -73
- package/examples/extensions/custom-provider-anthropic/index.ts +610 -610
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -24
- package/examples/extensions/custom-provider-anthropic/package.json +19 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +404 -404
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -82
- package/examples/extensions/dirty-repo-guard.ts +56 -56
- package/examples/extensions/doom-overlay/README.md +46 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -72
- package/examples/extensions/doom-overlay/doom-component.ts +132 -132
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -104
- package/examples/extensions/doom-overlay/index.ts +74 -74
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -51
- package/examples/extensions/dynamic-resources/SKILL.md +8 -8
- package/examples/extensions/dynamic-resources/dynamic.json +79 -79
- package/examples/extensions/dynamic-resources/dynamic.md +5 -5
- package/examples/extensions/dynamic-resources/index.ts +15 -15
- package/examples/extensions/dynamic-tools.ts +74 -74
- package/examples/extensions/event-bus.ts +43 -43
- package/examples/extensions/file-trigger.ts +41 -41
- package/examples/extensions/git-checkpoint.ts +53 -53
- package/examples/extensions/git-merge-and-resolve.ts +115 -115
- package/examples/extensions/github-issue-autocomplete.ts +185 -185
- package/examples/extensions/gondolin/index.ts +531 -531
- package/examples/extensions/gondolin/package-lock.json +185 -185
- package/examples/extensions/gondolin/package.json +19 -19
- package/examples/extensions/handoff.ts +199 -199
- package/examples/extensions/hello.ts +26 -26
- package/examples/extensions/hidden-thinking-label.ts +53 -53
- package/examples/extensions/inline-bash.ts +94 -94
- package/examples/extensions/input-transform-streaming.ts +39 -39
- package/examples/extensions/input-transform.ts +43 -43
- package/examples/extensions/interactive-shell.ts +196 -196
- package/examples/extensions/mac-system-theme.ts +47 -47
- package/examples/extensions/message-renderer.ts +59 -59
- package/examples/extensions/minimal-mode.ts +426 -426
- package/examples/extensions/modal-editor.ts +85 -85
- package/examples/extensions/model-status.ts +31 -31
- package/examples/extensions/notify.ts +55 -55
- package/examples/extensions/overlay-qa-tests.ts +1450 -1450
- package/examples/extensions/overlay-test.ts +153 -153
- package/examples/extensions/permission-gate.ts +34 -34
- package/examples/extensions/pirate.ts +47 -47
- package/examples/extensions/plan-mode/README.md +66 -66
- package/examples/extensions/plan-mode/index.ts +390 -390
- package/examples/extensions/plan-mode/utils.ts +168 -168
- package/examples/extensions/preset.ts +436 -436
- package/examples/extensions/project-trust.ts +64 -64
- package/examples/extensions/prompt-customizer.ts +97 -97
- package/examples/extensions/protected-paths.ts +30 -30
- package/examples/extensions/provider-payload.ts +18 -18
- package/examples/extensions/qna.ts +122 -122
- package/examples/extensions/question.ts +285 -285
- package/examples/extensions/questionnaire.ts +448 -448
- package/examples/extensions/rainbow-editor.ts +88 -88
- package/examples/extensions/reload-runtime.ts +37 -37
- package/examples/extensions/rpc-demo.ts +118 -118
- package/examples/extensions/sandbox/index.ts +321 -321
- package/examples/extensions/sandbox/package-lock.json +92 -92
- package/examples/extensions/sandbox/package.json +19 -19
- package/examples/extensions/send-user-message.ts +97 -97
- package/examples/extensions/session-name.ts +27 -27
- package/examples/extensions/shutdown-command.ts +63 -63
- package/examples/extensions/snake.ts +343 -343
- package/examples/extensions/space-invaders.ts +560 -560
- package/examples/extensions/ssh.ts +220 -220
- package/examples/extensions/status-line.ts +32 -32
- package/examples/extensions/structured-output.ts +65 -65
- package/examples/extensions/subagent/README.md +175 -175
- package/examples/extensions/subagent/agents/planner.md +37 -37
- package/examples/extensions/subagent/agents/reviewer.md +35 -35
- package/examples/extensions/subagent/agents/scout.md +50 -50
- package/examples/extensions/subagent/agents/worker.md +24 -24
- package/examples/extensions/subagent/agents.ts +126 -126
- package/examples/extensions/subagent/index.ts +1015 -1015
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -10
- package/examples/extensions/subagent/prompts/implement.md +10 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -9
- package/examples/extensions/summarize.ts +209 -209
- package/examples/extensions/system-prompt-header.ts +17 -17
- package/examples/extensions/tic-tac-toe.ts +1008 -1008
- package/examples/extensions/timed-confirm.ts +70 -70
- package/examples/extensions/titlebar-spinner.ts +58 -58
- package/examples/extensions/todo.ts +297 -297
- package/examples/extensions/tool-override.ts +144 -144
- package/examples/extensions/tools.ts +146 -146
- package/examples/extensions/trigger-compact.ts +50 -50
- package/examples/extensions/truncated-tool.ts +195 -195
- package/examples/extensions/widget-placement.ts +9 -9
- package/examples/extensions/with-deps/index.ts +32 -32
- package/examples/extensions/with-deps/package-lock.json +31 -31
- package/examples/extensions/with-deps/package.json +22 -22
- package/examples/extensions/working-indicator.ts +123 -123
- package/examples/extensions/working-message-test.ts +25 -25
- package/examples/rpc-extension-ui.ts +632 -632
- package/examples/sdk/01-minimal.ts +26 -26
- package/examples/sdk/02-custom-model.ts +53 -53
- package/examples/sdk/03-custom-prompt.ts +75 -75
- package/examples/sdk/04-skills.ts +55 -55
- package/examples/sdk/05-tools.ts +48 -48
- package/examples/sdk/06-extensions.ts +99 -99
- package/examples/sdk/07-context-files.ts +47 -47
- package/examples/sdk/08-prompt-templates.ts +51 -51
- package/examples/sdk/09-api-keys-and-oauth.ts +52 -52
- package/examples/sdk/10-settings.ts +53 -53
- package/examples/sdk/11-sessions.ts +52 -52
- package/examples/sdk/12-full-control.ts +79 -79
- package/examples/sdk/13-session-runtime.ts +67 -67
- package/examples/sdk/README.md +144 -144
- package/package.json +4 -4
- package/dist/core/tools/schema-prune.d.ts +0 -16
- package/dist/core/tools/schema-prune.js +0 -39
- package/dist/core/tools/schema-prune.test.js +0 -111
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +0 -172
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +0 -55
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +0 -61
- package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +0 -132
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +0 -181
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +0 -1354
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +0 -1581
- package/dist/extensions/pi-subagents/src/shared/env.ts +0 -16
- package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +0 -71
- package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +0 -136
- package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +0 -41
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +0 -466
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +0 -1859
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +0 -733
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +0 -63
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +0 -326
- package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +0 -103
- package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +0 -127
- package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +0 -73
- package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +0 -213
- package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +0 -65
- /package/dist/core/{tools/schema-prune.test.d.ts → extensions/loader.test.d.ts} +0 -0
|
@@ -15,12 +15,12 @@ import * as os from "node:os";
|
|
|
15
15
|
import * as path from "node:path";
|
|
16
16
|
import { createEventBus, createMockPi, createTempDir, events, makeAgent, makeMinimalCtx, removeTempDir, tryImport } from "../support/helpers.ts";
|
|
17
17
|
import type { MockPi } from "../support/helpers.ts";
|
|
18
|
-
import {
|
|
18
|
+
import { deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest } from "../../src/runs/background/control-channel.ts";
|
|
19
19
|
import { waitForSubagents } from "../../src/runs/background/subagent-wait.ts";
|
|
20
20
|
import { writeAtomicJson } from "../../src/shared/atomic-json.ts";
|
|
21
21
|
import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
|
|
22
22
|
import { MAX_CHILD_PENDING_LINE_BYTES, MAX_CHILD_STDERR_BYTES } from "../../src/runs/shared/child-protocol.ts";
|
|
23
|
-
import { SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION } from "../../src/shared/types.ts";
|
|
23
|
+
import { SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, TEMP_ARTIFACTS_DIR } from "../../src/shared/types.ts";
|
|
24
24
|
import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceiling.ts";
|
|
25
25
|
import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
|
|
26
26
|
import { discoverAgents } from "../../src/agents/agents.ts";
|
|
@@ -55,7 +55,7 @@ interface UsageBudgetState {
|
|
|
55
55
|
interface AsyncExecutionResult {
|
|
56
56
|
content: Array<{ text?: string }>;
|
|
57
57
|
isError?: boolean;
|
|
58
|
-
details: { asyncId?: string; asyncDir?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; usageBudget?: UsageBudgetState
|
|
58
|
+
details: { asyncId?: string; asyncDir?: string; launchContractDigest?: string; launchResolvedExtensions?: LaunchResolvedExtensions; runtimeAcknowledgedExtensions?: RuntimeAcknowledgedExtensions; usageBudget?: UsageBudgetState };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
interface AsyncResultPayload {
|
|
@@ -80,8 +80,7 @@ interface AsyncResultPayload {
|
|
|
80
80
|
totalTokens?: { input: number; output: number; total: number };
|
|
81
81
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
82
82
|
usageBudget?: UsageBudgetState;
|
|
83
|
-
|
|
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 } }>;
|
|
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; timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string }; 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; message?: string } }; 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 } }>;
|
|
85
84
|
outputs?: Record<string, { text?: string; structured?: unknown }>;
|
|
86
85
|
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 }> }> };
|
|
87
86
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
@@ -112,7 +111,6 @@ interface AsyncStatusPayload {
|
|
|
112
111
|
totalTokens?: { total: number };
|
|
113
112
|
totalCost?: { inputTokens: number; outputTokens: number; costUsd: number };
|
|
114
113
|
usageBudget?: UsageBudgetState;
|
|
115
|
-
checkpoint?: { name?: string; status?: string; message?: string };
|
|
116
114
|
parallelGroups?: Array<{ start: number; count: number; stepIndex: number }>;
|
|
117
115
|
parallelHandoff?: { version?: number; path?: string; groupCount?: number; childCount?: number; changedPatches?: number; cleanupState?: string };
|
|
118
116
|
capabilityCeiling?: { version?: number; allowedTools?: string[]; denyExtensions?: boolean; sources?: string[] };
|
|
@@ -128,6 +126,7 @@ interface AsyncStatusPayload {
|
|
|
128
126
|
status?: string;
|
|
129
127
|
exitCode?: number;
|
|
130
128
|
timedOut?: boolean;
|
|
129
|
+
timeoutRecovery?: { changedFiles?: string[]; message?: string; warning?: string };
|
|
131
130
|
error?: string;
|
|
132
131
|
model?: string;
|
|
133
132
|
thinking?: string;
|
|
@@ -151,6 +150,7 @@ interface AsyncStatusPayload {
|
|
|
151
150
|
interface MockPiCallRecord {
|
|
152
151
|
args?: string[];
|
|
153
152
|
systemPrompts?: Array<{ mode?: string; path?: string; text?: string; error?: string }>;
|
|
153
|
+
requiredChildTools?: string[];
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
function writeWatchdogSettings(projectDir: string, tailMs = 120_000): void {
|
|
@@ -331,6 +331,20 @@ async function waitForAsyncResultFile(id: string, timeoutMs = 15_000): Promise<s
|
|
|
331
331
|
return resultPath;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
async function waitForAsyncEvent(id: string, type: string, timeoutMs = 10_000): Promise<Record<string, unknown>> {
|
|
335
|
+
const eventsPath = path.join(ASYNC_DIR, id, "events.jsonl");
|
|
336
|
+
const deadline = Date.now() + timeoutMs;
|
|
337
|
+
while (Date.now() <= deadline) {
|
|
338
|
+
const event = readIfExists(eventsPath)
|
|
339
|
+
?.split("\n")
|
|
340
|
+
.map((line) => JSON.parse(line) as Record<string, unknown>)
|
|
341
|
+
.find((candidate) => candidate.type === type);
|
|
342
|
+
if (event) return event;
|
|
343
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
344
|
+
}
|
|
345
|
+
assert.fail(`Timed out waiting for async event '${type}': ${eventsPath}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
334
348
|
async function waitForAsyncState(id: string, predicate: (status: AsyncStatusPayload) => boolean, timeoutMs = 10_000): Promise<AsyncStatusPayload> {
|
|
335
349
|
const statusPath = path.join(ASYNC_DIR, id, "status.json");
|
|
336
350
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -405,6 +419,17 @@ function readMockPiArgs(mockPi: MockPi, index: number): string[] {
|
|
|
405
419
|
return payload.args;
|
|
406
420
|
}
|
|
407
421
|
|
|
422
|
+
function readMockPiRequiredTools(mockPi: MockPi, index: number): string[] {
|
|
423
|
+
const callFile = fs.readdirSync(mockPi.dir)
|
|
424
|
+
.filter((name) => name.startsWith("call-") && name.endsWith(".json"))
|
|
425
|
+
.sort()
|
|
426
|
+
.at(index);
|
|
427
|
+
assert.ok(callFile, `expected recorded call ${index}`);
|
|
428
|
+
const payload = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")) as MockPiCallRecord;
|
|
429
|
+
assert.ok(Array.isArray(payload.requiredChildTools), "expected recorded required child tools");
|
|
430
|
+
return payload.requiredChildTools;
|
|
431
|
+
}
|
|
432
|
+
|
|
408
433
|
function readMockPiArgsMatching(mockPi: MockPi, text: string): string[] {
|
|
409
434
|
const callFiles = fs.readdirSync(mockPi.dir)
|
|
410
435
|
.filter((name) => name.startsWith("call-") && name.endsWith(".json"))
|
|
@@ -476,6 +501,52 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
476
501
|
assert.equal(typeof result, "boolean");
|
|
477
502
|
});
|
|
478
503
|
|
|
504
|
+
it("does not persist terminal async workflow status when the result index write fails", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
505
|
+
const id = `async-workflow-result-index-failure-${Date.now().toString(36)}`;
|
|
506
|
+
const resultIndexPath = path.join(RESULTS_DIR, "result-index");
|
|
507
|
+
let asyncDir: string | undefined;
|
|
508
|
+
let resultPath: string | undefined;
|
|
509
|
+
let pendingPath: string | undefined;
|
|
510
|
+
const originalError = console.error;
|
|
511
|
+
try {
|
|
512
|
+
console.error = () => {};
|
|
513
|
+
fs.rmSync(resultIndexPath, { recursive: true, force: true });
|
|
514
|
+
fs.mkdirSync(RESULTS_DIR, { recursive: true });
|
|
515
|
+
fs.writeFileSync(resultIndexPath, "not a directory", "utf-8");
|
|
516
|
+
const executor = makeAsyncExecutor([]);
|
|
517
|
+
const context = makeMinimalCtx(tempDir);
|
|
518
|
+
context.sessionManager.getSessionId = () => "session-workflow-index";
|
|
519
|
+
|
|
520
|
+
const launch = await executor.execute(id, { workflowScript: "return 'done'", async: true }, new AbortController().signal, undefined, context);
|
|
521
|
+
assert.equal(launch.isError, undefined);
|
|
522
|
+
const asyncId = launch.details?.asyncId;
|
|
523
|
+
assert.ok(asyncId, "expected async workflow id");
|
|
524
|
+
asyncDir = path.join(ASYNC_DIR, asyncId);
|
|
525
|
+
resultPath = path.join(RESULTS_DIR, `${asyncId}.json`);
|
|
526
|
+
pendingPath = path.join(RESULTS_DIR, "result-pending", encodeURIComponent("session-workflow-index"), `${encodeURIComponent(asyncId)}.json`);
|
|
527
|
+
|
|
528
|
+
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
529
|
+
const deadline = Date.now() + 5_000;
|
|
530
|
+
let eventsText = "";
|
|
531
|
+
while (Date.now() <= deadline) {
|
|
532
|
+
eventsText = readIfExists(eventsPath) ?? "";
|
|
533
|
+
if (eventsText.includes("subagent.workflow.result_write_failed")) break;
|
|
534
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
535
|
+
}
|
|
536
|
+
assert.match(eventsText, /subagent\.workflow\.result_write_failed/);
|
|
537
|
+
const status = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
538
|
+
assert.equal(status.state, "running");
|
|
539
|
+
assert.equal(fs.existsSync(resultPath), false);
|
|
540
|
+
assert.equal(fs.existsSync(pendingPath), true);
|
|
541
|
+
} finally {
|
|
542
|
+
console.error = originalError;
|
|
543
|
+
if (asyncDir) fs.rmSync(asyncDir, { recursive: true, force: true });
|
|
544
|
+
if (resultPath) fs.rmSync(resultPath, { recursive: true, force: true });
|
|
545
|
+
if (pendingPath) fs.rmSync(pendingPath, { force: true });
|
|
546
|
+
fs.rmSync(resultIndexPath, { recursive: true, force: true });
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
479
550
|
it("background parses split UTF-8 JSON and a final unterminated protocol line", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
480
551
|
const line = Buffer.from(JSON.stringify(events.assistantMessage("你好 from fragmented async JSON")));
|
|
481
552
|
const unicodeStart = line.indexOf(Buffer.from("你"));
|
|
@@ -487,7 +558,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
487
558
|
assert.equal(payload.results[0]?.output, "你好 from fragmented async JSON");
|
|
488
559
|
});
|
|
489
560
|
|
|
490
|
-
it("persists terminal status
|
|
561
|
+
it("persists terminal status with the result artifact", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
491
562
|
mockPi.onCall({ output: "completed output" });
|
|
492
563
|
const id = `async-terminal-status-${Date.now().toString(36)}`;
|
|
493
564
|
launchProtocolTest(id);
|
|
@@ -497,6 +568,30 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
497
568
|
assert.equal(status.endedAt !== undefined, true);
|
|
498
569
|
});
|
|
499
570
|
|
|
571
|
+
it("persists intercom detach receipts on failed async steps", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
572
|
+
mockPi.onCall({ output: "Detached for intercom coordination before task completion.", exitCode: -2 });
|
|
573
|
+
const id = `async-intercom-detach-${Date.now().toString(36)}`;
|
|
574
|
+
launchProtocolTest(id);
|
|
575
|
+
await waitForAsyncResultFile(id);
|
|
576
|
+
const status = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "status.json"), "utf-8")) as AsyncStatusPayload;
|
|
577
|
+
assert.equal(status.state, "failed");
|
|
578
|
+
assert.equal(status.error, "Step failed: worker");
|
|
579
|
+
assert.equal(status.steps?.[0]?.error, "Detached for intercom coordination before task completion.");
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("persists actionable guidance for ambient extension registration conflicts", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
583
|
+
mockPi.onCall({
|
|
584
|
+
exitCode: 1,
|
|
585
|
+
stderr: 'Error: Failed to load extension "/tmp/pi-mcp-adapter-clone/index.ts": Flag "--mcp-config" conflicts with /tmp/pi-mcp-adapter/index.ts',
|
|
586
|
+
});
|
|
587
|
+
const id = `async-extension-conflict-${Date.now().toString(36)}`;
|
|
588
|
+
launchProtocolTest(id);
|
|
589
|
+
const payload = await readAsyncPayload(id);
|
|
590
|
+
assert.equal(payload.success, false);
|
|
591
|
+
assert.match(payload.results[0]?.error ?? "", /loaded conflicting ambient Pi extensions/);
|
|
592
|
+
assert.match(payload.results[0]?.error ?? "", /"worker":\{"extensions":\[\]\}/);
|
|
593
|
+
});
|
|
594
|
+
|
|
500
595
|
it("persists absent output provenance when async lifecycle text is synthetic", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
501
596
|
mockPi.onCall({ jsonl: [mockAssistantMessage("", "tool_use")], stderr: "mock child failure", exitCode: 1 });
|
|
502
597
|
const id = `async-output-absent-${Date.now().toString(36)}`;
|
|
@@ -521,37 +616,50 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
521
616
|
const agentName = `contract-worker-${Date.now().toString(36)}`;
|
|
522
617
|
const task = "Compare the resolved launch inputs.";
|
|
523
618
|
const turnBudget = { maxTurns: 2, graceTurns: 1 } as const;
|
|
619
|
+
const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
|
|
620
|
+
const agentDir = path.join(tempDir, "agent-home");
|
|
621
|
+
process.env.SELESAI_CODING_AGENT_DIR = agentDir;
|
|
622
|
+
const permissionExtDir = path.join(agentDir, "extensions", "pi-permission-system");
|
|
623
|
+
fs.mkdirSync(path.join(permissionExtDir, "src"), { recursive: true });
|
|
624
|
+
fs.writeFileSync(path.join(permissionExtDir, "src", "index.ts"), "export default () => {};", "utf-8");
|
|
625
|
+
fs.writeFileSync(path.join(permissionExtDir, "package.json"), JSON.stringify({ name: "test", pi: { extensions: ["./src/index.ts"] } }), "utf-8");
|
|
524
626
|
const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
|
|
525
627
|
fs.mkdirSync(path.dirname(agentPath), { recursive: true });
|
|
526
|
-
fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison worker\n---\n`, "utf-8");
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
628
|
+
fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Contract comparison worker\npermissions:\n write: ask\n---\n`, "utf-8");
|
|
629
|
+
try {
|
|
630
|
+
const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
|
|
631
|
+
assert.ok(discovered, "expected temporary agent definition to be discovered");
|
|
632
|
+
const preflight = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, turnBudget, runId: "contract-preflight" });
|
|
633
|
+
assert.equal(preflight.ok, true);
|
|
634
|
+
assert.ok(preflight.contract.tools.extensionArgs.some((entry) => entry.endsWith(path.join("pi-permission-system", "src", "index.ts"))));
|
|
635
|
+
|
|
636
|
+
mockPi.onCall({ output: "foreground contract comparison" });
|
|
637
|
+
const foreground = await runSync(tempDir, [discovered], agentName, task, { runId: "contract-foreground", acceptance: false, turnBudget });
|
|
638
|
+
assert.equal(foreground.exitCode, 0);
|
|
639
|
+
assert.equal(foreground.launchContractDigest, preflight.contract.launchContractDigest);
|
|
640
|
+
|
|
641
|
+
mockPi.onCall({ output: "async contract comparison" });
|
|
642
|
+
const asyncId = `async-contract-equivalence-${Date.now().toString(36)}`;
|
|
643
|
+
const launch = executeAsyncSingle(asyncId, {
|
|
644
|
+
agent: agentName,
|
|
645
|
+
task,
|
|
646
|
+
agentConfig: discovered,
|
|
647
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
648
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
649
|
+
shareEnabled: false,
|
|
650
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
651
|
+
maxSubagentDepth: 2,
|
|
652
|
+
acceptance: false,
|
|
653
|
+
turnBudget,
|
|
654
|
+
});
|
|
655
|
+
const payload = await readAsyncPayload(asyncId);
|
|
656
|
+
assert.equal(launch.details.launchContractDigest, preflight.contract.launchContractDigest);
|
|
657
|
+
assert.equal(payload.launchContractDigest, preflight.contract.launchContractDigest);
|
|
658
|
+
assert.equal(payload.results[0]?.launchContractDigest, preflight.contract.launchContractDigest);
|
|
659
|
+
} finally {
|
|
660
|
+
if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
|
|
661
|
+
else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
|
|
662
|
+
}
|
|
555
663
|
});
|
|
556
664
|
|
|
557
665
|
it("persists the actual launch digest in async status and result metadata", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -561,20 +669,28 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
561
669
|
});
|
|
562
670
|
const id = `async-launch-digest-${Date.now().toString(36)}`;
|
|
563
671
|
const privateExtension = path.join(tempDir, "extensions", "private-extension.ts");
|
|
672
|
+
const recoveryAgentConfig = makeAgent("worker", { completionGuard: false, extensions: [privateExtension], tools: ["read"], systemPrompt: "Base prompt" });
|
|
564
673
|
const launch = executeAsyncSingle(id, {
|
|
565
674
|
agent: "worker",
|
|
566
675
|
task: "Exercise launch digest reporting",
|
|
567
|
-
agentConfig:
|
|
676
|
+
agentConfig: { ...recoveryAgentConfig, tools: ["read", "intercom", "contact_supervisor"], systemPrompt: "Base prompt\n\nIntercom orchestration channel:" },
|
|
677
|
+
recoveryAgentConfig,
|
|
568
678
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
569
679
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
570
680
|
shareEnabled: false,
|
|
571
681
|
sessionRoot: path.join(tempDir, "sessions"),
|
|
572
682
|
maxSubagentDepth: 2,
|
|
573
683
|
acceptance: false,
|
|
684
|
+
context: "fork",
|
|
685
|
+
intercomBridge: { mode: "off" },
|
|
574
686
|
});
|
|
575
687
|
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 } };
|
|
688
|
+
const recovery = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, id, "recovery-descriptor.json"), "utf-8")) as { runFanoutBudget?: { rootRunId?: string; limit?: number }; context?: string; intercomBridge?: { mode?: string }; tools?: string[]; systemPrompt?: string };
|
|
577
689
|
assert.deepEqual(recovery.runFanoutBudget && { rootRunId: recovery.runFanoutBudget.rootRunId, limit: recovery.runFanoutBudget.limit }, { rootRunId: id, limit: 64 });
|
|
690
|
+
assert.equal(recovery.context, "fork");
|
|
691
|
+
assert.deepEqual(recovery.intercomBridge, { mode: "off" });
|
|
692
|
+
assert.deepEqual(recovery.tools, ["read"]);
|
|
693
|
+
assert.equal(recovery.systemPrompt, "Base prompt");
|
|
578
694
|
const payload = await readAsyncPayload(id);
|
|
579
695
|
const status = await waitForAsyncState(id, (candidate) => candidate.state === "complete" && candidate.runtimeAcknowledgedExtensions !== undefined);
|
|
580
696
|
assert.equal(payload.launchContractDigest, launch.details.launchContractDigest);
|
|
@@ -593,90 +709,208 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
593
709
|
assert.deepEqual(status.runtimeAcknowledgedExtensions, runtimeAck);
|
|
594
710
|
assert.deepEqual(status.steps?.[0]?.runtimeAcknowledgedExtensions, runtimeAck);
|
|
595
711
|
assert.ok(!JSON.stringify(launch.details.launchResolvedExtensions).includes(tempDir), "projection should not expose raw extension paths");
|
|
712
|
+
// Stale supervisor-bridge pair: the --tools allowlist passes both names
|
|
713
|
+
// through, but SELESAI_SUBAGENT_REQUIRED_TOOLS excludes them so the 0.50 child
|
|
714
|
+
// runtime cannot fail the run over the removed native intercom (#1207).
|
|
715
|
+
const recoveryCallArgs = readMockPiArgs(mockPi, 0);
|
|
716
|
+
assert.equal(recoveryCallArgs[recoveryCallArgs.indexOf("--tools") + 1], "read,intercom,contact_supervisor");
|
|
717
|
+
assert.deepEqual(readMockPiRequiredTools(mockPi, 0), ["read"]);
|
|
596
718
|
});
|
|
597
719
|
|
|
598
|
-
it("async
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
720
|
+
it("rejects async thinking above maxThinking before child startup", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, () => {
|
|
721
|
+
const launch = executeAsyncSingle(`async-thinking-ceiling-${Date.now().toString(36)}`, {
|
|
722
|
+
agent: "worker",
|
|
723
|
+
task: "Use the strongest available reasoning.",
|
|
724
|
+
agentConfig: makeAgent("worker", { model: "mock/test-model", maxThinking: "xhigh", completionGuard: false }),
|
|
725
|
+
thinkingOverride: "max",
|
|
726
|
+
availableModels: [{ provider: "mock", id: "test-model", fullId: "mock/test-model" }],
|
|
727
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
728
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
729
|
+
shareEnabled: false,
|
|
730
|
+
});
|
|
731
|
+
assert.equal(launch.isError, true);
|
|
732
|
+
assert.match(launch.content[0]?.text ?? "", /max.*xhigh.*worker/);
|
|
733
|
+
assert.equal(mockPi.callCount(), 0);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it("rejects implementation workers without mutation-capable tools before spawn", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, () => {
|
|
737
|
+
const id = `async-readonly-worker-contract-${Date.now().toString(36)}`;
|
|
738
|
+
mockPi.onCall({ output: "should not spawn" });
|
|
739
|
+
const launch = executeAsyncSingle(id, {
|
|
740
|
+
agent: "worker",
|
|
741
|
+
task: "Implement the requested source fix",
|
|
742
|
+
agentConfig: makeAgent("worker", { tools: ["read", "grep", "find", "ls", "contact_supervisor"] }),
|
|
743
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
744
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
745
|
+
shareEnabled: false,
|
|
746
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
747
|
+
maxSubagentDepth: 2,
|
|
748
|
+
acceptance: false,
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
assert.equal(launch.isError, true);
|
|
752
|
+
assert.match(launch.content[0]?.text ?? "", /no mutation-capable tools/);
|
|
753
|
+
assert.equal(mockPi.callCount(), 0);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it("lets unrestricted implementation workers spawn", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
757
|
+
const id = `async-unrestricted-worker-contract-${Date.now().toString(36)}`;
|
|
758
|
+
mockPi.onCall({ output: "implemented" });
|
|
759
|
+
const launch = executeAsyncSingle(id, {
|
|
760
|
+
agent: "worker",
|
|
761
|
+
task: "Implement the requested source fix",
|
|
762
|
+
agentConfig: makeAgent("worker"),
|
|
609
763
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
610
764
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
611
765
|
shareEnabled: false,
|
|
766
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
612
767
|
maxSubagentDepth: 2,
|
|
768
|
+
acceptance: false,
|
|
613
769
|
});
|
|
614
770
|
|
|
615
|
-
const paused = await waitForAsyncState(id, (status) => status.state === "paused" && status.checkpoint?.name === "review");
|
|
616
771
|
assert.equal(launch.isError, undefined);
|
|
617
|
-
|
|
772
|
+
await waitForAsyncResultFile(id, 10_000);
|
|
618
773
|
assert.equal(mockPi.callCount(), 1);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it("lets explicit fast false opt out async external single runs from inherited fast mode", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
777
|
+
const agentConfig = makeAgent("external", {
|
|
778
|
+
fast: true,
|
|
779
|
+
runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('external async fast false')"] },
|
|
780
|
+
} as never);
|
|
619
781
|
|
|
620
|
-
|
|
782
|
+
const rejected = executeAsyncSingle(`async-external-fast-inherited-${Date.now().toString(36)}`, {
|
|
783
|
+
agent: "external",
|
|
784
|
+
task: "Run external",
|
|
785
|
+
agentConfig,
|
|
786
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
787
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
788
|
+
shareEnabled: false,
|
|
789
|
+
});
|
|
790
|
+
assert.equal(rejected.isError, true);
|
|
791
|
+
assert.match(rejected.content[0]?.text ?? "", /does not support: fast mode/);
|
|
792
|
+
|
|
793
|
+
const id = `async-external-fast-false-${Date.now().toString(36)}`;
|
|
794
|
+
const launch = executeAsyncSingle(id, {
|
|
795
|
+
agent: "external",
|
|
796
|
+
task: "Run external",
|
|
797
|
+
agentConfig,
|
|
798
|
+
fast: false,
|
|
799
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
800
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
801
|
+
shareEnabled: false,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
|
|
621
805
|
const payload = await readAsyncPayload(id);
|
|
622
806
|
assert.equal(payload.success, true);
|
|
623
|
-
assert.
|
|
624
|
-
assert.equal(
|
|
625
|
-
assert.equal(payload.results.length, 2);
|
|
626
|
-
assert.equal(mockPi.callCount(), 2);
|
|
807
|
+
assert.match(payload.results[0]?.output ?? "", /external async fast false/);
|
|
808
|
+
assert.equal(mockPi.callCount(), 0);
|
|
627
809
|
});
|
|
628
810
|
|
|
629
|
-
it("
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const id = `foreground-checkpoint-${Date.now().toString(36)}`;
|
|
811
|
+
it("lets explicit fast false opt out async external chains from inherited fast mode", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
812
|
+
const agent = makeAgent("external", {
|
|
813
|
+
fast: true,
|
|
814
|
+
runner: { type: "external-cli", command: process.execPath, args: ["-e", "process.stdout.write('external chain fast false')"] },
|
|
815
|
+
} as never);
|
|
635
816
|
|
|
636
|
-
const
|
|
637
|
-
chain: [
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
817
|
+
const rejected = executeAsyncChain(`async-chain-external-fast-inherited-${Date.now().toString(36)}`, {
|
|
818
|
+
chain: [{ agent: "external", task: "Run external" }],
|
|
819
|
+
resultMode: "chain",
|
|
820
|
+
agents: [agent],
|
|
821
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
822
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
823
|
+
shareEnabled: false,
|
|
824
|
+
acceptance: false,
|
|
825
|
+
});
|
|
826
|
+
assert.equal(rejected.isError, true);
|
|
827
|
+
assert.match(rejected.content[0]?.text ?? "", /does not support: fast mode/);
|
|
643
828
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
829
|
+
const id = `async-chain-external-fast-false-${Date.now().toString(36)}`;
|
|
830
|
+
const launch = executeAsyncChain(id, {
|
|
831
|
+
chain: [{ agent: "external", task: "Run external" }],
|
|
832
|
+
resultMode: "chain",
|
|
833
|
+
agents: [agent],
|
|
834
|
+
fast: false,
|
|
835
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
836
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
837
|
+
shareEnabled: false,
|
|
838
|
+
acceptance: false,
|
|
839
|
+
});
|
|
649
840
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
assert.equal(
|
|
653
|
-
assert.
|
|
654
|
-
assert.equal(mockPi.callCount(),
|
|
841
|
+
assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "launch failed");
|
|
842
|
+
const payload = await readAsyncPayload(id);
|
|
843
|
+
assert.equal(payload.success, true);
|
|
844
|
+
assert.match(payload.results[0]?.output ?? "", /external chain fast false/);
|
|
845
|
+
assert.equal(mockPi.callCount(), 0);
|
|
655
846
|
});
|
|
656
847
|
|
|
657
|
-
it("
|
|
658
|
-
|
|
659
|
-
mockPi.onCall({ output: "should not
|
|
660
|
-
const
|
|
661
|
-
|
|
848
|
+
it("rejects implementation workers when a capability ceiling removes mutation tools before spawn", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, () => {
|
|
849
|
+
const id = `async-ceiling-readonly-worker-contract-${Date.now().toString(36)}`;
|
|
850
|
+
mockPi.onCall({ output: "should not spawn" });
|
|
851
|
+
const launch = executeAsyncSingle(id, {
|
|
852
|
+
agent: "worker",
|
|
853
|
+
task: "Implement the requested source fix",
|
|
854
|
+
agentConfig: makeAgent("worker", { tools: ["read", "write"] }),
|
|
855
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
856
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
857
|
+
shareEnabled: false,
|
|
858
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
859
|
+
maxSubagentDepth: 2,
|
|
860
|
+
acceptance: false,
|
|
861
|
+
capabilityCeiling: { version: 1, allowedTools: ["read"], denyExtensions: true, sources: ["test"] },
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
assert.equal(launch.isError, true);
|
|
865
|
+
assert.match(launch.content[0]?.text ?? "", /no mutation-capable tools/);
|
|
866
|
+
assert.equal(mockPi.callCount(), 0);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it("rejects workflow implementation workers without mutation-capable tools before spawn", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, () => {
|
|
870
|
+
const id = `async-workflow-readonly-worker-contract-${Date.now().toString(36)}`;
|
|
871
|
+
mockPi.onCall({ output: "should not spawn" });
|
|
872
|
+
const launch = executeAsyncChain(id, {
|
|
873
|
+
chain: [{ agent: "worker", task: "Implement the requested source fix" }],
|
|
874
|
+
resultMode: "chain",
|
|
875
|
+
agents: [makeAgent("worker", { tools: ["read", "grep", "find", "ls", "contact_supervisor"] })],
|
|
876
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
877
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
878
|
+
shareEnabled: false,
|
|
879
|
+
maxSubagentDepth: 2,
|
|
880
|
+
acceptance: false,
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
assert.equal(launch.isError, true);
|
|
884
|
+
assert.match(launch.content[0]?.text ?? "", /no mutation-capable tools/);
|
|
885
|
+
assert.equal(mockPi.callCount(), 0);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("rejects workflow read-only workers after previous-output templates resolve to implementation tasks", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
889
|
+
const id = `async-workflow-resolved-readonly-worker-contract-${Date.now().toString(36)}`;
|
|
890
|
+
mockPi.onCall({ output: "Implement the requested source fix" });
|
|
891
|
+
mockPi.onCall({ output: "should not spawn" });
|
|
892
|
+
const launch = executeAsyncChain(id, {
|
|
662
893
|
chain: [
|
|
663
|
-
{ agent: "
|
|
664
|
-
{
|
|
665
|
-
|
|
894
|
+
{ agent: "producer", task: "Return the next instruction" },
|
|
895
|
+
{ agent: "worker", task: "{previous}" },
|
|
896
|
+
],
|
|
897
|
+
resultMode: "chain",
|
|
898
|
+
agents: [
|
|
899
|
+
makeAgent("producer", { completionGuard: false }),
|
|
900
|
+
makeAgent("worker", { tools: ["read", "grep", "find", "ls", "contact_supervisor"] }),
|
|
666
901
|
],
|
|
667
|
-
agents: [makeAgent("analyst"), makeAgent("worker")],
|
|
668
902
|
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
669
903
|
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
670
904
|
shareEnabled: false,
|
|
671
905
|
maxSubagentDepth: 2,
|
|
906
|
+
acceptance: false,
|
|
672
907
|
});
|
|
673
908
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const payload =
|
|
909
|
+
assert.equal(launch.isError, undefined);
|
|
910
|
+
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
911
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
677
912
|
assert.equal(payload.success, false);
|
|
678
|
-
assert.
|
|
679
|
-
assert.equal(payload.checkpoint?.status, "rejected");
|
|
913
|
+
assert.match(payload.results[1]?.error ?? "", /no mutation-capable tools/);
|
|
680
914
|
assert.equal(mockPi.callCount(), 1);
|
|
681
915
|
});
|
|
682
916
|
|
|
@@ -937,7 +1171,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
937
1171
|
});
|
|
938
1172
|
|
|
939
1173
|
it("background treats agent_settled as a clean terminal watermark", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
940
|
-
mockPi.onCall({ jsonl: [mockAssistantMessage("settled async without a terminal assistant stop", "tool_use"), { type: "agent_settled" }], keepAliveAfterFinalMessageMs:
|
|
1174
|
+
mockPi.onCall({ jsonl: [mockAssistantMessage("settled async without a terminal assistant stop", "tool_use"), { type: "agent_settled" }], keepAliveAfterFinalMessageMs: 15_000 });
|
|
941
1175
|
const id = `async-lifecycle-settled-${Date.now().toString(36)}`;
|
|
942
1176
|
const startedAt = Date.now();
|
|
943
1177
|
launchProtocolTest(id);
|
|
@@ -945,7 +1179,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
945
1179
|
assert.equal(payload.success, true);
|
|
946
1180
|
assert.equal(payload.results[0]?.error, undefined);
|
|
947
1181
|
assert.equal(payload.results[0]?.output, "settled async without a terminal assistant stop");
|
|
948
|
-
assert.ok(Date.now() - startedAt <
|
|
1182
|
+
assert.ok(Date.now() - startedAt < 10_000, "agent_settled should trigger bounded child cleanup");
|
|
949
1183
|
});
|
|
950
1184
|
|
|
951
1185
|
it("keeps named output references literal in async single tasks", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
@@ -1118,15 +1352,15 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1118
1352
|
assert.equal(mockPi.callCount(), 3);
|
|
1119
1353
|
});
|
|
1120
1354
|
|
|
1121
|
-
it("
|
|
1122
|
-
mockPi.onCall({ delay: 5_000, output: "one
|
|
1123
|
-
mockPi.onCall({ delay:
|
|
1124
|
-
const id = `async-
|
|
1355
|
+
it("journals terminal child status events for running async child stops", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "cross-process stop delivery unreliable on Windows CI" : undefined }, async () => {
|
|
1356
|
+
mockPi.onCall({ delay: 5_000, output: "one late" });
|
|
1357
|
+
mockPi.onCall({ delay: 250, output: "two done" });
|
|
1358
|
+
const id = `async-child-stop-parallel-${Date.now().toString(36)}`;
|
|
1125
1359
|
executeAsyncChain(id, {
|
|
1126
1360
|
chain: [{
|
|
1127
1361
|
parallel: [
|
|
1128
1362
|
{ agent: "one", task: "Wait" },
|
|
1129
|
-
{ agent: "two", task: "
|
|
1363
|
+
{ agent: "two", task: "Finish" },
|
|
1130
1364
|
],
|
|
1131
1365
|
concurrency: 2,
|
|
1132
1366
|
}],
|
|
@@ -1143,28 +1377,80 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1143
1377
|
},
|
|
1144
1378
|
shareEnabled: false,
|
|
1145
1379
|
maxSubagentDepth: 2,
|
|
1146
|
-
timeoutMs: 1_500,
|
|
1147
1380
|
});
|
|
1148
1381
|
|
|
1149
1382
|
await waitForMockPiCall(mockPi, 1, 10_000);
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
assert.equal(
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
assert.
|
|
1162
|
-
assert.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1383
|
+
const asyncDir = path.join(ASYNC_DIR, id);
|
|
1384
|
+
const statusBeforeStop = await waitForAsyncState(id, (candidate) => candidate.steps?.[0]?.status === "running" && typeof candidate.pid === "number");
|
|
1385
|
+
deliverStopRequest({ asyncDir, pid: statusBeforeStop.pid, source: "test", targetIndex: 0, childId: "step:0" });
|
|
1386
|
+
|
|
1387
|
+
await waitForAsyncResultFile(id, 30_000);
|
|
1388
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state !== "running");
|
|
1389
|
+
assert.equal(status.steps?.[0]?.status, "stopped");
|
|
1390
|
+
const childStatusEvents = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8")
|
|
1391
|
+
.trim()
|
|
1392
|
+
.split("\n")
|
|
1393
|
+
.map((line) => JSON.parse(line) as { type?: string; childId?: string; status?: string });
|
|
1394
|
+
assert.ok(childStatusEvents.some((event) => event.type === "subagent.child-status" && event.childId === "step:0" && event.status === "stopping"));
|
|
1395
|
+
assert.ok(childStatusEvents.some((event) => event.type === "subagent.child-status" && event.childId === "step:0" && event.status === "stopped"));
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
it("marks async parallel runs that exceed timeoutMs as timed out", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1399
|
+
mockPi.onCall({ delay: 5_000, output: "one done" });
|
|
1400
|
+
mockPi.onCall({ delay: 5_000, output: "two done" });
|
|
1401
|
+
const repo = createRepo("pi-subagents-parallel-timeout-recovery-");
|
|
1402
|
+
try {
|
|
1403
|
+
const id = `async-timeout-parallel-${Date.now().toString(36)}`;
|
|
1404
|
+
executeAsyncChain(id, {
|
|
1405
|
+
chain: [{
|
|
1406
|
+
parallel: [
|
|
1407
|
+
{ agent: "one", task: "Wait" },
|
|
1408
|
+
{ agent: "two", task: "Wait" },
|
|
1409
|
+
],
|
|
1410
|
+
concurrency: 2,
|
|
1411
|
+
}],
|
|
1412
|
+
resultMode: "parallel",
|
|
1413
|
+
agents: [makeAgent("one"), makeAgent("two")],
|
|
1414
|
+
ctx: { pi: { events: { emit() {} } }, cwd: repo, currentSessionId: "session-1" },
|
|
1415
|
+
artifactConfig: {
|
|
1416
|
+
enabled: false,
|
|
1417
|
+
includeInput: false,
|
|
1418
|
+
includeOutput: false,
|
|
1419
|
+
includeJsonl: false,
|
|
1420
|
+
includeMetadata: false,
|
|
1421
|
+
cleanupDays: 7,
|
|
1422
|
+
},
|
|
1423
|
+
shareEnabled: false,
|
|
1424
|
+
maxSubagentDepth: 2,
|
|
1425
|
+
timeoutMs: 1_500,
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
await waitForMockPiCall(mockPi, 1, 10_000);
|
|
1429
|
+
fs.writeFileSync(path.join(repo, "input.md"), "parallel partial child change\n", "utf-8");
|
|
1430
|
+
const resultPath = await waitForAsyncResultFile(id, 8_000);
|
|
1431
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1432
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1433
|
+
assert.equal(payload.state, "failed");
|
|
1434
|
+
assert.equal(payload.success, false);
|
|
1435
|
+
assert.equal(payload.exitCode, 1);
|
|
1436
|
+
assert.equal(payload.timeoutMs, 1_500);
|
|
1437
|
+
assert.equal(payload.timedOut, true);
|
|
1438
|
+
assert.match(payload.summary ?? "", /Subagent timed out after 1500ms\./);
|
|
1439
|
+
assert.equal(status.state, "failed");
|
|
1440
|
+
assert.equal(status.timeoutMs, 1_500);
|
|
1441
|
+
assert.equal(status.timedOut, true);
|
|
1442
|
+
assert.match(status.error ?? "", /Subagent timed out after 1500ms\./);
|
|
1443
|
+
assert.deepEqual(status.steps?.map((step) => step.status), ["failed", "failed"]);
|
|
1444
|
+
assert.deepEqual(status.steps?.map((step) => step.timedOut), [true, true]);
|
|
1445
|
+
assert.deepEqual(status.steps?.map((step) => step.error), ["Subagent timed out after 1500ms.", "Subagent timed out after 1500ms."]);
|
|
1446
|
+
assert.deepEqual(status.steps?.map((step) => step.timeoutRecovery?.changedFiles), [["input.md"], ["input.md"]]);
|
|
1447
|
+
assert.deepEqual(payload.results.map((result) => result.timedOut), [true, true]);
|
|
1448
|
+
assert.deepEqual(payload.results.map((result) => result.timeoutRecovery?.changedFiles), [["input.md"], ["input.md"]]);
|
|
1449
|
+
assert.ok(payload.results.every((result) => /Recovery summary:/.test(result.output ?? "")));
|
|
1450
|
+
assert.equal(mockPi.callCount(), 2);
|
|
1451
|
+
} finally {
|
|
1452
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
1453
|
+
}
|
|
1168
1454
|
});
|
|
1169
1455
|
|
|
1170
1456
|
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 () => {
|
|
@@ -1193,6 +1479,241 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1193
1479
|
assert.equal(payload.results[0]?.error, "Subagent timed out after 150ms.");
|
|
1194
1480
|
});
|
|
1195
1481
|
|
|
1482
|
+
it("preserves async timeout recovery summaries in final results", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1483
|
+
mockPi.onCall({ delay: 5_000, output: "too late" });
|
|
1484
|
+
const repo = createRepo("pi-subagents-timeout-recovery-");
|
|
1485
|
+
try {
|
|
1486
|
+
const id = `async-timeout-recovery-${Date.now().toString(36)}`;
|
|
1487
|
+
executeAsyncChain(id, {
|
|
1488
|
+
chain: [{ agent: "slow", task: "Wait" }],
|
|
1489
|
+
agents: [makeAgent("slow")],
|
|
1490
|
+
ctx: { pi: { events: { emit() {} } }, cwd: repo, currentSessionId: "session-1" },
|
|
1491
|
+
artifactConfig: {
|
|
1492
|
+
enabled: true,
|
|
1493
|
+
includeInput: false,
|
|
1494
|
+
includeOutput: true,
|
|
1495
|
+
includeJsonl: true,
|
|
1496
|
+
includeMetadata: true,
|
|
1497
|
+
cleanupDays: 7,
|
|
1498
|
+
},
|
|
1499
|
+
shareEnabled: false,
|
|
1500
|
+
maxSubagentDepth: 2,
|
|
1501
|
+
timeoutMs: 1200,
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
await waitForMockPiCall(mockPi, 0, 10_000);
|
|
1505
|
+
fs.writeFileSync(path.join(repo, "input.md"), "partial child change\n", "utf-8");
|
|
1506
|
+
const payload = await readAsyncPayload(id);
|
|
1507
|
+
const status = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
1508
|
+
const result = payload.results[0];
|
|
1509
|
+
assert.equal(result?.timedOut, true);
|
|
1510
|
+
assert.deepEqual(result?.timeoutRecovery?.changedFiles, ["input.md"]);
|
|
1511
|
+
assert.match(result?.timeoutRecovery?.message ?? "", /changed tracked files: input\.md/);
|
|
1512
|
+
assert.match(result?.output ?? "", /Recovery summary:/);
|
|
1513
|
+
assert.match(result?.output ?? "", /Warning: Inspect partial changes before retrying/);
|
|
1514
|
+
assert.deepEqual(status.steps?.[0]?.timeoutRecovery?.changedFiles, ["input.md"]);
|
|
1515
|
+
} finally {
|
|
1516
|
+
fs.rmSync(repo, { recursive: true, force: true });
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
it("kills a wedged tool at the per-tool timeout with a tool-specific error before the run-level timeout", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1521
|
+
// Wedge: tool_execution_start fires, then the mock holds (long delay) so
|
|
1522
|
+
// tool_execution_end never arrives — output would keep flowing in a real
|
|
1523
|
+
// wedge, which is exactly why a run-level stall detector can't see it.
|
|
1524
|
+
mockPi.onCall({
|
|
1525
|
+
steps: [
|
|
1526
|
+
{ jsonl: [events.toolStart("bash")] },
|
|
1527
|
+
{ delay: 30_000 }, // hold far past the per-tool budget; no tool_execution_end
|
|
1528
|
+
],
|
|
1529
|
+
});
|
|
1530
|
+
const id = `async-tool-timeout-${Date.now().toString(36)}`;
|
|
1531
|
+
process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS = "1000";
|
|
1532
|
+
try {
|
|
1533
|
+
executeAsyncChain(id, {
|
|
1534
|
+
chain: [{ agent: "one", task: "Wait" }],
|
|
1535
|
+
agents: [makeAgent("one")],
|
|
1536
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1537
|
+
artifactConfig: {
|
|
1538
|
+
enabled: false,
|
|
1539
|
+
includeInput: false,
|
|
1540
|
+
includeOutput: false,
|
|
1541
|
+
includeJsonl: false,
|
|
1542
|
+
includeMetadata: false,
|
|
1543
|
+
cleanupDays: 7,
|
|
1544
|
+
},
|
|
1545
|
+
shareEnabled: false,
|
|
1546
|
+
maxSubagentDepth: 2,
|
|
1547
|
+
timeoutMs: 8_000, // run-level budget is longer; the per-tool timer must fire first
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
const payload = await readAsyncPayload(id);
|
|
1551
|
+
assert.equal(payload.state, "failed");
|
|
1552
|
+
assert.equal(payload.results[0]?.timedOut, true);
|
|
1553
|
+
assert.match(payload.results[0]?.error ?? "", /Tool 'bash' exceeded its timeout of 1000ms\./);
|
|
1554
|
+
} finally {
|
|
1555
|
+
delete process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS;
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it("keeps an earlier wedged tool armed when another tool starts and ends", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1560
|
+
mockPi.onCall({
|
|
1561
|
+
steps: [
|
|
1562
|
+
{ jsonl: [{ type: "tool_execution_start", toolCallId: "bash-1", toolName: "bash", args: {} }] },
|
|
1563
|
+
{ delay: 50, jsonl: [
|
|
1564
|
+
{ type: "tool_execution_start", toolCallId: "read-1", toolName: "read", args: { path: "README.md" } },
|
|
1565
|
+
{ type: "tool_execution_end", toolCallId: "read-1", toolName: "read" },
|
|
1566
|
+
] },
|
|
1567
|
+
{ delay: 30_000 },
|
|
1568
|
+
],
|
|
1569
|
+
});
|
|
1570
|
+
const id = `async-tool-timeout-overlap-${Date.now().toString(36)}`;
|
|
1571
|
+
process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS = "1000";
|
|
1572
|
+
try {
|
|
1573
|
+
executeAsyncChain(id, {
|
|
1574
|
+
chain: [{ agent: "one", task: "Wait" }],
|
|
1575
|
+
agents: [makeAgent("one")],
|
|
1576
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1577
|
+
artifactConfig: {
|
|
1578
|
+
enabled: false,
|
|
1579
|
+
includeInput: false,
|
|
1580
|
+
includeOutput: false,
|
|
1581
|
+
includeJsonl: false,
|
|
1582
|
+
includeMetadata: false,
|
|
1583
|
+
cleanupDays: 7,
|
|
1584
|
+
},
|
|
1585
|
+
shareEnabled: false,
|
|
1586
|
+
maxSubagentDepth: 2,
|
|
1587
|
+
timeoutMs: 8_000,
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
const payload = await readAsyncPayload(id);
|
|
1591
|
+
assert.equal(payload.state, "failed");
|
|
1592
|
+
assert.equal(payload.results[0]?.timedOut, true);
|
|
1593
|
+
assert.match(payload.results[0]?.error ?? "", /Tool 'bash' exceeded its timeout of 1000ms\./);
|
|
1594
|
+
} finally {
|
|
1595
|
+
delete process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS;
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
it("lets the shorter run-level deadline win over a per-tool timeout", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1600
|
+
mockPi.onCall({
|
|
1601
|
+
steps: [
|
|
1602
|
+
{ jsonl: [events.toolStart("bash")] },
|
|
1603
|
+
{ delay: 30_000 },
|
|
1604
|
+
],
|
|
1605
|
+
});
|
|
1606
|
+
const id = `async-tool-timeout-run-budget-${Date.now().toString(36)}`;
|
|
1607
|
+
process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS = "1000";
|
|
1608
|
+
try {
|
|
1609
|
+
executeAsyncChain(id, {
|
|
1610
|
+
chain: [{ agent: "one", task: "Wait" }],
|
|
1611
|
+
agents: [makeAgent("one")],
|
|
1612
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1613
|
+
artifactConfig: {
|
|
1614
|
+
enabled: false,
|
|
1615
|
+
includeInput: false,
|
|
1616
|
+
includeOutput: false,
|
|
1617
|
+
includeJsonl: false,
|
|
1618
|
+
includeMetadata: false,
|
|
1619
|
+
cleanupDays: 7,
|
|
1620
|
+
},
|
|
1621
|
+
shareEnabled: false,
|
|
1622
|
+
maxSubagentDepth: 2,
|
|
1623
|
+
timeoutMs: 300,
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
const payload = await readAsyncPayload(id);
|
|
1627
|
+
assert.equal(payload.results[0]?.timedOut, true);
|
|
1628
|
+
assert.equal(payload.results[0]?.error, "Subagent timed out after 300ms.");
|
|
1629
|
+
} finally {
|
|
1630
|
+
delete process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS;
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
it("does not kill a tool that completes before the per-tool timeout", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1635
|
+
mockPi.onCall({
|
|
1636
|
+
jsonl: [
|
|
1637
|
+
events.toolStart("bash"),
|
|
1638
|
+
events.toolEnd("bash"),
|
|
1639
|
+
events.assistantMessage("done"),
|
|
1640
|
+
{ type: "agent_end", willRetry: false },
|
|
1641
|
+
{ type: "agent_settled" },
|
|
1642
|
+
],
|
|
1643
|
+
});
|
|
1644
|
+
const id = `async-tool-complete-${Date.now().toString(36)}`;
|
|
1645
|
+
process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS = "1000";
|
|
1646
|
+
try {
|
|
1647
|
+
executeAsyncChain(id, {
|
|
1648
|
+
chain: [{ agent: "one", task: "Done" }],
|
|
1649
|
+
agents: [makeAgent("one")],
|
|
1650
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1651
|
+
artifactConfig: {
|
|
1652
|
+
enabled: false,
|
|
1653
|
+
includeInput: false,
|
|
1654
|
+
includeOutput: false,
|
|
1655
|
+
includeJsonl: false,
|
|
1656
|
+
includeMetadata: false,
|
|
1657
|
+
cleanupDays: 7,
|
|
1658
|
+
},
|
|
1659
|
+
shareEnabled: false,
|
|
1660
|
+
maxSubagentDepth: 2,
|
|
1661
|
+
timeoutMs: 8_000,
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
const payload = await readAsyncPayload(id);
|
|
1665
|
+
assert.equal(payload.state, "complete");
|
|
1666
|
+
assert.notEqual(payload.results[0]?.timedOut, true);
|
|
1667
|
+
} finally {
|
|
1668
|
+
delete process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS;
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
it("does not apply the per-tool timeout to supervisor tools (contact_supervisor/intercom)", { skip: !isAsyncAvailable() ? "jiti not available" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
1673
|
+
// contact_supervisor holds ~1.5s — longer than the 1s per-tool budget — but
|
|
1674
|
+
// is allowlisted, so the per-tool timer must not fire.
|
|
1675
|
+
mockPi.onCall({
|
|
1676
|
+
steps: [
|
|
1677
|
+
{
|
|
1678
|
+
delay: 1500,
|
|
1679
|
+
jsonl: [
|
|
1680
|
+
events.toolStart("contact_supervisor"),
|
|
1681
|
+
events.toolEnd("contact_supervisor"),
|
|
1682
|
+
events.assistantMessage("done"),
|
|
1683
|
+
{ type: "agent_end", willRetry: false },
|
|
1684
|
+
{ type: "agent_settled" },
|
|
1685
|
+
],
|
|
1686
|
+
},
|
|
1687
|
+
],
|
|
1688
|
+
});
|
|
1689
|
+
const id = `async-tool-allowlist-${Date.now().toString(36)}`;
|
|
1690
|
+
process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS = "1000";
|
|
1691
|
+
try {
|
|
1692
|
+
executeAsyncChain(id, {
|
|
1693
|
+
chain: [{ agent: "one", task: "Ask" }],
|
|
1694
|
+
agents: [makeAgent("one")],
|
|
1695
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
1696
|
+
artifactConfig: {
|
|
1697
|
+
enabled: false,
|
|
1698
|
+
includeInput: false,
|
|
1699
|
+
includeOutput: false,
|
|
1700
|
+
includeJsonl: false,
|
|
1701
|
+
includeMetadata: false,
|
|
1702
|
+
cleanupDays: 7,
|
|
1703
|
+
},
|
|
1704
|
+
shareEnabled: false,
|
|
1705
|
+
maxSubagentDepth: 2,
|
|
1706
|
+
timeoutMs: 8_000,
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
const payload = await readAsyncPayload(id);
|
|
1710
|
+
assert.equal(payload.state, "complete");
|
|
1711
|
+
assert.notEqual(payload.results[0]?.timedOut, true);
|
|
1712
|
+
} finally {
|
|
1713
|
+
delete process.env.SELESAI_SUBAGENT_TOOL_TIMEOUT_MS;
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1196
1717
|
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
1718
|
mockPi.onCall({ delay: 5_000, output: "one too late" });
|
|
1198
1719
|
mockPi.onCall({ delay: 5_000, output: "two too late" });
|
|
@@ -1648,6 +2169,37 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1648
2169
|
assert.match(payload.results[0]?.error ?? "", /subagentOnlyExtensions/);
|
|
1649
2170
|
});
|
|
1650
2171
|
|
|
2172
|
+
it("records blocked mutation effects when background implementation tools are missing", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2173
|
+
mockPi.onCall({ output: "I cannot edit because fixture_search is missing", missingTools: ["fixture_search"] });
|
|
2174
|
+
const id = `async-missing-implementation-tool-${Date.now().toString(36)}`;
|
|
2175
|
+
|
|
2176
|
+
executeAsyncSingle(id, {
|
|
2177
|
+
agent: "worker",
|
|
2178
|
+
task: "Implement the requested source fix",
|
|
2179
|
+
agentConfig: makeAgent("worker", { tools: ["read", "fixture_search"] }),
|
|
2180
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
2181
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
2182
|
+
shareEnabled: false,
|
|
2183
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
2184
|
+
maxSubagentDepth: 2,
|
|
2185
|
+
acceptance: false,
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
2189
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2190
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "failed");
|
|
2191
|
+
|
|
2192
|
+
assert.equal(payload.success, false);
|
|
2193
|
+
assert.equal(payload.state, "failed");
|
|
2194
|
+
assert.match(payload.results[0]?.error ?? "", /requested unavailable child tools: fixture_search/);
|
|
2195
|
+
assert.doesNotMatch(payload.results[0]?.error ?? "", /completed without making edits/);
|
|
2196
|
+
assert.equal(payload.results[0]?.effects?.fileMutation?.status, "blocked");
|
|
2197
|
+
assert.equal(payload.results[0]?.effects?.fileMutation?.expected, true);
|
|
2198
|
+
assert.equal(payload.results[0]?.effects?.fileMutation?.attempted, false);
|
|
2199
|
+
assert.match(payload.results[0]?.effects?.fileMutation?.message ?? "", /requested unavailable child tools: fixture_search/);
|
|
2200
|
+
assert.equal(statusPayload.steps?.[0]?.effects?.fileMutation?.status, "blocked");
|
|
2201
|
+
});
|
|
2202
|
+
|
|
1651
2203
|
it("applies agent acceptance roles to inferred async acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1652
2204
|
mockPi.onCall({ output: "exploration complete" });
|
|
1653
2205
|
const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
|
|
@@ -1666,23 +2218,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1666
2218
|
assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "attested");
|
|
1667
2219
|
});
|
|
1668
2220
|
|
|
1669
|
-
it("applies agent acceptance roles to inferred async parallel acceptance", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1670
|
-
mockPi.onCall({ output: "parallel exploration complete" });
|
|
1671
|
-
const executor = makeAsyncExecutor([makeAgent("worker", { acceptanceRole: "read-only" })]);
|
|
1672
2221
|
|
|
1673
|
-
const result = await executor.execute(
|
|
1674
|
-
"async-parallel-agent-acceptance-role",
|
|
1675
|
-
{ tasks: [{ agent: "worker", task: "Explore the authentication flow" }], async: true, clarify: false },
|
|
1676
|
-
new AbortController().signal,
|
|
1677
|
-
undefined,
|
|
1678
|
-
makeMinimalCtx(tempDir),
|
|
1679
|
-
);
|
|
1680
|
-
|
|
1681
|
-
const asyncId = result.details?.asyncId;
|
|
1682
|
-
assert.ok(asyncId, "expected asyncId");
|
|
1683
|
-
const payload = await readAsyncPayload(asyncId);
|
|
1684
|
-
assert.equal(payload.results[0]?.acceptance?.effectiveAcceptance.level, "attested");
|
|
1685
|
-
});
|
|
1686
2222
|
|
|
1687
2223
|
it("infers async chain acceptance after expanding top-level task templates", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1688
2224
|
mockPi.onCall({ output: "patched" });
|
|
@@ -1715,170 +2251,13 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1715
2251
|
assert.equal(reviewPayload.results[0]?.acceptance?.effectiveAcceptance?.level, "attested");
|
|
1716
2252
|
});
|
|
1717
2253
|
|
|
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");
|
|
1720
|
-
mockPi.onCall({ output: "Async top-level report" });
|
|
1721
|
-
const executor = createSubagentExecutor!({
|
|
1722
|
-
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
1723
|
-
state: { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
|
|
1724
|
-
config: {},
|
|
1725
|
-
asyncByDefault: false,
|
|
1726
|
-
tempArtifactsDir: tempDir,
|
|
1727
|
-
getSubagentSessionRoot: () => tempDir,
|
|
1728
|
-
expandTilde: (p: string) => p,
|
|
1729
|
-
discoverAgents: () => ({ agents: [makeAgent("worker", { defaultProgress: true })] }),
|
|
1730
|
-
});
|
|
1731
2254
|
|
|
1732
|
-
const result = await executor.execute(
|
|
1733
|
-
"async-parallel-fields",
|
|
1734
|
-
{
|
|
1735
|
-
tasks: [{ agent: "worker", task: "Do async work", output: "async-top-output.md", reads: ["input.md"] }],
|
|
1736
|
-
async: true,
|
|
1737
|
-
clarify: false,
|
|
1738
|
-
},
|
|
1739
|
-
new AbortController().signal,
|
|
1740
|
-
undefined,
|
|
1741
|
-
makeMinimalCtx(tempDir),
|
|
1742
|
-
);
|
|
1743
|
-
|
|
1744
|
-
const asyncId = result.details?.asyncId;
|
|
1745
|
-
assert.ok(asyncId, "expected asyncId");
|
|
1746
|
-
const resultPath = path.join(RESULTS_DIR, `${asyncId}.json`);
|
|
1747
|
-
const statusPath = path.join(ASYNC_DIR, asyncId, "status.json");
|
|
1748
|
-
const deadline = Date.now() + 10_000;
|
|
1749
|
-
while (!fs.existsSync(resultPath)) {
|
|
1750
|
-
if (Date.now() > deadline) assert.fail(`Timed out waiting for async result file: ${resultPath}`);
|
|
1751
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
1755
|
-
const status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
1756
|
-
assert.equal(payload.mode, "parallel");
|
|
1757
|
-
assert.equal(payload.sessionId, "session-123");
|
|
1758
|
-
assert.equal(payload.results[0]?.acceptance?.status, "review-required");
|
|
1759
|
-
assert.equal(payload.results[0]?.acceptance?.evidenceStatus, "checked");
|
|
1760
|
-
assert.equal(status.sessionId, "session-123");
|
|
1761
|
-
assert.equal(status.steps?.[0]?.acceptance?.status, "review-required");
|
|
1762
|
-
assert.equal(status.steps?.[0]?.acceptance?.evidenceStatus, "checked");
|
|
1763
|
-
const outputPath = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", asyncId, "async-top-output.md");
|
|
1764
|
-
const outputDeadline = Date.now() + 5_000;
|
|
1765
|
-
while (!fs.existsSync(outputPath)) {
|
|
1766
|
-
if (Date.now() > outputDeadline) {
|
|
1767
|
-
assert.fail(`Timed out waiting for saved output file: ${outputPath}`);
|
|
1768
|
-
}
|
|
1769
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1770
|
-
}
|
|
1771
|
-
assert.equal(fs.readFileSync(outputPath, "utf-8"), "Async top-level report");
|
|
1772
|
-
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.startsWith("call-"));
|
|
1773
|
-
assert.ok(callFile, "expected a recorded mock pi call");
|
|
1774
|
-
const args = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")).args as string[];
|
|
1775
|
-
const taskArg = args.at(-1) ?? "";
|
|
1776
|
-
const progressPath = path.join(tempDir, ".pi-subagents", "artifacts", "progress", asyncId, "progress.md");
|
|
1777
|
-
assert.ok(taskArg.includes(`[Read from: ${path.join(tempDir, "input.md")}]`));
|
|
1778
|
-
assert.ok(taskArg.includes(`Update progress at: ${progressPath}`));
|
|
1779
|
-
assert.ok(taskArg.includes(`Write your findings to exactly this path: ${outputPath}`));
|
|
1780
|
-
assert.equal(fs.existsSync(progressPath), true);
|
|
1781
|
-
assert.equal(fs.existsSync(path.join(tempDir, "progress.md")), false);
|
|
1782
|
-
});
|
|
1783
|
-
|
|
1784
|
-
for (const outputOverride of [undefined, true] as const) {
|
|
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 () => {
|
|
1786
|
-
mockPi.onCall({ matchArgIncludes: "Write the first report", output: "first async report" });
|
|
1787
|
-
mockPi.onCall({ matchArgIncludes: "Write the second report", output: "second async report" });
|
|
1788
|
-
const agent = makeAgent("worker", { output: "context.md", tools: ["read", "grep", "find", "ls"] });
|
|
1789
|
-
const executor = makeAsyncExecutor([agent]);
|
|
1790
|
-
const tasks = [
|
|
1791
|
-
{ agent: "worker", task: "Write the first report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
1792
|
-
{ agent: "worker", task: "Write the second report", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
1793
|
-
];
|
|
1794
|
-
const launch = await executor.execute(
|
|
1795
|
-
`async-inherited-output-${outputOverride === true ? "true" : "omitted"}`,
|
|
1796
|
-
{ tasks, async: true, clarify: false },
|
|
1797
|
-
new AbortController().signal,
|
|
1798
|
-
undefined,
|
|
1799
|
-
makeMinimalCtx(tempDir),
|
|
1800
|
-
);
|
|
1801
2255
|
|
|
1802
|
-
assert.equal(launch.isError, undefined);
|
|
1803
|
-
const payload = await readAsyncPayload(launch.details?.asyncId as string);
|
|
1804
|
-
assert.equal(payload.success, true);
|
|
1805
|
-
assert.equal(payload.results[0]?.output?.split("\n\nOutput saved to:")[0], "first async report");
|
|
1806
|
-
assert.equal(payload.results[1]?.output?.split("\n\nOutput saved to:")[0], "second async report");
|
|
1807
|
-
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
|
|
1808
|
-
const authoritativePaths = [
|
|
1809
|
-
path.join(outputDir, "parallel-0", "0-worker", "context.md"),
|
|
1810
|
-
path.join(outputDir, "parallel-0", "1-worker", "context.md"),
|
|
1811
|
-
];
|
|
1812
|
-
assert.equal(fs.readFileSync(authoritativePaths[0]!, "utf-8"), "first async report");
|
|
1813
|
-
assert.equal(fs.readFileSync(authoritativePaths[1]!, "utf-8"), "second async report");
|
|
1814
|
-
const artifactPaths = payload.results.map((result) => result.artifactPaths?.outputPath);
|
|
1815
|
-
assert.ok(artifactPaths[0] && artifactPaths[1]);
|
|
1816
|
-
assert.notEqual(artifactPaths[0], artifactPaths[1]);
|
|
1817
|
-
assert.equal(fs.readFileSync(artifactPaths[0], "utf-8"), "first async report");
|
|
1818
|
-
assert.equal(fs.readFileSync(artifactPaths[1], "utf-8"), "second async report");
|
|
1819
|
-
const calls = fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).sort();
|
|
1820
|
-
const taskArgs = calls.map((name) => (JSON.parse(fs.readFileSync(path.join(mockPi.dir, name), "utf-8")) as MockPiCallRecord).args?.at(-1) ?? "");
|
|
1821
|
-
const firstTask = taskArgs.find((task) => task.includes("Write the first report")) ?? "";
|
|
1822
|
-
const secondTask = taskArgs.find((task) => task.includes("Write the second report")) ?? "";
|
|
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")));
|
|
1825
|
-
for (const taskArg of [firstTask, secondTask]) {
|
|
1826
|
-
assert.match(taskArg, /Return the complete artifact in your final response\./);
|
|
1827
|
-
assert.match(taskArg, /Do not call contact_supervisor merely because no write-capable tool is available\./);
|
|
1828
|
-
assert.doesNotMatch(taskArg, /Write your findings to exactly this path/);
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
2256
|
|
|
1833
|
-
it("async top-level parallel rejects duplicate explicit output paths before spawning", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1834
|
-
const executor = makeAsyncExecutor([makeAgent("worker")]);
|
|
1835
|
-
const result = await executor.execute(
|
|
1836
|
-
"async-duplicate-output",
|
|
1837
|
-
{
|
|
1838
|
-
tasks: [
|
|
1839
|
-
{ agent: "worker", task: "Write A", output: "same.md" },
|
|
1840
|
-
{ agent: "worker", task: "Write B", output: "same.md" },
|
|
1841
|
-
],
|
|
1842
|
-
async: true,
|
|
1843
|
-
clarify: false,
|
|
1844
|
-
},
|
|
1845
|
-
new AbortController().signal,
|
|
1846
|
-
undefined,
|
|
1847
|
-
makeMinimalCtx(tempDir),
|
|
1848
|
-
);
|
|
1849
2257
|
|
|
1850
|
-
assert.equal(result.isError, true);
|
|
1851
|
-
assert.match(result.content[0]?.text ?? "", /Parallel tasks 1 \(worker\) and 2 \(worker\).*same\.md/);
|
|
1852
|
-
assert.equal(mockPi.callCount(), 0);
|
|
1853
|
-
});
|
|
1854
2258
|
|
|
1855
|
-
it("async top-level parallel preserves distinct explicit output destinations", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1856
|
-
mockPi.onCall({ matchArgIncludes: "first.md", output: "first explicit report" });
|
|
1857
|
-
mockPi.onCall({ matchArgIncludes: "second.md", output: "second explicit report" });
|
|
1858
|
-
const executor = makeAsyncExecutor([makeAgent("worker", { output: "context.md" })]);
|
|
1859
|
-
const launch = await executor.execute(
|
|
1860
|
-
"async-distinct-explicit-output",
|
|
1861
|
-
{
|
|
1862
|
-
tasks: [
|
|
1863
|
-
{ agent: "worker", task: "Write A", output: "first.md" },
|
|
1864
|
-
{ agent: "worker", task: "Write B", output: "second.md" },
|
|
1865
|
-
],
|
|
1866
|
-
async: true,
|
|
1867
|
-
clarify: false,
|
|
1868
|
-
},
|
|
1869
|
-
new AbortController().signal,
|
|
1870
|
-
undefined,
|
|
1871
|
-
makeMinimalCtx(tempDir),
|
|
1872
|
-
);
|
|
1873
2259
|
|
|
1874
|
-
|
|
1875
|
-
assert.equal(payload.success, true);
|
|
1876
|
-
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", launch.details?.asyncId as string);
|
|
1877
|
-
assert.equal(fs.readFileSync(path.join(outputDir, "first.md"), "utf-8"), "first explicit report");
|
|
1878
|
-
assert.equal(fs.readFileSync(path.join(outputDir, "second.md"), "utf-8"), "second explicit report");
|
|
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"));
|
|
1881
|
-
});
|
|
2260
|
+
|
|
1882
2261
|
|
|
1883
2262
|
it("async chain static parallel namespaces inherited default outputs", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
1884
2263
|
mockPi.onCall({ matchArgIncludes: "Write first", output: "chain first report" });
|
|
@@ -1964,46 +2343,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
1964
2343
|
assert.equal(status.steps?.[0]?.acceptance?.status, "review-required");
|
|
1965
2344
|
});
|
|
1966
2345
|
|
|
1967
|
-
it("top-level async chain suppresses progress for {task} review-only tasks", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
1968
|
-
mockPi.onCall({ output: "Async review" });
|
|
1969
|
-
const executor = createSubagentExecutor!({
|
|
1970
|
-
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
1971
|
-
state: { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
|
|
1972
|
-
config: {},
|
|
1973
|
-
asyncByDefault: false,
|
|
1974
|
-
tempArtifactsDir: tempDir,
|
|
1975
|
-
getSubagentSessionRoot: () => tempDir,
|
|
1976
|
-
expandTilde: (p: string) => p,
|
|
1977
|
-
discoverAgents: () => ({ agents: [makeAgent("reviewer", { defaultProgress: true })] }),
|
|
1978
|
-
});
|
|
1979
|
-
|
|
1980
|
-
const result = await executor.execute(
|
|
1981
|
-
"async-chain-read-only-progress",
|
|
1982
|
-
{
|
|
1983
|
-
chain: [{ agent: "reviewer" }],
|
|
1984
|
-
task: "Review-only. Do not edit files. Return findings.",
|
|
1985
|
-
async: true,
|
|
1986
|
-
clarify: false,
|
|
1987
|
-
},
|
|
1988
|
-
new AbortController().signal,
|
|
1989
|
-
undefined,
|
|
1990
|
-
makeMinimalCtx(tempDir),
|
|
1991
|
-
);
|
|
1992
2346
|
|
|
1993
|
-
const asyncId = result.details?.asyncId;
|
|
1994
|
-
assert.ok(asyncId, "expected asyncId");
|
|
1995
|
-
const resultPath = path.join(RESULTS_DIR, `${asyncId}.json`);
|
|
1996
|
-
const deadline = Date.now() + 10_000;
|
|
1997
|
-
while (!fs.existsSync(resultPath)) {
|
|
1998
|
-
if (Date.now() > deadline) assert.fail(`Timed out waiting for async result file: ${resultPath}`);
|
|
1999
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2000
|
-
}
|
|
2001
|
-
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.startsWith("call-"));
|
|
2002
|
-
assert.ok(callFile, "expected a recorded mock pi call");
|
|
2003
|
-
const args = JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")).args as string[];
|
|
2004
|
-
assert.doesNotMatch(args.at(-1) ?? "", /progress\.md/);
|
|
2005
|
-
assert.equal(fs.existsSync(path.join(tempDir, "progress.md")), false);
|
|
2006
|
-
});
|
|
2007
2347
|
|
|
2008
2348
|
it("async chains reject malformed named output references before spawning", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2009
2349
|
const id = `async-malformed-output-ref-${Date.now().toString(36)}`;
|
|
@@ -2577,64 +2917,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2577
2917
|
assert.match(payload.workflowGraph?.nodes?.[1]?.error ?? "", /Collected output validation failed/);
|
|
2578
2918
|
});
|
|
2579
2919
|
|
|
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 () => {
|
|
2581
|
-
const repoDir = createRepo("pi-subagent-async-worktree-");
|
|
2582
|
-
try {
|
|
2583
|
-
mockPi.onCall({ output: "Worktree report" });
|
|
2584
|
-
const executor = createSubagentExecutor!({
|
|
2585
|
-
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
2586
|
-
state: { baseCwd: repoDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
|
|
2587
|
-
config: {},
|
|
2588
|
-
asyncByDefault: false,
|
|
2589
|
-
tempArtifactsDir: repoDir,
|
|
2590
|
-
getSubagentSessionRoot: () => repoDir,
|
|
2591
|
-
expandTilde: (p: string) => p,
|
|
2592
|
-
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
2593
|
-
});
|
|
2594
2920
|
|
|
2595
|
-
const result = await executor.execute(
|
|
2596
|
-
"async-parallel-worktree-fields",
|
|
2597
|
-
{
|
|
2598
|
-
tasks: [{ agent: "worker", task: "Do worktree work", output: "report.md", reads: ["input.md"] }],
|
|
2599
|
-
async: true,
|
|
2600
|
-
clarify: false,
|
|
2601
|
-
worktree: true,
|
|
2602
|
-
},
|
|
2603
|
-
new AbortController().signal,
|
|
2604
|
-
undefined,
|
|
2605
|
-
makeMinimalCtx(repoDir),
|
|
2606
|
-
);
|
|
2607
|
-
|
|
2608
|
-
const asyncId = result.details?.asyncId;
|
|
2609
|
-
assert.ok(asyncId, "expected asyncId");
|
|
2610
|
-
|
|
2611
|
-
const worktreeCwd = path.join(os.tmpdir(), `pi-worktree-${asyncId}-s0-0`);
|
|
2612
|
-
const args = await waitForMockPiArgs(mockPi, 0);
|
|
2613
|
-
const taskArg = args.at(-1) ?? "";
|
|
2614
|
-
assert.match(taskArg, new RegExp(`\\[Read from: ${escapeRegExp(path.join(worktreeCwd, "input.md"))}\\]`));
|
|
2615
|
-
assert.ok(taskArg.includes(`Write your findings to exactly this path: ${path.join(repoDir, ".pi-subagents", "artifacts", "outputs", asyncId, "report.md")}`));
|
|
2616
|
-
const resultPath = await waitForAsyncResultFile(asyncId, 90_000);
|
|
2617
|
-
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as AsyncResultPayload;
|
|
2618
|
-
const status = await waitForAsyncState(asyncId, (candidate) => candidate.state === "complete");
|
|
2619
|
-
assert.equal(payload.parallelHandoff?.version, 1);
|
|
2620
|
-
assert.equal(payload.parallelHandoff?.path, path.join(result.details!.asyncDir!, "handoff.json"));
|
|
2621
|
-
assert.deepEqual(status.parallelHandoff, payload.parallelHandoff);
|
|
2622
|
-
assert.equal(payload.parallelHandoff?.childCount, 1);
|
|
2623
|
-
assert.equal(payload.parallelHandoff?.cleanupState, "complete");
|
|
2624
|
-
assert.equal(fs.existsSync(worktreeCwd), false, "temporary worktree should be removed before handoff publication");
|
|
2625
|
-
const handoff = JSON.parse(fs.readFileSync(payload.parallelHandoff!.path!, "utf-8")) as {
|
|
2626
|
-
version: number;
|
|
2627
|
-
groups: Array<{ children: Array<{ agent: string; status: string; patch: { path: string } }>; cleanup: { state: string } }>;
|
|
2628
|
-
};
|
|
2629
|
-
assert.equal(handoff.version, 1);
|
|
2630
|
-
assert.equal(handoff.groups[0]!.children[0]!.agent, "worker");
|
|
2631
|
-
assert.equal(handoff.groups[0]!.children[0]!.status, "completed");
|
|
2632
|
-
assert.equal(handoff.groups[0]!.cleanup.state, "complete");
|
|
2633
|
-
assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true, "patch artifact should outlive cleanup");
|
|
2634
|
-
} finally {
|
|
2635
|
-
removeTempDir(repoDir);
|
|
2636
|
-
}
|
|
2637
|
-
});
|
|
2638
2921
|
|
|
2639
2922
|
it("readStatus caches ordered sweeps above 50 files and invalidates same-mtime replacements", () => {
|
|
2640
2923
|
const root = createTempDir();
|
|
@@ -2771,7 +3054,14 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2771
3054
|
assert.equal(payload.results[0].modelAttempts.length, 2);
|
|
2772
3055
|
assert.deepEqual(payload.results[0].totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2773
3056
|
assert.deepEqual(payload.totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2774
|
-
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete"
|
|
3057
|
+
const statusPayload = await waitForAsyncState(id, (candidate) => candidate.state === "complete"
|
|
3058
|
+
&& candidate.lifecycleArtifactVersion !== undefined
|
|
3059
|
+
&& candidate.totalTokens?.total !== undefined
|
|
3060
|
+
&& candidate.totalCost !== undefined
|
|
3061
|
+
&& candidate.steps[0]?.model !== undefined
|
|
3062
|
+
&& candidate.steps[0]?.thinking !== undefined
|
|
3063
|
+
&& candidate.steps[0]?.tokens?.total !== undefined
|
|
3064
|
+
&& candidate.steps[0]?.totalCost !== undefined);
|
|
2775
3065
|
assert.equal(statusPayload.lifecycleArtifactVersion, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION);
|
|
2776
3066
|
assert.equal(statusPayload.steps[0]?.model, "anthropic/claude-sonnet-4:low");
|
|
2777
3067
|
assert.equal(statusPayload.steps[0]?.thinking, "low");
|
|
@@ -2781,9 +3071,9 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2781
3071
|
assert.deepEqual(statusPayload.totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2782
3072
|
const events = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8").trim().split("\n").map((line) => JSON.parse(line));
|
|
2783
3073
|
assert.equal(events.find((event) => event.type === "subagent.run.started")?.lifecycleArtifactVersion, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION);
|
|
2784
|
-
const completed =
|
|
2785
|
-
assert.equal(completed
|
|
2786
|
-
assert.deepEqual(completed
|
|
3074
|
+
const completed = await waitForAsyncEvent(id, "subagent.run.completed");
|
|
3075
|
+
assert.equal(completed.lifecycleArtifactVersion, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION);
|
|
3076
|
+
assert.deepEqual(completed.totalCost, { inputTokens: 110, outputTokens: 55, costUsd: 0.011 });
|
|
2787
3077
|
assert.match(fs.readFileSync(path.join(asyncDir, "output-0.log"), "utf-8"), /Recovered asynchronously/);
|
|
2788
3078
|
assert.equal(mockPi.callCount(), 2);
|
|
2789
3079
|
});
|
|
@@ -2825,6 +3115,44 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
2825
3115
|
assert.equal(mockPi.callCount(), 2);
|
|
2826
3116
|
});
|
|
2827
3117
|
|
|
3118
|
+
it("background runs fail when a configured provider-qualified model starts on a different child model", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3119
|
+
mockPi.onCall({ jsonl: [events.assistantMessage("wrong async provider", "openai-codex/gpt-5.6-sol")] });
|
|
3120
|
+
const id = `async-model-verification-${Date.now().toString(36)}`;
|
|
3121
|
+
executeAsyncSingle(id, {
|
|
3122
|
+
agent: "worker",
|
|
3123
|
+
task: "Do work",
|
|
3124
|
+
agentConfig: makeAgent("worker", { model: "opencode-go/ox-alpha-free:max" }),
|
|
3125
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3126
|
+
availableModels: [
|
|
3127
|
+
{ provider: "opencode-go", id: "ox-alpha-free", fullId: "opencode-go/ox-alpha-free" },
|
|
3128
|
+
{ provider: "openai-codex", id: "gpt-5.6-sol", fullId: "openai-codex/gpt-5.6-sol" },
|
|
3129
|
+
],
|
|
3130
|
+
artifactConfig: {
|
|
3131
|
+
enabled: false,
|
|
3132
|
+
includeInput: false,
|
|
3133
|
+
includeOutput: false,
|
|
3134
|
+
includeJsonl: false,
|
|
3135
|
+
includeMetadata: false,
|
|
3136
|
+
cleanupDays: 7,
|
|
3137
|
+
},
|
|
3138
|
+
shareEnabled: false,
|
|
3139
|
+
maxSubagentDepth: 2,
|
|
3140
|
+
});
|
|
3141
|
+
|
|
3142
|
+
const payload = JSON.parse(fs.readFileSync(await waitForAsyncResultFile(id), "utf-8")) as AsyncResultPayload;
|
|
3143
|
+
assert.equal(payload.success, false);
|
|
3144
|
+
assert.equal(payload.results[0]?.model, "opencode-go/ox-alpha-free:max");
|
|
3145
|
+
assert.deepEqual(payload.results[0]?.attemptedModels, ["opencode-go/ox-alpha-free:max"]);
|
|
3146
|
+
assert.equal(payload.results[0]?.modelAttempts?.[0]?.success, false);
|
|
3147
|
+
assert.match(payload.results[0]?.error ?? "", /model_verification_failed/);
|
|
3148
|
+
assert.match(payload.results[0]?.error ?? "", /Expected 'opencode-go\/ox-alpha-free:max'/);
|
|
3149
|
+
assert.match(payload.results[0]?.error ?? "", /observed 'openai-codex\/gpt-5\.6-sol'/);
|
|
3150
|
+
assert.match(payload.results[0]?.modelAttempts?.[0]?.error ?? "", /model_verification_failed/);
|
|
3151
|
+
const args = readMockPiArgs(mockPi, 0);
|
|
3152
|
+
assert.equal(args[args.indexOf("--model") + 1], "opencode-go/ox-alpha-free:max");
|
|
3153
|
+
assert.equal(mockPi.callCount(), 1);
|
|
3154
|
+
});
|
|
3155
|
+
|
|
2828
3156
|
it("background runs retry the fallback model when the provider stream ends without finish_reason", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
2829
3157
|
mockPi.onCall({
|
|
2830
3158
|
jsonl: [{
|
|
@@ -3451,6 +3779,107 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3451
3779
|
assert.doesNotMatch(eventsText, /Interrupt:/);
|
|
3452
3780
|
});
|
|
3453
3781
|
|
|
3782
|
+
it("does not use shared-cwd sibling tracked edits as parallel completion-guard proof", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3783
|
+
mockPi.onCall({
|
|
3784
|
+
matchArgIncludes: "Edit tracked file",
|
|
3785
|
+
delay: 50,
|
|
3786
|
+
writeFiles: [{ path: "input.md", content: "changed by first sibling\n" }],
|
|
3787
|
+
jsonl: [
|
|
3788
|
+
...events.completedWrite("input.md", "changed by first sibling\n"),
|
|
3789
|
+
events.assistantMessage("Implemented first sibling change."),
|
|
3790
|
+
],
|
|
3791
|
+
});
|
|
3792
|
+
mockPi.onCall({
|
|
3793
|
+
matchArgIncludes: "Implement second sibling change",
|
|
3794
|
+
delay: 500,
|
|
3795
|
+
output: "Implemented second sibling change.",
|
|
3796
|
+
});
|
|
3797
|
+
const repo = createRepo("pi-subagents-shared-cwd-mutation-guard-");
|
|
3798
|
+
const id = `async-parallel-shared-cwd-mutation-${Date.now().toString(36)}`;
|
|
3799
|
+
let runnerStarted = false;
|
|
3800
|
+
try {
|
|
3801
|
+
const launch = executeAsyncChain(id, {
|
|
3802
|
+
chain: [{
|
|
3803
|
+
parallel: [
|
|
3804
|
+
{ agent: "first", task: "Edit tracked file" },
|
|
3805
|
+
{ agent: "second", task: "Implement second sibling change" },
|
|
3806
|
+
],
|
|
3807
|
+
concurrency: 2,
|
|
3808
|
+
}],
|
|
3809
|
+
resultMode: "parallel",
|
|
3810
|
+
agents: [makeAgent("first"), makeAgent("second")],
|
|
3811
|
+
ctx: { pi: { events: { emit() {} } }, cwd: repo, currentSessionId: "session-1" },
|
|
3812
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
3813
|
+
shareEnabled: false,
|
|
3814
|
+
maxSubagentDepth: 2,
|
|
3815
|
+
});
|
|
3816
|
+
runnerStarted = !launch.isError;
|
|
3817
|
+
|
|
3818
|
+
const payload = await readAsyncPayload(id);
|
|
3819
|
+
assert.equal(payload.results[0]?.success, true);
|
|
3820
|
+
assert.equal(payload.results[0]?.effects?.fileMutation?.status, "observed");
|
|
3821
|
+
assert.equal(payload.results[1]?.success, false);
|
|
3822
|
+
assert.equal(payload.results[1]?.effects?.fileMutation?.status, "missing");
|
|
3823
|
+
assert.equal(payload.results[1]?.effects?.fileMutation?.attempted, false);
|
|
3824
|
+
assert.match(payload.results[1]?.error ?? "", /completed without making edits/);
|
|
3825
|
+
} finally {
|
|
3826
|
+
if (runnerStarted) await waitForAsyncEvent(id, "subagent.run.process_terminal");
|
|
3827
|
+
fs.rmSync(repo, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
3828
|
+
}
|
|
3829
|
+
});
|
|
3830
|
+
|
|
3831
|
+
it("background implementation challenges keep explicit no-change reports successful", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3832
|
+
mockPi.onCall({ output: [
|
|
3833
|
+
"Kept the current implementation. No new code or test changes were made in this challenge pass.",
|
|
3834
|
+
"Reason: the current candidate is the smallest correct shape.",
|
|
3835
|
+
].join("\n\n") });
|
|
3836
|
+
|
|
3837
|
+
const id = `async-no-change-challenge-${Date.now().toString(36)}`;
|
|
3838
|
+
const sessionRoot = path.join(tempDir, "sessions");
|
|
3839
|
+
const task = [
|
|
3840
|
+
"You are reviving a previous subagent conversation.",
|
|
3841
|
+
"",
|
|
3842
|
+
"Original run: source-run",
|
|
3843
|
+
"Original agent: worker",
|
|
3844
|
+
"Original session file: /tmp/source-session.jsonl",
|
|
3845
|
+
"",
|
|
3846
|
+
"Use the stored session context as background. Answer the orchestrator's follow-up below. Do not assume the original child process is still alive.",
|
|
3847
|
+
"",
|
|
3848
|
+
"Follow-up:",
|
|
3849
|
+
"Implementation challenge pass 1 for the accepted candidate. Reconsider it and implement any better current-scope change.",
|
|
3850
|
+
].join("\n");
|
|
3851
|
+
|
|
3852
|
+
executeAsyncSingle(id, {
|
|
3853
|
+
agent: "worker",
|
|
3854
|
+
task,
|
|
3855
|
+
agentConfig: makeAgent("worker"),
|
|
3856
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
3857
|
+
artifactConfig: {
|
|
3858
|
+
enabled: false,
|
|
3859
|
+
includeInput: false,
|
|
3860
|
+
includeOutput: false,
|
|
3861
|
+
includeJsonl: false,
|
|
3862
|
+
includeMetadata: false,
|
|
3863
|
+
cleanupDays: 7,
|
|
3864
|
+
},
|
|
3865
|
+
shareEnabled: false,
|
|
3866
|
+
sessionRoot,
|
|
3867
|
+
maxSubagentDepth: 2,
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
const resultPath = await waitForAsyncResultFile(id, 10_000);
|
|
3871
|
+
const payload = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
|
|
3872
|
+
assert.equal(payload.success, true);
|
|
3873
|
+
assert.equal(payload.exitCode, 0);
|
|
3874
|
+
assert.equal(payload.results[0].success, true);
|
|
3875
|
+
assert.match(String(payload.results[0].output), /Kept the current implementation/);
|
|
3876
|
+
assert.doesNotMatch(String(payload.results[0].error ?? ""), /completed without making edits/);
|
|
3877
|
+
|
|
3878
|
+
const eventsText = fs.readFileSync(path.join(ASYNC_DIR, id, "events.jsonl"), "utf-8");
|
|
3879
|
+
assert.doesNotMatch(eventsText, /Subagent failed: worker/);
|
|
3880
|
+
assert.doesNotMatch(eventsText, /"reason":"completion_guard"/);
|
|
3881
|
+
});
|
|
3882
|
+
|
|
3454
3883
|
it("agent contract v1 keeps async acceptance and file-mutation effects separate from execution", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3455
3884
|
mockPi.onCall({ output: "I’ll do that now and report back after implementing.\n```acceptance-report\n{\"criteriaSatisfied\":[{\"id\":\"criterion-1\",\"status\":\"not-satisfied\",\"evidence\":\"no proof\"}]}\n```" });
|
|
3456
3885
|
const id = `async-v1-separate-${Date.now().toString(36)}`;
|
|
@@ -3651,10 +4080,105 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3651
4080
|
assert.match(result.content[0]?.type === "text" ? result.content[0].text : "", /Active async run capacity exhausted: 1\/1 used/);
|
|
3652
4081
|
assert.equal(fs.existsSync(rejectedAsyncDir), false);
|
|
3653
4082
|
assert.equal(fs.existsSync(rejectedResultPath), false);
|
|
3654
|
-
assert.equal(fs.existsSync(path.join(tempDir, ".
|
|
4083
|
+
assert.equal(fs.existsSync(path.join(tempDir, ".selesai", "subagents")), false);
|
|
3655
4084
|
fs.rmSync(path.join(ACTIVE_ASYNC_CAPACITY_DIR, activeAsyncCapacitySessionKey("session-cap")), { recursive: true, force: true });
|
|
3656
4085
|
});
|
|
3657
4086
|
|
|
4087
|
+
it("scheduled executor launches retain the live active session ownership", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
4088
|
+
mockPi.onCall({ output: "Scheduled work completed" });
|
|
4089
|
+
const liveCwd = path.join(tempDir, "live-project");
|
|
4090
|
+
fs.mkdirSync(liveCwd);
|
|
4091
|
+
const state = {
|
|
4092
|
+
baseCwd: liveCwd,
|
|
4093
|
+
currentSessionId: "session-b",
|
|
4094
|
+
lastParentModel: { provider: "deepseek", id: "live-model" },
|
|
4095
|
+
subagentSpawns: { sessionId: "session-b", count: 1, configuredLimit: 1, granted: 1, grantHistory: [] },
|
|
4096
|
+
asyncJobs: new Map(),
|
|
4097
|
+
fleetJobs: new Map(),
|
|
4098
|
+
foregroundControls: new Map(),
|
|
4099
|
+
lastForegroundControlId: null,
|
|
4100
|
+
};
|
|
4101
|
+
const executor = createSubagentExecutor!({
|
|
4102
|
+
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
4103
|
+
state,
|
|
4104
|
+
config: { maxSubagentSpawnsPerSession: 1 },
|
|
4105
|
+
asyncByDefault: false,
|
|
4106
|
+
tempArtifactsDir: tempDir,
|
|
4107
|
+
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
4108
|
+
expandTilde: (p: string) => p,
|
|
4109
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
4110
|
+
});
|
|
4111
|
+
const retainedCtx = makeMinimalCtx(tempDir);
|
|
4112
|
+
retainedCtx.sessionManager.getSessionId = () => "session-a";
|
|
4113
|
+
retainedCtx.model = { provider: "deepseek", id: "scheduled-model" };
|
|
4114
|
+
const launch = await executor.executeScheduled(
|
|
4115
|
+
`scheduled-owner-${Date.now().toString(36)}`,
|
|
4116
|
+
{ agent: "worker", task: "Run retained project timer", async: true, acceptance: false },
|
|
4117
|
+
new AbortController().signal,
|
|
4118
|
+
retainedCtx,
|
|
4119
|
+
) as AsyncExecutionResult;
|
|
4120
|
+
|
|
4121
|
+
assert.equal(launch.isError, undefined);
|
|
4122
|
+
assert.ok(launch.details.asyncId);
|
|
4123
|
+
assert.equal(state.currentSessionId, "session-b");
|
|
4124
|
+
assert.equal(state.baseCwd, liveCwd);
|
|
4125
|
+
assert.deepEqual(state.lastParentModel, { provider: "deepseek", id: "live-model" });
|
|
4126
|
+
assert.deepEqual(state.subagentSpawns, { sessionId: "session-b", count: 1, configuredLimit: 1, granted: 1, grantHistory: [] });
|
|
4127
|
+
const blocked = await executor.executeScheduled(
|
|
4128
|
+
`scheduled-owner-blocked-${Date.now().toString(36)}`,
|
|
4129
|
+
{ agent: "worker", task: "Exceed the retained owner budget", async: true, acceptance: false },
|
|
4130
|
+
new AbortController().signal,
|
|
4131
|
+
retainedCtx,
|
|
4132
|
+
);
|
|
4133
|
+
assert.equal(blocked.isError, true);
|
|
4134
|
+
assert.match(blocked.content[0]?.type === "text" ? blocked.content[0].text : "", /1\/1 used/);
|
|
4135
|
+
assert.deepEqual(state.subagentSpawns, { sessionId: "session-b", count: 1, configuredLimit: 1, granted: 1, grantHistory: [] });
|
|
4136
|
+
await readAsyncPayload(launch.details.asyncId);
|
|
4137
|
+
});
|
|
4138
|
+
|
|
4139
|
+
it("scheduled workflows keep owner status and registries isolated from the live session", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
4140
|
+
const liveCwd = path.join(tempDir, "live-workflow-project");
|
|
4141
|
+
fs.mkdirSync(liveCwd);
|
|
4142
|
+
const state = {
|
|
4143
|
+
baseCwd: liveCwd,
|
|
4144
|
+
currentSessionId: "session-b",
|
|
4145
|
+
subagentSpawns: { sessionId: "session-b", count: 4, configuredLimit: 5, granted: 0, grantHistory: [] },
|
|
4146
|
+
asyncJobs: new Map(),
|
|
4147
|
+
fleetJobs: new Map(),
|
|
4148
|
+
foregroundControls: new Map(),
|
|
4149
|
+
lastForegroundControlId: null,
|
|
4150
|
+
};
|
|
4151
|
+
const executor = createSubagentExecutor!({
|
|
4152
|
+
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
4153
|
+
state,
|
|
4154
|
+
config: { maxSubagentSpawnsPerSession: 5 },
|
|
4155
|
+
asyncByDefault: false,
|
|
4156
|
+
tempArtifactsDir: tempDir,
|
|
4157
|
+
getSubagentSessionRoot: () => path.join(tempDir, "sessions"),
|
|
4158
|
+
expandTilde: (p: string) => p,
|
|
4159
|
+
discoverAgents: () => ({ agents: [makeAgent("worker")] }),
|
|
4160
|
+
});
|
|
4161
|
+
const retainedCtx = makeMinimalCtx(tempDir);
|
|
4162
|
+
retainedCtx.sessionManager.getSessionId = () => "session-a";
|
|
4163
|
+
const workflowId = `scheduled-workflow-${Date.now().toString(36)}`;
|
|
4164
|
+
const launch = await executor.executeScheduled(
|
|
4165
|
+
workflowId,
|
|
4166
|
+
{ workflowScript: `return await runs.status("${workflowId}")`, async: true },
|
|
4167
|
+
new AbortController().signal,
|
|
4168
|
+
retainedCtx,
|
|
4169
|
+
) as AsyncExecutionResult;
|
|
4170
|
+
|
|
4171
|
+
assert.equal(launch.isError, undefined);
|
|
4172
|
+
assert.equal(state.asyncJobs.size, 0);
|
|
4173
|
+
assert.equal(state.fleetJobs.size, 0);
|
|
4174
|
+
assert.deepEqual(state.subagentSpawns, { sessionId: "session-b", count: 4, configuredLimit: 5, granted: 0, grantHistory: [] });
|
|
4175
|
+
const payload = await readAsyncPayload(launch.details.asyncId);
|
|
4176
|
+
assert.equal(state.asyncJobs.size, 0);
|
|
4177
|
+
assert.equal(state.fleetJobs.size, 0);
|
|
4178
|
+
assert.match(payload.workflow.value.output, /Spawn budget: 0\/5 used/);
|
|
4179
|
+
assert.match(payload.workflow.value.output, new RegExp(workflowId));
|
|
4180
|
+
});
|
|
4181
|
+
|
|
3658
4182
|
it("async executor keeps the last parent session model after continuation drops ctx.model", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
3659
4183
|
mockPi.onCall({ output: "Done asynchronously" });
|
|
3660
4184
|
const state = {
|
|
@@ -3698,27 +4222,7 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3698
4222
|
assert.equal(args[args.indexOf("--model") + 1], "deepseek/deepseek-v4-flash");
|
|
3699
4223
|
});
|
|
3700
4224
|
|
|
3701
|
-
it("foreground chains keep the last parent session model after continuation drops ctx.model", { skip: !createSubagentExecutor ? "executor not available" : undefined }, async () => {
|
|
3702
|
-
mockPi.onCall({ output: "Done foreground" });
|
|
3703
|
-
const executor = makeAsyncExecutor([makeAgent("worker", { completionGuard: false })]);
|
|
3704
|
-
const initialCtx = makeMinimalCtx(tempDir);
|
|
3705
|
-
initialCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3706
|
-
initialCtx.model = { provider: "deepseek", id: "deepseek-v4-flash" };
|
|
3707
|
-
await executor.execute("prime-parent-model", { action: "list" }, new AbortController().signal, undefined, initialCtx);
|
|
3708
4225
|
|
|
3709
|
-
const continuedCtx = makeMinimalCtx(tempDir);
|
|
3710
|
-
continuedCtx.sessionManager.getSessionId = () => "session-continued";
|
|
3711
|
-
const result = await executor.execute(
|
|
3712
|
-
"continued-foreground-chain",
|
|
3713
|
-
{ chain: [{ agent: "worker", task: "Do work" }], acceptance: false },
|
|
3714
|
-
new AbortController().signal,
|
|
3715
|
-
undefined,
|
|
3716
|
-
continuedCtx,
|
|
3717
|
-
);
|
|
3718
|
-
assert.equal(result.isError, undefined);
|
|
3719
|
-
const args = readMockPiArgs(mockPi, 0);
|
|
3720
|
-
assert.equal(args[args.indexOf("--model") + 1], "deepseek/deepseek-v4-flash");
|
|
3721
|
-
});
|
|
3722
4226
|
|
|
3723
4227
|
it("background single runs inherit the parent session model when no model is set", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3724
4228
|
mockPi.onCall({ output: "Done asynchronously" });
|
|
@@ -3757,6 +4261,74 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
3757
4261
|
assert.equal(args[args.indexOf("--model") + 1], "deepseek/deepseek-v4-flash");
|
|
3758
4262
|
});
|
|
3759
4263
|
|
|
4264
|
+
it("background forked runs inherit a parent model outside the registry", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
4265
|
+
mockPi.onCall({ output: "Forked async work" });
|
|
4266
|
+
const parentSessionFile = path.join(tempDir, "parent.jsonl");
|
|
4267
|
+
const forkedSessionFile = path.join(tempDir, "forked.jsonl");
|
|
4268
|
+
const sessionHeader = JSON.stringify({ type: "session", cwd: fs.realpathSync(tempDir) });
|
|
4269
|
+
fs.writeFileSync(parentSessionFile, `${sessionHeader}\n`, "utf-8");
|
|
4270
|
+
fs.writeFileSync(forkedSessionFile, `${sessionHeader}\n`, "utf-8");
|
|
4271
|
+
const ctx = {
|
|
4272
|
+
...makeMinimalCtx(tempDir),
|
|
4273
|
+
model: { provider: "gateway", id: "parent-model" },
|
|
4274
|
+
modelRegistry: { getAvailable: () => [{ provider: "openai", id: "gpt-5-mini" }] },
|
|
4275
|
+
sessionManager: {
|
|
4276
|
+
getSessionId: () => "session-123",
|
|
4277
|
+
getSessionFile: () => parentSessionFile,
|
|
4278
|
+
getLeafId: () => "leaf-current",
|
|
4279
|
+
openSession: () => ({ createBranchedSession: () => forkedSessionFile }),
|
|
4280
|
+
},
|
|
4281
|
+
};
|
|
4282
|
+
const launch = await makeAsyncExecutor([makeAgent("worker")]).execute(
|
|
4283
|
+
"forked-parent-model",
|
|
4284
|
+
{ agent: "worker", task: "Do work", async: true, context: "fork" },
|
|
4285
|
+
new AbortController().signal,
|
|
4286
|
+
undefined,
|
|
4287
|
+
ctx,
|
|
4288
|
+
) as AsyncExecutionResult;
|
|
4289
|
+
assert.ok(!launch.isError, launch.content[0]?.text);
|
|
4290
|
+
assert.ok(launch.details.asyncId);
|
|
4291
|
+
const payload = await readAsyncPayload(launch.details.asyncId);
|
|
4292
|
+
assert.equal(payload.results[0]?.model, "gateway/parent-model");
|
|
4293
|
+
});
|
|
4294
|
+
|
|
4295
|
+
it("revives an inherited parent model outside the current registry", { skip: !isAsyncAvailable() || !createSubagentExecutor ? "jiti or executor not available" : undefined }, async () => {
|
|
4296
|
+
mockPi.onCall({ output: "Initial async work" });
|
|
4297
|
+
const sourceId = `async-revive-parent-model-${Date.now().toString(36)}`;
|
|
4298
|
+
const sessionFile = path.join(tempDir, "sessions", "source.jsonl");
|
|
4299
|
+
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
4300
|
+
fs.writeFileSync(sessionFile, "", "utf-8");
|
|
4301
|
+
executeAsyncSingle(sourceId, {
|
|
4302
|
+
agent: "worker",
|
|
4303
|
+
task: "Initial work",
|
|
4304
|
+
agentConfig: makeAgent("worker"),
|
|
4305
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-123" },
|
|
4306
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
4307
|
+
shareEnabled: false,
|
|
4308
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
4309
|
+
sessionFile,
|
|
4310
|
+
modelOverride: "gateway/parent-model",
|
|
4311
|
+
modelOverrideFromParent: true,
|
|
4312
|
+
maxSubagentDepth: 2,
|
|
4313
|
+
});
|
|
4314
|
+
await readAsyncPayload(sourceId);
|
|
4315
|
+
const descriptor = JSON.parse(fs.readFileSync(path.join(ASYNC_DIR, sourceId, "recovery-descriptor.json"), "utf-8"));
|
|
4316
|
+
assert.equal(descriptor.modelOverrideFromParent, true);
|
|
4317
|
+
|
|
4318
|
+
mockPi.onCall({ output: "Revived async work" });
|
|
4319
|
+
const result = await makeAsyncExecutor([makeAgent("worker")]).execute(
|
|
4320
|
+
"revive-parent-model",
|
|
4321
|
+
{ action: "resume", id: sourceId, message: "Continue" },
|
|
4322
|
+
new AbortController().signal,
|
|
4323
|
+
undefined,
|
|
4324
|
+
makeMinimalCtx(tempDir),
|
|
4325
|
+
) as AsyncExecutionResult;
|
|
4326
|
+
assert.ok(!result.isError, result.content[0]?.text);
|
|
4327
|
+
assert.ok(result.details.asyncId);
|
|
4328
|
+
const payload = await readAsyncPayload(result.details.asyncId);
|
|
4329
|
+
assert.equal(payload.results[0]?.model, "gateway/parent-model");
|
|
4330
|
+
});
|
|
4331
|
+
|
|
3760
4332
|
it("background chains inherit the parent session model when no step or agent model is set", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
3761
4333
|
mockPi.onCall({ output: "Done asynchronously" });
|
|
3762
4334
|
|
|
@@ -4628,6 +5200,73 @@ describe("async execution utilities", { skip: !available ? "pi packages not avai
|
|
|
4628
5200
|
assert.equal(payload.success, true);
|
|
4629
5201
|
});
|
|
4630
5202
|
|
|
5203
|
+
it("background open-tool attention survives an overlapping quick tool", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
5204
|
+
mockPi.onCall({
|
|
5205
|
+
steps: [
|
|
5206
|
+
{ jsonl: [{ type: "tool_execution_start", toolCallId: "bash-1", toolName: "bash", args: { command: "sleep 2" } }] },
|
|
5207
|
+
{ delay: 50, jsonl: [
|
|
5208
|
+
{ type: "tool_execution_start", toolCallId: "read-1", toolName: "read", args: { path: "README.md" } },
|
|
5209
|
+
{ type: "tool_execution_end", toolCallId: "read-1", toolName: "read" },
|
|
5210
|
+
] },
|
|
5211
|
+
{ delay: 2_000, jsonl: [
|
|
5212
|
+
{ type: "tool_execution_end", toolCallId: "bash-1", toolName: "bash" },
|
|
5213
|
+
events.toolResult("bash", "done"),
|
|
5214
|
+
events.assistantMessage("Done"),
|
|
5215
|
+
] },
|
|
5216
|
+
],
|
|
5217
|
+
});
|
|
5218
|
+
|
|
5219
|
+
const id = `async-overlap-tool-attention-${Date.now().toString(36)}`;
|
|
5220
|
+
const asyncDir = path.join(ASYNC_DIR, id);
|
|
5221
|
+
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
5222
|
+
const statusPath = path.join(asyncDir, "status.json");
|
|
5223
|
+
const resultPath = path.join(RESULTS_DIR, `${id}.json`);
|
|
5224
|
+
executeAsyncSingle(id, {
|
|
5225
|
+
agent: "worker",
|
|
5226
|
+
task: "Run the command",
|
|
5227
|
+
agentConfig: makeAgent("worker"),
|
|
5228
|
+
ctx: { pi: { events: { emit() {} } }, cwd: tempDir, currentSessionId: "session-1" },
|
|
5229
|
+
artifactConfig: { enabled: false, includeInput: false, includeOutput: false, includeJsonl: false, includeMetadata: false, cleanupDays: 7 },
|
|
5230
|
+
shareEnabled: false,
|
|
5231
|
+
sessionRoot: path.join(tempDir, "sessions"),
|
|
5232
|
+
maxSubagentDepth: 2,
|
|
5233
|
+
controlConfig: {
|
|
5234
|
+
enabled: true,
|
|
5235
|
+
needsAttentionAfterMs: 999_999,
|
|
5236
|
+
activeNoticeAfterMs: 100,
|
|
5237
|
+
failedToolAttemptsBeforeAttention: 3,
|
|
5238
|
+
notifyOn: ["needs_attention"],
|
|
5239
|
+
notifyChannels: ["event", "async", "intercom"],
|
|
5240
|
+
},
|
|
5241
|
+
});
|
|
5242
|
+
|
|
5243
|
+
const deadline = Date.now() + 10_000;
|
|
5244
|
+
let eventText = "";
|
|
5245
|
+
let statusDuringEvent: AsyncStatusPayload | undefined;
|
|
5246
|
+
while (Date.now() < deadline) {
|
|
5247
|
+
if (fs.existsSync(eventsPath)) eventText = fs.readFileSync(eventsPath, "utf-8");
|
|
5248
|
+
if (eventText.includes('"reason":"tool_open_threshold"') && fs.existsSync(statusPath)) {
|
|
5249
|
+
const status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatusPayload;
|
|
5250
|
+
if (status.activityState === "needs_attention" && status.steps?.[0]?.activityState === "needs_attention") {
|
|
5251
|
+
statusDuringEvent = status;
|
|
5252
|
+
break;
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
if (eventText.includes('"reason":"tool_open_threshold"') && fs.existsSync(resultPath)) {
|
|
5256
|
+
assert.fail("run completed before status.json exposed overlapping tool attention");
|
|
5257
|
+
}
|
|
5258
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
5259
|
+
}
|
|
5260
|
+
|
|
5261
|
+
assert.match(eventText, /"type":"needs_attention"/);
|
|
5262
|
+
assert.match(eventText, /"reason":"tool_open_threshold"/);
|
|
5263
|
+
assert.match(eventText, /"currentTool":"bash"/);
|
|
5264
|
+
assert.ok(statusDuringEvent, "expected status.json to expose overlapping tool attention while the run is active");
|
|
5265
|
+
assert.equal(statusDuringEvent.currentTool, "bash");
|
|
5266
|
+
assert.equal(statusDuringEvent.steps?.[0]?.currentTool, "bash");
|
|
5267
|
+
await waitForAsyncResultFile(id);
|
|
5268
|
+
});
|
|
5269
|
+
|
|
4631
5270
|
it("subagent_wait wakes when an async child is waiting on contact_supervisor", { skip: !isAsyncAvailable() ? "jiti not available" : undefined }, async () => {
|
|
4632
5271
|
const id = `async-supervisor-attention-${Date.now().toString(36)}`;
|
|
4633
5272
|
const replyReleasePath = path.join(tempDir, `${id}.reply`);
|