mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -231,6 +231,29 @@ function persistAgentConfig(build) {
231
231
  updateSection('agent', (current) => build(hasKeys(current) ? current : {}));
232
232
  }
233
233
 
234
+ // Recap toggle (recap.enabled, default true) gates ONLY the background memory
235
+ // cycles. The memory module itself is always-on. On load we fold a legacy
236
+ // `modules.memory === false` flag into recap.enabled=false one time; the caller
237
+ // (loadConfig) persists the migration so the legacy flag is dropped from disk.
238
+ let _migratedRecapLegacy = false;
239
+ function normalizeRecapConfig(rawRecap, rawModules) {
240
+ const recap = rawRecap && typeof rawRecap === 'object' ? { ...rawRecap } : {};
241
+ if (!('enabled' in recap)) {
242
+ const legacyMemory = rawModules && typeof rawModules === 'object' ? rawModules.memory : undefined;
243
+ const legacyDisabled = legacyMemory === false
244
+ || (legacyMemory && typeof legacyMemory === 'object' && legacyMemory.enabled === false);
245
+ if (legacyDisabled) {
246
+ recap.enabled = false;
247
+ _migratedRecapLegacy = true;
248
+ } else {
249
+ recap.enabled = true;
250
+ }
251
+ } else {
252
+ recap.enabled = recap.enabled !== false;
253
+ }
254
+ return recap;
255
+ }
256
+
234
257
  export function loadConfig(options = {}) {
235
258
  const includeSecrets = options.secrets !== false;
236
259
  const sectionRaw = readSection('agent');
@@ -353,6 +376,40 @@ export function loadConfig(options = {}) {
353
376
  process.stderr.write(`[config] persist maintenance migration failed: ${err?.message}\n`);
354
377
  }
355
378
  }
379
+ // One-time recap migration: fold a legacy `modules.memory === false`
380
+ // flag into `recap.enabled=false` and drop the legacy flag on disk.
381
+ // Compute the recap shape here (before the return) so the
382
+ // _migratedRecapLegacy latch is set, then persist under the lock.
383
+ const recapConfig = normalizeRecapConfig(raw.recap, raw.modules);
384
+ if (_migratedRecapLegacy) {
385
+ _migratedRecapLegacy = false;
386
+ try {
387
+ persistAgentConfig((current) => {
388
+ const cur = { ...current };
389
+ const target = (cur.agent && cur.agent.providers)
390
+ ? (cur.agent = { ...cur.agent })
391
+ : cur;
392
+ const recap = (target.recap && typeof target.recap === 'object') ? { ...target.recap } : {};
393
+ if (!('enabled' in recap)) recap.enabled = false;
394
+ target.recap = recap;
395
+ if (target.modules && typeof target.modules === 'object' && target.modules.memory === false) {
396
+ const modules = { ...target.modules };
397
+ delete modules.memory;
398
+ target.modules = modules;
399
+ } else if (target.modules && typeof target.modules === 'object' && target.modules.memory) {
400
+ const modules = { ...target.modules };
401
+ const memoryMod = { ...modules.memory };
402
+ delete memoryMod.enabled;
403
+ if (Object.keys(memoryMod).length === 0) delete modules.memory;
404
+ else modules.memory = memoryMod;
405
+ target.modules = modules;
406
+ }
407
+ return cur;
408
+ });
409
+ } catch (err) {
410
+ process.stderr.write(`[config] persist recap migration failed: ${err?.message}\n`);
411
+ }
412
+ }
356
413
  const rawPresets = Array.isArray(raw.presets) ? raw.presets : [];
357
414
  const normalizedPresets = rawPresets
358
415
  .map(p => normalizePreset(p))
