@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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native session adapter: openSession / sendTurn / closeSession over
|
|
3
|
+
* nax-ai, no subprocess. openSession/closeSession are transcript-file
|
|
4
|
+
* bookkeeping, and sendTurn maps the native turn loop (session/turn-loop.ts)
|
|
5
|
+
* over complete() — nax owns the conversation because nax-ai's client is
|
|
6
|
+
* stateless (ADR-027 section 10, ADR-028).
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { priceCall } from "#src/cost/core/index";
|
|
10
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
11
|
+
import { SessionTurnError, } from "#src/session/session-types";
|
|
12
|
+
import { createTurnDeadline } from "#src/session/turn-deadline";
|
|
13
|
+
import { _adapterDeps, authFields, isProtocolStreamError } from "./adapter-deps.js";
|
|
14
|
+
import { getNativeClient } from "./client.js";
|
|
15
|
+
import { toAdapterFailure } from "./errors.js";
|
|
16
|
+
import { buildRateCard, NATIVE_AGENT, parseNativeModel, resolveContextWindow, toThinkingLevel } from "./models.js";
|
|
17
|
+
import { closeNativeSession, markNativeTurnOutcome, nativeSessionCompaction, nativeSessionSpinBreaker, nativeSessionStreamHooks, nativeSessionTimeouts, nativeSessionTranscriptOwners, nativeSessionTransportRetry, openNativeSession, } from "./session/session.js";
|
|
18
|
+
import { buildNativeStreamEvent } from "./session/turn-events.js";
|
|
19
|
+
import { runNativeTurn } from "./session/turn-loop.js";
|
|
20
|
+
import { readNativeTurnFailureUsage } from "./session/turn-types.js";
|
|
21
|
+
import { nativeSessionId } from "./session-affinity.js";
|
|
22
|
+
/**
|
|
23
|
+
* Fallback whole-turn budget when a session's timeout entry is missing.
|
|
24
|
+
*
|
|
25
|
+
* Matches `execution.sessionTimeoutSeconds`' own default. The turn MUST stay
|
|
26
|
+
* bounded: an absent entry previously degraded to an unbounded deadline with
|
|
27
|
+
* no per-call timer either, silently removing the guard this module exists to
|
|
28
|
+
* apply. Bounded-and-logged beats unbounded-and-quiet.
|
|
29
|
+
*/
|
|
30
|
+
const FALLBACK_TURN_TIMEOUT_SECONDS = 3600;
|
|
31
|
+
function summaryPrompt(previousSummary) {
|
|
32
|
+
const base = "Summarize the conversation above so it can be dropped from context. " +
|
|
33
|
+
"Record what was attempted, what was rejected and why, any decisions that still bind, " +
|
|
34
|
+
"and list the files read and the files modified. Be specific: this summary is the only " +
|
|
35
|
+
"memory of this work that survives.";
|
|
36
|
+
if (previousSummary === undefined)
|
|
37
|
+
return base;
|
|
38
|
+
return (`${base}\n\nAn earlier summary of still-older history follows. Merge it into your summary ` +
|
|
39
|
+
`rather than repeating or discarding it:\n\n${previousSummary}`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* US-003: the plugin-contributed loop handlers a turn carries, in the shape
|
|
43
|
+
* `runNativeTurn` takes them in. Built here rather than inline in `sendTurn`
|
|
44
|
+
* because those two conditional spreads are what pushed that method past its
|
|
45
|
+
* recorded cognitive complexity; each field is omitted entirely when the
|
|
46
|
+
* caller supplied none, so a session that loaded no `loop-handlers` plugin
|
|
47
|
+
* carries neither key.
|
|
48
|
+
*/
|
|
49
|
+
function loopHandlerDeps(opts) {
|
|
50
|
+
return {
|
|
51
|
+
...(opts.loopHandlers !== undefined ? { loopHandlers: opts.loopHandlers } : {}),
|
|
52
|
+
...(opts.loopHandlerContext !== undefined ? { loopHandlerContext: opts.loopHandlerContext } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export class NativeSessionAdapter {
|
|
56
|
+
catalogOverrides;
|
|
57
|
+
constructor(catalogOverrides = []) {
|
|
58
|
+
this.catalogOverrides = catalogOverrides;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Can this agent authenticate to at least one provider?
|
|
62
|
+
*
|
|
63
|
+
* Deliberately not "is the provider this run needs satisfied": this method
|
|
64
|
+
* takes no provider, and it cannot get one. The registry receives the
|
|
65
|
+
* manager's config slice, and agentManagerConfigSelector excludes
|
|
66
|
+
* config.models by design (ADR-019). Probing every provider for a specific
|
|
67
|
+
* answer is not an alternative either — pi's resolve() may execute commands.
|
|
68
|
+
*
|
|
69
|
+
* So this prunes exactly one case: nothing stored anywhere and nothing
|
|
70
|
+
* ambient. That is the real failure — a user who has never run
|
|
71
|
+
* `nax auth login` and has no provider environment variables — and every
|
|
72
|
+
* native call is going to fail anyway. A wrong-provider credential still
|
|
73
|
+
* surfaces per request, through the typed mapping from ProtocolError.kind
|
|
74
|
+
* "auth" to availability / fail-auth.
|
|
75
|
+
*
|
|
76
|
+
* US-004: with `auth.source: "exec"` it answers true without asking anything.
|
|
77
|
+
* A helper's providers cannot be listed, so an empty credential file says
|
|
78
|
+
* nothing about whether this run can authenticate — and spawning the helper
|
|
79
|
+
* here would put a credential read before the run has any reason for one.
|
|
80
|
+
*
|
|
81
|
+
* Errors resolve to true. Pruning an agent that would have worked kills a
|
|
82
|
+
* run; the opposite costs one request-time error that is already handled.
|
|
83
|
+
*/
|
|
84
|
+
async hasCredentials() {
|
|
85
|
+
try {
|
|
86
|
+
if (await _adapterDeps.authSourceIsExec())
|
|
87
|
+
return true;
|
|
88
|
+
if ((await _adapterDeps.listStoredProviders()).length > 0)
|
|
89
|
+
return true;
|
|
90
|
+
return await _adapterDeps.anyAmbientCredential();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
openSession(name, opts) {
|
|
97
|
+
return openNativeSession(name, opts);
|
|
98
|
+
}
|
|
99
|
+
async sendTurn(handle, prompt, opts) {
|
|
100
|
+
const { provider, model, effort } = parseNativeModel(handle.modelDef?.model ?? "");
|
|
101
|
+
const thinking = toThinkingLevel(effort);
|
|
102
|
+
const client = await getNativeClient(this.catalogOverrides);
|
|
103
|
+
const resolved = await client.model(provider, model);
|
|
104
|
+
const catalog = client.pricing(resolved);
|
|
105
|
+
const { rates, source: pricingSource } = buildRateCard(catalog, handle.modelDef?.pricing);
|
|
106
|
+
const storedTimeoutSeconds = nativeSessionTimeouts.get(handle.id);
|
|
107
|
+
if (storedTimeoutSeconds === undefined) {
|
|
108
|
+
getSafeLogger()?.warn("native-adapter", "session has no recorded timeout; falling back to the default budget", {
|
|
109
|
+
sessionName: handle.id,
|
|
110
|
+
fallbackTimeoutSeconds: FALLBACK_TURN_TIMEOUT_SECONDS,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const timeoutSeconds = storedTimeoutSeconds ?? FALLBACK_TURN_TIMEOUT_SECONDS;
|
|
114
|
+
// Keyed on the session, so every turn of one conversation carries the same
|
|
115
|
+
// id and the provider can keep its cache warm across them.
|
|
116
|
+
const sessionId = nativeSessionId(handle.id);
|
|
117
|
+
// One budget for the whole turn, not one per round-trip. Created here
|
|
118
|
+
// because this is where `timeoutSeconds` is known; consulted by the loop.
|
|
119
|
+
const deadline = createTurnDeadline(timeoutSeconds);
|
|
120
|
+
const hooks = nativeSessionStreamHooks.get(handle.id);
|
|
121
|
+
// One callId per turn, mirroring SpawnAcpSession.prompt(). `runId` is
|
|
122
|
+
// backfilled by the runtime's forwarding closure, which is the only place
|
|
123
|
+
// that knows it — see runtime/index.ts.
|
|
124
|
+
const callId = randomUUID();
|
|
125
|
+
// The transcript/ledger join key, not a stream id. `nativeSessionTranscriptOwners`
|
|
126
|
+
// is keyed on this same `handle.id` and already holds the session's
|
|
127
|
+
// `transcriptOwner` (`scopeId ?? callId` from session-run-hop.ts). Omitted
|
|
128
|
+
// entirely when the session declared no owner, so an absent key reads as
|
|
129
|
+
// "unknown" rather than as a wrong one.
|
|
130
|
+
const owner = nativeSessionTranscriptOwners.get(handle.id);
|
|
131
|
+
const eventBase = {
|
|
132
|
+
callId,
|
|
133
|
+
runId: "",
|
|
134
|
+
agentName: handle.agentName,
|
|
135
|
+
sessionName: handle.id,
|
|
136
|
+
...(owner !== undefined ? { scopeId: owner } : {}),
|
|
137
|
+
};
|
|
138
|
+
const turnController = new AbortController();
|
|
139
|
+
// The watchdog's cancel handle IS the turn controller, so an idle cancel
|
|
140
|
+
// and the whole-turn deadline end the same in-flight call. Registering
|
|
141
|
+
// through the hook (rather than a private registry) is what lets
|
|
142
|
+
// sendPrompt tell a watchdog cancel from an unrelated process kill.
|
|
143
|
+
hooks?.onActiveCall?.(callId, async () => turnController.abort());
|
|
144
|
+
hooks?.onStreamActivity?.({
|
|
145
|
+
...eventBase,
|
|
146
|
+
kind: "agent.call_started",
|
|
147
|
+
model: handle.modelDef?.model ?? "",
|
|
148
|
+
timeoutSeconds,
|
|
149
|
+
timestamp: Date.now(),
|
|
150
|
+
});
|
|
151
|
+
// US-002: one per-turn signal, fanned in from the caller's
|
|
152
|
+
// `opts.signal`, the watchdog's `turnController.signal`, and a
|
|
153
|
+
// whole-turn deadline timer armed with `deadline.remainingMs()`. The
|
|
154
|
+
// timer is cleared at turn settlement (finally, below) so the AbortSignal
|
|
155
|
+
// any(...) does not keep a timer alive past the turn's end. Complete and
|
|
156
|
+
// summarize retain their per-call timers but combine them with this same
|
|
157
|
+
// turnSignal so a turn-cancel reaches an in-flight call mid-roundtrip.
|
|
158
|
+
const deadlineController = new AbortController();
|
|
159
|
+
const deadlineMs = deadline.remainingMs();
|
|
160
|
+
const deadlineTimer = deadlineMs !== undefined ? _adapterDeps.setTimeout(() => deadlineController.abort(), deadlineMs) : undefined;
|
|
161
|
+
const turnSignals = [turnController.signal, deadlineController.signal];
|
|
162
|
+
if (opts.signal !== undefined)
|
|
163
|
+
turnSignals.unshift(opts.signal);
|
|
164
|
+
const turnSignal = AbortSignal.any(turnSignals);
|
|
165
|
+
let result;
|
|
166
|
+
try {
|
|
167
|
+
// US-002: clear the deadline timer at turn settlement, no matter how
|
|
168
|
+
// the turn ends (clean exit, throw, or any other path). The
|
|
169
|
+
// `AbortSignal.any` above holds a reference to `deadlineController.signal`,
|
|
170
|
+
// so a settled turn must drop its timer to avoid keeping it armed past
|
|
171
|
+
// the turn boundary.
|
|
172
|
+
result = await runNativeTurn(handle, prompt, opts, {
|
|
173
|
+
deadline,
|
|
174
|
+
contextWindow: resolveContextWindow(handle.modelDef?.contextWindow, resolved.contextWindow),
|
|
175
|
+
...(nativeSessionCompaction.get(handle.id) !== undefined
|
|
176
|
+
? { compaction: nativeSessionCompaction.get(handle.id) }
|
|
177
|
+
: {}),
|
|
178
|
+
...(nativeSessionTransportRetry.get(handle.id) !== undefined
|
|
179
|
+
? { transportRetry: nativeSessionTransportRetry.get(handle.id) }
|
|
180
|
+
: {}),
|
|
181
|
+
...(nativeSessionSpinBreaker.get(handle.id) !== undefined
|
|
182
|
+
? { spinBreaker: nativeSessionSpinBreaker.get(handle.id) }
|
|
183
|
+
: {}),
|
|
184
|
+
...(opts.loopEvents !== undefined ? { loopEvents: opts.loopEvents } : {}),
|
|
185
|
+
// US-003: the run's plugin-contributed loop handlers and the facts
|
|
186
|
+
// their handlers read travel the same way as `loopEvents` — both are
|
|
187
|
+
// per-turn inputs the loop installs onto its registry rather than
|
|
188
|
+
// state the adapter owns.
|
|
189
|
+
...loopHandlerDeps(opts),
|
|
190
|
+
// US-002: the one per-turn signal threaded into the batch and the
|
|
191
|
+
// in-flight coding-tool runtime. When `opts.signal` and the watchdog
|
|
192
|
+
// and the deadline are all absent, `turnSignal` is a non-aborted
|
|
193
|
+
// composite that behaves as the no-signal regression guard requires.
|
|
194
|
+
signal: turnSignal,
|
|
195
|
+
// US-002 AC14: forward an onWaiting callback the batch can hand to
|
|
196
|
+
// every coding-tool request. Wired in US-004 to emit the keepalive
|
|
197
|
+
// activity; today it is a no-op so the field plumbing is real in the
|
|
198
|
+
// production native path even though the keepalive is not yet
|
|
199
|
+
// active. The handler copies it onto the tool's `ToolCallContext`
|
|
200
|
+
// and a tool that blocks on a human approval calls it before the wait.
|
|
201
|
+
onWaiting: () => { },
|
|
202
|
+
pricingSource,
|
|
203
|
+
onActivity: (activity) => {
|
|
204
|
+
hooks?.onStreamActivity?.(buildNativeStreamEvent(eventBase, activity, Date.now()));
|
|
205
|
+
},
|
|
206
|
+
summarize: async (span, previousSummary) => {
|
|
207
|
+
// Same model, same clock, no tools. The prompt asks for what a coding
|
|
208
|
+
// agent needs back: what was tried, what was rejected and why, and the
|
|
209
|
+
// files touched -- without them the agent re-reads what it already read.
|
|
210
|
+
const remainingMs = deadline.remainingMs();
|
|
211
|
+
const controller = new AbortController();
|
|
212
|
+
const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
|
|
213
|
+
// US-002: the per-call signal still combines with the per-turn signal
|
|
214
|
+
// (watchdog + deadline + caller), so a turn cancel ends the summary
|
|
215
|
+
// even mid-call. The per-call timer is the additional budget on top.
|
|
216
|
+
const signal = AbortSignal.any(opts.signal !== undefined
|
|
217
|
+
? [opts.signal, controller.signal, turnController.signal, deadlineController.signal]
|
|
218
|
+
: [controller.signal, turnController.signal, deadlineController.signal]);
|
|
219
|
+
try {
|
|
220
|
+
const res = await client.complete(resolved, {
|
|
221
|
+
messages: [...span, { role: "user", content: summaryPrompt(previousSummary) }],
|
|
222
|
+
sessionId,
|
|
223
|
+
signal,
|
|
224
|
+
});
|
|
225
|
+
const summaryUsage = res.usage;
|
|
226
|
+
const { costUsd, resolvedRates } = priceCall(summaryUsage, rates);
|
|
227
|
+
return { text: res.text, usage: summaryUsage, costUsd, rates: resolvedRates };
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
if (timer !== undefined)
|
|
231
|
+
_adapterDeps.clearTimeout(timer);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
complete: async (messages, tools, requestOptions) => {
|
|
235
|
+
// The controller is armed with what is LEFT of the turn, so N
|
|
236
|
+
// round-trips can no longer add up to N x timeoutSeconds. Still
|
|
237
|
+
// combined with any caller-supplied opts.signal via AbortSignal.any so
|
|
238
|
+
// either can end the call. US-002: also combined with the per-turn
|
|
239
|
+
// turnController + deadlineController signals so an in-flight call
|
|
240
|
+
// observes the same cancellation the batch sees between calls.
|
|
241
|
+
const remainingMs = deadline.remainingMs();
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
const timer = remainingMs !== undefined ? _adapterDeps.setTimeout(() => controller.abort(), remainingMs) : undefined;
|
|
244
|
+
const signal = AbortSignal.any(opts.signal !== undefined
|
|
245
|
+
? [opts.signal, controller.signal, turnController.signal, deadlineController.signal]
|
|
246
|
+
: [controller.signal, turnController.signal, deadlineController.signal]);
|
|
247
|
+
// The loop-event bag speaks in booleans: `false` explicitly drops
|
|
248
|
+
// the session's inherited thinking level for this request, while
|
|
249
|
+
// `true` leaves that resolved level in place. nax-ai accepts a
|
|
250
|
+
// thinking LEVEL rather than a boolean, so normalize at this boundary.
|
|
251
|
+
const requestThinking = requestOptions?.thinking === false ? undefined : thinking;
|
|
252
|
+
try {
|
|
253
|
+
const res = await client.complete(resolved, {
|
|
254
|
+
messages,
|
|
255
|
+
...(tools.length > 0 ? { tools } : {}),
|
|
256
|
+
sessionId,
|
|
257
|
+
signal,
|
|
258
|
+
...(requestThinking !== undefined ? { thinking: requestThinking } : {}),
|
|
259
|
+
...(requestOptions?.temperature !== undefined ? { temperature: requestOptions.temperature } : {}),
|
|
260
|
+
// nax#1835: "short" is fixed, not config-driven (this repo's
|
|
261
|
+
// precedent -- the compaction design -- rejects knobs added
|
|
262
|
+
// before evidence). The turn loop's round trips are seconds
|
|
263
|
+
// apart, so "short" already hits; "long" would only pay off for
|
|
264
|
+
// a later turn and bills more at write time for a window this
|
|
265
|
+
// turn does not need. Only this round-trip closure sets it: the
|
|
266
|
+
// one-shot complete() and the summarize closure below have no
|
|
267
|
+
// successor turn (or, for summarize, a shape unlikely to repeat)
|
|
268
|
+
// to reuse the entry, so a cache write there costs more than it
|
|
269
|
+
// saves.
|
|
270
|
+
cacheRetention: "short",
|
|
271
|
+
});
|
|
272
|
+
const usage = res.usage;
|
|
273
|
+
// Single `priceCall` invocation: `costUsd` and `resolvedRates`
|
|
274
|
+
// come from the same call so they cannot diverge — the
|
|
275
|
+
// verifiability property the story names ("recorded rates
|
|
276
|
+
// reproduce recorded cost") would silently break if tier
|
|
277
|
+
// selection ever grew a side channel.
|
|
278
|
+
const { costUsd, resolvedRates } = priceCall(usage, rates);
|
|
279
|
+
return {
|
|
280
|
+
text: res.text,
|
|
281
|
+
...(res.toolCalls !== undefined ? { toolCalls: res.toolCalls } : {}),
|
|
282
|
+
...(res.thinking !== undefined ? { thinking: res.thinking } : {}),
|
|
283
|
+
usage,
|
|
284
|
+
costUsd,
|
|
285
|
+
// US-002: thread the per-call resolved rates back to
|
|
286
|
+
// `runNativeTurn` so it can stamp them on the `TurnResult`.
|
|
287
|
+
// The last round-trip's rates win — each round-trip re-prices
|
|
288
|
+
// on its own usage, and the most recent decision is what
|
|
289
|
+
// affected the user's last-mile spend.
|
|
290
|
+
rates: resolvedRates,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
if (timer !== undefined)
|
|
295
|
+
_adapterDeps.clearTimeout(timer);
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
hooks?.onStreamActivity?.({
|
|
302
|
+
...eventBase,
|
|
303
|
+
kind: "agent.call_ended",
|
|
304
|
+
status: turnController.signal.aborted ? "cancelled" : "error",
|
|
305
|
+
timestamp: Date.now(),
|
|
306
|
+
});
|
|
307
|
+
markNativeTurnOutcome(handle.id, true);
|
|
308
|
+
// The same treatment complete() gives a protocol fault, on the path that
|
|
309
|
+
// was missing it (nax#1838). Rethrowing untouched left build-hop-callback
|
|
310
|
+
// to synthesise a generic fail-adapter-error, which cost a rate limit its
|
|
311
|
+
// backoff and an auth failure its unavailable mark.
|
|
312
|
+
//
|
|
313
|
+
// nax#1840: classification and cost are read off two different error
|
|
314
|
+
// classes, and a throw can only be one. SessionTurnError is the carrier
|
|
315
|
+
// both hop callbacks already read cost off, so it now also carries the
|
|
316
|
+
// classification (its optional adapterFailure field) — one throw, both
|
|
317
|
+
// facts. runNativeTurn attaches whatever was already spent on earlier
|
|
318
|
+
// round trips to this same err before it reaches here; read it back
|
|
319
|
+
// rather than dropping it as the pre-#1840 SessionFailureError did.
|
|
320
|
+
//
|
|
321
|
+
// Only a protocol fault is wrapped. A TypeError from our own code is not a
|
|
322
|
+
// vendor failure, and dressing it as one would hide the bug.
|
|
323
|
+
if (isProtocolStreamError(err)) {
|
|
324
|
+
const adapterFailure = toAdapterFailure(err.protocolError);
|
|
325
|
+
const usage = readNativeTurnFailureUsage(err);
|
|
326
|
+
throw new SessionTurnError(err.protocolError.message, false, adapterFailure.retriable, usage?.tokenUsage, usage?.costUsd, undefined,
|
|
327
|
+
// pricingSource — the native protocol-fault path has no rate card
|
|
328
|
+
// to name here; left undefined (SessionTurnError.pricingSource).
|
|
329
|
+
undefined, adapterFailure);
|
|
330
|
+
}
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
if (deadlineTimer !== undefined)
|
|
335
|
+
_adapterDeps.clearTimeout(deadlineTimer);
|
|
336
|
+
}
|
|
337
|
+
hooks?.onStreamActivity?.({
|
|
338
|
+
...eventBase,
|
|
339
|
+
kind: "agent.call_ended",
|
|
340
|
+
status: result.timedOut === true ? "timeout" : turnController.signal.aborted ? "cancelled" : "success",
|
|
341
|
+
timestamp: Date.now(),
|
|
342
|
+
});
|
|
343
|
+
markNativeTurnOutcome(handle.id, false);
|
|
344
|
+
// US-006: the store only observes `provider` while a request is in flight,
|
|
345
|
+
// so the stamp is read here, after the loop.
|
|
346
|
+
return { ...result, ...authFields(provider) };
|
|
347
|
+
}
|
|
348
|
+
closeSession(handle) {
|
|
349
|
+
// The adapter interface has no failure signal, so the verdict comes from the
|
|
350
|
+
// session's last turn (markNativeTurnOutcome) rather than from this call.
|
|
351
|
+
// Passing a literal false here is what deleted the transcript of a failed
|
|
352
|
+
// session -- the one the retry reloads and a human reads (nax#1838).
|
|
353
|
+
return closeNativeSession(handle);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Run teardown addresses a session the process no longer has a handle for,
|
|
357
|
+
* by id string (execution/session-manager-runtime.ts). Without this the
|
|
358
|
+
* native maps were unreachable at teardown: every `keepOpen` session was
|
|
359
|
+
* removed from SessionManager._sessions by closeStory while its nine entries
|
|
360
|
+
* stayed behind for the process lifetime.
|
|
361
|
+
*
|
|
362
|
+
* The ACP contract takes a handle string and closeNativeSession takes a
|
|
363
|
+
* SessionHandle, but the maps are keyed by the session-name string both
|
|
364
|
+
* carry, so clear by name rather than synthesising a handle.
|
|
365
|
+
*/
|
|
366
|
+
async closePhysicalSession(handle, _workdir, _options) {
|
|
367
|
+
return closeNativeSession({ id: handle, agentName: NATIVE_AGENT });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session identity for native requests.
|
|
3
|
+
*
|
|
4
|
+
* nax owns what a session *is*; nax-ai owns how one reaches a provider. So this
|
|
5
|
+
* file derives an id and stops there — which header carries it, and for which
|
|
6
|
+
* vendor, is nax-ai's business (`vendorSessionHeaders`, plus everything pi-ai
|
|
7
|
+
* already derives from a per-model affinity format and the prompt-cache key).
|
|
8
|
+
* An earlier revision kept that mapping here, which meant nax had learned a
|
|
9
|
+
* vendor vocabulary it has no reason to know.
|
|
10
|
+
*
|
|
11
|
+
* The id is a hash, never the session name. nax builds session ids from role,
|
|
12
|
+
* story and feature ("implementer-US-001-auth-system"), so sending one raw
|
|
13
|
+
* would hand a third party the shape of a private repository's work for no
|
|
14
|
+
* gain — a provider needs an opaque token it can match, and nothing else.
|
|
15
|
+
* Hashing also makes the value safe to put on the wire by construction,
|
|
16
|
+
* whatever the id happened to contain.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The session id, independent of who will serve the request.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately not memoised: it is a hash of its input, so it is already stable
|
|
22
|
+
* for a given session, and a cache keyed on session ids would outlive the runs
|
|
23
|
+
* that created them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function nativeSessionId(sessionKey: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* A fresh key for a caller that has no session id of its own.
|
|
28
|
+
*
|
|
29
|
+
* `complete()` is one call with no successor, so there is nothing to name. A
|
|
30
|
+
* fresh key per *call* would satisfy the letter of it and defeat the purpose,
|
|
31
|
+
* since affinity only pays when two requests share an id. The adapter holds one
|
|
32
|
+
* of these for its own lifetime, which the agent registry's adapter cache
|
|
33
|
+
* scopes to a run, so the one-shots of a run share a backend while unrelated
|
|
34
|
+
* runs do not.
|
|
35
|
+
*/
|
|
36
|
+
export declare function newSessionKey(): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session identity for native requests.
|
|
3
|
+
*
|
|
4
|
+
* nax owns what a session *is*; nax-ai owns how one reaches a provider. So this
|
|
5
|
+
* file derives an id and stops there — which header carries it, and for which
|
|
6
|
+
* vendor, is nax-ai's business (`vendorSessionHeaders`, plus everything pi-ai
|
|
7
|
+
* already derives from a per-model affinity format and the prompt-cache key).
|
|
8
|
+
* An earlier revision kept that mapping here, which meant nax had learned a
|
|
9
|
+
* vendor vocabulary it has no reason to know.
|
|
10
|
+
*
|
|
11
|
+
* The id is a hash, never the session name. nax builds session ids from role,
|
|
12
|
+
* story and feature ("implementer-US-001-auth-system"), so sending one raw
|
|
13
|
+
* would hand a third party the shape of a private repository's work for no
|
|
14
|
+
* gain — a provider needs an opaque token it can match, and nothing else.
|
|
15
|
+
* Hashing also makes the value safe to put on the wire by construction,
|
|
16
|
+
* whatever the id happened to contain.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
19
|
+
/** 128 bits of a SHA-256, far past collision concerns for routing. */
|
|
20
|
+
const ID_LENGTH = 32;
|
|
21
|
+
/**
|
|
22
|
+
* The session id, independent of who will serve the request.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately not memoised: it is a hash of its input, so it is already stable
|
|
25
|
+
* for a given session, and a cache keyed on session ids would outlive the runs
|
|
26
|
+
* that created them.
|
|
27
|
+
*/
|
|
28
|
+
export function nativeSessionId(sessionKey) {
|
|
29
|
+
return createHash("sha256").update(sessionKey).digest("hex").slice(0, ID_LENGTH);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A fresh key for a caller that has no session id of its own.
|
|
33
|
+
*
|
|
34
|
+
* `complete()` is one call with no successor, so there is nothing to name. A
|
|
35
|
+
* fresh key per *call* would satisfy the letter of it and defeat the purpose,
|
|
36
|
+
* since affinity only pays when two requests share an id. The adapter holds one
|
|
37
|
+
* of these for its own lifetime, which the agent registry's adapter cache
|
|
38
|
+
* scopes to a run, so the one-shots of a run share a backend while unrelated
|
|
39
|
+
* runs do not.
|
|
40
|
+
*/
|
|
41
|
+
export function newSessionKey() {
|
|
42
|
+
return randomUUID();
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two config fields this reads, declared here so the move set needs no nax
|
|
3
|
+
* config type (S1 spec section 4.2, port 4). nax's `PrecheckConfig` satisfies it.
|
|
4
|
+
*/
|
|
5
|
+
export interface NativeTierConfig {
|
|
6
|
+
readonly agent?: {
|
|
7
|
+
readonly native?: {
|
|
8
|
+
readonly catalogOverrides?: readonly {
|
|
9
|
+
readonly provider: string;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
readonly models?: Readonly<Record<string, Readonly<Record<string, string | {
|
|
14
|
+
readonly model: string;
|
|
15
|
+
} | undefined>> | undefined>>;
|
|
16
|
+
}
|
|
17
|
+
/** provider -> tiers, for the `models.native` map, skipping catalog-override providers. */
|
|
18
|
+
export declare function nativeTierProviders(config: NativeTierConfig): Map<string, string[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NATIVE_AGENT } from "./models.js";
|
|
2
|
+
/** The provider prefix of a native id, or undefined when it has none (never guessed). */
|
|
3
|
+
function providerOf(id) {
|
|
4
|
+
const slash = id.indexOf("/");
|
|
5
|
+
return slash > 0 ? id.slice(0, slash) : undefined;
|
|
6
|
+
}
|
|
7
|
+
/** provider -> tiers, for the `models.native` map, skipping catalog-override providers. */
|
|
8
|
+
export function nativeTierProviders(config) {
|
|
9
|
+
const overridden = new Set((config.agent?.native?.catalogOverrides ?? []).map((override) => override.provider));
|
|
10
|
+
const byProvider = new Map();
|
|
11
|
+
for (const [tier, entry] of Object.entries(config.models?.[NATIVE_AGENT] ?? {})) {
|
|
12
|
+
if (entry === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
const provider = providerOf(typeof entry === "string" ? entry : entry.model);
|
|
15
|
+
if (provider === undefined || overridden.has(provider))
|
|
16
|
+
continue;
|
|
17
|
+
byProvider.set(provider, [...(byProvider.get(provider) ?? []), tier]);
|
|
18
|
+
}
|
|
19
|
+
return byProvider;
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AskDecidedBy } from "./ask-chain.ts";
|
|
2
|
+
import type { AskRequest } from "./types.ts";
|
|
3
|
+
export interface ApprovalAuditRow {
|
|
4
|
+
readonly request: AskRequest;
|
|
5
|
+
readonly decision: "allow" | "deny";
|
|
6
|
+
readonly decidedBy: AskDecidedBy;
|
|
7
|
+
readonly latencyMs: number;
|
|
8
|
+
readonly at: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function appendApprovalAudit(dir: string, runId: string, row: ApprovalAuditRow): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus of resolved permission asks (P2 design 7.2).
|
|
3
|
+
*
|
|
4
|
+
* One row is appended for EVERY resolved ask -- allow or deny, whichever link
|
|
5
|
+
* answered. `decidedBy` distinguishes a human decision from the rest (cache,
|
|
6
|
+
* model, timeout, unavailable, unshowable), so P5's classifier can select the
|
|
7
|
+
* human-labelled ground-truth rows -- from the actual decision-maker, on the
|
|
8
|
+
* real distribution -- out of the same file. P5's design records that such a
|
|
9
|
+
* corpus does not exist; writing it now means P5 starts with labels rather
|
|
10
|
+
* than bootstrapping them.
|
|
11
|
+
*
|
|
12
|
+
* One JSON object per line, mirroring finish-audit (src/finish/audit.ts:42).
|
|
13
|
+
* String leaves pass through redactRowStrings so a secret in the request never
|
|
14
|
+
* reaches the local corpus.
|
|
15
|
+
*/
|
|
16
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { redactRowStrings } from "./secret-spans.js";
|
|
19
|
+
export async function appendApprovalAudit(dir, runId, row) {
|
|
20
|
+
await mkdir(dir, { recursive: true });
|
|
21
|
+
await appendFile(join(dir, `${runId}.jsonl`), `${JSON.stringify(redactRowStrings(row))}\n`, "utf8");
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AskLink } from "./ask-chain.ts";
|
|
2
|
+
export declare function createApprovalsLink(opts: {
|
|
3
|
+
readonly approvalsFile: string;
|
|
4
|
+
readonly repoRoot: string;
|
|
5
|
+
/**
|
|
6
|
+
* The project root (`ctx.projectDir`). An entry is honoured only when its
|
|
7
|
+
* `root` is this root or lies inside it, as worktrees and package dirs do.
|
|
8
|
+
*/
|
|
9
|
+
readonly projectRoot: string;
|
|
10
|
+
/** Every stage's resolved bashApproval mode in this run. */
|
|
11
|
+
readonly stageModes: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* `execution.sandbox.enabled` (P4). With the sandbox enabled a raw stage is
|
|
14
|
+
* either wrapped -- and the approvals file is ALWAYS in its denyWrite, even
|
|
15
|
+
* when outputDir puts it inside a write root -- or refused outright, so it
|
|
16
|
+
* cannot forge this file DURING THIS RUN. What earlier runs wrote is
|
|
17
|
+
* precondition 3's job. Config-only by design: no dependency on the probe.
|
|
18
|
+
*/
|
|
19
|
+
readonly sandboxEnabled: boolean;
|
|
20
|
+
}): AskLink;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approvals cache as the FIRST link of the ask chain (P2 design 4.3, 6.6).
|
|
3
|
+
*
|
|
4
|
+
* TRUST BOUNDARY. Living outside repoRoot protects this file from the TYPED
|
|
5
|
+
* path-bearing tools, but NOT from Bash: nax-owned-writes.ts:52-58 excludes
|
|
6
|
+
* Bash by design, and under `raw` mode screenRawBashCommand's protectedHit
|
|
7
|
+
* skips every path outside the root (policy-bash-raw.ts:84), so the file is
|
|
8
|
+
* never screened. A raw shell can forge entries.
|
|
9
|
+
*
|
|
10
|
+
* That is not a new vulnerability -- a raw shell needs no forged permission to
|
|
11
|
+
* run a command -- but it IS a cross-stage escalation in a MIXED-mode run,
|
|
12
|
+
* where a raw stage poisons the cache an escalate stage later trusts. Hence
|
|
13
|
+
* precondition 1. P4's sandbox closes the underlying hole when enabled: see
|
|
14
|
+
* `sandboxEnabled`.
|
|
15
|
+
*
|
|
16
|
+
* The file is project-scoped and OUTLIVES the run, so precondition 1 covers
|
|
17
|
+
* only THIS run: an EARLIER forge-capable run could have written it (#2199).
|
|
18
|
+
* Precondition 3 covers that history: a tainted store is never trusted (see
|
|
19
|
+
* approvals-taint.ts, which also states the residual risk). Entry `root` is
|
|
20
|
+
* compared with the project root too, but only as hygiene -- a forger
|
|
21
|
+
* controls `root`.
|
|
22
|
+
*
|
|
23
|
+
* Every precondition fails by ABSTAINING, which escalates to the human, so a
|
|
24
|
+
* failure costs prompts rather than safety.
|
|
25
|
+
*/
|
|
26
|
+
import { relative, resolve } from "node:path";
|
|
27
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
28
|
+
import { findApproval, readApprovalsFile } from "./approvals-store.js";
|
|
29
|
+
import { isForgeCapable } from "./approvals-taint.js";
|
|
30
|
+
const ABSTAIN = { decision: "abstain", decidedBy: "cache" };
|
|
31
|
+
function insideRepo(repoRoot, file) {
|
|
32
|
+
const rel = relative(resolve(repoRoot), resolve(file));
|
|
33
|
+
return rel !== "" && !rel.startsWith("..");
|
|
34
|
+
}
|
|
35
|
+
export function createApprovalsLink(opts) {
|
|
36
|
+
const rawStage = isForgeCapable(opts.stageModes, opts.sandboxEnabled);
|
|
37
|
+
const inRepo = insideRepo(opts.repoRoot, opts.approvalsFile);
|
|
38
|
+
const disabled = rawStage || inRepo;
|
|
39
|
+
if (disabled) {
|
|
40
|
+
getSafeLogger()?.warn("permissions", "[approvals] cache disabled; every ask reaches the human", {
|
|
41
|
+
reason: rawStage
|
|
42
|
+
? "a stage resolves to bashApproval:raw without the sandbox, which can forge this file"
|
|
43
|
+
: "approvals file is inside repoRoot",
|
|
44
|
+
approvalsFile: opts.approvalsFile,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
name: "approvals-cache",
|
|
49
|
+
async resolve(req) {
|
|
50
|
+
if (disabled)
|
|
51
|
+
return ABSTAIN;
|
|
52
|
+
if (req.command === undefined)
|
|
53
|
+
return ABSTAIN;
|
|
54
|
+
// Read per ask: a concurrent forge-capable run can taint the store mid-run.
|
|
55
|
+
const store = await readApprovalsFile(opts.approvalsFile);
|
|
56
|
+
if (store.taint !== undefined)
|
|
57
|
+
return ABSTAIN;
|
|
58
|
+
const hit = findApproval(store.entries, req.stage, req.command, opts.projectRoot);
|
|
59
|
+
return hit === undefined ? ABSTAIN : { decision: "allow", decidedBy: "cache" };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|