@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,38 @@
|
|
|
1
|
+
import type { AgentContract, EffectsProjection, ExecutionProjection, ReviewProjection, SingleResult } from "../../shared/types.ts";
|
|
2
|
+
|
|
3
|
+
export function isAgentContractV1(contract: AgentContract | undefined): boolean {
|
|
4
|
+
return contract?.version === 1;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildExecutionProjection(result: Pick<SingleResult, "exitCode" | "error" | "interrupted" | "timedOut" | "stopped" | "detached">): ExecutionProjection {
|
|
8
|
+
if (result.detached) {
|
|
9
|
+
return { status: "detached", success: false, exitCode: result.exitCode, detached: true, ...(result.error ? { error: result.error } : {}) };
|
|
10
|
+
}
|
|
11
|
+
if (result.stopped) {
|
|
12
|
+
return { status: "stopped", success: false, exitCode: result.exitCode, stopped: true, ...(result.error ? { error: result.error } : {}) };
|
|
13
|
+
}
|
|
14
|
+
if (result.interrupted) {
|
|
15
|
+
return { status: "paused", success: true, exitCode: result.exitCode, interrupted: true, ...(result.error ? { error: result.error } : {}) };
|
|
16
|
+
}
|
|
17
|
+
const success = result.exitCode === 0 && !result.error && !result.timedOut;
|
|
18
|
+
return {
|
|
19
|
+
status: success ? "completed" : "failed",
|
|
20
|
+
success,
|
|
21
|
+
exitCode: result.exitCode,
|
|
22
|
+
...(result.error ? { error: result.error } : {}),
|
|
23
|
+
...(result.timedOut ? { timedOut: true } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildReviewProjection(result: Pick<SingleResult, "acceptance">): ReviewProjection {
|
|
28
|
+
const review = result.acceptance?.reviewResult;
|
|
29
|
+
if (!review) return { status: "not-requested" };
|
|
30
|
+
return { status: review.status, findings: review.findings };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function attachContractProjections<T extends SingleResult>(result: T): T {
|
|
34
|
+
result.execution = buildExecutionProjection(result);
|
|
35
|
+
result.review = buildReviewProjection(result);
|
|
36
|
+
if (!result.effects) result.effects = {} satisfies EffectsProjection;
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import "../../shared/env.ts";
|
|
3
|
+
|
|
4
|
+
export const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;
|
|
5
|
+
export const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = "pi-subagents.capability-ceiling.v1";
|
|
6
|
+
export const SUBAGENT_CAPABILITY_CEILING_ENV = "SELESAI_SUBAGENT_CAPABILITY_CEILING_V1";
|
|
7
|
+
|
|
8
|
+
export type SubagentCapabilityCeiling =
|
|
9
|
+
| { allowedTools: readonly string[]; denyExtensions?: boolean }
|
|
10
|
+
| { allowedTools?: readonly string[]; denyExtensions: boolean };
|
|
11
|
+
|
|
12
|
+
export interface ResolvedSubagentCapabilityCeiling {
|
|
13
|
+
version: typeof SUBAGENT_CAPABILITY_CEILING_VERSION;
|
|
14
|
+
allowedTools?: string[];
|
|
15
|
+
denyExtensions: boolean;
|
|
16
|
+
sources: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SubagentCapabilityAudit {
|
|
20
|
+
ceiling: ResolvedSubagentCapabilityCeiling;
|
|
21
|
+
requestedTools?: string[];
|
|
22
|
+
effectiveTools: string[];
|
|
23
|
+
removedTools: string[];
|
|
24
|
+
internalTools: string[];
|
|
25
|
+
extensionsDenied: boolean;
|
|
26
|
+
removedExtensionCount: number;
|
|
27
|
+
requestedMcpToolCount: number;
|
|
28
|
+
effectiveMcpTools: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RegisterSubagentCapabilityCeilingOptions {
|
|
32
|
+
sessionId: string;
|
|
33
|
+
source: string;
|
|
34
|
+
ceiling: SubagentCapabilityCeiling;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SubagentCapabilityCeilingHandle {
|
|
38
|
+
update(ceiling: SubagentCapabilityCeiling): void;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Registration = { source: string; ceiling: ResolvedSubagentCapabilityCeiling };
|
|
43
|
+
type Registry = Map<string, Map<symbol, Registration>>;
|
|
44
|
+
|
|
45
|
+
function registry(): Registry {
|
|
46
|
+
const key = Symbol.for(SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY);
|
|
47
|
+
const store = globalThis as typeof globalThis & { [key: symbol]: unknown };
|
|
48
|
+
const existing = store[key];
|
|
49
|
+
if (existing instanceof Map) return existing as Registry;
|
|
50
|
+
const created: Registry = new Map();
|
|
51
|
+
store[key] = created;
|
|
52
|
+
return created;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateText(value: unknown, field: string): string {
|
|
56
|
+
if (typeof value !== "string" || !value.trim() || /[\u0000-\u001f\u007f]/u.test(value) || Buffer.byteLength(value.trim(), "utf8") > 256) {
|
|
57
|
+
throw new Error(`Invalid capability ceiling ${field}; expected a non-empty string without control characters (max 256 UTF-8 bytes).`);
|
|
58
|
+
}
|
|
59
|
+
return value.trim();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeCeiling(ceiling: SubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling {
|
|
63
|
+
if (!ceiling || typeof ceiling !== "object" || Array.isArray(ceiling)) throw new Error("Invalid capability ceiling; expected an object.");
|
|
64
|
+
const hasAllowedTools = Object.hasOwn(ceiling, "allowedTools");
|
|
65
|
+
const hasDenyExtensions = Object.hasOwn(ceiling, "denyExtensions");
|
|
66
|
+
if (!hasAllowedTools && !hasDenyExtensions) throw new Error("Invalid capability ceiling; expected allowedTools or denyExtensions.");
|
|
67
|
+
if (hasDenyExtensions && typeof ceiling.denyExtensions !== "boolean") throw new Error("Invalid capability ceiling denyExtensions; expected a boolean.");
|
|
68
|
+
let allowedTools: string[] | undefined;
|
|
69
|
+
if (hasAllowedTools) {
|
|
70
|
+
if (!Array.isArray(ceiling.allowedTools)) throw new Error("Invalid capability ceiling allowedTools; expected an array.");
|
|
71
|
+
if (ceiling.allowedTools.length > 256) throw new Error("Invalid capability ceiling allowedTools; expected at most 256 names.");
|
|
72
|
+
allowedTools = [...new Set(ceiling.allowedTools.map((tool) => {
|
|
73
|
+
const name = validateText(tool, "allowedTools entry");
|
|
74
|
+
if (!/^[A-Za-z0-9_.:-]+$/u.test(name)) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'.`);
|
|
75
|
+
if (Buffer.byteLength(name, "utf8") > 128) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'; max 128 UTF-8 bytes.`);
|
|
76
|
+
return name;
|
|
77
|
+
}))].sort();
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
version: SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
81
|
+
...(allowedTools ? { allowedTools } : {}),
|
|
82
|
+
denyExtensions: ceiling.denyExtensions === true,
|
|
83
|
+
sources: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parseSubagentCapabilityCeiling(value: unknown, field = "capability ceiling"): ResolvedSubagentCapabilityCeiling {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid ${field}; expected an object.`);
|
|
89
|
+
const record = value as Record<string, unknown>;
|
|
90
|
+
if (record.version !== SUBAGENT_CAPABILITY_CEILING_VERSION) throw new Error(`Invalid ${field} version.`);
|
|
91
|
+
const normalized = normalizeCeiling(record as SubagentCapabilityCeiling);
|
|
92
|
+
const sources = record.sources;
|
|
93
|
+
if (!Array.isArray(sources) || sources.some((source) => typeof source !== "string")) throw new Error(`Invalid ${field} sources; expected an array of strings.`);
|
|
94
|
+
normalized.sources = [...new Set(sources.map((source) => validateText(source, `${field} source`)))].sort();
|
|
95
|
+
return normalized;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle {
|
|
99
|
+
const sessionId = validateText(options.sessionId, "sessionId");
|
|
100
|
+
const source = validateText(options.source, "source");
|
|
101
|
+
let normalized = normalizeCeiling(options.ceiling);
|
|
102
|
+
const token = Symbol(source);
|
|
103
|
+
const store = registry();
|
|
104
|
+
let session = store.get(sessionId);
|
|
105
|
+
if (!session) {
|
|
106
|
+
session = new Map();
|
|
107
|
+
store.set(sessionId, session);
|
|
108
|
+
}
|
|
109
|
+
const setRegistration = () => {
|
|
110
|
+
normalized = normalizeCeiling(normalized);
|
|
111
|
+
normalized.sources = [source];
|
|
112
|
+
session!.set(token, { source, ceiling: normalized });
|
|
113
|
+
};
|
|
114
|
+
setRegistration();
|
|
115
|
+
let disposed = false;
|
|
116
|
+
return {
|
|
117
|
+
update(ceiling) {
|
|
118
|
+
if (disposed) throw new Error("Cannot update a disposed capability ceiling handle.");
|
|
119
|
+
normalized = normalizeCeiling(ceiling);
|
|
120
|
+
normalized.sources = [source];
|
|
121
|
+
session!.set(token, { source, ceiling: normalized });
|
|
122
|
+
},
|
|
123
|
+
dispose() {
|
|
124
|
+
if (disposed) return;
|
|
125
|
+
disposed = true;
|
|
126
|
+
session!.delete(token);
|
|
127
|
+
if (session!.size === 0) store.delete(sessionId);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined {
|
|
133
|
+
const active = ceilings.filter((ceiling): ceiling is ResolvedSubagentCapabilityCeiling => ceiling !== undefined);
|
|
134
|
+
if (active.length === 0) return undefined;
|
|
135
|
+
const definedLists = active.filter((ceiling) => ceiling.allowedTools !== undefined).map((ceiling) => new Set(ceiling.allowedTools));
|
|
136
|
+
let allowedTools: string[] | undefined;
|
|
137
|
+
if (definedLists.length > 0) {
|
|
138
|
+
allowedTools = [...definedLists[0]!].filter((tool) => definedLists.every((list) => list.has(tool))).sort();
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
version: SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
142
|
+
...(allowedTools ? { allowedTools } : {}),
|
|
143
|
+
denyExtensions: active.some((ceiling) => ceiling.denyExtensions),
|
|
144
|
+
sources: [...new Set(active.flatMap((ceiling) => ceiling.sources))].sort(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined {
|
|
149
|
+
const active: ResolvedSubagentCapabilityCeiling[] = [];
|
|
150
|
+
if (sessionId) {
|
|
151
|
+
const registrations = registry().get(sessionId);
|
|
152
|
+
if (registrations) active.push(...Array.from(registrations.values(), ({ ceiling }) => ceiling));
|
|
153
|
+
}
|
|
154
|
+
return intersectSubagentCapabilityCeilings(inherited, ...active);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
|
|
158
|
+
return resolveSubagentCapabilityCeiling(sessionId, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function encodeSubagentCapabilityCeiling(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {
|
|
162
|
+
if (!ceiling) return undefined;
|
|
163
|
+
return Buffer.from(JSON.stringify(ceiling), "utf8").toString("base64url");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function decodeSubagentCapabilityCeiling(value: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
|
|
167
|
+
if (value === undefined || value === "") return undefined;
|
|
168
|
+
let parsed: unknown;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error(`Invalid inherited capability ceiling: ${error instanceof Error ? error.message : String(error)}`);
|
|
173
|
+
}
|
|
174
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || (parsed as { version?: unknown }).version !== SUBAGENT_CAPABILITY_CEILING_VERSION) {
|
|
175
|
+
throw new Error("Invalid inherited capability ceiling version.");
|
|
176
|
+
}
|
|
177
|
+
return parseSubagentCapabilityCeiling(parsed, "inherited capability ceiling");
|
|
178
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ProtocolOutputLimit } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export type { ProtocolOutputLimit } from "../../shared/types.ts";
|
|
5
|
+
|
|
6
|
+
export const MAX_CHILD_PENDING_LINE_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
export const MAX_CHILD_STDERR_BYTES = 128 * 1024;
|
|
8
|
+
const MAX_PROTOCOL_DIAGNOSTIC_BYTES = 4096;
|
|
9
|
+
|
|
10
|
+
export function formatProtocolOutputLimit(limit: ProtocolOutputLimit): string {
|
|
11
|
+
return `${limit.code}: child ${limit.stream} line exceeded ${limit.limitBytes} bytes (observed at least ${limit.observedBytes} bytes without a newline).`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createBoundedLineReader(options: {
|
|
15
|
+
stream?: "stdout" | "stderr";
|
|
16
|
+
maxPendingLineBytes?: number;
|
|
17
|
+
onLine: (line: string) => void;
|
|
18
|
+
onLimit: (limit: ProtocolOutputLimit) => void;
|
|
19
|
+
}): {
|
|
20
|
+
push(chunk: Buffer | string): void;
|
|
21
|
+
end(): void;
|
|
22
|
+
exceeded(): boolean;
|
|
23
|
+
} {
|
|
24
|
+
const maxPendingLineBytes = options.maxPendingLineBytes ?? MAX_CHILD_PENDING_LINE_BYTES;
|
|
25
|
+
if (!Number.isInteger(maxPendingLineBytes) || maxPendingLineBytes < 1) {
|
|
26
|
+
throw new Error("maxPendingLineBytes must be a positive integer.");
|
|
27
|
+
}
|
|
28
|
+
let pending: Buffer[] = [];
|
|
29
|
+
let pendingBytes = 0;
|
|
30
|
+
let limitExceeded = false;
|
|
31
|
+
|
|
32
|
+
const emitPending = (): void => {
|
|
33
|
+
if (pendingBytes === 0) return;
|
|
34
|
+
options.onLine(Buffer.concat(pending, pendingBytes).toString("utf8"));
|
|
35
|
+
pending = [];
|
|
36
|
+
pendingBytes = 0;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const append = (segment: Buffer): boolean => {
|
|
40
|
+
if (segment.length === 0) return true;
|
|
41
|
+
const observedBytes = pendingBytes + segment.length;
|
|
42
|
+
if (observedBytes > maxPendingLineBytes) {
|
|
43
|
+
const prior = pendingBytes > 0 ? Buffer.concat(pending, pendingBytes) : Buffer.alloc(0);
|
|
44
|
+
const prefixFromPrior = prior.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES);
|
|
45
|
+
const prefix = prefixFromPrior.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
|
|
46
|
+
? prefixFromPrior
|
|
47
|
+
: Buffer.concat([prefixFromPrior, segment.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES - prefixFromPrior.length)]);
|
|
48
|
+
const tailFromSegment = segment.subarray(Math.max(0, segment.length - MAX_PROTOCOL_DIAGNOSTIC_BYTES));
|
|
49
|
+
const tail = tailFromSegment.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
|
|
50
|
+
? tailFromSegment
|
|
51
|
+
: Buffer.concat([prior.subarray(Math.max(0, prior.length - (MAX_PROTOCOL_DIAGNOSTIC_BYTES - tailFromSegment.length))), tailFromSegment]);
|
|
52
|
+
limitExceeded = true;
|
|
53
|
+
pending = [];
|
|
54
|
+
pendingBytes = 0;
|
|
55
|
+
options.onLimit({
|
|
56
|
+
code: "protocol_output_limit",
|
|
57
|
+
stream: options.stream ?? "stdout",
|
|
58
|
+
limitBytes: maxPendingLineBytes,
|
|
59
|
+
observedBytes,
|
|
60
|
+
diagnosticPrefix: prefix.toString("utf8"),
|
|
61
|
+
diagnosticTail: tail.toString("utf8"),
|
|
62
|
+
});
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
pending.push(segment);
|
|
66
|
+
pendingBytes = observedBytes;
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
push(chunk) {
|
|
72
|
+
if (limitExceeded) return;
|
|
73
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
74
|
+
let start = 0;
|
|
75
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
76
|
+
if (bytes[index] !== 0x0a) continue;
|
|
77
|
+
if (!append(bytes.subarray(start, index))) return;
|
|
78
|
+
emitPending();
|
|
79
|
+
start = index + 1;
|
|
80
|
+
}
|
|
81
|
+
append(bytes.subarray(start));
|
|
82
|
+
},
|
|
83
|
+
end() {
|
|
84
|
+
if (!limitExceeded) emitPending();
|
|
85
|
+
},
|
|
86
|
+
exceeded: () => limitExceeded,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function trimToUtf8Boundary(buffer: Buffer, maxBytes: number): Buffer {
|
|
91
|
+
if (buffer.length <= maxBytes) return buffer;
|
|
92
|
+
let start = buffer.length - maxBytes;
|
|
93
|
+
while (start < buffer.length && (buffer[start]! & 0xc0) === 0x80) start++;
|
|
94
|
+
return buffer.subarray(start);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createBoundedByteTail(maxBytes = MAX_CHILD_STDERR_BYTES): {
|
|
98
|
+
push(chunk: Buffer | string): void;
|
|
99
|
+
text(): string;
|
|
100
|
+
byteLength(): number;
|
|
101
|
+
} {
|
|
102
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1) throw new Error("maxBytes must be a positive integer.");
|
|
103
|
+
let tail = Buffer.alloc(0);
|
|
104
|
+
return {
|
|
105
|
+
push(chunk) {
|
|
106
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
107
|
+
tail = trimToUtf8Boundary(Buffer.concat([tail, bytes]), maxBytes);
|
|
108
|
+
},
|
|
109
|
+
text: () => tail.toString("utf8"),
|
|
110
|
+
byteLength: () => tail.length,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type ChildLifecycleAction = "start-drain" | "cancel-drain" | "none";
|
|
115
|
+
|
|
116
|
+
export function projectChildLifecycle(event: { type?: string; willRetry?: unknown }, terminalAssistantStop = false): ChildLifecycleAction {
|
|
117
|
+
if (event.type === "agent_end" && event.willRetry === true) return "cancel-drain";
|
|
118
|
+
if (event.type === "agent_settled") return "start-drain";
|
|
119
|
+
if (terminalAssistantStop) return "start-drain";
|
|
120
|
+
return "none";
|
|
121
|
+
}
|
|
@@ -1,58 +1,8 @@
|
|
|
1
1
|
import type { Message } from "@earendil-works/pi-ai";
|
|
2
|
-
import {
|
|
2
|
+
import { isMutatingTool } from "./long-running-guard.ts";
|
|
3
|
+
import { expectsImplementationMutation } from "./task-intent.ts";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
/\breview only\b/i,
|
|
6
|
-
/\bsuggest fixes only\b/i,
|
|
7
|
-
/\bonly return findings\b/i,
|
|
8
|
-
/\breturn findings only\b/i,
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
12
|
-
/\bmust\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
13
|
-
/\brequired\s+to\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
14
|
-
/\bregardless\s+of\s+findings\b/i,
|
|
15
|
-
/\balways\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
16
|
-
/\bapply\s+(?:the\s+)?fix(?:es)?\s+directly\b/i,
|
|
17
|
-
/\bmake\s+(?:the\s+)?code\s+changes\b/i,
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const EXPLICIT_NO_EDIT_PATTERNS = [
|
|
21
|
-
/\bdo not edit\b/i,
|
|
22
|
-
/\bdon't edit\b/i,
|
|
23
|
-
/\bdo not modify\b/i,
|
|
24
|
-
/\bdo not change files\b/i,
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const SCOPED_NO_EDIT_CONSTRAINT_PATTERNS = [
|
|
28
|
-
/\bdo not edit files?\s+outside\b/i,
|
|
29
|
-
/\bdo not edit\s+outside\b/i,
|
|
30
|
-
/\bdo not edit\s+unrelated files?\b/i,
|
|
31
|
-
/\bdo not change\s+unrelated files?\b/i,
|
|
32
|
-
/\bdo not modify\s+unrelated files?\b/i,
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const RESEARCH_AGENT_PATTERNS = [
|
|
36
|
-
/\binvestigate\b/i,
|
|
37
|
-
/\bscout\b/i,
|
|
38
|
-
/\bresearch(?:er)?\b/i,
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const WORKER_IMPLEMENTATION_PATTERNS = [
|
|
42
|
-
/\b(?:implement|fix|edit|modify|patch|refactor|delete)\b/i,
|
|
43
|
-
/\b(?:update|add|remove|replace|create)\b(?!\s+(?:(?:a|an|the)\s+)?(?:report|summary|findings?)(?:\b|$))/i,
|
|
44
|
-
/\bapply\s+(?:the\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
45
|
-
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
46
|
-
/\bdo those fixes\b/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const GENERAL_IMPLEMENTATION_PATTERNS = [
|
|
50
|
-
/\b(?:implement|fix|edit|modify|patch|refactor)\b/i,
|
|
51
|
-
/\bapply\s+(?:the\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
52
|
-
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
53
|
-
/\bdo those fixes\b/i,
|
|
54
|
-
/\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,
|
|
55
|
-
];
|
|
5
|
+
export { expectsImplementationMutation };
|
|
56
6
|
|
|
57
7
|
const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
58
8
|
"read",
|
|
@@ -66,6 +16,12 @@ const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
|
66
16
|
"contact_supervisor",
|
|
67
17
|
]);
|
|
68
18
|
|
|
19
|
+
// Cursor native edit/write often land as thinking traces (inactive_trace /
|
|
20
|
+
// transcript_trace) rather than toolCall parts when native tool replay is off
|
|
21
|
+
// or the tool is inactive in context. Match pi-cursor-sdk display labels.
|
|
22
|
+
const CURSOR_FILE_MUTATION_THINKING =
|
|
23
|
+
/(?:^|\n)\s*Cursor (?:edit|write)\s*:/i;
|
|
24
|
+
|
|
69
25
|
interface CompletionMutationGuardInput {
|
|
70
26
|
agent: string;
|
|
71
27
|
task: string;
|
|
@@ -80,64 +36,56 @@ interface CompletionMutationGuardResult {
|
|
|
80
36
|
triggered: boolean;
|
|
81
37
|
}
|
|
82
38
|
|
|
83
|
-
function
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.filter((line) => !/^\s*(?:Create and maintain progress at:|Update progress at:|\*\*Output:\*\*|Write your findings to(?: exactly this path)?:|This path is authoritative for this run\.|Ignore any other output filename or output path mentioned elsewhere)/i.test(line))
|
|
88
|
-
.join("\n");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function stripScopedNoEditConstraints(task: string): string {
|
|
92
|
-
let stripped = task;
|
|
93
|
-
for (const pattern of SCOPED_NO_EDIT_CONSTRAINT_PATTERNS) {
|
|
94
|
-
stripped = stripped.replace(pattern, " ");
|
|
95
|
-
}
|
|
96
|
-
return stripped;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function declaresOnlyReadOnlyTools(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
|
|
100
|
-
return tools !== undefined
|
|
101
|
-
&& tools.length > 0
|
|
102
|
-
&& (mcpDirectTools?.length ?? 0) === 0
|
|
103
|
-
&& tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
|
|
39
|
+
export function hasMutationToolCapability(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
|
|
40
|
+
if ((mcpDirectTools?.length ?? 0) > 0) return true;
|
|
41
|
+
if (tools === undefined) return true;
|
|
42
|
+
return !tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
|
|
104
43
|
}
|
|
105
44
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
45
|
+
function hasCheckpointMutationEvidence(message: Message): boolean {
|
|
46
|
+
const record = message as unknown as {
|
|
47
|
+
role?: string;
|
|
48
|
+
type?: string;
|
|
49
|
+
customType?: unknown;
|
|
50
|
+
data?: unknown;
|
|
51
|
+
details?: unknown;
|
|
52
|
+
};
|
|
53
|
+
if ((record.role !== "custom" && record.type !== "custom") || record.customType !== "pi-checkpoint") return false;
|
|
54
|
+
const details = typeof record.details === "object" && record.details !== null && !Array.isArray(record.details)
|
|
55
|
+
? record.details as Record<string, unknown>
|
|
56
|
+
: undefined;
|
|
57
|
+
const data = typeof record.data === "object" && record.data !== null && !Array.isArray(record.data)
|
|
58
|
+
? record.data as Record<string, unknown>
|
|
59
|
+
: typeof details?.beforeCommit === "string" || typeof details?.afterCommit === "string"
|
|
60
|
+
? details
|
|
61
|
+
: details?.data && typeof details.data === "object" && !Array.isArray(details.data)
|
|
62
|
+
? details.data as Record<string, unknown>
|
|
63
|
+
: undefined;
|
|
64
|
+
return typeof data?.beforeCommit === "string"
|
|
65
|
+
&& typeof data.afterCommit === "string"
|
|
66
|
+
&& data.beforeCommit !== data.afterCommit;
|
|
119
67
|
}
|
|
120
68
|
|
|
121
69
|
export function hasMutationToolCall(messages: Message[]): boolean {
|
|
122
70
|
for (const message of messages) {
|
|
71
|
+
if (hasCheckpointMutationEvidence(message)) return true;
|
|
123
72
|
if (message.role !== "assistant") continue;
|
|
124
73
|
for (const part of message.content) {
|
|
74
|
+
if (part.type === "thinking" && CURSOR_FILE_MUTATION_THINKING.test(part.thinking)) return true;
|
|
125
75
|
if (part.type !== "toolCall") continue;
|
|
126
|
-
if (part.name === "edit" || part.name === "write") return true;
|
|
127
|
-
if (part.name !== "bash") continue;
|
|
128
76
|
const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments)
|
|
129
77
|
? part.arguments as Record<string, unknown>
|
|
130
78
|
: {};
|
|
131
|
-
if (
|
|
79
|
+
if (isMutatingTool(part.name, args)) return true;
|
|
132
80
|
}
|
|
133
81
|
}
|
|
134
82
|
return false;
|
|
135
83
|
}
|
|
136
84
|
|
|
137
85
|
export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
|
|
138
|
-
const expectedMutation =
|
|
139
|
-
?
|
|
140
|
-
:
|
|
86
|
+
const expectedMutation = hasMutationToolCapability(input.tools, input.mcpDirectTools)
|
|
87
|
+
? expectsImplementationMutation(input.agent, input.task)
|
|
88
|
+
: false;
|
|
141
89
|
const attemptedMutation = hasMutationToolCall(input.messages);
|
|
142
90
|
return {
|
|
143
91
|
expectedMutation,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type ContextMode = "fresh" | "fork";
|
|
2
|
+
export type ContextSummary = ContextMode | "mixed";
|
|
3
|
+
|
|
4
|
+
export function isContextMode(value: unknown): value is ContextMode {
|
|
5
|
+
return value === "fresh" || value === "fork";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function isContextSummary(value: unknown): value is ContextSummary {
|
|
9
|
+
return isContextMode(value) || value === "mixed";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function summarizeContextModes(modes: Array<ContextMode | undefined>): ContextSummary | undefined {
|
|
13
|
+
const resolved = modes.filter(isContextMode);
|
|
14
|
+
if (resolved.length === 0) return undefined;
|
|
15
|
+
const first = resolved[0]!;
|
|
16
|
+
return resolved.every((mode) => mode === first) ? first : "mixed";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function contextModeLabel(mode: ContextMode | ContextSummary | undefined): string {
|
|
20
|
+
if (mode === "fork") return "[fork]";
|
|
21
|
+
if (mode === "fresh") return "[fresh]";
|
|
22
|
+
if (mode === "mixed") return "[mixed]";
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function contextModeBadge(
|
|
27
|
+
theme: { fg(name: string, text: string): string },
|
|
28
|
+
mode: ContextMode | ContextSummary | undefined,
|
|
29
|
+
): string {
|
|
30
|
+
const label = contextModeLabel(mode);
|
|
31
|
+
if (!label) return "";
|
|
32
|
+
if (mode === "fork") return theme.fg("warning", ` ${label}`);
|
|
33
|
+
return theme.fg("dim", ` ${label}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function contextModePrefix(
|
|
37
|
+
theme: { fg(name: string, text: string): string },
|
|
38
|
+
mode: ContextMode | ContextSummary | undefined,
|
|
39
|
+
): string {
|
|
40
|
+
const label = contextModeLabel(mode);
|
|
41
|
+
if (!label) return "";
|
|
42
|
+
if (mode === "fork") return `${theme.fg("warning", label)} `;
|
|
43
|
+
return `${theme.fg("dim", label)} `;
|
|
44
|
+
}
|
|
@@ -27,6 +27,7 @@ export interface DynamicCollectedResult {
|
|
|
27
27
|
structured?: unknown;
|
|
28
28
|
error?: string;
|
|
29
29
|
timedOut?: boolean;
|
|
30
|
+
stopped?: boolean;
|
|
30
31
|
outputPath?: string;
|
|
31
32
|
artifactPaths?: ArtifactPaths;
|
|
32
33
|
}
|
|
@@ -41,16 +42,16 @@ const SAFE_OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
41
42
|
const ITEM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
42
43
|
const ITEM_REF_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)(?:\.([^{}]+))?\}/g;
|
|
43
44
|
const RESERVED_TEMPLATE_NAMES = new Set(["task", "previous", "chain_dir", "outputs"]);
|
|
44
|
-
const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance"]);
|
|
45
|
-
const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "sessionFiles", "thinkingOverrides"]);
|
|
45
|
+
const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance", "agentContract", "gateOn"]);
|
|
46
|
+
const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "sessionFiles", "thinkingOverrides"]);
|
|
46
47
|
const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
|
|
47
48
|
const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
|
|
48
|
-
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "toolBudget", "acceptance"]);
|
|
49
|
+
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "toolBudget", "acceptance", "agentContract", "gateOn"]);
|
|
49
50
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
50
51
|
...DYNAMIC_PARALLEL_KEYS,
|
|
51
|
-
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "maxSubagentDepth",
|
|
52
|
-
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "completionGuard", "systemPrompt",
|
|
53
|
-
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "parentSessionId",
|
|
52
|
+
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "waitToolEnabled",
|
|
53
|
+
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "capabilityCeiling", "completionGuard", "systemPrompt",
|
|
54
|
+
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId",
|
|
54
55
|
]);
|
|
55
56
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
56
57
|
|
|
@@ -263,7 +264,7 @@ export function materializeDynamicParallelStep(step: DynamicParallelStep, output
|
|
|
263
264
|
export function collectDynamicResults(
|
|
264
265
|
step: DynamicParallelStep,
|
|
265
266
|
items: DynamicMaterializedItem[],
|
|
266
|
-
results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
|
|
267
|
+
results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "stopped" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
|
|
267
268
|
): DynamicCollectedResult[] {
|
|
268
269
|
return items.map((entry, index) => {
|
|
269
270
|
const result = results[index];
|
|
@@ -280,15 +281,16 @@ export function collectDynamicResults(
|
|
|
280
281
|
...(result?.structuredOutput !== undefined ? { structured: result.structuredOutput } : {}),
|
|
281
282
|
...(result?.error ? { error: result.error } : {}),
|
|
282
283
|
...(result?.timedOut ? { timedOut: true } : {}),
|
|
284
|
+
...(result?.stopped ? { stopped: true } : {}),
|
|
283
285
|
...(result?.savedOutputPath ? { outputPath: result.savedOutputPath } : {}),
|
|
284
286
|
...(result?.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
285
287
|
};
|
|
286
288
|
});
|
|
287
289
|
}
|
|
288
290
|
|
|
289
|
-
export function validateDynamicCollection(schema: JsonSchemaObject | undefined, value: DynamicCollectedResult[]): void {
|
|
291
|
+
export async function validateDynamicCollection(schema: JsonSchemaObject | undefined, value: DynamicCollectedResult[]): Promise<void> {
|
|
290
292
|
if (!schema) return;
|
|
291
|
-
const validation = validateStructuredOutputValue(schema, value);
|
|
293
|
+
const validation = await validateStructuredOutputValue(schema, value);
|
|
292
294
|
if (validation.status === "invalid") {
|
|
293
295
|
throw new DynamicFanoutError(`Collected output validation failed: ${validation.message}`);
|
|
294
296
|
}
|
|
@@ -103,6 +103,10 @@ export function isMutatingBashCommand(command: string): boolean {
|
|
|
103
103
|
export function isMutatingTool(toolName: string | undefined, args: Record<string, unknown> | undefined): boolean {
|
|
104
104
|
if (!toolName) return false;
|
|
105
105
|
if (toolName === "edit" || toolName === "write") return true;
|
|
106
|
+
if (toolName === "cursor") {
|
|
107
|
+
const activityTitle = typeof args?.activityTitle === "string" ? args.activityTitle : "";
|
|
108
|
+
return /^Cursor (?:edit|write)\b/i.test(activityTitle);
|
|
109
|
+
}
|
|
106
110
|
if (toolName !== "bash") return false;
|
|
107
111
|
const command = typeof args?.command === "string" ? args.command : "";
|
|
108
112
|
if (!command.trim()) return false;
|