mixdog 0.9.62 → 0.9.64

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.
Files changed (403) hide show
  1. package/README.md +3 -0
  2. package/package.json +9 -2
  3. package/scripts/code-graph-description-contract.mjs +3 -3
  4. package/scripts/verify-release-assets.mjs +2 -2
  5. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +11 -14
  6. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -6
  7. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -2
  8. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  9. package/src/runtime/agent/orchestrator/config.mjs +19 -124
  10. package/src/runtime/agent/orchestrator/context/collect.mjs +6 -6
  11. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +2 -2
  12. package/src/runtime/agent/orchestrator/internal-agents.mjs +2 -2
  13. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +1 -1
  14. package/src/runtime/agent/orchestrator/mcp/client.mjs +4 -4
  15. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +2 -2
  16. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +4 -4
  17. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +2 -2
  18. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +4 -4
  19. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +333 -0
  20. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +2 -2
  21. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -4
  22. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
  23. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -277
  24. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +2 -2
  25. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +1 -1
  26. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +2 -2
  27. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -6
  28. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +196 -0
  29. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +460 -0
  30. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +14 -593
  31. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +1 -1
  32. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +1 -1
  33. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
  34. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +4 -4
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2 -2
  36. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -280
  37. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +357 -0
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-headers.mjs +216 -0
  39. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +2 -188
  40. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  41. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/registry.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -2
  46. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +3 -3
  49. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +2 -2
  50. package/src/runtime/agent/orchestrator/session/compact/file-reattach.mjs +2 -2
  51. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +2 -2
  54. package/src/runtime/agent/orchestrator/session/context-utils.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +17 -0
  57. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +18 -26
  59. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +1 -1
  60. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +3 -3
  61. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -0
  62. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +1 -1
  64. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +49 -0
  66. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +49 -0
  67. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +260 -0
  68. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +72 -0
  69. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +163 -0
  70. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +4 -1
  73. package/src/runtime/agent/orchestrator/session/store.mjs +9 -561
  74. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  76. package/src/runtime/agent/orchestrator/stall-policy.mjs +11 -11
  77. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  81. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -154
  83. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/tools/builtin/cwd-utils.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/path-locks.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -2
  91. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +2 -3
  92. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +4 -4
  93. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +2 -2
  96. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -5
  97. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +2 -2
  99. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +2 -2
  100. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +519 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +5 -449
  102. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +212 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -1
  105. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +1 -7
  106. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +9 -36
  107. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +15 -3
  108. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +429 -0
  109. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -395
  110. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +2 -2
  111. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +1 -1
  112. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  114. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +3 -3
  115. package/src/runtime/agent/orchestrator/tools/mutation-planner.mjs +5 -5
  116. package/src/runtime/agent/orchestrator/tools/next-call-utils.mjs +1 -1
  117. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +7 -7
  119. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +72 -9
  120. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -4
  121. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  123. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -4
  124. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +2 -444
  127. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +505 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +2 -2
  129. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +1 -1
  130. package/src/runtime/channels/lib/backend-dispatch.mjs +20 -5
  131. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  132. package/src/runtime/channels/lib/telegram-format.mjs +1 -1
  133. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +4 -4
  134. package/src/runtime/channels/tool-defs.mjs +2 -3
  135. package/src/runtime/memory/index.mjs +1 -5
  136. package/src/runtime/memory/lib/core-memory-candidates.mjs +357 -0
  137. package/src/runtime/memory/lib/core-memory-store.mjs +7 -357
  138. package/src/runtime/memory/lib/cycle-signatures.mjs +1 -1
  139. package/src/runtime/memory/lib/embedding-model-config.mjs +1 -1
  140. package/src/runtime/memory/lib/embedding-provider.mjs +1 -5
  141. package/src/runtime/memory/lib/embedding-worker.mjs +1 -5
  142. package/src/runtime/memory/lib/ko-morph.mjs +2 -2
  143. package/src/runtime/memory/lib/memory-config-flags.mjs +2 -5
  144. package/src/runtime/memory/lib/memory-cycle-requests.mjs +3 -7
  145. package/src/runtime/memory/lib/memory-cycle1.mjs +4 -8
  146. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -1
  147. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -1
  148. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +2 -5
  149. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -5
  150. package/src/runtime/memory/lib/memory-embed.mjs +2 -6
  151. package/src/runtime/memory/lib/memory-log.mjs +8 -0
  152. package/src/runtime/memory/lib/memory-ops-policy.mjs +4 -8
  153. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +1 -1
  154. package/src/runtime/memory/lib/memory-recall-store.mjs +2 -193
  155. package/src/runtime/memory/lib/memory-score.mjs +1 -1
  156. package/src/runtime/memory/lib/memory-text-utils.mjs +2 -2
  157. package/src/runtime/memory/lib/memory.mjs +2 -6
  158. package/src/runtime/memory/lib/pg/adapter.mjs +2 -6
  159. package/src/runtime/memory/lib/pg/process.mjs +1 -5
  160. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -5
  161. package/src/runtime/memory/lib/recall-format.mjs +2 -2
  162. package/src/runtime/memory/lib/recall-scoring.mjs +195 -0
  163. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -5
  164. package/src/runtime/memory/lib/trace-store.mjs +2 -6
  165. package/src/runtime/search/lib/http-fetch.mjs +6 -6
  166. package/src/runtime/search/lib/web-tools.mjs +2 -2
  167. package/src/runtime/shared/buffered-appender.mjs +1 -1
  168. package/src/runtime/shared/child-guardian.mjs +1 -1
  169. package/src/runtime/shared/child-spawn-gate.mjs +1 -1
  170. package/src/runtime/shared/config.mjs +4 -4
  171. package/src/runtime/shared/launcher-control.mjs +9 -10
  172. package/src/runtime/shared/llm/index.mjs +5 -20
  173. package/src/runtime/shared/markdown-frontmatter.mjs +1 -1
  174. package/src/runtime/shared/memory-snapshot.mjs +4 -4
  175. package/src/runtime/shared/pristine-execution.mjs +1 -1
  176. package/src/runtime/shared/process-lifecycle.mjs +0 -1
  177. package/src/runtime/shared/process-shutdown.mjs +1 -1
  178. package/src/runtime/shared/resource-admission.mjs +2 -2
  179. package/src/runtime/shared/schedule-session-run.mjs +1 -1
  180. package/src/runtime/shared/schedules-db.mjs +1 -1
  181. package/src/runtime/shared/service-discovery.mjs +2 -2
  182. package/src/runtime/shared/staged-update.mjs +6 -6
  183. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  184. package/src/runtime/shared/tool-primitives.mjs +1 -1
  185. package/src/runtime/shared/tool-surface.mjs +2 -2
  186. package/src/runtime/shared/update-checker.mjs +1 -1
  187. package/src/runtime/shared/user-cwd.mjs +3 -3
  188. package/src/runtime/shared/user-data-guard.mjs +4 -4
  189. package/src/runtime/shared/workspace-router.mjs +5 -5
  190. package/src/session-runtime/config-helpers.mjs +2 -3
  191. package/src/session-runtime/config-lifecycle.mjs +1 -1
  192. package/src/session-runtime/hitch-profile.mjs +1 -1
  193. package/src/session-runtime/lifecycle-api.mjs +6 -4
  194. package/src/session-runtime/model-recency.mjs +5 -5
  195. package/src/session-runtime/plugin-mcp.mjs +1 -1
  196. package/src/session-runtime/provider-request-snapshot.mjs +338 -0
  197. package/src/session-runtime/session-text.mjs +4 -4
  198. package/src/session-runtime/tool-catalog-schema.mjs +197 -0
  199. package/src/session-runtime/tool-catalog.mjs +6 -483
  200. package/src/session-runtime/tool-defs.mjs +1 -1
  201. package/src/standalone/agent-task-status.mjs +1 -1
  202. package/src/standalone/agent-tool/helpers.mjs +2 -2
  203. package/src/standalone/channel-admin.mjs +4 -4
  204. package/src/standalone/explore-tool.mjs +3 -3
  205. package/src/standalone/hook-bus/config.mjs +1 -1
  206. package/src/standalone/hook-bus/constants.mjs +1 -1
  207. package/src/standalone/hook-bus/rules.mjs +1 -1
  208. package/src/standalone/projects.mjs +1 -1
  209. package/src/standalone/provider-admin.mjs +2 -2
  210. package/src/tui/app/app-format.mjs +2 -2
  211. package/src/tui/app/clipboard.mjs +3 -3
  212. package/src/tui/app/input-parsers.mjs +1 -1
  213. package/src/tui/app/text-layout.mjs +1 -1
  214. package/src/tui/app/transcript-row-estimate.mjs +360 -0
  215. package/src/tui/app/transcript-window.mjs +5 -347
  216. package/src/tui/app/use-mouse-input.mjs +1 -1
  217. package/src/tui/app/use-prompt-handlers.mjs +1 -1
  218. package/src/tui/components/tool-execution/surface-detail.mjs +9 -9
  219. package/src/tui/components/tool-execution/text-format.mjs +2 -2
  220. package/src/tui/components/tool-output-format.mjs +7 -7
  221. package/src/tui/display-width.mjs +4 -4
  222. package/src/tui/dist/index.mjs +63 -97
  223. package/src/tui/engine/agent-envelope.mjs +5 -5
  224. package/src/tui/engine/notification-plan.mjs +1 -1
  225. package/src/tui/engine/queue-helpers.mjs +3 -3
  226. package/src/tui/engine/render-timing.mjs +4 -5
  227. package/src/tui/engine/session-api-ext.mjs +3 -3
  228. package/src/tui/engine/tool-result-status.mjs +5 -5
  229. package/src/tui/engine/tool-result-text.mjs +1 -1
  230. package/src/tui/engine/transcript-spill.mjs +585 -0
  231. package/src/tui/engine/tui-steering-persist.mjs +1 -1
  232. package/src/tui/engine.mjs +2 -474
  233. package/src/tui/lib/voice-setup.mjs +1 -1
  234. package/src/tui/markdown/format-token.mjs +1 -1
  235. package/src/tui/markdown/render-ansi.mjs +1 -1
  236. package/src/tui/markdown/table-layout.mjs +4 -4
  237. package/src/tui/paste-attachments.mjs +1 -1
  238. package/src/tui/prompt-history-store.mjs +3 -3
  239. package/src/tui/statusline-ansi-bridge.mjs +2 -2
  240. package/src/tui/theme.mjs +1 -1
  241. package/src/tui/transcript-tool-failures.mjs +2 -2
  242. package/src/ui/ansi.mjs +0 -9
  243. package/src/ui/statusline-format.mjs +2 -4
  244. package/scripts/_bench-cwc.json +0 -20
  245. package/scripts/_jitter-fuzz.jsx +0 -42
  246. package/scripts/_jitter-fuzz2.jsx +0 -30
  247. package/scripts/_jitter-probe.jsx +0 -35
  248. package/scripts/_jp2.jsx +0 -16
  249. package/scripts/_smoke_wd.mjs +0 -7
  250. package/scripts/abort-recovery-test.mjs +0 -175
  251. package/scripts/agent-dispatch-abort-compose-test.mjs +0 -31
  252. package/scripts/agent-live-arg-guard-smoke.mjs +0 -20
  253. package/scripts/agent-live-path-suffix-smoke.mjs +0 -34
  254. package/scripts/agent-live-toolcall-args-smoke.mjs +0 -45
  255. package/scripts/agent-loop-policy-test.mjs +0 -37
  256. package/scripts/agent-model-liveness-test.mjs +0 -763
  257. package/scripts/agent-parallel-smoke.mjs +0 -435
  258. package/scripts/agent-route-batch-test.mjs +0 -40
  259. package/scripts/agent-tag-reuse-smoke.mjs +0 -441
  260. package/scripts/agent-terminal-reap-test.mjs +0 -252
  261. package/scripts/agent-trace-io-test.mjs +0 -133
  262. package/scripts/ansi-color-capability-test.mjs +0 -90
  263. package/scripts/anthropic-admission-retry-integration-test.mjs +0 -119
  264. package/scripts/anthropic-maxtokens-test.mjs +0 -119
  265. package/scripts/anthropic-oauth-refresh-race-test.mjs +0 -397
  266. package/scripts/anthropic-transport-policy-test.mjs +0 -726
  267. package/scripts/apply-patch-edit-smoke.mjs +0 -71
  268. package/scripts/arg-guard-test.mjs +0 -93
  269. package/scripts/async-notify-settlement-test.mjs +0 -99
  270. package/scripts/atomic-lock-tryonce-test.mjs +0 -125
  271. package/scripts/background-task-meta-smoke.mjs +0 -38
  272. package/scripts/bench-run.mjs +0 -508
  273. package/scripts/boot-smoke.mjs +0 -137
  274. package/scripts/build-runtime-windows.ps1 +0 -242
  275. package/scripts/channel-daemon-smoke.mjs +0 -1103
  276. package/scripts/code-graph-aggregate-cwd-test.mjs +0 -158
  277. package/scripts/code-graph-disk-hit-test.mjs +0 -290
  278. package/scripts/code-graph-dispatch-test.mjs +0 -96
  279. package/scripts/code-graph-root-federation-test.mjs +0 -273
  280. package/scripts/compact-active-turn-test.mjs +0 -68
  281. package/scripts/compact-file-reattach-test.mjs +0 -88
  282. package/scripts/compact-pressure-test.mjs +0 -788
  283. package/scripts/compact-prior-context-flatten-test.mjs +0 -252
  284. package/scripts/compact-recall-digest-test.mjs +0 -57
  285. package/scripts/compact-smoke.mjs +0 -1017
  286. package/scripts/compact-trigger-migration-smoke.mjs +0 -261
  287. package/scripts/compacted-placeholder-scrub-test.mjs +0 -63
  288. package/scripts/context-mcp-metering-test.mjs +0 -1406
  289. package/scripts/debounced-skills-async-save-test.mjs +0 -57
  290. package/scripts/deferred-tool-loading-test.mjs +0 -250
  291. package/scripts/desktop-session-bridge-test.mjs +0 -1013
  292. package/scripts/dispatch-persist-recovery-test.mjs +0 -141
  293. package/scripts/embedding-worker-exit-test.mjs +0 -76
  294. package/scripts/execution-completion-dedup-test.mjs +0 -205
  295. package/scripts/execution-pending-resume-kick-test.mjs +0 -151
  296. package/scripts/execution-resume-esc-integration-test.mjs +0 -176
  297. package/scripts/explore-bench-tmp.mjs +0 -36
  298. package/scripts/explore-bench.mjs +0 -248
  299. package/scripts/explore-prompt-policy-test.mjs +0 -256
  300. package/scripts/explore-timeout-cancel-test.mjs +0 -345
  301. package/scripts/find-fuzzy-hidden-test.mjs +0 -267
  302. package/scripts/forwarder-rebind-tail-test.mjs +0 -67
  303. package/scripts/freevar-smoke.mjs +0 -98
  304. package/scripts/gemini-provider-test.mjs +0 -1448
  305. package/scripts/generate-oc-icons.mjs +0 -11
  306. package/scripts/grok-oauth-refresh-race-test.mjs +0 -273
  307. package/scripts/headless-pristine-execution-test.mjs +0 -614
  308. package/scripts/hook-bus-test.mjs +0 -552
  309. package/scripts/ingest-pure-conversation-smoke.mjs +0 -175
  310. package/scripts/internal-comms-bench-test.mjs +0 -226
  311. package/scripts/internal-comms-bench.mjs +0 -853
  312. package/scripts/internal-comms-smoke.mjs +0 -277
  313. package/scripts/internal-tools-normalization-test.mjs +0 -62
  314. package/scripts/interrupted-turn-history-test.mjs +0 -399
  315. package/scripts/lead-workflow-smoke.mjs +0 -592
  316. package/scripts/lifecycle-api-test.mjs +0 -137
  317. package/scripts/live-share-test.mjs +0 -148
  318. package/scripts/live-worker-smoke.mjs +0 -333
  319. package/scripts/log-writer-guard-smoke.mjs +0 -131
  320. package/scripts/maintenance-default-routes-test.mjs +0 -164
  321. package/scripts/max-output-recovery-persist-test.mjs +0 -81
  322. package/scripts/max-output-recovery-test.mjs +0 -368
  323. package/scripts/mcp-client-normalization-test.mjs +0 -45
  324. package/scripts/mcp-grace-deferred-test.mjs +0 -225
  325. package/scripts/memory-core-input-test.mjs +0 -167
  326. package/scripts/memory-cycle-routing-test.mjs +0 -111
  327. package/scripts/memory-meta-concurrency-test.mjs +0 -20
  328. package/scripts/memory-pg-recovery-test.mjs +0 -59
  329. package/scripts/memory-rule-contract-test.mjs +0 -93
  330. package/scripts/model-list-sanitize-test.mjs +0 -37
  331. package/scripts/mouse-tracking-restore-smoke.mjs +0 -107
  332. package/scripts/native-edit-wire-test.mjs +0 -152
  333. package/scripts/notify-completion-mirror-test.mjs +0 -73
  334. package/scripts/openai-oauth-refresh-race-test.mjs +0 -120
  335. package/scripts/openai-oauth-ws-1006-retry-test.mjs +0 -954
  336. package/scripts/openai-ws-early-settle-test.mjs +0 -216
  337. package/scripts/output-style-bench.mjs +0 -292
  338. package/scripts/output-style-smoke.mjs +0 -145
  339. package/scripts/parent-abort-link-test.mjs +0 -90
  340. package/scripts/patch-binary-cache-test.mjs +0 -181
  341. package/scripts/path-suffix-test.mjs +0 -57
  342. package/scripts/pending-completion-drop-test.mjs +0 -239
  343. package/scripts/pending-messages-lock-nonblocking-test.mjs +0 -62
  344. package/scripts/pretool-ask-runtime-test.mjs +0 -54
  345. package/scripts/process-lifecycle-test.mjs +0 -461
  346. package/scripts/prompt-immediate-render-test.mjs +0 -89
  347. package/scripts/prompt-input-parity-test.mjs +0 -145
  348. package/scripts/provider-admission-scheduler-test.mjs +0 -681
  349. package/scripts/provider-contract-test.mjs +0 -632
  350. package/scripts/provider-stream-stall-test.mjs +0 -276
  351. package/scripts/provider-toolcall-test.mjs +0 -3063
  352. package/scripts/reactive-compact-persist-smoke.mjs +0 -203
  353. package/scripts/recall-bench.mjs +0 -359
  354. package/scripts/resource-admission-test.mjs +0 -792
  355. package/scripts/result-classification-test.mjs +0 -75
  356. package/scripts/rg-runner-test.mjs +0 -240
  357. package/scripts/routing-corpus-test.mjs +0 -349
  358. package/scripts/sanitize-tool-pairs-test.mjs +0 -260
  359. package/scripts/session-bench-cache-break-test.mjs +0 -102
  360. package/scripts/session-bench.mjs +0 -1708
  361. package/scripts/session-context-bench.mjs +0 -344
  362. package/scripts/session-heartbeat-lifecycle-test.mjs +0 -68
  363. package/scripts/session-ingest-compaction-smoke.mjs +0 -279
  364. package/scripts/session-ingest-smoke.mjs +0 -177
  365. package/scripts/session-orphan-sweep-test.mjs +0 -109
  366. package/scripts/set-effort-config-test.mjs +0 -41
  367. package/scripts/shell-failure-diagnostics-test.mjs +0 -280
  368. package/scripts/shell-hardening-test.mjs +0 -209
  369. package/scripts/shell-jobs-windows-hide-test.mjs +0 -164
  370. package/scripts/ship-mode-test.mjs +0 -98
  371. package/scripts/smoke-loop-failure-summary-test.mjs +0 -38
  372. package/scripts/smoke-loop-failure-summary.mjs +0 -16
  373. package/scripts/smoke-loop-report.mjs +0 -221
  374. package/scripts/smoke-loop.mjs +0 -202
  375. package/scripts/smoke-runtime-negative.ps1 +0 -106
  376. package/scripts/statusline-quota-hysteresis-test.mjs +0 -62
  377. package/scripts/steering-drain-buckets-test.mjs +0 -467
  378. package/scripts/steering-fold-provenance-test.mjs +0 -71
  379. package/scripts/streaming-tail-window-test.mjs +0 -175
  380. package/scripts/submit-commandbusy-race-test.mjs +0 -257
  381. package/scripts/task-bench.mjs +0 -205
  382. package/scripts/terminal-bench-isolation-guards-test.mjs +0 -102
  383. package/scripts/termio-input-smoke.mjs +0 -199
  384. package/scripts/tool-result-hook-test.mjs +0 -48
  385. package/scripts/tool-smoke.mjs +0 -2453
  386. package/scripts/tool-tui-presentation-test.mjs +0 -286
  387. package/scripts/toolcall-args-test.mjs +0 -239
  388. package/scripts/tui-ambiguous-width-test.mjs +0 -113
  389. package/scripts/tui-background-failure-smoke.mjs +0 -73
  390. package/scripts/tui-perf-run.ps1 +0 -26
  391. package/scripts/tui-render-smoke.mjs +0 -90
  392. package/scripts/tui-runtime-load-bench-entry.jsx +0 -608
  393. package/scripts/tui-runtime-load-bench.mjs +0 -50
  394. package/scripts/tui-store-frame-batch-test.mjs +0 -99
  395. package/scripts/tui-transcript-jitter-harness-entry.jsx +0 -383
  396. package/scripts/tui-transcript-perf-test.mjs +0 -934
  397. package/scripts/usage-metrics-epoch-smoke.mjs +0 -114
  398. package/scripts/verify-release-assets-test.mjs +0 -281
  399. package/scripts/web-fetch-routing-test.mjs +0 -158
  400. package/scripts/webhook-smoke.mjs +0 -201
  401. package/scripts/windows-hide-spawn-options-test.mjs +0 -19
  402. package/scripts/worker-notify-rejection-test.mjs +0 -51
  403. package/scripts/write-backpressure-test.mjs +0 -147