@@ -378,12 +435,18 @@ export function loadConfig(options = {}) {
378
435
  workflow: raw.workflow && typeof raw.workflow === 'object' ? { active: String(raw.workflow.active || 'default') } : { active: 'default' },
379
436
  agentMaintenance: { enabled: true, interval: '1h', ...raw.agentMaintenance },
380
437
  profile: normalizeProfileConfig(raw.profile),
381
- autoClear: { enabled: true, idleMs: 60 * 60 * 1000, ...raw.autoClear },
438
+ // No idleMs default here: absent idleMs means "provider default"
439
+ // (config-helpers normalizeAutoClearConfig custom:false path).
440
+ // Injecting a fixed 1h would mark every config custom:true and
441
+ // bypass the per-provider auto-clear table.
442
+ autoClear: { enabled: true, ...raw.autoClear },
382
443
  compaction: raw.compaction && typeof raw.compaction === 'object' ? { ...raw.compaction } : {},
383
444
  trajectory: { enabled: true, ...raw.trajectory },
384
445
  runtime: raw.runtime && typeof raw.runtime === 'object' ? raw.runtime : {},
385
446
  shell: raw.shell && typeof raw.shell === 'object' ? raw.shell : {},
386
447
  update: raw.update && typeof raw.update === 'object' ? { ...raw.update } : {},
448
+ recap: recapConfig,
449
+ modules: raw.modules && typeof raw.modules === 'object' ? { ...raw.modules } : {},
387
450
  };
388
451
  }
389
452
  catch { /* fall through */ }
@@ -404,12 +467,14 @@ export function loadConfig(options = {}) {
404
467
  workflow: { active: 'default' },
405
468
  agentMaintenance: { enabled: true, interval: '1h' },
406
469
  profile: normalizeProfileConfig(null),
407
- autoClear: { enabled: true, idleMs: 60 * 60 * 1000 },
470
+ autoClear: { enabled: true },
408
471
  compaction: {},
409
472
  trajectory: { enabled: true },
410
473
  runtime: {},
411
474
  shell: {},
412
475
  update: {},
476
+ recap: { enabled: true },
477
+ modules: {},
413
478
  };
414
479
  }
415
480
  /**
@@ -485,6 +550,8 @@ export function saveConfig(config) {
485
550
  runtime: config.runtime || {},
486
551
  shell: config.shell || {},
487
552
  update: config.update || {},
553
+ recap: config.recap || {},
554
+ modules: config.modules || existingRaw.modules || {},
488
555
  }));
489
556
  }
490
557
  // --- Preset helpers ---
@@ -33,6 +33,47 @@ function mixdogAssetDirs(projectDir, kind) {
33
33
  dirs.push(mixdogGlobalDir(kind));
34
34
  return dirs;
35
35
  }
36
+ /**
37
+ * Absolute path to the plugin registry file, or null when the data dir is
38
+ * unresolvable. Included in the skills mtime gate so plugin add/remove
39
+ * (which rewrites registry.json) invalidates the cached skill list even
40
+ * when no surviving skills dir got a newer mtime.
41
+ */
42
+ function pluginRegistryPath() {
43
+ try {
44
+ return join(resolvePluginData(), 'plugins', 'registry.json');
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Read `<resolvePluginData()>/plugins/registry.json` (safe JSON parse, ignore
51
+ * errors) and yield `<root>/skills` for each registered plugin whose `root`
52
+ * exists on disk and has a `skills` subdirectory.
53
+ */
54
+ function pluginSkillDirs() {
55
+ const registryPath = pluginRegistryPath();
56
+ if (!registryPath)
57
+ return [];
58
+ let registry;
59
+ try {
60
+ registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
61
+ } catch {
62
+ return [];
63
+ }
64
+ if (!registry || !Array.isArray(registry.plugins))
65
+ return [];
66
+ const dirs = [];
67
+ for (const entry of registry.plugins) {
68
+ const root = entry && typeof entry.root === 'string' ? entry.root : null;
69
+ if (!root || !existsSync(root))
70
+ continue;
71
+ const skillsDir = join(root, 'skills');
72
+ if (existsSync(skillsDir))
73
+ dirs.push(skillsDir);
74
+ }
75
+ return dirs;
76
+ }
36
77
  /**
37
78
  * Collect available skills (frontmatter only — token efficient).
38
79
  * Full content loaded on demand via loadSkillContent().
@@ -49,6 +90,9 @@ export function collectSkills(cwd) {
49
90
  // first, then user-global. When cwd is missing, only the global dir is
50
91
  // searched.
51
92
  const dirs = mixdogAssetDirs(projectDir, 'skills');
93
+ // Plugin-provided skills load last so project-local and global mixdog
94
+ // skill dirs keep precedence; `seen` below dedupes by frontmatter name.
95
+ dirs.push(...pluginSkillDirs());
52
96
  const seen = new Set();
53
97
  for (const dir of dirs) {
54
98
  if (!existsSync(dir))
@@ -88,6 +132,13 @@ export function collectSkillsCached(cwd) {
88
132
  const projectDir = (typeof cwd === 'string' && cwd.length > 0) ? cwd : null;
89
133
  // Same mixdog-owned dirs collectSkills() reads, used as the freshness gate.
90
134
  const skillsDirs = mixdogAssetDirs(projectDir, 'skills');
135
+ skillsDirs.push(...pluginSkillDirs());
136
+ // registry.json itself gates plugin add/remove: removal deletes the
137
+ // plugin's skills dir (so no dir mtime advances), but saveRegistry()
138
+ // always rewrites this file. maxMtimeRecursive stats plain files directly.
139
+ const registryPath = pluginRegistryPath();
140
+ if (registryPath)
141
+ skillsDirs.push(registryPath);
91
142
  let mtime;
92
143
  const mtimeCached = _mtimeCache.get(key);
93
144
  if (mtimeCached && Date.now() - mtimeCached.checkedAt < _MTIME_TTL_MS) {
@@ -1,4 +1,5 @@
1
- import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
1
+ import { readFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { writeFile } from 'fs/promises';
2
3
  import { join } from 'path';
3
4
  import { tmpdir } from 'os';
4
5
  import { randomUUID } from 'crypto';
@@ -273,7 +274,10 @@ function capMcpOutput(content) {
273
274
  const dir = join(tmpdir(), 'mixdog-mcp-output');
274
275
  mkdirSync(dir, { recursive: true });
275
276
  spillPath = join(dir, `mcp-${Date.now()}-${randomUUID().slice(0, 8)}.txt`);
276
- writeFileSync(spillPath, s, 'utf-8');
277
+ // Fire-and-forget: the spill path is returned to the caller
278
+ // immediately (below) for later recovery; the write itself must not
279
+ // block this hot tool-result path.
280
+ writeFile(spillPath, s, 'utf-8').catch(() => { /* spill best-effort */ });
277
281
  } catch { /* spill best-effort */ }
