@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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native session's in-process loop event seam (nax#2151, US-002).
|
|
3
|
+
*
|
|
4
|
+
* Typed function registrations, not an extension of `src/hooks/`: those events
|
|
5
|
+
* fire around a story, shell out with a 5s timeout, and cannot carry a
|
|
6
|
+
* rewritten tool result. Two events exist because two have real consumers
|
|
7
|
+
* today — invalid-call repair and the spin breaker on `before_tool`, and the
|
|
8
|
+
* truncation policy on `after_tool`.
|
|
9
|
+
*
|
|
10
|
+
* The dispatcher enforces four rules rather than leaving them to handler
|
|
11
|
+
* authors, because those rules are what let a future event be added without
|
|
12
|
+
* redesigning the seam:
|
|
13
|
+
*
|
|
14
|
+
* 1. Results are partial patches, never mutations. A handler returns only what
|
|
15
|
+
* it wants changed and the dispatcher merges. Payloads carry the live
|
|
16
|
+
* message array, readonly-TYPED only — mutation is not prevented at
|
|
17
|
+
* runtime; what §3 enforces mechanically is history REWRITING across the
|
|
18
|
+
* cache boundary, not object immutability.
|
|
19
|
+
* 2. Handlers chain, each seeing the previous handler's output, in
|
|
20
|
+
* registration order.
|
|
21
|
+
* 3. A throwing handler is logged at warn and skipped — it never fails the
|
|
22
|
+
* turn for its own defect. A rejected promise counts as a throw (spec
|
|
23
|
+
* 4.2): the await sits inside the try, so a handler whose promise rejects
|
|
24
|
+
* is logged-and-skipped identically to one that throws — a bare try/catch
|
|
25
|
+
* catches only the throw.
|
|
26
|
+
* 4. No handler may rewrite history. Measured prompt-cache hit rate on the
|
|
27
|
+
* native path is 96.7%, and Anthropic-style caching is prefix-matched, so
|
|
28
|
+
* rewriting anything early in the array re-bills every downstream turn at
|
|
29
|
+
* input rather than cacheRead — roughly 5x more expensive, not cheaper.
|
|
30
|
+
* `after_tool` is safe by construction: it shapes a result before the
|
|
31
|
+
* result enters the array. Across the full event set that safety is no
|
|
32
|
+
* longer left to construction — the cache-boundary checker (spec 4.7, §3)
|
|
33
|
+
* enforces it mechanically.
|
|
34
|
+
*/
|
|
35
|
+
import type { BeforeToolOutcome, HandlerOf, LoopEvent, PatchOf, PayloadOf } from "./types.ts";
|
|
36
|
+
export interface LoopEventRegistry {
|
|
37
|
+
register<E extends LoopEvent>(event: E, handler: HandlerOf<E>): void;
|
|
38
|
+
dispatch<E extends LoopEvent>(event: E, payload: PayloadOf<E>): Promise<PatchOf<E>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The event names, at runtime (US-001).
|
|
42
|
+
*
|
|
43
|
+
* A plugin's registration is validated against this set before it is staged,
|
|
44
|
+
* because an event name arriving from a plugin is `unknown` until then. The
|
|
45
|
+
* list is read off `PATCHABLE_FIELDS` rather than written out again: that map
|
|
46
|
+
* is pinned to `LoopEvent` in both directions — a missing event fails the
|
|
47
|
+
* `satisfies` clause, an extra one fails the field map's exhaustiveness pin —
|
|
48
|
+
* so its keys are the event set, and a second hand-written copy cannot drift
|
|
49
|
+
* out of step with the type.
|
|
50
|
+
*/
|
|
51
|
+
export declare const LOOP_EVENTS: readonly LoopEvent[];
|
|
52
|
+
/**
|
|
53
|
+
* US-002: is this a well-formed `before_tool` outcome?
|
|
54
|
+
*
|
|
55
|
+
* A plugin module is plain JavaScript and can answer anything at all — a
|
|
56
|
+
* string, `null`, an array, or an object whose `kind` is not a decision. What
|
|
57
|
+
* makes an answer an outcome is its SHAPE, not merely its object-ness, so the
|
|
58
|
+
* dispatcher and `wrapExternalHandler` ask this one question rather than each
|
|
59
|
+
* holding a half of it.
|
|
60
|
+
*
|
|
61
|
+
* This checks the `kind` only. The dispatcher also calls it on built-in
|
|
62
|
+
* handlers, whose return type already guarantees the kind's payload, so
|
|
63
|
+
* requiring the payload here would reject a built-in that a mutable test
|
|
64
|
+
* fixture handed an out-of-type value. The untrusted boundary — a plugin's
|
|
65
|
+
* answer — additionally needs the payload and is checked by
|
|
66
|
+
* `isCompleteBeforeToolOutcome`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isBeforeToolOutcome(value: unknown): value is BeforeToolOutcome;
|
|
69
|
+
/**
|
|
70
|
+
* Is this a plugin answer that carries everything its `kind` promises? A
|
|
71
|
+
* `kind` alone is not enough: `block`/`terminate` must carry the `content` the
|
|
72
|
+
* loop records as the call's result, and `nudge` must carry the `text` it
|
|
73
|
+
* appends. A plugin answering `{ kind: "block" }` with nothing to say would
|
|
74
|
+
* otherwise reach the transcript builder as `content: undefined`, and a
|
|
75
|
+
* textless `nudge` would be silently downgraded to `allow` — so both are
|
|
76
|
+
* malformed, exactly like a `kind` that is not a decision.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isCompleteBeforeToolOutcome(value: unknown): value is BeforeToolOutcome;
|
|
79
|
+
/**
|
|
80
|
+
* Registration order is the chain order within each event; events are
|
|
81
|
+
* independent chains. The built-ins register on the same registry the loop
|
|
82
|
+
* dispatches from (see ./loop-handlers for their per-turn lifetime).
|
|
83
|
+
*/
|
|
84
|
+
export declare function createLoopEventRegistry(): LoopEventRegistry;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native session's in-process loop event seam (nax#2151, US-002).
|
|
3
|
+
*
|
|
4
|
+
* Typed function registrations, not an extension of `src/hooks/`: those events
|
|
5
|
+
* fire around a story, shell out with a 5s timeout, and cannot carry a
|
|
6
|
+
* rewritten tool result. Two events exist because two have real consumers
|
|
7
|
+
* today — invalid-call repair and the spin breaker on `before_tool`, and the
|
|
8
|
+
* truncation policy on `after_tool`.
|
|
9
|
+
*
|
|
10
|
+
* The dispatcher enforces four rules rather than leaving them to handler
|
|
11
|
+
* authors, because those rules are what let a future event be added without
|
|
12
|
+
* redesigning the seam:
|
|
13
|
+
*
|
|
14
|
+
* 1. Results are partial patches, never mutations. A handler returns only what
|
|
15
|
+
* it wants changed and the dispatcher merges. Payloads carry the live
|
|
16
|
+
* message array, readonly-TYPED only — mutation is not prevented at
|
|
17
|
+
* runtime; what §3 enforces mechanically is history REWRITING across the
|
|
18
|
+
* cache boundary, not object immutability.
|
|
19
|
+
* 2. Handlers chain, each seeing the previous handler's output, in
|
|
20
|
+
* registration order.
|
|
21
|
+
* 3. A throwing handler is logged at warn and skipped — it never fails the
|
|
22
|
+
* turn for its own defect. A rejected promise counts as a throw (spec
|
|
23
|
+
* 4.2): the await sits inside the try, so a handler whose promise rejects
|
|
24
|
+
* is logged-and-skipped identically to one that throws — a bare try/catch
|
|
25
|
+
* catches only the throw.
|
|
26
|
+
* 4. No handler may rewrite history. Measured prompt-cache hit rate on the
|
|
27
|
+
* native path is 96.7%, and Anthropic-style caching is prefix-matched, so
|
|
28
|
+
* rewriting anything early in the array re-bills every downstream turn at
|
|
29
|
+
* input rather than cacheRead — roughly 5x more expensive, not cheaper.
|
|
30
|
+
* `after_tool` is safe by construction: it shapes a result before the
|
|
31
|
+
* result enters the array. Across the full event set that safety is no
|
|
32
|
+
* longer left to construction — the cache-boundary checker (spec 4.7, §3)
|
|
33
|
+
* enforces it mechanically.
|
|
34
|
+
*/
|
|
35
|
+
import { errorMessage } from "#src/infra/errors";
|
|
36
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
37
|
+
import { isThenable } from "#src/internal/thenable";
|
|
38
|
+
import { restoreMutated, snapshotArrays } from "./payload-guard.js";
|
|
39
|
+
/**
|
|
40
|
+
* The fields each event's patch type declares, keyed by event. Only these are
|
|
41
|
+
* read off a handler's return, so a handler that bypasses the type cannot
|
|
42
|
+
* surface a non-patchable field. Two compile-time pins keep this map aligned
|
|
43
|
+
* with the patch types in `./types`, in both directions:
|
|
44
|
+
*
|
|
45
|
+
* - the `satisfies` pins every event to an entry whose members are valid
|
|
46
|
+
* patch keys for that event;
|
|
47
|
+
* - `_patchableFieldsExhaustive` below pins the converse — every patch key
|
|
48
|
+
* appears under its own event.
|
|
49
|
+
*
|
|
50
|
+
* Together: a patch field must appear here, or typecheck fails — either
|
|
51
|
+
* because the entry is invalid, or because the field was added to a patch
|
|
52
|
+
* type without an entry. The second direction is the load-bearing one: an
|
|
53
|
+
* unlisted field compiles fine on the type and would be silently stripped by
|
|
54
|
+
* `pickPatchFields`, which is exactly the failure this map exists to make
|
|
55
|
+
* impossible.
|
|
56
|
+
*/
|
|
57
|
+
const PATCHABLE_FIELDS = {
|
|
58
|
+
// A decision, not a field patch — chained in dispatchBeforeTool below.
|
|
59
|
+
before_tool: [],
|
|
60
|
+
after_tool: ["content", "isError"],
|
|
61
|
+
before_turn: ["seed", "history"],
|
|
62
|
+
transform_context: ["messages"],
|
|
63
|
+
before_request: ["options"],
|
|
64
|
+
after_response: ["text", "toolCalls", "thinking"],
|
|
65
|
+
before_compaction: ["decline", "summary"],
|
|
66
|
+
before_turn_end: ["followUp"],
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The event names, at runtime (US-001).
|
|
70
|
+
*
|
|
71
|
+
* A plugin's registration is validated against this set before it is staged,
|
|
72
|
+
* because an event name arriving from a plugin is `unknown` until then. The
|
|
73
|
+
* list is read off `PATCHABLE_FIELDS` rather than written out again: that map
|
|
74
|
+
* is pinned to `LoopEvent` in both directions — a missing event fails the
|
|
75
|
+
* `satisfies` clause, an extra one fails the field map's exhaustiveness pin —
|
|
76
|
+
* so its keys are the event set, and a second hand-written copy cannot drift
|
|
77
|
+
* out of step with the type.
|
|
78
|
+
*/
|
|
79
|
+
export const LOOP_EVENTS = Object.freeze(Object.keys(PATCHABLE_FIELDS));
|
|
80
|
+
/**
|
|
81
|
+
* Only the patchable fields are read, so a handler that returns a
|
|
82
|
+
* denied-bearing object (bypassing the type) cannot surface one — the same
|
|
83
|
+
* defence keeps `usage` off an `after_response` patch, and every other event's
|
|
84
|
+
* surfaced-only fields out of its patch.
|
|
85
|
+
*/
|
|
86
|
+
function pickPatchFields(returned, fields) {
|
|
87
|
+
if (typeof returned !== "object" || returned === null)
|
|
88
|
+
return {};
|
|
89
|
+
const source = returned;
|
|
90
|
+
const picked = {};
|
|
91
|
+
for (const field of fields) {
|
|
92
|
+
const value = source[field];
|
|
93
|
+
if (value !== undefined)
|
|
94
|
+
picked[field] = value;
|
|
95
|
+
}
|
|
96
|
+
return picked;
|
|
97
|
+
}
|
|
98
|
+
/** The four kinds that make a `before_tool` answer an outcome, and nothing else. */
|
|
99
|
+
const BEFORE_TOOL_KINDS = ["allow", "nudge", "block", "terminate"];
|
|
100
|
+
/**
|
|
101
|
+
* US-002: is this a well-formed `before_tool` outcome?
|
|
102
|
+
*
|
|
103
|
+
* A plugin module is plain JavaScript and can answer anything at all — a
|
|
104
|
+
* string, `null`, an array, or an object whose `kind` is not a decision. What
|
|
105
|
+
* makes an answer an outcome is its SHAPE, not merely its object-ness, so the
|
|
106
|
+
* dispatcher and `wrapExternalHandler` ask this one question rather than each
|
|
107
|
+
* holding a half of it.
|
|
108
|
+
*
|
|
109
|
+
* This checks the `kind` only. The dispatcher also calls it on built-in
|
|
110
|
+
* handlers, whose return type already guarantees the kind's payload, so
|
|
111
|
+
* requiring the payload here would reject a built-in that a mutable test
|
|
112
|
+
* fixture handed an out-of-type value. The untrusted boundary — a plugin's
|
|
113
|
+
* answer — additionally needs the payload and is checked by
|
|
114
|
+
* `isCompleteBeforeToolOutcome`.
|
|
115
|
+
*/
|
|
116
|
+
export function isBeforeToolOutcome(value) {
|
|
117
|
+
if (typeof value !== "object" || value === null)
|
|
118
|
+
return false;
|
|
119
|
+
const kind = value.kind;
|
|
120
|
+
return typeof kind === "string" && BEFORE_TOOL_KINDS.includes(kind);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Is this a plugin answer that carries everything its `kind` promises? A
|
|
124
|
+
* `kind` alone is not enough: `block`/`terminate` must carry the `content` the
|
|
125
|
+
* loop records as the call's result, and `nudge` must carry the `text` it
|
|
126
|
+
* appends. A plugin answering `{ kind: "block" }` with nothing to say would
|
|
127
|
+
* otherwise reach the transcript builder as `content: undefined`, and a
|
|
128
|
+
* textless `nudge` would be silently downgraded to `allow` — so both are
|
|
129
|
+
* malformed, exactly like a `kind` that is not a decision.
|
|
130
|
+
*/
|
|
131
|
+
export function isCompleteBeforeToolOutcome(value) {
|
|
132
|
+
if (!isBeforeToolOutcome(value))
|
|
133
|
+
return false;
|
|
134
|
+
const outcome = value;
|
|
135
|
+
if (value.kind === "block" || value.kind === "terminate")
|
|
136
|
+
return typeof outcome.content === "string";
|
|
137
|
+
if (value.kind === "nudge")
|
|
138
|
+
return typeof outcome.text === "string";
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* A handler's answer arrives typed, but its `kind` may be missing or invented,
|
|
143
|
+
* and reading `kind` off such an answer is a dereference the dispatcher must
|
|
144
|
+
* not do: it would throw out of a seam whose whole contract is that it never
|
|
145
|
+
* fails the turn, and it would do so OUTSIDE the handler's try (US-002).
|
|
146
|
+
*/
|
|
147
|
+
function asOutcome(returned, tool) {
|
|
148
|
+
if (isBeforeToolOutcome(returned))
|
|
149
|
+
return returned;
|
|
150
|
+
getSafeLogger()?.warn("native-loop-events", "before_tool handler answered no valid outcome; treating it as allow", {
|
|
151
|
+
tool,
|
|
152
|
+
});
|
|
153
|
+
return { kind: "allow" };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Registration order is the chain order. `block` and `terminate` are a single
|
|
157
|
+
* decision, not a chained one: they short-circuit, because the outcome is the
|
|
158
|
+
* dispatcher's verdict and a later handler must not be able to veto it.
|
|
159
|
+
*
|
|
160
|
+
* `before_tool` is the one event whose patch is a DECISION, not a field patch,
|
|
161
|
+
* so it does not share the merge chain: an empty chain must still yield a
|
|
162
|
+
* verdict (`allow`, exactly as the pre-map dispatcher returned), and the thing
|
|
163
|
+
* a later handler sees is the accumulated input rewrite inside the call, not a
|
|
164
|
+
* merged payload.
|
|
165
|
+
*/
|
|
166
|
+
async function dispatchBeforeTool(handlers, payload) {
|
|
167
|
+
const { call, tools } = payload;
|
|
168
|
+
let input;
|
|
169
|
+
let nudgeText;
|
|
170
|
+
for (const [index, handler] of handlers.entries()) {
|
|
171
|
+
let returned;
|
|
172
|
+
const snapshot = snapshotArrays({ tools });
|
|
173
|
+
try {
|
|
174
|
+
// Each handler sees the previous handler's output, so an `allow`
|
|
175
|
+
// rewrite is what the next one judges — and what the loop runs.
|
|
176
|
+
//
|
|
177
|
+
// Invoke synchronously and await ONLY a real thenable. The two built-in
|
|
178
|
+
// handlers ahead of the plugin entries answer synchronously; awaiting
|
|
179
|
+
// them would yield a microtask before a plugin wrapper is invoked, and
|
|
180
|
+
// its abort subscription is installed when it is called. An abort
|
|
181
|
+
// landing in that gap would then be missed and a never-settling plugin
|
|
182
|
+
// handler would run to its deadline instead of blocking promptly
|
|
183
|
+
// (nax#2151 US-003). A thenable is still awaited inside the try, so a
|
|
184
|
+
// rejection is caught exactly like a throw (spec 4.2).
|
|
185
|
+
const candidate = handler({
|
|
186
|
+
call: { ...call, input: input ?? call.input },
|
|
187
|
+
tools,
|
|
188
|
+
});
|
|
189
|
+
returned = isThenable(candidate) ? await candidate : candidate;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
getSafeLogger()?.warn("native-loop-events", "before_tool handler threw; skipping it", {
|
|
193
|
+
tool: call.name,
|
|
194
|
+
error: errorMessage(err),
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
restoreMutated(snapshot, "before_tool", index);
|
|
200
|
+
}
|
|
201
|
+
const outcome = asOutcome(returned, call.name);
|
|
202
|
+
if (outcome.kind === "block" || outcome.kind === "terminate")
|
|
203
|
+
return outcome;
|
|
204
|
+
if (outcome.kind === "nudge")
|
|
205
|
+
nudgeText = outcome.text;
|
|
206
|
+
if (outcome.input !== undefined)
|
|
207
|
+
input = outcome.input;
|
|
208
|
+
}
|
|
209
|
+
// The nudge carries the accumulated rewrite: a nudged call still runs, so
|
|
210
|
+
// dropping the rewrite would run the model's uncorrected input. The
|
|
211
|
+
// built-in chain produces this pair — the invalid-call repair strips a
|
|
212
|
+
// `null` optional, then the spin breaker nudges a repeat of it (nax#2200).
|
|
213
|
+
if (nudgeText !== undefined) {
|
|
214
|
+
return input === undefined ? { kind: "nudge", text: nudgeText } : { kind: "nudge", text: nudgeText, input };
|
|
215
|
+
}
|
|
216
|
+
return input === undefined ? { kind: "allow" } : { kind: "allow", input };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The seven field-patch events. Serial `for`, each handler awaited INSIDE the
|
|
220
|
+
* try (spec 4.2), patches accumulated in registration order, each handler
|
|
221
|
+
* seeing the previous one's output as the payload it is handed.
|
|
222
|
+
*/
|
|
223
|
+
async function dispatchChain(event, handlers, payload) {
|
|
224
|
+
if (handlers.length === 0) {
|
|
225
|
+
// The empty fast path returns BEFORE touching the payload — never clone:
|
|
226
|
+
// transform_context fires before every request against a 200k+ token
|
|
227
|
+
// array, and cloning for an empty chain would make every request
|
|
228
|
+
// allocate a full copy for nothing (spec 4.4).
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
const fields = PATCHABLE_FIELDS[event];
|
|
232
|
+
let current = payload;
|
|
233
|
+
let accumulated = {};
|
|
234
|
+
for (const [index, handler] of handlers.entries()) {
|
|
235
|
+
const snapshot = snapshotArrays(current);
|
|
236
|
+
let returned;
|
|
237
|
+
try {
|
|
238
|
+
// The await sits inside the try, so a rejected promise is caught
|
|
239
|
+
// exactly like a throw (spec 4.2).
|
|
240
|
+
returned = await handler(current);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
// When the payload carries a tool name (after_tool does), the warn
|
|
244
|
+
// names the tool the failing handler was shaping — the context the
|
|
245
|
+
// first-argument dispatcher carried as `tool: call.name` before the
|
|
246
|
+
// single-payload signature replaced it.
|
|
247
|
+
const { toolName } = payload;
|
|
248
|
+
getSafeLogger()?.warn("native-loop-events", `${event} handler threw; skipping it`, {
|
|
249
|
+
event,
|
|
250
|
+
...(toolName !== undefined ? { tool: toolName } : {}),
|
|
251
|
+
error: errorMessage(err),
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
restoreMutated(snapshot, event, index);
|
|
257
|
+
}
|
|
258
|
+
const picked = pickPatchFields(returned, fields);
|
|
259
|
+
if (Object.keys(picked).length > 0) {
|
|
260
|
+
// `before_request.options` is itself a partial patch. Preserve fields
|
|
261
|
+
// supplied by earlier handlers so independently registered policy hooks
|
|
262
|
+
// compose (for example, one can set temperature while another disables
|
|
263
|
+
// thinking for a retry).
|
|
264
|
+
const merged = event === "before_request" && typeof picked.options === "object" && picked.options !== null
|
|
265
|
+
? { ...picked, options: { ...current.options, ...picked.options } }
|
|
266
|
+
: picked;
|
|
267
|
+
accumulated = { ...accumulated, ...merged };
|
|
268
|
+
// Only patch-declared fields ever overwrite anything, so the next
|
|
269
|
+
// handler sees the previous handler's output and nothing else moved.
|
|
270
|
+
current = { ...current, ...merged };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Sound: `accumulated` holds only fields PATCHABLE_FIELDS declares for this
|
|
274
|
+
// event, which is exactly what PatchOf<E> declares.
|
|
275
|
+
return accumulated;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Registration order is the chain order within each event; events are
|
|
279
|
+
* independent chains. The built-ins register on the same registry the loop
|
|
280
|
+
* dispatches from (see ./loop-handlers for their per-turn lifetime).
|
|
281
|
+
*/
|
|
282
|
+
export function createLoopEventRegistry() {
|
|
283
|
+
const byEvent = new Map();
|
|
284
|
+
return {
|
|
285
|
+
register(event, handler) {
|
|
286
|
+
// Erase E for storage: the map keys by event, and dispatch only ever
|
|
287
|
+
// calls a handler back with the payload type it registered for, so the
|
|
288
|
+
// widening loses nothing at the call sites.
|
|
289
|
+
const erased = handler;
|
|
290
|
+
const handlers = byEvent.get(event);
|
|
291
|
+
if (handlers === undefined) {
|
|
292
|
+
byEvent.set(event, [erased]);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
handlers.push(erased);
|
|
296
|
+
},
|
|
297
|
+
async dispatch(event, payload) {
|
|
298
|
+
const handlers = byEvent.get(event) ?? [];
|
|
299
|
+
if (event === "before_tool") {
|
|
300
|
+
return dispatchBeforeTool(handlers, payload);
|
|
301
|
+
}
|
|
302
|
+
return dispatchChain(event, handlers, payload);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native session's loop-event map (nax#2151, P3).
|
|
3
|
+
*
|
|
4
|
+
* pi's `before_run`/`before_run_end` are `before_turn`/`before_turn_end` here:
|
|
5
|
+
* `runNativeTurn` is ONE TURN, while a nax "run" holds many stories each
|
|
6
|
+
* holding many turns, and `src/hooks/` already fires genuine run-level events.
|
|
7
|
+
* Keeping the vocabularies disjoint is the same reasoning ADR-030's D11 applied
|
|
8
|
+
* to the bash mode axis.
|
|
9
|
+
*
|
|
10
|
+
* `before_payload`, `before_drive` and `before_navigation` are deliberately
|
|
11
|
+
* absent: the payload is built inside nax-ai behind the import boundary, and
|
|
12
|
+
* nax has no branch navigation.
|
|
13
|
+
*/
|
|
14
|
+
import type { ThinkingBlock, ToolCall, ToolDefinition } from "@nathapp/nax-ai";
|
|
15
|
+
import type { TokenUsage } from "#src/cost/standard-types";
|
|
16
|
+
import type { TranscriptMessage as NativeTranscriptMessage } from "../compaction.ts";
|
|
17
|
+
import type { DenialInfo } from "../tool-result.ts";
|
|
18
|
+
export type LoopEvent = "before_tool" | "after_tool" | "before_turn" | "transform_context" | "before_request" | "after_response" | "before_compaction" | "before_turn_end";
|
|
19
|
+
export interface BeforeToolPayload {
|
|
20
|
+
readonly call: ToolCall;
|
|
21
|
+
readonly tools: readonly ToolDefinition[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `before_tool` outcomes:
|
|
25
|
+
*
|
|
26
|
+
* - `allow` optionally rewrites the tool's input; later handlers see the
|
|
27
|
+
* rewrite, and the loop records it in the transcript and invokes the tool
|
|
28
|
+
* with it.
|
|
29
|
+
* - `nudge` still invokes the tool, and prefixes its result with the handler's
|
|
30
|
+
* text. `input` carries any `allow` rewrite accumulated earlier in the chain,
|
|
31
|
+
* so a nudge never drops a correction — the null-optional strip ahead of the
|
|
32
|
+
* spin breaker is the built-in pair (nax#2200).
|
|
33
|
+
* - `block` answers without invoking the tool. `input` optionally carries the
|
|
34
|
+
* input the transcript should record instead, which is how the invalid-call
|
|
35
|
+
* repair keeps the rejected value out of history (nax#2047) — recording the
|
|
36
|
+
* model's call minus that property, never a fabricated one (nax#2200).
|
|
37
|
+
* - `terminate` answers every outstanding call in the current batch — the spin
|
|
38
|
+
* breaker's stop is a batch-level outcome, not a per-call one, and a batch
|
|
39
|
+
* left with an unanswered `tool_call` is rejected by strict providers.
|
|
40
|
+
*/
|
|
41
|
+
export type BeforeToolOutcome = {
|
|
42
|
+
kind: "allow";
|
|
43
|
+
input?: Record<string, unknown>;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "nudge";
|
|
46
|
+
text: string;
|
|
47
|
+
input?: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "block";
|
|
50
|
+
content: string;
|
|
51
|
+
isError?: boolean;
|
|
52
|
+
input?: Record<string, unknown>;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "terminate";
|
|
55
|
+
content: string;
|
|
56
|
+
isError?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* What an `after_tool` handler may change. Deliberately partial: a handler
|
|
60
|
+
* returns only the fields it wants patched.
|
|
61
|
+
*
|
|
62
|
+
* `denied` is NOT patchable — a refused Write is not a crashed Write (ADR-029
|
|
63
|
+
* s5), and letting a handler flip that erases a distinction the model is meant
|
|
64
|
+
* to act on.
|
|
65
|
+
*/
|
|
66
|
+
export type AfterToolPatch = {
|
|
67
|
+
content?: string;
|
|
68
|
+
isError?: boolean;
|
|
69
|
+
};
|
|
70
|
+
export interface AfterToolPayload {
|
|
71
|
+
readonly content: string;
|
|
72
|
+
readonly isError?: boolean;
|
|
73
|
+
/** Surfaced to handlers, never writable by them. */
|
|
74
|
+
readonly denied?: DenialInfo;
|
|
75
|
+
/** The name of the tool call the result answers, from the dispatched call. */
|
|
76
|
+
readonly toolName?: string;
|
|
77
|
+
/** The provider tool-call id the result answers, from the dispatched call. */
|
|
78
|
+
readonly callId?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Present when `before_tool` produced a nudge that will be PREPENDED to the
|
|
81
|
+
* surviving content. The model-facing truncation handler spends these bytes
|
|
82
|
+
* out of the result's own budget (`nudgeOverheadBytes`), so the ceiling
|
|
83
|
+
* stays unconditional — without them a nudged result would exceed
|
|
84
|
+
* `MODEL_MAX_BYTES` by exactly the nudge's width.
|
|
85
|
+
*/
|
|
86
|
+
readonly nudgeText?: string;
|
|
87
|
+
}
|
|
88
|
+
/** `before_turn` — fires once as a turn starts, after the transcript loads. */
|
|
89
|
+
export interface BeforeTurnPayload {
|
|
90
|
+
readonly prompt: string;
|
|
91
|
+
/** The loaded transcript. Readonly: only a boundary rewrite may touch it. */
|
|
92
|
+
readonly history: readonly NativeTranscriptMessage[];
|
|
93
|
+
readonly sessionName: string;
|
|
94
|
+
/** Dispatcher-computed (spec 3.4). A handler may never assert a boundary. */
|
|
95
|
+
readonly boundary: boolean;
|
|
96
|
+
}
|
|
97
|
+
export interface BeforeTurnPatch {
|
|
98
|
+
/** The seed messages being appended now. */
|
|
99
|
+
readonly seed?: readonly NativeTranscriptMessage[];
|
|
100
|
+
/** Honoured ONLY when payload.boundary is true (spec 3.2, 6.1). */
|
|
101
|
+
readonly history?: readonly NativeTranscriptMessage[];
|
|
102
|
+
}
|
|
103
|
+
/** `transform_context` — fires before every provider request attempt. */
|
|
104
|
+
export interface TransformContextPayload {
|
|
105
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
106
|
+
readonly tools: readonly ToolDefinition[];
|
|
107
|
+
readonly model?: string;
|
|
108
|
+
readonly anchorIndex?: number;
|
|
109
|
+
readonly boundary: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface TransformContextPatch {
|
|
112
|
+
readonly messages?: readonly NativeTranscriptMessage[];
|
|
113
|
+
}
|
|
114
|
+
/** `before_request` — per request ATTEMPT, including transport retries. */
|
|
115
|
+
export interface BeforeRequestPayload {
|
|
116
|
+
readonly model?: string;
|
|
117
|
+
readonly roundTrip: number;
|
|
118
|
+
/** 1 for the first attempt, 2..n inside retryTransportFault. */
|
|
119
|
+
readonly attempt: number;
|
|
120
|
+
readonly options: CompleteCallOptions;
|
|
121
|
+
}
|
|
122
|
+
export interface BeforeRequestPatch {
|
|
123
|
+
readonly options?: Partial<CompleteCallOptions>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The per-call options bag added to TurnDeps.complete in Task 4 (spec 6.3).
|
|
127
|
+
* Minimal and additive by ruling: P6's extraction inherits one optional
|
|
128
|
+
* parameter, not a new concept.
|
|
129
|
+
*/
|
|
130
|
+
export interface CompleteCallOptions {
|
|
131
|
+
readonly thinking?: boolean;
|
|
132
|
+
readonly temperature?: number;
|
|
133
|
+
}
|
|
134
|
+
/** `after_response` — fires on a settled assistant message. */
|
|
135
|
+
export interface AfterResponsePayload {
|
|
136
|
+
readonly text: string;
|
|
137
|
+
readonly toolCalls?: readonly ToolCall[];
|
|
138
|
+
readonly thinking?: readonly ThinkingBlock[];
|
|
139
|
+
/** Surfaced, NEVER patchable: billing truth is not a handler's to rewrite. */
|
|
140
|
+
readonly usage: TokenUsage;
|
|
141
|
+
readonly costUsd: number;
|
|
142
|
+
readonly roundTrip: number;
|
|
143
|
+
}
|
|
144
|
+
export interface AfterResponsePatch {
|
|
145
|
+
readonly text?: string;
|
|
146
|
+
readonly toolCalls?: readonly ToolCall[];
|
|
147
|
+
readonly thinking?: readonly ThinkingBlock[];
|
|
148
|
+
}
|
|
149
|
+
/** `before_compaction` — fires in both the proactive and overflow branches. */
|
|
150
|
+
export interface BeforeCompactionPayload {
|
|
151
|
+
readonly reason: "proactive" | "overflow";
|
|
152
|
+
readonly toSummarize: readonly NativeTranscriptMessage[];
|
|
153
|
+
readonly previousSummary?: string;
|
|
154
|
+
readonly estimatedTokens: number;
|
|
155
|
+
}
|
|
156
|
+
export interface BeforeCompactionPatch {
|
|
157
|
+
/** Honoured when reason is "proactive", IGNORED + logged when "overflow". */
|
|
158
|
+
readonly decline?: boolean;
|
|
159
|
+
/** A replacement summary, skipping the summarizer call. */
|
|
160
|
+
readonly summary?: string;
|
|
161
|
+
}
|
|
162
|
+
/** `before_turn_end` — fires before the final saveTranscript. */
|
|
163
|
+
export interface BeforeTurnEndPayload {
|
|
164
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
165
|
+
readonly roundTrips: number;
|
|
166
|
+
/**
|
|
167
|
+
* How the turn ended. "completed" includes a stop (spin, invalid-call budget,
|
|
168
|
+
* deadline) -- see `stopped`. "aborted" means the turn signal fired (caller,
|
|
169
|
+
* idle watchdog or whole-turn deadline); "errored" is any other throw.
|
|
170
|
+
* On "aborted"/"errored" the result is ignored (review #20).
|
|
171
|
+
*/
|
|
172
|
+
readonly ended: "completed" | "aborted" | "errored";
|
|
173
|
+
/** True when the turn ended by a STOP; followUp is not offered then. */
|
|
174
|
+
readonly stopped: boolean;
|
|
175
|
+
readonly followUpsSoFar: number;
|
|
176
|
+
}
|
|
177
|
+
export interface BeforeTurnEndPatch {
|
|
178
|
+
/** Re-enters the loop with another user turn. Capped; see registry.ts. */
|
|
179
|
+
readonly followUp?: string;
|
|
180
|
+
}
|
|
181
|
+
export interface LoopEventMap {
|
|
182
|
+
before_tool: {
|
|
183
|
+
payload: BeforeToolPayload;
|
|
184
|
+
patch: BeforeToolOutcome;
|
|
185
|
+
};
|
|
186
|
+
after_tool: {
|
|
187
|
+
payload: AfterToolPayload;
|
|
188
|
+
patch: AfterToolPatch;
|
|
189
|
+
};
|
|
190
|
+
before_turn: {
|
|
191
|
+
payload: BeforeTurnPayload;
|
|
192
|
+
patch: BeforeTurnPatch;
|
|
193
|
+
};
|
|
194
|
+
transform_context: {
|
|
195
|
+
payload: TransformContextPayload;
|
|
196
|
+
patch: TransformContextPatch;
|
|
197
|
+
};
|
|
198
|
+
before_request: {
|
|
199
|
+
payload: BeforeRequestPayload;
|
|
200
|
+
patch: BeforeRequestPatch;
|
|
201
|
+
};
|
|
202
|
+
after_response: {
|
|
203
|
+
payload: AfterResponsePayload;
|
|
204
|
+
patch: AfterResponsePatch;
|
|
205
|
+
};
|
|
206
|
+
before_compaction: {
|
|
207
|
+
payload: BeforeCompactionPayload;
|
|
208
|
+
patch: BeforeCompactionPatch;
|
|
209
|
+
};
|
|
210
|
+
before_turn_end: {
|
|
211
|
+
payload: BeforeTurnEndPayload;
|
|
212
|
+
patch: BeforeTurnEndPatch;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export type PayloadOf<E extends LoopEvent> = LoopEventMap[E]["payload"];
|
|
216
|
+
export type PatchOf<E extends LoopEvent> = LoopEventMap[E]["patch"];
|
|
217
|
+
export type HandlerOf<E extends LoopEvent> = (payload: PayloadOf<E>) => PatchOf<E> | Promise<PatchOf<E>>;
|
|
218
|
+
/**
|
|
219
|
+
* The read-only facts a plugin handler is handed alongside its payload.
|
|
220
|
+
*
|
|
221
|
+
* Owned by the coding-agent side on purpose: a plugin imports these types, and
|
|
222
|
+
* native code never imports the plugin system. Everything here is a fact about
|
|
223
|
+
* the session the handler is running in — there is deliberately no abort, no UI
|
|
224
|
+
* and no config access, so a plugin scopes itself by reading this at runtime
|
|
225
|
+
* rather than by declaring filters.
|
|
226
|
+
*/
|
|
227
|
+
export interface LoopHandlerContext {
|
|
228
|
+
readonly sessionName: string;
|
|
229
|
+
readonly role?: string;
|
|
230
|
+
readonly storyId?: string;
|
|
231
|
+
readonly feature?: string;
|
|
232
|
+
readonly workdir?: string;
|
|
233
|
+
readonly model?: string;
|
|
234
|
+
readonly provider?: string;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A plugin's handler for one event: same payload and patch as a built-in,
|
|
238
|
+
* plus the context, and `undefined` is a valid "no patch" answer.
|
|
239
|
+
*/
|
|
240
|
+
export type ExternalHandlerOf<E extends LoopEvent> = (payload: PayloadOf<E>, ctx: LoopHandlerContext) => PatchOf<E> | undefined | Promise<PatchOf<E> | undefined>;
|
|
241
|
+
/** One staged registration: which plugin it came from, its event, its handler. */
|
|
242
|
+
export interface LoopHandlerEntry {
|
|
243
|
+
readonly plugin: string;
|
|
244
|
+
readonly event: LoopEvent;
|
|
245
|
+
readonly handler: ExternalHandlerOf<LoopEvent>;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Every plugin-contributed handler for a run, in install order. The registry
|
|
249
|
+
* returns this frozen, and an empty set is the normal case.
|
|
250
|
+
*/
|
|
251
|
+
export type LoopHandlerSet = readonly LoopHandlerEntry[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native session's loop-event map (nax#2151, P3).
|
|
3
|
+
*
|
|
4
|
+
* pi's `before_run`/`before_run_end` are `before_turn`/`before_turn_end` here:
|
|
5
|
+
* `runNativeTurn` is ONE TURN, while a nax "run" holds many stories each
|
|
6
|
+
* holding many turns, and `src/hooks/` already fires genuine run-level events.
|
|
7
|
+
* Keeping the vocabularies disjoint is the same reasoning ADR-030's D11 applied
|
|
8
|
+
* to the bash mode axis.
|
|
9
|
+
*
|
|
10
|
+
* `before_payload`, `before_drive` and `before_navigation` are deliberately
|
|
11
|
+
* absent: the payload is built inside nax-ai behind the import boundary, and
|
|
12
|
+
* nax has no branch navigation.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|