@@ -1,1406 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import {
5
- createContextStatus,
6
- requestSerializedToolsForContext,
7
- } from '../src/session-runtime/context-status.mjs';
8
- import { splitToolStatusCounts } from '../src/session-runtime/session-turn-api.mjs';
9
- import {
10
- estimateToolSchemaBreakdown,
11
- snapshotProviderRequestTools,
12
- toolKind,
13
- } from '../src/session-runtime/tool-catalog.mjs';
14
- import {
15
- invalidateProviderRequestToolsScope,
16
- providerNativeToolPrefixCount,
17
- runWithProviderRequestToolsScope,
18
- scopedProviderRequestTools,
19
- } from '../src/session-runtime/provider-request-tools.mjs';
20
- import { _buildRequestBodyForCacheSmoke as buildAnthropicRequestBody } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
21
- import { _test as anthropicApiKeyTest } from '../src/runtime/agent/orchestrator/providers/anthropic.mjs';
22
- import {
23
- estimateMessagesTokens,
24
- estimateRequestReserveTokens,
25
- estimateToolSchemaTokens,
26
- toolSchemaSignature,
27
- } from '../src/runtime/agent/orchestrator/session/context-utils.mjs';
28
- import { agentLoop } from '../src/runtime/agent/orchestrator/session/agent-loop.mjs';
29
- import {
30
- recordProviderContextBaseline,
31
- resolveCompactionPressureTokens,
32
- resolveWorkerCompactPolicy,
33
- } from '../src/runtime/agent/orchestrator/session/loop/compact-policy.mjs';
34
-
35
- const schema = (description) => ({
36
- type: 'object',
37
- properties: { value: { type: 'string', description } },
38
- });
39
-
40
- test('context meters only provider-visible Anthropic deferred schemas', () => {
41
- const read = { name: 'read', description: 'Read a file', inputSchema: schema('path') };
42
- const mcp = { name: 'mcp__demo__lookup', description: 'MCP lookup', inputSchema: schema('mcp payload '.repeat(100)) };
43
- const skill = { name: 'skill_hidden', description: 'Undiscovered skill', inputSchema: schema('skill payload '.repeat(100)) };
44
- const control = {
45
- name: 'hidden_control',
46
- description: 'Undiscovered control',
47
- annotations: { agentHidden: true },
48
- inputSchema: schema('control payload '.repeat(100)),
49
- };
50
- const session = {
51
- id: 'deferred-mcp-context',
52
- provider: 'anthropic',
53
- contextWindow: 100_000,
54
- messages: [{ role: 'user', content: 'hello' }],
55
- tools: [read],
56
- deferredNativeTools: true,
57
- deferredToolCatalog: [read, mcp, skill, control],
58
- compaction: {},
59
- };
60
- const route = { provider: 'openai', model: 'test' };
61
- const { contextStatus } = createContextStatus({
62
- getSession: () => session,
63
- getRoute: () => route,
64
- getCurrentCwd: () => '',
65
- getMode: () => 'default',
66
- });
67
-
68
- assert.deepEqual(requestSerializedToolsForContext(session, 'openai'), [read]);
69
- assert.deepEqual(requestSerializedToolsForContext(session, 'anthropic'), [read]);
70
- const before = contextStatus();
71
- assert.equal(before.request.toolSchemaBreakdown.mcp, undefined);
72
- assert.equal(before.request.toolSchemaBreakdown.skills, undefined);
73
- assert.equal(before.request.toolSchemaBreakdown.control, undefined);
74
-
75
- mcp.inputSchema.properties.value.description += ' mutated payload '.repeat(100);
76
- session.deferredToolCatalog.push({
77
- name: 'mcp__demo__second',
78
- description: 'Second MCP tool',
79
- inputSchema: schema('second payload '.repeat(100)),
80
- });
81
- const undiscoveredChanged = contextStatus();
82
- assert.equal(undiscoveredChanged, before, 'undiscovered catalog changes must not churn status/baselines');
83
-
84
- session.deferredDiscoveredTools = [mcp.name];
85
- const discovered = contextStatus();
86
- assert.notEqual(discovered, before);
87
- assert.equal(discovered.request.toolSchemaBreakdown.mcp.count, 1);
88
- assert.equal(discovered.request.toolSchemaBreakdown.skills, undefined);
89
- assert.equal(discovered.request.toolSchemaBreakdown.control, undefined);
90
- assert.ok(discovered.request.toolSchemaTokens > before.request.toolSchemaTokens);
91
- assert.ok(discovered.request.reserveTokens > before.request.reserveTokens);
92
- assert.ok(discovered.usedTokens > before.usedTokens);
93
-
94
- mcp.inputSchema.properties.value.description += ' discovered mutation '.repeat(200);
95
- const mutated = contextStatus();
96
- assert.notEqual(mutated, discovered);
97
- assert.equal(mutated.request.toolSchemaBreakdown.mcp.count, 1);
98
- assert.ok(mutated.request.toolSchemaTokens > discovered.request.toolSchemaTokens);
99
- assert.ok(mutated.request.reserveTokens > discovered.request.reserveTokens);
100
- assert.ok(mutated.usedTokens > discovered.usedTokens);
101
- });
102
-
103
- test('provider baseline ignores undiscovered schemas and invalidates after visible discovery/mutation', () => {
104
- const read = { name: 'read', inputSchema: schema('path') };
105
- const mcp = { name: 'mcp__demo__lookup', inputSchema: schema('initial') };
106
- const session = {
107
- provider: 'anthropic',
108
- model: 'claude-sonnet-4-6',
109
- contextWindow: 100_000,
110
- messages: [{ role: 'user', content: 'hello' }],
111
- tools: [read],
112
- deferredNativeTools: true,
113
- deferredToolCatalog: [read, mcp],
114
- compaction: {},
115
- };
116
- const pressure = (requestTools) => resolveCompactionPressureTokens(
117
- estimateMessagesTokens(session.messages),
118
- resolveWorkerCompactPolicy(session, requestTools),
119
- { messages: session.messages, sessionRef: session },
120
- );
121
-
122
- const baseTools = requestSerializedToolsForContext(session, session.provider);
123
- recordProviderContextBaseline(
124
- session,
125
- session.messages,
126
- { inputTokens: 80_000 },
127
- { sendTools: baseTools },
128
- );
129
- mcp.inputSchema.properties.value.description = 'undiscovered mutation '.repeat(200);
130
- assert.equal(
131
- pressure(requestSerializedToolsForContext(session, session.provider)),
132
- 80_000,
133
- 'an undiscovered schema mutation must retain the provider baseline',
134
- );
135
-
136
- session.deferredDiscoveredTools = [mcp.name];
137
- const discoveredTools = requestSerializedToolsForContext(session, session.provider);
138
- assert.notEqual(
139
- pressure(discoveredTools),
140
- 80_000,
141
- 'discovery must invalidate a baseline recorded against the base surface',
142
- );
143
- recordProviderContextBaseline(
144
- session,
145
- session.messages,
146
- { inputTokens: 80_000 },
147
- { sendTools: discoveredTools },
148
- );
149
- assert.equal(pressure(discoveredTools), 80_000);
150
-
151
- mcp.inputSchema.properties.value.description += ' visible mutation '.repeat(200);
152
- assert.notEqual(
153
- pressure(requestSerializedToolsForContext(session, session.provider)),
154
- 80_000,
155
- 'a discovered in-place schema mutation must invalidate the provider baseline',
156
- );
157
- });
158
-
159
- test('agent-loop repairs history before one coherent pressure/send/baseline tool snapshot', async () => {
160
- const read = { name: 'read', description: 'Read', inputSchema: schema('path') };
161
- const mcp = { name: 'mcp__demo__lookup', description: 'Lookup', inputSchema: schema('mcp') };
162
- const messages = [
163
- { role: 'user', content: 'hello' },
164
- {
165
- role: 'tool',
166
- toolCallId: 'orphan-load',
167
- content: 'Loaded',
168
- nativeToolSearch: {
169
- provider: 'anthropic-oauth',
170
- toolReferences: [mcp.name],
171
- },
172
- },
173
- ];
174
- const session = {
175
- id: 'snapshot-repair',
176
- provider: 'anthropic-oauth',
177
- model: 'claude-sonnet-4-6',
178
- contextWindow: 100_000,
179
- messages,
180
- tools: [read],
181
- deferredNativeTools: true,
182
- deferredToolCatalog: [read, mcp],
183
- compaction: {},
184
- };
185
- let body;
186
- let apiKeyTools;
187
- let retryBody;
188
- let retryApiKeyTools;
189
- const provider = {
190
- name: session.provider,
191
- async send(outgoing, model, tools, opts) {
192
- assert.equal(opts.providerToolSnapshotAuthoritative, true);
193
- const late = {
194
- name: 'mcp__demo__late',
195
- description: 'Connected after snapshot',
196
- inputSchema: schema('late'),
197
- };
198
- session.deferredToolCatalog.push(late);
199
- session.deferredDiscoveredTools = [late.name];
200
- await Promise.resolve();
201
- body = buildAnthropicRequestBody(outgoing, model, tools, opts);
202
- apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(tools, outgoing, opts);
203
- session.deferredToolCatalog.push({
204
- name: 'mcp__demo__retry_late',
205
- inputSchema: schema('retry late'),
206
- });
207
- session.deferredDiscoveredTools.push('mcp__demo__retry_late');
208
- await Promise.resolve();
209
- retryBody = buildAnthropicRequestBody(outgoing, model, tools, opts);
210
- retryApiKeyTools = anthropicApiKeyTest.requestAnthropicTools(tools, outgoing, opts);
211
- return { content: 'done', usage: { inputTokens: 100, outputTokens: 1 } };
212
- },
213
- };
214
- const result = await agentLoop(provider, messages, session.model, session.tools, null, '', {
215
- session,
216
- sessionId: session.id,
217
- onUsageDelta(delta) {
218
- if (delta.source !== 'provider_send') return;
219
- recordProviderContextBaseline(session, messages, {
220
- inputTokens: delta.deltaInput,
221
- outputTokens: delta.deltaOutput,
222
- }, { boundary: 'request', sendTools: delta.sendTools });
223
- },
224
- });
225
-
226
- assert.deepEqual(body.tools.map((tool) => tool.name), [read.name]);
227
- assert.deepEqual(apiKeyTools.map((tool) => tool.name), [read.name]);
228
- assert.deepEqual(retryBody.tools, body.tools, 'OAuth retry must reuse authoritative provider bytes');
229
- assert.deepEqual(retryApiKeyTools, apiKeyTools, 'API-key retry must reuse authoritative provider bytes');
230
- assert.equal(messages.some((message) => message.toolCallId === 'orphan-load'), false);
231
- assert.equal(Object.isFrozen(result.lastSendTools), true);
232
- assert.equal(Object.isFrozen(result.lastSendTools[0].inputSchema), true);
233
- assert.equal(session.compaction.requestReserveTokens, estimateRequestReserveTokens(result.lastSendTools));
234
- assert.equal(session.contextPressureBaselineToolSignature, toolSchemaSignature(result.lastSendTools));
235
- });
236
-
237
- test('direct Anthropic adapter callers retain unresolved deferred resolution', () => {
238
- const read = { name: 'read', description: 'Read', inputSchema: schema('path') };
239
- const mcp = { name: 'mcp__demo__lookup', description: 'Lookup', inputSchema: schema('mcp') };
240
- const messages = [{ role: 'user', content: 'hello' }];
241
- const session = {
242
- provider: 'anthropic-oauth',
243
- deferredNativeTools: true,
244
- deferredDiscoveredTools: [mcp.name],
245
- deferredToolCatalog: [read, mcp],
246
- };
247
- const native = { type: 'web_search_20250305', name: 'web_search', max_uses: 2 };
248
- const opts = { session, nativeTools: [native] };
249
- const oauthBody = buildAnthropicRequestBody(messages, 'claude-sonnet-4-6', [read], opts);
250
- const apiKeyTools = anthropicApiKeyTest.requestAnthropicTools([read], messages, opts);
251
- assert.deepEqual(oauthBody.tools.map((tool) => tool.name), [native.name, read.name, mcp.name]);
252
- assert.deepEqual(apiKeyTools.map((tool) => tool.name), [native.name, read.name, mcp.name]);
253
- assert.equal(oauthBody.tools[0], native);
254
- assert.equal(apiKeyTools[0], native);
255
- assert.equal(oauthBody.tools.at(-1).defer_loading, true);
256
- assert.equal(apiKeyTools.at(-1).defer_loading, true);
257
-
258
- const nativeOnlySnapshot = snapshotProviderRequestTools({
259
- provider: session.provider,
260
- tools: [],
261
- nativeTools: [native],
262
- messages,
263
- session,
264
- });
265
- assert.deepEqual(nativeOnlySnapshot.map((tool) => tool.name), [native.name]);
266
- });
267
-
268
- test('provider tool snapshots match JSON normalization and preserve stable provider bytes', () => {
269
- let accessorDescription = 'snapshotted accessor';
270
- const inherited = { inheritedMutation: 'before' };
271
- const properties = Object.create(inherited);
272
- Object.defineProperty(properties, 'dynamic', {
273
- enumerable: true,
274
- get() {
275
- return { type: 'string', description: accessorDescription };
276
- },
277
- });
278
- Object.defineProperty(properties, '__proto__', {
279
- enumerable: true,
280
- configurable: true,
281
- writable: true,
282
- value: { type: 'string', description: 'own prototype key' },
283
- });
284
- const inputSchema = Object.create({ inheritedSchemaField: 'not provider-visible' });
285
- inputSchema.type = 'object';
286
- inputSchema.properties = properties;
287
- inputSchema.optionalUndefined = undefined;
288
- inputSchema.optionalFunction = () => {};
289
- inputSchema.optionalSymbol = Symbol('omit');
290
- inputSchema.arrayValues = [
291
- undefined,
292
- () => {},
293
- Symbol('array-null'),
294
- Number.NaN,
295
- Number.POSITIVE_INFINITY,
296
- Number.NEGATIVE_INFINITY,
297
- null,
298
- '[Circular]',
299
- ];
300
- inputSchema.nullValue = null;
301
- const tool = Object.assign(Object.create({ inheritedToolField: 'hidden' }), {
302
- name: 'read',
303
- description: 'Read',
304
- inputSchema,
305
- });
306
- const snapshot = snapshotProviderRequestTools({
307
- provider: 'anthropic-oauth',
308
- tools: [tool],
309
- messages: [{ role: 'user', content: 'hello' }],
310
- session: { provider: 'anthropic-oauth' },
311
- });
312
- const signature = toolSchemaSignature(snapshot);
313
- const reserve = estimateRequestReserveTokens(snapshot);
314
-
315
- accessorDescription = 'mutated accessor';
316
- inherited.inheritedMutation = 'after';
317
- properties.__proto__.description = 'mutated own prototype key';
318
- inputSchema.type = 'array';
319
-
320
- assert.equal(Object.getPrototypeOf(snapshot[0]), Object.prototype);
321
- assert.equal(Object.getPrototypeOf(snapshot[0].inputSchema), Object.prototype);
322
- assert.equal(snapshot[0].inputSchema.type, 'object');
323
- assert.equal(snapshot[0].inputSchema.properties.dynamic.description, 'snapshotted accessor');
324
- assert.equal(snapshot[0].inputSchema.properties.__proto__.description, 'own prototype key');
325
- assert.equal(Object.hasOwn(snapshot[0].inputSchema.properties, '__proto__'), true);
326
- assert.equal(Object.hasOwn(snapshot[0].inputSchema, 'optionalUndefined'), false);
327
- assert.equal(Object.hasOwn(snapshot[0].inputSchema, 'optionalFunction'), false);
328
- assert.equal(Object.hasOwn(snapshot[0].inputSchema, 'optionalSymbol'), false);
329
- assert.deepEqual(snapshot[0].inputSchema.arrayValues, [
330
- null, null, null, null, null, null, null, '[Circular]',
331
- ]);
332
- assert.equal(snapshot[0].inputSchema.nullValue, null);
333
- assert.equal('inheritedMutation' in snapshot[0].inputSchema.properties, false);
334
- assert.equal('inheritedSchemaField' in snapshot[0].inputSchema, false);
335
- assert.equal('inheritedToolField' in snapshot[0], false);
336
- assert.equal(toolSchemaSignature(snapshot), signature);
337
- assert.equal(estimateRequestReserveTokens(snapshot), reserve);
338
-
339
- const opts = {
340
- providerToolSnapshotAuthoritative: true,
341
- session: {
342
- provider: 'anthropic-oauth',
343
- deferredNativeTools: true,
344
- deferredDiscoveredTools: ['mcp__should_not_appear'],
345
- deferredToolCatalog: [{ name: 'mcp__should_not_appear', inputSchema: schema('late') }],
346
- },
347
- };
348
- const messages = [{ role: 'user', content: 'hello' }];
349
- const oauthBody = buildAnthropicRequestBody(messages, 'claude-sonnet-4-6', snapshot, opts);
350
- const apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(snapshot, messages, opts);
351
- for (const wireTool of [oauthBody.tools[0], apiKeyTools[0]]) {
352
- assert.equal(wireTool.input_schema.type, 'object');
353
- assert.equal(wireTool.input_schema.properties.dynamic.description, 'snapshotted accessor');
354
- assert.equal(wireTool.input_schema.properties.__proto__.description, 'own prototype key');
355
- assert.equal(Object.hasOwn(wireTool.input_schema.properties, '__proto__'), true);
356
- }
357
- });
358
-
359
- test('provider tool snapshot captures JSON array length once', () => {
360
- const snapshotArray = (values) => snapshotProviderRequestTools({
361
- provider: 'anthropic-oauth',
362
- tools: [{ name: 'array_tool', inputSchema: { type: 'object', values } }],
363
- messages: [],
364
- session: { provider: 'anthropic-oauth' },
365
- })[0].inputSchema.values;
366
-
367
- const extending = [null];
368
- Object.defineProperty(extending, 0, {
369
- enumerable: true,
370
- get() {
371
- extending.push('late');
372
- return 'first';
373
- },
374
- });
375
- assert.deepEqual(snapshotArray(extending), ['first']);
376
-
377
- const shrinking = ['first', 'second', 'third'];
378
- Object.defineProperty(shrinking, 0, {
379
- enumerable: true,
380
- get() {
381
- shrinking.length = 1;
382
- return 'first';
383
- },
384
- });
385
- assert.deepEqual(snapshotArray(shrinking), ['first', null, null]);
386
-
387
- const sparse = new Array(4);
388
- sparse[2] = 'present';
389
- assert.deepEqual(snapshotArray(sparse), [null, null, 'present', null]);
390
-
391
- const getterFailure = new Error('array getter failed');
392
- const throwing = new Array(2);
393
- Object.defineProperty(throwing, 0, {
394
- enumerable: true,
395
- get() { throw getterFailure; },
396
- });
397
- assert.throws(() => snapshotArray(throwing), (error) => error === getterFailure);
398
-
399
- let conversions = 0;
400
- const fractional = new Proxy([], {
401
- get(target, key, receiver) {
402
- if (key === 'length') {
403
- return {
404
- valueOf() {
405
- conversions += 1;
406
- return 2.9;
407
- },
408
- };
409
- }
410
- if (key === '0') return 'zero';
411
- if (key === '1') return 'one';
412
- return Reflect.get(target, key, receiver);
413
- },
414
- });
415
- assert.deepEqual(snapshotArray(fractional), ['zero', 'one']);
416
- assert.equal(conversions, 1);
417
-
418
- let nullExoticConversions = 0;
419
- const nullExotic = new Proxy([], {
420
- get(target, key, receiver) {
421
- if (key === 'length') {
422
- return {
423
- [Symbol.toPrimitive]: null,
424
- valueOf() {
425
- nullExoticConversions += 1;
426
- return 1.9;
427
- },
428
- };
429
- }
430
- if (key === '0') return 'ordinary';
431
- return Reflect.get(target, key, receiver);
432
- },
433
- });
434
- assert.deepEqual(snapshotArray(nullExotic), ['ordinary']);
435
- assert.equal(nullExoticConversions, 1);
436
-
437
- const cappedFraction = new Proxy([], {
438
- get(target, key, receiver) {
439
- if (key === 'length') return 1_000_000.1;
440
- return Reflect.get(target, key, receiver);
441
- },
442
- });
443
- assert.equal(snapshotArray(cappedFraction).length, 1_000_000);
444
-
445
- for (const [rawLength, expected] of [
446
- [-1, []],
447
- [Number.NaN, []],
448
- [0.9, []],
449
- ]) {
450
- const values = new Proxy([], {
451
- get(target, key, receiver) {
452
- return key === 'length' ? rawLength : Reflect.get(target, key, receiver);
453
- },
454
- });
455
- assert.deepEqual(snapshotArray(values), expected);
456
- }
457
-
458
- for (const rawLength of [
459
- 1n,
460
- Symbol('length'),
461
- { valueOf: () => 1n },
462
- { [Symbol.toPrimitive]: () => Symbol('length') },
463
- ]) {
464
- const values = new Proxy([], {
465
- get(target, key, receiver) {
466
- return key === 'length' ? rawLength : Reflect.get(target, key, receiver);
467
- },
468
- });
469
- assert.throws(
470
- () => snapshotArray(values),
471
- (error) => error instanceof TypeError
472
- && error.message === 'provider tool snapshot: invalid array length',
473
- );
474
- }
475
- for (const rawLength of [Number.POSITIVE_INFINITY, 1_000_001, Number.MAX_SAFE_INTEGER]) {
476
- const values = new Proxy([], {
477
- get(target, key, receiver) {
478
- return key === 'length' ? rawLength : Reflect.get(target, key, receiver);
479
- },
480
- });
481
- assert.throws(
482
- () => snapshotArray(values),
483
- (error) => error instanceof RangeError
484
- && error.message === 'provider tool snapshot: array length exceeds safe limit 1000000',
485
- );
486
- }
487
- });
488
-
489
- test('request tool scopes isolate parallel and nested same-session gauges and restore on errors', async () => {
490
- const session = {
491
- id: 'scope-isolation',
492
- provider: 'anthropic-oauth',
493
- messages: [{ role: 'user', content: 'fallback' }],
494
- tools: [{ name: 'fallback', inputSchema: schema('fallback') }],
495
- };
496
- const messagesA = [{ role: 'user', content: 'a' }];
497
- const messagesB = [{ role: 'user', content: 'b' }];
498
- const messagesNested = [{ role: 'user', content: 'nested' }];
499
- const snapshotA = snapshotProviderRequestTools({
500
- provider: session.provider,
501
- nativeTools: [{ type: 'native_a', name: 'native_a', input_schema: { ambiguous: true } }],
502
- tools: [{ name: 'a', inputSchema: schema('a') }],
503
- messages: messagesA,
504
- session,
505
- });
506
- const snapshotB = snapshotProviderRequestTools({
507
- provider: session.provider,
508
- tools: [{ name: 'b', inputSchema: schema('b') }],
509
- messages: messagesB,
510
- session,
511
- });
512
- const nestedSnapshot = snapshotProviderRequestTools({
513
- provider: session.provider,
514
- tools: [{ name: 'nested', inputSchema: schema('nested') }],
515
- messages: messagesNested,
516
- session,
517
- });
518
- const scopeFor = (messages, requestTools) => ({
519
- session,
520
- provider: session.provider,
521
- messages,
522
- requestTools,
523
- nativePrefixCount: providerNativeToolPrefixCount(requestTools),
524
- });
525
-
526
- const [seenA, seenB] = await Promise.all([
527
- runWithProviderRequestToolsScope(scopeFor(messagesA, snapshotA), async () => {
528
- await new Promise((resolve) => setTimeout(resolve, 5));
529
- assert.equal(
530
- requestSerializedToolsForContext(session, session.provider, messagesA),
531
- snapshotA,
532
- );
533
- assert.equal(scopedProviderRequestTools(session, 'anthropic', messagesA), null);
534
- await runWithProviderRequestToolsScope(scopeFor(messagesNested, nestedSnapshot), async () => {
535
- await Promise.resolve();
536
- assert.equal(
537
- requestSerializedToolsForContext(session, session.provider, messagesNested),
538
- nestedSnapshot,
539
- );
540
- });
541
- assert.equal(
542
- requestSerializedToolsForContext(session, session.provider, messagesA),
543
- snapshotA,
544
- );
545
- return requestSerializedToolsForContext(session, session.provider, messagesA);
546
- }),
547
- runWithProviderRequestToolsScope(scopeFor(messagesB, snapshotB), async () => {
548
- await Promise.resolve();
549
- return requestSerializedToolsForContext(session, session.provider, messagesB);
550
- }),
551
- ]);
552
- assert.equal(seenA, snapshotA);
553
- assert.equal(seenB, snapshotB);
554
-
555
- let delayedErrorLookup;
556
- let resolveDelayedError;
557
- const delayedError = new Promise((resolve) => {
558
- resolveDelayedError = resolve;
559
- });
560
- await assert.rejects(
561
- runWithProviderRequestToolsScope(scopeFor(messagesA, snapshotA), async () => {
562
- assert.equal(
563
- requestSerializedToolsForContext(session, session.provider, messagesA),
564
- snapshotA,
565
- );
566
- setTimeout(() => {
567
- delayedErrorLookup = scopedProviderRequestTools(session, session.provider, messagesA);
568
- resolveDelayedError();
569
- }, 10);
570
- throw new Error('scope failure');
571
- }),
572
- /scope failure/,
573
- );
574
- await delayedError;
575
- assert.equal(delayedErrorLookup, null);
576
- assert.equal(scopedProviderRequestTools(session, session.provider, messagesA), null);
577
-
578
- let delayedOldLookup;
579
- const delayedOld = new Promise((resolve) => {
580
- runWithProviderRequestToolsScope(scopeFor(messagesA, snapshotA), () => {
581
- setTimeout(() => {
582
- delayedOldLookup = scopedProviderRequestTools(session, session.provider, messagesA);
583
- resolve();
584
- }, 15);
585
- });
586
- });
587
- await runWithProviderRequestToolsScope(scopeFor(messagesA, snapshotB), async () => {
588
- assert.equal(
589
- scopedProviderRequestTools(session, session.provider, messagesA)?.requestTools,
590
- snapshotB,
591
- );
592
- await delayedOld;
593
- assert.equal(delayedOldLookup, null);
594
- });
595
-
596
- await runWithProviderRequestToolsScope(scopeFor(messagesA, snapshotA), async () => {
597
- const outerScope = scopedProviderRequestTools(session, session.provider, messagesA);
598
- await runWithProviderRequestToolsScope(scopeFor(messagesNested, nestedSnapshot), async () => {
599
- invalidateProviderRequestToolsScope(outerScope);
600
- assert.equal(
601
- scopedProviderRequestTools(session, session.provider, messagesNested)?.requestTools,
602
- nestedSnapshot,
603
- );
604
- });
605
- assert.equal(scopedProviderRequestTools(session, session.provider, messagesA), null);
606
- });
607
-
608
- const explicitNative = [{ type: 'fallback_native', name: 'fallback_native' }];
609
- const fallback = requestSerializedToolsForContext(
610
- session,
611
- session.provider,
612
- session.messages,
613
- { nativeTools: explicitNative },
614
- );
615
- assert.deepEqual(fallback.map((tool) => tool.name), ['fallback_native', 'fallback']);
616
- assert.equal(providerNativeToolPrefixCount(fallback), 1);
617
- });
618
-
619
- test('selected deferred snapshots force one pre-freeze deferLoading value', () => {
620
- const snapshotSelected = (tool) => snapshotProviderRequestTools({
621
- provider: 'anthropic-oauth',
622
- tools: [{ name: 'read', inputSchema: schema('path') }],
623
- messages: [],
624
- session: {
625
- provider: 'anthropic-oauth',
626
- deferredNativeTools: true,
627
- deferredDiscoveredTools: ['mcp__demo__selected'],
628
- deferredToolCatalog: [tool],
629
- },
630
- });
631
- for (const initial of [true, false]) {
632
- const tool = { name: 'mcp__demo__selected', inputSchema: schema(String(initial)) };
633
- Object.defineProperty(tool, 'deferLoading', {
634
- value: initial,
635
- enumerable: true,
636
- configurable: false,
637
- });
638
- const selected = snapshotSelected(tool).at(-1);
639
- assert.equal(selected.deferLoading, true);
640
- assert.equal(Object.isFrozen(selected), true);
641
- }
642
-
643
- let getterReads = 0;
644
- const accessor = { name: 'mcp__demo__selected', inputSchema: schema('getter') };
645
- Object.defineProperty(accessor, 'deferLoading', {
646
- enumerable: true,
647
- configurable: false,
648
- get() {
649
- getterReads += 1;
650
- if (getterReads > 1) throw new Error('deferLoading read twice');
651
- return false;
652
- },
653
- });
654
- const selected = snapshotSelected(accessor).at(-1);
655
- assert.equal(getterReads, 1);
656
- assert.equal(selected.deferLoading, true);
657
-
658
- let calls = 0;
659
- const callable = {
660
- name: 'mcp__demo__selected',
661
- toJSON() {
662
- calls += 1;
663
- const properties = {};
664
- Object.defineProperty(properties, '__proto__', {
665
- value: { type: 'string' },
666
- enumerable: true,
667
- });
668
- return {
669
- name: 'mcp__demo__selected',
670
- inputSchema: {
671
- type: 'object',
672
- properties,
673
- },
674
- deferLoading: false,
675
- };
676
- },
677
- };
678
- const callableSelected = snapshotSelected(callable).at(-1);
679
- assert.equal(calls, 1);
680
- assert.equal(callableSelected.deferLoading, true);
681
- assert.equal(Object.hasOwn(callableSelected.inputSchema.properties, '__proto__'), true);
682
- });
683
-
684
- test('authoritative snapshot includes native tools in gauge, pressure, baseline, and both Anthropic bodies', async () => {
685
- let schemaReads = 0;
686
- let schemaToJsonReads = 0;
687
- let schemaToJsonCalls = 0;
688
- const read = { name: 'read' };
689
- Object.defineProperty(read, 'inputSchema', {
690
- enumerable: true,
691
- get() {
692
- schemaReads += 1;
693
- if (schemaReads > 1) throw new Error('request schema observed twice');
694
- const inputSchema = {};
695
- Object.defineProperty(inputSchema, 'toJSON', {
696
- enumerable: true,
697
- get() {
698
- schemaToJsonReads += 1;
699
- if (schemaToJsonReads > 1) throw new Error('request schema toJSON observed twice');
700
- return function toJSON() {
701
- schemaToJsonCalls += 1;
702
- if (schemaToJsonCalls > 1) throw new Error('request schema toJSON called twice');
703
- return schema('single gauge/send snapshot');
704
- };
705
- },
706
- });
707
- return inputSchema;
708
- },
709
- });
710
- const native = {
711
- type: 'web_search_20250305',
712
- name: 'web_search',
713
- max_uses: 3,
714
- };
715
- const ambiguousNative = {
716
- type: 'ambiguous_native',
717
- name: 'read',
718
- input_schema: { nativeAlias: 'input_schema' },
719
- inputSchema: { nativeAlias: 'inputSchema' },
720
- parameters: { nativeAlias: 'parameters' },
721
- schema: { nativeAlias: 'schema' },
722
- };
723
- const session = {
724
- id: 'native-authoritative-snapshot',
725
- owner: 'agent',
726
- provider: 'anthropic-oauth',
727
- model: 'claude-sonnet-4-6',
728
- contextWindow: 100_000,
729
- messages: [{ role: 'user', content: 'hello' }],
730
- tools: [read],
731
- compaction: {},
732
- };
733
- const { contextStatus } = createContextStatus({
734
- getSession: () => session,
735
- getRoute: () => ({ provider: session.provider, model: session.model }),
736
- getCurrentCwd: () => '',
737
- getMode: () => 'default',
738
- getNativeTools() {
739
- throw new Error('scoped gauge must not read live native tools');
740
- },
741
- });
742
- let firstBody;
743
- let retryBody;
744
- let apiKeyTools;
745
- let gauge;
746
- let baselineGauge;
747
- const provider = {
748
- name: session.provider,
749
- async send(messages, model, tools, opts) {
750
- gauge = contextStatus();
751
- assert.equal(schemaReads, 1);
752
- assert.equal(schemaToJsonReads, 1);
753
- assert.equal(schemaToJsonCalls, 1);
754
- assert.equal(opts.providerNativeToolPrefixCount, 2);
755
- const expectedSignature = toolSchemaSignature(tools);
756
- const expectedReserve = estimateRequestReserveTokens(tools);
757
- native.max_uses = 99;
758
- ambiguousNative.input_schema.nativeAlias = 'mutated';
759
- opts.nativeTools.push({ type: 'code_execution_20250825', name: 'late_native' });
760
- opts.providerNativeToolPrefixCount = 0;
761
- firstBody = buildAnthropicRequestBody(messages, model, tools, opts);
762
- apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(tools, messages, opts);
763
- retryBody = buildAnthropicRequestBody(messages, model, tools, opts);
764
- assert.equal(toolSchemaSignature(tools), expectedSignature);
765
- assert.equal(estimateRequestReserveTokens(tools), expectedReserve);
766
- return { content: 'done', usage: { inputTokens: 100, outputTokens: 1 } };
767
- },
768
- };
769
- const result = await agentLoop(provider, session.messages, session.model, session.tools, null, '', {
770
- session,
771
- sessionId: session.id,
772
- nativeTools: [native, ambiguousNative],
773
- onUsageDelta(delta) {
774
- if (delta.source !== 'provider_send') return;
775
- baselineGauge = contextStatus();
776
- recordProviderContextBaseline(session, session.messages, {
777
- inputTokens: delta.deltaInput,
778
- outputTokens: delta.deltaOutput,
779
- }, { boundary: 'request', sendTools: delta.sendTools });
780
- },
781
- });
782
-
783
- assert.equal(schemaReads, 1);
784
- assert.equal(schemaToJsonReads, 1);
785
- assert.equal(schemaToJsonCalls, 1);
786
- assert.deepEqual(result.lastSendTools.map((tool) => tool.name), ['web_search', 'read', 'read']);
787
- assert.equal(providerNativeToolPrefixCount(result.lastSendTools), 2);
788
- assert.deepEqual(firstBody.tools, retryBody.tools);
789
- assert.deepEqual(firstBody.tools, apiKeyTools);
790
- assert.equal(firstBody.tools[0].max_uses, 3);
791
- assert.deepEqual(firstBody.tools[1], {
792
- type: 'ambiguous_native',
793
- name: 'read',
794
- input_schema: { nativeAlias: 'input_schema' },
795
- inputSchema: { nativeAlias: 'inputSchema' },
796
- parameters: { nativeAlias: 'parameters' },
797
- schema: { nativeAlias: 'schema' },
798
- });
799
- assert.equal(firstBody.tools.some((tool) => tool.name === 'late_native'), false);
800
- assert.equal(gauge.request.reserveTokens, estimateRequestReserveTokens(result.lastSendTools));
801
- assert.equal(baselineGauge.request.reserveTokens, gauge.request.reserveTokens);
802
- const nativeWebOnly = snapshotProviderRequestTools({
803
- provider: session.provider,
804
- nativeTools: [result.lastSendTools[0]],
805
- tools: [],
806
- messages: session.messages,
807
- session,
808
- });
809
- const nativeAmbiguousOnly = snapshotProviderRequestTools({
810
- provider: session.provider,
811
- nativeTools: [result.lastSendTools[1]],
812
- tools: [],
813
- messages: session.messages,
814
- session,
815
- });
816
- const functionOnly = snapshotProviderRequestTools({
817
- provider: session.provider,
818
- tools: [result.lastSendTools[2]],
819
- messages: session.messages,
820
- session,
821
- });
822
- assert.equal(
823
- gauge.request.toolSchemaBreakdown.other.tokens,
824
- estimateToolSchemaTokens(nativeWebOnly),
825
- );
826
- assert.equal(
827
- gauge.request.toolSchemaBreakdown.code.tokens,
828
- estimateToolSchemaTokens(nativeAmbiguousOnly) + estimateToolSchemaTokens(functionOnly),
829
- );
830
- assert.deepEqual(
831
- estimateToolSchemaBreakdown(result.lastSendTools),
832
- gauge.request.toolSchemaBreakdown,
833
- );
834
- assert.equal(session.compaction.requestReserveTokens, estimateRequestReserveTokens(result.lastSendTools));
835
- assert.equal(session.contextPressureBaselineToolSignature, toolSchemaSignature(result.lastSendTools));
836
- });
837
-
838
- test('provider tool snapshots reject BigInt, cycles, and throwing accessors without wire substitutes', () => {
839
- const snapshotForSchema = (inputSchema) => snapshotProviderRequestTools({
840
- provider: 'anthropic-oauth',
841
- tools: [{ name: 'read', inputSchema }],
842
- messages: [],
843
- session: { provider: 'anthropic-oauth' },
844
- });
845
-
846
- assert.throws(
847
- () => snapshotForSchema({ type: 'object', invalid: 1n }),
848
- (error) => error instanceof TypeError
849
- && error.message === 'provider tool snapshot: BigInt is not JSON-serializable',
850
- );
851
- assert.throws(
852
- () => snapshotForSchema({ type: 'object', invalid: Object(1n) }),
853
- (error) => error instanceof TypeError
854
- && error.message === 'provider tool snapshot: BigInt is not JSON-serializable',
855
- );
856
-
857
- const cyclic = { type: 'object' };
858
- cyclic.self = cyclic;
859
- assert.throws(
860
- () => snapshotForSchema(cyclic),
861
- (error) => error instanceof TypeError
862
- && error.message === 'provider tool snapshot: cyclic value is not JSON-serializable',
863
- );
864
-
865
- const accessorFailure = new Error('schema accessor failed');
866
- const throwing = { type: 'object' };
867
- Object.defineProperty(throwing, 'properties', {
868
- enumerable: true,
869
- get() { throw accessorFailure; },
870
- });
871
- assert.throws(
872
- () => snapshotForSchema(throwing),
873
- (error) => error === accessorFailure,
874
- );
875
-
876
- const legitimate = snapshotForSchema({
877
- type: 'object',
878
- properties: {
879
- literal: { type: 'string', description: '[Circular]' },
880
- nullable: { type: 'string', default: null },
881
- },
882
- });
883
- assert.equal(legitimate[0].inputSchema.properties.literal.description, '[Circular]');
884
- assert.equal(legitimate[0].inputSchema.properties.nullable.default, null);
885
- assert.doesNotThrow(() => toolSchemaSignature(legitimate));
886
- assert.doesNotThrow(() => estimateRequestReserveTokens(legitimate));
887
- });
888
-
889
- test('provider tool snapshot evaluates active and selected catalog names exactly once', () => {
890
- let activeNameReads = 0;
891
- const active = {
892
- description: 'Read',
893
- inputSchema: schema('active'),
894
- };
895
- Object.defineProperty(active, 'name', {
896
- enumerable: true,
897
- get() {
898
- activeNameReads += 1;
899
- if (activeNameReads > 1) throw new Error('active name read twice');
900
- return 'read';
901
- },
902
- });
903
-
904
- let selectedNameReads = 0;
905
- let selectedSchemaReads = 0;
906
- const selected = { description: 'Selected MCP' };
907
- Object.defineProperty(selected, 'name', {
908
- enumerable: true,
909
- get() {
910
- selectedNameReads += 1;
911
- if (selectedNameReads > 1) throw new Error('selected name read twice');
912
- return 'mcp__demo__selected';
913
- },
914
- });
915
- Object.defineProperty(selected, 'inputSchema', {
916
- enumerable: true,
917
- get() {
918
- selectedSchemaReads += 1;
919
- return schema('selected');
920
- },
921
- });
922
- const undiscovered = { name: 'mcp__demo__undiscovered' };
923
- Object.defineProperty(undiscovered, 'inputSchema', {
924
- enumerable: true,
925
- get() { throw new Error('undiscovered schema must stay inert'); },
926
- });
927
- const duplicate = { name: 'mcp__demo__selected' };
928
- Object.defineProperty(duplicate, 'inputSchema', {
929
- enumerable: true,
930
- get() { throw new Error('duplicate schema must stay inert'); },
931
- });
932
- const session = {
933
- provider: 'anthropic-oauth',
934
- deferredNativeTools: true,
935
- deferredDiscoveredTools: ['mcp__demo__selected'],
936
- // The active object is intentionally also in the catalog; identity skip
937
- // must prevent a second active-name observation.
938
- deferredToolCatalog: [active, selected, undiscovered, duplicate],
939
- };
940
- const messages = [{ role: 'user', content: 'hello' }];
941
- const snapshot = snapshotProviderRequestTools({
942
- provider: session.provider,
943
- tools: [active],
944
- messages,
945
- session,
946
- });
947
-
948
- assert.equal(activeNameReads, 1);
949
- assert.equal(selectedNameReads, 1);
950
- assert.equal(selectedSchemaReads, 1);
951
- assert.deepEqual(snapshot.map((tool) => tool.name), ['read', 'mcp__demo__selected']);
952
- assert.equal(new Set(snapshot.map((tool) => tool.name)).size, snapshot.length);
953
- const signature = toolSchemaSignature(snapshot);
954
- const reserve = estimateRequestReserveTokens(snapshot);
955
- const opts = { session, providerToolSnapshotAuthoritative: true };
956
- const oauthBody = buildAnthropicRequestBody(messages, 'claude-sonnet-4-6', snapshot, opts);
957
- const apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(snapshot, messages, opts);
958
- assert.deepEqual(oauthBody.tools.map((tool) => tool.name), snapshot.map((tool) => tool.name));
959
- assert.deepEqual(apiKeyTools.map((tool) => tool.name), snapshot.map((tool) => tool.name));
960
- assert.equal(toolSchemaSignature(snapshot), signature);
961
- assert.equal(estimateRequestReserveTokens(snapshot), reserve);
962
- });
963
-
964
- test('selected deferred tool identity is stable before pressure, baseline, or provider effects', async () => {
965
- const read = { name: 'read', inputSchema: schema('path') };
966
- const selectedTool = (toJSON) => ({
967
- name: 'mcp__demo__selected',
968
- toJSON,
969
- });
970
- const snapshotSelected = (tool) => snapshotProviderRequestTools({
971
- provider: 'anthropic-oauth',
972
- tools: [read],
973
- messages: [{ role: 'user', content: 'hello' }],
974
- session: {
975
- provider: 'anthropic-oauth',
976
- deferredNativeTools: true,
977
- deferredDiscoveredTools: ['mcp__demo__selected'],
978
- deferredToolCatalog: [read, tool],
979
- },
980
- });
981
- const identityError = (error) => error instanceof TypeError
982
- && error.message === 'provider tool snapshot: selected tool identity mismatch for "mcp__demo__selected"';
983
-
984
- for (const tool of [
985
- selectedTool(() => ({ name: 'mcp__demo__other', inputSchema: schema('changed identity') })),
986
- selectedTool(() => ({ inputSchema: schema('missing identity') })),
987
- selectedTool(() => ({ name: 42, inputSchema: schema('non-string identity') })),
988
- ]) {
989
- assert.throws(() => snapshotSelected(tool), identityError);
990
- }
991
-
992
- const activeByNormalizedName = snapshotProviderRequestTools({
993
- provider: 'anthropic-oauth',
994
- tools: [
995
- {
996
- name: 'pre_normalized_name',
997
- toJSON: () => ({ name: 'normalized_name', inputSchema: schema('first') }),
998
- },
999
- { name: 'normalized_name', inputSchema: schema('duplicate') },
1000
- ],
1001
- messages: [],
1002
- session: { provider: 'anthropic-oauth' },
1003
- });
1004
- assert.deepEqual(activeByNormalizedName.map((tool) => tool.name), ['normalized_name']);
1005
-
1006
- let providerCalls = 0;
1007
- const session = {
1008
- id: 'selected-identity-mismatch',
1009
- owner: 'agent',
1010
- provider: 'anthropic-oauth',
1011
- model: 'claude-sonnet-4-6',
1012
- contextWindow: 100_000,
1013
- messages: [{ role: 'user', content: 'hello' }],
1014
- tools: [read],
1015
- deferredNativeTools: true,
1016
- deferredDiscoveredTools: ['mcp__demo__selected'],
1017
- deferredToolCatalog: [
1018
- read,
1019
- selectedTool(() => ({ name: 'mcp__demo__other', inputSchema: schema('agent mismatch') })),
1020
- ],
1021
- compaction: {},
1022
- };
1023
- const provider = {
1024
- name: session.provider,
1025
- async send() {
1026
- providerCalls += 1;
1027
- return { content: 'must not send', usage: { inputTokens: 1, outputTokens: 1 } };
1028
- },
1029
- };
1030
- await assert.rejects(
1031
- agentLoop(provider, session.messages, session.model, session.tools, null, '', {
1032
- session,
1033
- sessionId: session.id,
1034
- }),
1035
- identityError,
1036
- );
1037
- assert.equal(providerCalls, 0);
1038
- assert.deepEqual(session.compaction, {});
1039
- assert.equal(Object.hasOwn(session, 'contextPressureBaselineToolSignature'), false);
1040
- assert.equal(Object.hasOwn(session, 'contextPressureBaseline'), false);
1041
- });
1042
-
1043
- test('provider tool snapshot applies valid toJSON once and rejects invalid toJSON results', () => {
1044
- let topLevelCalls = 0;
1045
- let nestedCalls = 0;
1046
- let nestedDescription = 'nested snapshot';
1047
- const nestedSchema = {
1048
- toJSON() {
1049
- nestedCalls += 1;
1050
- return {
1051
- type: 'object',
1052
- properties: { value: { type: 'string', description: nestedDescription } },
1053
- };
1054
- },
1055
- };
1056
- const topLevelTool = {
1057
- toJSON() {
1058
- topLevelCalls += 1;
1059
- return {
1060
- name: 'read',
1061
- description: 'top-level snapshot',
1062
- inputSchema: nestedSchema,
1063
- };
1064
- },
1065
- };
1066
- const valid = snapshotProviderRequestTools({
1067
- provider: 'anthropic-oauth',
1068
- tools: [topLevelTool],
1069
- messages: [],
1070
- session: { provider: 'anthropic-oauth' },
1071
- });
1072
- nestedDescription = 'mutated after snapshot';
1073
- assert.equal(topLevelCalls, 1);
1074
- assert.equal(nestedCalls, 1);
1075
- assert.equal(valid[0].inputSchema.properties.value.description, 'nested snapshot');
1076
- assert.equal(Object.isFrozen(valid[0].inputSchema), true);
1077
- const validSignature = toolSchemaSignature(valid);
1078
- const validReserve = estimateRequestReserveTokens(valid);
1079
- const opts = { providerToolSnapshotAuthoritative: true, session: { provider: 'anthropic-oauth' } };
1080
- const messages = [{ role: 'user', content: 'hello' }];
1081
- const oauthBody = buildAnthropicRequestBody(messages, 'claude-sonnet-4-6', valid, opts);
1082
- const apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(valid, messages, opts);
1083
- assert.equal(oauthBody.tools[0].input_schema.properties.value.description, 'nested snapshot');
1084
- assert.equal(apiKeyTools[0].input_schema.properties.value.description, 'nested snapshot');
1085
- assert.equal(toolSchemaSignature(valid), validSignature);
1086
- assert.equal(estimateRequestReserveTokens(valid), validReserve);
1087
-
1088
- const snapshotTool = (tool) => snapshotProviderRequestTools({
1089
- provider: 'anthropic-oauth',
1090
- tools: [tool],
1091
- messages: [],
1092
- session: { provider: 'anthropic-oauth' },
1093
- });
1094
- let ownNonFunctionReads = 0;
1095
- const ownNonFunction = { name: 'own_non_function', inputSchema: schema('own') };
1096
- Object.defineProperty(ownNonFunction, 'toJSON', {
1097
- enumerable: true,
1098
- get() {
1099
- ownNonFunctionReads += 1;
1100
- if (ownNonFunctionReads > 1) throw new Error('own non-function toJSON read twice');
1101
- return 'metadata';
1102
- },
1103
- });
1104
- const ownNonFunctionSnapshot = snapshotTool(ownNonFunction);
1105
- assert.equal(ownNonFunctionReads, 1);
1106
- assert.equal(ownNonFunctionSnapshot[0].toJSON, 'metadata');
1107
- assert.deepEqual(Object.keys(ownNonFunctionSnapshot[0]), ['name', 'inputSchema', 'toJSON']);
1108
-
1109
- let inheritedNonFunctionReads = 0;
1110
- const inheritedPrototype = {};
1111
- Object.defineProperty(inheritedPrototype, 'toJSON', {
1112
- get() {
1113
- inheritedNonFunctionReads += 1;
1114
- if (inheritedNonFunctionReads > 1) throw new Error('inherited non-function toJSON read twice');
1115
- return 'inherited metadata';
1116
- },
1117
- });
1118
- const inheritedNonFunction = Object.assign(Object.create(inheritedPrototype), {
1119
- name: 'inherited_non_function',
1120
- inputSchema: schema('inherited'),
1121
- });
1122
- const inheritedNonFunctionSnapshot = snapshotTool(inheritedNonFunction);
1123
- assert.equal(inheritedNonFunctionReads, 1);
1124
- assert.equal(Object.hasOwn(inheritedNonFunctionSnapshot[0], 'toJSON'), false);
1125
-
1126
- let callableReads = 0;
1127
- let callableCalls = 0;
1128
- const callableAccessor = {};
1129
- Object.defineProperty(callableAccessor, 'toJSON', {
1130
- enumerable: true,
1131
- get() {
1132
- callableReads += 1;
1133
- if (callableReads > 1) throw new Error('callable toJSON read twice');
1134
- return function toJSON() {
1135
- callableCalls += 1;
1136
- return { name: 'callable_accessor', inputSchema: schema('callable') };
1137
- };
1138
- },
1139
- });
1140
- const callableSnapshot = snapshotTool(callableAccessor);
1141
- assert.equal(callableReads, 1);
1142
- assert.equal(callableCalls, 1);
1143
- assert.equal(callableSnapshot[0].name, 'callable_accessor');
1144
-
1145
- for (const tool of [
1146
- { toJSON: () => 1n },
1147
- { toJSON: () => ({ name: 'read', inputSchema: { toJSON: () => ({ invalid: 1n }) } }) },
1148
- ]) {
1149
- assert.throws(
1150
- () => snapshotTool(tool),
1151
- (error) => error instanceof TypeError
1152
- && error.message === 'provider tool snapshot: BigInt is not JSON-serializable',
1153
- );
1154
- }
1155
- const cyclicOutput = { name: 'read' };
1156
- cyclicOutput.self = cyclicOutput;
1157
- assert.throws(
1158
- () => snapshotTool({ toJSON: () => cyclicOutput }),
1159
- (error) => error instanceof TypeError
1160
- && error.message === 'provider tool snapshot: cyclic value is not JSON-serializable',
1161
- );
1162
- });
1163
-
1164
- test('toJSON source-reference cycles fail once before pressure, baseline, or provider effects', async () => {
1165
- const cycleError = (error) => error instanceof TypeError
1166
- && error.message === 'provider tool snapshot: cyclic value is not JSON-serializable';
1167
- const sourceCycle = (replacementFor) => {
1168
- let getterReads = 0;
1169
- let calls = 0;
1170
- const source = {};
1171
- Object.defineProperty(source, 'toJSON', {
1172
- enumerable: true,
1173
- get() {
1174
- getterReads += 1;
1175
- if (getterReads > 1) throw new Error('cyclic toJSON getter read twice');
1176
- return function toJSON() {
1177
- calls += 1;
1178
- if (calls > 1) throw new Error('cyclic toJSON called twice');
1179
- return replacementFor(source);
1180
- };
1181
- },
1182
- });
1183
- return {
1184
- source,
1185
- counts: () => ({ getterReads, calls }),
1186
- };
1187
- };
1188
- const snapshot = (tool) => snapshotProviderRequestTools({
1189
- provider: 'anthropic-oauth',
1190
- tools: [tool],
1191
- messages: [],
1192
- session: { provider: 'anthropic-oauth' },
1193
- });
1194
-
1195
- for (const replacementFor of [
1196
- (source) => source,
1197
- (source) => ({ name: 'read', source }),
1198
- (source) => ({ name: 'read', inputSchema: { nested: { source } } }),
1199
- (source) => ({ name: 'read', inputSchema: { values: [source] } }),
1200
- ]) {
1201
- const cyclic = sourceCycle(replacementFor);
1202
- assert.throws(() => snapshot(cyclic.source), cycleError);
1203
- assert.deepEqual(cyclic.counts(), { getterReads: 1, calls: 1 });
1204
- }
1205
-
1206
- let deep = { type: 'string', description: 'finite leaf' };
1207
- for (let index = 0; index < 64; index += 1) deep = { nested: deep };
1208
- const valid = sourceCycle(() => ({
1209
- name: 'deep_valid',
1210
- inputSchema: { type: 'object', properties: { value: deep } },
1211
- }));
1212
- const validSnapshot = snapshot(valid.source);
1213
- assert.deepEqual(valid.counts(), { getterReads: 1, calls: 1 });
1214
- let leaf = validSnapshot[0].inputSchema.properties.value;
1215
- for (let index = 0; index < 64; index += 1) leaf = leaf.nested;
1216
- assert.equal(leaf.description, 'finite leaf');
1217
- const signature = toolSchemaSignature(validSnapshot);
1218
- const reserve = estimateRequestReserveTokens(validSnapshot);
1219
- const messages = [{ role: 'user', content: 'hello' }];
1220
- const opts = {
1221
- providerToolSnapshotAuthoritative: true,
1222
- session: { provider: 'anthropic-oauth' },
1223
- };
1224
- const oauthBody = buildAnthropicRequestBody(messages, 'claude-sonnet-4-6', validSnapshot, opts);
1225
- const apiKeyTools = anthropicApiKeyTest.requestAnthropicTools(validSnapshot, messages, opts);
1226
- assert.equal(oauthBody.tools[0].name, 'deep_valid');
1227
- assert.equal(apiKeyTools[0].name, 'deep_valid');
1228
- assert.equal(toolSchemaSignature(validSnapshot), signature);
1229
- assert.equal(estimateRequestReserveTokens(validSnapshot), reserve);
1230
-
1231
- const cyclic = sourceCycle((source) => ({
1232
- name: 'read',
1233
- inputSchema: { nested: { source } },
1234
- }));
1235
- let providerCalls = 0;
1236
- const session = {
1237
- id: 'to-json-source-cycle',
1238
- owner: 'agent',
1239
- provider: 'anthropic-oauth',
1240
- model: 'claude-sonnet-4-6',
1241
- contextWindow: 100_000,
1242
- messages: [{ role: 'user', content: 'hello' }],
1243
- tools: [cyclic.source],
1244
- compaction: {},
1245
- };
1246
- const provider = {
1247
- name: session.provider,
1248
- async send() {
1249
- providerCalls += 1;
1250
- return { content: 'must not send', usage: { inputTokens: 1, outputTokens: 1 } };
1251
- },
1252
- };
1253
- await assert.rejects(
1254
- agentLoop(provider, session.messages, session.model, session.tools, null, '', {
1255
- session,
1256
- sessionId: session.id,
1257
- }),
1258
- cycleError,
1259
- );
1260
- assert.deepEqual(cyclic.counts(), { getterReads: 1, calls: 1 });
1261
- assert.equal(providerCalls, 0);
1262
- assert.deepEqual(session.compaction, {});
1263
- assert.equal(Object.hasOwn(session, 'contextPressureBaselineToolSignature'), false);
1264
- assert.equal(Object.hasOwn(session, 'contextPressureBaseline'), false);
1265
- });
1266
-
1267
- test('agent-loop re-snapshots after reactive compaction without pressure/send signature drift', async () => {
1268
- const read = { name: 'read', description: 'Read', inputSchema: schema('path') };
1269
- const mcp = { name: 'mcp__demo__lookup', description: 'Lookup', inputSchema: schema('before snapshot') };
1270
- const messages = [
1271
- { role: 'user', content: `old request ${'x'.repeat(30_000)}` },
1272
- { role: 'assistant', content: `old answer ${'y'.repeat(10_000)}` },
1273
- { role: 'user', content: 'current request' },
1274
- ];
1275
- const session = {
1276
- id: 'snapshot-reactive-compact',
1277
- owner: 'agent',
1278
- provider: 'anthropic-oauth',
1279
- model: 'claude-sonnet-4-6',
1280
- contextWindow: 100_000,
1281
- messages,
1282
- tools: [read],
1283
- deferredNativeTools: true,
1284
- deferredDiscoveredTools: [mcp.name],
1285
- deferredToolCatalog: [read, mcp],
1286
- compaction: {
1287
- type: 'semantic',
1288
- compactType: 'semantic',
1289
- tailTurns: 1,
1290
- keepTokens: 100,
1291
- preserveRecentTokens: 100,
1292
- },
1293
- };
1294
- const requestBodies = [];
1295
- let actualAttempts = 0;
1296
- let compactCalls = 0;
1297
- let compactMutationDone = false;
1298
- let preCompactGauge;
1299
- let postCompactTools;
1300
- let postCompactScoped;
1301
- let delayedPreCompactLookup;
1302
- let resolveDelayedPreCompact;
1303
- const delayedPreCompact = new Promise((resolve) => {
1304
- resolveDelayedPreCompact = resolve;
1305
- });
1306
- const { contextStatus } = createContextStatus({
1307
- getSession: () => session,
1308
- getRoute: () => ({ provider: session.provider, model: session.model }),
1309
- getCurrentCwd: () => '',
1310
- getMode: () => 'default',
1311
- getNativeTools: () => [],
1312
- });
1313
- const provider = {
1314
- name: session.provider,
1315
- async send(outgoing, model, tools, opts) {
1316
- if (String(opts?.sessionId || '').endsWith(':compact')) {
1317
- compactCalls += 1;
1318
- return { content: 'Compacted prior context.', usage: { inputTokens: 10, outputTokens: 2 } };
1319
- }
1320
- assert.equal(opts.providerToolSnapshotAuthoritative, true);
1321
- actualAttempts += 1;
1322
- if (actualAttempts === 1) {
1323
- const snapshottedDescription = tools.find((tool) => tool.name === mcp.name)
1324
- .inputSchema.properties.value.description;
1325
- mcp.inputSchema.properties.value.description = 'mutated after first snapshot';
1326
- await Promise.resolve();
1327
- const body = buildAnthropicRequestBody(outgoing, model, tools, opts);
1328
- requestBodies.push(body);
1329
- assert.equal(
1330
- body.tools.find((tool) => tool.name === mcp.name).input_schema.properties.value.description,
1331
- snapshottedDescription,
1332
- 'post-snapshot catalog mutation must not alter provider bytes',
1333
- );
1334
- throw new Error('context length exceeded');
1335
- }
1336
- const body = buildAnthropicRequestBody(outgoing, model, tools, opts);
1337
- requestBodies.push(body);
1338
- return { content: 'done', usage: { inputTokens: 200, outputTokens: 2 } };
1339
- },
1340
- };
1341
- const usageSnapshots = [];
1342
- const result = await agentLoop(provider, messages, session.model, session.tools, null, '', {
1343
- session,
1344
- sessionId: session.id,
1345
- async preCompactHook() {
1346
- preCompactGauge = contextStatus();
1347
- setTimeout(() => {
1348
- delayedPreCompactLookup = scopedProviderRequestTools(session, session.provider, messages);
1349
- resolveDelayedPreCompact();
1350
- }, 30);
1351
- await Promise.resolve();
1352
- mcp.inputSchema.properties.value.description = 'mutated during compaction';
1353
- compactMutationDone = true;
1354
- },
1355
- async postCompactHook() {
1356
- if (!compactMutationDone || postCompactTools) return;
1357
- postCompactScoped = scopedProviderRequestTools(session, session.provider, messages);
1358
- postCompactTools = requestSerializedToolsForContext(session, session.provider, messages);
1359
- contextStatus();
1360
- await Promise.resolve();
1361
- },
1362
- onUsageDelta(delta) {
1363
- if (delta.source !== 'provider_send') return;
1364
- usageSnapshots.push(delta.sendTools);
1365
- recordProviderContextBaseline(session, messages, {
1366
- inputTokens: delta.deltaInput,
1367
- outputTokens: delta.deltaOutput,
1368
- }, { boundary: 'request', sendTools: delta.sendTools });
1369
- },
1370
- });
1371
-
1372
- assert.equal(compactCalls, 1);
1373
- assert.equal(preCompactGauge.request.toolSchemaBreakdown.mcp.count, 1);
1374
- assert.equal(postCompactScoped, null);
1375
- assert.equal(
1376
- postCompactTools.find((tool) => tool.name === mcp.name).inputSchema.properties.value.description,
1377
- 'mutated during compaction',
1378
- );
1379
- await delayedPreCompact;
1380
- assert.equal(delayedPreCompactLookup, null);
1381
- assert.equal(compactMutationDone, true);
1382
- assert.equal(actualAttempts, 2);
1383
- const finalBodyTool = requestBodies.at(-1).tools.find((tool) => tool.name === mcp.name);
1384
- assert.equal(finalBodyTool.input_schema.properties.value.description, 'mutated during compaction');
1385
- assert.equal(
1386
- session.compaction.requestReserveTokens,
1387
- estimateRequestReserveTokens(result.lastSendTools),
1388
- 'post-compaction pressure must use the exact final send snapshot',
1389
- );
1390
- assert.equal(toolSchemaSignature(usageSnapshots.at(-1)), toolSchemaSignature(result.lastSendTools));
1391
- assert.equal(session.contextPressureBaselineToolSignature, toolSchemaSignature(result.lastSendTools));
1392
- });
1393
-
1394
- test('tool status counts exclude MCP and skills while exposing MCP tool count', () => {
1395
- const rows = [
1396
- { name: 'read', kind: toolKind({ name: 'read' }), active: true },
1397
- { name: 'mcp__demo__lookup', kind: toolKind({ name: 'mcp__demo__lookup' }), active: false },
1398
- { name: 'Skill', kind: toolKind({ name: 'Skill' }), active: true },
1399
- ];
1400
- assert.deepEqual(splitToolStatusCounts(rows), {
1401
- count: 1,
1402
- activeCount: 1,
1403
- mcpToolCount: 1,
1404
- activeMcpToolCount: 0,
1405
- });
1406
- });