mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -3,15 +3,16 @@ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'nod
3
3
  import { tmpdir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { __renderToolSearchForTest, compactToolSearchDescription, defaultDeferredToolNames, SKILL_TOOL, TOOL_SEARCH_TOOL } from '../src/mixdog-session-runtime.mjs';
6
+ import { __applyStandaloneToolDefaultsForTest, __renderToolSearchForTest, compactToolSearchDescription, defaultDeferredToolNames, SKILL_TOOL, TOOL_SEARCH_TOOL } from '../src/mixdog-session-runtime.mjs';
7
7
  import { buildExplorerPrompt, EXPLORE_TOOL, MAX_FANOUT_QUERIES, normalizeExploreQueries } from '../src/standalone/explore-tool.mjs';
8
8
  import { AGENT_TOOL, createStandaloneAgent } from '../src/standalone/agent-tool.mjs';
9
9
  import { parseHeadlessRoleCommand } from '../src/app.mjs';
10
10
  import { buildHeadlessSpawnArgs } from '../src/headless-role.mjs';
11
11
  import { createStandaloneChannelWorker } from '../src/standalone/channel-worker.mjs';
12
12
  import { OpenAIOAuthProvider, buildRequestBody, sendViaHttpSse } from '../src/runtime/agent/orchestrator/providers/openai-oauth.mjs';
13
+ import { _test as _anthropicOAuthTest } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
13
14
  import { _logicalResponseItemMatch, _resolveOpenAiPromptCacheRatePolicy } from '../src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs';
14
- import { _mergePendingMessageEntries, closeSession, createSession, drainPendingMessages, enqueuePendingMessage, resumeSession } from '../src/runtime/agent/orchestrator/session/manager.mjs';
15
+ import { _mergePendingMessageEntries, applyAskTerminalUsageTotals, closeSession, createSession, drainPendingMessages, enqueuePendingMessage, resumeSession } from '../src/runtime/agent/orchestrator/session/manager.mjs';
15
16
  import {
16
17
  contentHasImage,
17
18
  normalizeContentForAnthropic,
@@ -28,7 +29,8 @@ import {
28
29
  shouldRecordObservedForProvider,
29
30
  } from '../src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs';
30
31
  import { executeBuiltinTool } from '../src/runtime/agent/orchestrator/tools/builtin.mjs';
31
- import { validateBuiltinArgs } from '../src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs';
32
+ import { applyGrepContextLeadPolicy, GREP_CONTEXT_MAX, validateBuiltinArgs } from '../src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs';
33
+ import { normaliseReadLineWindowArgs } from '../src/runtime/agent/orchestrator/tools/builtin/read-args.mjs';
32
34
  import { BUILTIN_TOOLS } from '../src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs';
33
35
  import { runResultCacheInFlight } from '../src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs';
34
36
  import { executeCodeGraphTool } from '../src/runtime/agent/orchestrator/tools/code-graph.mjs';
@@ -44,7 +46,7 @@ import { composeSystemPrompt } from '../src/runtime/agent/orchestrator/context/c
44
46
  import { setInternalToolsProvider } from '../src/runtime/agent/orchestrator/internal-tools.mjs';
45
47
  import { prepareAgentSession } from '../src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs';
46
48
  import { resolveHiddenRoleSchemaAllowedTools } from '../src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs';
47
- import { getHiddenRole, resolveAgentSessionPermission } from '../src/runtime/agent/orchestrator/internal-roles.mjs';
49
+ import { getHiddenAgent, resolveAgentSessionPermission } from '../src/runtime/agent/orchestrator/internal-agents.mjs';
48
50
 
49
51
  const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
50
52
 
@@ -63,6 +65,27 @@ function assertOk(name, result, pattern = null) {
63
65
  return text;
64
66
  }
65
67
 
68
+ {
69
+ const session = { provider: 'openai-oauth' };
70
+ applyAskTerminalUsageTotals(session, {
71
+ usage: { inputTokens: 100_000, outputTokens: 10, cachedTokens: 98_000, cacheWriteTokens: 0 },
72
+ });
73
+ assert(session.lastInputTokens === 100_000, `inclusive last input should retain provider total: ${JSON.stringify(session)}`);
74
+ assert(session.lastUncachedInputTokens === 2_000, `inclusive last uncached input should subtract cache reads: ${JSON.stringify(session)}`);
75
+ assert(session.totalUncachedInputTokens === 2_000, `inclusive total uncached input should be tracked: ${JSON.stringify(session)}`);
76
+ }
77
+
78
+ {
79
+ const session = { provider: 'anthropic-oauth' };
80
+ applyAskTerminalUsageTotals(session, {
81
+ usage: { inputTokens: 2_000, outputTokens: 10, cachedTokens: 90_000, cacheWriteTokens: 8_000 },
82
+ });
83
+ assert(session.lastInputTokens === 2_000, `additive last input should retain provider input field: ${JSON.stringify(session)}`);
84
+ assert(session.lastUncachedInputTokens === 10_000, `additive uncached input should include cache writes: ${JSON.stringify(session)}`);
85
+ assert(session.lastContextTokens === 100_000, `additive context should include input+cache read+cache write: ${JSON.stringify(session)}`);
86
+ assert(session.totalUncachedInputTokens === 10_000, `additive total uncached input should include cache writes: ${JSON.stringify(session)}`);
87
+ }
88
+
66
89
  {
67
90
  const sid = `tool-smoke-rich-pending-${process.pid}-${Date.now()}`.replace(/[^A-Za-z0-9_-]/g, '_');
68
91
  const richContent = [
@@ -193,7 +216,9 @@ function assertOk(name, result, pattern = null) {
193
216
  {
194
217
  const publicStrategy = resolveCacheStrategy('worker');
195
218
  assert(publicStrategy.tools === 'none', `Anthropic tools must not spend a cache_control BP: ${JSON.stringify(publicStrategy)}`);
196
- assert(publicStrategy.system === '1h' && publicStrategy.tier3 === '1h' && publicStrategy.messages === '1h', `public cache tiers changed unexpectedly: ${JSON.stringify(publicStrategy)}`);
219
+ // BP1~3 stay 1h (pool-stable prefix); volatile message tail is 5m for all
220
+ // sessions — see resolveCacheStrategy docs (trace p99 gap ≈ 4.5min).
221
+ assert(publicStrategy.system === '1h' && publicStrategy.tier3 === '1h' && publicStrategy.messages === '5m', `public cache tiers changed unexpectedly: ${JSON.stringify(publicStrategy)}`);
197
222
  assert(cacheCapabilityForProvider('anthropic-oauth') === 'explicit-breakpoint', 'Anthropic OAuth should remain explicit-breakpoint');
198
223
  assert(cacheCapabilityForProvider('openai-oauth') === 'key-prefix', 'OpenAI OAuth should remain key-prefix');
199
224
  assert(cacheCapabilityForProvider('xai') === 'key-prefix', 'xAI should remain key-prefix');
@@ -355,6 +380,7 @@ const grepOut = await executeBuiltinTool('grep', {
355
380
  pattern: ['standalone mixdog CLI/TUI coding agent', 'smoke passed'],
356
381
  path: 'scripts',
357
382
  glob: '*.mjs',
383
+ output_mode: 'content_with_context',
358
384
  head_limit: 10,
359
385
  }, root);
360
386
  assertOk('grep', grepOut, /smoke\.mjs/);
@@ -390,8 +416,8 @@ assertOk('find', findOut, /scripts[\\/]tool-smoke\.mjs/i);
390
416
 
391
417
  const readOut = await executeBuiltinTool('read', {
392
418
  path: 'scripts/smoke.mjs',
393
- line: 1,
394
- context: 3,
419
+ offset: 0,
420
+ limit: 4,
395
421
  }, root);
396
422
  assertOk('read', readOut, /spawnSync/);
397
423
 
@@ -402,12 +428,71 @@ if (!/^Error[\s:[]/.test(String(readDirOut)) || !/read expects a file/i.test(Str
402
428
  throw new Error(`read directory must be classified as Error:\n${readDirOut}`);
403
429
  }
404
430
 
431
+ const readRegionBatchOut = await executeBuiltinTool('read', {
432
+ path: [
433
+ { path: 'scripts/smoke.mjs', offset: 0, limit: 2 },
434
+ { path: 'scripts/smoke.mjs', offset: 2, limit: 2 },
435
+ ],
436
+ }, root);
437
+ if (!/^read 2\b/m.test(String(readRegionBatchOut))
438
+ || (String(readRegionBatchOut).match(/scripts\/smoke\.mjs \[full\] \[ok\]/g) || []).length < 2
439
+ || !/1→import \{ spawnSync \}/.test(String(readRegionBatchOut))
440
+ || !/3→import \{ fileURLToPath \}/.test(String(readRegionBatchOut))
441
+ || !/pass offset:2 to continue/.test(String(readRegionBatchOut))
442
+ || !/pass offset:4 to continue/.test(String(readRegionBatchOut))) {
443
+ throw new Error(`read region batch must preserve both requested spans:\n${readRegionBatchOut}`);
444
+ }
445
+
446
+ const readStringifiedRegionArgs = {
447
+ path: JSON.stringify([{ path: 'scripts/smoke.mjs', offset: 0, limit: 2 }]),
448
+ };
449
+ const readStringifiedRegionErr = validateBuiltinArgs('read', readStringifiedRegionArgs);
450
+ if (readStringifiedRegionErr || !Array.isArray(readStringifiedRegionArgs.path)) {
451
+ throw new Error(`read guard must losslessly coerce stringified path arrays: err=${readStringifiedRegionErr} args=${JSON.stringify(readStringifiedRegionArgs)}`);
452
+ }
453
+ const readStringifiedRegionOut = await executeBuiltinTool('read', {
454
+ path: JSON.stringify([{ path: 'scripts/smoke.mjs', offset: 0, limit: 2 }]),
455
+ }, root);
456
+ if (!/^read 1\b/m.test(String(readStringifiedRegionOut)) || !/scripts\/smoke\.mjs \[full\] \[ok\]/.test(String(readStringifiedRegionOut)) || !/1→import \{ spawnSync \}/.test(String(readStringifiedRegionOut))) {
457
+ throw new Error(`read stringified region batch must execute after guard coercion:\n${readStringifiedRegionOut}`);
458
+ }
459
+ const readStringifiedLineArgs = {
460
+ path: JSON.stringify([{ path: 'scripts/smoke.mjs', line: 10, context: 2 }]),
461
+ };
462
+ const readStringifiedLineErr = validateBuiltinArgs('read', readStringifiedLineArgs);
463
+ if (readStringifiedLineErr || readStringifiedLineArgs.path[0].offset !== 7 || readStringifiedLineArgs.path[0].limit !== 5) {
464
+ throw new Error(`read guard must losslessly convert legacy line/context inside stringified arrays to offset/limit: err=${readStringifiedLineErr} args=${JSON.stringify(readStringifiedLineArgs)}`);
465
+ }
466
+
405
467
  const graphOut = await executeCodeGraphTool('code_graph', {
406
468
  mode: 'symbols',
407
469
  file: 'scripts/smoke.mjs',
408
470
  }, root);
409
471
  assertOk('code_graph', graphOut, /binding|spawnSync|symbol/i);
410
472
 
473
+ const graphSymbolBatchOut = await executeCodeGraphTool('code_graph', {
474
+ mode: 'symbol_search',
475
+ symbols: ['executeBuiltinTool', 'validateBuiltinArgs'],
476
+ limit: 2,
477
+ }, root);
478
+ if (!/# symbol_search executeBuiltinTool\b/.test(String(graphSymbolBatchOut)) || !/# symbol_search validateBuiltinArgs\b/.test(String(graphSymbolBatchOut))) {
479
+ throw new Error(`code_graph symbol_search symbols[] batch execution failed:\n${graphSymbolBatchOut}`);
480
+ }
481
+
482
+ const graphMissingFileOut = await executeCodeGraphTool('code_graph', {
483
+ mode: 'symbols',
484
+ file: 'src/runtime/loop.mjs',
485
+ }, root);
486
+ if (!/^Error: code_graph: file not found: src\/runtime\/loop\.mjs/.test(String(graphMissingFileOut))) {
487
+ throw new Error(`code_graph missing-file fast path failed:\n${graphMissingFileOut}`);
488
+ }
489
+
490
+ const graphDotDirOut = await executeCodeGraphTool('code_graph', {
491
+ mode: 'overview',
492
+ file: '.',
493
+ }, root);
494
+ assertOk('code_graph dot directory anchor', graphDotDirOut, /files\s+\d+|edges\s+\d+/i);
495
+
411
496
  const patchOut = await executePatchTool('apply_patch', {
412
497
  base_path: root,
413
498
  dry_run: true,
@@ -498,6 +583,12 @@ if (literalBackslashPipeArray) {
498
583
  throw new Error(`grep array literal \\| should be allowed: ${literalBackslashPipeArray}`);
499
584
  }
500
585
 
586
+ const grepContextPolicyArgs = { pattern: 'smoke', path: root, context: GREP_CONTEXT_MAX + 999 };
587
+ applyGrepContextLeadPolicy(grepContextPolicyArgs);
588
+ if (grepContextPolicyArgs['-C'] !== GREP_CONTEXT_MAX || Object.prototype.hasOwnProperty.call(grepContextPolicyArgs, 'context')) {
589
+ throw new Error(`grep context policy must canonicalize and clamp explicit context: ${JSON.stringify(grepContextPolicyArgs)}`);
590
+ }
591
+
501
592
  const invalidGrepPath = validateBuiltinArgs('grep', {
502
593
  pattern: 'providerStatus',
503
594
  path: 'C:\\Project\\mixdog\\src\\tui C:\\Project\\mixdog\\src\\mixdog-session-runtime.mjs',
@@ -514,11 +605,20 @@ if (!/lookaround\/backrefs/i.test(invalidGrepLookaround || '')) {
514
605
  throw new Error(`grep unsupported-regex guard failed: ${invalidGrepLookaround}`);
515
606
  }
516
607
 
517
- const invalidShellPath = validateBuiltinArgs('shell', {
608
+ // Windows drive path + no explicit shell used to be rejected with a retry hint;
609
+ // the guard now auto-coerces to shell:'powershell' (drive paths are a definitive
610
+ // powershell signal — they can never work under Git Bash unconverted).
611
+ const shellDrivePathArgs = {
518
612
  command: 'cd C:\\Project\\mixdog && node scripts/build-tui.mjs',
519
- });
520
- if (process.platform === 'win32' && !/shell:'powershell'/i.test(invalidShellPath || '')) {
521
- throw new Error(`shell Windows-path guard failed: ${invalidShellPath}`);
613
+ };
614
+ const shellDrivePathErr = validateBuiltinArgs('shell', shellDrivePathArgs);
615
+ if (process.platform === 'win32') {
616
+ if (shellDrivePathErr !== null) {
617
+ throw new Error(`shell Windows-path auto-coercion failed: ${shellDrivePathErr}`);
618
+ }
619
+ if (shellDrivePathArgs.shell !== 'powershell') {
620
+ throw new Error(`shell Windows-path auto-coercion did not set shell:'powershell' (got ${JSON.stringify(shellDrivePathArgs.shell)})`);
621
+ }
522
622
  }
523
623
 
524
624
  const invalidShellCwdAliasConflict = validateBuiltinArgs('shell', {
@@ -534,16 +634,28 @@ if (!/cwd.*workdir.*conflict/i.test(invalidShellCwdAliasConflict || '')) {
534
634
  const shellWorkdirOut = await shellWorkdirOutPromise;
535
635
  assertOk('shell workdir alias', shellWorkdirOut, /scripts\s*$/i);
536
636
 
537
- const mixedReadWindow = {
637
+ const offsetReadWindow = {
538
638
  path: 'scripts/smoke.mjs',
539
- line: 1,
540
- context: 3,
541
- offset: 1,
639
+ offset: 0,
542
640
  limit: 20,
543
641
  };
544
- const readWindowErr = validateBuiltinArgs('read', mixedReadWindow);
545
- if (!/exactly one window family/i.test(readWindowErr || '')) {
546
- throw new Error(`read mixed-window guard failed: err=${readWindowErr} args=${JSON.stringify(mixedReadWindow)}`);
642
+ const readWindowErr = validateBuiltinArgs('read', offsetReadWindow);
643
+ if (readWindowErr) {
644
+ throw new Error(`read offset/limit window guard failed: err=${readWindowErr} args=${JSON.stringify(offsetReadWindow)}`);
645
+ }
646
+ const readLineArgs = { path: 'scripts/smoke.mjs', line: 10, context: 2 };
647
+ const readLineErr = validateBuiltinArgs('read', readLineArgs);
648
+ if (readLineErr || readLineArgs.offset !== 7 || readLineArgs.limit !== 5 || 'line' in readLineArgs || 'context' in readLineArgs) {
649
+ throw new Error(`read guard must losslessly convert top-level legacy line/context args to offset/limit: err=${readLineErr} args=${JSON.stringify(readLineArgs)}`);
650
+ }
651
+ const batchedReadLineArgs = { path: [{ path: 'scripts/smoke.mjs', line: 10, context: 2 }] };
652
+ const batchedReadLineErr = validateBuiltinArgs('read', batchedReadLineArgs);
653
+ if (batchedReadLineErr || batchedReadLineArgs.path[0].offset !== 7 || batchedReadLineArgs.path[0].limit !== 5) {
654
+ throw new Error(`read guard must losslessly convert batched legacy line/context args to offset/limit: err=${batchedReadLineErr} args=${JSON.stringify(batchedReadLineArgs)}`);
655
+ }
656
+ const pathLineWithLimit = normaliseReadLineWindowArgs({ path: 'scripts/smoke.mjs#L10', limit: 5 }, root);
657
+ if (pathLineWithLimit.offset !== 9 || pathLineWithLimit.limit !== 5) {
658
+ throw new Error(`read path#line compatibility must anchor offset when limit is explicit: ${JSON.stringify(pathLineWithLimit)}`);
547
659
  }
548
660
 
549
661
  function assertHas(set, name) {
@@ -630,27 +742,27 @@ const agentProps = AGENT_TOOL.inputSchema?.properties || {};
630
742
  if (agentProps.mode || agentProps.wait) throw new Error('agent schema should not expose execution mode controls');
631
743
  {
632
744
  const heavyPrompt = composeSystemPrompt({
633
- role: 'heavy-worker',
745
+ agent: 'heavy-worker',
634
746
  provider: 'anthropic-oauth',
635
747
  agentRules: '# Tool Use',
636
748
  skillManifest: '',
637
749
  });
638
- if (!heavyPrompt.stableSystemContext.includes('## heavy-worker') || !heavyPrompt.stableSystemContext.includes('Complex implementation agent')) {
750
+ if (!heavyPrompt.stableSystemContext.includes('## heavy-worker')) {
639
751
  throw new Error(`heavy-worker AGENT.md must be included in scoped role instructions: ${heavyPrompt.stableSystemContext}`);
640
752
  }
641
753
  const workerPrompt = composeSystemPrompt({
642
- role: 'worker',
754
+ agent: 'worker',
643
755
  provider: 'anthropic-oauth',
644
756
  agentRules: '# Tool Use',
645
757
  skillManifest: '',
646
758
  });
647
- if (!workerPrompt.stableSystemContext.includes('## worker') || !workerPrompt.stableSystemContext.includes('Basic implementation agent')) {
759
+ if (!workerPrompt.stableSystemContext.includes('## worker')) {
648
760
  throw new Error(`worker AGENT.md must be included in scoped role instructions: ${workerPrompt.stableSystemContext}`);
649
761
  }
650
762
  }
651
763
  {
652
764
  const shorthand = parseHeadlessRoleCommand(['reviewer', 'check', 'this']);
653
- if (shorthand?.role !== 'reviewer' || shorthand?.message !== 'check this') {
765
+ if (shorthand?.agent !== 'reviewer' || shorthand?.message !== 'check this') {
654
766
  throw new Error(`headless shorthand command parse failed: ${JSON.stringify(shorthand)}`);
655
767
  }
656
768
  const explicit = parseHeadlessRoleCommand(['role', 'debug', 'trace', 'failure']);
@@ -662,7 +774,7 @@ if (agentProps.mode || agentProps.wait) throw new Error('agent schema should not
662
774
  throw new Error(`empty argv must keep TUI default: ${JSON.stringify(tuiDefault)}`);
663
775
  }
664
776
  const modelOnlySpawn = buildHeadlessSpawnArgs({
665
- role: 'reviewer',
777
+ agent: 'reviewer',
666
778
  tag: 'headless-smoke',
667
779
  cwd: root,
668
780
  message: 'check this',
@@ -672,7 +784,7 @@ if (agentProps.mode || agentProps.wait) throw new Error('agent schema should not
672
784
  throw new Error(`headless model-only route must preserve --model without forcing provider: ${JSON.stringify(modelOnlySpawn)}`);
673
785
  }
674
786
  }
675
- if (!/always start background tasks/i.test(AGENT_TOOL.description || '') || !/distinct tags/i.test(AGENT_TOOL.description || '') || !/completion notification/i.test(AGENT_TOOL.description || '') || !/do not (?:call|poll) status\/read/i.test(AGENT_TOOL.description || '')) {
787
+ if (!/always start background tasks/i.test(AGENT_TOOL.description || '') || !/distinct tags/i.test(AGENT_TOOL.description || '') || !/same scope/i.test(AGENT_TOOL.description || '') || !/send/i.test(AGENT_TOOL.description || '') || !/completion notification/i.test(AGENT_TOOL.description || '') || !/do not (?:call|poll) status\/read/i.test(AGENT_TOOL.description || '')) {
676
788
  throw new Error('agent description must preserve async tagged delegation contract');
677
789
  }
678
790
  const agentSmoke = createStandaloneAgent({
@@ -846,6 +958,15 @@ if (EXPLORE_TOOL.annotations?.readOnlyHint !== true || EXPLORE_TOOL.annotations?
846
958
  if (EXPLORE_TOOL.annotations?.agentHidden === true) {
847
959
  throw new Error('explore must stay visible to agent sessions');
848
960
  }
961
+ {
962
+ const runtimeSearchTool = __applyStandaloneToolDefaultsForTest(SEARCH_TOOL_DEFS.find((tool) => tool?.name === 'search'));
963
+ if (runtimeSearchTool?.annotations?.agentHidden === true) {
964
+ throw new Error('production search tool must stay visible to agent sessions');
965
+ }
966
+ if (TOOL_SEARCH_TOOL.annotations?.agentHidden !== true) {
967
+ throw new Error('deferred tool_search wrapper must stay hidden from agent sessions');
968
+ }
969
+ }
849
970
  const exploreProps = EXPLORE_TOOL.inputSchema?.properties || {};
850
971
  if (!/Repo anchor locator/i.test(EXPLORE_TOOL.description || '') || !/broad\/uncertain/i.test(EXPLORE_TOOL.description || '') || !/independent targets/i.test(EXPLORE_TOOL.description || '') || (EXPLORE_TOOL.description || '').length > 90) {
851
972
  throw new Error('explore description must stay compact and anchor-oriented');
@@ -897,7 +1018,7 @@ setInternalToolsProvider({
897
1018
  provider: 'openai-oauth',
898
1019
  model: 'tool-smoke-model',
899
1020
  owner: 'cli',
900
- role: 'lead',
1021
+ agent: 'lead',
901
1022
  cwd: skillManifestTmp,
902
1023
  permission: 'read-write',
903
1024
  });
@@ -917,7 +1038,7 @@ setInternalToolsProvider({
917
1038
  provider: 'openai-oauth',
918
1039
  model: 'tool-smoke-model',
919
1040
  owner: AGENT_OWNER,
920
- role: 'worker',
1041
+ agent: 'worker',
921
1042
  cwd: skillManifestTmp,
922
1043
  permission: 'read-write',
923
1044
  });
@@ -938,8 +1059,8 @@ setInternalToolsProvider({
938
1059
  throw new Error(`agent system layers must carry BP1 tool policy and BP2 role rules: ${systemVisible.slice(0, 1200)}`);
939
1060
  }
940
1061
  const agentSkillToolNames = (agentSkillSession.tools || []).map((tool) => tool?.name).filter(Boolean);
941
- if (agentSkillToolNames.includes('Skill')) {
942
- throw new Error(`read-write agent schema must omit Skill loader: ${agentSkillToolNames.join(', ')}`);
1062
+ if (!agentSkillToolNames.includes('Skill')) {
1063
+ throw new Error(`read-write agent schema must expose Skill loader with the manifest: ${agentSkillToolNames.join(', ')}`);
943
1064
  }
944
1065
  } finally {
945
1066
  closeSession(agentSkillSession.id, 'tool-smoke');
@@ -951,7 +1072,7 @@ setInternalToolsProvider({
951
1072
  provider: 'openai-oauth',
952
1073
  model: 'tool-smoke-model',
953
1074
  owner: AGENT_OWNER,
954
- role: 'explorer',
1075
+ agent: 'explorer',
955
1076
  cwd: root,
956
1077
  permission: 'read',
957
1078
  skipSkills: true,
@@ -970,7 +1091,7 @@ setInternalToolsProvider({
970
1091
  if (!/Read-only retrieval role/i.test(visible) || /# environment/i.test(visible) || /git operations deferred to Lead/i.test(visible)) {
971
1092
  throw new Error(`explorer hidden retrieval context should stay slim: ${visible.slice(0, 1200)}`);
972
1093
  }
973
- if (!/# Role: explorer/i.test(systemVisible) || /# Role: explorer/i.test(userReminderVisible) || !/Locator only/i.test(systemVisible)) {
1094
+ if (!/# Role: explorer/i.test(systemVisible) || /# Role: explorer/i.test(userReminderVisible) || !/one-shot locator/i.test(systemVisible)) {
974
1095
  throw new Error(`explorer role md must ride BP2 system, not BP3 user reminder: system=${systemVisible.slice(0, 600)} user=${userReminderVisible.slice(0, 600)}`);
975
1096
  }
976
1097
  const visibleBytes = Buffer.byteLength(visible, 'utf8');
@@ -984,7 +1105,7 @@ setInternalToolsProvider({
984
1105
  provider: 'openai-oauth',
985
1106
  model: 'tool-smoke-model',
986
1107
  owner: AGENT_OWNER,
987
- role: 'worker',
1108
+ agent: 'worker',
988
1109
  cwd: root,
989
1110
  permission: 'read-write',
990
1111
  taskBrief: 'Implement a scoped smoke check.',
@@ -1029,7 +1150,7 @@ setInternalToolsProvider({
1029
1150
  provider: 'openai-oauth',
1030
1151
  model: 'tool-smoke-model',
1031
1152
  owner: AGENT_OWNER,
1032
- role: 'worker',
1153
+ agent: 'worker',
1033
1154
  cwd: root,
1034
1155
  permission: 'read',
1035
1156
  });
@@ -1037,7 +1158,7 @@ setInternalToolsProvider({
1037
1158
  provider: 'openai-oauth',
1038
1159
  model: 'tool-smoke-model',
1039
1160
  owner: AGENT_OWNER,
1040
- role: 'worker',
1161
+ agent: 'worker',
1041
1162
  cwd: root,
1042
1163
  permission: 'read-write',
1043
1164
  });
@@ -1045,7 +1166,7 @@ setInternalToolsProvider({
1045
1166
  provider: 'openai-oauth',
1046
1167
  model: 'tool-smoke-model',
1047
1168
  owner: AGENT_OWNER,
1048
- role: 'worker',
1169
+ agent: 'worker',
1049
1170
  cwd: root,
1050
1171
  permission: 'full',
1051
1172
  });
@@ -1062,8 +1183,8 @@ setInternalToolsProvider({
1062
1183
  const writeTools = (writeAgentSession.tools || []).map((tool) => tool?.name).filter(Boolean);
1063
1184
  const fullTools = (fullAgentSession.tools || []).map((tool) => tool?.name).filter(Boolean);
1064
1185
  const publicExploreTools = (publicExploreSession.tools || []).map((tool) => tool?.name).filter(Boolean);
1065
- const expectedReadTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'explore', 'search', 'web_fetch'];
1066
- const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch'];
1186
+ const expectedReadTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'explore', 'search', 'web_fetch', 'Skill'];
1187
+ const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch', 'Skill'];
1067
1188
  if (JSON.stringify(readTools) !== JSON.stringify(expectedReadTools)) {
1068
1189
  throw new Error(`read agent schema must be fixed allow-list: expected=${expectedReadTools.join(', ')} actual=${readTools.join(', ')}`);
1069
1190
  }
@@ -1076,7 +1197,7 @@ setInternalToolsProvider({
1076
1197
  if (readTools.includes('shell') || writeTools.includes('shell')) {
1077
1198
  throw new Error(`read/read-write agent schemas must omit shell: read=${readTools.join(', ')} write=${writeTools.join(', ')}`);
1078
1199
  }
1079
- for (const name of ['shell', 'apply_patch', 'task', 'diagnostics', 'open_config', 'Skill']) {
1200
+ for (const name of ['shell', 'apply_patch', 'task', 'diagnostics', 'open_config']) {
1080
1201
  if (readTools.includes(name)) {
1081
1202
  throw new Error(`read agent schema must omit non-read tool ${name}: read=${readTools.join(', ')}`);
1082
1203
  }
@@ -1089,7 +1210,7 @@ setInternalToolsProvider({
1089
1210
  throw new Error(`read-write agent schema must omit non-edit tool ${name}: write=${writeTools.join(', ')}`);
1090
1211
  }
1091
1212
  }
1092
- for (const name of ['open_config', 'Skill']) {
1213
+ for (const name of ['open_config']) {
1093
1214
  if (writeTools.includes(name)) {
1094
1215
  throw new Error(`read-write agent schema must omit config/skill tool ${name}: write=${writeTools.join(', ')}`);
1095
1216
  }
@@ -1121,14 +1242,14 @@ setInternalToolsProvider({
1121
1242
  provider: 'openai-oauth',
1122
1243
  model: 'tool-smoke-model',
1123
1244
  owner: AGENT_OWNER,
1124
- role: 'worker',
1245
+ agent: 'worker',
1125
1246
  cwd: root,
1126
1247
  permission: 'read-write',
1127
1248
  });
1128
1249
  try {
1129
1250
  const resumed = await resumeSession(resumeAgentSession.id, 'full');
1130
1251
  const resumedTools = (resumed?.tools || []).map((tool) => tool?.name).filter(Boolean);
1131
- const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch'];
1252
+ const expectedWriteTools = ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch', 'Skill'];
1132
1253
  if (JSON.stringify(resumedTools) !== JSON.stringify(expectedWriteTools)) {
1133
1254
  throw new Error(`resumed read-write agent schema must keep fixed allow-list: expected=${expectedWriteTools.join(', ')} actual=${resumedTools.join(', ')}`);
1134
1255
  }
@@ -1139,7 +1260,7 @@ setInternalToolsProvider({
1139
1260
  provider: 'openai-oauth',
1140
1261
  model: 'tool-smoke-model',
1141
1262
  owner: AGENT_OWNER,
1142
- role: 'worker',
1263
+ agent: 'worker',
1143
1264
  cwd: root,
1144
1265
  permission: 'none',
1145
1266
  });
@@ -1156,7 +1277,7 @@ setInternalToolsProvider({
1156
1277
  provider: 'openai-oauth',
1157
1278
  model: 'tool-smoke-model',
1158
1279
  owner: AGENT_OWNER,
1159
- role: 'worker',
1280
+ agent: 'worker',
1160
1281
  cwd: root,
1161
1282
  permission: { allow: ['read', 'grep'], deny: ['grep'] },
1162
1283
  });
@@ -1169,55 +1290,55 @@ setInternalToolsProvider({
1169
1290
  } finally {
1170
1291
  closeSession(objectPermissionSession.id, 'tool-smoke');
1171
1292
  }
1172
- const hiddenRoles = JSON.parse(readFileSync(join(root, 'src', 'defaults', 'hidden-roles.json'), 'utf8')).roles || [];
1293
+ const hiddenAgents = JSON.parse(readFileSync(join(root, 'src', 'defaults', 'agents.json'), 'utf8')).agents || [];
1173
1294
  const hiddenPreset = { id: 'hidden-smoke', name: 'hidden-smoke', type: 'agent', provider: 'openai-oauth', model: 'tool-smoke-model', tools: 'full' };
1174
1295
  const hiddenRuntimeSpec = { scopeKey: 'hidden-role-smoke', lane: 'agent' };
1175
1296
  const hiddenBadTools = new Set(['shell', 'task', 'diagnostics', 'open_config', 'Skill', 'memory', 'reply', 'edit_message', 'recall', 'reload_config', 'inject_command']);
1176
- const expectedForHiddenRole = (permission, schemaAllowedTools) => {
1297
+ const expectedForHiddenAgent = (permission, schemaAllowedTools) => {
1177
1298
  if (Array.isArray(schemaAllowedTools)) return schemaAllowedTools.slice();
1178
1299
  if (permission === 'none') return [];
1179
1300
  if (permission === 'read') return ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'explore', 'search', 'web_fetch'];
1180
1301
  if (permission === 'read-write') return ['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'apply_patch', 'explore', 'search', 'web_fetch'];
1181
1302
  return null;
1182
1303
  };
1183
- for (const entry of hiddenRoles) {
1184
- const role = String(entry?.name || '').trim();
1185
- if (!role) continue;
1186
- const hidden = getHiddenRole(role);
1187
- const permission = resolveAgentSessionPermission(role, hidden?.permission || null);
1304
+ for (const entry of hiddenAgents) {
1305
+ const agent = String(entry?.agent || '').trim();
1306
+ if (!agent) continue;
1307
+ const hidden = getHiddenAgent(agent);
1308
+ const permission = resolveAgentSessionPermission(agent, hidden?.permission || null);
1188
1309
  const schemaAllowedTools = resolveHiddenRoleSchemaAllowedTools(hidden);
1189
1310
  const { session } = prepareAgentSession({
1190
- role,
1311
+ agent,
1191
1312
  presetName: 'hidden-smoke',
1192
1313
  preset: hiddenPreset,
1193
1314
  runtimeSpec: hiddenRuntimeSpec,
1194
1315
  permission,
1195
1316
  cwd: root,
1196
1317
  sourceType: 'hidden-role-smoke',
1197
- sourceName: role,
1318
+ sourceName: agent,
1198
1319
  schemaAllowedTools,
1199
1320
  });
1200
1321
  try {
1201
1322
  const tools = (session.tools || []).map((tool) => tool?.name).filter(Boolean);
1202
1323
  const resumed = await resumeSession(session.id, 'full');
1203
1324
  const resumedTools = (resumed?.tools || []).map((tool) => tool?.name).filter(Boolean);
1204
- const expected = expectedForHiddenRole(permission, schemaAllowedTools);
1325
+ const expected = expectedForHiddenAgent(permission, schemaAllowedTools);
1205
1326
  if (expected && (JSON.stringify(tools) !== JSON.stringify(expected) || JSON.stringify(resumedTools) !== JSON.stringify(expected))) {
1206
- throw new Error(`hidden role ${role} schema mismatch: expected=${expected.join(', ')} tools=${tools.join(', ')} resumed=${resumedTools.join(', ')}`);
1327
+ throw new Error(`hidden agent ${agent} schema mismatch: expected=${expected.join(', ')} tools=${tools.join(', ')} resumed=${resumedTools.join(', ')}`);
1207
1328
  }
1208
1329
  const leaked = tools.filter((name) => hiddenBadTools.has(name) && !(expected || []).includes(name));
1209
1330
  if (leaked.length) {
1210
- throw new Error(`hidden role ${role} leaked forbidden full-runtime tools: ${leaked.join(', ')} from ${tools.join(', ')}`);
1331
+ throw new Error(`hidden agent ${agent} leaked forbidden full-runtime tools: ${leaked.join(', ')} from ${tools.join(', ')}`);
1211
1332
  }
1212
1333
  const systemVisible = (session.messages || [])
1213
1334
  .filter((m) => m?.role === 'system')
1214
1335
  .map((m) => String(m.content || ''))
1215
1336
  .join('\n');
1216
1337
  if (/available-skills|Skill\(/i.test(systemVisible)) {
1217
- throw new Error(`hidden role ${role} must not carry Skill manifest without Skill tool`);
1338
+ throw new Error(`hidden agent ${agent} must not carry Skill manifest without Skill tool`);
1218
1339
  }
1219
1340
  if (/effective-cwd|Override cwd|# environment|# task-brief/i.test(systemVisible)) {
1220
- throw new Error(`hidden role ${role} must not carry cwd/environment/task-brief injection`);
1341
+ throw new Error(`hidden agent ${agent} must not carry cwd/environment/task-brief injection`);
1221
1342
  }
1222
1343
  } finally {
1223
1344
  closeSession(session.id, 'tool-smoke');
@@ -1311,21 +1432,93 @@ for (const requiredGrammarLine of [
1311
1432
  throw new Error(`custom apply_patch SSE parser must eager-emit patch args: ${JSON.stringify(emitted)}`);
1312
1433
  }
1313
1434
  }
1314
- const readPathDescription = BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.inputSchema?.properties?.path?.description || '';
1315
- if (!/File path or array/i.test(readPathDescription) || !/Dirs use list/i.test(readPathDescription)) {
1435
+ const readPathSchema = BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.inputSchema?.properties?.path || {};
1436
+ const readPathDescription = readPathSchema.description || '';
1437
+ if (!/File path/i.test(readPathDescription) || !/\{path,offset,limit\}\[\]/i.test(readPathDescription) || !/Pass arrays directly/i.test(readPathDescription) || !/legacy recovery only/i.test(readPathDescription)) {
1316
1438
  throw new Error('read schema must keep directory-vs-file guidance');
1317
1439
  }
1318
- const readDescription = BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.description || '';
1319
- if (!/known file path\(s\)/i.test(readDescription) || !/line\+context/i.test(readDescription)) {
1320
- throw new Error('read description must stay narrow-target oriented');
1440
+ if (!/Dirs use list/i.test((BUILTIN_TOOLS.find((tool) => tool.name === 'read')?.description) || '')) {
1441
+ throw new Error('read description must keep directory-vs-file guidance');
1442
+ }
1443
+ const readTool = BUILTIN_TOOLS.find((tool) => tool.name === 'read');
1444
+ const readDescription = readTool?.description || '';
1445
+ const readProps = readTool?.inputSchema?.properties || {};
1446
+ const readArraySchema = readPathSchema.anyOf?.find((entry) => entry?.type === 'array');
1447
+ const readArrayItemAnyOf = readArraySchema?.items?.anyOf || [];
1448
+ if (!readArrayItemAnyOf.some((entry) => entry?.type === 'object' && entry?.properties?.offset && entry?.properties?.limit)) {
1449
+ throw new Error('read schema must expose array-of-region objects for batched spans');
1450
+ }
1451
+ if (/line\+context/i.test(readDescription) || !/numeric offset\+limit/i.test(readDescription) || !/Batch paths\/regions as real arrays/i.test(readDescription) || !/grep content_with_context or code_graph anchors/i.test(readDescription)) {
1452
+ throw new Error('read description must expose offset/limit as the single window form');
1453
+ }
1454
+ if (readProps.line || readProps.context) {
1455
+ throw new Error('read schema must not expose legacy line/context window fields');
1456
+ }
1457
+ if (readProps.offset?.minimum !== 0 || !/Numeric lines to skip/i.test(readProps.offset?.description || '') || !/offset:N/i.test(readProps.offset?.description || '') || !/Numeric max lines/i.test(readProps.limit?.description || '')) {
1458
+ throw new Error('read offset schema must describe Mixdog paging cursor semantics');
1459
+ }
1460
+ if (/line\/context/i.test(JSON.stringify(readTool?.inputSchema || {}))) {
1461
+ throw new Error('read schema surface must not mention legacy line/context');
1462
+ }
1463
+ {
1464
+ const benchRunSrc = readFileSync(resolve(root, 'scripts/bench-run.mjs'), 'utf8');
1465
+ if (!/task_complete:\s*results\.length > 0 && completed === results\.length/.test(benchRunSrc)) {
1466
+ throw new Error('bench-run must require every task to complete before saving a round');
1467
+ }
1468
+ if (!/score_complete:\s*results\.length > 0 && taskErrors\.length === 0 && scoreErrors\.length === 0 && \(score\?\.cards\?\.length \|\| 0\) === results\.length/.test(benchRunSrc)) {
1469
+ throw new Error('bench-run must require a scorecard for every task before saving a round');
1470
+ }
1471
+ if (!/not saving incomplete round/.test(benchRunSrc) || !/process\.exit\(1\)/.test(benchRunSrc)) {
1472
+ throw new Error('bench-run must not save incomplete rounds and must exit non-zero');
1473
+ }
1474
+ const taskBenchSrc = readFileSync(resolve(root, 'scripts/task-bench.mjs'), 'utf8');
1475
+ if (!/const allowPartial = hasFlag\('--allow-partial'\)/.test(taskBenchSrc) || !/skipped\.length && !allowPartial/.test(taskBenchSrc) || !/process\.exit\(1\)/.test(taskBenchSrc)) {
1476
+ throw new Error('task-bench must fail partial scoring unless --allow-partial is explicit');
1477
+ }
1478
+ }
1479
+ {
1480
+ // setRoute must default to "next session only": a bare
1481
+ // runtime.setRoute({model}) call (no options) must NOT rewrite a live
1482
+ // session's provider/model in place, or a mid-conversation model/provider
1483
+ // switch silently forces a full prompt-cache rewrite (seen as a
1484
+ // promptΔ spike + cache_ratio=0% turn in session-bench).
1485
+ const runtimeSrc = readFileSync(resolve(root, 'src/mixdog-session-runtime.mjs'), 'utf8');
1486
+ const setRouteBlock = runtimeSrc.match(/async setRoute\(next, options = \{\}\) \{[\s\S]*?\n \},\n/)?.[0] || '';
1487
+ if (!/applyToCurrentSession = options\?\.applyToCurrentSession === true/.test(setRouteBlock)) {
1488
+ throw new Error('setRoute must default applyToCurrentSession to false (model changes apply to the next session only)');
1489
+ }
1490
+ if (!/if \(!applyToCurrentSession\)/.test(setRouteBlock) || !/return route;/.test(setRouteBlock)) {
1491
+ throw new Error('setRoute must early-return before touching the live session when applyToCurrentSession is false');
1492
+ }
1493
+ const engineSrc = readFileSync(resolve(root, 'src/tui/engine.mjs'), 'utf8');
1494
+ if (/setRoute\(\{ model: m \}, \{ applyToCurrentSession: true \}\)/.test(engineSrc)) {
1495
+ throw new Error('TUI setModel must not force applyToCurrentSession:true (model changes must apply to the next session only)');
1496
+ }
1497
+ if (!/routeOpts\.applyToCurrentSession === true/.test(engineSrc)) {
1498
+ throw new Error('TUI setRoute wrapper must default applyToCurrentSession to false');
1499
+ }
1321
1500
  }
1322
1501
  const codeGraphDescription = CODE_GRAPH_TOOL_DEFS[0]?.description || '';
1323
1502
  const codeGraphProps = CODE_GRAPH_TOOL_DEFS[0]?.inputSchema?.properties || {};
1324
- if (!/Code structure/i.test(codeGraphDescription) || !/symbols/i.test(codeGraphDescription) || codeGraphDescription.length > 90) {
1325
- throw new Error('code_graph description must stay compact and structure-oriented');
1503
+ const codeGraphSymbolSearchErr = validateBuiltinArgs('code_graph', { mode: 'symbol_search', symbols: ['hook', 'deny'], limit: 5 });
1504
+ if (codeGraphSymbolSearchErr) {
1505
+ throw new Error(`code_graph guard must accept symbol_search with symbols[] batching: ${codeGraphSymbolSearchErr}`);
1506
+ }
1507
+ // code_graph description stays structure-oriented and must actively route
1508
+ // symbol/definition/caller lookups AWAY from repeated grep (the grep_retry +
1509
+ // find_symbol_noscope anti-patterns). It is allowed to be verbose enough to
1510
+ // enumerate modes, but must not drift into web-search territory.
1511
+ if (!/code structure\/flow/i.test(codeGraphDescription) || !/symbols\/references\/calls\/deps/i.test(codeGraphDescription)) {
1512
+ throw new Error('code_graph description must stay structure-oriented and name its symbol modes');
1513
+ }
1514
+ if (!/before text grep/i.test(codeGraphDescription)) {
1515
+ throw new Error('code_graph description must steer symbol/caller lookups to structure lookup before grep');
1326
1516
  }
1327
- if (!/^Operation\.$/i.test(codeGraphProps.mode?.description || '') || !/^Source file\.$/i.test(codeGraphProps.file?.description || '')) {
1328
- throw new Error('code_graph schema must keep compact field descriptions');
1517
+ if (!/repo-local/i.test(codeGraphDescription) || !/NOT web search|not web/i.test(codeGraphDescription)) {
1518
+ throw new Error('code_graph description must mark itself repo-local (not web search)');
1519
+ }
1520
+ if (!/repo-local|not web/i.test(codeGraphProps.mode?.description || '') || !/source file/i.test(codeGraphProps.file?.description || '')) {
1521
+ throw new Error('code_graph schema must keep compact, repo-local field descriptions');
1329
1522
  }
1330
1523
  const recallTool = MEMORY_TOOL_DEFS.find((tool) => tool.name === 'recall');
1331
1524
  const recallProps = recallTool?.inputSchema?.properties || {};
@@ -1572,13 +1765,63 @@ if (!/NOT for URLs/i.test(fetchTool?.description || '') || !/web_fetch/i.test(fe
1572
1765
  const grepTool = BUILTIN_TOOLS.find((tool) => tool.name === 'grep');
1573
1766
  const grepPatternDescription = grepTool?.inputSchema?.properties?.pattern?.description || '';
1574
1767
  const grepPathDescription = grepTool?.inputSchema?.properties?.path?.description || '';
1575
- if (!/Array = OR/i.test(grepPatternDescription) || !/^File or directory\.$/i.test(grepPathDescription)) {
1768
+ const grepGlobDescription = grepTool?.inputSchema?.properties?.glob?.description || '';
1769
+ const grepOutputModeDescription = grepTool?.inputSchema?.properties?.output_mode?.description || '';
1770
+ const grepHeadLimitDescription = grepTool?.inputSchema?.properties?.head_limit?.description || '';
1771
+ if (!/synonyms in pattern\[\]/i.test(grepPatternDescription) || !/OR in ONE grep/i.test(grepPatternDescription) || !/serial rewording/i.test(grepPatternDescription) || !/equivalent repeats/i.test(grepPatternDescription) || !/Narrowest file\/dir/i.test(grepPathDescription) || !/broad scopes return paths first/i.test(grepPathDescription) || !/refine from returned paths/i.test(grepPathDescription)) {
1576
1772
  throw new Error('grep schema must keep compact pattern/path guidance');
1577
1773
  }
1774
+ if (!/same grep/i.test(grepGlobDescription) || !/no follow-up grep/i.test(grepGlobDescription) || !/equivalent scope changes/i.test(grepGlobDescription)) {
1775
+ throw new Error('grep glob schema must steer narrowing into the same grep call');
1776
+ }
1777
+ if (!/Broad scope: files_with_matches\/count/i.test(grepOutputModeDescription) || !/Narrow scope: content_with_context/i.test(grepOutputModeDescription) || !/answer from/i.test(grepOutputModeDescription) || !/skip read/i.test(grepOutputModeDescription) || !/span is not shown/i.test(grepOutputModeDescription)) {
1778
+ throw new Error('grep output_mode schema must steer content_with_context away from follow-up reads');
1779
+ }
1780
+ if (grepTool?.inputSchema?.properties?.head_limit?.minimum !== 0 || !/keep small/i.test(grepHeadLimitDescription)) {
1781
+ throw new Error('grep head_limit schema must keep locator caps explicit');
1782
+ }
1783
+ if (grepTool?.inputSchema?.properties?.type) {
1784
+ throw new Error('grep type schema must stay hidden; prefer glob for extension narrowing');
1785
+ }
1786
+ if (!/code flow before text search/i.test(codeGraphProps.mode?.description || '') || !/one anchor is enough/i.test(codeGraphProps.symbol?.description || '') || !/one call/i.test(codeGraphProps.symbols?.description || '')) {
1787
+ throw new Error('code_graph schema fields must steer away from repeated lookup loops');
1788
+ }
1578
1789
 
1579
1790
  const longToolSearchText = compactToolSearchDescription(`${patchDescription}\n${patchDescription}`);
1580
1791
  if (longToolSearchText.length > 220 || /\n/.test(longToolSearchText)) {
1581
1792
  throw new Error(`tool_search descriptions must be compact single-line snippets, got ${longToolSearchText.length} chars`);
1582
1793
  }
1583
1794
 
1795
+ {
1796
+ // Regression guard for the sonnet-5 16384 cap bug (thinking exhausted the
1797
+ // whole output budget). Both the catalog path (outputTokens=128000 → capped
1798
+ // 65536) and the catalog-miss heuristic (sonnet 5+ → 65536) must yield
1799
+ // 65536, so assert the exact value with the env override cleared.
1800
+ const _prevMaxOut = process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS;
1801
+ delete process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS;
1802
+ try {
1803
+ const sonnet5MaxTokens = _anthropicOAuthTest.resolveMaxTokens('claude-sonnet-5');
1804
+ if (sonnet5MaxTokens !== 65536) {
1805
+ throw new Error(`resolveMaxTokens('claude-sonnet-5') must be 65536 (catalog-capped or sonnet-5+ fallback), got ${sonnet5MaxTokens}`);
1806
+ }
1807
+ const sonnet46MaxTokens = _anthropicOAuthTest.resolveMaxTokens('claude-sonnet-4-6');
1808
+ if (!(sonnet46MaxTokens >= 16384)) {
1809
+ throw new Error(`resolveMaxTokens('claude-sonnet-4-6') must be >= 16384, got ${sonnet46MaxTokens}`);
1810
+ }
1811
+ process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS = 'garbage';
1812
+ const garbageOverride = _anthropicOAuthTest.resolveMaxTokens('claude-sonnet-5');
1813
+ if (garbageOverride !== 65536) {
1814
+ throw new Error(`invalid MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS must be ignored (catalog/fallback path), got ${garbageOverride}`);
1815
+ }
1816
+ process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS = '32768';
1817
+ const validOverride = _anthropicOAuthTest.resolveMaxTokens('claude-sonnet-5');
1818
+ if (validOverride !== 32768) {
1819
+ throw new Error(`valid MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS=32768 must win, got ${validOverride}`);
1820
+ }
1821
+ } finally {
1822
+ if (_prevMaxOut === undefined) delete process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS;
1823
+ else process.env.MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS = _prevMaxOut;
1824
+ }
1825
+ }
1826
+
1584
1827
  process.stdout.write(`tool smoke passed surface_chars=${surfaceSize}\n`);