@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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-line call description an AskResolver receives, and the reason a
|
|
3
|
+
* `denied:ask` ledger row carries.
|
|
4
|
+
*
|
|
5
|
+
* Summaries are outbound to a human, so they are masked through
|
|
6
|
+
* `maskForPrompt` before they leave the process; a secret that cannot be
|
|
7
|
+
* shown safely withholds the arguments entirely (`unshowable`, review #9).
|
|
8
|
+
*/
|
|
9
|
+
import { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_TIMEOUT_REASON, ASK_UNSHOWABLE_REASON, maskForPrompt, } from "#src/permissions/index";
|
|
10
|
+
/** Ceiling for the one-line call description an AskResolver receives. */
|
|
11
|
+
export const MAX_ASK_SUMMARY_CHARS = 200;
|
|
12
|
+
/**
|
|
13
|
+
* One human-readable line describing the call an `ask` rule matched.
|
|
14
|
+
*
|
|
15
|
+
* Built from the scope's DECLARED fields rather than from `JSON.stringify` of
|
|
16
|
+
* the whole input: the input carries a tool's full payload -- file contents on
|
|
17
|
+
* a Write, a commit message, whatever a provider tool takes -- and an
|
|
18
|
+
* AskResolver is by definition an outbound channel to a human. A summary is
|
|
19
|
+
* what approval needs; the payload is what the audit sink already holds.
|
|
20
|
+
*/
|
|
21
|
+
export function askSummary(tool, scope, input) {
|
|
22
|
+
const fields = [scope.commandField, scope.argvField, scope.verbField, ...scope.pathFields];
|
|
23
|
+
const parts = fields.flatMap((field) => {
|
|
24
|
+
if (field === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
const value = input[field];
|
|
27
|
+
if (typeof value === "string")
|
|
28
|
+
return [`${field}=${value}`];
|
|
29
|
+
if (Array.isArray(value))
|
|
30
|
+
return [`${field}=${value.filter((v) => typeof v === "string").join(" ")}`];
|
|
31
|
+
return [];
|
|
32
|
+
});
|
|
33
|
+
// Mask the FULL line, then cut: cutting first can split a secret so no
|
|
34
|
+
// pattern matches what is left, and the partial secret would go out.
|
|
35
|
+
const masked = maskForPrompt(`${tool} ${parts.join(" ")}`.trim());
|
|
36
|
+
if (!masked.ok)
|
|
37
|
+
return { summary: `${tool} [arguments withheld: contains a secret]`, unshowable: true };
|
|
38
|
+
return { summary: masked.masked.slice(0, MAX_ASK_SUMMARY_CHARS), unshowable: false };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The reason a `denied:ask` ledger row carries, chosen by WHO refused.
|
|
42
|
+
*
|
|
43
|
+
* One constant for every ask denial asserted "no approval channel is
|
|
44
|
+
* configured" even when a human answered (deny) or nobody answered in time
|
|
45
|
+
* (timeout). Those are materially different facts; see ASK_*_REASON.
|
|
46
|
+
*/
|
|
47
|
+
export function askDenyReason(decidedBy) {
|
|
48
|
+
if (decidedBy === "timeout")
|
|
49
|
+
return ASK_TIMEOUT_REASON;
|
|
50
|
+
if (decidedBy === "human")
|
|
51
|
+
return ASK_DENIED_REASON;
|
|
52
|
+
if (decidedBy === "cancelled")
|
|
53
|
+
return ASK_CANCELLED_REASON;
|
|
54
|
+
if (decidedBy === "unshowable")
|
|
55
|
+
return ASK_UNSHOWABLE_REASON;
|
|
56
|
+
return ASK_NO_CHANNEL_REASON;
|
|
57
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-segment shell working-directory tracking, shared by the two Bash
|
|
3
|
+
* payload screens (`policy-bash.ts`'s `checkBashCommand` and
|
|
4
|
+
* `policy-bash-raw.ts`'s `screenRawBashCommand`).
|
|
5
|
+
*
|
|
6
|
+
* A `cd` in one segment moves the frame of reference for every LATER segment
|
|
7
|
+
* in the same command (`cd child && cat ../secret` reads `<root>/secret`, not
|
|
8
|
+
* `<root>/child/secret`), so both screens must track it identically to avoid
|
|
9
|
+
* being fooled by it. This module is the ONE definition of "what does a `cd`
|
|
10
|
+
* segment resolve to, and how do separators fold that into the running set
|
|
11
|
+
* of candidate working directories" -- mirroring `isNaxOwnedWritePath` in
|
|
12
|
+
* `nax-owned-writes.ts`, which is the same one-definition-two-callers shape
|
|
13
|
+
* for a different seam.
|
|
14
|
+
*
|
|
15
|
+
* What this module does NOT decide: what happens when a `cd` cannot be
|
|
16
|
+
* modelled (an option-shaped target, an opaque one, one that fails to
|
|
17
|
+
* resolve). The two callers have OPPOSITE answers to that question --
|
|
18
|
+
* `checkBashCommand` denies (containment is the whole point of gated mode),
|
|
19
|
+
* `screenRawBashCommand` must fail open (raw enforces no containment by
|
|
20
|
+
* definition; see the caller's own docblock for why). `cdTargetsFor` reports
|
|
21
|
+
* WHY a `cd` could not be modelled and leaves the verdict to the caller.
|
|
22
|
+
*/
|
|
23
|
+
import type { BashSegment } from "#src/permissions/index";
|
|
24
|
+
/**
|
|
25
|
+
* The outcome of inspecting one segment for a `cd`:
|
|
26
|
+
*
|
|
27
|
+
* - `not-cd`: the segment does not start with `cd`; nothing to track.
|
|
28
|
+
* - `resolved`: a `cd` whose target resolved against EVERY current frame.
|
|
29
|
+
* - `no-target`: `cd` with no argument at all.
|
|
30
|
+
* - `option-shaped`: the target starts with `-` (`cd -`, `cd -P dir`) and
|
|
31
|
+
* puts the path in a slot this reader does not parse.
|
|
32
|
+
* - `opaque`: the target contains `$`-expansion; its runtime value is
|
|
33
|
+
* unknown here.
|
|
34
|
+
* - `unresolved`: the target failed to resolve from at least one current
|
|
35
|
+
* frame (root escape, `.git/`, or whatever `resolvePath` itself refuses).
|
|
36
|
+
*/
|
|
37
|
+
export type CdTargetResult = {
|
|
38
|
+
readonly kind: "not-cd";
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: "resolved";
|
|
41
|
+
readonly targets: readonly string[];
|
|
42
|
+
} | {
|
|
43
|
+
readonly kind: "no-target";
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "option-shaped";
|
|
46
|
+
readonly text: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: "opaque";
|
|
49
|
+
readonly text: string;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: "unresolved";
|
|
52
|
+
readonly text: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Reads a segment as a possible `cd`, resolving its target against every
|
|
56
|
+
* frame in `cwd` via the caller-supplied `resolvePath` (the same containment
|
|
57
|
+
* callback both policy modules already receive from `policy.ts`).
|
|
58
|
+
*
|
|
59
|
+
* Resolves against ALL frames, not just the first, for the same reason
|
|
60
|
+
* `checkPayload`'s old `resolveAll` did: after a `;`-joined `cd`, more than
|
|
61
|
+
* one frame can be live at once (see `nextWorkingDirectories` below), and a
|
|
62
|
+
* target that is safe from one frame but escapes from another must not be
|
|
63
|
+
* silently narrowed to the safe one.
|
|
64
|
+
*/
|
|
65
|
+
export declare function cdTargetsFor(segment: BashSegment, cwd: readonly string[], resolvePath: (candidate: string, cwd: string) => string | null): CdTargetResult;
|
|
66
|
+
/**
|
|
67
|
+
* Folds a segment's `cd` targets (if any) into the running set of candidate
|
|
68
|
+
* working directories, per the separator that FOLLOWS it:
|
|
69
|
+
*
|
|
70
|
+
* - `&&`: the shell only reaches the next segment if this one succeeded, and
|
|
71
|
+
* a successful `cd` REPLACES the working directory outright.
|
|
72
|
+
* - `;`: the next segment runs regardless of whether the `cd` succeeded, so
|
|
73
|
+
* both the old and new frames stay live -- the command might still be
|
|
74
|
+
* sitting in the OLD directory when the next segment runs.
|
|
75
|
+
* - anything else (`||`, `|`, `&`, or no separator / the last segment): a
|
|
76
|
+
* `cd`'s effect on a LATER segment is not modelled, so the frame set is
|
|
77
|
+
* left unchanged.
|
|
78
|
+
*
|
|
79
|
+
* `cdTargets` is `undefined` for a non-`cd` segment (or one this reader could
|
|
80
|
+
* not resolve, per `cdTargetsFor`), in which case the frame set never changes.
|
|
81
|
+
*/
|
|
82
|
+
export declare function nextWorkingDirectories(segment: BashSegment, current: readonly string[], cdTargets: readonly string[] | undefined): readonly string[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a segment as a possible `cd`, resolving its target against every
|
|
3
|
+
* frame in `cwd` via the caller-supplied `resolvePath` (the same containment
|
|
4
|
+
* callback both policy modules already receive from `policy.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Resolves against ALL frames, not just the first, for the same reason
|
|
7
|
+
* `checkPayload`'s old `resolveAll` did: after a `;`-joined `cd`, more than
|
|
8
|
+
* one frame can be live at once (see `nextWorkingDirectories` below), and a
|
|
9
|
+
* target that is safe from one frame but escapes from another must not be
|
|
10
|
+
* silently narrowed to the safe one.
|
|
11
|
+
*/
|
|
12
|
+
export function cdTargetsFor(segment, cwd, resolvePath) {
|
|
13
|
+
const words = segment.tokens.map((token) => token.text);
|
|
14
|
+
if (words[0] !== "cd")
|
|
15
|
+
return { kind: "not-cd" };
|
|
16
|
+
const target = segment.tokens[1];
|
|
17
|
+
if (target === undefined)
|
|
18
|
+
return { kind: "no-target" };
|
|
19
|
+
// `cd -` returns to $OLDPWD and `cd -P x` puts the path in a later slot:
|
|
20
|
+
// both leave a naive reader tracking `<root>/-` as the new frame of
|
|
21
|
+
// reference while the shell is somewhere else. Option-shaped, not modelled.
|
|
22
|
+
if (target.text.startsWith("-"))
|
|
23
|
+
return { kind: "option-shaped", text: target.text };
|
|
24
|
+
if (target.opaque)
|
|
25
|
+
return { kind: "opaque", text: target.text };
|
|
26
|
+
const resolved = cwd.map((directory) => resolvePath(target.text, directory));
|
|
27
|
+
if (!resolved.every((path) => path !== null))
|
|
28
|
+
return { kind: "unresolved", text: target.text };
|
|
29
|
+
return { kind: "resolved", targets: resolved };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Folds a segment's `cd` targets (if any) into the running set of candidate
|
|
33
|
+
* working directories, per the separator that FOLLOWS it:
|
|
34
|
+
*
|
|
35
|
+
* - `&&`: the shell only reaches the next segment if this one succeeded, and
|
|
36
|
+
* a successful `cd` REPLACES the working directory outright.
|
|
37
|
+
* - `;`: the next segment runs regardless of whether the `cd` succeeded, so
|
|
38
|
+
* both the old and new frames stay live -- the command might still be
|
|
39
|
+
* sitting in the OLD directory when the next segment runs.
|
|
40
|
+
* - anything else (`||`, `|`, `&`, or no separator / the last segment): a
|
|
41
|
+
* `cd`'s effect on a LATER segment is not modelled, so the frame set is
|
|
42
|
+
* left unchanged.
|
|
43
|
+
*
|
|
44
|
+
* `cdTargets` is `undefined` for a non-`cd` segment (or one this reader could
|
|
45
|
+
* not resolve, per `cdTargetsFor`), in which case the frame set never changes.
|
|
46
|
+
*/
|
|
47
|
+
export function nextWorkingDirectories(segment, current, cdTargets) {
|
|
48
|
+
if (cdTargets === undefined)
|
|
49
|
+
return current;
|
|
50
|
+
if (segment.separator === "&&")
|
|
51
|
+
return cdTargets;
|
|
52
|
+
if (segment.separator === ";")
|
|
53
|
+
return [...new Set([...current, ...cdTargets])];
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-authored shell tool (spec §4 US-004).
|
|
3
|
+
*
|
|
4
|
+
* Session-local like `RunCommand`, not a global registry entry: it needs the
|
|
5
|
+
* project's shell and secret-strip list, which are per-config. Created only
|
|
6
|
+
* when the operation DECLARED it (see coding-tool-support.ts) — a tool the
|
|
7
|
+
* global registry held would be callable by an op that never declared it,
|
|
8
|
+
* because `callTool` looks a name up before it consults advertisement.
|
|
9
|
+
*
|
|
10
|
+
* WHERE IT RUNS: `ctx.root`, the hop's permitted root and the same root the
|
|
11
|
+
* policy resolved every path against. Never the runtime's workdir — under `-d`
|
|
12
|
+
* those differ, and that difference is the #1794 defect.
|
|
13
|
+
*
|
|
14
|
+
* WHAT GATES IT: nothing here. The command string reached this function only
|
|
15
|
+
* because `policy.check` already lexed it, matched every segment against the
|
|
16
|
+
* stage's `Bash(...)` rules and containment-checked its paths and redirects
|
|
17
|
+
* (src/tools/policy-bash.ts). This module must never be given a "safe enough"
|
|
18
|
+
* check of its own: two gates in two places drift, and the second one is the
|
|
19
|
+
* one nobody tests. The launcher (P4) changes HOW the command runs -- inside
|
|
20
|
+
* an OS sandbox when enabled -- never WHETHER.
|
|
21
|
+
*/
|
|
22
|
+
import { runArgv } from "#src/internal/argv-exec";
|
|
23
|
+
import type { BashApprovalMode } from "../config/bash-approval.ts";
|
|
24
|
+
import { type CommandLauncher } from "../sandbox/index.ts";
|
|
25
|
+
import type { CodingTool } from "./registry.ts";
|
|
26
|
+
/**
|
|
27
|
+
* Deadline for a Bash spawn — the same ceiling the Exec branch uses
|
|
28
|
+
* (`EXEC_TIMEOUT_MS`, src/tools/run-command-exec.ts). A model-authored command
|
|
29
|
+
* may legitimately be a build or a full test run, and a shorter default would
|
|
30
|
+
* be worked around rather than respected.
|
|
31
|
+
*/
|
|
32
|
+
export declare const BASH_TIMEOUT_MS = 300000;
|
|
33
|
+
export declare const DEFAULT_BASH_SHELL = "/bin/sh";
|
|
34
|
+
export interface BashToolOptions {
|
|
35
|
+
/** `quality.shell`. */
|
|
36
|
+
readonly shell?: string;
|
|
37
|
+
/** `quality.stripEnvVars` — secrets removed before the spawn. */
|
|
38
|
+
readonly stripEnvVars?: readonly string[];
|
|
39
|
+
/** The stage's granted patterns, for the DESCRIPTION only. The policy is the
|
|
40
|
+
* gate; naming the granted forms here is what stops the model spending a
|
|
41
|
+
* turn discovering them by denial. Ignored under `raw` (see
|
|
42
|
+
* `rawDescription` below) -- there is no per-pattern grant to name. */
|
|
43
|
+
readonly patterns?: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* ADR-030 mode this description should reflect. Defaults to `gated`, the
|
|
46
|
+
* conservative posture, matching `compileToolPolicy`'s own default so an
|
|
47
|
+
* omitted mode never advertises a capability the policy would not actually
|
|
48
|
+
* grant.
|
|
49
|
+
*/
|
|
50
|
+
readonly bashApproval?: BashApprovalMode;
|
|
51
|
+
/**
|
|
52
|
+
* Whether a human can answer an escalated command (ADR-030, amended for P4).
|
|
53
|
+
* Read only under `escalate`; absent or false keeps `gated`'s wording, since
|
|
54
|
+
* a run with no interaction channel denies every ask as `unavailable`.
|
|
55
|
+
*/
|
|
56
|
+
readonly humanApproval?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* P4: how the command runs. Absent = today's direct spawn through
|
|
59
|
+
* `_bashToolDeps.runArgv` (unit tests). Production always passes one --
|
|
60
|
+
* disabled, available or unavailable -- and its state also shapes the
|
|
61
|
+
* description, so the agent always knows whether it is sandboxed.
|
|
62
|
+
*/
|
|
63
|
+
readonly launcher?: CommandLauncher;
|
|
64
|
+
}
|
|
65
|
+
/** Injectable spawn seam, mirroring `_argvExecDeps`. */
|
|
66
|
+
export declare const _bashToolDeps: {
|
|
67
|
+
runArgv: typeof runArgv;
|
|
68
|
+
};
|
|
69
|
+
export declare function createBashTool(opts?: BashToolOptions): CodingTool;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-authored shell tool (spec §4 US-004).
|
|
3
|
+
*
|
|
4
|
+
* Session-local like `RunCommand`, not a global registry entry: it needs the
|
|
5
|
+
* project's shell and secret-strip list, which are per-config. Created only
|
|
6
|
+
* when the operation DECLARED it (see coding-tool-support.ts) — a tool the
|
|
7
|
+
* global registry held would be callable by an op that never declared it,
|
|
8
|
+
* because `callTool` looks a name up before it consults advertisement.
|
|
9
|
+
*
|
|
10
|
+
* WHERE IT RUNS: `ctx.root`, the hop's permitted root and the same root the
|
|
11
|
+
* policy resolved every path against. Never the runtime's workdir — under `-d`
|
|
12
|
+
* those differ, and that difference is the #1794 defect.
|
|
13
|
+
*
|
|
14
|
+
* WHAT GATES IT: nothing here. The command string reached this function only
|
|
15
|
+
* because `policy.check` already lexed it, matched every segment against the
|
|
16
|
+
* stage's `Bash(...)` rules and containment-checked its paths and redirects
|
|
17
|
+
* (src/tools/policy-bash.ts). This module must never be given a "safe enough"
|
|
18
|
+
* check of its own: two gates in two places drift, and the second one is the
|
|
19
|
+
* one nobody tests. The launcher (P4) changes HOW the command runs -- inside
|
|
20
|
+
* an OS sandbox when enabled -- never WHETHER.
|
|
21
|
+
*/
|
|
22
|
+
import { interceptShell } from "#src/command-interceptor/index";
|
|
23
|
+
import { agentOutputOverlay } from "#src/internal/agent-output-env";
|
|
24
|
+
import { runArgv } from "#src/internal/argv-exec";
|
|
25
|
+
import { formatExecBody } from "#src/internal/exec-framing";
|
|
26
|
+
import { rawBashRefusalReason, sandboxSentence, unsandboxedSentence } from "../sandbox/index.js";
|
|
27
|
+
import { cutToByteCap, READ_CEILING } from "./truncate.js";
|
|
28
|
+
import { BASH_TOOL_NAME } from "./types.js";
|
|
29
|
+
/**
|
|
30
|
+
* Deadline for a Bash spawn — the same ceiling the Exec branch uses
|
|
31
|
+
* (`EXEC_TIMEOUT_MS`, src/tools/run-command-exec.ts). A model-authored command
|
|
32
|
+
* may legitimately be a build or a full test run, and a shorter default would
|
|
33
|
+
* be worked around rather than respected.
|
|
34
|
+
*/
|
|
35
|
+
export const BASH_TIMEOUT_MS = 300_000;
|
|
36
|
+
/** Floor for a caller-requested deadline: below this, a real command cannot
|
|
37
|
+
* even start, and a 0 would disable the deadline entirely. */
|
|
38
|
+
const MIN_BASH_TIMEOUT_MS = 1_000;
|
|
39
|
+
export const DEFAULT_BASH_SHELL = "/bin/sh";
|
|
40
|
+
/** Injectable spawn seam, mirroring `_argvExecDeps`. */
|
|
41
|
+
export const _bashToolDeps = {
|
|
42
|
+
runArgv,
|
|
43
|
+
};
|
|
44
|
+
function describeGrants(patterns) {
|
|
45
|
+
const named = (patterns ?? []).filter((pattern) => pattern !== "*");
|
|
46
|
+
if (patterns?.includes("*") === true)
|
|
47
|
+
return "every command form is granted for this stage";
|
|
48
|
+
if (named.length === 0)
|
|
49
|
+
return "no command forms are granted for this stage";
|
|
50
|
+
return `granted command forms: ${named.join(", ")}`;
|
|
51
|
+
}
|
|
52
|
+
const PREFER_STRUCTURED_TOOLS_SENTENCE = "PREFER the structured tools when they express the task -- Read, Glob, Grep, Git and RunCommand return " +
|
|
53
|
+
"bounded, parseable output, and Bash exists for what they cannot express. ";
|
|
54
|
+
/**
|
|
55
|
+
* US-001: a background process holding a pipe keeps the shell from exiting
|
|
56
|
+
* cleanly, so the runtime SIGKILLs the whole process group after a short
|
|
57
|
+
* drain grace. Every description variant states this so the model knows its
|
|
58
|
+
* `&`ed processes will not outlive the call.
|
|
59
|
+
*/
|
|
60
|
+
const BACKGROUND_PROCESSES_KILLED_SENTENCE = "background processes still holding the command's output are killed when the command exits. ";
|
|
61
|
+
/**
|
|
62
|
+
* `gated`'s description, also used verbatim for `escalate` when no human is
|
|
63
|
+
* reachable -- see the `escalate` branch of `bashToolDescription`.
|
|
64
|
+
*/
|
|
65
|
+
function gatedDescription(shell, patterns) {
|
|
66
|
+
return (`Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` +
|
|
67
|
+
`${describeGrants(patterns)}; anything else is refused. ` +
|
|
68
|
+
"Each segment of a `&&`/`||`/`;`/`|` chain is checked separately, and command substitution ($(...), backticks), " +
|
|
69
|
+
"process substitution, here-documents and `2>&1` are refused outright because they cannot be analysed. " +
|
|
70
|
+
"Paths and redirect targets must stay inside the repository root. " +
|
|
71
|
+
BACKGROUND_PROCESSES_KILLED_SENTENCE);
|
|
72
|
+
}
|
|
73
|
+
function capitalize(text) {
|
|
74
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `escalate`'s description when a human is reachable (ADR-030, amended
|
|
78
|
+
* 2026-09-23 and again for US-001). It states checkBashCommand's ACTUAL order:
|
|
79
|
+
* the out-of-bounds payload checks and the deny matcher run BEFORE the
|
|
80
|
+
* escalatable sites -- a lexer refusal, a grant miss, and the one payload
|
|
81
|
+
* refusal the gate cannot adjudicate, an option-shaped `cd` -- so an
|
|
82
|
+
* out-of-bounds command never reaches the human; on a refused command those
|
|
83
|
+
* checks cover the lexable prefix -- the part before the unreadable construct
|
|
84
|
+
* -- and an approved command runs as written. Pinned against the policy in
|
|
85
|
+
* coding-tool-bash-escalate-truth.test.ts.
|
|
86
|
+
*/
|
|
87
|
+
function escalateDescription(shell, patterns) {
|
|
88
|
+
return (`Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` +
|
|
89
|
+
`${capitalize(describeGrants(patterns))}. Every command, granted or not, is checked first: paths ` +
|
|
90
|
+
"and redirect targets must stay inside the repository root, and `.git/` access, denied flags, unexpanded " +
|
|
91
|
+
"`$VAR`, glob or brace characters, `~`, a bare `cd`, and a command matching a deny rule are " +
|
|
92
|
+
"refused without asking. For a command using a construct that cannot be analysed (e.g. command or process " +
|
|
93
|
+
"substitution, backticks, here-documents, subshells, `2>&1`, `#` comments, an option-shaped `cd`), these " +
|
|
94
|
+
"checks cover the part before that construct. A command outside the granted forms, or one using a construct " +
|
|
95
|
+
"that cannot be analysed, is not refused: it is sent to a human for approval (unless an identical command " +
|
|
96
|
+
"was already approved and remembered) and, if they allow it, runs exactly as written; it is refused if they " +
|
|
97
|
+
"deny it or do not answer in time, so prefer the granted forms. Each segment of a `&&`/`||`/`;`/`|` chain is " +
|
|
98
|
+
"checked separately. " +
|
|
99
|
+
BACKGROUND_PROCESSES_KILLED_SENTENCE);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* `raw`'s description (ADR-030 / F3). Under `raw`, `checkBashCommand` is
|
|
103
|
+
* never consulted at all -- `commandBranch` in policy-command-branch.ts
|
|
104
|
+
* dispatches to `screenRawBashCommand` and returns before it ever reads
|
|
105
|
+
* `denyBy`/`askBy` -- so every clause of `gatedDescription` above is false
|
|
106
|
+
* under this mode, and advertising it would push the model toward the exact
|
|
107
|
+
* workaround (reading whole files instead of piping/grepping them) this ADR
|
|
108
|
+
* exists to stop paying for.
|
|
109
|
+
*/
|
|
110
|
+
const RAW_UNCONTAINED = "The command runs from the repository root, but paths are NOT contained to it: a command may read or write anywhere the nax process " +
|
|
111
|
+
"itself can reach, inside the repository or outside it. ";
|
|
112
|
+
function rawDescription(shell, containment = RAW_UNCONTAINED) {
|
|
113
|
+
return (`Run one shell command string under ${shell}. ${PREFER_STRUCTURED_TOOLS_SENTENCE}` +
|
|
114
|
+
"This stage runs under raw mode (ADR-030): pipes, redirects, command substitution ($(...), backticks), " +
|
|
115
|
+
"process substitution, here-documents and subshells all work here -- nothing is refused for being unparseable, " +
|
|
116
|
+
"and Bash allow/deny/ask rules configured for this stage are NOT consulted. " +
|
|
117
|
+
containment +
|
|
118
|
+
"One refusal is a command the lexer CAN parse that names or redirects into one of the exact file " +
|
|
119
|
+
"paths nax owns -- .nax/config.json, .nax/mono/*/config.json, " +
|
|
120
|
+
".nax/features/**/prd.json, or the root queue-control files -- change those through nax rather than by " + // nax-feature-dir-allow: prose naming the raw-mode protected-path screen, not a path construction
|
|
121
|
+
"writing them directly. That screen is advisory, not a boundary: it matches exact file paths only, so a " +
|
|
122
|
+
"command using command substitution, a directory target (cp x .nax/), a glob, a nested shell (sh -c '...'), " +
|
|
123
|
+
"tar -C or dd of=, or a symlink alias all skip it; use the sandbox for a boundary. " +
|
|
124
|
+
"A `find` whose start path is `/`, `~` or `$HOME` is refused -- search within the repository instead. " +
|
|
125
|
+
BACKGROUND_PROCESSES_KILLED_SENTENCE);
|
|
126
|
+
}
|
|
127
|
+
function rawUnavailableDescription(shell, reason) {
|
|
128
|
+
return (`Run one shell command string under ${shell} -- but on this machine ${rawBashRefusalReason(reason)} ` +
|
|
129
|
+
"Under raw mode every call is refused; use the structured tools (Read, Glob, Grep, Git, RunCommand) instead. " +
|
|
130
|
+
BACKGROUND_PROCESSES_KILLED_SENTENCE);
|
|
131
|
+
}
|
|
132
|
+
function bashToolDescription(shell, opts) {
|
|
133
|
+
const state = opts.launcher?.state ?? { kind: "disabled" };
|
|
134
|
+
if (opts.bashApproval === "raw") {
|
|
135
|
+
if (state.kind === "available")
|
|
136
|
+
return rawDescription(shell, `The command runs from the repository root ${sandboxSentence(state.network, state.sharedTmp !== false)} `);
|
|
137
|
+
if (state.kind === "unavailable")
|
|
138
|
+
return rawUnavailableDescription(shell, state.reason);
|
|
139
|
+
return rawDescription(shell);
|
|
140
|
+
}
|
|
141
|
+
// `escalate` describes escalation ONLY when a human is reachable (ADR-030,
|
|
142
|
+
// amended for P4). Reachability is a separate config axis -- is an
|
|
143
|
+
// interaction channel configured? -- resolved at the execution stage and
|
|
144
|
+
// passed in as data. Without one every ask resolves `unavailable` and
|
|
145
|
+
// denies, so the wording stays byte-identical to `gated`'s: promising a
|
|
146
|
+
// human there would be the D13a fail-open shape stated in prose.
|
|
147
|
+
const policyDescription = opts.bashApproval === "escalate" && opts.humanApproval === true
|
|
148
|
+
? escalateDescription(shell, opts.patterns)
|
|
149
|
+
: gatedDescription(shell, opts.patterns);
|
|
150
|
+
if (state.kind === "available") {
|
|
151
|
+
return `${policyDescription} Commands that pass run ${sandboxSentence(state.network, state.sharedTmp !== false)}`;
|
|
152
|
+
}
|
|
153
|
+
if (state.kind === "unavailable")
|
|
154
|
+
return `${policyDescription} ${unsandboxedSentence(state.reason)}`;
|
|
155
|
+
return policyDescription;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Intercept, launch, post-process — everything that decides WHICH command ran.
|
|
159
|
+
* `run` keeps only the framing.
|
|
160
|
+
*
|
|
161
|
+
* The interception belongs HERE and not above the tool: `callTool` has already
|
|
162
|
+
* lexed and gated the model's ORIGINAL string against the stage's `Bash(...)`
|
|
163
|
+
* rules, and this is the first point after that verdict. The rewrite is
|
|
164
|
+
* therefore never the string the policy judged, which is exactly why
|
|
165
|
+
* `interceptShell` re-validates the provider's answer rather than trusting it.
|
|
166
|
+
*/
|
|
167
|
+
async function launchIntercepted({ command, shell, opts, ctx, timeoutMs, }) {
|
|
168
|
+
// The run's interceptor arrives on the context (S1 spec port 7), immutable
|
|
169
|
+
// for this call, so the launch below and `postProcess` see the same object.
|
|
170
|
+
const interceptor = ctx.interceptor;
|
|
171
|
+
const intercepted = await interceptShell(command, ctx.root, interceptor);
|
|
172
|
+
const executed = [shell, "-c", intercepted.command];
|
|
173
|
+
// US-004: opt the child into agent-friendly output. The same rule the
|
|
174
|
+
// quality and verification runners apply, just at the agent-facing spawn
|
|
175
|
+
// site. Marker presence is read from the nax process environment (via
|
|
176
|
+
// `_agentOutputEnvDeps.processEnv`), so an inherited CLAUDECODE/REPL_ID is
|
|
177
|
+
// left to speak for itself, and a stripped AGENT stays stripped. The overlay
|
|
178
|
+
// is a small record layered on top of `process.env` — undefined when there
|
|
179
|
+
// is nothing to add, so the child inherits its parent untouched.
|
|
180
|
+
const agentOverlay = agentOutputOverlay(opts.stripEnvVars ?? []);
|
|
181
|
+
const common = {
|
|
182
|
+
cwd: ctx.root,
|
|
183
|
+
timeoutMs,
|
|
184
|
+
stripEnvVars: [...(opts.stripEnvVars ?? [])],
|
|
185
|
+
...(agentOverlay !== undefined ? { env: agentOverlay } : {}),
|
|
186
|
+
...(ctx.signal !== undefined ? { signal: ctx.signal } : {}),
|
|
187
|
+
};
|
|
188
|
+
const launched = opts.launcher !== undefined
|
|
189
|
+
? await opts.launcher.run({
|
|
190
|
+
spec: { kind: "shell", shell, command: intercepted.command },
|
|
191
|
+
root: ctx.root,
|
|
192
|
+
...common,
|
|
193
|
+
})
|
|
194
|
+
: { ...(await _bashToolDeps.runArgv({ argv: executed, ...common })), executed, sandbox: undefined };
|
|
195
|
+
if (!intercepted.rewritten)
|
|
196
|
+
return { launched, stdout: launched.stdout };
|
|
197
|
+
// The request describes the command the MODEL wrote — the same shape
|
|
198
|
+
// `interceptShell` sent — so post-processing stays keyed to the original,
|
|
199
|
+
// mirroring the Git site, which passes the original argv.
|
|
200
|
+
const req = { kind: "shell", command, cwd: ctx.root, site: "bash" };
|
|
201
|
+
try {
|
|
202
|
+
const processed = interceptor?.postProcess?.(launched.stdout, req)?.output;
|
|
203
|
+
return { launched, stdout: processed ?? launched.stdout };
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Advisory, like the rewrite-time fail-open: a sick post-processor must not
|
|
207
|
+
// discard the output of a command that actually ran.
|
|
208
|
+
return { launched, stdout: launched.stdout };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
export function createBashTool(opts = {}) {
|
|
212
|
+
const shell = opts.shell ?? DEFAULT_BASH_SHELL;
|
|
213
|
+
return {
|
|
214
|
+
name: BASH_TOOL_NAME,
|
|
215
|
+
// A non-zero exit from a command the model wrote is its own red/green loop,
|
|
216
|
+
// not a fault worth an operator's attention — the same reason RunCommand
|
|
217
|
+
// sets this.
|
|
218
|
+
routineErrors: true,
|
|
219
|
+
description: bashToolDescription(shell, opts),
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
command: { type: "string", description: 'The shell command to run, e.g. "bun test src/a.test.ts"' },
|
|
224
|
+
timeoutMs: {
|
|
225
|
+
type: "number",
|
|
226
|
+
description: `Deadline in milliseconds (default and maximum ${BASH_TIMEOUT_MS}).`,
|
|
227
|
+
},
|
|
228
|
+
description: { type: "string", description: "One short line on what this command is for." },
|
|
229
|
+
},
|
|
230
|
+
required: ["command"],
|
|
231
|
+
},
|
|
232
|
+
// `commandField` is what routes this call to the Bash branch of the policy
|
|
233
|
+
// (src/tools/policy-bash.ts). No pathFields: the paths are inside the
|
|
234
|
+
// command string, where only that branch can see them.
|
|
235
|
+
scope: { pathFields: [], commandField: "command" },
|
|
236
|
+
async run(input, ctx) {
|
|
237
|
+
const command = input.command;
|
|
238
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
239
|
+
return { content: '"command" must be a non-empty string', isError: true };
|
|
240
|
+
}
|
|
241
|
+
const requested = typeof input.timeoutMs === "number" && Number.isFinite(input.timeoutMs) ? input.timeoutMs : BASH_TIMEOUT_MS;
|
|
242
|
+
const timeoutMs = Math.min(Math.max(Math.trunc(requested), MIN_BASH_TIMEOUT_MS), BASH_TIMEOUT_MS);
|
|
243
|
+
try {
|
|
244
|
+
const { launched, stdout } = await launchIntercepted({ command, shell, opts, ctx, timeoutMs });
|
|
245
|
+
// Same framing ladder as RunCommand (Exec) via the shared
|
|
246
|
+
// `formatExecBody`: an aborted call opens with the cancellation line
|
|
247
|
+
// and surfaces whatever partial output the readers captured before the
|
|
248
|
+
// process group was SIGKILLed (AC14); an orphansKilled call appends the
|
|
249
|
+
// "[nax] background processes ..." footer so the model can tell that a
|
|
250
|
+
// `&`ed process held the pipe and was reaped (AC15). Both are
|
|
251
|
+
// independent of the regular `timed out` / `exit N` framing.
|
|
252
|
+
const body = formatExecBody({ ...launched, stdout }, timeoutMs);
|
|
253
|
+
// The tool's own bound is the I/O ceiling, not the model-facing cap:
|
|
254
|
+
// `maxBytes` shapes what the model is told and belongs to the session's
|
|
255
|
+
// truncation policy (which also spills what it cuts), while this one
|
|
256
|
+
// only keeps a runaway command from being buffered without limit. The
|
|
257
|
+
// full size still rides out on `resultBytesPreTruncation`.
|
|
258
|
+
return {
|
|
259
|
+
content: cutToByteCap(body, ctx.readCeiling ?? READ_CEILING),
|
|
260
|
+
isError: launched.timedOut || launched.exitCode !== 0 || launched.aborted === true,
|
|
261
|
+
// The ledger records what actually ran, not what was requested.
|
|
262
|
+
// `exitCode` is recorded only when nax did not kill the process
|
|
263
|
+
// group: under `timedOut` or `aborted` the code is nax's own kill
|
|
264
|
+
// (128+signal) or the -1 never-spawned sentinel, not the command's.
|
|
265
|
+
// `orphansKilled` still records it -- the shell exited by itself and
|
|
266
|
+
// only background processes holding the pipe were reaped afterward.
|
|
267
|
+
audit: {
|
|
268
|
+
executed: launched.executed,
|
|
269
|
+
...(launched.sandbox !== undefined ? { sandbox: launched.sandbox } : {}),
|
|
270
|
+
...(!launched.timedOut && launched.aborted !== true ? { exitCode: launched.exitCode } : {}),
|
|
271
|
+
},
|
|
272
|
+
resultBytesPreTruncation: Buffer.byteLength(body, "utf8"),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
// A spawn-time failure (an unresolvable cwd, a missing shell) rejects
|
|
277
|
+
// rather than resolving with an exit code; surfaced as an ordinary tool
|
|
278
|
+
// error so it is indistinguishable from any other refusal above.
|
|
279
|
+
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete a file whose path the policy already resolved and approved.
|
|
3
|
+
*
|
|
4
|
+
* nax#1972: this used to be tracked-only, on the theory that a tracked
|
|
5
|
+
* file's content is in git history (an undo away) while an untracked file
|
|
6
|
+
* exists only on disk and is therefore unrecoverable. That rationale is
|
|
7
|
+
* retired -- it made the guard unsatisfiable for a file the agent itself
|
|
8
|
+
* wrote this session, which is untracked by construction. Paired with the
|
|
9
|
+
* #1937 denial redirect (Exec `rm` bounces back to Delete), the agent had no
|
|
10
|
+
* legal way to remove its own scratch output, and its fallback was to blank
|
|
11
|
+
* files to 0 bytes with Write -- which the auto-commit sweep then carried
|
|
12
|
+
* into history anyway. Refusing the deletion did not make anything more
|
|
13
|
+
* recoverable; it just moved the damage to a different tool.
|
|
14
|
+
*
|
|
15
|
+
* The new rule follows the three states git actually puts a path in:
|
|
16
|
+
*
|
|
17
|
+
* - tracked -> allow (unchanged)
|
|
18
|
+
* - untracked AND gitignored -> refuse (unchanged verdict, new reason)
|
|
19
|
+
* - untracked and NOT gitignored -> allow (the fix)
|
|
20
|
+
*
|
|
21
|
+
* Both untracked classes are equally unrecoverable -- neither is in git
|
|
22
|
+
* history -- so recoverability alone no longer explains the split. What
|
|
23
|
+
* distinguishes them is declared intent. A gitignored path has a rule
|
|
24
|
+
* somebody wrote on purpose, saying "this lives only on this machine"
|
|
25
|
+
* (`.env`, `.nax-pids`, `.claude/settings.local.json`, the fragments and
|
|
26
|
+
* plan-log directories under `.nax` are real examples in this repo) --
|
|
27
|
+
* deleting it is
|
|
28
|
+
* unrecoverable AND was never meant to be sent anywhere else, so the refusal
|
|
29
|
+
* stays, but the message now says why instead of telling the agent to commit
|
|
30
|
+
* a file its own author chose to keep out of git. An untracked-and-not-
|
|
31
|
+
* ignored path has no such rule attached to it at all: nobody has said
|
|
32
|
+
* anything about it one way or the other. In practice that is agent scratch
|
|
33
|
+
* or work in progress, so it is allowed.
|
|
34
|
+
*
|
|
35
|
+
* Accepted cost: a genuine new source file the agent wrote and has not yet
|
|
36
|
+
* committed is untracked-and-not-ignored too, so this permission also makes
|
|
37
|
+
* IT deletable -- there is no signal in git alone that separates "my own
|
|
38
|
+
* throwaway probe" from "a real file I just haven't committed yet". That is
|
|
39
|
+
* deliberate, not an oversight: it is the same permission that lets an agent
|
|
40
|
+
* clean up after itself, and the completion-phase auto-commit sweep
|
|
41
|
+
* (`autoCommitIfDirty`, `src/utils/git.ts`) is the backstop if something
|
|
42
|
+
* real gets removed before it was staged.
|
|
43
|
+
*
|
|
44
|
+
* `.git/` never reaches this point at all -- `resolveWithin`
|
|
45
|
+
* (`src/tools/policy.ts`) excludes it for every path-bearing tool, so
|
|
46
|
+
* `ctx.resolvedPaths` cannot contain a `.git/` path in the first place
|
|
47
|
+
* (nax#1943). Re-checking it here would be exactly the per-tool
|
|
48
|
+
* re-remembering that issue exists to prevent.
|
|
49
|
+
*
|
|
50
|
+
* Not `git rm`: gitTool documents a deliberate read/write split, and `git rm`
|
|
51
|
+
* both deletes and stages, folding two capabilities into one call. Plain
|
|
52
|
+
* unlink plus the existing GitCommit keeps each seam doing one thing --
|
|
53
|
+
* `git add -- <deleted path>` stages a deletion, so no change to GitCommit is
|
|
54
|
+
* needed.
|
|
55
|
+
*/
|
|
56
|
+
import type { CodingTool } from "./registry.ts";
|
|
57
|
+
export declare const deleteTool: CodingTool;
|