278
282
  const spillNote = spillPath
279
283
  ? `\n\n... [full output spilled to ${spillPath}] ...`
@@ -1,8 +1,21 @@
1
1
  /**
2
- * Anthropic effort API alignment (Claude Code effort.ts + configureEffortParams).
2
+ * Anthropic "effort" (extended-thinking budget) request parameter handling.
3
3
  */
4
4
 
5
- export const EFFORT_LEVELS = Object.freeze(['low', 'medium', 'high', 'max']);
5
+ export const EFFORT_LEVELS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
6
+
7
+ // Canonical display/transport ordering for effort levels. Used only to sort
8
+ // whatever set the catalog actually advertises — NOT as an allowlist. The
9
+ // catalog (capabilities.effort) is the source of truth for which levels a
10
+ // model exposes; this array just keeps them in a stable low→max order.
11
+ const EFFORT_LEVEL_ORDER = Object.freeze(['none', 'low', 'medium', 'high', 'xhigh', 'max']);
12
+ function _sortEffortLevels(levels) {
13
+ const rank = (l) => {
14
+ const i = EFFORT_LEVEL_ORDER.indexOf(l);
15
+ return i === -1 ? EFFORT_LEVEL_ORDER.length : i;
16
+ };
17
+ return [...levels].sort((a, b) => rank(a) - rank(b));
18
+ }
6
19
 
7
20
  export const EFFORT_BETA_HEADER = 'effort-2025-11-24';
8
21
 
@@ -10,6 +23,7 @@ export const LEGACY_EFFORT_BUDGET = Object.freeze({
10
23
  low: 1024,
11
24
  medium: 4096,
12
25
  high: 16384,
26
+ xhigh: 32768,
13
27
  max: 32768,
14
28
  });
15
29
  const _LOGGED_EFFORT_NORMALIZATION = new Set();
@@ -59,11 +73,28 @@ export function modelSupportsEffort(model) {
59
73
  return false;
60
74
  }
61
75
 
