@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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two compaction steps of the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* `runProactiveCompaction` runs before a round trip when the estimate crosses
|
|
5
|
+
* the threshold; `runOverflowCompaction` is the backstop when a round trip
|
|
6
|
+
* throws a context overflow anyway. They are deliberately TWO functions, not one
|
|
7
|
+
* with a `reactive` flag, because they differ in exactly three ways a boolean
|
|
8
|
+
* would hide:
|
|
9
|
+
*
|
|
10
|
+
* 1. the backstop halves the keep budget (`keepBudget(..., true)`);
|
|
11
|
+
* 2. a summarizer throw is swallowed by the proactive step (the request may
|
|
12
|
+
* still fit) and propagates out of the backstop;
|
|
13
|
+
* 3. only the proactive step logs its progress (Finding 2, whole-branch
|
|
14
|
+
* review 2026-09-04);
|
|
15
|
+
* 4. a `before_compaction` decline is HONOURED proactively — the branch runs
|
|
16
|
+
* before any request, so sending uncompacted is a legitimate outcome — and
|
|
17
|
+
* IGNORED + logged at overflow, where the request has already failed with
|
|
18
|
+
* a context overflow and declining would leave no recovery (spec 6.2).
|
|
19
|
+
*
|
|
20
|
+
* They share only the summarize-and-apply core below. The failure semantics
|
|
21
|
+
* stay with the callers on purpose — see each function's catch.
|
|
22
|
+
*/
|
|
23
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
24
|
+
import { applyCompaction, estimateContextTokens, keepBudget, prepareCompaction, } from "./compaction.js";
|
|
25
|
+
import { usageBeat } from "./turn-accumulator.js";
|
|
26
|
+
/**
|
|
27
|
+
* The half both branches share: summarize the dropped span, then apply the
|
|
28
|
+
* summary to the transcript. A throw here is left to propagate — catching it is
|
|
29
|
+
* the proactive caller's decision, not this function's.
|
|
30
|
+
*/
|
|
31
|
+
async function summarizeAndApply(messages, plan, deps, source = {}) {
|
|
32
|
+
const summary = source.replacementSummary !== undefined
|
|
33
|
+
? { text: source.replacementSummary, usage: { inputTokens: 0, outputTokens: 0 }, costUsd: 0 }
|
|
34
|
+
: await deps.summarize(plan.toSummarize, plan.previousSummary);
|
|
35
|
+
// Rebound, not spliced in place: `messages` is a local accumulator and
|
|
36
|
+
// rebinding it keeps the compacted array a fresh value.
|
|
37
|
+
const compacted = applyCompaction(messages, plan, summary.text);
|
|
38
|
+
return { messages: compacted, summary };
|
|
39
|
+
}
|
|
40
|
+
export async function runProactiveCompaction(args) {
|
|
41
|
+
const { messages, usage, sessionName, lastUsage, anchorIndex, deps, signal, loopEvents } = args;
|
|
42
|
+
const preCompactionTokens = estimateContextTokens(messages, lastUsage, anchorIndex);
|
|
43
|
+
const plan = prepareCompaction(messages, keepBudget(deps.contextWindow, deps.compaction));
|
|
44
|
+
if (plan === undefined) {
|
|
45
|
+
return { messages, compacted: false, summarizeFailed: false };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const patch = await loopEvents.dispatch("before_compaction", {
|
|
49
|
+
reason: "proactive",
|
|
50
|
+
toSummarize: plan.toSummarize,
|
|
51
|
+
...(plan.previousSummary !== undefined ? { previousSummary: plan.previousSummary } : {}),
|
|
52
|
+
estimatedTokens: preCompactionTokens,
|
|
53
|
+
});
|
|
54
|
+
// Nothing has been requested yet, so sending uncompacted is a legitimate
|
|
55
|
+
// outcome — the decline is HONOURED here (spec 6.2). `summarizeFailed`
|
|
56
|
+
// stays false: the overflow backstop must remain armed for the uncompacted
|
|
57
|
+
// try. A decline wins over a replacement summary — not compacting at all
|
|
58
|
+
// subsumes replacing what the summarizer would say.
|
|
59
|
+
if (patch.decline === true) {
|
|
60
|
+
return { messages, compacted: false, summarizeFailed: false };
|
|
61
|
+
}
|
|
62
|
+
const applied = await summarizeAndApply(messages, plan, deps, { replacementSummary: patch.summary });
|
|
63
|
+
// Finding 2 (whole-branch review, 2026-09-04): a previous-summary merge
|
|
64
|
+
// can produce a same-size (or larger) array — a paid model call that
|
|
65
|
+
// shrank nothing. Not fatal (the reactive backstop is the real safety
|
|
66
|
+
// net if this repeats into an overflow) but worth surfacing, since it
|
|
67
|
+
// would otherwise burn a model call every round trip with no signal.
|
|
68
|
+
const postCompactionTokens = estimateContextTokens(applied.messages, undefined, undefined);
|
|
69
|
+
if (postCompactionTokens >= preCompactionTokens) {
|
|
70
|
+
getSafeLogger()?.warn("native-adapter", "compaction made no size progress", {
|
|
71
|
+
sessionName,
|
|
72
|
+
preCompactionTokens,
|
|
73
|
+
postCompactionTokens,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
getSafeLogger()?.info("native-adapter", "compaction completed", {
|
|
77
|
+
sessionName,
|
|
78
|
+
// Keep token counts under the plural `tokens` metric key so the
|
|
79
|
+
// logger's credential redactor does not mistake them for secrets.
|
|
80
|
+
tokens: { before: preCompactionTokens, after: postCompactionTokens },
|
|
81
|
+
messagesDropped: plan.toSummarize.length,
|
|
82
|
+
summaryLength: applied.summary.text.length,
|
|
83
|
+
});
|
|
84
|
+
usage.add(applied.summary.usage, applied.summary.costUsd, applied.summary.rates);
|
|
85
|
+
// Resets the watchdog's lastActivityAt between the summary and the
|
|
86
|
+
// round trip, so the two silent spans do not add up against one budget.
|
|
87
|
+
deps.onActivity?.(usageBeat(applied.summary.usage, applied.summary.costUsd));
|
|
88
|
+
return { messages: applied.messages, compacted: true, summarizeFailed: false };
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (deps.deadline?.expired() === true || signal?.aborted === true)
|
|
92
|
+
throw err;
|
|
93
|
+
// Not fatal: the request may still fit, and if it does not it fails
|
|
94
|
+
// through the path #1837 and #1839 made correct. Killing a story
|
|
95
|
+
// because a summarizer hiccuped would be worse than the problem.
|
|
96
|
+
getSafeLogger()?.warn("native-adapter", "compaction summary failed; sending uncompacted", {
|
|
97
|
+
sessionName,
|
|
98
|
+
error: err instanceof Error ? err.message : String(err),
|
|
99
|
+
});
|
|
100
|
+
return { messages, compacted: false, summarizeFailed: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function runOverflowCompaction(args) {
|
|
104
|
+
const { messages, usage, sessionName, lastUsage, anchorIndex, deps, error, loopEvents } = args;
|
|
105
|
+
// Same code path, half the keep budget. Not a second algorithm.
|
|
106
|
+
const plan = prepareCompaction(messages, keepBudget(deps.contextWindow, deps.compaction, true));
|
|
107
|
+
if (plan === undefined)
|
|
108
|
+
throw error;
|
|
109
|
+
const patch = await loopEvents.dispatch("before_compaction", {
|
|
110
|
+
reason: "overflow",
|
|
111
|
+
toSummarize: plan.toSummarize,
|
|
112
|
+
...(plan.previousSummary !== undefined ? { previousSummary: plan.previousSummary } : {}),
|
|
113
|
+
estimatedTokens: estimateContextTokens(messages, lastUsage, anchorIndex),
|
|
114
|
+
});
|
|
115
|
+
// The request has ALREADY failed with a context overflow: there is no
|
|
116
|
+
// uncompacted path left, so a decline has nothing to decline into and
|
|
117
|
+
// honouring it would kill the story. The signal stops, the compaction does
|
|
118
|
+
// not (spec 6.2), and the ignore is on the record.
|
|
119
|
+
if (patch.decline === true) {
|
|
120
|
+
getSafeLogger()?.warn("native-loop-events", "before_compaction decline ignored at overflow", {
|
|
121
|
+
sessionName,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const applied = await summarizeAndApply(messages, plan, deps, { replacementSummary: patch.summary });
|
|
125
|
+
usage.add(applied.summary.usage, applied.summary.costUsd, applied.summary.rates);
|
|
126
|
+
deps.onActivity?.(usageBeat(applied.summary.usage, applied.summary.costUsd));
|
|
127
|
+
return { messages: applied.messages, compacted: true, summarizeFailed: false };
|
|
128
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete-with-recovery step of the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* `deps.complete` plus its two recoveries — the bounded transport-fault retry
|
|
5
|
+
* (nax#1870) and the context-overflow compaction backstop — live here as one
|
|
6
|
+
* site: one round trip is issued, and a throw gets exactly one of the two
|
|
7
|
+
* recoveries. They were two branches of the loop's single try/catch; folding
|
|
8
|
+
* them into a step is what will let one seam observe every round trip. P3
|
|
9
|
+
* attaches `transform_context` and `before_request` here: both fire per request
|
|
10
|
+
* attempt from the private `request()` wrapper below (spec 6.5, 6.6) —
|
|
11
|
+
* before_request shapes the per-call options, transform_context shapes the WIRE
|
|
12
|
+
* COPY only, and the array the caller holds (which saveTranscript persists) is
|
|
13
|
+
* returned untouched. An honoured rewrite is reported as `honoured`, with the
|
|
14
|
+
* boundary fact that produced it as `boundary`, so the caller can clear the
|
|
15
|
+
* cache anchor exactly when spec 3.6 says to — a prefix-stable honour leaves
|
|
16
|
+
* the anchor valid (spec 6.6).
|
|
17
|
+
*
|
|
18
|
+
* The private `isContextOverflow` guard is this module's alone: it decides
|
|
19
|
+
* which recovery a thrown round trip gets.
|
|
20
|
+
*/
|
|
21
|
+
import type { TranscriptMessage as NativeTranscriptMessage } from "./compaction.ts";
|
|
22
|
+
import type { LoopEventRegistry } from "./loop-events/index.ts";
|
|
23
|
+
import type { TurnAccumulator } from "./turn-accumulator.ts";
|
|
24
|
+
import type { NativeTurnResponse, TurnDeps } from "./turn-types.ts";
|
|
25
|
+
export interface CompleteStepResult {
|
|
26
|
+
readonly res: NativeTurnResponse;
|
|
27
|
+
/** The transcript after recovery; rebound only by the overflow branch. */
|
|
28
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
29
|
+
/** True when the overflow branch ran: it always rebinds messages (and the caller clears lastUsage/anchorIndex). */
|
|
30
|
+
readonly compacted: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* True when the provider was sent an honoured `transform_context` rewrite
|
|
33
|
+
* (spec 6.6) — the last attempt to reach the wire is the one that counts.
|
|
34
|
+
* The caller clears lastUsage/anchorIndex on it ONLY together with
|
|
35
|
+
* `boundary`: a prefix-stable honour leaves the anchor valid (spec 6.6 —
|
|
36
|
+
* wire and persisted prefix are reference-identical, nothing to
|
|
37
|
+
* invalidate), while a boundary-exempt honour changed the prefix the
|
|
38
|
+
* provider saw even though the saved array did not (spec 3.6).
|
|
39
|
+
*/
|
|
40
|
+
readonly honoured: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* True when the honoured rewrite (if any) rode the boundary exemption —
|
|
43
|
+
* the post-compaction retry is the only boundary this step can produce
|
|
44
|
+
* (`compacted`; spec 3.4: a handler may never assert one). Paired with
|
|
45
|
+
* `honoured` rather than folded into it: the step-level contract keeps both
|
|
46
|
+
* facts.
|
|
47
|
+
*/
|
|
48
|
+
readonly boundary: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface CompleteStepArgs {
|
|
51
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
52
|
+
readonly tools: Parameters<TurnDeps["complete"]>[1];
|
|
53
|
+
readonly usage: TurnAccumulator;
|
|
54
|
+
/**
|
|
55
|
+
* Set by the proactive step for this round trip: the summarizer already threw,
|
|
56
|
+
* so the overflow retry must not try again.
|
|
57
|
+
*/
|
|
58
|
+
readonly summarizeFailed: boolean;
|
|
59
|
+
readonly sessionName: string;
|
|
60
|
+
readonly lastUsage: {
|
|
61
|
+
readonly promptTokens: number;
|
|
62
|
+
} | undefined;
|
|
63
|
+
readonly anchorIndex: number | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* The registry actually in use — the loop's local (`deps.loopEvents ??
|
|
66
|
+
* createLoopEventRegistry()`), not `deps.loopEvents` itself, which is
|
|
67
|
+
* usually absent and would dispatch to nothing.
|
|
68
|
+
*/
|
|
69
|
+
readonly loopEvents: LoopEventRegistry;
|
|
70
|
+
/** The round trip this request belongs to, as counted by the loop. */
|
|
71
|
+
readonly roundTrip: number;
|
|
72
|
+
/** The session's resolved model; undefined when driven without a modelDef. */
|
|
73
|
+
readonly model?: string;
|
|
74
|
+
readonly deps: TurnDeps;
|
|
75
|
+
readonly signal?: AbortSignal;
|
|
76
|
+
}
|
|
77
|
+
export declare function completeWithRecovery(args: CompleteStepArgs): Promise<CompleteStepResult>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete-with-recovery step of the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* `deps.complete` plus its two recoveries — the bounded transport-fault retry
|
|
5
|
+
* (nax#1870) and the context-overflow compaction backstop — live here as one
|
|
6
|
+
* site: one round trip is issued, and a throw gets exactly one of the two
|
|
7
|
+
* recoveries. They were two branches of the loop's single try/catch; folding
|
|
8
|
+
* them into a step is what will let one seam observe every round trip. P3
|
|
9
|
+
* attaches `transform_context` and `before_request` here: both fire per request
|
|
10
|
+
* attempt from the private `request()` wrapper below (spec 6.5, 6.6) —
|
|
11
|
+
* before_request shapes the per-call options, transform_context shapes the WIRE
|
|
12
|
+
* COPY only, and the array the caller holds (which saveTranscript persists) is
|
|
13
|
+
* returned untouched. An honoured rewrite is reported as `honoured`, with the
|
|
14
|
+
* boundary fact that produced it as `boundary`, so the caller can clear the
|
|
15
|
+
* cache anchor exactly when spec 3.6 says to — a prefix-stable honour leaves
|
|
16
|
+
* the anchor valid (spec 6.6).
|
|
17
|
+
*
|
|
18
|
+
* The private `isContextOverflow` guard is this module's alone: it decides
|
|
19
|
+
* which recovery a thrown round trip gets.
|
|
20
|
+
*/
|
|
21
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
22
|
+
import { applyHistoryPatch } from "./loop-events/cache-boundary.js";
|
|
23
|
+
import { runOverflowCompaction } from "./turn-compaction-step.js";
|
|
24
|
+
import { realSleep, retryTransportFault } from "./turn-retry.js";
|
|
25
|
+
/**
|
|
26
|
+
* Structural, matching adapter-deps.ts's guard: nax-ai's error class is not
|
|
27
|
+
* importable here and the kind is what matters.
|
|
28
|
+
*/
|
|
29
|
+
function isContextOverflow(err) {
|
|
30
|
+
if (typeof err !== "object" || err === null || !("protocolError" in err))
|
|
31
|
+
return false;
|
|
32
|
+
const { protocolError } = err;
|
|
33
|
+
return protocolError?.kind === "context-overflow";
|
|
34
|
+
}
|
|
35
|
+
export async function completeWithRecovery(args) {
|
|
36
|
+
const { tools, usage, summarizeFailed, sessionName, lastUsage, anchorIndex, loopEvents, roundTrip, model, deps, signal, } = args;
|
|
37
|
+
let messages = args.messages;
|
|
38
|
+
let compacted = false;
|
|
39
|
+
let honoured = false;
|
|
40
|
+
let res;
|
|
41
|
+
// Model, thinking level and timeout are bound in the adapter's closure above
|
|
42
|
+
// the loop (spec 6.3), so the bag this dispatches starts empty; a handler's
|
|
43
|
+
// patch is the only thing that ever fills it.
|
|
44
|
+
const baseOptions = {};
|
|
45
|
+
// One wrapper, three call sites (spec 6.5). The wrapper owns `attempt`, so
|
|
46
|
+
// the retry machinery reports 2..n without knowing an event exists.
|
|
47
|
+
let attempt = 0;
|
|
48
|
+
const request = async (msgs) => {
|
|
49
|
+
attempt += 1;
|
|
50
|
+
const patch = await loopEvents.dispatch("before_request", {
|
|
51
|
+
...(model !== undefined ? { model } : {}),
|
|
52
|
+
roundTrip,
|
|
53
|
+
attempt,
|
|
54
|
+
options: baseOptions,
|
|
55
|
+
});
|
|
56
|
+
const options = patch.options === undefined ? baseOptions : { ...baseOptions, ...patch.options };
|
|
57
|
+
// transform_context fires here too, per attempt (spec 6.6). The patch
|
|
58
|
+
// shapes ONLY the wire copy handed to deps.complete below: `msgs` — the
|
|
59
|
+
// caller's array, the one saveTranscript persists — is returned untouched,
|
|
60
|
+
// so the transcript stays the true record. `boundary` is this step's
|
|
61
|
+
// overflow fact (`compacted`): true only on the post-compaction retry,
|
|
62
|
+
// where a prefix rewrite is free. A model change never reaches this step:
|
|
63
|
+
// the transcript store refuses another model's history (spec 8.3).
|
|
64
|
+
const transformed = await loopEvents.dispatch("transform_context", {
|
|
65
|
+
messages: msgs,
|
|
66
|
+
tools,
|
|
67
|
+
anchorIndex,
|
|
68
|
+
boundary: compacted,
|
|
69
|
+
...(model !== undefined ? { model } : {}),
|
|
70
|
+
});
|
|
71
|
+
const wire = applyHistoryPatch({
|
|
72
|
+
before: msgs,
|
|
73
|
+
patched: transformed.messages,
|
|
74
|
+
anchorIndex,
|
|
75
|
+
boundary: compacted,
|
|
76
|
+
event: "transform_context",
|
|
77
|
+
});
|
|
78
|
+
// Last write wins: a failed attempt is discarded wholesale, so the
|
|
79
|
+
// successful attempt is the last one to reach this line — `honoured`
|
|
80
|
+
// describes the wire the provider actually answered (spec 3.6).
|
|
81
|
+
honoured = wire.honoured;
|
|
82
|
+
return deps.complete(wire.messages, tools, options);
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
res = await request(messages);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// Written as one guarded `if` (not a separate `canRetry` boolean) so
|
|
89
|
+
// TypeScript's narrowing carries deps.summarize/contextWindow/compaction
|
|
90
|
+
// as defined below — a boolean flag loses that narrowing.
|
|
91
|
+
if (!isContextOverflow(err) ||
|
|
92
|
+
summarizeFailed ||
|
|
93
|
+
deps.summarize === undefined ||
|
|
94
|
+
deps.contextWindow === undefined ||
|
|
95
|
+
deps.compaction === undefined ||
|
|
96
|
+
!deps.compaction.enabled) {
|
|
97
|
+
// nax#1870: not an overflow this branch can handle. One more
|
|
98
|
+
// guarded branch beside the overflow backstop above, not a second
|
|
99
|
+
// loop or a second try/catch here — the retry's own looping and
|
|
100
|
+
// backoff live in retryTransportFault (./turn-retry), called once.
|
|
101
|
+
if (deps.transportRetry === undefined)
|
|
102
|
+
throw err;
|
|
103
|
+
res = await retryTransportFault(err, {
|
|
104
|
+
attempt: () => request(messages),
|
|
105
|
+
config: deps.transportRetry,
|
|
106
|
+
deadline: deps.deadline,
|
|
107
|
+
signal,
|
|
108
|
+
sleep: deps.sleep ?? realSleep,
|
|
109
|
+
onRetry: (retryNumber, delayMs, fault) => {
|
|
110
|
+
getSafeLogger()?.warn("native-adapter", `retrying after a ${fault.protocolError.kind} fault`, {
|
|
111
|
+
sessionName,
|
|
112
|
+
retryNumber,
|
|
113
|
+
delayMs,
|
|
114
|
+
kind: fault.protocolError.kind,
|
|
115
|
+
message: fault.protocolError.message,
|
|
116
|
+
...(fault.protocolError.status !== undefined ? { status: fault.protocolError.status } : {}),
|
|
117
|
+
...(fault.protocolError.retryAfter !== undefined ? { retryAfter: fault.protocolError.retryAfter } : {}),
|
|
118
|
+
});
|
|
119
|
+
// Resets the watchdog's lastActivityAt so a call being retried
|
|
120
|
+
// is not mistaken for an idle one — same mechanism the
|
|
121
|
+
// compaction summary above uses. All-zero is honest, not
|
|
122
|
+
// fabricated: a pre-first-event transport throw bills nothing
|
|
123
|
+
// (see nax-ai's retry.ts), so this beat truly carries zero
|
|
124
|
+
// tokens, not a guessed non-zero number.
|
|
125
|
+
deps.onActivity?.({ kind: "usage", inputTokens: 0, outputTokens: 0, costUsd: 0 });
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
// Falls through to the shared round-trip bookkeeping and tool
|
|
129
|
+
// execution below, exactly like the overflow-retry branch's own
|
|
130
|
+
// `res = await request(messages)` two lines down — one success
|
|
131
|
+
// path, reached from either recovery, not a second copy of it.
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const step = await runOverflowCompaction({
|
|
135
|
+
messages,
|
|
136
|
+
usage,
|
|
137
|
+
sessionName,
|
|
138
|
+
lastUsage,
|
|
139
|
+
anchorIndex,
|
|
140
|
+
loopEvents,
|
|
141
|
+
deps: {
|
|
142
|
+
summarize: deps.summarize,
|
|
143
|
+
contextWindow: deps.contextWindow,
|
|
144
|
+
compaction: deps.compaction,
|
|
145
|
+
onActivity: deps.onActivity,
|
|
146
|
+
deadline: deps.deadline,
|
|
147
|
+
},
|
|
148
|
+
error: err,
|
|
149
|
+
...(signal !== undefined ? { signal } : {}),
|
|
150
|
+
});
|
|
151
|
+
messages = [...step.messages];
|
|
152
|
+
compacted = true;
|
|
153
|
+
// Retried once. A second overflow propagates: compacting further would be
|
|
154
|
+
// guessing, and the failure now carries a correct diagnosis.
|
|
155
|
+
res = await request(messages);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { res, messages, compacted, honoured, boundary: compacted };
|
|
159
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { LoopEventRegistry } from "./loop-events/index.ts";
|
|
2
|
+
import type { BeforeTurnEndPayload } from "./loop-events/types.ts";
|
|
3
|
+
export declare function dispatchTurnEndOnError(loopEvents: LoopEventRegistry, payload: Omit<BeforeTurnEndPayload, "ended">, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review #20: `before_turn_end` also fires when a turn throws, so a handler
|
|
3
|
+
* sees every ending. The result is ignored on this path -- there is no turn
|
|
4
|
+
* left to continue. The dispatch is awaited without a timeout of its own: a
|
|
5
|
+
* plugin handler is timed by its wrapper (`wrapExternalHandler`, US-002),
|
|
6
|
+
* and a built-in handler is not timed at all.
|
|
7
|
+
*/
|
|
8
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
9
|
+
export async function dispatchTurnEndOnError(loopEvents, payload, signal) {
|
|
10
|
+
const patch = await loopEvents.dispatch("before_turn_end", {
|
|
11
|
+
...payload,
|
|
12
|
+
ended: signal?.aborted === true ? "aborted" : "errored",
|
|
13
|
+
});
|
|
14
|
+
if (patch.followUp !== undefined) {
|
|
15
|
+
getSafeLogger()?.warn("native-loop-events", "before_turn_end followUp ignored on error path", {});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native round-trip activity -> AgentStreamEvent.
|
|
3
|
+
*
|
|
4
|
+
* The idle watchdog subscribes to the runtime stream bus and tracks calls from
|
|
5
|
+
* these events; nothing under src/agents/native/ emitted any, so
|
|
6
|
+
* `agent.idleWatchdog` was inert on every native session in both modes.
|
|
7
|
+
*
|
|
8
|
+
* Native has no token streaming — one `complete()` is a single call — so these
|
|
9
|
+
* are emitted at round-trip boundaries rather than continuously. That is
|
|
10
|
+
* sufficient: a HUNG call is already bounded by the per-call abort, and the
|
|
11
|
+
* watchdog's unique job is the productive-looking loop that keeps calling tools
|
|
12
|
+
* forever. That loop emits `tool` AND `usage` on every iteration, so the usage
|
|
13
|
+
* event carries `perRoundTrip` to mark it as a round-trip boundary rather than
|
|
14
|
+
* semantic progress — otherwise it reset `lastNonToolCallActivityAt` every
|
|
15
|
+
* iteration and `toolCallOnlyIdleTimeout` could never fire (nax#2013).
|
|
16
|
+
*/
|
|
17
|
+
import type { AgentStreamEvent } from "#src/session/agent-stream-events";
|
|
18
|
+
export type NativeTurnActivity = {
|
|
19
|
+
kind: "message";
|
|
20
|
+
bytes: number;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "thinking";
|
|
23
|
+
bytes: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "usage";
|
|
26
|
+
inputTokens: number;
|
|
27
|
+
outputTokens: number;
|
|
28
|
+
costUsd: number;
|
|
29
|
+
/** Absent when the round trip reported no cache data — never coerced to 0. */
|
|
30
|
+
cacheRead?: number;
|
|
31
|
+
/** Absent when the round trip reported no cache data — never coerced to 0. */
|
|
32
|
+
cacheWrite?: number;
|
|
33
|
+
/** 1-based ordinal of the round trip this beat covers. Absent on a
|
|
34
|
+
* compaction-summary or retry beat, which are not round-trip boundaries. */
|
|
35
|
+
roundTrip?: number;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "tool";
|
|
38
|
+
toolName: string;
|
|
39
|
+
} | {
|
|
40
|
+
/**
|
|
41
|
+
* US-004: the turn is waiting on a human approval prompt. Carried so the
|
|
42
|
+
* runtime stream bus can turn it into `agent.awaiting_human` for the idle
|
|
43
|
+
* watchdog, which must not read legitimate human waiting as idleness.
|
|
44
|
+
*/
|
|
45
|
+
kind: "awaiting_human";
|
|
46
|
+
};
|
|
47
|
+
export interface NativeStreamEventBase {
|
|
48
|
+
readonly callId: string;
|
|
49
|
+
readonly runId: string;
|
|
50
|
+
readonly agentName: string;
|
|
51
|
+
readonly sessionName: string;
|
|
52
|
+
readonly storyId?: string;
|
|
53
|
+
/** Pipeline stage label; the contract does not know nax's stage union (S1 spec port 4). */
|
|
54
|
+
readonly stage?: string;
|
|
55
|
+
/**
|
|
56
|
+
* The transcript/ledger join key (`transcript.owner` = `ledger.scopeId`),
|
|
57
|
+
* forwarded from the native session's owner map. Absent when the session was
|
|
58
|
+
* opened without a `transcriptOwner` — absent reads as "unknown". Never the
|
|
59
|
+
* stream-local `callId`.
|
|
60
|
+
*/
|
|
61
|
+
readonly scopeId?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function buildNativeStreamEvent(base: NativeStreamEventBase, activity: NativeTurnActivity, timestamp: number): AgentStreamEvent;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native round-trip activity -> AgentStreamEvent.
|
|
3
|
+
*
|
|
4
|
+
* The idle watchdog subscribes to the runtime stream bus and tracks calls from
|
|
5
|
+
* these events; nothing under src/agents/native/ emitted any, so
|
|
6
|
+
* `agent.idleWatchdog` was inert on every native session in both modes.
|
|
7
|
+
*
|
|
8
|
+
* Native has no token streaming — one `complete()` is a single call — so these
|
|
9
|
+
* are emitted at round-trip boundaries rather than continuously. That is
|
|
10
|
+
* sufficient: a HUNG call is already bounded by the per-call abort, and the
|
|
11
|
+
* watchdog's unique job is the productive-looking loop that keeps calling tools
|
|
12
|
+
* forever. That loop emits `tool` AND `usage` on every iteration, so the usage
|
|
13
|
+
* event carries `perRoundTrip` to mark it as a round-trip boundary rather than
|
|
14
|
+
* semantic progress — otherwise it reset `lastNonToolCallActivityAt` every
|
|
15
|
+
* iteration and `toolCallOnlyIdleTimeout` could never fire (nax#2013).
|
|
16
|
+
*/
|
|
17
|
+
export function buildNativeStreamEvent(base, activity, timestamp) {
|
|
18
|
+
const common = { ...base, timestamp };
|
|
19
|
+
switch (activity.kind) {
|
|
20
|
+
case "message":
|
|
21
|
+
return { ...common, kind: "agent.message_update", deltaBytes: activity.bytes };
|
|
22
|
+
case "thinking":
|
|
23
|
+
return { ...common, kind: "agent.thinking_update", deltaBytes: activity.bytes };
|
|
24
|
+
case "usage":
|
|
25
|
+
return {
|
|
26
|
+
...common,
|
|
27
|
+
kind: "agent.usage_update",
|
|
28
|
+
inputTokens: activity.inputTokens,
|
|
29
|
+
outputTokens: activity.outputTokens,
|
|
30
|
+
costUsd: activity.costUsd,
|
|
31
|
+
...(activity.cacheRead !== undefined ? { cacheRead: activity.cacheRead } : {}),
|
|
32
|
+
...(activity.cacheWrite !== undefined ? { cacheWrite: activity.cacheWrite } : {}),
|
|
33
|
+
// Only a real round-trip boundary may claim `perRoundTrip` and an
|
|
34
|
+
// ordinal: the compaction-summary and transport-retry beats are usage
|
|
35
|
+
// events too, but they are not round trips (nax#2013, nax#2045). The
|
|
36
|
+
// cadence is still nax's own loop even on those beats.
|
|
37
|
+
...(activity.roundTrip !== undefined ? { roundTrip: activity.roundTrip, perRoundTrip: true } : {}),
|
|
38
|
+
cadence: "round-trip",
|
|
39
|
+
};
|
|
40
|
+
case "tool":
|
|
41
|
+
return { ...common, kind: "agent.tool_call_update", toolName: activity.toolName };
|
|
42
|
+
case "awaiting_human":
|
|
43
|
+
// US-004: the turn is waiting on a human approval prompt. The watchdog
|
|
44
|
+
// treats this as semantic progress on both activity clocks and clears an
|
|
45
|
+
// open grace window, so a pending prompt never reads as idle.
|
|
46
|
+
return { ...common, kind: "agent.awaiting_human" };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The round-trip loop and turn-end phase of the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `./turn-loop.ts` during the complexity drain
|
|
5
|
+
* (docs/plans/STATUS-complexity-drain.md A2): `runNativeTurn` scored 97 as one
|
|
6
|
+
* function covering setup, the round-trip loop (deliberately unbounded —
|
|
7
|
+
* bounded by the deadline, the idle watchdog, and the spin breaker, not a
|
|
8
|
+
* call count), and the `before_turn_end` continuation decision. Both loop
|
|
9
|
+
* bodies move here unchanged; only the plumbing that threads state across
|
|
10
|
+
* calls changed shape. A pure extraction: no loop rule changed.
|
|
11
|
+
*
|
|
12
|
+
* `runRoundTripLoop` mutates its `state` argument in place rather than
|
|
13
|
+
* returning a new one, and does so on every field the moment each value is
|
|
14
|
+
* known — not only at a clean exit. The original was one function where
|
|
15
|
+
* `messages` (and friends) were plain `let`s: a mid-loop throw (the
|
|
16
|
+
* `batch.cancelled` path below) still left the catch block reading the
|
|
17
|
+
* latest values, because it was the same variable. Returning a fresh object
|
|
18
|
+
* only at the end of the function would lose every update since the last
|
|
19
|
+
* return the moment a throw skips it — exactly the synthetic tool-result
|
|
20
|
+
* messages `runToolBatch` just appended before signalling cancellation. The
|
|
21
|
+
* caller passes one `TurnLoopState` and keeps reading through that same
|
|
22
|
+
* reference after an `await` that might throw.
|
|
23
|
+
*
|
|
24
|
+
* `spinStopped` / `spinWarned` were two `let`s in `runNativeTurn`, one of them
|
|
25
|
+
* (`spinWarned`) mutated by an `onSpinStop` closure registered ONCE in setup,
|
|
26
|
+
* before either extracted function is ever called, and read/written across
|
|
27
|
+
* every outer-loop iteration after that. Splitting the loop body out cannot
|
|
28
|
+
* split that closure's target too, so both flags now live in one mutable
|
|
29
|
+
* `SpinFlags` object created in setup and threaded by reference into every
|
|
30
|
+
* call here — the same shared-mutable-state semantics as the original two
|
|
31
|
+
* `let`s, just addressed through an object instead of two closed-over
|
|
32
|
+
* bindings.
|
|
33
|
+
*/
|
|
34
|
+
import type { ToolDefinition } from "@nathapp/nax-ai";
|
|
35
|
+
import type { SpinBreaker } from "#src/infra/spin-breaker/index";
|
|
36
|
+
import type { InteractionExchange, SendTurnOpts, SessionHandle } from "#src/session/session-types";
|
|
37
|
+
import { type TranscriptMessage as NativeTranscriptMessage } from "./compaction.ts";
|
|
38
|
+
import type { InvalidCallBudget } from "./handle-invalid-tool-call.ts";
|
|
39
|
+
import type { LoopEventRegistry } from "./loop-events/index.ts";
|
|
40
|
+
import type { createTurnAccumulator } from "./turn-accumulator.ts";
|
|
41
|
+
import type { TurnDeps } from "./turn-types.ts";
|
|
42
|
+
/**
|
|
43
|
+
* The per-turn bound on `before_turn_end`'s followUp channel (spec 6.4): the
|
|
44
|
+
* only event that can spend money on its own, so injections are capped per
|
|
45
|
+
* turn regardless of what handlers return. At the cap the channel stops and
|
|
46
|
+
* the turn ends.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MAX_FOLLOW_UPS_PER_TURN = 3;
|
|
49
|
+
/** Shared across every call in one turn — see the file header. */
|
|
50
|
+
export interface SpinFlags {
|
|
51
|
+
/** Set ONLY when the breaker ended the turn, so the wiring layer can classify it as `fail-spin` rather than a generic incomplete turn. */
|
|
52
|
+
stopped: boolean;
|
|
53
|
+
/** nax#2120: the first stop verdict spends a terminal round trip rather than tearing the turn down, so a false positive does not cost the transcript. */
|
|
54
|
+
warned: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Values that change across outer-loop (followUp) iterations. */
|
|
57
|
+
export interface TurnLoopState {
|
|
58
|
+
messages: NativeTranscriptMessage[];
|
|
59
|
+
lastUsage: {
|
|
60
|
+
promptTokens: number;
|
|
61
|
+
} | undefined;
|
|
62
|
+
anchorIndex: number | undefined;
|
|
63
|
+
roundTrips: number;
|
|
64
|
+
output: string;
|
|
65
|
+
completedNormally: boolean;
|
|
66
|
+
timedOut: boolean;
|
|
67
|
+
followUpsSoFar: number;
|
|
68
|
+
}
|
|
69
|
+
/** Turn-scoped values that never change across outer-loop iterations. */
|
|
70
|
+
export interface TurnRoundParams {
|
|
71
|
+
handle: SessionHandle;
|
|
72
|
+
opts: SendTurnOpts;
|
|
73
|
+
deps: TurnDeps;
|
|
74
|
+
/** `transcriptIdentity.model` — carried into every `nativeSessionLastUsage.set` this turn writes. */
|
|
75
|
+
transcriptModel: string | undefined;
|
|
76
|
+
tools: ToolDefinition[];
|
|
77
|
+
codingToolNames: ReadonlySet<string>;
|
|
78
|
+
loopEvents: LoopEventRegistry;
|
|
79
|
+
invalidCallBudget: InvalidCallBudget;
|
|
80
|
+
spinBreaker: SpinBreaker | undefined;
|
|
81
|
+
spinFlags: SpinFlags;
|
|
82
|
+
maxInteractions: number;
|
|
83
|
+
/** Mutated in place — the caller owns the turn-lifetime array. */
|
|
84
|
+
interactions: InteractionExchange[];
|
|
85
|
+
/** Mutated in place — the caller owns the turn-lifetime array. */
|
|
86
|
+
codingToolsCalled: string[];
|
|
87
|
+
/** Mutated in place via `.add()` — the caller owns the turn-lifetime accumulator. */
|
|
88
|
+
usage: ReturnType<typeof createTurnAccumulator>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Deliberately unbounded by COUNT of varied calls. A coding agent working a
|
|
92
|
+
* story is bounded by wall clock (deps.deadline), by the idle watchdog, and
|
|
93
|
+
* — since nax#2013 — by the spin breaker, which ends a turn that keeps
|
|
94
|
+
* REPEATING a call it already made. `agent.maxInteractionTurns` is NOT this
|
|
95
|
+
* budget — it bounds human Q&A exchanges, which are counted separately.
|
|
96
|
+
*/
|
|
97
|
+
export declare function runRoundTripLoop(state: TurnLoopState, params: TurnRoundParams): Promise<void>;
|
|
98
|
+
export type TurnEndOutcome = {
|
|
99
|
+
action: "break";
|
|
100
|
+
} | {
|
|
101
|
+
action: "continue";
|
|
102
|
+
state: TurnLoopState;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* P3 `before_turn_end` (spec 6.4): fires at every turn ENDING, before the
|
|
106
|
+
* final saveTranscript. `stopped` is dispatcher-computed: the three endings
|
|
107
|
+
* that are stops, not completions — the spin breaker (nax#2120), the
|
|
108
|
+
* invalid-call budget (nax#2047), and the deadline — close the followUp
|
|
109
|
+
* channel entirely, because resurrecting a turn a breaker just killed would
|
|
110
|
+
* re-open the loops those breakers exist to close. The payload's `stopped`
|
|
111
|
+
* flag is all a handler sees: WHICH breaker fired is not leaked, and any
|
|
112
|
+
* followUp returned against a stop is ignored (with a warn — a handler
|
|
113
|
+
* trying to resurrect is worth a trace). The channel is also capped per turn
|
|
114
|
+
* at MAX_FOLLOW_UPS_PER_TURN.
|
|
115
|
+
*/
|
|
116
|
+
export declare function runTurnEndPhase(state: TurnLoopState, params: TurnRoundParams): Promise<TurnEndOutcome>;
|