@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,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile declarative grants into a policy, and answer one call at a time.
|
|
3
|
+
*
|
|
4
|
+
* nax-permission-mode-allow: applies grants resolved by resolvePermissions;
|
|
5
|
+
* decides no permission of its own.
|
|
6
|
+
*
|
|
7
|
+
* Containment runs BEFORE pattern matching and is not expressible in config:
|
|
8
|
+
* the root is a boundary no PROFILE can widen. `unrestricted` means "any
|
|
9
|
+
* tool, any path within the root", never "any path on the machine".
|
|
10
|
+
*
|
|
11
|
+
* The `execTouchedPaths` carve-out (Task 10) was retired in the single-frame
|
|
12
|
+
* root move (PR2/Task 13): the containment root became the repo root, so a
|
|
13
|
+
* workspace install's root manifest is in-root by construction and no
|
|
14
|
+
* exception to the boundary is needed.
|
|
15
|
+
*/
|
|
16
|
+
import { relative, resolve, sep } from "node:path";
|
|
17
|
+
import { realOrRaw } from "#src/internal/realpath";
|
|
18
|
+
import { validateArgv } from "./exec-guard.js";
|
|
19
|
+
import { naxOwnedWriteRefusal, naxWriteOptIns } from "./nax-owned-writes.js";
|
|
20
|
+
import { commandBranch } from "./policy-command-branch.js";
|
|
21
|
+
import { compileArgvPattern, compileRuleMap, globToRegExp, matchedArgvSource, matchedGlobSource, matchedRulePatterns, matchesAny, matchesArgvGrant, } from "./policy-match.js";
|
|
22
|
+
import { pathsBranch, resolveWithin } from "./policy-paths-branch.js";
|
|
23
|
+
import { EXEC_TOOL_NAME } from "./types.js";
|
|
24
|
+
import { argvShellHint } from "./verb-denial-argv-hint.js";
|
|
25
|
+
/** The single containment seam now lives beside the path branch that drives it. */
|
|
26
|
+
export { resolveWithin };
|
|
27
|
+
function isFieldlessScope(scope) {
|
|
28
|
+
return (scope.argvField === undefined &&
|
|
29
|
+
scope.commandField === undefined &&
|
|
30
|
+
scope.verbField === undefined &&
|
|
31
|
+
scope.pathFields.length === 0 &&
|
|
32
|
+
(scope.listPathFields?.length ?? 0) === 0 &&
|
|
33
|
+
(scope.arrayPathFields?.length ?? 0) === 0 &&
|
|
34
|
+
(scope.refPathFields?.length ?? 0) === 0);
|
|
35
|
+
}
|
|
36
|
+
export function compileToolPolicy(grants, root, options) {
|
|
37
|
+
const resolvedRoot = realOrRaw(root);
|
|
38
|
+
// nax#2115: the SAME transform `relativeTo` applies to every checked path, so
|
|
39
|
+
// the guard compares like with like. A path outside the root yields a
|
|
40
|
+
// ".."-prefixed rel that can never equal a checked path's, exempting nothing.
|
|
41
|
+
const ownedWriteExemption = options?.ownedWriteExemption === undefined
|
|
42
|
+
? undefined
|
|
43
|
+
: relative(resolvedRoot, realOrRaw(options.ownedWriteExemption)).split(sep).join("/");
|
|
44
|
+
const naxOptIns = naxWriteOptIns(root, options?.naxAllowWrite ?? []);
|
|
45
|
+
const denyBy = compileRuleMap(options?.denyRules);
|
|
46
|
+
const askBy = compileRuleMap(options?.askRules);
|
|
47
|
+
const bashApproval = options?.bashApproval ?? "gated";
|
|
48
|
+
const compiled = new Map();
|
|
49
|
+
for (const grant of grants) {
|
|
50
|
+
const unconditional = grant.patterns.includes("*");
|
|
51
|
+
const nonWildcard = grant.patterns.filter((p) => p !== "*");
|
|
52
|
+
compiled.set(grant.tool, {
|
|
53
|
+
unconditional,
|
|
54
|
+
matchers: nonWildcard.map((source) => ({ source, re: globToRegExp(source) })),
|
|
55
|
+
argvPatterns: nonWildcard.map((source) => compileArgvPattern(source)),
|
|
56
|
+
raw: grant.patterns,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function deny(reason, breach = false, escalatable = false) {
|
|
60
|
+
return { allowed: false, reason, breach, escalatable, outcome: "denied" };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* `allowed: false` on purpose: any consumer that only reads `allowed` fails
|
|
64
|
+
* closed. `outcome: "ask"` is the sole signal that an AskResolver may approve
|
|
65
|
+
* this call, and `resolvedPaths` is what an approval would admit.
|
|
66
|
+
*/
|
|
67
|
+
function askVerdict(resolvedPaths, rule) {
|
|
68
|
+
return {
|
|
69
|
+
allowed: false,
|
|
70
|
+
reason: `matched ask rule "${rule}" — requires approval before it may run`,
|
|
71
|
+
breach: false,
|
|
72
|
+
outcome: "ask",
|
|
73
|
+
resolvedPaths,
|
|
74
|
+
rule,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** `Tool` for an unconditional entry, else `Tool(pattern, ...)`. */
|
|
78
|
+
function ruleExpr(tool, entry, source) {
|
|
79
|
+
const patterns = source === undefined ? entry.raw : matchedRulePatterns(entry, source);
|
|
80
|
+
return patterns.includes("*") ? tool : `${tool}(${patterns.join(", ")})`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Deny (spec R6) outranks ask, and both are evaluated per resolved path.
|
|
84
|
+
* Returns a denial verdict when a deny glob matches; otherwise records the
|
|
85
|
+
* matching ask source on `state` and returns undefined. Ask never grants on
|
|
86
|
+
* its own: the caller's allow checks still run, so an ungranted path stays
|
|
87
|
+
* denied and an ask on an ungranted call never becomes an approval prompt.
|
|
88
|
+
*/
|
|
89
|
+
function applyPathRules(tool, rel, state) {
|
|
90
|
+
const naxOwned = naxOwnedWriteRefusal(tool, rel, ownedWriteExemption, naxOptIns);
|
|
91
|
+
if (naxOwned !== undefined)
|
|
92
|
+
return deny(`${tool} may not modify ${naxOwned}`);
|
|
93
|
+
const denyEntry = denyBy.get(tool);
|
|
94
|
+
const askEntry = askBy.get(tool);
|
|
95
|
+
if (denyEntry !== undefined && (denyEntry.unconditional || matchesAny(denyEntry.matchers, rel))) {
|
|
96
|
+
return deny(`${tool} path "${rel}" is denied for this stage`);
|
|
97
|
+
}
|
|
98
|
+
if (askEntry !== undefined && (askEntry.unconditional || matchesAny(askEntry.matchers, rel))) {
|
|
99
|
+
state.ask = ruleExpr(tool, askEntry, askEntry.unconditional ? "*" : matchedGlobSource(askEntry.matchers, rel));
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* RunCommand's `Exec` branch, checked entirely here and never falling through
|
|
105
|
+
* to verbField/pathFields: there is no "command" field on this shape for
|
|
106
|
+
* verbField to read. `validateArgv` runs FIRST -- before this grant's patterns
|
|
107
|
+
* are even consulted -- so a malformed token can never be admitted by a `*`
|
|
108
|
+
* grant that would otherwise wave anything through unchecked. Returns
|
|
109
|
+
* undefined when this is not an argv call, so the caller tries the next branch.
|
|
110
|
+
*/
|
|
111
|
+
function argvBranch(tool, scope, input, grant) {
|
|
112
|
+
if (scope.argvField === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
const rawArgv = input[scope.argvField];
|
|
115
|
+
if (rawArgv === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
const invalid = validateArgv(rawArgv);
|
|
118
|
+
if (invalid !== undefined)
|
|
119
|
+
return deny(invalid);
|
|
120
|
+
const argv = rawArgv;
|
|
121
|
+
const denyEntry = denyBy.get(tool);
|
|
122
|
+
if (denyEntry !== undefined && (denyEntry.unconditional || matchesArgvGrant(denyEntry.argvPatterns, argv))) {
|
|
123
|
+
return deny(`${tool} is denied for argv "${argv.join(" ")}" for this stage`);
|
|
124
|
+
}
|
|
125
|
+
if (!grant.unconditional && !matchesArgvGrant(grant.argvPatterns, argv)) {
|
|
126
|
+
// Name the granted forms, not just the refusal. A bare "no" is what
|
|
127
|
+
// produced the defect this branch exists to fix: denied with no legal
|
|
128
|
+
// alternative named, the model deleted the requirement instead of
|
|
129
|
+
// installing it. An unconditional grant never enters this branch (see
|
|
130
|
+
// the guard above), so `raw` cannot contain the bare "*" element --
|
|
131
|
+
// entries *containing* "*" (`bun add*`) are exactly what we want to name.
|
|
132
|
+
const granted = grant.raw.join(", ");
|
|
133
|
+
const alternatives = granted === "" ? "no argv forms are granted for this stage" : `granted forms: ${granted}`;
|
|
134
|
+
return deny(`${tool} is not granted for argv "${argv.join(" ")}" -- ${alternatives}`);
|
|
135
|
+
}
|
|
136
|
+
const askEntry = askBy.get(tool);
|
|
137
|
+
if (askEntry !== undefined && (askEntry.unconditional || matchesArgvGrant(askEntry.argvPatterns, argv))) {
|
|
138
|
+
return askVerdict([], ruleExpr(tool, askEntry, askEntry.unconditional ? "*" : matchedArgvSource(askEntry, argv)));
|
|
139
|
+
}
|
|
140
|
+
return { allowed: true, resolvedPaths: [] };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Verb gating: the tool's own allowedVerbs bound what config can grant, so a
|
|
144
|
+
* "*" grant can never reach a mutating subcommand. A deny/ask rule names a
|
|
145
|
+
* verb directly here -- unlike the allow path (`pathMatchers`), these are not
|
|
146
|
+
* filtered through `allowedVerbs`, so a deny for a verb the tool does not even
|
|
147
|
+
* allow still reads as the rule's denial, not a generic "not a permitted
|
|
148
|
+
* subcommand". Returns undefined once the gate passes (or is absent), so the
|
|
149
|
+
* caller can check paths next.
|
|
150
|
+
*/
|
|
151
|
+
function verbBranch(tool, scope, input, grant, state) {
|
|
152
|
+
if (scope.verbField === undefined)
|
|
153
|
+
return undefined;
|
|
154
|
+
const verb = input[scope.verbField];
|
|
155
|
+
if (typeof verb !== "string")
|
|
156
|
+
return deny(`"${scope.verbField}" must be a string`);
|
|
157
|
+
const denyEntry = denyBy.get(tool);
|
|
158
|
+
if (denyEntry !== undefined && (denyEntry.unconditional || denyEntry.raw.includes(verb))) {
|
|
159
|
+
return deny(`${tool} is denied the "${verb}" subcommand for this stage`);
|
|
160
|
+
}
|
|
161
|
+
// Name what the stage can actually use, not merely what the tool allows
|
|
162
|
+
// (nax#1971). `allowedVerbs` alone would advertise a verb a narrower grant
|
|
163
|
+
// refuses one line below -- sending the model straight back into a denial,
|
|
164
|
+
// which is the defect #1937 exists to fix.
|
|
165
|
+
const usableVerbs = scope.allowedVerbs === undefined
|
|
166
|
+
? []
|
|
167
|
+
: grant.unconditional
|
|
168
|
+
? [...scope.allowedVerbs]
|
|
169
|
+
: scope.allowedVerbs.filter((v) => grant.raw.includes(v));
|
|
170
|
+
const permitted = usableVerbs.length === 0 ? "no subcommands are permitted for this stage" : `permitted: ${usableVerbs.join(", ")}`;
|
|
171
|
+
// See verb-denial-argv-hint.ts (run-2026-09-14T05-55-54-734Z, Shape B).
|
|
172
|
+
const argvHint = argvShellHint(scope.verbField, scope.argvField, compiled.get(EXEC_TOOL_NAME)?.raw ?? []);
|
|
173
|
+
if (scope.allowedVerbs !== undefined && !scope.allowedVerbs.includes(verb)) {
|
|
174
|
+
return deny(`"${verb}" is not a permitted ${tool} subcommand -- ${permitted}${argvHint}`);
|
|
175
|
+
}
|
|
176
|
+
if (!grant.unconditional && !grant.raw.includes(verb)) {
|
|
177
|
+
return deny(`${tool} is not granted the "${verb}" subcommand for this stage -- ${permitted}`);
|
|
178
|
+
}
|
|
179
|
+
const askEntry = askBy.get(tool);
|
|
180
|
+
if (askEntry !== undefined && (askEntry.unconditional || askEntry.raw.includes(verb))) {
|
|
181
|
+
state.ask = ruleExpr(tool, askEntry, askEntry.unconditional ? "*" : verb);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The path branch's collaborators, by reference (see `PathsBranchContext`):
|
|
187
|
+
* the nested helpers below stay authoritative, and the sibling reads them at
|
|
188
|
+
* call time — `pathsBranch` itself lives in `./policy-paths-branch`.
|
|
189
|
+
*/
|
|
190
|
+
const pathsBranchContext = { resolvedRoot, deny, askVerdict, applyPathRules };
|
|
191
|
+
return {
|
|
192
|
+
root: resolvedRoot,
|
|
193
|
+
grantedTools() {
|
|
194
|
+
// A tool with an UNCONDITIONAL deny never reaches a caller; a scoped
|
|
195
|
+
// (pattern) deny leaves it advertised, because some calls still pass.
|
|
196
|
+
return [...compiled.keys()].filter((t) => denyBy.get(t)?.unconditional !== true);
|
|
197
|
+
},
|
|
198
|
+
check(tool, scope, input) {
|
|
199
|
+
const grant = compiled.get(tool);
|
|
200
|
+
if (grant === undefined)
|
|
201
|
+
return deny(`tool "${tool}" is not permitted for this stage`);
|
|
202
|
+
// Unconditional deny is final and outranks every other gate.
|
|
203
|
+
const denyEntry = denyBy.get(tool);
|
|
204
|
+
if (denyEntry?.unconditional === true) {
|
|
205
|
+
return deny(`tool "${tool}" is denied for this stage by rule ${ruleExpr(tool, denyEntry)}`);
|
|
206
|
+
}
|
|
207
|
+
const askEntry = askBy.get(tool);
|
|
208
|
+
if (askEntry?.unconditional === true && isFieldlessScope(scope)) {
|
|
209
|
+
return askVerdict([], ruleExpr(tool, askEntry));
|
|
210
|
+
}
|
|
211
|
+
const state = {};
|
|
212
|
+
return (commandBranch({
|
|
213
|
+
tool,
|
|
214
|
+
scope,
|
|
215
|
+
input,
|
|
216
|
+
grant,
|
|
217
|
+
bashApproval,
|
|
218
|
+
rawBashRefusal: options?.rawBashRefusal,
|
|
219
|
+
sandboxWrapped: options?.sandboxWrapped,
|
|
220
|
+
resolvedRoot,
|
|
221
|
+
denyBy,
|
|
222
|
+
askBy,
|
|
223
|
+
resolvePath: (candidate, cwd) => resolveWithin(resolvedRoot, resolve(cwd, candidate)),
|
|
224
|
+
deny,
|
|
225
|
+
askVerdict,
|
|
226
|
+
}) ??
|
|
227
|
+
argvBranch(tool, scope, input, grant) ??
|
|
228
|
+
verbBranch(tool, scope, input, grant, state) ??
|
|
229
|
+
pathsBranch({ ctx: pathsBranchContext, tool, scope, input, grant, state }));
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths the HOST owns, which the coding tools and the sandbox must keep the
|
|
3
|
+
* agent away from (S1 spec section 4.2, port 6). Data only: nax builds it
|
|
4
|
+
* from its own path definitions (`naxProtectedPaths`) once per dispatch; an
|
|
5
|
+
* embedder supplies its own.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProtectedPathsPolicy {
|
|
8
|
+
/** Long-form pathspecs the Git tool appends to its default (no-path) view (nax#2007). */
|
|
9
|
+
readonly gitExcludePathspecs: readonly string[];
|
|
10
|
+
/** Gitignore patterns GitCommit refuses to stage. */
|
|
11
|
+
readonly gitIgnorePatterns: readonly string[];
|
|
12
|
+
/** Project-relative directory whose top-level entries the sandbox policy lists. */
|
|
13
|
+
readonly projectStateDir: string;
|
|
14
|
+
/** Directory whose `credentials*` files the sandbox denies. */
|
|
15
|
+
readonly credentialDir: string;
|
|
16
|
+
/** File the sandbox denies writes to: the trust store deciding whether repository code runs. */
|
|
17
|
+
readonly trustStoreFile: string;
|
|
18
|
+
}
|
|
19
|
+
/** The Git tool's default excludes; none when the session carries no policy. */
|
|
20
|
+
export declare function gitExcludePathspecsOf(ctx: {
|
|
21
|
+
readonly protectedPaths?: ProtectedPathsPolicy;
|
|
22
|
+
}): readonly string[];
|
|
23
|
+
/** GitCommit's ignore patterns; none when the session carries no policy. */
|
|
24
|
+
export declare function gitIgnorePatternsOf(ctx: {
|
|
25
|
+
readonly protectedPaths?: ProtectedPathsPolicy;
|
|
26
|
+
}): readonly string[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The Git tool's default excludes; none when the session carries no policy. */
|
|
2
|
+
export function gitExcludePathspecsOf(ctx) {
|
|
3
|
+
return ctx.protectedPaths?.gitExcludePathspecs ?? [];
|
|
4
|
+
}
|
|
5
|
+
/** GitCommit's ignore patterns; none when the session carries no policy. */
|
|
6
|
+
export function gitIgnorePatternsOf(ctx) {
|
|
7
|
+
return ctx.protectedPaths?.gitIgnorePatterns ?? [];
|
|
8
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderTool } from "./provider-types.ts";
|
|
2
|
+
import type { CodingTool } from "./registry.ts";
|
|
3
|
+
export declare function namespacedToolName(providerId: string, localName: string): string;
|
|
4
|
+
export declare function adaptProviderTool(providerId: string, tool: ProviderTool): CodingTool;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespacing and adaptation of provider tools into plain CodingTools.
|
|
3
|
+
*
|
|
4
|
+
* The namespaced name is NEVER parsed back apart. That is what lets a local
|
|
5
|
+
* name contain `__` without escaping, and it is why the ledger carries an
|
|
6
|
+
* explicit `provider` field rather than expecting a consumer to string-split.
|
|
7
|
+
*/
|
|
8
|
+
import { NaxError } from "#src/infra/index";
|
|
9
|
+
import { validateProviderId } from "./provider-types.js";
|
|
10
|
+
import { RESERVED_TOOL_NAMES } from "./registry.js";
|
|
11
|
+
export function namespacedToolName(providerId, localName) {
|
|
12
|
+
validateProviderId(providerId);
|
|
13
|
+
if (localName.length === 0) {
|
|
14
|
+
throw new NaxError(`provider ${providerId} advertised a tool with an empty name`, "PROVIDER_TOOL_NAME_EMPTY");
|
|
15
|
+
}
|
|
16
|
+
const name = `${providerId}__${localName}`;
|
|
17
|
+
if (RESERVED_TOOL_NAMES.includes(name)) {
|
|
18
|
+
throw new NaxError(`provider tool ${name} collides with a built-in`, "PROVIDER_TOOL_NAME_RESERVED");
|
|
19
|
+
}
|
|
20
|
+
return name;
|
|
21
|
+
}
|
|
22
|
+
export function adaptProviderTool(providerId, tool) {
|
|
23
|
+
return {
|
|
24
|
+
name: namespacedToolName(providerId, tool.localName),
|
|
25
|
+
description: tool.description,
|
|
26
|
+
inputSchema: tool.inputSchema,
|
|
27
|
+
// No path fields and no verb field: the grant lookup is the whole gate,
|
|
28
|
+
// which is the honest expression for a tool whose arguments are not paths.
|
|
29
|
+
scope: { pathFields: [] },
|
|
30
|
+
run: (input, ctx) => tool.run(input, ctx),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ProviderGrantEntry } from "./provider-grants.ts";
|
|
2
|
+
import { type ToolProvider } from "./provider-types.ts";
|
|
3
|
+
import type { CodingTool } from "./registry.ts";
|
|
4
|
+
import type { ToolGrant } from "./types.ts";
|
|
5
|
+
export interface ResolvedProviderTools {
|
|
6
|
+
readonly tools: readonly CodingTool[];
|
|
7
|
+
readonly grants: readonly ToolGrant[];
|
|
8
|
+
readonly failures: readonly {
|
|
9
|
+
providerId: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}[];
|
|
12
|
+
/**
|
|
13
|
+
* Advertised tool name -> owning provider id, carried explicitly so the
|
|
14
|
+
* ledger never has to parse the `<id>__<local>` name apart (a naming
|
|
15
|
+
* convention change would otherwise break telemetry silently).
|
|
16
|
+
*/
|
|
17
|
+
readonly providerIdByTool: ReadonlyMap<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* (providerId, localNames) pairs for the tools that survived sanitisation,
|
|
20
|
+
* the lock and `allowedTools`. Exposed because `Mcp(server:tool)` rules must
|
|
21
|
+
* be matched against the LOCAL name, and `<id>__<local>` is never parsed
|
|
22
|
+
* back apart (src/tools/provider-adapt.ts's header).
|
|
23
|
+
*/
|
|
24
|
+
readonly entries: readonly ProviderGrantEntry[];
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveProviderTools(providers: readonly ToolProvider[], stage: string, workdir: string, options?: {
|
|
27
|
+
/**
|
|
28
|
+
* Narrows which discovered tools are advertised at all. Applied HERE, not
|
|
29
|
+
* after the fact: a tool a scoped stage did not admit must never be
|
|
30
|
+
* adapted, advertised or granted (spec US-006).
|
|
31
|
+
*/
|
|
32
|
+
readonly admits?: (providerId: string, localName: string) => boolean;
|
|
33
|
+
}): Promise<ResolvedProviderTools>;
|
|
34
|
+
/**
|
|
35
|
+
* Bytes a tool list costs the prompt, paid on EVERY hop whether or not any
|
|
36
|
+
* tool is called. This is the per-hop tax that appears in no ledger today and
|
|
37
|
+
* which nax#1991's context-burn report needs.
|
|
38
|
+
*/
|
|
39
|
+
export declare function advertisedSchemaBytes(tools: readonly CodingTool[]): number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-hop resolution of provider tools.
|
|
3
|
+
*
|
|
4
|
+
* A provider whose `tools()` rejects is DROPPED, not fatal: a broken provider
|
|
5
|
+
* must never wedge a run. The failure is logged by the caller, which owns the
|
|
6
|
+
* logger; this module stays pure so it can be tested without one.
|
|
7
|
+
*/
|
|
8
|
+
import { adaptProviderTool } from "./provider-adapt.js";
|
|
9
|
+
import { expandProviderGrants } from "./provider-grants.js";
|
|
10
|
+
import { sanitizeProviderTools } from "./provider-sanitize.js";
|
|
11
|
+
import { providerAttachesTo } from "./provider-types.js";
|
|
12
|
+
export async function resolveProviderTools(providers, stage, workdir, options) {
|
|
13
|
+
const tools = [];
|
|
14
|
+
const entries = [];
|
|
15
|
+
const failures = [];
|
|
16
|
+
const providerIdByTool = new Map();
|
|
17
|
+
for (const provider of providers) {
|
|
18
|
+
if (!providerAttachesTo(provider, stage))
|
|
19
|
+
continue;
|
|
20
|
+
try {
|
|
21
|
+
const sanitized = sanitizeProviderTools(provider.kind, await provider.tools(workdir));
|
|
22
|
+
const localNames = [];
|
|
23
|
+
for (const tool of sanitized) {
|
|
24
|
+
if (options?.admits !== undefined && !options.admits(provider.id, tool.localName))
|
|
25
|
+
continue;
|
|
26
|
+
const adapted = adaptProviderTool(provider.id, tool);
|
|
27
|
+
tools.push(adapted);
|
|
28
|
+
providerIdByTool.set(adapted.name, provider.id);
|
|
29
|
+
localNames.push(tool.localName);
|
|
30
|
+
}
|
|
31
|
+
entries.push({ providerId: provider.id, localNames });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
failures.push({ providerId: provider.id, reason: String(error) });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { tools, grants: expandProviderGrants(entries), failures, providerIdByTool, entries };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bytes a tool list costs the prompt, paid on EVERY hop whether or not any
|
|
41
|
+
* tool is called. This is the per-hop tax that appears in no ledger today and
|
|
42
|
+
* which nax#1991's context-burn report needs.
|
|
43
|
+
*/
|
|
44
|
+
export function advertisedSchemaBytes(tools) {
|
|
45
|
+
let total = 0;
|
|
46
|
+
for (const tool of tools) {
|
|
47
|
+
total += Buffer.byteLength(tool.description, "utf8") + Buffer.byteLength(JSON.stringify(tool.inputSchema), "utf8");
|
|
48
|
+
}
|
|
49
|
+
return total;
|
|
50
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ToolGrant } from "./types.ts";
|
|
2
|
+
export interface ProviderGrantEntry {
|
|
3
|
+
readonly providerId: string;
|
|
4
|
+
readonly localNames: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function expandProviderGrants(entries: readonly ProviderGrantEntry[]): readonly ToolGrant[];
|
|
7
|
+
/**
|
|
8
|
+
* The pseudo-tool name `Mcp(...)` parses to. SURFACE SYNTAX ONLY: it must be
|
|
9
|
+
* partitioned out and expanded before compilation (see this file's header).
|
|
10
|
+
*/
|
|
11
|
+
export declare const MCP_RULE_TOOL = "Mcp";
|
|
12
|
+
/**
|
|
13
|
+
* Split `Mcp(...)` rules out of a parsed rule list.
|
|
14
|
+
*
|
|
15
|
+
* Patterns MERGE across entries rather than overwriting, unlike the allow
|
|
16
|
+
* compiler's per-tool last-write-wins: two `Mcp(...)` expressions in one list
|
|
17
|
+
* are two servers a human named, and dropping the earlier one would silently
|
|
18
|
+
* withdraw a grant that is written in the config.
|
|
19
|
+
*/
|
|
20
|
+
export declare function partitionMcpRules(grants: readonly ToolGrant[]): {
|
|
21
|
+
readonly grants: readonly ToolGrant[];
|
|
22
|
+
readonly mcpPatterns: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
/** Does an `Mcp` pattern list admit `<providerId>__<localName>`?
|
|
25
|
+
* `*` = every server; `server` = every tool of that server; `server:tool` =
|
|
26
|
+
* one tool; `server:*` = every tool of that server, written explicitly. */
|
|
27
|
+
export declare function mcpRuleAdmits(patterns: readonly string[], providerId: string, localName: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Expand `Mcp` patterns against the tools a provider actually advertised.
|
|
30
|
+
*
|
|
31
|
+
* Keyed on the post-lock, post-`allowedTools` list the caller passes in, so a
|
|
32
|
+
* rule can only ever narrow what the provider layer already admitted — and the
|
|
33
|
+
* result is concrete `<id>__<tool>` grants, which is the only shape
|
|
34
|
+
* `compileToolPolicy` can key on.
|
|
35
|
+
*/
|
|
36
|
+
export declare function expandMcpRuleGrants(patterns: readonly string[], entries: readonly ProviderGrantEntry[]): readonly ToolGrant[];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expansion of provider grants into the only shape the policy can use.
|
|
3
|
+
*
|
|
4
|
+
* `compileToolPolicy` keys grants by exact tool name, and `advertised()` tests
|
|
5
|
+
* `granted.has(<namespaced name>)`. Any grant-expression sugar a consuming
|
|
6
|
+
* feature defines — `Mcp(server)`, `Mcp(server:tool)` — is SURFACE SYNTAX FOR
|
|
7
|
+
* HUMANS and must be expanded here before compilation. A grant that survives in
|
|
8
|
+
* parsed form compiles to the key `"Mcp"`, matches no advertised name, and
|
|
9
|
+
* denies every call while every parser unit test still passes.
|
|
10
|
+
*/
|
|
11
|
+
import { namespacedToolName } from "./provider-adapt.js";
|
|
12
|
+
export function expandProviderGrants(entries) {
|
|
13
|
+
const grants = [];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
for (const localName of entry.localNames) {
|
|
16
|
+
grants.push({ tool: namespacedToolName(entry.providerId, localName), patterns: ["*"] });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return grants;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The pseudo-tool name `Mcp(...)` parses to. SURFACE SYNTAX ONLY: it must be
|
|
23
|
+
* partitioned out and expanded before compilation (see this file's header).
|
|
24
|
+
*/
|
|
25
|
+
export const MCP_RULE_TOOL = "Mcp";
|
|
26
|
+
/**
|
|
27
|
+
* Split `Mcp(...)` rules out of a parsed rule list.
|
|
28
|
+
*
|
|
29
|
+
* Patterns MERGE across entries rather than overwriting, unlike the allow
|
|
30
|
+
* compiler's per-tool last-write-wins: two `Mcp(...)` expressions in one list
|
|
31
|
+
* are two servers a human named, and dropping the earlier one would silently
|
|
32
|
+
* withdraw a grant that is written in the config.
|
|
33
|
+
*/
|
|
34
|
+
export function partitionMcpRules(grants) {
|
|
35
|
+
const kept = [];
|
|
36
|
+
const mcpPatterns = [];
|
|
37
|
+
for (const grant of grants) {
|
|
38
|
+
if (grant.tool === MCP_RULE_TOOL)
|
|
39
|
+
mcpPatterns.push(...grant.patterns);
|
|
40
|
+
else
|
|
41
|
+
kept.push(grant);
|
|
42
|
+
}
|
|
43
|
+
return { grants: kept, mcpPatterns };
|
|
44
|
+
}
|
|
45
|
+
/** Does an `Mcp` pattern list admit `<providerId>__<localName>`?
|
|
46
|
+
* `*` = every server; `server` = every tool of that server; `server:tool` =
|
|
47
|
+
* one tool; `server:*` = every tool of that server, written explicitly. */
|
|
48
|
+
export function mcpRuleAdmits(patterns, providerId, localName) {
|
|
49
|
+
return patterns.some((pattern) => {
|
|
50
|
+
if (pattern === "*")
|
|
51
|
+
return true;
|
|
52
|
+
const colon = pattern.indexOf(":");
|
|
53
|
+
if (colon === -1)
|
|
54
|
+
return pattern === providerId;
|
|
55
|
+
return pattern.slice(0, colon) === providerId && ["*", localName].includes(pattern.slice(colon + 1));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Expand `Mcp` patterns against the tools a provider actually advertised.
|
|
60
|
+
*
|
|
61
|
+
* Keyed on the post-lock, post-`allowedTools` list the caller passes in, so a
|
|
62
|
+
* rule can only ever narrow what the provider layer already admitted — and the
|
|
63
|
+
* result is concrete `<id>__<tool>` grants, which is the only shape
|
|
64
|
+
* `compileToolPolicy` can key on.
|
|
65
|
+
*/
|
|
66
|
+
export function expandMcpRuleGrants(patterns, entries) {
|
|
67
|
+
const admitted = entries.map((entry) => ({
|
|
68
|
+
providerId: entry.providerId,
|
|
69
|
+
localNames: entry.localNames.filter((localName) => mcpRuleAdmits(patterns, entry.providerId, localName)),
|
|
70
|
+
}));
|
|
71
|
+
return expandProviderGrants(admitted.filter((entry) => entry.localNames.length > 0));
|
|
72
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds on what a `discovered` provider may put into the model's tool list.
|
|
3
|
+
*
|
|
4
|
+
* A discovered tool's description and schema come from an external process and
|
|
5
|
+
* go straight into the prompt, which makes the description a prompt-injection
|
|
6
|
+
* surface and the schema a per-hop context tax paid whether or not the tool is
|
|
7
|
+
* ever called. A `static` provider's schema is authored in this repo, so
|
|
8
|
+
* sanitising it would be theatre — hence the kind check rather than a blanket
|
|
9
|
+
* pass.
|
|
10
|
+
*
|
|
11
|
+
* Truncate rather than drop: an over-long description degrades into a shorter
|
|
12
|
+
* one, where dropping would remove a working tool over a cosmetic problem. A
|
|
13
|
+
* malformed or oversized SCHEMA is different — it cannot be safely truncated,
|
|
14
|
+
* so that one tool is skipped and its siblings survive.
|
|
15
|
+
*/
|
|
16
|
+
import type { ProviderKind, ProviderTool } from "./provider-types.ts";
|
|
17
|
+
export declare const MAX_PROVIDER_DESCRIPTION_BYTES = 2000;
|
|
18
|
+
export declare const MAX_PROVIDER_SCHEMA_BYTES = 20000;
|
|
19
|
+
export declare function sanitizeProviderTools(kind: ProviderKind, tools: readonly ProviderTool[]): readonly ProviderTool[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const MAX_PROVIDER_DESCRIPTION_BYTES = 2_000;
|
|
2
|
+
export const MAX_PROVIDER_SCHEMA_BYTES = 20_000;
|
|
3
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching control bytes is the point of this function
|
|
4
|
+
const CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
|
|
5
|
+
function strip(value) {
|
|
6
|
+
return value.replace(CONTROL_CHARS, "");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Truncate to a byte ceiling without splitting a code point.
|
|
10
|
+
*
|
|
11
|
+
* `String.prototype.slice` counts UTF-16 code units, so a multi-byte code point
|
|
12
|
+
* straddling the boundary is cut in half and becomes a replacement character.
|
|
13
|
+
* Iterating code points and stopping at the ceiling keeps the result valid.
|
|
14
|
+
*/
|
|
15
|
+
function truncateToBytes(value, maxBytes) {
|
|
16
|
+
if (Buffer.byteLength(value, "utf8") <= maxBytes)
|
|
17
|
+
return value;
|
|
18
|
+
let out = "";
|
|
19
|
+
let bytes = 0;
|
|
20
|
+
for (const char of value) {
|
|
21
|
+
const charBytes = Buffer.byteLength(char, "utf8");
|
|
22
|
+
if (bytes + charBytes > maxBytes)
|
|
23
|
+
break;
|
|
24
|
+
out += char;
|
|
25
|
+
bytes += charBytes;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export function sanitizeProviderTools(kind, tools) {
|
|
30
|
+
if (kind === "static")
|
|
31
|
+
return tools;
|
|
32
|
+
const out = [];
|
|
33
|
+
let schemaBytes = 0;
|
|
34
|
+
for (const tool of tools) {
|
|
35
|
+
const schema = tool.inputSchema;
|
|
36
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema))
|
|
37
|
+
continue;
|
|
38
|
+
// US-005 caps the TOTAL schema bytes one provider may put into the prompt,
|
|
39
|
+
// not just each schema: many small tools still add up to an unbounded hop
|
|
40
|
+
// tax. A single oversized schema fails the same test with a zero running
|
|
41
|
+
// total, which preserves the per-tool skip this file already had.
|
|
42
|
+
const toolBytes = Buffer.byteLength(JSON.stringify(schema), "utf8");
|
|
43
|
+
if (schemaBytes + toolBytes > MAX_PROVIDER_SCHEMA_BYTES)
|
|
44
|
+
continue;
|
|
45
|
+
schemaBytes += toolBytes;
|
|
46
|
+
out.push({
|
|
47
|
+
...tool,
|
|
48
|
+
localName: strip(tool.localName),
|
|
49
|
+
description: truncateToBytes(strip(tool.description), MAX_PROVIDER_DESCRIPTION_BYTES),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocabulary for tools supplied by a provider rather than compiled into
|
|
3
|
+
* `CodingToolName`.
|
|
4
|
+
*
|
|
5
|
+
* Providers split by ONE axis that matters: who authored the tool's schema.
|
|
6
|
+
* A `static` provider's schema is written in this repo and reviewed in this
|
|
7
|
+
* repo's PRs, making it exactly as trustworthy as Read's. A `discovered`
|
|
8
|
+
* provider's schema arrives from an external process at runtime, which makes
|
|
9
|
+
* its description a prompt-injection surface and its schema an unbounded
|
|
10
|
+
* context cost. Sanitisation and pinning attach to the KIND, so a trusted
|
|
11
|
+
* provider never inherits ceremony it does not need.
|
|
12
|
+
*/
|
|
13
|
+
import type { JSONSchema } from "#src/session/tool-descriptor";
|
|
14
|
+
import type { ToolResult, ToolRunContext } from "./registry.ts";
|
|
15
|
+
export type ProviderKind = "static" | "discovered";
|
|
16
|
+
export interface ProviderTool {
|
|
17
|
+
/** Unqualified, as the provider knows it. Namespaced by `provider-adapt`. */
|
|
18
|
+
readonly localName: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly inputSchema: JSONSchema;
|
|
21
|
+
run(input: Record<string, unknown>, ctx: ToolRunContext): Promise<ToolResult>;
|
|
22
|
+
}
|
|
23
|
+
export interface ToolProvider {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly kind: ProviderKind;
|
|
26
|
+
/** Stage names this provider attaches to, or "*" for every stage. */
|
|
27
|
+
readonly stages: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* `workdir` is the HOP'S PERMITTED ROOT, never the runtime's workdir. A
|
|
30
|
+
* provider may legitimately expose different tools per working root, and a
|
|
31
|
+
* run executes stories in parallel worktrees.
|
|
32
|
+
*/
|
|
33
|
+
tools(workdir: string): Promise<readonly ProviderTool[]>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* No `__`: that sequence is the namespace separator, and an id containing it
|
|
37
|
+
* would make `<id>__<local>` ambiguous to a human reading a ledger row.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PROVIDER_ID_RE: RegExp;
|
|
40
|
+
export declare function validateProviderId(id: string): void;
|
|
41
|
+
export declare function providerAttachesTo(provider: Pick<ToolProvider, "stages">, stage: string): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocabulary for tools supplied by a provider rather than compiled into
|
|
3
|
+
* `CodingToolName`.
|
|
4
|
+
*
|
|
5
|
+
* Providers split by ONE axis that matters: who authored the tool's schema.
|
|
6
|
+
* A `static` provider's schema is written in this repo and reviewed in this
|
|
7
|
+
* repo's PRs, making it exactly as trustworthy as Read's. A `discovered`
|
|
8
|
+
* provider's schema arrives from an external process at runtime, which makes
|
|
9
|
+
* its description a prompt-injection surface and its schema an unbounded
|
|
10
|
+
* context cost. Sanitisation and pinning attach to the KIND, so a trusted
|
|
11
|
+
* provider never inherits ceremony it does not need.
|
|
12
|
+
*/
|
|
13
|
+
import { NaxError } from "#src/infra/index";
|
|
14
|
+
/**
|
|
15
|
+
* No `__`: that sequence is the namespace separator, and an id containing it
|
|
16
|
+
* would make `<id>__<local>` ambiguous to a human reading a ledger row.
|
|
17
|
+
*/
|
|
18
|
+
export const PROVIDER_ID_RE = /^[a-z0-9][a-z0-9_-]*$/;
|
|
19
|
+
export function validateProviderId(id) {
|
|
20
|
+
if (!PROVIDER_ID_RE.test(id) || id.includes("__")) {
|
|
21
|
+
throw new NaxError(`provider id ${JSON.stringify(id)} must match ${PROVIDER_ID_RE}`, "PROVIDER_ID_INVALID");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function providerAttachesTo(provider, stage) {
|
|
25
|
+
return provider.stages.some((s) => s === "*" || s === stage);
|
|
26
|
+
}
|