@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,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only git, for reviewers that need a real diff rather than one pushed
|
|
3
|
+
* into the prompt.
|
|
4
|
+
*
|
|
5
|
+
* This spawns a subprocess, which ADR-029 section 3 severed for Bash. The
|
|
6
|
+
* distinction, written down rather than assumed: git is a FIXED binary invoked
|
|
7
|
+
* with an argv nax constructs entirely, with no shell. The model supplies
|
|
8
|
+
* structure — a subcommand, refs, pathspecs — never a command string. Bash
|
|
9
|
+
* inverts that, which is why it needs a sandbox and a threat model instead of
|
|
10
|
+
* an allowlist.
|
|
11
|
+
*
|
|
12
|
+
* Reuses gitWithTimeout, which already provides the argv-array spawn, the
|
|
13
|
+
* explicit cwd, the SIGKILL timeout, and concurrent pipe draining — the last of
|
|
14
|
+
* which matters here because `git log -p` is exactly the output that fills a
|
|
15
|
+
* 64KB pipe buffer and deadlocks a naive implementation.
|
|
16
|
+
*/
|
|
17
|
+
import { interceptArgv } from "#src/command-interceptor/index";
|
|
18
|
+
import { gitWithTimeout } from "#src/internal/git-exec";
|
|
19
|
+
import { gitExcludePathspecsOf } from "./protected-paths.js";
|
|
20
|
+
import { cutToByteCap, READ_CEILING } from "./truncate.js";
|
|
21
|
+
/**
|
|
22
|
+
* Read-only verbs. Mutating verbs are not representable in the input type.
|
|
23
|
+
*
|
|
24
|
+
* Note what bounds these: the argv shape below, not the verb list. A read-only
|
|
25
|
+
* verb still spans the whole repository unless its scope is stated.
|
|
26
|
+
*/
|
|
27
|
+
export const GIT_READ_VERBS = ["diff", "log", "show", "status", "blame"];
|
|
28
|
+
/**
|
|
29
|
+
* Typed flag fields, and the verbs each one is valid on.
|
|
30
|
+
*
|
|
31
|
+
* nax emits every one of these flags itself: a boolean or a closed enum comes
|
|
32
|
+
* in, a fixed string goes out. The model never supplies flag text, so the
|
|
33
|
+
* property the header comment defends — the model supplies structure, nax
|
|
34
|
+
* constructs the argv — is unchanged by their existence. That is the whole
|
|
35
|
+
* reason they are typed fields rather than a passthrough list.
|
|
36
|
+
*
|
|
37
|
+
* They exist because `git diff --name-only --diff-filter=A` is step 1 of the
|
|
38
|
+
* adversarial reviewer's test-audit workflow and was not expressible at all:
|
|
39
|
+
* the only way to ask for it was to put the flags in `refs`/`paths`, where
|
|
40
|
+
* `looksLikeFlag` refuses them (#1818). A field is gated to the verbs it
|
|
41
|
+
* applies to so a wrong pairing is a nax error the model can act on, rather
|
|
42
|
+
* than a git usage error it has to interpret.
|
|
43
|
+
*/
|
|
44
|
+
export const GIT_DIFF_FILTERS = ["A", "M", "D", "R"];
|
|
45
|
+
// `show` is deliberately absent: git itself refuses it — "options '--name-only',
|
|
46
|
+
// '--name-status', '--check', and '-s' cannot be used together" (git 2.50.1),
|
|
47
|
+
// because `show` already supplies a conflicting output selector. Verified by
|
|
48
|
+
// running it; a nax-side refusal here is the clearer of the two errors.
|
|
49
|
+
// `show` takes git's ordinary diff and log options, so it belongs in all three.
|
|
50
|
+
// It was omitted from every gate while sitting in GIT_READ_VERBS, which made
|
|
51
|
+
// `git show --name-only <ref>` inexpressible: 22 denials across 4 features in
|
|
52
|
+
// the tool audit, from verifier, implementer and test-writer alike (nax#1800).
|
|
53
|
+
// `status` stays out -- git has no --name-only there, and inventing an
|
|
54
|
+
// acceptance would only move the failure to a raw git error.
|
|
55
|
+
const GIT_NAME_ONLY_VERBS = ["diff", "log", "show"];
|
|
56
|
+
const GIT_DIFF_FILTER_VERBS = ["diff", "log", "show"];
|
|
57
|
+
const GIT_ONELINE_VERBS = ["log", "show"];
|
|
58
|
+
/**
|
|
59
|
+
* `log` alone takes a commit bound. Every other read verb already names what it
|
|
60
|
+
* operates on -- `show` and `blame` take a ref, `diff` a range, `status` the
|
|
61
|
+
* working tree -- so none of them is unbounded in the way `log` is, and
|
|
62
|
+
* defaulting them would invent a truncation nobody asked for.
|
|
63
|
+
*/
|
|
64
|
+
const GIT_MAX_COUNT_VERBS = ["log"];
|
|
65
|
+
/**
|
|
66
|
+
* Commit bound applied to a `log` that does not supply its own.
|
|
67
|
+
*
|
|
68
|
+
* `log` was the one read verb with no bound on how much it returns: a pathspec
|
|
69
|
+
* selects WHICH commits to show, then each selected commit prints in full. With
|
|
70
|
+
* `nameOnly` that compounds, because `--name-only` lists every file in each
|
|
71
|
+
* matching commit rather than the path that selected it -- nax#2009 measured
|
|
72
|
+
* 79KB from 7 commits on a single-file pathspec, the largest call in a
|
|
73
|
+
* 267-call session.
|
|
74
|
+
*
|
|
75
|
+
* Applied ONLY to a `log` that names no `refs`. A ref range is a scope the
|
|
76
|
+
* caller already chose, and capping on top of it discards commits they asked
|
|
77
|
+
* for with no marker. A commit cap appends nothing, so the model cannot tell
|
|
78
|
+
* 12 commits from 200-capped-to-20. The reviewer prompt asks for a story's
|
|
79
|
+
* history as `log <ref>..HEAD --oneline`
|
|
80
|
+
* (`src/prompts/sections/protocol-region.ts`), and `--max-count` keeps the
|
|
81
|
+
* NEWEST n -- a default there would have silently dropped the initial
|
|
82
|
+
* implementation commits. An unscoped `log` walks the whole history of HEAD
|
|
83
|
+
* and is the shape with no bound at all.
|
|
84
|
+
*/
|
|
85
|
+
export const DEFAULT_LOG_MAX_COUNT = 20;
|
|
86
|
+
/**
|
|
87
|
+
* Compact commit rendering for `log`.
|
|
88
|
+
*
|
|
89
|
+
* `format:` rather than `tformat:` or the bare form: it places the separator
|
|
90
|
+
* BETWEEN commits, so each --name-only file list stays grouped with the commit
|
|
91
|
+
* that produced it instead of being orphaned after a blank line. Observed
|
|
92
|
+
* against a real repo, `format:%h %ad %s --name-only` renders the file list on
|
|
93
|
+
* the line directly after its commit's header; `tformat:` (which is what a bare
|
|
94
|
+
* `--format=` means) leaves a blank line between the two.
|
|
95
|
+
*
|
|
96
|
+
* `--date=short` pairs with it: `%ad` renders in whatever form `--date`
|
|
97
|
+
* selects, and the default is the raw commit timestamp
|
|
98
|
+
* (`Thu Apr 7 15:13:13 2005 -0700`) -- more noise than the compact line is
|
|
99
|
+
* meant to carry. (`%ai`/`%as` would be the ISO-8601 spellings; neither is
|
|
100
|
+
* used here.)
|
|
101
|
+
*/
|
|
102
|
+
export const DEFAULT_LOG_FORMAT = "format:%h %ad %s";
|
|
103
|
+
/**
|
|
104
|
+
* `fullMessage` opts back in to git's `medium` default by suppressing the
|
|
105
|
+
* compact `--format=`. It only makes sense for `log`: `show` renders a single
|
|
106
|
+
* commit whose message is usually the reason for the call, and the other
|
|
107
|
+
* read verbs do not render commit messages at all.
|
|
108
|
+
*/
|
|
109
|
+
const GIT_FULL_MESSAGE_VERBS = ["log"];
|
|
110
|
+
/**
|
|
111
|
+
* git parses `--max-count` into a C int, so INT_MAX is the real ceiling.
|
|
112
|
+
*
|
|
113
|
+
* Above it the field would defeat its own purpose: `Number.isInteger(1e21)` is
|
|
114
|
+
* true and interpolation renders it `1e+21`, so `maxCount: 1e21` reached git as
|
|
115
|
+
* `fatal: '1e+21': not an integer` -- a git usage error the model has to
|
|
116
|
+
* interpret, which is the class this field refuses rather than coerces.
|
|
117
|
+
*/
|
|
118
|
+
const GIT_MAX_COUNT_CEILING = 2_147_483_647;
|
|
119
|
+
/**
|
|
120
|
+
* A positive-integer field, gated to the verbs it applies to.
|
|
121
|
+
*
|
|
122
|
+
* Refused rather than coerced, for the reason `flagFromBoolean` gives: a
|
|
123
|
+
* non-integer would reach git as `--max-count=1.5` and come back as a git usage
|
|
124
|
+
* error the model then has to interpret. The `typeof` check rejects every
|
|
125
|
+
* non-number -- the numeric string `"5"`, `true`, `null`, objects -- and
|
|
126
|
+
* `Number.isInteger` rejects NaN and Infinity.
|
|
127
|
+
*/
|
|
128
|
+
function flagFromPositiveInteger(input, field, subcommand, validVerbs, flag) {
|
|
129
|
+
const value = input[field];
|
|
130
|
+
if (value === undefined)
|
|
131
|
+
return null;
|
|
132
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > GIT_MAX_COUNT_CEILING) {
|
|
133
|
+
return { error: `"${field}" must be a positive integer` };
|
|
134
|
+
}
|
|
135
|
+
if (!validVerbs.includes(subcommand)) {
|
|
136
|
+
return { error: `"${field}" is not valid for "${subcommand}" (valid for: ${validVerbs.join(", ")})` };
|
|
137
|
+
}
|
|
138
|
+
return `${flag}=${value}`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A boolean flag field: absent or `false` emits nothing, `true` emits the flag.
|
|
142
|
+
*
|
|
143
|
+
* A non-boolean is refused rather than coerced — `nameOnly: "false"` is truthy
|
|
144
|
+
* in JavaScript, so coercion would turn a model's mistake into the opposite of
|
|
145
|
+
* what it asked for.
|
|
146
|
+
*/
|
|
147
|
+
function flagFromBoolean(input, field, subcommand, validVerbs, flag) {
|
|
148
|
+
const value = input[field];
|
|
149
|
+
if (value === undefined)
|
|
150
|
+
return null;
|
|
151
|
+
if (typeof value !== "boolean")
|
|
152
|
+
return { error: `"${field}" must be a boolean` };
|
|
153
|
+
// Checked before the verb gate: `false` asks for nothing, so refusing it for
|
|
154
|
+
// the wrong verb would invent a refusal — the failure class this change
|
|
155
|
+
// exists to reduce.
|
|
156
|
+
if (!value)
|
|
157
|
+
return null;
|
|
158
|
+
if (!validVerbs.includes(subcommand)) {
|
|
159
|
+
return { error: `"${field}" is not valid for "${subcommand}" (valid for: ${validVerbs.join(", ")})` };
|
|
160
|
+
}
|
|
161
|
+
return flag;
|
|
162
|
+
}
|
|
163
|
+
export { GIT_ESCAPE_FLAGS } from "#src/tools/git-flags/index";
|
|
164
|
+
function looksLikeFlag(value) {
|
|
165
|
+
return value.startsWith("-");
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* `defaultExcludes`: pathspecs appended to an unscoped call (the host's own
|
|
169
|
+
* state, nax#2007); none by default.
|
|
170
|
+
*/
|
|
171
|
+
export function buildGitArgv(input, defaultExcludes = []) {
|
|
172
|
+
const subcommand = input.subcommand;
|
|
173
|
+
if (typeof subcommand !== "string" || !GIT_READ_VERBS.includes(subcommand)) {
|
|
174
|
+
return { error: `subcommand must be one of: ${GIT_READ_VERBS.join(", ")}` };
|
|
175
|
+
}
|
|
176
|
+
const refs = Array.isArray(input.refs) ? input.refs : [];
|
|
177
|
+
const paths = Array.isArray(input.paths) ? input.paths : [];
|
|
178
|
+
// No `--relative` is injected. Since the single-frame redesign the permitted
|
|
179
|
+
// root is the repository root, so git's default repo-rooted path framing
|
|
180
|
+
// already agrees with Read/Grep/Glob. The flag only ever compensated for a
|
|
181
|
+
// package-subdir root (#1807); from the repo root it is wrong.
|
|
182
|
+
const argv = [subcommand];
|
|
183
|
+
// Flags precede the refs. Git accepts them in either position, but a flag
|
|
184
|
+
// placed after a revision list reads as a pathspec to anyone (model or
|
|
185
|
+
// human) scanning the argv, and this argv is written into the tool-audit
|
|
186
|
+
// ledger that #1818 was filed from.
|
|
187
|
+
const nameOnly = flagFromBoolean(input, "nameOnly", subcommand, GIT_NAME_ONLY_VERBS, "--name-only");
|
|
188
|
+
if (nameOnly !== null && typeof nameOnly === "object")
|
|
189
|
+
return nameOnly;
|
|
190
|
+
if (nameOnly !== null)
|
|
191
|
+
argv.push(nameOnly);
|
|
192
|
+
const diffFilter = input.diffFilter;
|
|
193
|
+
if (diffFilter !== undefined) {
|
|
194
|
+
if (typeof diffFilter !== "string" || !GIT_DIFF_FILTERS.includes(diffFilter)) {
|
|
195
|
+
return { error: `"diffFilter" must be one of: ${GIT_DIFF_FILTERS.join(", ")}` };
|
|
196
|
+
}
|
|
197
|
+
if (!GIT_DIFF_FILTER_VERBS.includes(subcommand)) {
|
|
198
|
+
return {
|
|
199
|
+
error: `"diffFilter" is not valid for "${subcommand}" (valid for: ${GIT_DIFF_FILTER_VERBS.join(", ")})`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
argv.push(`--diff-filter=${diffFilter}`);
|
|
203
|
+
}
|
|
204
|
+
// `fullMessage` is validated first, before `oneline` is pushed into argv, so
|
|
205
|
+
// the contradiction refusal is the actual return and the local argv is
|
|
206
|
+
// never built. The non-boolean check precedes the verb gate for the same
|
|
207
|
+
// reason `flagFromBoolean` does: a non-boolean still names the field it's
|
|
208
|
+
// about.
|
|
209
|
+
const fullMessage = input.fullMessage;
|
|
210
|
+
if (fullMessage !== undefined) {
|
|
211
|
+
if (typeof fullMessage !== "boolean") {
|
|
212
|
+
return { error: `"fullMessage" must be a boolean` };
|
|
213
|
+
}
|
|
214
|
+
if (fullMessage) {
|
|
215
|
+
if (input.oneline === true) {
|
|
216
|
+
// Both at once is a contradiction: `oneline` requests a one-line
|
|
217
|
+
// commit list, `fullMessage` requests git's medium default (subject,
|
|
218
|
+
// author, date, body). Refused by name rather than resolved silently.
|
|
219
|
+
return {
|
|
220
|
+
error: `"oneline" and "fullMessage" cannot both be true; pick one`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (!GIT_FULL_MESSAGE_VERBS.includes(subcommand)) {
|
|
224
|
+
return {
|
|
225
|
+
error: `"fullMessage" is not valid for "${subcommand}" (valid for: ${GIT_FULL_MESSAGE_VERBS.join(", ")})`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const oneline = flagFromBoolean(input, "oneline", subcommand, GIT_ONELINE_VERBS, "--oneline");
|
|
231
|
+
if (oneline !== null && typeof oneline === "object")
|
|
232
|
+
return oneline;
|
|
233
|
+
if (oneline !== null)
|
|
234
|
+
argv.push(oneline);
|
|
235
|
+
// `--oneline` and `--format=` are last-wins on the same argv; `oneline`
|
|
236
|
+
// continuing to win is intentional — it is the caller's explicit request.
|
|
237
|
+
// `fullMessage` suppresses the default because its whole purpose is to
|
|
238
|
+
// restore git's medium rendering.
|
|
239
|
+
if (subcommand === "log" && oneline === null && fullMessage !== true) {
|
|
240
|
+
argv.push(`--format=${DEFAULT_LOG_FORMAT}`);
|
|
241
|
+
argv.push("--date=short");
|
|
242
|
+
}
|
|
243
|
+
const maxCount = flagFromPositiveInteger(input, "maxCount", subcommand, GIT_MAX_COUNT_VERBS, "--max-count");
|
|
244
|
+
if (maxCount !== null && typeof maxCount === "object")
|
|
245
|
+
return maxCount;
|
|
246
|
+
if (maxCount !== null)
|
|
247
|
+
argv.push(maxCount);
|
|
248
|
+
// Only an UNSCOPED log gets the default -- see DEFAULT_LOG_MAX_COUNT. A refs
|
|
249
|
+
// range is the caller's own scope, and capping it would drop commits they
|
|
250
|
+
// asked for with nothing in the output to say so.
|
|
251
|
+
else if (GIT_MAX_COUNT_VERBS.includes(subcommand) && refs.length === 0) {
|
|
252
|
+
argv.push(`--max-count=${DEFAULT_LOG_MAX_COUNT}`);
|
|
253
|
+
}
|
|
254
|
+
for (const ref of refs) {
|
|
255
|
+
if (typeof ref !== "string")
|
|
256
|
+
return { error: "refs must be strings" };
|
|
257
|
+
// A ref that begins with "-" would be parsed as an option, which is how an
|
|
258
|
+
// escape flag would arrive. Refuse rather than sanitise.
|
|
259
|
+
if (looksLikeFlag(ref))
|
|
260
|
+
return { error: `ref "${ref}" may not begin with "-"` };
|
|
261
|
+
argv.push(ref);
|
|
262
|
+
}
|
|
263
|
+
// `--` ALWAYS terminates the revision list, even with no paths to follow it.
|
|
264
|
+
// Without it git disambiguates an argument that is not a valid revision by
|
|
265
|
+
// checking whether it names a path, and silently reinterprets it as a
|
|
266
|
+
// pathspec -- so `show ../../outside/secret` read a file outside the root,
|
|
267
|
+
// never reaching resolveWithin because a colon-less ref was treated as "a
|
|
268
|
+
// pure revision, nothing to contain". With `--` git rejects it outright.
|
|
269
|
+
argv.push("--");
|
|
270
|
+
if (paths.length === 0) {
|
|
271
|
+
// Scope an unrestricted call to the root. A ref names a whole commit, whose
|
|
272
|
+
// diff spans the entire repository, so `show HEAD` returned outside-root
|
|
273
|
+
// content without naming a path at all -- nothing in the argv was wrong,
|
|
274
|
+
// the command's own scope was. `.` is resolved by git against the cwd,
|
|
275
|
+
// which gitWithTimeout sets to the permitted root.
|
|
276
|
+
argv.push(".");
|
|
277
|
+
// The host's own run state under .nax/ is git-tracked during a run, so an
|
|
278
|
+
// unscoped call reported it back as the agent's diff (#2007). Excluded only
|
|
279
|
+
// on this DEFAULT branch: a caller that names a path under .nax/ gets it --
|
|
280
|
+
// the tool is read-only, and silently returning nothing for an explicitly
|
|
281
|
+
// requested path would be a worse failure than the one being fixed.
|
|
282
|
+
// `blame` is exempt because git rejects exclude pathspecs on it and exits
|
|
283
|
+
// 128; do not "unify" it back in.
|
|
284
|
+
if (subcommand !== "blame")
|
|
285
|
+
argv.push(...defaultExcludes);
|
|
286
|
+
return argv;
|
|
287
|
+
}
|
|
288
|
+
for (const path of paths) {
|
|
289
|
+
if (typeof path !== "string")
|
|
290
|
+
return { error: "paths must be strings" };
|
|
291
|
+
if (looksLikeFlag(path))
|
|
292
|
+
return { error: `path "${path}" may not begin with "-"` };
|
|
293
|
+
argv.push(path);
|
|
294
|
+
}
|
|
295
|
+
return argv;
|
|
296
|
+
}
|
|
297
|
+
export const gitTool = {
|
|
298
|
+
name: "Git",
|
|
299
|
+
description: "Run a read-only git command (diff, log, show, status, blame) in the repository. Supply refs and pathspecs as arrays, not as a command line. Command-line flags are not accepted in any field; use the nameOnly, diffFilter, oneline, fullMessage and maxCount fields instead.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
subcommand: { type: "string", enum: [...GIT_READ_VERBS], description: "Read-only git subcommand" },
|
|
304
|
+
refs: { type: "array", items: { type: "string" }, description: "Refs, e.g. ['HEAD~1','HEAD']" },
|
|
305
|
+
paths: { type: "array", items: { type: "string" }, description: "Pathspecs, relative to the permitted root" },
|
|
306
|
+
nameOnly: {
|
|
307
|
+
type: "boolean",
|
|
308
|
+
description: "List file names only, no content (diff, log, show). On log the file list is restricted to the pathspec; log renders a compact commit line by default — set fullMessage to restore full commit bodies.",
|
|
309
|
+
},
|
|
310
|
+
diffFilter: {
|
|
311
|
+
type: "string",
|
|
312
|
+
enum: [...GIT_DIFF_FILTERS],
|
|
313
|
+
description: "Select only files Added (A), Modified (M), Deleted (D) or Renamed (R) (diff)",
|
|
314
|
+
},
|
|
315
|
+
oneline: { type: "boolean", description: "One line per commit (log)" },
|
|
316
|
+
fullMessage: {
|
|
317
|
+
type: "boolean",
|
|
318
|
+
description: "Restore git's medium commit rendering on log (subject, author, date, body). The default is a compact one-line form; set this to opt back in to the full message.",
|
|
319
|
+
},
|
|
320
|
+
maxCount: {
|
|
321
|
+
type: "integer",
|
|
322
|
+
description: `Maximum commits to return, newest first (log). A log with no refs defaults to ${DEFAULT_LOG_MAX_COUNT}; a log with a ref range is left unbounded. Raise it to walk further back.`,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
required: ["subcommand"],
|
|
326
|
+
},
|
|
327
|
+
// `paths` entries and the path-portion of `refs` entries (git's
|
|
328
|
+
// "<rev>:<path>" syntax) are checked for containment via arrayPathFields /
|
|
329
|
+
// refPathFields — see the ToolScope doc comment for why they carry no
|
|
330
|
+
// pattern matching. The verb gate above is still what bounds which git
|
|
331
|
+
// subcommands may run at all.
|
|
332
|
+
scope: {
|
|
333
|
+
pathFields: [],
|
|
334
|
+
arrayPathFields: ["paths"],
|
|
335
|
+
refPathFields: ["refs"],
|
|
336
|
+
verbField: "subcommand",
|
|
337
|
+
allowedVerbs: GIT_READ_VERBS,
|
|
338
|
+
},
|
|
339
|
+
async run(input, ctx) {
|
|
340
|
+
const built = buildGitArgv(input, gitExcludePathspecsOf(ctx));
|
|
341
|
+
if ("error" in built)
|
|
342
|
+
return { content: built.error, isError: true };
|
|
343
|
+
const interceptor = ctx.interceptor;
|
|
344
|
+
try {
|
|
345
|
+
// nax-git-env-allow: not a spawn; the (possibly rewritten) argv runs through gitWithTimeout below
|
|
346
|
+
const intercepted = await interceptArgv(["git", ...built], ctx.root, interceptor);
|
|
347
|
+
// The I/O ceiling, not the model-facing cap — see the `cutToByteCap`
|
|
348
|
+
// call below, which bounds the same body at the same value.
|
|
349
|
+
const ioCeiling = ctx.readCeiling ?? READ_CEILING;
|
|
350
|
+
const { stdout, stderr, exitCode } = await gitWithTimeout(built, ctx.root, undefined, ioCeiling, intercepted.argv);
|
|
351
|
+
// Computed before the error branch so a REWRITTEN command that ran and
|
|
352
|
+
// FAILED is still ledged — replay fidelity is exactly where the failure
|
|
353
|
+
// path matters (US-008). `InterceptOutcome.executed` is optional but
|
|
354
|
+
// `ToolResult.audit.executed` is required, so the narrowing stays.
|
|
355
|
+
const audit = intercepted.executed !== undefined ? { executed: intercepted.executed } : undefined;
|
|
356
|
+
if (exitCode !== 0 && stdout.trim() === "") {
|
|
357
|
+
return {
|
|
358
|
+
content: stderr.trim() || `git exited ${exitCode}`,
|
|
359
|
+
isError: true,
|
|
360
|
+
...(audit !== undefined ? { audit } : {}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// postProcess runs BEFORE trimEnd/truncate: stripping a hint changes what
|
|
364
|
+
// the trailing whitespace and the byte budget apply to, and truncating
|
|
365
|
+
// first would hand postProcess a hint that truncation cut in half. It is
|
|
366
|
+
// consulted only for output of a command this interceptor actually
|
|
367
|
+
// rewrote, and a throw degrades to the raw output (advisory, like the
|
|
368
|
+
// rewrite-time fail-open in interceptArgv).
|
|
369
|
+
let body = stdout;
|
|
370
|
+
if (intercepted.rewritten) {
|
|
371
|
+
// nax-git-env-allow: not a spawn; the postProcess request describing the argv already run
|
|
372
|
+
const req = { kind: "argv", argv: ["git", ...built], cwd: ctx.root, site: "git" };
|
|
373
|
+
try {
|
|
374
|
+
body = interceptor?.postProcess?.(stdout, req)?.output ?? stdout;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
body = stdout;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Known limitation: gitWithTimeout bounds stdout with the I/O ceiling
|
|
381
|
+
// BEFORE postProcess sees it, so a trailing hint on a very large output
|
|
382
|
+
// can be truncated mid-string and escape stripping. Fixing it means
|
|
383
|
+
// moving the bound after post-processing, which changes the drain
|
|
384
|
+
// contract — its own change.
|
|
385
|
+
//
|
|
386
|
+
// The model-facing cap is the session policy's, not this one: what is
|
|
387
|
+
// returned here is bounded only by the tool-layer ceiling, so a result
|
|
388
|
+
// the model cannot see whole still reaches the policy intact — and is
|
|
389
|
+
// spilled rather than silently replaced by a per-tool marker.
|
|
390
|
+
const content = cutToByteCap(body.trimEnd(), ioCeiling) || "(no output)";
|
|
391
|
+
return { content, ...(audit !== undefined ? { audit } : {}) };
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List files matching a glob, always relative to and bounded by the root.
|
|
3
|
+
*
|
|
4
|
+
* The runtime scans from a cwd, so the root is the cwd and results are relative by
|
|
5
|
+
* construction. A pattern that tries to climb out ("../**") therefore matches
|
|
6
|
+
* nothing rather than escaping.
|
|
7
|
+
*
|
|
8
|
+
* Output is directory-grouped — one line per parent directory, basenames
|
|
9
|
+
* joined with single spaces, basenames containing whitespace or a quote
|
|
10
|
+
* wrapped in double quotes and escaped (`\\`, `\"`, `\n`, `\r`, `\t`). A
|
|
11
|
+
* match directly at the repository root is grouped under the prefix `./`, so
|
|
12
|
+
* every line has a directory prefix and there is no second shape to
|
|
13
|
+
* recognise. The format is lossless: parsing each line back into
|
|
14
|
+
* `<dir>/ <b1> <b2> ...`, unescaping each quoted basename, and joining each
|
|
15
|
+
* basename to its directory reconstructs the matched set exactly.
|
|
16
|
+
*/
|
|
17
|
+
import type { CodingTool } from "./registry.ts";
|
|
18
|
+
/**
|
|
19
|
+
* @internal Injectable for tests — exercises the catch path without planting
|
|
20
|
+
* a malformed pattern on disk. Production wires this to `Bun.Glob`'s async
|
|
21
|
+
* `scan({ cwd, absolute })` iterator.
|
|
22
|
+
*/
|
|
23
|
+
export declare const _globDeps: {
|
|
24
|
+
scan(pattern: string, opts: {
|
|
25
|
+
cwd: string;
|
|
26
|
+
absolute: boolean;
|
|
27
|
+
}): AsyncIterable<string>;
|
|
28
|
+
};
|
|
29
|
+
export declare const globTool: CodingTool;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List files matching a glob, always relative to and bounded by the root.
|
|
3
|
+
*
|
|
4
|
+
* The runtime scans from a cwd, so the root is the cwd and results are relative by
|
|
5
|
+
* construction. A pattern that tries to climb out ("../**") therefore matches
|
|
6
|
+
* nothing rather than escaping.
|
|
7
|
+
*
|
|
8
|
+
* Output is directory-grouped — one line per parent directory, basenames
|
|
9
|
+
* joined with single spaces, basenames containing whitespace or a quote
|
|
10
|
+
* wrapped in double quotes and escaped (`\\`, `\"`, `\n`, `\r`, `\t`). A
|
|
11
|
+
* match directly at the repository root is grouped under the prefix `./`, so
|
|
12
|
+
* every line has a directory prefix and there is no second shape to
|
|
13
|
+
* recognise. The format is lossless: parsing each line back into
|
|
14
|
+
* `<dir>/ <b1> <b2> ...`, unescaping each quoted basename, and joining each
|
|
15
|
+
* basename to its directory reconstructs the matched set exactly.
|
|
16
|
+
*/
|
|
17
|
+
import { sep } from "node:path";
|
|
18
|
+
import { getAgentRuntime } from "#src/runtime/index";
|
|
19
|
+
import { resolveWithin } from "./policy.js";
|
|
20
|
+
const MAX_MATCHES = 500;
|
|
21
|
+
/**
|
|
22
|
+
* A pattern with a ".." segment can only match paths outside the root, so it
|
|
23
|
+
* is answered without scanning at all: the scanner would otherwise walk the
|
|
24
|
+
* parent tree (unbounded on real filesystems) just to yield hits that
|
|
25
|
+
* resolveWithin would discard. The result echoes no pattern, so no ".." can
|
|
26
|
+
* leak into the output.
|
|
27
|
+
*/
|
|
28
|
+
function climbsOut(pattern) {
|
|
29
|
+
return pattern.split(/[\\/]/).includes("..");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @internal Injectable for tests — exercises the catch path without planting
|
|
33
|
+
* a malformed pattern on disk. Production wires this to `Bun.Glob`'s async
|
|
34
|
+
* `scan({ cwd, absolute })` iterator.
|
|
35
|
+
*/
|
|
36
|
+
export const _globDeps = {
|
|
37
|
+
scan(pattern, opts) {
|
|
38
|
+
return getAgentRuntime().glob(pattern, opts);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Escapes applied inside a quoted basename. Four are required to keep the
|
|
43
|
+
* format lossless: `"` (would otherwise end the quoted form early), `\` (it
|
|
44
|
+
* introduces the escapes, so it must escape itself), and the line terminators
|
|
45
|
+
* `\n` / `\r` (group lines are joined by `\n`, so a literal one inside a
|
|
46
|
+
* basename would split a single line into two). `\t` is escaped as well, so a
|
|
47
|
+
* consumer that tokenises on whitespace rather than on this grammar still
|
|
48
|
+
* sees one basename. Everything else passes through unchanged; this is not a
|
|
49
|
+
* general-purpose escape.
|
|
50
|
+
*/
|
|
51
|
+
const ESCAPED_BASENAME_CHARS = {
|
|
52
|
+
"\\": "\\\\",
|
|
53
|
+
'"': '\\"',
|
|
54
|
+
"\n": "\\n",
|
|
55
|
+
"\r": "\\r",
|
|
56
|
+
"\t": "\\t",
|
|
57
|
+
};
|
|
58
|
+
function escapeBasename(b) {
|
|
59
|
+
// The `?? c` arm is unreachable — the regex only matches the keys above —
|
|
60
|
+
// but it keeps the callback's return type `string` for `String.replace`.
|
|
61
|
+
return b.replace(/[\\"\n\r\t]/g, (c) => ESCAPED_BASENAME_CHARS[c] ?? c);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Bucket a flat match list by parent directory and render each bucket as one
|
|
65
|
+
* line: `<dir>/ <b1> <b2> ...`. The shape is identical whether the result
|
|
66
|
+
* holds one match or many, so a wide listing and an existence probe read the
|
|
67
|
+
* same way. A basename containing whitespace or a quote is wrapped in double
|
|
68
|
+
* quotes, with the characters above escaped inside the quotes; every other
|
|
69
|
+
* basename — including one that contains only a backslash — passes through
|
|
70
|
+
* unchanged. Group lines and basenames are both sorted ascending, so the
|
|
71
|
+
* output is deterministic for a given match set.
|
|
72
|
+
*/
|
|
73
|
+
function renderGrouped(matches) {
|
|
74
|
+
const byDir = new Map();
|
|
75
|
+
for (const path of matches) {
|
|
76
|
+
const lastSlash = path.lastIndexOf("/");
|
|
77
|
+
const dir = lastSlash === -1 ? "./" : `${path.slice(0, lastSlash + 1)}`;
|
|
78
|
+
const basename = lastSlash === -1 ? path : path.slice(lastSlash + 1);
|
|
79
|
+
let bucket = byDir.get(dir);
|
|
80
|
+
if (bucket === undefined) {
|
|
81
|
+
bucket = [];
|
|
82
|
+
byDir.set(dir, bucket);
|
|
83
|
+
}
|
|
84
|
+
bucket.push(basename);
|
|
85
|
+
}
|
|
86
|
+
const dirs = [...byDir.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
87
|
+
const lines = [];
|
|
88
|
+
for (const dir of dirs) {
|
|
89
|
+
const bucket = byDir.get(dir) ?? [];
|
|
90
|
+
bucket.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
91
|
+
const rendered = bucket.map((b) => (needsQuoting(b) ? `"${escapeBasename(b)}"` : b));
|
|
92
|
+
// The dir prefix carries the same quoting rules a basename does: a dir
|
|
93
|
+
// containing whitespace or `"` is wrapped in double quotes with the same
|
|
94
|
+
// escapes, so `<dir>/ <b1> <b2> ...` stays mechanically splittable in
|
|
95
|
+
// either shape and the round-trip — AC-5 — survives a directory whose
|
|
96
|
+
// name contains a space (e.g. `my code/` would otherwise render as
|
|
97
|
+
// `my code/ a.ts`, ambiguous on whitespace).
|
|
98
|
+
const dirPrefix = needsQuoting(dir) ? `"${escapeBasename(dir)}"` : dir;
|
|
99
|
+
lines.push(`${dirPrefix} ${rendered.join(" ")}`);
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* True when a basename needs quoting to keep the format unambiguous: any
|
|
105
|
+
* whitespace (would split a single basename into multiple tokens), or the
|
|
106
|
+
* quote character itself (would terminate the quoted form early).
|
|
107
|
+
*/
|
|
108
|
+
function needsQuoting(b) {
|
|
109
|
+
return /[\s"]/.test(b);
|
|
110
|
+
}
|
|
111
|
+
export const globTool = {
|
|
112
|
+
name: "Glob",
|
|
113
|
+
description: "List repository files matching a glob pattern, grouping matches by their parent directory. One line per parent directory, basenames sorted ascending, e.g. 'path/to/ a.ts b.ts'. Each line is lossless — concatenating the prefix and a basename reproduces the matched path. A basename is wrapped in double quotes when it contains whitespace or a double quote, and inside quotes the escapes \\\\, \\\", \\n, \\r and \\t stand for the literal character; outside quotes nothing is escaped, so a bare backslash is literal. The tool can also be used to check whether a path exists (call with a literal path like 'src/a.ts'; a hit returns 'src/ a.ts', a miss returns 'no matches for \"src/a.ts\"'). Patterns are matched relative to the repository root; a pattern that tries to climb out matches nothing.",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: { pattern: { type: "string", description: "Glob pattern, relative to the repository root" } },
|
|
117
|
+
required: ["pattern"],
|
|
118
|
+
},
|
|
119
|
+
// The pattern is not a path: it is matched inside the root by construction,
|
|
120
|
+
// so there is no path field for the policy to gate. Grant-level gating still
|
|
121
|
+
// applies, which is what decides whether Glob may run at all.
|
|
122
|
+
scope: { pathFields: [] },
|
|
123
|
+
async run(input, ctx) {
|
|
124
|
+
const pattern = input.pattern;
|
|
125
|
+
if (typeof pattern !== "string")
|
|
126
|
+
return { content: "pattern must be a string", isError: true };
|
|
127
|
+
if (climbsOut(pattern))
|
|
128
|
+
return { content: "no matches" };
|
|
129
|
+
const matches = [];
|
|
130
|
+
try {
|
|
131
|
+
// `absolute: false` is the repo-wide idiom (test-scanner.ts:318,
|
|
132
|
+
// fragments/store.ts:53, manifest-purge.ts:64) and yields root-relative
|
|
133
|
+
// paths directly. Each is still re-checked through resolveWithin, because
|
|
134
|
+
// a pattern that climbs out must produce nothing rather than escape.
|
|
135
|
+
// The scan itself goes through `_globDeps.scan` so a test can substitute
|
|
136
|
+
// a throwing iterator and exercise the catch branch without planting a
|
|
137
|
+
// malformed pattern on disk.
|
|
138
|
+
for await (const hit of _globDeps.scan(pattern, { cwd: ctx.root, absolute: false })) {
|
|
139
|
+
if (resolveWithin(ctx.root, hit) === null)
|
|
140
|
+
continue;
|
|
141
|
+
matches.push(hit.split(sep).join("/"));
|
|
142
|
+
if (matches.length >= MAX_MATCHES)
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return { content: err instanceof Error ? err.message : String(err), isError: true };
|
|
148
|
+
}
|
|
149
|
+
if (matches.length === 0)
|
|
150
|
+
return { content: `no matches for "${pattern}"` };
|
|
151
|
+
return { content: renderGrouped(matches) };
|
|
152
|
+
},
|
|
153
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search file contents, preferring ripgrep and falling back to grep.
|
|
3
|
+
*
|
|
4
|
+
* Both branches spawn a subprocess, so this tool is NOT evidence that the
|
|
5
|
+
* default tool set is in-process — it is not. What makes it safe is the same
|
|
6
|
+
* property that makes Git safe: a fixed binary, an argv nax constructs
|
|
7
|
+
* entirely, and no shell, so the model supplies data and never a command.
|
|
8
|
+
*
|
|
9
|
+
* The two binaries take different flags, so the argv builder is per-binary
|
|
10
|
+
* rather than shared, and the fallback is tested explicitly: a machine without
|
|
11
|
+
* ripgrep must produce the same matches, not a silent empty result.
|
|
12
|
+
*/
|
|
13
|
+
import type { CodingTool } from "./registry.ts";
|
|
14
|
+
/** @internal Injectable for tests — exercises the fallback without uninstalling ripgrep. */
|
|
15
|
+
export declare const _grepDeps: {
|
|
16
|
+
which: (name: string) => string | null;
|
|
17
|
+
spawn: (cmd: readonly string[], opts: import("#src/runtime/index").AgentSpawnOptions) => import("#src/runtime/index").AgentSpawnResult;
|
|
18
|
+
};
|
|
19
|
+
export type GrepPatternType = "literal" | "regex";
|
|
20
|
+
export declare function buildGrepArgv(binary: "rg" | "grep", pattern: string, path: string | undefined, patternType?: GrepPatternType): string[];
|
|
21
|
+
export declare const grepTool: CodingTool;
|