@nathapp/nax-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
- package/dist/coding-tools/coding-tool-bash.js +44 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
- package/dist/coding-tools/coding-tool-extras.js +56 -0
- package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
- package/dist/coding-tools/coding-tool-sandbox.js +215 -0
- package/dist/coding-tools/coding-tool-support.d.ts +132 -0
- package/dist/coding-tools/coding-tool-support.js +159 -0
- package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
- package/dist/coding-tools/universal-coding-tools.js +5 -0
- package/dist/command-interceptor/index.d.ts +55 -0
- package/dist/command-interceptor/index.js +60 -0
- package/dist/command-interceptor/shell.d.ts +36 -0
- package/dist/command-interceptor/shell.js +137 -0
- package/dist/command-safety/build.d.ts +19 -0
- package/dist/command-safety/build.js +47 -0
- package/dist/command-safety/guard.d.ts +32 -0
- package/dist/command-safety/guard.js +179 -0
- package/dist/command-safety/identifiers.d.ts +24 -0
- package/dist/command-safety/identifiers.js +20 -0
- package/dist/command-safety/index.d.ts +17 -0
- package/dist/command-safety/index.js +11 -0
- package/dist/command-safety/questions.d.ts +19 -0
- package/dist/command-safety/questions.js +60 -0
- package/dist/command-safety/row.d.ts +2 -0
- package/dist/command-safety/row.js +14 -0
- package/dist/command-safety/rule-scorer.d.ts +9 -0
- package/dist/command-safety/rule-scorer.js +391 -0
- package/dist/command-safety/shadow.d.ts +33 -0
- package/dist/command-safety/shadow.js +188 -0
- package/dist/command-safety/systemone-client.d.ts +16 -0
- package/dist/command-safety/systemone-client.js +92 -0
- package/dist/command-safety/tap.d.ts +37 -0
- package/dist/command-safety/tap.js +77 -0
- package/dist/command-safety/temp-only.d.ts +6 -0
- package/dist/command-safety/temp-only.js +215 -0
- package/dist/command-safety/tmp-write.d.ts +2 -0
- package/dist/command-safety/tmp-write.js +112 -0
- package/dist/command-safety/types.d.ts +185 -0
- package/dist/command-safety/types.js +21 -0
- package/dist/config/bash-approval.d.ts +33 -0
- package/dist/config/bash-approval.js +30 -0
- package/dist/config/catalog-overrides.d.ts +94 -0
- package/dist/config/catalog-overrides.js +7 -0
- package/dist/config/native-agent/index.d.ts +8 -0
- package/dist/config/native-agent/index.js +8 -0
- package/dist/config/schemas-sandbox.d.ts +31 -0
- package/dist/config/schemas-sandbox.js +45 -0
- package/dist/cost/core/index.d.ts +8 -0
- package/dist/cost/core/index.js +7 -0
- package/dist/cost/estimate.d.ts +34 -0
- package/dist/cost/estimate.js +65 -0
- package/dist/cost/model-spec.d.ts +28 -0
- package/dist/cost/model-spec.js +29 -0
- package/dist/cost/standard-types.d.ts +9 -0
- package/dist/cost/standard-types.js +1 -0
- package/dist/cost/usage-math.d.ts +18 -0
- package/dist/cost/usage-math.js +46 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/infra/agent-logger.d.ts +22 -0
- package/dist/infra/agent-logger.js +19 -0
- package/dist/infra/credentials-config.d.ts +20 -0
- package/dist/infra/credentials-config.js +15 -0
- package/dist/infra/errors.d.ts +9 -0
- package/dist/infra/errors.js +11 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/nax-error.d.ts +10 -0
- package/dist/infra/nax-error.js +16 -0
- package/dist/infra/spin-breaker/hash.d.ts +2 -0
- package/dist/infra/spin-breaker/hash.js +5 -0
- package/dist/infra/spin-breaker/index.d.ts +120 -0
- package/dist/infra/spin-breaker/index.js +340 -0
- package/dist/internal/agent-output-env.d.ts +44 -0
- package/dist/internal/agent-output-env.js +57 -0
- package/dist/internal/argv-exec.d.ts +58 -0
- package/dist/internal/argv-exec.js +224 -0
- package/dist/internal/bounded-io.d.ts +24 -0
- package/dist/internal/bounded-io.js +51 -0
- package/dist/internal/command-spec/index.d.ts +29 -0
- package/dist/internal/command-spec/index.js +36 -0
- package/dist/internal/describe-value-type.d.ts +8 -0
- package/dist/internal/describe-value-type.js +14 -0
- package/dist/internal/exec-framing.d.ts +18 -0
- package/dist/internal/exec-framing.js +22 -0
- package/dist/internal/file-lock.d.ts +91 -0
- package/dist/internal/file-lock.js +212 -0
- package/dist/internal/file-size.d.ts +2 -0
- package/dist/internal/file-size.js +12 -0
- package/dist/internal/git-add.d.ts +43 -0
- package/dist/internal/git-add.js +59 -0
- package/dist/internal/git-env.d.ts +59 -0
- package/dist/internal/git-env.js +93 -0
- package/dist/internal/git-exec.d.ts +58 -0
- package/dist/internal/git-exec.js +115 -0
- package/dist/internal/path-file-lock.d.ts +23 -0
- package/dist/internal/path-file-lock.js +25 -0
- package/dist/internal/process-alive.d.ts +42 -0
- package/dist/internal/process-alive.js +56 -0
- package/dist/internal/process-kill.d.ts +27 -0
- package/dist/internal/process-kill.js +54 -0
- package/dist/internal/realpath.d.ts +29 -0
- package/dist/internal/realpath.js +52 -0
- package/dist/internal/redact.d.ts +40 -0
- package/dist/internal/redact.js +161 -0
- package/dist/internal/shell-quote.d.ts +2 -0
- package/dist/internal/shell-quote.js +4 -0
- package/dist/internal/sort.d.ts +20 -0
- package/dist/internal/sort.js +20 -0
- package/dist/internal/strip-control-chars.d.ts +19 -0
- package/dist/internal/strip-control-chars.js +38 -0
- package/dist/internal/thenable.d.ts +10 -0
- package/dist/internal/thenable.js +15 -0
- package/dist/internal.d.ts +91 -0
- package/dist/internal.js +91 -0
- package/dist/native/adapter-deps.d.ts +46 -0
- package/dist/native/adapter-deps.js +43 -0
- package/dist/native/auth-types.d.ts +70 -0
- package/dist/native/auth-types.js +13 -0
- package/dist/native/auth.d.ts +133 -0
- package/dist/native/auth.js +362 -0
- package/dist/native/client.d.ts +50 -0
- package/dist/native/client.js +174 -0
- package/dist/native/complete.d.ts +33 -0
- package/dist/native/complete.js +88 -0
- package/dist/native/credentials/chained-store.d.ts +30 -0
- package/dist/native/credentials/chained-store.js +68 -0
- package/dist/native/credentials/change-guard.d.ts +51 -0
- package/dist/native/credentials/change-guard.js +136 -0
- package/dist/native/credentials/exec-source.d.ts +50 -0
- package/dist/native/credentials/exec-source.js +222 -0
- package/dist/native/credentials/fingerprint.d.ts +26 -0
- package/dist/native/credentials/fingerprint.js +174 -0
- package/dist/native/credentials/helper-process.d.ts +79 -0
- package/dist/native/credentials/helper-process.js +259 -0
- package/dist/native/credentials/helper-protocol.d.ts +36 -0
- package/dist/native/credentials/helper-protocol.js +79 -0
- package/dist/native/credentials/index.d.ts +60 -0
- package/dist/native/credentials/index.js +184 -0
- package/dist/native/errors.d.ts +34 -0
- package/dist/native/errors.js +109 -0
- package/dist/native/index.d.ts +25 -0
- package/dist/native/index.js +23 -0
- package/dist/native/model-resolver.d.ts +57 -0
- package/dist/native/model-resolver.js +73 -0
- package/dist/native/models.d.ts +116 -0
- package/dist/native/models.js +183 -0
- package/dist/native/session/ask-human.d.ts +15 -0
- package/dist/native/session/ask-human.js +22 -0
- package/dist/native/session/compaction.d.ts +94 -0
- package/dist/native/session/compaction.js +170 -0
- package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
- package/dist/native/session/handle-invalid-tool-call.js +159 -0
- package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
- package/dist/native/session/loop-events/cache-boundary.js +29 -0
- package/dist/native/session/loop-events/external-handler.d.ts +38 -0
- package/dist/native/session/loop-events/external-handler.js +193 -0
- package/dist/native/session/loop-events/index.d.ts +16 -0
- package/dist/native/session/loop-events/index.js +15 -0
- package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
- package/dist/native/session/loop-events/payload-guard.js +31 -0
- package/dist/native/session/loop-events/registry.d.ts +84 -0
- package/dist/native/session/loop-events/registry.js +305 -0
- package/dist/native/session/loop-events/types.d.ts +251 -0
- package/dist/native/session/loop-events/types.js +14 -0
- package/dist/native/session/loop-handlers.d.ts +73 -0
- package/dist/native/session/loop-handlers.js +137 -0
- package/dist/native/session/nudge.d.ts +29 -0
- package/dist/native/session/nudge.js +35 -0
- package/dist/native/session/rate-provenance.d.ts +17 -0
- package/dist/native/session/rate-provenance.js +39 -0
- package/dist/native/session/session.d.ts +139 -0
- package/dist/native/session/session.js +208 -0
- package/dist/native/session/tool-input-exemplar.d.ts +2 -0
- package/dist/native/session/tool-input-exemplar.js +66 -0
- package/dist/native/session/tool-input-validation.d.ts +15 -0
- package/dist/native/session/tool-input-validation.js +146 -0
- package/dist/native/session/tool-mapping.d.ts +18 -0
- package/dist/native/session/tool-mapping.js +27 -0
- package/dist/native/session/tool-result.d.ts +37 -0
- package/dist/native/session/tool-result.js +25 -0
- package/dist/native/session/transcript-store.d.ts +77 -0
- package/dist/native/session/transcript-store.js +200 -0
- package/dist/native/session/truncation-handler.d.ts +47 -0
- package/dist/native/session/truncation-handler.js +83 -0
- package/dist/native/session/turn-accumulator.d.ts +45 -0
- package/dist/native/session/turn-accumulator.js +67 -0
- package/dist/native/session/turn-ask-human.d.ts +23 -0
- package/dist/native/session/turn-ask-human.js +41 -0
- package/dist/native/session/turn-compaction-step.d.ts +74 -0
- package/dist/native/session/turn-compaction-step.js +128 -0
- package/dist/native/session/turn-complete-step.d.ts +77 -0
- package/dist/native/session/turn-complete-step.js +159 -0
- package/dist/native/session/turn-end-event.d.ts +3 -0
- package/dist/native/session/turn-end-event.js +17 -0
- package/dist/native/session/turn-events.d.ts +63 -0
- package/dist/native/session/turn-events.js +48 -0
- package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
- package/dist/native/session/turn-loop-round-trip.js +318 -0
- package/dist/native/session/turn-loop.d.ts +23 -0
- package/dist/native/session/turn-loop.js +250 -0
- package/dist/native/session/turn-result.d.ts +39 -0
- package/dist/native/session/turn-result.js +64 -0
- package/dist/native/session/turn-retry.d.ts +120 -0
- package/dist/native/session/turn-retry.js +182 -0
- package/dist/native/session/turn-tool-batch.d.ts +61 -0
- package/dist/native/session/turn-tool-batch.js +259 -0
- package/dist/native/session/turn-types.d.ts +164 -0
- package/dist/native/session/turn-types.js +36 -0
- package/dist/native/session-adapter.d.ts +56 -0
- package/dist/native/session-adapter.js +369 -0
- package/dist/native/session-affinity.d.ts +36 -0
- package/dist/native/session-affinity.js +43 -0
- package/dist/native/tier-providers.d.ts +18 -0
- package/dist/native/tier-providers.js +20 -0
- package/dist/permissions/approval-audit.d.ts +10 -0
- package/dist/permissions/approval-audit.js +22 -0
- package/dist/permissions/approvals-link.d.ts +20 -0
- package/dist/permissions/approvals-link.js +62 -0
- package/dist/permissions/approvals-store.d.ts +168 -0
- package/dist/permissions/approvals-store.js +259 -0
- package/dist/permissions/approvals-taint.d.ts +33 -0
- package/dist/permissions/approvals-taint.js +138 -0
- package/dist/permissions/ask-chain.d.ts +69 -0
- package/dist/permissions/ask-chain.js +32 -0
- package/dist/permissions/ask.d.ts +23 -0
- package/dist/permissions/ask.js +25 -0
- package/dist/permissions/bash-lex.d.ts +59 -0
- package/dist/permissions/bash-lex.js +279 -0
- package/dist/permissions/grammar.d.ts +12 -0
- package/dist/permissions/grammar.js +24 -0
- package/dist/permissions/index.d.ts +11 -0
- package/dist/permissions/index.js +9 -0
- package/dist/permissions/secret-spans.d.ts +19 -0
- package/dist/permissions/secret-spans.js +88 -0
- package/dist/permissions/types.d.ts +85 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/node-glob.d.ts +3 -0
- package/dist/runtime/node-glob.js +83 -0
- package/dist/runtime/node-runtime.d.ts +3 -0
- package/dist/runtime/node-runtime.js +126 -0
- package/dist/runtime/slot.d.ts +11 -0
- package/dist/runtime/slot.js +17 -0
- package/dist/runtime/types.d.ts +51 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/which.d.ts +8 -0
- package/dist/runtime/which.js +32 -0
- package/dist/sandbox/argv-quote.d.ts +11 -0
- package/dist/sandbox/argv-quote.js +13 -0
- package/dist/sandbox/defaults.d.ts +27 -0
- package/dist/sandbox/defaults.js +47 -0
- package/dist/sandbox/git-guards.d.ts +64 -0
- package/dist/sandbox/git-guards.js +161 -0
- package/dist/sandbox/index.d.ts +11 -0
- package/dist/sandbox/index.js +10 -0
- package/dist/sandbox/launcher.d.ts +37 -0
- package/dist/sandbox/launcher.js +160 -0
- package/dist/sandbox/messages.d.ts +17 -0
- package/dist/sandbox/messages.js +46 -0
- package/dist/sandbox/policy-builder.d.ts +44 -0
- package/dist/sandbox/policy-builder.js +149 -0
- package/dist/sandbox/policy-inputs.d.ts +53 -0
- package/dist/sandbox/policy-inputs.js +76 -0
- package/dist/sandbox/probe.d.ts +8 -0
- package/dist/sandbox/probe.js +66 -0
- package/dist/sandbox/registry.d.ts +17 -0
- package/dist/sandbox/registry.js +57 -0
- package/dist/sandbox/session-tmp.d.ts +20 -0
- package/dist/sandbox/session-tmp.js +95 -0
- package/dist/sandbox/srt-backend.d.ts +21 -0
- package/dist/sandbox/srt-backend.js +99 -0
- package/dist/sandbox/types.d.ts +102 -0
- package/dist/sandbox/types.js +1 -0
- package/dist/session/adapter-failure.d.ts +60 -0
- package/dist/session/adapter-failure.js +11 -0
- package/dist/session/agent-stream-events.d.ts +103 -0
- package/dist/session/agent-stream-events.js +6 -0
- package/dist/session/interaction-handler.d.ts +53 -0
- package/dist/session/interaction-handler.js +2 -0
- package/dist/session/no-op-interaction-handler.d.ts +9 -0
- package/dist/session/no-op-interaction-handler.js +11 -0
- package/dist/session/protocol-types.d.ts +12 -0
- package/dist/session/protocol-types.js +1 -0
- package/dist/session/session-types.d.ts +456 -0
- package/dist/session/session-types.js +64 -0
- package/dist/session/tool-descriptor.d.ts +23 -0
- package/dist/session/tool-descriptor.js +5 -0
- package/dist/session/turn-deadline.d.ts +17 -0
- package/dist/session/turn-deadline.js +23 -0
- package/dist/tools/ask-request.d.ts +35 -0
- package/dist/tools/ask-request.js +57 -0
- package/dist/tools/bash-cwd.d.ts +82 -0
- package/dist/tools/bash-cwd.js +55 -0
- package/dist/tools/bash.d.ts +69 -0
- package/dist/tools/bash.js +283 -0
- package/dist/tools/delete.d.ts +57 -0
- package/dist/tools/delete.js +154 -0
- package/dist/tools/denial-redirect.d.ts +42 -0
- package/dist/tools/denial-redirect.js +266 -0
- package/dist/tools/deny-paths.d.ts +35 -0
- package/dist/tools/deny-paths.js +76 -0
- package/dist/tools/edit-region.d.ts +50 -0
- package/dist/tools/edit-region.js +90 -0
- package/dist/tools/edit.d.ts +25 -0
- package/dist/tools/edit.js +114 -0
- package/dist/tools/exec-allowlist-text.d.ts +18 -0
- package/dist/tools/exec-allowlist-text.js +24 -0
- package/dist/tools/exec-guard.d.ts +69 -0
- package/dist/tools/exec-guard.js +151 -0
- package/dist/tools/git-commit.d.ts +59 -0
- package/dist/tools/git-commit.js +220 -0
- package/dist/tools/git-flags/index.d.ts +10 -0
- package/dist/tools/git-flags/index.js +10 -0
- package/dist/tools/git.d.ts +88 -0
- package/dist/tools/git.js +397 -0
- package/dist/tools/glob.d.ts +29 -0
- package/dist/tools/glob.js +153 -0
- package/dist/tools/grep.d.ts +21 -0
- package/dist/tools/grep.js +162 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +28 -0
- package/dist/tools/narrow-grants.d.ts +19 -0
- package/dist/tools/narrow-grants.js +54 -0
- package/dist/tools/nax-owned-writes.d.ts +135 -0
- package/dist/tools/nax-owned-writes.js +259 -0
- package/dist/tools/package-managers-table.d.ts +146 -0
- package/dist/tools/package-managers-table.js +325 -0
- package/dist/tools/package-managers-types.d.ts +36 -0
- package/dist/tools/package-managers-types.js +6 -0
- package/dist/tools/package-managers.d.ts +85 -0
- package/dist/tools/package-managers.js +391 -0
- package/dist/tools/path-list.d.ts +38 -0
- package/dist/tools/path-list.js +62 -0
- package/dist/tools/policy-bash-raw.d.ts +19 -0
- package/dist/tools/policy-bash-raw.js +210 -0
- package/dist/tools/policy-bash.d.ts +64 -0
- package/dist/tools/policy-bash.js +232 -0
- package/dist/tools/policy-command-branch.d.ts +32 -0
- package/dist/tools/policy-command-branch.js +63 -0
- package/dist/tools/policy-input.d.ts +2 -0
- package/dist/tools/policy-input.js +10 -0
- package/dist/tools/policy-match.d.ts +75 -0
- package/dist/tools/policy-match.js +117 -0
- package/dist/tools/policy-paths-branch.d.ts +97 -0
- package/dist/tools/policy-paths-branch.js +314 -0
- package/dist/tools/policy.d.ts +65 -0
- package/dist/tools/policy.js +232 -0
- package/dist/tools/protected-paths.d.ts +26 -0
- package/dist/tools/protected-paths.js +8 -0
- package/dist/tools/provider-adapt.d.ts +4 -0
- package/dist/tools/provider-adapt.js +32 -0
- package/dist/tools/provider-advertise.d.ts +39 -0
- package/dist/tools/provider-advertise.js +50 -0
- package/dist/tools/provider-grants.d.ts +36 -0
- package/dist/tools/provider-grants.js +72 -0
- package/dist/tools/provider-sanitize.d.ts +19 -0
- package/dist/tools/provider-sanitize.js +53 -0
- package/dist/tools/provider-types.d.ts +41 -0
- package/dist/tools/provider-types.js +26 -0
- package/dist/tools/read-continuation.d.ts +147 -0
- package/dist/tools/read-continuation.js +176 -0
- package/dist/tools/read-file.d.ts +32 -0
- package/dist/tools/read-file.js +85 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.js +182 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +67 -0
- package/dist/tools/request-capability.d.ts +13 -0
- package/dist/tools/request-capability.js +23 -0
- package/dist/tools/run-command-exec.d.ts +14 -0
- package/dist/tools/run-command-exec.js +161 -0
- package/dist/tools/run-command.d.ts +107 -0
- package/dist/tools/run-command.js +398 -0
- package/dist/tools/runtime-calltool.d.ts +146 -0
- package/dist/tools/runtime-calltool.js +223 -0
- package/dist/tools/runtime.d.ts +149 -0
- package/dist/tools/runtime.js +345 -0
- package/dist/tools/scratchpad.d.ts +20 -0
- package/dist/tools/scratchpad.js +197 -0
- package/dist/tools/spill.d.ts +81 -0
- package/dist/tools/spill.js +251 -0
- package/dist/tools/tool-audit.d.ts +171 -0
- package/dist/tools/tool-audit.js +183 -0
- package/dist/tools/truncate.d.ts +98 -0
- package/dist/tools/truncate.js +297 -0
- package/dist/tools/types.d.ts +145 -0
- package/dist/tools/types.js +24 -0
- package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
- package/dist/tools/verb-denial-argv-hint.js +40 -0
- package/dist/tools/write.d.ts +9 -0
- package/dist/tools/write.js +45 -0
- package/package.json +54 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs a command the PROJECT declared, never one the model wrote.
|
|
3
|
+
*
|
|
4
|
+
* This reaches a shell, deliberately: nax's quality runner
|
|
5
|
+
* (`src/quality/runner.ts`, supplied as `runDeclaredCommand`) executes every
|
|
6
|
+
* configured command through one to preserve their quoting semantics, and a
|
|
7
|
+
* declared command like "CI=1 bun test {{files}}" has no argv form -- CI=1 is a
|
|
8
|
+
* shell assignment, not a binary. Building a second execution path would mean
|
|
9
|
+
* the same command behaved differently depending on who invoked it.
|
|
10
|
+
*
|
|
11
|
+
* So the property here is narrower than the Git tool's, and is stated rather
|
|
12
|
+
* than implied: the model does not author the command string. It names a
|
|
13
|
+
* declared key and supplies placeholder values, and those values are the entire
|
|
14
|
+
* injection surface. They are quoted with shellQuoteArg -- the same helper
|
|
15
|
+
* command-resolver.ts already applies to {{package}}.
|
|
16
|
+
*/
|
|
17
|
+
import { statSync } from "node:fs";
|
|
18
|
+
import { describeValuesType } from "#src/internal/describe-value-type";
|
|
19
|
+
import { shellQuoteArg } from "#src/internal/shell-quote";
|
|
20
|
+
import { sandboxSentence, unsandboxedSentence } from "../sandbox/index.js";
|
|
21
|
+
import { describeExecAllowlist } from "./exec-allowlist-text.js";
|
|
22
|
+
import { pathListElements } from "./path-list.js";
|
|
23
|
+
import { runExecBranch } from "./run-command-exec.js";
|
|
24
|
+
import { cutToByteCap, READ_CEILING } from "./truncate.js";
|
|
25
|
+
const PLACEHOLDER = /\{\{([a-zA-Z]+)\}\}/g;
|
|
26
|
+
function quoteAt(template, end) {
|
|
27
|
+
let quote;
|
|
28
|
+
let escaped = false;
|
|
29
|
+
for (let i = 0; i < end; i += 1) {
|
|
30
|
+
const char = template[i];
|
|
31
|
+
if (escaped) {
|
|
32
|
+
escaped = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === "\\" && quote !== "single") {
|
|
36
|
+
escaped = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (char === "'" && quote !== "double")
|
|
40
|
+
quote = quote === "single" ? undefined : "single";
|
|
41
|
+
if (char === '"' && quote !== "single")
|
|
42
|
+
quote = quote === "double" ? undefined : "double";
|
|
43
|
+
}
|
|
44
|
+
return quote;
|
|
45
|
+
}
|
|
46
|
+
function placeholderContextError(template) {
|
|
47
|
+
for (const match of template.matchAll(PLACEHOLDER)) {
|
|
48
|
+
const start = match.index ?? 0;
|
|
49
|
+
const key = match[1];
|
|
50
|
+
if (quoteAt(template, start) !== undefined) {
|
|
51
|
+
return `placeholder {{${key}}} may not appear inside shell quotes`;
|
|
52
|
+
}
|
|
53
|
+
const tokenStart = Math.max(template.lastIndexOf(" ", start), template.lastIndexOf("\t", start)) + 1;
|
|
54
|
+
const tokenEndMatch = /[ \t\r\n]/.exec(template.slice(start));
|
|
55
|
+
const tokenEnd = tokenEndMatch === null ? template.length : start + tokenEndMatch.index;
|
|
56
|
+
const token = template.slice(tokenStart, tokenEnd);
|
|
57
|
+
if (/[$`()]/.test(token)) {
|
|
58
|
+
return `placeholder {{${key}}} may not appear in a shell expansion`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
// A bare "no" is what produced the defect this exists to fix (#1924): denied
|
|
64
|
+
// with no legal alternative named, the model repeated the identical rejected
|
|
65
|
+
// call 18 times in one session because it was never told the placeholder
|
|
66
|
+
// set. Matches git.ts's "valid for: ..." phrasing convention. A command with
|
|
67
|
+
// no placeholders reads naturally rather than printing "(declared: )".
|
|
68
|
+
function declaredPlaceholdersSuffix(declared) {
|
|
69
|
+
if (declared.size === 0)
|
|
70
|
+
return "this command declares no placeholders";
|
|
71
|
+
return `declared: ${[...declared].join(", ")}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* An ARRAY value is quoted per element and joined; a string value is quoted
|
|
75
|
+
* whole. The distinction carries the element boundaries through substitution,
|
|
76
|
+
* which a pre-joined string cannot: the join would be re-split and a path
|
|
77
|
+
* under `/tmp/my dir/` would become two arguments (nax#1998).
|
|
78
|
+
*/
|
|
79
|
+
export function substituteCommand(template, values) {
|
|
80
|
+
const declared = new Set([...template.matchAll(PLACEHOLDER)].map((m) => m[1]));
|
|
81
|
+
for (const key of Object.keys(values)) {
|
|
82
|
+
if (!declared.has(key)) {
|
|
83
|
+
return { error: `value "${key}" is not a placeholder in this command (${declaredPlaceholdersSuffix(declared)})` };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const key of declared) {
|
|
87
|
+
if (values[key] === undefined) {
|
|
88
|
+
return { error: `placeholder {{${key}}} has no value (${declaredPlaceholdersSuffix(declared)})` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const contextError = placeholderContextError(template);
|
|
92
|
+
if (contextError !== undefined)
|
|
93
|
+
return { error: contextError };
|
|
94
|
+
return template.replaceAll(PLACEHOLDER, (_m, key) => {
|
|
95
|
+
const value = values[key];
|
|
96
|
+
return Array.isArray(value) ? value.map(shellQuoteArg).join(" ") : shellQuoteArg(value);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Apply placeholder substitution across a spec. A list substitutes into every
|
|
101
|
+
* entry and fails as a whole if any entry fails, so a partially-substituted
|
|
102
|
+
* list can never reach the shell.
|
|
103
|
+
*
|
|
104
|
+
* Each entry only receives the subset of `values` it actually declares
|
|
105
|
+
* placeholders for -- list entries commonly have different placeholder needs
|
|
106
|
+
* (e.g. `["tsc --noEmit", "bun test {{files}}"]`), and `substituteCommand`
|
|
107
|
+
* rejects a value that isn't a placeholder in the template it's checking, by
|
|
108
|
+
* design (it's what stops a value silently going nowhere). Passing the full
|
|
109
|
+
* shared `values` object to every entry would make every entry without
|
|
110
|
+
* {{files}} fail that check.
|
|
111
|
+
*
|
|
112
|
+
* But per-entry filtering alone would let a value that no entry in the whole
|
|
113
|
+
* spec declares vanish silently (every entry's filtered set would just be
|
|
114
|
+
* empty, and every entry would substitute cleanly) -- so before filtering
|
|
115
|
+
* per entry, every key in `values` is checked against the UNION of
|
|
116
|
+
* placeholders declared across the whole spec, and rejected with the same
|
|
117
|
+
* "unrecognized value" contract `substituteCommand` gives the single-string
|
|
118
|
+
* case if it isn't declared anywhere.
|
|
119
|
+
*/
|
|
120
|
+
export function substituteCommandSpec(spec, values) {
|
|
121
|
+
if (typeof spec === "string")
|
|
122
|
+
return substituteCommand(spec, values);
|
|
123
|
+
const allDeclared = new Set();
|
|
124
|
+
for (const entry of spec) {
|
|
125
|
+
for (const match of entry.matchAll(PLACEHOLDER))
|
|
126
|
+
allDeclared.add(match[1]);
|
|
127
|
+
}
|
|
128
|
+
for (const key of Object.keys(values)) {
|
|
129
|
+
if (!allDeclared.has(key)) {
|
|
130
|
+
return {
|
|
131
|
+
error: `value "${key}" is not a placeholder in this command (${declaredPlaceholdersSuffix(allDeclared)})`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const entry of spec) {
|
|
137
|
+
const declaredKeys = new Set([...entry.matchAll(PLACEHOLDER)].map((m) => m[1]));
|
|
138
|
+
const entryValues = {};
|
|
139
|
+
for (const [key, value] of Object.entries(values)) {
|
|
140
|
+
if (declaredKeys.has(key))
|
|
141
|
+
entryValues[key] = value;
|
|
142
|
+
}
|
|
143
|
+
const substituted = substituteCommand(entry, entryValues);
|
|
144
|
+
if (typeof substituted !== "string")
|
|
145
|
+
return substituted;
|
|
146
|
+
out.push(substituted);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
// #1924, second half: the description-only fix, which is what prevents the
|
|
151
|
+
// repeated-call loop rather than merely explaining it after the first
|
|
152
|
+
// failure. Renders each declared command with the placeholders its OWN
|
|
153
|
+
// template actually contains, so the model can pick values before it
|
|
154
|
+
// guesses -- e.g. "test (no placeholders), testScoped ({{files}})".
|
|
155
|
+
function placeholdersOf(spec) {
|
|
156
|
+
const templates = typeof spec === "string" ? [spec] : spec;
|
|
157
|
+
return new Set(templates.flatMap((template) => [...template.matchAll(PLACEHOLDER)].map((m) => m[1])));
|
|
158
|
+
}
|
|
159
|
+
function describeDeclaredCommand(name, spec) {
|
|
160
|
+
const placeholders = [...placeholdersOf(spec)];
|
|
161
|
+
if (placeholders.length === 0)
|
|
162
|
+
return `${name} (no placeholders)`;
|
|
163
|
+
return `${name} (${placeholders.map((p) => `{{${p}}}`).join(", ")})`;
|
|
164
|
+
}
|
|
165
|
+
function describeDeclaredCommands(declared) {
|
|
166
|
+
return [...declared.entries()].map(([name, template]) => describeDeclaredCommand(name, template)).join(", ");
|
|
167
|
+
}
|
|
168
|
+
// The live-run defect this exists to fix (run-2026-09-14T08-21-43-607Z): the
|
|
169
|
+
// "values" input was cast straight to Record<string, unknown> with no shape
|
|
170
|
+
// check, so a model sending a STRING got its characters iterated as if they
|
|
171
|
+
// were object keys. An empty string produced no rejected keys at all, so the
|
|
172
|
+
// unrelated "has no value" branch fired instead; "\t" produced the key "0",
|
|
173
|
+
// so the response named a placeholder that was never in the input. 84 of 97
|
|
174
|
+
// RunCommand calls in that run hit one of these two shapes, in a loop the
|
|
175
|
+
// model could not escape because neither message ever said "values" itself
|
|
176
|
+
// was the wrong type. Checked before "raw" is derived, so a malformed shape
|
|
177
|
+
// can never reach Object.keys/Object.entries below.
|
|
178
|
+
function isPlainValuesObject(value) {
|
|
179
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
180
|
+
}
|
|
181
|
+
// #1924, third half (run-2026-09-14T05-55-54-734Z audit, Shape A): the
|
|
182
|
+
// original error only ever named what the CHOSEN command declares, never
|
|
183
|
+
// which OTHER declared command accepts the rejected key -- an agent that
|
|
184
|
+
// wants scoped tests and picks "test" learns "no placeholders" and has to
|
|
185
|
+
// guess a second time. Naming the real alternative ends the loop on the
|
|
186
|
+
// first denial. Returns an empty array (no hint appended) when no other
|
|
187
|
+
// command declares the key either, so a genuinely unrecognized value stays
|
|
188
|
+
// exactly as informative as before -- never a false pointer.
|
|
189
|
+
function otherCommandsDeclaring(declared, key, currentCommand) {
|
|
190
|
+
const names = [];
|
|
191
|
+
for (const [name, spec] of declared) {
|
|
192
|
+
if (name !== currentCommand && placeholdersOf(spec).has(key))
|
|
193
|
+
names.push(name);
|
|
194
|
+
}
|
|
195
|
+
return names;
|
|
196
|
+
}
|
|
197
|
+
function execSandboxNote(launcher) {
|
|
198
|
+
const state = launcher?.state;
|
|
199
|
+
if (state?.kind === "available")
|
|
200
|
+
return ` An "argv" call runs ${sandboxSentence(state.network, state.sharedTmp !== false)}`;
|
|
201
|
+
if (state?.kind === "unavailable")
|
|
202
|
+
return ` ${unsandboxedSentence(state.reason)}`;
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
const MISSING_RUNNER_OUTPUT = "no declared-command runner is configured for this session";
|
|
206
|
+
/** The runner a tool built without one uses: refuses, spawns nothing. */
|
|
207
|
+
async function refuseWithoutRunner() {
|
|
208
|
+
return { success: false, exitCode: 1, output: MISSING_RUNNER_OUTPUT };
|
|
209
|
+
}
|
|
210
|
+
/** One named default, so the refusal path lives in a single place. */
|
|
211
|
+
function declaredCommandRunner(opts) {
|
|
212
|
+
return opts.runDeclaredCommand ?? refuseWithoutRunner;
|
|
213
|
+
}
|
|
214
|
+
export function createRunCommandTool(declared, opts = {}) {
|
|
215
|
+
const keys = [...declared.keys()];
|
|
216
|
+
const exec = opts.exec;
|
|
217
|
+
const hasExec = exec !== undefined;
|
|
218
|
+
const commandDescriptions = describeDeclaredCommands(declared);
|
|
219
|
+
const runDeclared = declaredCommandRunner(opts);
|
|
220
|
+
return {
|
|
221
|
+
name: "RunCommand",
|
|
222
|
+
// A non-zero exit here is the agent's red/green loop, not a fault.
|
|
223
|
+
routineErrors: true,
|
|
224
|
+
description: exec !== undefined
|
|
225
|
+
? `Two ways to run something. (1) Run one of this project's declared commands: ${commandDescriptions} — supply "command" and, optionally, "values" for its placeholders. (2) Run an allowlisted external command via "argv" (an array, e.g. ["bun","add","left-pad"]) — no shell, so no quoting and no shell metacharacters; ${describeExecAllowlist(exec.patterns)}. Supply exactly one of "command" or "argv".${execSandboxNote(exec.launcher)}`
|
|
226
|
+
: `Run one of this project's declared commands: ${commandDescriptions}. Supply values for its placeholders; you cannot write a command of your own.`,
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
command: { type: "string", enum: keys, description: "Which declared command to run" },
|
|
231
|
+
values: {
|
|
232
|
+
type: "object",
|
|
233
|
+
description: 'Values for the command\'s placeholders, e.g. { files: "a.test.ts" }. A path placeholder takes several paths separated by spaces, e.g. { files: "a.test.ts b.test.ts" }, or as an array of paths, e.g. { files: ["test/a.test.ts", "test/b.test.ts"] } -- one call, not one per file.',
|
|
234
|
+
},
|
|
235
|
+
...(hasExec
|
|
236
|
+
? {
|
|
237
|
+
argv: {
|
|
238
|
+
type: "array",
|
|
239
|
+
items: { type: "string" },
|
|
240
|
+
description: 'Argv for an allowlisted external command, e.g. ["bun","add","left-pad"]. No shell: each element is one literal argument. Supply exactly one of "command" or "argv".',
|
|
241
|
+
},
|
|
242
|
+
target: {
|
|
243
|
+
type: "string",
|
|
244
|
+
enum: ["repoRoot", "package"],
|
|
245
|
+
description: 'Working directory for "argv": this package (default) or the repo root.',
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
249
|
+
},
|
|
250
|
+
// With exec available, neither field is unconditionally required —
|
|
251
|
+
// run() enforces "exactly one of command or argv" at call time, which
|
|
252
|
+
// a static JSON Schema `required` list cannot express as an either/or.
|
|
253
|
+
...(hasExec ? {} : { required: ["command"] }),
|
|
254
|
+
},
|
|
255
|
+
// `{{files}}` is the declared scoped-test path parameter. Keeping it in
|
|
256
|
+
// the policy's containment seam prevents a quoted-but-otherwise harmless
|
|
257
|
+
// absolute filename from making the configured command act outside root.
|
|
258
|
+
// `argvField` is set only when exec is available: it is what lets the
|
|
259
|
+
// policy recognize an argv call and check it under the `Exec` identity
|
|
260
|
+
// (see src/tools/runtime.ts and src/tools/policy.ts) rather than under
|
|
261
|
+
// RunCommand's own grant.
|
|
262
|
+
scope: {
|
|
263
|
+
pathFields: [],
|
|
264
|
+
listPathFields: ["values.files"],
|
|
265
|
+
verbField: "command",
|
|
266
|
+
allowedVerbs: keys,
|
|
267
|
+
...(hasExec ? { argvField: "argv" } : {}),
|
|
268
|
+
},
|
|
269
|
+
async run(input, ctx) {
|
|
270
|
+
const hasCommand = typeof input.command === "string" && input.command.length > 0;
|
|
271
|
+
const hasArgv = input.argv !== undefined;
|
|
272
|
+
if (hasCommand && hasArgv) {
|
|
273
|
+
return { content: "supply exactly one of command or argv, not both", isError: true };
|
|
274
|
+
}
|
|
275
|
+
if (hasArgv)
|
|
276
|
+
return runExecBranch(input, ctx, opts);
|
|
277
|
+
// #2066: `target` is read only on the argv branch (run-command-exec.ts).
|
|
278
|
+
// 48 of 136 declared-command calls in one audited run carried a `target`
|
|
279
|
+
// that was silently discarded, and the transcript shows the model
|
|
280
|
+
// reasoning about it while trying to fix a failure. Silently ignoring it
|
|
281
|
+
// is the one option that teaches nothing.
|
|
282
|
+
if (input.target !== undefined) {
|
|
283
|
+
return {
|
|
284
|
+
content: `"target" applies only to "argv" calls; a declared command runs in the directory its configuration declares. Remove "target" and call "${typeof input.command === "string" ? input.command : ""}" on its own.`,
|
|
285
|
+
isError: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const key = typeof input.command === "string" ? input.command : "";
|
|
289
|
+
const template = declared.get(key);
|
|
290
|
+
if (template === undefined)
|
|
291
|
+
return { content: `unknown command "${key}"`, isError: true };
|
|
292
|
+
if (input.values !== undefined && !isPlainValuesObject(input.values)) {
|
|
293
|
+
return {
|
|
294
|
+
content: `"values" must be an object mapping placeholder names to values, e.g. { files: "a.test.ts" } -- got ${describeValuesType(input.values)} instead`,
|
|
295
|
+
isError: true,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const raw = (input.values ?? {});
|
|
299
|
+
// Shape A of the run-2026-09-14T05-55-54-734Z audit: check every
|
|
300
|
+
// rejected key against every OTHER declared command before falling
|
|
301
|
+
// through to substituteCommandSpec's generic error, so the response
|
|
302
|
+
// can name the command that actually accepts it. Only the first
|
|
303
|
+
// rejected key with a real alternative is reported -- consistent with
|
|
304
|
+
// substituteCommandSpec, which also stops at the first offending key.
|
|
305
|
+
const templateKeys = placeholdersOf(template);
|
|
306
|
+
for (const rejectedKey of Object.keys(raw)) {
|
|
307
|
+
if (templateKeys.has(rejectedKey))
|
|
308
|
+
continue;
|
|
309
|
+
const alternates = otherCommandsDeclaring(declared, rejectedKey, key);
|
|
310
|
+
if (alternates.length === 0)
|
|
311
|
+
continue;
|
|
312
|
+
return {
|
|
313
|
+
content: `value "${rejectedKey}" is not a placeholder in this command (${declaredPlaceholdersSuffix(templateKeys)}) -- "${rejectedKey}" is a placeholder in: ${alternates.join(", ")}`,
|
|
314
|
+
isError: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const values = {};
|
|
318
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
319
|
+
values[k] = Array.isArray(v) && v.every((element) => typeof element === "string") ? v : String(v);
|
|
320
|
+
}
|
|
321
|
+
// `scope.listPathFields: ["values.files"]` means the policy split this
|
|
322
|
+
// value on whitespace and resolved EACH element into ctx.resolvedPaths --
|
|
323
|
+
// absolute, and approved against the grant. Substituting those instead of
|
|
324
|
+
// the raw string is what read.ts, write.ts, edit.ts and grep.ts already
|
|
325
|
+
// do, and it fixes a real defect: `bun test .nax/features/x/foo.test.ts`
|
|
326
|
+
// treats a bare dot-prefixed relative path as a test FILTER, not a path,
|
|
327
|
+
// and reports a confident false "no tests matched" (#1936). The absolute
|
|
328
|
+
// form runs.
|
|
329
|
+
//
|
|
330
|
+
// But a path field is a POLICY declaration, not a claim about what the
|
|
331
|
+
// agent actually passed. `{{files}}` is equally a test-NAME filter
|
|
332
|
+
// (`bun test run-command`), and resolveWithin accepts one happily,
|
|
333
|
+
// turning it into a nonexistent absolute path. So the resolved form is
|
|
334
|
+
// taken only for an element that is an existing FILE; everything else
|
|
335
|
+
// keeps the raw token it arrived as. "" splits to nothing, so it cannot
|
|
336
|
+
// absolutise to the root and run the entire suite.
|
|
337
|
+
//
|
|
338
|
+
// The result is an ARRAY, so each element survives substitution as its
|
|
339
|
+
// own argument -- which is what makes a multi-file scoped run possible
|
|
340
|
+
// at all, and what makes this path agree with scoped-selection.ts
|
|
341
|
+
// (nax#1998). One stat per element against a process spawn is free.
|
|
342
|
+
const rawFiles = raw.files;
|
|
343
|
+
const rawFilesValue = typeof rawFiles === "string"
|
|
344
|
+
? rawFiles
|
|
345
|
+
: Array.isArray(rawFiles) && rawFiles.every((element) => typeof element === "string")
|
|
346
|
+
? rawFiles
|
|
347
|
+
: undefined;
|
|
348
|
+
const elements = typeof rawFilesValue === "string"
|
|
349
|
+
? pathListElements(rawFilesValue, ctx.root)
|
|
350
|
+
: Array.isArray(rawFilesValue)
|
|
351
|
+
? rawFilesValue
|
|
352
|
+
: undefined;
|
|
353
|
+
if (elements !== undefined && rawFilesValue !== undefined) {
|
|
354
|
+
// The pairing is positional, and sound because `values.files` is this
|
|
355
|
+
// tool's only path field, so the policy appended exactly these
|
|
356
|
+
// elements in exactly this order (pinned by a test on `scope`).
|
|
357
|
+
//
|
|
358
|
+
// A length mismatch means the two layers disagreed -- a different root
|
|
359
|
+
// spelling, or a file created between the two stats. Falling back to
|
|
360
|
+
// the raw value is the fail-safe direction in BOTH directions: the
|
|
361
|
+
// shell then gets one argument, and one argument is always a subset of
|
|
362
|
+
// what the policy approved, however it split.
|
|
363
|
+
values.files =
|
|
364
|
+
ctx.resolvedPaths.length === elements.length
|
|
365
|
+
? elements.map((element, i) => {
|
|
366
|
+
const resolved = ctx.resolvedPaths[i];
|
|
367
|
+
const isFile = resolved !== undefined && statSync(resolved, { throwIfNoEntry: false })?.isFile() === true;
|
|
368
|
+
return isFile ? resolved : element;
|
|
369
|
+
})
|
|
370
|
+
: rawFilesValue;
|
|
371
|
+
}
|
|
372
|
+
const command = substituteCommandSpec(template, values);
|
|
373
|
+
if (typeof command !== "string" && !Array.isArray(command))
|
|
374
|
+
return { content: command.error, isError: true };
|
|
375
|
+
const result = await runDeclared({
|
|
376
|
+
commandName: key,
|
|
377
|
+
command,
|
|
378
|
+
// Post-PR2 `ctx.root` is the repo root (not the package dir), so an
|
|
379
|
+
// omitted `commandCwd` runs the declared command at the repo root —
|
|
380
|
+
// every production caller threads `commandCwd` via `buildCodingToolSupport`;
|
|
381
|
+
// this fallback only fires from tests that bypass the support seam.
|
|
382
|
+
workdir: opts.commandCwd ?? ctx.root,
|
|
383
|
+
stripEnvVars: [...(opts.stripEnvVars ?? [])],
|
|
384
|
+
// The agent's own iteration loop, not a harness gate: kept in the JSONL
|
|
385
|
+
// at debug, off the console. Its outcome reaches the agent through the
|
|
386
|
+
// returned content, and the harness reports its own gates separately.
|
|
387
|
+
origin: "agent-tool",
|
|
388
|
+
});
|
|
389
|
+
const body = `exit ${result.exitCode}\n${result.output}`;
|
|
390
|
+
// Tool-layer I/O bound, not the model-facing cap: `maxBytes` shapes what
|
|
391
|
+
// the model is told and belongs to the session's truncation policy, which
|
|
392
|
+
// spills the cut. Bounded here so a runaway command cannot be buffered
|
|
393
|
+
// without limit, and cut on a codepoint boundary so the returned body
|
|
394
|
+
// never carries a replacement character.
|
|
395
|
+
return { content: cutToByteCap(body, ctx.readCeiling ?? READ_CEILING), isError: !result.success };
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `callTool`'s branch bodies, extracted from `runtime.ts` for the A13
|
|
3
|
+
* complexity drain (docs/plans/STATUS-complexity-drain.md).
|
|
4
|
+
*
|
|
5
|
+
* Each helper is one free function taking a single options object; the
|
|
6
|
+
* closure-bound collaborators (`logCall`, `runTool`, the session-wide
|
|
7
|
+
* `signal`) arrive BY REFERENCE and are read at call time, so the
|
|
8
|
+
* `_codingToolDeps` seam stays defined in `runtime.ts` exactly as the barrel
|
|
9
|
+
* (`src/tools/index.ts`) re-exports it. This file imports `runtime.ts`
|
|
10
|
+
* TYPE-ONLY — a runtime import would cycle back to the factory.
|
|
11
|
+
*
|
|
12
|
+
* One asymmetry is preserved on purpose: the denial-path breach warn below and the
|
|
13
|
+
* rejected-guard warn (US-004) call the module-level `getSafeLogger` import
|
|
14
|
+
* directly, while the ledger `log` in `runtime.ts` reads
|
|
15
|
+
* `_codingToolDeps.getLogger()`. Both behaved that way before the extraction;
|
|
16
|
+
* the seam stub in the mirror suites has never covered either warn.
|
|
17
|
+
*/
|
|
18
|
+
import { type CommandGuard, type CommandShadow, openShadowTap } from "#src/command-safety/index";
|
|
19
|
+
import { type AskResolver } from "#src/permissions/index";
|
|
20
|
+
import type { SandboxRecord } from "../sandbox/index.ts";
|
|
21
|
+
import type { CodingTool } from "./registry.ts";
|
|
22
|
+
import type { CodingToolOutcome, ToolCallContext } from "./runtime.ts";
|
|
23
|
+
import { type ToolPolicy } from "./types.ts";
|
|
24
|
+
type PolicyVerdict = ReturnType<ToolPolicy["check"]>;
|
|
25
|
+
/** The verdict shape the ask and deny branches see: the policy said no. */
|
|
26
|
+
type DeniedVerdict = Extract<PolicyVerdict, {
|
|
27
|
+
allowed: false;
|
|
28
|
+
}>;
|
|
29
|
+
/** Per-call audit data a tool's result carries into the ledger. */
|
|
30
|
+
export interface CallAudit {
|
|
31
|
+
executed?: readonly string[];
|
|
32
|
+
target?: "package" | "repoRoot";
|
|
33
|
+
/** Exec only: forwarded to the command shadow; never written to the ledger. */
|
|
34
|
+
cwd?: string;
|
|
35
|
+
approval?: {
|
|
36
|
+
decidedBy: string;
|
|
37
|
+
remembered: boolean;
|
|
38
|
+
latencyMs: number;
|
|
39
|
+
};
|
|
40
|
+
sandbox?: SandboxRecord;
|
|
41
|
+
exitCode?: number;
|
|
42
|
+
}
|
|
43
|
+
/** The per-call log wrapper `callTool` builds; settles the shadow tap too. */
|
|
44
|
+
export type LogCallFn = (tool: string, outcome: CodingToolOutcome["kind"] | "denied:ask", resultBytes: number, input: Record<string, unknown>, context: ToolCallContext | undefined, breach?: boolean, reason?: string, routineErrors?: boolean, audit?: CallAudit, resultBytesPreTruncation?: number) => void;
|
|
45
|
+
/** Executes a permitted call and records its outcome. */
|
|
46
|
+
export type RunToolFn = (target: CodingTool, callInput: Record<string, unknown>, resolvedPaths: readonly string[], approval?: {
|
|
47
|
+
decidedBy: string;
|
|
48
|
+
remembered: boolean;
|
|
49
|
+
latencyMs: number;
|
|
50
|
+
}) => Promise<CodingToolOutcome>;
|
|
51
|
+
export interface PolicyIdentity {
|
|
52
|
+
readonly policyIdentity: string;
|
|
53
|
+
readonly argvField: string | undefined;
|
|
54
|
+
readonly hasArgv: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A call carrying the tool's declared argv field (RunCommand's `Exec`
|
|
58
|
+
* branch) is checked, and ledgered, under the `Exec` identity rather
|
|
59
|
+
* than the tool's own name. Left as `name`, an `Exec(...)` grant would
|
|
60
|
+
* never be consulted — the call would run under RunCommand's own grant
|
|
61
|
+
* (often a wildcard for its declared commands), making the allowlist
|
|
62
|
+
* decorative. The tool's registered name is unaffected; this changes
|
|
63
|
+
* only which identity the policy and ledger see for THIS call.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolvePolicyIdentity(name: string, tool: CodingTool, input: Record<string, unknown>): PolicyIdentity;
|
|
66
|
+
/**
|
|
67
|
+
* P5: observe the command now (not awaited), settled from the caller's
|
|
68
|
+
* logCall below. Undefined whenever the runtime has no shadow configured.
|
|
69
|
+
*/
|
|
70
|
+
export declare function openCallShadowTap(p: {
|
|
71
|
+
shadow: CommandShadow | undefined;
|
|
72
|
+
identity: string;
|
|
73
|
+
tool: CodingTool;
|
|
74
|
+
input: Record<string, unknown>;
|
|
75
|
+
hasArgv: boolean;
|
|
76
|
+
argvField: string | undefined;
|
|
77
|
+
root: string;
|
|
78
|
+
stage: string | undefined;
|
|
79
|
+
storyId: string | undefined;
|
|
80
|
+
callId: string | undefined;
|
|
81
|
+
scopeId: string | undefined;
|
|
82
|
+
context: ToolCallContext | undefined;
|
|
83
|
+
verdict: PolicyVerdict;
|
|
84
|
+
}): ReturnType<typeof openShadowTap> | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* US-004: the guard's flag-for-review route for an ALLOWED command.
|
|
87
|
+
*
|
|
88
|
+
* Returns the ask-shaped denial the flagged call routes through
|
|
89
|
+
* `resolveAskOutcome`, and undefined in every other case — no guard
|
|
90
|
+
* configured, an identity other than `Bash`/`Exec`, or an unflagged
|
|
91
|
+
* assessment — so the caller runs the tool exactly as it did before the guard
|
|
92
|
+
* existed. The guard is total by contract (US-003); a rejection, or a resolved
|
|
93
|
+
* decision the type does not actually hold, means that contract broke, so the
|
|
94
|
+
* call runs unguarded rather than being refused, and the failure is logged once
|
|
95
|
+
* per broken call.
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveGuardDenial(p: {
|
|
98
|
+
identity: string;
|
|
99
|
+
tool: CodingTool;
|
|
100
|
+
input: Record<string, unknown>;
|
|
101
|
+
argvField: string | undefined;
|
|
102
|
+
guard: CommandGuard | undefined;
|
|
103
|
+
root: string;
|
|
104
|
+
tempConfined: boolean;
|
|
105
|
+
resolvedPaths: readonly string[];
|
|
106
|
+
}): Promise<DeniedVerdict | undefined>;
|
|
107
|
+
/**
|
|
108
|
+
* The ask branch: a call the policy gates matched an ask rule, so an
|
|
109
|
+
* AskResolver decides. US-003: build the AskControl from the per-call
|
|
110
|
+
* context. The turn's abort signal is forwarded verbatim, and onWaiting is
|
|
111
|
+
* wired so the resolver can tell the turn loop a human prompt is pending.
|
|
112
|
+
*/
|
|
113
|
+
export declare function resolveAskOutcome(p: {
|
|
114
|
+
identity: string;
|
|
115
|
+
tool: CodingTool;
|
|
116
|
+
input: Record<string, unknown>;
|
|
117
|
+
context: ToolCallContext | undefined;
|
|
118
|
+
verdict: DeniedVerdict;
|
|
119
|
+
/** The runtime-level signal, falling back behind the per-call one. */
|
|
120
|
+
runtimeSignal: AbortSignal | undefined;
|
|
121
|
+
askResolver: AskResolver;
|
|
122
|
+
stage: string | undefined;
|
|
123
|
+
storyId: string | undefined;
|
|
124
|
+
root: string;
|
|
125
|
+
logCall: LogCallFn;
|
|
126
|
+
runTool: RunToolFn;
|
|
127
|
+
}): Promise<CodingToolOutcome>;
|
|
128
|
+
/**
|
|
129
|
+
* The deny branch: a final policy refusal, sharpened where possible with a
|
|
130
|
+
* redirect naming an advertised tool that serves the call's intent.
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveDenialOutcome(p: {
|
|
133
|
+
name: string;
|
|
134
|
+
identity: string;
|
|
135
|
+
tool: CodingTool;
|
|
136
|
+
input: Record<string, unknown>;
|
|
137
|
+
context: ToolCallContext | undefined;
|
|
138
|
+
verdict: DeniedVerdict;
|
|
139
|
+
argvField: string | undefined;
|
|
140
|
+
/** What `advertised()` actually returned; a redirect names only these. */
|
|
141
|
+
advertisedNames: ReadonlySet<string>;
|
|
142
|
+
declaredCommands: ReadonlySet<string> | undefined;
|
|
143
|
+
root: string;
|
|
144
|
+
logCall: LogCallFn;
|
|
145
|
+
}): CodingToolOutcome;
|
|
146
|
+
export {};
|