mixdog 0.9.3 → 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 (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. 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 ---
@@ -2,7 +2,20 @@
2
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,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
+ }