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
@@ -0,0 +1,324 @@
1
+ /**
2
+ * maintenance-pickers.mjs — Update / Auto-clear / Profile picker cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory. These openers drive setPicker + setSettingsPrompt and read live
6
+ * store state, so they can't be pure. Every function body is the original App
7
+ * logic verbatim, with closure identifiers threaded through the factory
8
+ * argument.
9
+ */
10
+ export function createMaintenancePickers({
11
+ store,
12
+ theme,
13
+ formatDuration,
14
+ setPicker,
15
+ setProviderPrompt,
16
+ setChannelPrompt,
17
+ setHookPrompt,
18
+ setSettingsPrompt,
19
+ setContextPanel,
20
+ closeUsagePanel,
21
+ }) {
22
+ const openUpdatePicker = (options = {}) => {
23
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
24
+ const readSettings = () => {
25
+ try { return store.getUpdateSettings?.() || {}; } catch { return {}; }
26
+ };
27
+ const readStatus = () => {
28
+ try { return store.getUpdateStatus?.() || { phase: 'idle' }; } catch { return { phase: 'idle' }; }
29
+ };
30
+ const render = ({ checking = false } = {}) => {
31
+ const upd = readSettings();
32
+ const status = readStatus();
33
+ const current = upd.currentVersion || 'unknown';
34
+ const latestMeta = checking || status.phase === 'checking'
35
+ ? 'checking…'
36
+ : (upd.latestVersion || 'unknown');
37
+ const items = [
38
+ {
39
+ value: 'current',
40
+ label: 'Current version',
41
+ meta: current,
42
+ description: 'Installed mixdog version.',
43
+ _action: 'current',
44
+ },
45
+ {
46
+ value: 'latest',
47
+ label: 'Latest version',
48
+ meta: latestMeta,
49
+ description: 'Enter to re-check now.',
50
+ _action: 'latest',
51
+ },
52
+ {
53
+ value: 'auto-update',
54
+ label: 'Auto-update',
55
+ meta: upd.autoUpdate ? 'On' : 'Off',
56
+ description: 'Enter to toggle automatic updates.',
57
+ _action: 'auto-update',
58
+ },
59
+ ];
60
+ setProviderPrompt(null);
61
+ setChannelPrompt(null);
62
+ setHookPrompt(null);
63
+ setSettingsPrompt(null);
64
+ setPicker({
65
+ title: 'Update',
66
+ description: 'Check version and update mixdog.',
67
+ help: '↑/↓ Select · Enter Open/Toggle · Esc Close',
68
+ indexMode: 'always',
69
+ labelWidth: 16,
70
+ metaWidth: 16,
71
+ items,
72
+ confirmBar: {
73
+ buttons: [
74
+ {
75
+ value: 'update-now',
76
+ label: upd.updateAvailable
77
+ ? `Update to v${upd.latestVersion || 'latest'}`
78
+ : 'Update now',
79
+ },
80
+ ],
81
+ onConfirm: (button) => {
82
+ if (button?.value === 'update-now') runUpdate();
83
+ },
84
+ },
85
+ onSelect: (_value, item) => {
86
+ if (item?._action === 'latest') {
87
+ recheck();
88
+ } else if (item?._action === 'auto-update') {
89
+ toggleAutoUpdate(!upd.autoUpdate);
90
+ }
91
+ },
92
+ onCancel: () => {
93
+ setPicker(null);
94
+ if (returnTo) returnTo();
95
+ },
96
+ });
97
+ };
98
+ const toggleAutoUpdate = (enabled) => {
99
+ try {
100
+ void Promise.resolve(store.setAutoUpdate?.(enabled)).finally(() => render());
101
+ store.pushNotice(`Auto-update ${enabled ? 'on' : 'off'}`, 'info');
102
+ } catch (e) {
103
+ store.pushNotice(`auto-update failed: ${e?.message || e}`, 'error');
104
+ }
105
+ render();
106
+ };
107
+ const recheck = () => {
108
+ render({ checking: true });
109
+ void Promise.resolve(store.checkForUpdate?.({ force: true }))
110
+ .then(() => render())
111
+ .catch((e) => {
112
+ store.pushNotice(`update check failed: ${e?.message || e}`, 'error');
113
+ render();
114
+ });
115
+ };
116
+ const runUpdate = () => {
117
+ store.pushNotice('Updating…', 'info');
118
+ void Promise.resolve(store.runUpdateNow?.())
119
+ .then((result) => {
120
+ if (result?.ok) {
121
+ store.pushNotice(`v${result.version} installed — restart to apply`, 'info');
122
+ } else {
123
+ store.pushNotice(`Update failed: ${result?.error || 'unknown error'}`, 'error');
124
+ }
125
+ render();
126
+ })
127
+ .catch((e) => {
128
+ store.pushNotice(`Update failed: ${e?.message || e}`, 'error');
129
+ render();
130
+ });
131
+ };
132
+ render({ checking: true });
133
+ void Promise.resolve(store.checkForUpdate?.({}))
134
+ .then(() => render())
135
+ .catch(() => render());
136
+ };
137
+
138
+ const openAutoClearPicker = (options = {}) => {
139
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
140
+ // Lead BP4 messages cache TTL follows autoClear (cache-strategy.mjs
141
+ // resolveLeadMessagesTtl): off or idle>=1h -> 1h, shorter idle -> 5m.
142
+ const HOUR_MS = 60 * 60 * 1000;
143
+ const IDLE_WINDOWS = [
144
+ { value: '5m', label: '5m', ms: 5 * 60 * 1000 },
145
+ { value: '10m', label: '10m', ms: 10 * 60 * 1000 },
146
+ { value: '30m', label: '30m', ms: 30 * 60 * 1000 },
147
+ { value: '1h', label: '1h', ms: HOUR_MS },
148
+ { value: '6h', label: '6h', ms: 6 * HOUR_MS },
149
+ { value: '24h', label: '24h', ms: 24 * HOUR_MS },
150
+ ];
151
+ const readCurrent = () => {
152
+ try { return store.getAutoClear?.() || null; } catch { return null; }
153
+ };
154
+ const applyAutoClear = (patch = {}) => {
155
+ try {
156
+ const next = store.setAutoClear?.(patch);
157
+ if (!next) {
158
+ store.pushNotice('autoclear unavailable', 'warn');
159
+ return;
160
+ }
161
+ store.pushNotice(next.enabled ? `autoclear on · idle ${formatDuration(next.idleMs)}` : 'autoclear off', 'info');
162
+ } catch (e) {
163
+ store.pushNotice(`autoclear failed: ${e?.message || e}`, 'error');
164
+ }
165
+ render();
166
+ };
167
+ const render = () => {
168
+ const current = readCurrent();
169
+ const enabled = current?.enabled !== false;
170
+ const idleMs = Number(current?.idleMs || HOUR_MS);
171
+ const custom = current?.custom === true;
172
+ const providerDefault = Number(current?.providerDefault || HOUR_MS);
173
+ const cacheTtlLabel = !enabled || idleMs >= HOUR_MS ? '1h' : '5m';
174
+ const items = [
175
+ {
176
+ value: 'toggle',
177
+ label: 'Auto-clear',
178
+ meta: enabled ? 'On' : 'Off',
179
+ description: enabled
180
+ ? `Clear idle sessions after ${formatDuration(idleMs)} · lead cache TTL ${cacheTtlLabel}.`
181
+ : 'Idle auto-clear disabled.',
182
+ _action: 'toggle',
183
+ },
184
+ {
185
+ value: 'default',
186
+ label: 'Default',
187
+ marker: !custom ? '✓' : '',
188
+ markerColor: theme.success,
189
+ meta: `Provider default · ${formatDuration(providerDefault)}`,
190
+ description: `Uses the ${current?.provider || 'current'} provider default idle window.`,
191
+ _action: 'default',
192
+ },
193
+ ...IDLE_WINDOWS.map((win) => ({
194
+ value: win.value,
195
+ label: win.label,
196
+ marker: custom && idleMs === win.ms ? '✓' : '',
197
+ markerColor: theme.success,
198
+ description: `Clear idle sessions after ${win.label}.`,
199
+ _action: 'window',
200
+ _idleMs: win.ms,
201
+ })),
202
+ ];
203
+ setPicker({
204
+ title: 'Auto-clear',
205
+ description: `Clear idle context after ${enabled ? formatDuration(idleMs) : 'never'} · lead cache TTL ${cacheTtlLabel}.`,
206
+ help: '↑/↓ Select · ←/→ Toggle On/Off · Enter Apply · Esc Close',
207
+ indexMode: 'always',
208
+ labelWidth: 10,
209
+ items,
210
+ onLeft: (item) => {
211
+ if (item?._action === 'toggle') applyAutoClear({ enabled: false });
212
+ },
213
+ onRight: (item) => {
214
+ if (item?._action === 'toggle') applyAutoClear({ enabled: true });
215
+ },
216
+ onSelect: (_value, item) => {
217
+ if (item?._action === 'toggle') {
218
+ applyAutoClear({ enabled: !enabled });
219
+ } else if (item?._action === 'default') {
220
+ applyAutoClear({ reset: true });
221
+ } else if (item?._action === 'window') {
222
+ applyAutoClear({ enabled: true, idleMs: item._idleMs });
223
+ }
224
+ },
225
+ onCancel: () => {
226
+ setPicker(null);
227
+ if (returnTo) returnTo();
228
+ },
229
+ });
230
+ };
231
+ setProviderPrompt(null);
232
+ setChannelPrompt(null);
233
+ setHookPrompt(null);
234
+ setSettingsPrompt(null);
235
+ setContextPanel(null);
236
+ closeUsagePanel();
237
+ render();
238
+ };
239
+
240
+ const openProfilePicker = (options = {}) => {
241
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : null;
242
+ let profile = null;
243
+ try {
244
+ profile = store.getProfile?.() || null;
245
+ } catch {
246
+ profile = null;
247
+ }
248
+ const languages = Array.isArray(profile?.languages) && profile.languages.length
249
+ ? profile.languages
250
+ : [{ id: 'system', label: 'System (locale)' }];
251
+ const currentLangId = profile?.language || 'system';
252
+ const currentLang = languages.find((lang) => lang.id === currentLangId) || languages[0];
253
+ const titleValue = String(profile?.title || '').trim();
254
+ const cycleLanguage = (direction = 1) => {
255
+ const idx = Math.max(0, languages.findIndex((lang) => lang.id === currentLangId));
256
+ const next = languages[(idx + direction + languages.length) % languages.length];
257
+ try {
258
+ store.setProfile?.({ language: next.id });
259
+ store.pushNotice(`Language set to ${next.label}`, 'info');
260
+ } catch (e) {
261
+ store.pushNotice(`profile update failed: ${e?.message || e}`, 'error');
262
+ }
263
+ openProfilePicker({ returnTo });
264
+ };
265
+ setProviderPrompt(null);
266
+ setChannelPrompt(null);
267
+ setHookPrompt(null);
268
+ setSettingsPrompt(null);
269
+ setContextPanel(null);
270
+ closeUsagePanel();
271
+ setPicker({
272
+ title: 'Profile',
273
+ description: 'How the assistant addresses you and which language it replies in.',
274
+ help: '↑/↓ Select · ←/→ Change · Enter Edit · Esc Close',
275
+ indexMode: 'always',
276
+ labelWidth: 12,
277
+ metaWidth: 20,
278
+ items: [
279
+ {
280
+ value: 'title',
281
+ label: 'Title',
282
+ meta: titleValue || '(not set)',
283
+ description: 'Preferred form of address. Enter to edit.',
284
+ _action: 'title',
285
+ },
286
+ {
287
+ value: 'language',
288
+ label: 'Language',
289
+ meta: currentLang?.label || 'System (locale)',
290
+ description: 'Response language. ←/→ to change, Enter to cycle.',
291
+ _action: 'language',
292
+ },
293
+ ],
294
+ onLeft: (item) => {
295
+ if (item?._action === 'language') cycleLanguage(-1);
296
+ },
297
+ onRight: (item) => {
298
+ if (item?._action === 'language') cycleLanguage(1);
299
+ },
300
+ onSelect: (_value, item) => {
301
+ if (item?._action === 'title') {
302
+ setPicker(null);
303
+ setSettingsPrompt({
304
+ kind: 'profile-title',
305
+ label: 'Profile · Title',
306
+ hint: 'How should the assistant address you? Leave blank to clear.',
307
+ });
308
+ } else if (item?._action === 'language') {
309
+ cycleLanguage(1);
310
+ }
311
+ },
312
+ onCancel: () => {
313
+ setPicker(null);
314
+ if (returnTo) returnTo();
315
+ },
316
+ });
317
+ };
318
+
319
+ return {
320
+ openUpdatePicker,
321
+ openAutoClearPicker,
322
+ openProfilePicker,
323
+ };
324
+ }
@@ -0,0 +1,330 @@
1
+ /*
2
+ * model-options.mjs — pure model/route label, ordering, and picker-item helpers.
3
+ *
4
+ * Extracted verbatim from App.jsx. No React, no engine state — every function
5
+ * is a pure transform over model records / route objects. Depends only on the
6
+ * shared theme palette and the displayModelName formatter.
7
+ */
8
+ import { theme } from '../theme.mjs';
9
+ import { displayModelName } from '../../ui/model-display.mjs';
10
+
11
+ export const parsedModelVersion = (id) => {
12
+ const text = String(id || '').toLowerCase();
13
+ const claude = text.match(/^claude-[a-z]+-(\d+)(?:[-.](\d+))?/);
14
+ if (claude) return [Number(claude[1]) || 0, Number(claude[2]) || 0];
15
+ const compact = text.match(/(?:^|[-_])(?:o|gpt|grok|qwen|llama|mistral|gemma|phi|glm)(\d+)(?:\.(\d+))?(?:\.(\d{1,3}))?/);
16
+ if (compact) return compact.slice(1).filter((v) => v != null).map((v) => Number(v) || 0);
17
+ const generic = text.match(/(?:^|[-_v])(\d+)(?:\.(\d+))?(?:\.(\d{1,3}))?/);
18
+ if (!generic) return [];
19
+ return generic.slice(1).filter((v) => v != null).map((v) => Number(v) || 0);
20
+ };
21
+
22
+ export const releaseTime = (m) => {
23
+ if (m?.releaseDate) {
24
+ const t = Date.parse(m.releaseDate);
25
+ if (Number.isFinite(t)) return t;
26
+ }
27
+ const created = Number(m?.created);
28
+ if (Number.isFinite(created) && created > 0) {
29
+ return created < 1_000_000_000_000 ? created * 1000 : created;
30
+ }
31
+ const dated = String(m?.id || '').match(/(?:^|-)(\d{4})(\d{2})(\d{2})(?:$|-)/);
32
+ if (!dated) return 0;
33
+ return Date.parse(`${dated[1]}-${dated[2]}-${dated[3]}`) || 0;
34
+ };
35
+
36
+ export const isClaudeModel = (m) => {
37
+ const provider = String(m?.provider || '').toLowerCase();
38
+ const id = String(m?.id || '').toLowerCase();
39
+ return provider.includes('anthropic') && /^claude-[a-z]+-/.test(id);
40
+ };
41
+
42
+ export const modelVersion = (m) => {
43
+ const fromId = parsedModelVersion(m?.id);
44
+ return fromId.length ? fromId : parsedModelVersion(m?.display || m?.name);
45
+ };
46
+
47
+ export const compareModelVersion = (a, b) => {
48
+ const va = modelVersion(a);
49
+ const vb = modelVersion(b);
50
+ if (va.length === 0 && vb.length === 0) return 0;
51
+ if (va.length === 0) return 1;
52
+ if (vb.length === 0) return -1;
53
+ for (let i = 0; i < Math.max(va.length, vb.length); i += 1) {
54
+ const delta = (vb[i] || 0) - (va[i] || 0);
55
+ if (delta) return delta;
56
+ }
57
+ return 0;
58
+ };
59
+
60
+ export const compareModelRecency = (a, b) => {
61
+ if (isClaudeModel(a) && isClaudeModel(b)) {
62
+ if (!!a?.latest !== !!b?.latest) return a?.latest ? -1 : 1;
63
+ const versionDelta = compareModelVersion(a, b);
64
+ if (versionDelta) return versionDelta;
65
+ const ta = releaseTime(a);
66
+ const tb = releaseTime(b);
67
+ if (ta !== tb) return tb - ta;
68
+ return String(a?.display || a?.id || '').localeCompare(String(b?.display || b?.id || ''));
69
+ }
70
+
71
+ const ta = releaseTime(a);
72
+ const tb = releaseTime(b);
73
+ if (ta !== tb) return tb - ta;
74
+
75
+ if (!!a?.latest !== !!b?.latest) return a?.latest ? -1 : 1;
76
+ const versionDelta = compareModelVersion(a, b);
77
+ if (versionDelta) return versionDelta;
78
+ return String(a?.display || a?.id || '').localeCompare(String(b?.display || b?.id || ''));
79
+ };
80
+
81
+ export const modelFamily = (m) => {
82
+ const text = String(m?.id || m?.display || '').toLowerCase();
83
+ const claude = text.match(/^claude-([a-z]+)/);
84
+ if (claude) return claude[1];
85
+ if (m?.family) return String(m.family).toLowerCase();
86
+ const first = text.match(/^[a-z]+(?:-[a-z]+)?/);
87
+ return first ? first[0] : 'model';
88
+ };
89
+
90
+ export const modelContextWindow = (m) => {
91
+ const raw = Number(m?.contextWindow);
92
+ const n = Number.isFinite(raw) && raw > 0 ? raw : 0;
93
+ const provider = String(m?.provider || '').toLowerCase();
94
+ const id = String(m?.id || '').toLowerCase();
95
+ const version = parsedModelVersion(id);
96
+ if (provider.includes('anthropic') && /^claude-[a-z]+-/.test(id)) {
97
+ if ((version[0] || 0) >= 5) return Math.max(n, 1_000_000);
98
+ if (/^claude-(opus|sonnet)-4-(6|7|8)(?:$|-)/.test(id)) return Math.max(n, 1_000_000);
99
+ }
100
+ return n;
101
+ };
102
+
103
+ export const formatContextWindow = (tokens) => {
104
+ const n = Number(tokens);
105
+ if (!Number.isFinite(n) || n <= 0) return '';
106
+ if (n >= 1_000_000) {
107
+ const m = n / 1_000_000;
108
+ return `${Number.isInteger(m) ? m.toFixed(0) : m.toFixed(1)}M Context`;
109
+ }
110
+ return `${Math.round(n / 1000)}k Context`;
111
+ };
112
+
113
+ export const modelFamilyLimit = (provider, family) => {
114
+ const p = String(provider || '').toLowerCase();
115
+ if (!p.includes('anthropic')) return 8;
116
+ if (family === 'opus') return 3;
117
+ return 1;
118
+ };
119
+
120
+ export const normalizeModelOptions = (models) => {
121
+ if (!Array.isArray(models)) return [];
122
+ const providers = new Map();
123
+ for (const model of models) {
124
+ if (!model?.provider || !model?.id) continue;
125
+ if (!providers.has(model.provider)) providers.set(model.provider, new Map());
126
+ const families = providers.get(model.provider);
127
+ const family = modelFamily(model);
128
+ if (!families.has(family)) families.set(family, []);
129
+ families.get(family).push(model);
130
+ }
131
+
132
+ const normalized = [];
133
+ for (const [provider, families] of providers.entries()) {
134
+ const providerModels = [];
135
+ for (const [family, group] of families.entries()) {
136
+ const limit = modelFamilyLimit(provider, family);
137
+ providerModels.push(...group.slice().sort(compareModelRecency).slice(0, limit));
138
+ }
139
+ normalized.push(...providerModels.sort(compareModelRecency));
140
+ }
141
+ return normalized;
142
+ };
143
+
144
+ export const providerDisplayName = (provider) => {
145
+ const key = String(provider || '').toLowerCase();
146
+ if (key === 'openai-oauth') return 'OpenAI OAuth';
147
+ if (key === 'anthropic-oauth') return 'Anthropic OAuth';
148
+ if (key === 'grok-oauth') return 'Grok OAuth';
149
+ if (key === 'openai' || key === 'openai-api') return 'OpenAI API';
150
+ if (key === 'anthropic' || key === 'anthropic-api') return 'Anthropic API';
151
+ if (key === 'gemini' || key === 'gemini-api') return 'Gemini API';
152
+ if (key === 'xai' || key === 'xai-api') return 'xAI API';
153
+ if (key === 'deepseek' || key === 'deepseek-api') return 'DeepSeek API';
154
+ if (key === 'opencode-go') return 'OpenCode Go API';
155
+ if (key === 'ollama') return 'Ollama';
156
+ if (key === 'lmstudio') return 'LM Studio';
157
+ if (key === 'default') return 'Default';
158
+ return provider || 'Provider';
159
+ };
160
+
161
+ export const providerDisplayRank = (provider) => {
162
+ const key = String(provider || '').toLowerCase();
163
+ const ranks = {
164
+ default: 0,
165
+ 'openai-oauth': 10,
166
+ 'anthropic-oauth': 20,
167
+ 'grok-oauth': 30,
168
+ 'opencode-go': 35,
169
+ openai: 40,
170
+ 'openai-api': 40,
171
+ anthropic: 50,
172
+ 'anthropic-api': 50,
173
+ gemini: 60,
174
+ 'gemini-api': 60,
175
+ xai: 70,
176
+ 'xai-api': 70,
177
+ deepseek: 90,
178
+ 'deepseek-api': 90,
179
+ ollama: 100,
180
+ lmstudio: 110,
181
+ };
182
+ return ranks[key] ?? 900;
183
+ };
184
+
185
+ export const titleCaseOption = (value) => String(value || '')
186
+ .split(/([\s_-]+)/)
187
+ .map((part) => /^[\s_-]+$/.test(part) ? part : `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`)
188
+ .join('');
189
+
190
+ export const effortDisplayLabel = (value) => {
191
+ const text = String(value || '').trim();
192
+ if (!text) return '';
193
+ if (text.toLowerCase() === 'xhigh') return 'XHigh';
194
+ return titleCaseOption(text);
195
+ };
196
+
197
+ export const fastDisplayLabel = (enabled = true) => `Fast ${enabled ? 'On' : 'Off'}`;
198
+
199
+ export const modelDescription = (m) => [formatContextWindow(modelContextWindow(m)), m.fastCapable ? 'Fast Available' : ''].filter(Boolean).join(' · ');
200
+
201
+ export const modelRecordDisplayName = (model) => displayModelName(
202
+ model?.id,
203
+ model?.provider,
204
+ model?.display || model?.name,
205
+ );
206
+
207
+ export const routeModelDisplayName = (route) => displayModelName(route?.model, route?.provider);
208
+
209
+ export const groupModelsByProvider = (models) => {
210
+ const providers = new Map();
211
+ for (const model of models) {
212
+ if (!providers.has(model.provider)) providers.set(model.provider, []);
213
+ providers.get(model.provider).push(model);
214
+ }
215
+
216
+ const orderedProviders = [...providers.keys()].sort((a, b) => {
217
+ const rank = providerDisplayRank(a) - providerDisplayRank(b);
218
+ if (rank !== 0) return rank;
219
+ const label = providerDisplayName(a).localeCompare(providerDisplayName(b));
220
+ if (label !== 0) return label;
221
+ return a.localeCompare(b);
222
+ });
223
+ return { providers, orderedProviders };
224
+ };
225
+
226
+ export const buildModelProviderItems = (models, currentRoute = null) => {
227
+ const { providers, orderedProviders } = groupModelsByProvider(models);
228
+ return orderedProviders.map((provider) => {
229
+ const providerModels = providers.get(provider) || [];
230
+ const currentModel = currentRoute?.provider === provider
231
+ ? providerModels.find((model) => model.id === currentRoute.model)
232
+ : null;
233
+ return {
234
+ value: `provider:${provider}`,
235
+ label: providerDisplayName(provider),
236
+ marker: currentModel ? '✓' : '',
237
+ markerColor: theme.success,
238
+ meta: currentModel ? modelRecordDisplayName(currentModel) : '',
239
+ description: `${providerModels.length} model${providerModels.length === 1 ? '' : 's'}`,
240
+ _action: 'open-provider',
241
+ _provider: provider,
242
+ };
243
+ });
244
+ };
245
+
246
+ export const buildProviderModelItems = (models, provider, currentRoute = null) => {
247
+ const providerModels = models.filter((model) => model.provider === provider);
248
+ return providerModels.map((model) => ({
249
+ value: `model:${model.provider}:${model.id}`,
250
+ label: modelRecordDisplayName(model),
251
+ marker: currentRoute?.provider === model.provider && currentRoute?.model === model.id ? '✓' : '',
252
+ markerColor: theme.success,
253
+ description: modelDescription(model),
254
+ _action: 'select-model',
255
+ _provider: model.provider,
256
+ _modelId: model.id,
257
+ _model: model,
258
+ }));
259
+ };
260
+
261
+ export const routeLabel = (route) => {
262
+ if (!route?.provider || !route?.model) return '(unset)';
263
+ return [
264
+ providerDisplayName(route.provider),
265
+ routeModelDisplayName(route),
266
+ route.effort ? effortDisplayLabel(route.effort) : '',
267
+ route.fast ? 'Fast' : '',
268
+ ].filter(Boolean).join(' · ');
269
+ };
270
+
271
+ export const routeModelLabel = (route) => {
272
+ if (!route?.model) return '(unset)';
273
+ return [
274
+ routeModelDisplayName(route),
275
+ route.effort ? effortDisplayLabel(route.effort) : '',
276
+ route.fast ? 'Fast' : '',
277
+ ].filter(Boolean).join(' · ');
278
+ };
279
+
280
+ export const agentModelProfile = (route) => {
281
+ if (!route?.model) return '';
282
+ return [
283
+ routeModelDisplayName(route),
284
+ route.effort ? effortDisplayLabel(route.effort) : '',
285
+ route.fast ? 'Fast' : '',
286
+ ].filter(Boolean).join(' · ');
287
+ };
288
+
289
+ export const agentModelParts = (route) => [
290
+ { text: route?.model ? routeModelDisplayName(route) : '', width: 17 },
291
+ { text: route?.effort ? effortDisplayLabel(route.effort) : '', width: 6 },
292
+ { text: route?.fast ? 'Fast' : '', width: 4 },
293
+ ];
294
+
295
+ export const routeFromModel = (model, effort = null) => ({
296
+ provider: model.provider,
297
+ model: model.id,
298
+ ...(effort && effort !== 'auto' ? { effort } : {}),
299
+ });
300
+
301
+ export const modelScore = (model, slot) => {
302
+ const text = `${model.provider} ${model.id} ${model.display} ${model.family || ''} ${model.tier || ''}`.toLowerCase();
303
+ let score = 0;
304
+ if (model.latest) score += 6;
305
+ if (slot === 'lead' || slot === 'review') {
306
+ if (/opus|gpt-5\.5|gpt-5|sonnet/.test(text)) score += 20;
307
+ if (/mini|nano|haiku|flash/.test(text)) score -= 5;
308
+ } else if (slot === 'memory') {
309
+ if (/haiku|mini|nano|flash|fast/.test(text)) score += 20;
310
+ if (/opus|max/.test(text)) score -= 4;
311
+ } else if (slot === 'explorer' || slot === 'agent') {
312
+ if (/sonnet|gpt-5|mini|haiku|flash/.test(text)) score += 12;
313
+ if (/opus/.test(text)) score += slot === 'agent' ? 3 : -2;
314
+ }
315
+ if (model.supportsFunctionCalling) score += 2;
316
+ return score;
317
+ };
318
+
319
+ export const chooseRecommendedModel = (models, slot, fallbackRoute) => {
320
+ if (!Array.isArray(models) || models.length === 0) return null;
321
+ const sorted = models.slice().sort((a, b) => modelScore(b, slot) - modelScore(a, slot));
322
+ return sorted[0] ? routeFromModel(sorted[0]) : (fallbackRoute || null);
323
+ };
324
+
325
+ export const buildWorkflowDefaults = (models, defaultRoute) => ({
326
+ lead: defaultRoute,
327
+ agent: chooseRecommendedModel(models, 'agent', defaultRoute),
328
+ explorer: chooseRecommendedModel(models, 'explorer', defaultRoute),
329
+ memory: chooseRecommendedModel(models, 'memory', defaultRoute),
330
+ });