76
+ // @[MODEL LAUNCH]: extend when new models support xhigh effort.
77
+ // Per platform.claude.com/docs/en/build-with-claude/effort — xhigh is
78
+ // supported by: Fable 5, Mythos 5, Opus 4.8, Opus 4.7, Sonnet 5.
79
+ // NOT Opus 4.6 / Sonnet 4.6 (those support max but not xhigh).
80
+ export function modelSupportsXhighEffort(model) {
81
+ const m = normalizeModelId(model);
82
+ if (/^claude-opus-4-(7|8)(?:$|[-@])/.test(m)) return true;
83
+ if (/^claude-opus-5(?:$|[-@])/.test(m)) return true;
84
+ if (/^claude-sonnet-5(?:$|[-@])/.test(m)) return true;
85
+ if (/^claude-fable-5(?:$|[-@])/.test(m)) return true;
86
+ if (/^claude-mythos-5(?:$|[-@])/.test(m)) return true;
87
+ if (/^claude-mythos-preview(?:$|[-@])/.test(m)) return true;
88
+ return false;
89
+ }
90
+
62
91
  // @[MODEL LAUNCH]: extend when new Opus models support max effort.
92
+ // Max list = xhigh list PLUS Opus 4.6, Sonnet 4.6, Opus 4.5.
63
93
  export function modelSupportsMaxEffort(model) {
94
+ if (modelSupportsXhighEffort(model)) return true;
64
95
  const m = normalizeModelId(model);
65
- if (/^claude-opus-4-(6|7|8)(?:$|[-@])/.test(m)) return true;
66
- if (/^claude-opus-5-/.test(m)) return true;
96
+ if (/^claude-opus-4-(5|6)(?:$|[-@])/.test(m)) return true;
97
+ if (/^claude-sonnet-4-6(?:$|[-@])/.test(m)) return true;
67
98
  return false;
68
99
  }
69
100
 
@@ -79,26 +110,24 @@ function logOnce(key, write) {
79
110
  }
80
111
 
81
112
  /**
82
- * Normalize user/config effort to API levels (low/medium/high/max).
83
- * Maps legacy xhigh max (Opus 4.6+) or high; clamps max on non-Opus models.
113
+ * Normalize user/config effort to API levels (low/medium/high/xhigh/max).
114
+ * Accepts extra-high / very-high aliases as xhigh; clamps xhigh/max down to
115
+ * high on models that don't support the top Opus tiers.
84
116
  */
