@selesai/code 0.5.20 → 0.5.21
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/dist/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared task mutation-intent classifier.
|
|
3
|
+
*
|
|
4
|
+
* Single authority for reading a task's wording, answering two different
|
|
5
|
+
* questions from one prohibition analysis:
|
|
6
|
+
*
|
|
7
|
+
* - `classifyTaskMutationIntent` / `expectsImplementationMutation`: does the
|
|
8
|
+
* task REQUIRE file changes? Consumed by the completion mutation guard,
|
|
9
|
+
* which blocks completion, so its vocabulary is deliberately narrow.
|
|
10
|
+
* - `taskMayMutate`: COULD the task plausibly change files? Consumed by
|
|
11
|
+
* acceptance level inference, which only raises evidence gates, so its
|
|
12
|
+
* vocabulary is deliberately broad (any bare write verb).
|
|
13
|
+
*
|
|
14
|
+
* Explicit read-only wording ("do not modify", "review only") is a task-level
|
|
15
|
+
* intent only when no write imperative survives outside those phrases. A task
|
|
16
|
+
* like "Do not modify tests; implement the fix" is an implementation task with
|
|
17
|
+
* a scoped constraint, not a read-only task.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const REVIEW_ONLY_PATTERNS = [
|
|
21
|
+
/\breview only\b/i,
|
|
22
|
+
/\bsuggest fixes only\b/i,
|
|
23
|
+
/\bonly return findings\b/i,
|
|
24
|
+
/\breturn findings only\b/i,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
28
|
+
/\bmust\s+(?:edit|modify|change|fix|patch|apply|implement)\b/i,
|
|
29
|
+
/\brequired\s+to\s+(?:edit|modify|change|fix|patch|apply|implement)\b/i,
|
|
30
|
+
/(?:^|[.!?\n]\s*)implement\s+(?:the\s+)?(?:approved|requested|specified|file|code|source|fix(?:es)?|changes?)\b/i,
|
|
31
|
+
/\bregardless\s+of\s+findings\b/i,
|
|
32
|
+
/\balways\s+(?:edit|modify|change|fix|patch|apply|implement)\b/i,
|
|
33
|
+
/\bapply\s+(?:the\s+)?fix(?:es)?\s+directly\b/i,
|
|
34
|
+
/\bmake\s+(?:the\s+)?code\s+changes\b/i,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// The prohibition's object ends at punctuation or at a coordinating word
|
|
38
|
+
// (but/and/then), so a follow-on clause like "but implement the fix" stays in
|
|
39
|
+
// the text for write-intent testing instead of being swallowed as the object.
|
|
40
|
+
const NO_EDIT_PROHIBITION_PATTERN = /\b(?:do not|don't|must not)\s+(?:edit|modify|write(?:\s+to)?|touch|change)\b((?:(?!\b(?:but|and|then)\b)[^.;,:!?\n–—-])*)/gi;
|
|
41
|
+
|
|
42
|
+
/** Objects of a no-edit prohibition that mean "the codebase in general" rather than a named scope. */
|
|
43
|
+
const GENERIC_PROHIBITION_OBJECT = /^\s*(?:(?:any|all|the|these|those|your|our|existing|project|source|sources|repo|repository)[\s/,-]*)*(?:files?|code|codebase|sources?|anything|repo(?:sitory)?)?\s*$/i;
|
|
44
|
+
|
|
45
|
+
const SCOPED_NO_EDIT_CONSTRAINT_PATTERNS = [
|
|
46
|
+
/\bdo not edit files?\s+outside\b/i,
|
|
47
|
+
/\bdo not edit\s+outside\b/i,
|
|
48
|
+
/\bdo not edit\s+unrelated files?\b/i,
|
|
49
|
+
/\bdo not change\s+unrelated files?\b/i,
|
|
50
|
+
/\bdo not modify\s+unrelated files?\b/i,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const NO_TOOL_INTENT_PATTERNS = [
|
|
54
|
+
/\bno tools? needed\b/i,
|
|
55
|
+
/\bno tools? required\b/i,
|
|
56
|
+
/\bwithout using tools\b/i,
|
|
57
|
+
/\bdo not use tools\b/i,
|
|
58
|
+
/\bdon't use tools\b/i,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const READ_ONLY_DELIVERABLE_PATTERNS = [
|
|
62
|
+
/\b(?:draft|write|compose|prepare|produce)\s+(?:(?:a|an|the)\s+)?(?:github\s+)?(?:issue|bug report|issue draft|issue body|proposal|plan|report|summary|findings?|analysis|recommendations?)\b/i,
|
|
63
|
+
/\b(?:issue|bug report)\s+(?:draft|body|template)\b/i,
|
|
64
|
+
/\b(?:return|provide|produce)\s+(?:text|markdown|answer|findings?|recommendations?)\s+only\b/i,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const RESEARCH_AGENT_PATTERNS = [
|
|
68
|
+
/\binvestigate\b/i,
|
|
69
|
+
/\bscout\b/i,
|
|
70
|
+
/\bresearch(?:er)?\b/i,
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const FIX_OR_PATCH_IMPLEMENTATION_PATTERN = /\b(?:fix|patch)\s+(?:(?:it|this|that|them|each|any|all|these|those)\b|(?:(?:a|an|the|any|all)\s+)?(?:(?:failing|failed|broken|flaky|red|cold|start|current|existing|reported|approved|known|regression|unit|integration|e2e|source|typescript|type-?script|ts|type-?check|compiler)\s+)*(?:bug|defect|issues?|problems?|failures?|regressions?|tests?|errors?|items?|typos?|code|source|implementation|component|function|module|class|method|logic|file|files|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command|lint(?:ing)?|build|ci|type-?check|type\s+checking)\b)/i;
|
|
74
|
+
|
|
75
|
+
const WORKER_IMPLEMENTATION_PATTERNS = [
|
|
76
|
+
/\b(?:implement|edit|modify|refactor|delete)\b/i,
|
|
77
|
+
FIX_OR_PATCH_IMPLEMENTATION_PATTERN,
|
|
78
|
+
/\b(?:update|add|remove|replace|create)\b(?!\s+(?:(?:a|an|the)\s+)?(?:report|summary|findings?)(?:\b|$))/i,
|
|
79
|
+
/\bapply\s+(?:the\s+)?(?:(?:suggested|proposed|recommended)\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
80
|
+
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
81
|
+
/\bdo those fixes\b/i,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const GENERAL_IMPLEMENTATION_PATTERNS = [
|
|
85
|
+
/\b(?:implement|edit|modify|refactor)\b/i,
|
|
86
|
+
FIX_OR_PATCH_IMPLEMENTATION_PATTERN,
|
|
87
|
+
/\bapply\s+(?:the\s+)?(?:(?:suggested|proposed|recommended)\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
88
|
+
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
89
|
+
/\bdo those fixes\b/i,
|
|
90
|
+
/\b(?:update|add|remove|replace|delete|create)\s+(?:the\s+)?(?:file|files|code|source|implementation|test|tests|component|function|module|class|method|logic|import|imports|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command)\b/i,
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
export type TaskMutationIntent = { kind: "implementation" } | { kind: "read-only" } | { kind: "unknown" };
|
|
94
|
+
|
|
95
|
+
function stripFrameworkInstructions(task: string): string {
|
|
96
|
+
return task
|
|
97
|
+
.split("\n")
|
|
98
|
+
.filter((line) => !/^\s*\[(?:Write to|Read from):/i.test(line))
|
|
99
|
+
.filter((line) => !/^\s*(?:Create and maintain progress at:|Update progress at:|\*\*Output:\*\*|Write your findings to(?: exactly this path)?:|Return the complete artifact in your final response\.|The runtime will persist it to exactly this path:|Do not call contact_supervisor merely because no write-capable tool is available\.|This path is authoritative for this run\.|Ignore any other output filename or output path mentioned elsewhere)/i.test(line))
|
|
100
|
+
.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stripPatterns(task: string, patterns: RegExp[]): string {
|
|
104
|
+
let stripped = task;
|
|
105
|
+
for (const pattern of patterns) {
|
|
106
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
107
|
+
stripped = stripped.replace(new RegExp(pattern.source, flags), " ");
|
|
108
|
+
}
|
|
109
|
+
return stripped;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface NoEditProhibitionAnalysis {
|
|
113
|
+
/** True when any prohibition (or review-only/no-tool wording) is present. */
|
|
114
|
+
present: boolean;
|
|
115
|
+
/** True when a prohibition covers files/code in general; such wording wins over write verbs. */
|
|
116
|
+
blanket: boolean;
|
|
117
|
+
/** Task text with prohibition phrases and their objects removed, for write-intent testing. */
|
|
118
|
+
strippedText: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function analyzeNoEditProhibitions(taskText: string): NoEditProhibitionAnalysis {
|
|
122
|
+
let present = REVIEW_ONLY_PATTERNS.some((pattern) => pattern.test(taskText))
|
|
123
|
+
|| NO_TOOL_INTENT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
124
|
+
let blanket = present;
|
|
125
|
+
let strippedText = stripPatterns(taskText, [...REVIEW_ONLY_PATTERNS, ...NO_TOOL_INTENT_PATTERNS]);
|
|
126
|
+
strippedText = strippedText.replace(new RegExp(NO_EDIT_PROHIBITION_PATTERN.source, NO_EDIT_PROHIBITION_PATTERN.flags), (_match, object: string) => {
|
|
127
|
+
present = true;
|
|
128
|
+
if (GENERIC_PROHIBITION_OBJECT.test(object)) blanket = true;
|
|
129
|
+
return " ";
|
|
130
|
+
});
|
|
131
|
+
return { present, blanket, strippedText };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function taskHasReadOnlyDeliverable(taskText: string): boolean {
|
|
135
|
+
return READ_ONLY_DELIVERABLE_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isReviewerStyleAgent(agent: string): boolean {
|
|
139
|
+
return /\b(?:architect|commentator|explorer|recapper|researcher)\b/i.test(agent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function hasImplementationIntent(agent: string, taskText: string): boolean {
|
|
143
|
+
if (isReviewerStyleAgent(agent)) return REVIEWER_REQUIRED_EDIT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
144
|
+
if (agent === "builder") return WORKER_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
145
|
+
return GENERAL_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function classifyTaskMutationIntent(agent: string, task: string): TaskMutationIntent {
|
|
149
|
+
const taskText = stripFrameworkInstructions(task);
|
|
150
|
+
const taskTextWithoutScopedConstraints = stripPatterns(taskText, SCOPED_NO_EDIT_CONSTRAINT_PATTERNS);
|
|
151
|
+
const prohibitions = analyzeNoEditProhibitions(taskTextWithoutScopedConstraints);
|
|
152
|
+
if (prohibitions.present) {
|
|
153
|
+
if (prohibitions.blanket) return { kind: "read-only" };
|
|
154
|
+
return hasImplementationIntent(agent, prohibitions.strippedText) ? { kind: "implementation" } : { kind: "read-only" };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (RESEARCH_AGENT_PATTERNS.some((pattern) => pattern.test(agent))) return { kind: "read-only" };
|
|
158
|
+
if (hasImplementationIntent(agent, taskText)) return { kind: "implementation" };
|
|
159
|
+
if (isReviewerStyleAgent(agent)) return { kind: "read-only" };
|
|
160
|
+
return taskHasReadOnlyDeliverable(taskTextWithoutScopedConstraints) ? { kind: "read-only" } : { kind: "unknown" };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function expectsImplementationMutation(agent: string, task: string): boolean {
|
|
164
|
+
return classifyTaskMutationIntent(agent, task).kind === "implementation";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Bare write verbs that make a task write-capable for acceptance inference. */
|
|
168
|
+
const MAY_MUTATE_VERB_PATTERN = /\b(?:fix|implement|update|write|edit|modify|migrate|delete|remove|refactor|commit)\b/i;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Whether the task could plausibly change files. Blanket prohibitions win;
|
|
172
|
+
* write verbs inside a scoped prohibition's object or a read-only deliverable
|
|
173
|
+
* phrase ("write a summary report") do not count; any other bare write verb
|
|
174
|
+
* does.
|
|
175
|
+
*/
|
|
176
|
+
export function taskMayMutate(task: string): boolean {
|
|
177
|
+
const taskText = stripPatterns(stripFrameworkInstructions(task), SCOPED_NO_EDIT_CONSTRAINT_PATTERNS);
|
|
178
|
+
const prohibitions = analyzeNoEditProhibitions(taskText);
|
|
179
|
+
if (prohibitions.blanket) return false;
|
|
180
|
+
return MAY_MUTATE_VERB_PATTERN.test(stripPatterns(prohibitions.strippedText, READ_ONLY_DELIVERABLE_PATTERNS));
|
|
181
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const REQUIRED_CHILD_TOOLS_ENV = "SELESAI_SUBAGENT_REQUIRED_TOOLS";
|
|
5
|
+
export const MCP_DIRECT_CHILD_TOOLS_ENV = "SELESAI_SUBAGENT_MCP_DIRECT_TOOLS";
|
|
6
|
+
export const CHILD_TOOL_DIAGNOSTIC_PATH_ENV = "SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH";
|
|
7
|
+
|
|
8
|
+
export interface ChildToolDiagnostic {
|
|
9
|
+
agent?: string;
|
|
10
|
+
required: string[];
|
|
11
|
+
available: string[];
|
|
12
|
+
missing: string[];
|
|
13
|
+
missingMcpDirectTools?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PI_CORE_CHILD_TOOLS = new Set(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
|
17
|
+
|
|
18
|
+
export function writeChildToolDiagnostic(
|
|
19
|
+
filePath: string,
|
|
20
|
+
required: string[],
|
|
21
|
+
available: string[],
|
|
22
|
+
agent?: string,
|
|
23
|
+
mcpDirectTools?: string[],
|
|
24
|
+
): ChildToolDiagnostic | undefined {
|
|
25
|
+
const availableNames = new Set([...available, ...PI_CORE_CHILD_TOOLS]);
|
|
26
|
+
const missing = required.filter((name) => !availableNames.has(name));
|
|
27
|
+
if (missing.length === 0) {
|
|
28
|
+
fs.rmSync(filePath, { force: true });
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const missingMcpDirectTools = mcpDirectTools?.length
|
|
33
|
+
? missing.filter((name) => mcpDirectTools.includes(name))
|
|
34
|
+
: [];
|
|
35
|
+
const diagnostic: ChildToolDiagnostic = {
|
|
36
|
+
agent,
|
|
37
|
+
required,
|
|
38
|
+
available,
|
|
39
|
+
missing,
|
|
40
|
+
...(missingMcpDirectTools.length > 0 ? { missingMcpDirectTools } : {}),
|
|
41
|
+
};
|
|
42
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
43
|
+
fs.writeFileSync(filePath, JSON.stringify(diagnostic), { mode: 0o600 });
|
|
44
|
+
return diagnostic;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readChildToolDiagnostic(filePath: string | undefined): ChildToolDiagnostic | undefined {
|
|
48
|
+
if (!filePath || !fs.existsSync(filePath)) return undefined;
|
|
49
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Partial<ChildToolDiagnostic>;
|
|
50
|
+
const stringArray = (value: unknown): value is string[] => Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.length > 0);
|
|
51
|
+
if (!stringArray(parsed.required) || !stringArray(parsed.available) || !stringArray(parsed.missing) || (parsed.agent !== undefined && typeof parsed.agent !== "string") || (parsed.missingMcpDirectTools !== undefined && !stringArray(parsed.missingMcpDirectTools))) {
|
|
52
|
+
throw new Error(`Malformed child tool diagnostic at '${filePath}'.`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...(parsed.agent ? { agent: parsed.agent } : {}),
|
|
56
|
+
required: parsed.required,
|
|
57
|
+
available: parsed.available,
|
|
58
|
+
missing: parsed.missing,
|
|
59
|
+
...(parsed.missingMcpDirectTools ? { missingMcpDirectTools: parsed.missingMcpDirectTools } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatChildToolDiagnostic(diagnostic: ChildToolDiagnostic): string {
|
|
64
|
+
const subject = diagnostic.agent ? `Agent '${diagnostic.agent}'` : "Subagent";
|
|
65
|
+
return [
|
|
66
|
+
`${subject} requested unavailable child tools: ${diagnostic.missing.join(", ")}.`,
|
|
67
|
+
"The `tools` field is a strict allowlist; it does not load extension code.",
|
|
68
|
+
...(diagnostic.missingMcpDirectTools?.length
|
|
69
|
+
? [`Resolved MCP direct tools missing from the child registry: ${diagnostic.missingMcpDirectTools.join(", ")}. This indicates a host/pi-mcp-adapter registration problem, not a tool-call failure.`]
|
|
70
|
+
: []),
|
|
71
|
+
"For extension tools, add the provider path to `subagentOnlyExtensions` (child-only), `extensions`, or as a path-like entry in `tools`, while keeping each registered tool name in `tools`.",
|
|
72
|
+
"For MCP tools, verify the MCP adapter configuration and selected tool names. For builtin tools, verify the name against the installed Pi version.",
|
|
73
|
+
].join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function readChildToolDiagnosticError(filePath: string | undefined): string | undefined {
|
|
77
|
+
try {
|
|
78
|
+
const diagnostic = readChildToolDiagnostic(filePath);
|
|
79
|
+
return diagnostic ? formatChildToolDiagnostic(diagnostic) : undefined;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return `Failed to read child tool availability diagnostic: ${error instanceof Error ? error.message : String(error)}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { ResolvedToolBudget, ToolBudgetConfig, ToolBudgetState } from "../.
|
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_TOOL_BUDGET_BLOCK = ["read", "grep", "find", "ls"] as const;
|
|
4
4
|
export const TOOL_BUDGET_ENV = "SELESAI_SUBAGENT_TOOL_BUDGET";
|
|
5
|
+
export const TOOL_BUDGET_ZERO_AUTH_ENV = "SELESAI_SUBAGENT_TOOL_BUDGET_ZERO_AUTH";
|
|
5
6
|
|
|
6
7
|
export function normalizeToolBudgetBlock(block: ToolBudgetConfig["block"] | undefined): "*" | string[] {
|
|
7
8
|
if (block === "*") return "*";
|
|
@@ -9,12 +10,17 @@ export function normalizeToolBudgetBlock(block: ToolBudgetConfig["block"] | unde
|
|
|
9
10
|
return [...new Set(block.map((tool) => tool.trim()).filter(Boolean))];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export function validateToolBudgetConfig(
|
|
13
|
+
export function validateToolBudgetConfig(
|
|
14
|
+
raw: unknown,
|
|
15
|
+
label = "toolBudget",
|
|
16
|
+
options: { minimumHard?: 0 | 1 } = {},
|
|
17
|
+
): { budget?: ResolvedToolBudget; error?: string } {
|
|
13
18
|
if (raw === undefined) return {};
|
|
14
19
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { error: `${label} must be an object with hard and optional soft/block.` };
|
|
15
20
|
const value = raw as ToolBudgetConfig;
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
const minimumHard = options.minimumHard ?? 1;
|
|
22
|
+
if (typeof value.hard !== "number" || !Number.isInteger(value.hard) || value.hard < minimumHard) {
|
|
23
|
+
return { error: `${label}.hard must be an integer >= ${minimumHard}.` };
|
|
18
24
|
}
|
|
19
25
|
if (value.soft !== undefined && (typeof value.soft !== "number" || !Number.isInteger(value.soft) || value.soft < 1)) {
|
|
20
26
|
return { error: `${label}.soft must be an integer >= 1 when provided.` };
|
|
@@ -65,10 +71,10 @@ export function encodeToolBudgetEnv(budget: ResolvedToolBudget | undefined): str
|
|
|
65
71
|
return budget ? JSON.stringify(budget) : undefined;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
export function decodeToolBudgetEnv(value: string | undefined): ResolvedToolBudget | undefined {
|
|
74
|
+
export function decodeToolBudgetEnv(value: string | undefined, options: { allowZero?: boolean } = {}): ResolvedToolBudget | undefined {
|
|
69
75
|
if (!value?.trim()) return undefined;
|
|
70
76
|
const parsed = JSON.parse(value) as unknown;
|
|
71
|
-
const normalized = validateToolBudgetConfig(parsed, TOOL_BUDGET_ENV);
|
|
77
|
+
const normalized = validateToolBudgetConfig(parsed, TOOL_BUDGET_ENV, options.allowZero ? { minimumHard: 0 } : undefined);
|
|
72
78
|
if (normalized.error) throw new Error(normalized.error);
|
|
73
79
|
return normalized.budget;
|
|
74
80
|
}
|
|
@@ -2,6 +2,27 @@ import type { ResolvedTurnBudget, TurnBudgetState } from "../../shared/types.ts"
|
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_TURN_BUDGET_GRACE_TURNS = 1;
|
|
4
4
|
|
|
5
|
+
export function resolveTurnBudgetConfig(
|
|
6
|
+
raw: unknown,
|
|
7
|
+
label = "turnBudget",
|
|
8
|
+
): { turnBudget?: ResolvedTurnBudget; error?: string } {
|
|
9
|
+
if (raw === undefined) return {};
|
|
10
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
11
|
+
return { error: `${label} must be an object with maxTurns and optional graceTurns.` };
|
|
12
|
+
}
|
|
13
|
+
const unknownField = Object.keys(raw).find((key) => key !== "maxTurns" && key !== "graceTurns");
|
|
14
|
+
if (unknownField) return { error: `${label}.${unknownField} is not supported.` };
|
|
15
|
+
const budget = raw as { maxTurns?: unknown; graceTurns?: unknown };
|
|
16
|
+
if (typeof budget.maxTurns !== "number" || !Number.isInteger(budget.maxTurns) || budget.maxTurns < 1) {
|
|
17
|
+
return { error: `${label}.maxTurns must be an integer >= 1.` };
|
|
18
|
+
}
|
|
19
|
+
const graceTurns = budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
|
|
20
|
+
if (typeof graceTurns !== "number" || !Number.isInteger(graceTurns) || graceTurns < 0) {
|
|
21
|
+
return { error: `${label}.graceTurns must be an integer >= 0.` };
|
|
22
|
+
}
|
|
23
|
+
return { turnBudget: { maxTurns: budget.maxTurns, graceTurns } };
|
|
24
|
+
}
|
|
25
|
+
|
|
5
26
|
export function appendTurnBudgetSystemPrompt(systemPrompt: string, budget: ResolvedTurnBudget | undefined): string {
|
|
6
27
|
if (!budget) return systemPrompt;
|
|
7
28
|
const grace = budget.graceTurns === 1 ? "1 additional assistant turn" : `${budget.graceTurns} additional assistant turns`;
|
|
@@ -24,6 +45,10 @@ export function turnBudgetExceededMessage(budget: ResolvedTurnBudget, turnCount:
|
|
|
24
45
|
return `Subagent exceeded turn budget after ${turnCount} assistant turn${turnCount === 1 ? "" : "s"} (soft limit ${budget.maxTurns} + grace ${budget.graceTurns}).`;
|
|
25
46
|
}
|
|
26
47
|
|
|
48
|
+
export function turnBudgetDeferredNote(budget: ResolvedTurnBudget, turnCount: number): string {
|
|
49
|
+
return `Turn-budget termination was deferred at ${turnCount} assistant turn${turnCount === 1 ? "" : "s"} (soft limit ${budget.maxTurns} + grace ${budget.graceTurns}) because the assistant started tool work. The run ended before another safe assistant boundary; output may be partial.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
export function formatTurnBudgetOutput(message: string, output: string): string {
|
|
28
53
|
return output.trim()
|
|
29
54
|
? `${message}\n\nPartial output before turn-budget abort:\n${output}`
|
|
@@ -44,9 +69,30 @@ export function turnBudgetState(budget: ResolvedTurnBudget, turnCount: number, e
|
|
|
44
69
|
};
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
export function
|
|
72
|
+
export function turnBudgetDeferredState(
|
|
73
|
+
budget: ResolvedTurnBudget,
|
|
74
|
+
turnCount: number,
|
|
75
|
+
terminationDeferredAtTurn = turnCount,
|
|
76
|
+
): TurnBudgetState {
|
|
77
|
+
return {
|
|
78
|
+
...budget,
|
|
79
|
+
turnCount,
|
|
80
|
+
outcome: "termination-deferred",
|
|
81
|
+
wrapUpRequestedAtTurn: budget.maxTurns,
|
|
82
|
+
terminationDeferredAtTurn,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function turnBudgetDecision(
|
|
87
|
+
budget: ResolvedTurnBudget,
|
|
88
|
+
turnCount: number,
|
|
89
|
+
terminalAssistantStop: boolean,
|
|
90
|
+
toolWorkActiveOrStarting: boolean,
|
|
91
|
+
enforceHardLimit = false,
|
|
92
|
+
): "continue" | "defer" | "abort" {
|
|
48
93
|
const hardLimit = budget.maxTurns + budget.graceTurns;
|
|
49
|
-
if (turnCount < hardLimit) return
|
|
50
|
-
if (
|
|
51
|
-
return
|
|
94
|
+
if (turnCount < hardLimit) return "continue";
|
|
95
|
+
if (toolWorkActiveOrStarting && !enforceHardLimit) return "defer";
|
|
96
|
+
if (turnCount === hardLimit && terminalAssistantStop) return "continue";
|
|
97
|
+
return "abort";
|
|
52
98
|
}
|
|
@@ -18,7 +18,7 @@ interface WorktreeInfo {
|
|
|
18
18
|
syntheticPaths: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
interface WorktreeDiff {
|
|
21
|
+
export interface WorktreeDiff {
|
|
22
22
|
index: number;
|
|
23
23
|
agent: string;
|
|
24
24
|
branch: string;
|
|
@@ -27,6 +27,23 @@ interface WorktreeDiff {
|
|
|
27
27
|
insertions: number;
|
|
28
28
|
deletions: number;
|
|
29
29
|
patchPath: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WorktreeCleanupTask {
|
|
34
|
+
index: number;
|
|
35
|
+
path: string;
|
|
36
|
+
branch: string;
|
|
37
|
+
worktreeRemoved: boolean;
|
|
38
|
+
branchRemoved: boolean;
|
|
39
|
+
errors?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WorktreeCleanupReport {
|
|
43
|
+
state: "complete" | "partial";
|
|
44
|
+
tasks: WorktreeCleanupTask[];
|
|
45
|
+
pruned: boolean;
|
|
46
|
+
errors?: string[];
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
interface WorktreeTaskCwdConflict {
|
|
@@ -426,7 +443,7 @@ function removeSyntheticPathsBeforeDiff(worktree: WorktreeInfo): void {
|
|
|
426
443
|
}
|
|
427
444
|
}
|
|
428
445
|
|
|
429
|
-
function emptyDiff(index: number, agent: string, branch: string, patchPath: string): WorktreeDiff {
|
|
446
|
+
function emptyDiff(index: number, agent: string, branch: string, patchPath: string, error?: string): WorktreeDiff {
|
|
430
447
|
return {
|
|
431
448
|
index,
|
|
432
449
|
agent,
|
|
@@ -436,6 +453,7 @@ function emptyDiff(index: number, agent: string, branch: string, patchPath: stri
|
|
|
436
453
|
insertions: 0,
|
|
437
454
|
deletions: 0,
|
|
438
455
|
patchPath,
|
|
456
|
+
...(error ? { error } : {}),
|
|
439
457
|
};
|
|
440
458
|
}
|
|
441
459
|
|
|
@@ -497,13 +515,30 @@ function writeEmptyPatch(patchPath: string): void {
|
|
|
497
515
|
}
|
|
498
516
|
}
|
|
499
517
|
|
|
500
|
-
function cleanupSingleWorktree(repoCwd: string, worktree: WorktreeInfo):
|
|
501
|
-
|
|
502
|
-
|
|
518
|
+
function cleanupSingleWorktree(repoCwd: string, worktree: WorktreeInfo): WorktreeCleanupTask {
|
|
519
|
+
const errors: string[] = [];
|
|
520
|
+
let worktreeRemoved = false;
|
|
521
|
+
let branchRemoved = false;
|
|
522
|
+
try {
|
|
523
|
+
runGitChecked(repoCwd, ["worktree", "remove", "--force", worktree.path]);
|
|
524
|
+
worktreeRemoved = true;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
errors.push(`worktree removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
503
527
|
}
|
|
504
|
-
try {
|
|
505
|
-
|
|
528
|
+
try {
|
|
529
|
+
runGitChecked(repoCwd, ["branch", "-D", worktree.branch]);
|
|
530
|
+
branchRemoved = true;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
errors.push(`branch removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
506
533
|
}
|
|
534
|
+
return {
|
|
535
|
+
index: worktree.index,
|
|
536
|
+
path: worktree.path,
|
|
537
|
+
branch: worktree.branch,
|
|
538
|
+
worktreeRemoved,
|
|
539
|
+
branchRemoved,
|
|
540
|
+
...(errors.length ? { errors } : {}),
|
|
541
|
+
};
|
|
507
542
|
}
|
|
508
543
|
|
|
509
544
|
function hasWorktreeChanges(diff: WorktreeDiff): boolean {
|
|
@@ -560,23 +595,37 @@ export function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir:
|
|
|
560
595
|
const patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);
|
|
561
596
|
try {
|
|
562
597
|
diffs.push(captureWorktreeDiff(setup, worktree, agent, patchPath));
|
|
563
|
-
} catch {
|
|
564
|
-
// Preserve execution flow
|
|
598
|
+
} catch (error) {
|
|
599
|
+
// Preserve execution flow while retaining the failed capture as handoff evidence.
|
|
565
600
|
writeEmptyPatch(patchPath);
|
|
566
|
-
diffs.push(emptyDiff(index, agent, worktree.branch, patchPath));
|
|
601
|
+
diffs.push(emptyDiff(index, agent, worktree.branch, patchPath, error instanceof Error ? error.message : String(error)));
|
|
567
602
|
}
|
|
568
603
|
}
|
|
569
604
|
|
|
570
605
|
return diffs;
|
|
571
606
|
}
|
|
572
607
|
|
|
573
|
-
export function cleanupWorktrees(setup: WorktreeSetup):
|
|
608
|
+
export function cleanupWorktrees(setup: WorktreeSetup): WorktreeCleanupReport {
|
|
609
|
+
const tasks: WorktreeCleanupTask[] = [];
|
|
574
610
|
for (let index = setup.worktrees.length - 1; index >= 0; index--) {
|
|
575
|
-
cleanupSingleWorktree(setup.cwd, setup.worktrees[index]!);
|
|
611
|
+
tasks.push(cleanupSingleWorktree(setup.cwd, setup.worktrees[index]!));
|
|
576
612
|
}
|
|
577
|
-
|
|
578
|
-
|
|
613
|
+
tasks.sort((left, right) => left.index - right.index);
|
|
614
|
+
const errors: string[] = [];
|
|
615
|
+
let pruned = false;
|
|
616
|
+
try {
|
|
617
|
+
runGitChecked(setup.cwd, ["worktree", "prune"]);
|
|
618
|
+
pruned = true;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
errors.push(`worktree prune failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
579
621
|
}
|
|
622
|
+
const state = tasks.every((task) => task.worktreeRemoved && task.branchRemoved) && pruned ? "complete" : "partial";
|
|
623
|
+
return {
|
|
624
|
+
state,
|
|
625
|
+
tasks,
|
|
626
|
+
pruned,
|
|
627
|
+
...(errors.length ? { errors } : {}),
|
|
628
|
+
};
|
|
580
629
|
}
|
|
581
630
|
|
|
582
631
|
export function formatWorktreeDiffSummary(diffs: WorktreeDiff[]): string {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS, runFileSystemOperationWithRetry, waitForFileSystemRetry } from "./file-system-retry.ts";
|
|
3
|
+
|
|
4
|
+
type AccessibleDirFs = Pick<typeof fs, "accessSync" | "mkdirSync">;
|
|
5
|
+
|
|
6
|
+
type AccessibleDirOptions = {
|
|
7
|
+
fs?: AccessibleDirFs;
|
|
8
|
+
retryDirectoryErrors?: boolean;
|
|
9
|
+
retryDelaysMs?: readonly number[];
|
|
10
|
+
wait?: (delayMs: number) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function ensureAccessibleDir(dirPath: string, options: AccessibleDirOptions = {}): void {
|
|
14
|
+
const fsImpl = options.fs ?? fs;
|
|
15
|
+
const retryDirectoryErrors = options.retryDirectoryErrors ?? process.platform === "win32";
|
|
16
|
+
const retryDelaysMs = retryDirectoryErrors ? options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS : [];
|
|
17
|
+
const wait = options.wait ?? waitForFileSystemRetry;
|
|
18
|
+
|
|
19
|
+
runFileSystemOperationWithRetry(() => {
|
|
20
|
+
fsImpl.mkdirSync(dirPath, { recursive: true });
|
|
21
|
+
}, { retryDelaysMs, wait });
|
|
22
|
+
runFileSystemOperationWithRetry(() => {
|
|
23
|
+
fsImpl.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
24
|
+
}, { retryDelaysMs, wait });
|
|
25
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { TEMP_ARTIFACTS_DIR, type ArtifactPaths } from "./types.ts";
|
|
3
|
+
import { TEMP_ARTIFACTS_DIR, type ArtifactPaths, type ArtifactDirPreference } from "./types.ts";
|
|
4
4
|
import { getAgentDir } from "./utils.ts";
|
|
5
5
|
const CLEANUP_MARKER_FILE = ".last-cleanup";
|
|
6
6
|
const PROJECT_ARTIFACT_ROOT = ".pi-subagents";
|
|
@@ -17,13 +17,30 @@ export function getProjectChainRunsDir(cwd: string): string {
|
|
|
17
17
|
return path.join(getProjectSubagentsDir(cwd), "chain-runs");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function getArtifactsDir(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
export function getArtifactsDir(
|
|
21
|
+
sessionFile: string | null,
|
|
22
|
+
projectCwd?: string,
|
|
23
|
+
dirPreference: ArtifactDirPreference = "project",
|
|
24
|
+
): string {
|
|
25
|
+
switch (dirPreference) {
|
|
26
|
+
case "session":
|
|
27
|
+
if (sessionFile) {
|
|
28
|
+
const sessionDir = path.dirname(sessionFile);
|
|
29
|
+
return path.join(sessionDir, "subagent-artifacts");
|
|
30
|
+
}
|
|
31
|
+
return TEMP_ARTIFACTS_DIR;
|
|
32
|
+
case "temp":
|
|
33
|
+
return TEMP_ARTIFACTS_DIR;
|
|
34
|
+
case "project":
|
|
35
|
+
if (projectCwd) return getProjectArtifactsDir(projectCwd);
|
|
36
|
+
if (sessionFile) {
|
|
37
|
+
const sessionDir = path.dirname(sessionFile);
|
|
38
|
+
return path.join(sessionDir, "subagent-artifacts");
|
|
39
|
+
}
|
|
40
|
+
return TEMP_ARTIFACTS_DIR;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected "project", "session", or "temp".`);
|
|
25
43
|
}
|
|
26
|
-
return TEMP_ARTIFACTS_DIR;
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
export function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {
|
|
@@ -47,6 +64,19 @@ export function writeArtifact(filePath: string, content: string): void {
|
|
|
47
64
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
48
65
|
}
|
|
49
66
|
|
|
67
|
+
export function formatOutputArtifactContent(input: {
|
|
68
|
+
output: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
transcriptPath?: string;
|
|
71
|
+
metadataPath?: string;
|
|
72
|
+
}): string {
|
|
73
|
+
if (input.output.trim() || !input.error) return input.output;
|
|
74
|
+
const lines = ["Subagent run failed before producing output.", "", "Error:", input.error];
|
|
75
|
+
if (input.transcriptPath) lines.push("", `Transcript: ${input.transcriptPath}`);
|
|
76
|
+
if (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
export function writeMetadata(filePath: string, metadata: object): void {
|
|
51
81
|
fs.writeFileSync(filePath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
52
82
|
}
|