85
117
  export function normalizeAnthropicEffortInput(raw, model, logTag = 'anthropic') {
86
118
  if (raw === undefined || raw === null || raw === '') return undefined;
87
119
  let level = String(raw).trim().toLowerCase();
88
120
  if (level === 'extra-high' || level === 'very-high') level = 'xhigh';
89
121
 
90
- if (level === 'xhigh') {
91
- const mapped = modelSupportsMaxEffort(model) ? 'max' : 'high';
92
- logOnce(`xhigh:${mapped}`, () => {
93
- process.stderr.write(
94
- `[${logTag}] effort "xhigh" normalized to "${mapped}" (official levels: ${EFFORT_LEVELS.join(', ')})\n`,
95
- );
122
+ if (level === 'xhigh' && !modelSupportsXhighEffort(model)) {
123
+ logOnce(`xhigh:high`, () => {
124
+ process.stderr.write(`[${logTag}] effort "xhigh" downgraded to "high" for model ${model}\n`);
96
125
  });
97
- return mapped;
126
+ return 'high';
98
127
  }
99
128
 
100
129
  if (level === 'max' && !modelSupportsMaxEffort(model)) {
101
- logOnce('max:high', () => {
130
+ logOnce(`max:high`, () => {
102
131
  process.stderr.write(`[${logTag}] effort "max" downgraded to "high" for model ${model}\n`);
103
132
  });
104
133
  return 'high';
@@ -174,15 +203,28 @@ export function applyAnthropicEffortToBody(
174
203
 
175
204
  export function effortValuesForModel(capabilities, modelId) {
176
205
  const effort = capabilities?.effort;
206
+ if (!effort) return [];
207
+ // Catalog-first: when capabilities.effort enumerates per-level support,
208
+ // that map is the source of truth — expose exactly the levels the model
209
+ // advertises (xhigh, max, or anything future), no hardcoded allowlist.
210
+ if (effort !== true && typeof effort === 'object') {
211
+ const advertised = Object.keys(effort).filter(
212
+ (level) => effort[level] === true || effort[level]?.supported === true,
213
+ );
214
+ if (advertised.length) return _sortEffortLevels(advertised);
215
+ // Object with no per-level flags: fall through to the boolean/supported
216
+ // fallback below only when it explicitly marks blanket support.
217
+ if (effort.supported !== true) return [];
218
+ }
219
+ // Fallback (effort === true, or {supported:true} with no per-level map):
220
+ // the model supports effort but doesn't enumerate levels, so derive the
221
+ // set from the known level ladder, gated by the top-tier model check.
177
222
  let levels = [...EFFORT_LEVELS];
178
223
  if (!modelSupportsMaxEffort(modelId)) {
179
224
  levels = levels.filter((level) => level !== 'max');
180
225
  }
181
- if (!effort) return [];
182
- if (effort === true) return levels;
183
- const values = levels.filter(
184
- (level) => effort?.[level] === true || effort?.[level]?.supported === true,
185
- );
186
- if (values.length) return values;
187
- return effort.supported === true ? levels : [];
226
+ if (!modelSupportsXhighEffort(modelId)) {
227
+ levels = levels.filter((level) => level !== 'xhigh');
228
+ }
229
+ return levels;
188
230
  }
@@ -0,0 +1,93 @@
1
+ // Shared catalog-driven max_tokens resolution for both Anthropic providers
2
+ // (OAuth and API-key). Extracted from anthropic-oauth.mjs so anthropic.mjs
3
+ // (the API-key twin) gets the same sonnet-5+ / haiku fix instead of drifting
4
+ // with its own hardcoded table.
5
+ //
6
+ // Resolution order:
7
+ // 1. MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS env override, if set to a positive
8
+ // int, wins outright (returned as the safety cap itself).
9
+ // 2. Catalog outputTokens for the model id (trusted over hardcoded
10
+ // heuristics when present — the API reports real per-model limits),
11
+ // clamped to [MAX_TOKENS_FLOOR, safetyCap].
12
+ // 3. Static per-model table / family heuristic fallback when the catalog
13
+ // has no entry for this model, also capped at the safety cap.
14
+
15
+ export const MAX_TOKENS_FLOOR = 8192;
16
+ export const DEFAULT_SAFETY_CAP = 65536;
17
+ const ENV_VAR = 'MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS';
18
+
19
+ // Per-model max_tokens when the model id is explicitly listed. New models
20
+ // (e.g., Sonnet 4.7) won't match a specific entry and fall through to the
21
+ // family-based heuristic below. Conservative defaults — model may support
22
+ // more but we'd rather stay within safe bounds.
23
+ const MAX_TOKENS = {
24
+ 'claude-opus-4-8': 65536,
25
+ 'claude-opus-4-7': 65536,
26
+ 'claude-opus-4-6': 65536,
27
+ 'claude-sonnet-4-6': 16384,
28
+ 'claude-haiku-4-5-20251001': 8192,
29
+ };
30
+
31
+ // Strict-positive env override parsing. Invalid values ("0", negatives,
32
+ // garbage, whitespace) are treated as UNSET — not as "use the default cap
33
+ // outright" — so catalog/fallback still decide for low-cap models. Raw env
34
+ // truthiness must never bypass resolution.
35
+ export function envAnthropicMaxOutputOverride() {
36
+ const raw = process.env[ENV_VAR];
37
+ if (raw == null || String(raw).trim() === '') return null;
38
+ const n = Number(raw);
39
+ if (!Number.isFinite(n) || n <= 0) return null;
40
+ return Math.floor(n);
41
+ }
42
+
43
+ export function resolveAnthropicSafetyCap() {
44
+ return envAnthropicMaxOutputOverride() ?? DEFAULT_SAFETY_CAP;
45
+ }
46
+
47
+ // Pure lookup over an already-loaded catalog array (disk cache or in-memory
48
+ // mirror) — no I/O, no caching strategy. Callers own how/when the array is
49
+ // loaded/refreshed.
50
+ export function lookupCatalogOutputTokens(models, id) {
51
+ if (!id || !Array.isArray(models)) return null;
52
+ const entry = models.find(m => m?.id === id);
53
+ const out = Number(entry?.outputTokens);
54
+ return Number.isFinite(out) && out > 0 ? out : null;
55
+ }
56
+
57
+ // Static per-model table + family heuristic, used when the catalog has no
58
+ // entry for this model id. Sonnet 5+ ships a much larger output budget than
59
+ // the legacy 4.x line (this is the claude-sonnet-5 fix: 16384 was starving
60
+ // visible output once extended thinking ate into the same hard cap). Keep
61
+ // sonnet-4-x conservative at 16384; only bump 5+.
62
+ export function fallbackAnthropicMaxTokens(model) {
63
+ if (MAX_TOKENS[model]) return MAX_TOKENS[model];
64
+ const id = String(model || '').toLowerCase();
65
+ if (id.includes('opus')) return 65536;
66
+ if (id.includes('fable')) return 65536;
67
+ const sonnetVersion = id.match(/^claude-sonnet-(\d+)/);
68
+ if (sonnetVersion) return Number(sonnetVersion[1]) >= 5 ? 65536 : 16384;
69
+ if (id.includes('sonnet')) return 16384;
70
+ if (id.includes('haiku')) return 8192;
71
+ return 8192;
72
+ }
73
+
74
+ // catalogLookup(model) -> number|null. Providers supply their own strategy
75
+ // for sourcing the catalog array (in-memory mirror + disk fallback for OAuth,
76
+ // plain disk read for the API-key twin — see anthropic.mjs).
77
+ export function resolveAnthropicMaxTokens(model, { catalogLookup } = {}) {
78
+ const envOverride = envAnthropicMaxOutputOverride();
79
+ if (envOverride != null) return envOverride;
80
+ const safetyCap = DEFAULT_SAFETY_CAP;
81
+ let catalogValue = null;
82
+ if (typeof catalogLookup === 'function') {
83
+ try {
84
+ catalogValue = catalogLookup(model);
85
+ } catch {
86
+ catalogValue = null;
87
+ }
88
+ }
89
+ if (Number.isFinite(catalogValue) && catalogValue > 0) {
90
+ return Math.max(MAX_TOKENS_FLOOR, Math.min(catalogValue, safetyCap));
91
+ }
92
+ return Math.min(fallbackAnthropicMaxTokens(model), safetyCap);
93
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * anthropic-model-resolve.mjs — Anthropic OAuth model-catalog cache + resolvers.
3
+ *
4
+ * Extracted from anthropic-oauth.mjs. Owns the disk-backed catalog cache and
5
+ * its in-memory mirror (single instance; the provider imports every reader/
6
+ * writer from here so there is exactly ONE catalog state in the process).
7
+ */
8
+ import { enrichModels } from './model-catalog.mjs';
9
+ import { sanitizeModelList } from './model-list-sanitize.mjs';
10
+ import { makeModelCache } from './model-cache.mjs';
11
+ import { effortValuesForModel } from './anthropic-effort.mjs';
12
+
13
+ // Disk-backed cache so repeated process starts (cron, tool calls) don't
14
+ // hammer /v1/models. 24h TTL matches the upstream client cadence.
15
+ const MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
16
+ // Bump when the on-disk cache shape changes so stale-shape entries are
17
+ // discarded instead of misread.
18
+ const ANTHROPIC_MODEL_CACHE_SCHEMA_VERSION = 1;
19
+
20
+ const _modelCache = makeModelCache({
21
+ fileName: 'anthropic-oauth-models.json',
22
+ ttlMs: MODEL_CACHE_TTL_MS,
23
+ version: ANTHROPIC_MODEL_CACHE_SCHEMA_VERSION,
24
+ onSave: (m) => { _inMemoryCatalog = Array.isArray(m) ? m.slice() : null; },
25
+ });
26
+
27
+ // Async wrappers so callers can keep awaiting; the shared cache CRUD is sync.
28
+ export async function _loadModelCache() {
29
+ return _modelCache.loadSync();
30
+ }
31
+
32
+ export async function _saveModelCache(models) {
33
+ _modelCache.save(models);
34
+ }
35
+
36
+ // In-memory mirror of the disk catalog — populated on first listModels() and
37
+ // refreshed after every _saveModelCache. Used by _catalogHas and _displayModel
38
+ // so hot paths don't hit disk on every response.
39
+ let _inMemoryCatalog = null;
40
+
41
+ // The mirror is written both by this module (onSave) and by the provider's
42
+ // listModels() warm path, so expose a setter instead of the raw binding.
43
+ export function _setInMemoryCatalog(models) {
44
+ _inMemoryCatalog = Array.isArray(models) ? models.slice() : null;
45
+ }
46
+
47
+ export function _catalogHas(id) {
48
+ if (!id || !Array.isArray(_inMemoryCatalog)) return false;
49
+ return _inMemoryCatalog.some(m => m.id === id);
50
+ }
51
+
52
+ // Display-name normalization for trace / usage. Turns dated or version-alias
53
+ // ids into the version alias form: claude-opus-4-7 → claude-opus-4.7,
54
+ // claude-haiku-4-5-20251001 → claude-haiku-4.5. Falls back to the raw id.
55
+ export function _displayModel(id) {
56
+ if (!id || typeof id !== 'string') return id;
57
+ const m = id.match(/^claude-([a-z]+)-(\d+)(?:-(\d+))?(?:-\d{8})?$/i);
58
+ if (!m) return id;
59
+ return `claude-${m[1].toLowerCase()}-${m[2]}${m[3] ? `.${m[3]}` : ''}`;
60
+ }
61
+
62
+ function _capabilitySupported(capability) {
63
+ return capability === true || capability?.supported === true;
64
+ }
65
+
66
+ // Classify a model id into our common tier/family shape. Anthropic's catalog
67
+ // mixes dated ids (claude-opus-4-5-20251101), versioned aliases
68
+ // (claude-opus-4-6), and the raw family tokens resolved via env vars.
69
+ export function _normalizeAnthropicModel(raw) {
70
+ const id = raw?.id || raw?.name;
71
+ if (!id) return null;
72
+ const familyMatch = id.match(/^claude-([a-z]+)/i);
73
+ const family = familyMatch ? familyMatch[1].toLowerCase() : 'other';
74
+ // Dated: trailing -YYYYMMDD (8 digits).
75
+ const dated = /-\d{8}$/.test(id);
76
+ // Versioned alias: claude-<family>-<major>-<minor>[-...] with no dated suffix.
77
+ const versioned = !dated && /^claude-[a-z]+-\d+(?:-\d+)?$/i.test(id);
78
+ const tier = dated ? 'dated' : versioned ? 'version' : 'family';
79
+ const releaseDate = dated
80
+ ? id.match(/-(\d{4})(\d{2})(\d{2})$/)
81
+ : null;
82
+ const effortValues = effortValuesForModel(raw?.capabilities, id);
83
+ return {
84
+ id,
85
+ display: raw?.display_name || _prettyName(id, family),
86
+ family,
87
+ provider: 'anthropic-oauth',
88
+ contextWindow: raw?.context_window || raw?.max_context_window || raw?.max_input_tokens || _defaultContextForModel(id, family),
89
+ outputTokens: raw?.max_tokens || raw?.max_output_tokens || null,
90
+ tier,
91
+ latest: false, // assigned in a second pass once full list is known
92
+ releaseDate: releaseDate ? `${releaseDate[1]}-${releaseDate[2]}-${releaseDate[3]}` : null,
93
+ supportsReasoning: effortValues.length > 0 || _capabilitySupported(raw?.capabilities?.thinking),
94
+ reasoningOptions: effortValues.length ? [{ type: 'effort', values: effortValues }] : [],
95
+ };
96
+ }
97
+
98
+ function _prettyName(id, family) {
99
+ const v = id.match(/^claude-[a-z]+-(\d+)(?:-(\d+))?/i);
100
+ const base = family[0].toUpperCase() + family.slice(1);
101
+ return v ? `${base} ${v[1]}${v[2] ? `.${v[2]}` : ''}` : base;
102
+ }
103
+
104
+ function _defaultContextForModel(id, family) {
105
+ const text = String(id || '');
106
+ const version = text.match(/^claude-[a-z]+-(\d+)(?:-(\d+))?/i);
107
+ if (Number(version?.[1] || 0) >= 5) return 1000000;
108
+ if (/^claude-(opus|sonnet)-4-(6|7|8)(?:$|-)/i.test(text)) return 1000000;
109
+ if (family && family !== 'other') return 200000;
110
+ return 200000;
111
+ }
112
+
113
+ // Mark the highest-numbered version per family as `latest: true`. Uses a simple
114
+ // lexicographic comparison on the numeric parts embedded in the id.
115
+ export function _markLatestByFamily(models) {
116
+ const byFamily = new Map();
117
+ for (const m of models) {
118
+ if (m.tier !== 'version') continue;
119
+ const cur = byFamily.get(m.family);
120
+ if (!cur || _compareVersion(m.id, cur.id) > 0) {
121
+ byFamily.set(m.family, m);
122
+ }
123
+ }
124
+ for (const m of byFamily.values()) m.latest = true;
125
+ }
126
+
127
+ function _compareVersion(a, b) {
128
+ const na = (a.match(/^claude-[a-z]+-(\d+)(?:-(\d+))?/i) || []).slice(1).map(Number);
129
+ const nb = (b.match(/^claude-[a-z]+-(\d+)(?:-(\d+))?/i) || []).slice(1).map(Number);
130
+ for (let i = 0; i < Math.max(na.length, nb.length); i++) {
131
+ if ((na[i] || 0) !== (nb[i] || 0)) return (na[i] || 0) - (nb[i] || 0);
132
+ }
133
+ return a.localeCompare(b);
134
+ }
135
+
136
+ // Newest HIGH-TIER chat model by version, read from the SYNC in-memory catalog
137
+ // mirror. Anthropic ships three families: opus / sonnet / haiku. "Latest" is the
138
+ // highest version across opus + sonnet only — haiku is the cheap tier and is
139
+ // never the flagship default. Returns null until listModels() populates the
140
+ // mirror; callers must warm the catalog (ensureLatestAnthropicModel) when null.
141
+ export function resolveLatestAnthropicModel() {
142
+ if (!Array.isArray(_inMemoryCatalog)) return null;
143
+ let best = null;
144
+ for (const m of _inMemoryCatalog) {
145
+ if (!m?.id || (m.family !== 'opus' && m.family !== 'sonnet')) continue;
146
+ if (!best || _compareVersion(m.id, best.id) > 0) best = m;
147
+ }
148
+ return best?.id || null;
149
+ }
150
+
151
+ export function resolveAnthropicModelAfter404(requested) {
152
+ if (!Array.isArray(_inMemoryCatalog)) return null;
153
+ const wanted = String(requested || '');
154
+ const family = (wanted.match(/^claude-([a-z]+)/i) || [])[1]?.toLowerCase() || null;
155
+ let best = null;
156
+ for (const m of _inMemoryCatalog) {
157
+ if (!m?.id) continue;
158
+ if (family && m.family !== family) continue;
159
+ if (!family && m.family !== 'opus' && m.family !== 'sonnet') continue;
160
+ if (!best || _compareVersion(m.id, best.id) > 0) best = m;
161
+ }
162
+ if (best?.id && best.id !== wanted) return best.id;
163
+ if (family === 'opus') {
164
+ const flagship = resolveLatestAnthropicModel();
165
+ if (flagship && flagship !== wanted) return flagship;
166
+ }
167
+ return null;
168
+ }
169
+
170
+ export async function ensureLatestAnthropicModel(provider) {
171
+ let m = resolveLatestAnthropicModel();
172
+ if (m) return m;
173
+ await provider._refreshModelCache();
174
+ m = resolveLatestAnthropicModel();
175
+ if (m) return m;
176
+ throw new Error('[anthropic-oauth] model catalog unavailable after warmup — cannot resolve default model');
177
+ }
178
+
179
+ // Catalog-reported outputTokens for a model id, read from the in-memory
180
+ // catalog mirror (lazily populated from the disk cache if the mirror hasn't
181
+ // been warmed yet by listModels()). Never throws — any failure just means
182
+ // "no catalog data", and callers fall back to the static heuristics.
183
+ export function _catalogOutputTokens(model) {
184
+ if (!model) return null;
185
+ try {
186
+ if (!Array.isArray(_inMemoryCatalog)) {
187
+ const cached = _modelCache.loadSync();
188
+ if (Array.isArray(cached)) _inMemoryCatalog = cached.slice();
189
+ }
190
+ if (!Array.isArray(_inMemoryCatalog)) return null;
191
+ const entry = _inMemoryCatalog.find(m => m?.id === model);
192
+ const out = Number(entry?.outputTokens);
193
+ return Number.isFinite(out) && out > 0 ? out : null;
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+
199
+ // Normalize + mark-latest + LiteLLM-enrich + persist one fetched /v1/models
200
+ // payload. Shared by the provider's listModels() and _refreshModelCache().
201
+ export async function normalizeAndSaveCatalog(items) {
202
+ const normalized = (Array.isArray(items) ? items : [])
203
+ .map(m => _normalizeAnthropicModel(m))
204
+ .filter(Boolean);
205
+ _markLatestByFamily(normalized);
206
+ const enriched = sanitizeModelList(await enrichModels(normalized), { provider: 'anthropic' });
207
+ await _saveModelCache(enriched);
208
+ return enriched;
209
+ }