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
@@ -14,8 +14,14 @@
14
14
  * `mixdog: transcript-writer: `) and suppress duplicates so a broken path
15
15
  * cannot spam the terminal.
16
16
  */
17
- import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
17
+ import { existsSync, mkdirSync, renameSync, statSync, writeFileSync } from 'node:fs';
18
18
  import { dirname, join, resolve } from 'node:path';
19
+ import { appendBuffered, drainPathSync, hasInFlightWrite } from './buffered-appender.mjs';
20
+
21
+ // Rotate the JSONL transcript once it exceeds this size, keeping one prior
22
+ // generation (`<path>.1`). Checked on each append; the check itself is a
23
+ // cheap statSync so no extra timer/interval is needed.
24
+ const TRANSCRIPT_ROTATE_BYTES = 10 * 1024 * 1024;
19
25
 
20
26
  // Byte-identical to cwdToProjectSlug() in
21
27
  // src/runtime/channels/lib/session-discovery.mjs. Inlined to avoid a
@@ -56,10 +62,31 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
56
62
  }
57
63
  }
58
64
 
65
+ function rotateIfNeeded() {
66
+ try {
67
+ if (!existsSync(transcriptPath)) return;
68
+ const { size } = statSync(transcriptPath);
69
+ if (size < TRANSCRIPT_ROTATE_BYTES) return;
70
+ // An async appendFile may be in flight for this path; renaming out
71
+ // from under it races the write on Windows. Skip this round and
72
+ // retry rotation on the next append instead.
73
+ if (hasInFlightWrite(transcriptPath)) return;
74
+ // Force any still-buffered bytes onto disk before renaming, so the
75
+ // rotated-out file ends with everything queued for it and the fresh
76
+ // file post-rename doesn't inherit stale in-memory chunks.
77
+ drainPathSync(transcriptPath);
78
+ const rotatedPath = `${transcriptPath}.1`;
79
+ try { renameSync(transcriptPath, rotatedPath); } catch (err) { logOnce(err); }
80
+ } catch (err) {
81
+ logOnce(err);
82
+ }
83
+ }
84
+
59
85
  function appendLine(entry) {
60
86
  ensureProjectDir();
87
+ rotateIfNeeded();
61
88
  try {
62
- appendFileSync(transcriptPath, `${JSON.stringify(entry)}\n`);
89
+ appendBuffered(transcriptPath, `${JSON.stringify(entry)}\n`);
63
90
  } catch (err) {
64
91
  logOnce(err);
65
92
  }
@@ -117,10 +144,33 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
117
144
  });
118
145
  }
119
146
 
147
+ // Idempotently create the transcript file as an empty 0-byte file if it
148
+ // does not already exist. Called on every remote turn (before the first
149
+ // append) so the channel worker's fs.watch can attach to the path right
150
+ // away instead of waiting on schedulePendingTranscriptRearm(). Never
151
+ // truncates an existing transcript: guarded by existsSync + the 'wx'
152
+ // (exclusive-create) flag, and any EEXIST/other error is swallowed via
153
+ // logOnce like the other writers.
154
+ function ensureTranscriptFile() {
155
+ try {
156
+ ensureProjectDir();
157
+ if (existsSync(transcriptPath)) return;
158
+ try {
159
+ writeFileSync(transcriptPath, '', { flag: 'wx' });
160
+ } catch (err) {
161
+ if (err && err.code === 'EEXIST') return;
162
+ logOnce(err);
163
+ }
164
+ } catch (err) {
165
+ logOnce(err);
166
+ }
167
+ }
168
+
120
169
  return {
121
170
  transcriptPath,
122
171
  sessionRecordPath,
123
172
  writeSessionRecord,
173
+ ensureTranscriptFile,
124
174
  refresh,
125
175
  appendAssistant,
126
176
  appendToolUse,
@@ -154,12 +154,15 @@ export async function checkLatestVersion({ force = false, dataDir } = {}) {
154
154
  }
155
155
  // Registry unreachable: fall back to a stale cache (if any) rather than
156
156
  // reporting "no update info" every time the network hiccups.
157
- if (cache && cache.latestVersion) {
157
+ // Re-read the cache here: a force:true call skips the read above, but a
158
+ // stale answer still beats none when the network is down.
159
+ const staleCache = cache || readCache(dataDir);
160
+ if (staleCache && staleCache.latestVersion) {
158
161
  return {
159
162
  currentVersion,
160
- latestVersion: cache.latestVersion,
161
- updateAvailable: isNewerVersion(cache.latestVersion, currentVersion),
162
- lastCheckedAt: cache.lastCheckedAt || now,
163
+ latestVersion: staleCache.latestVersion,
164
+ updateAvailable: isNewerVersion(staleCache.latestVersion, currentVersion),
165
+ lastCheckedAt: staleCache.lastCheckedAt || now,
163
166
  fromCache: true,
164
167
  };
165
168
  }
@@ -0,0 +1,291 @@
1
+ // Config normalization helpers: default provider, presets/routes, module
2
+ // enable flags, shell/auto-clear/compaction config, and duration formatting.
3
+ import { clean, hasOwn } from './session-text.mjs';
4
+ import { normalizeEffortInput, normalizeSavedEffort } from './effort.mjs';
5
+ import { routeFastKey, fastPreferenceFor } from './model-capabilities.mjs';
6
+
7
+ export const DEFAULT_PROVIDER = 'anthropic-oauth';
8
+ export const DEFAULT_MODEL = '';
9
+
10
+ // Resolve the provider to use when a route carries no explicit provider.
11
+ // Priority: config.defaultProvider (when it names a known provider) > DEFAULT_PROVIDER.
12
+ export function makeResolveDefaultProvider(isKnownProvider) {
13
+ return function resolveDefaultProvider(config) {
14
+ const configured = clean(config?.defaultProvider);
15
+ if (configured && isKnownProvider(configured)) return configured;
16
+ return DEFAULT_PROVIDER;
17
+ };
18
+ }
19
+
20
+ export function modelSettingsFor(config, provider, model) {
21
+ const key = routeFastKey(provider, model);
22
+ const value = key ? config?.modelSettings?.[key] : null;
23
+ return value && typeof value === 'object' ? value : {};
24
+ }
25
+
26
+ export function findPreset(config, key) {
27
+ const wanted = clean(key).toLowerCase();
28
+ if (!wanted) return null;
29
+ const presets = Array.isArray(config?.presets) ? config.presets : [];
30
+ return presets.find((p) => {
31
+ const id = clean(p?.id).toLowerCase();
32
+ const name = clean(p?.name).toLowerCase();
33
+ return id === wanted || name === wanted;
34
+ }) || null;
35
+ }
36
+
37
+ export function makeResolveRoute(resolveDefaultProvider) {
38
+ return function resolveRoute(config, { provider, model, effort, fast } = {}) {
39
+ const explicitProvider = clean(provider);
40
+ const explicitModel = clean(model);
41
+ const hasExplicitEffort = effort !== undefined;
42
+ const explicitEffort = hasExplicitEffort ? normalizeEffortInput(effort) : undefined;
43
+ const hasExplicitFast = fast !== undefined;
44
+ const explicitFast = fast === true;
45
+
46
+ if (explicitModel && !explicitProvider) {
47
+ const preset = findPreset(config, explicitModel);
48
+ if (preset) {
49
+ const p = clean(preset.provider) || DEFAULT_PROVIDER;
50
+ const m = clean(preset.model) || DEFAULT_MODEL;
51
+ const saved = modelSettingsFor(config, p, m);
52
+ return {
53
+ provider: p,
54
+ model: m,
55
+ preset,
56
+ effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
57
+ fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
58
+ };
59
+ }
60
+ }
61
+
62
+ if (!explicitProvider && !explicitModel) {
63
+ const defaultKey = config?.default;
64
+ const preset = findPreset(config, defaultKey);
65
+ if (preset) {
66
+ const p = clean(preset.provider) || DEFAULT_PROVIDER;
67
+ const m = clean(preset.model) || DEFAULT_MODEL;
68
+ const saved = modelSettingsFor(config, p, m);
69
+ return {
70
+ provider: p,
71
+ model: m,
72
+ preset,
73
+ effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort ?? preset.effort),
74
+ fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : (preset.fast === true || fastPreferenceFor(config, p, m))),
75
+ };
76
+ }
77
+ }
78
+
79
+ const p = explicitProvider || resolveDefaultProvider(config);
80
+ const m = explicitModel || DEFAULT_MODEL;
81
+ const saved = modelSettingsFor(config, p, m);
82
+ return {
83
+ provider: p,
84
+ model: m,
85
+ preset: null,
86
+ effort: hasExplicitEffort ? explicitEffort : normalizeSavedEffort(saved.effort),
87
+ fast: hasExplicitFast ? explicitFast : (hasOwn(saved, 'fast') ? saved.fast === true : fastPreferenceFor(config, p, m)),
88
+ };
89
+ };
90
+ }
91
+
92
+ export function isLikelyRawModelId(value) {
93
+ const model = clean(value);
94
+ if (!model || model.length > 160) return false;
95
+ if (/\s/.test(model)) return false;
96
+ return /^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/.test(model);
97
+ }
98
+
99
+ export function validateRequestedModelSelector(config, requested = {}) {
100
+ const model = clean(requested.model);
101
+ if (!model) return;
102
+ if (findPreset(config, model)) return;
103
+ if (isLikelyRawModelId(model)) return;
104
+ throw new Error(`Invalid model selector "${model}". Use a preset or a model id; free-form text cannot be used as a model.`);
105
+ }
106
+
107
+ export function ensureProviderEnabled(config, provider) {
108
+ const providers = { ...(config?.providers || {}) };
109
+ providers[provider] = { ...(providers[provider] || {}), enabled: true };
110
+ return providers;
111
+ }
112
+
113
+ const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
114
+
115
+ // Provider-aware auto-clear idle-sweep defaults. Rationale mirrors the
116
+ // provider cache TTLs documented in agent-runtime/cache-strategy.mjs
117
+ // (explicit breakpoint / managed-cache windows): Anthropic, Gemini, xAI and
118
+ // Mistral caches run ~1h, Groq's implicit cache persists ~2h, OpenAI/
119
+ // DeepSeek key-prefix/implicit caches persist far longer (~24h), and OpenAI
120
+ // OAuth's in-memory server cache is short-lived (~5-10min), so we round up
121
+ // slightly to 10m to avoid sweeping a still-warm cache. Unknown/
122
+ // unrecognized providers fall back to the 'default' 1h entry.
123
+ export const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
124
+ 'anthropic': 60 * 60 * 1000,
125
+ 'anthropic-oauth': 60 * 60 * 1000,
126
+ 'gemini': 60 * 60 * 1000,
127
+ 'groq': 2 * 60 * 60 * 1000,
128
+ 'openai': 24 * 60 * 60 * 1000,
129
+ 'deepseek': 24 * 60 * 60 * 1000,
130
+ 'xai': 60 * 60 * 1000,
131
+ 'openai-oauth': 10 * 60 * 1000,
132
+ 'mistral': 60 * 60 * 1000,
133
+ 'default': AUTO_CLEAR_DEFAULT_IDLE_MS,
134
+ });
135
+ export const AUTO_CLEAR_UNKNOWN_PROVIDER_IDLE_MS = AUTO_CLEAR_PROVIDER_IDLE_MS.default;
136
+
137
+ // Resolve the auto-clear idle-sweep threshold for a given provider id.
138
+ // Unknown/missing providers fall back to the 'default' 1h entry (above).
139
+ export function autoClearIdleMsForProvider(provider) {
140
+ const key = clean(provider).toLowerCase();
141
+ return AUTO_CLEAR_PROVIDER_IDLE_MS[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
142
+ }
143
+
144
+ // Resolve the effective auto-clear idle window for a config + provider:
145
+ // an explicit user-set idleMs (config.autoClear.idleMs) always wins; else
146
+ // fall back to the provider's default; else the global 1h default.
147
+ export function resolveAutoClearIdleMs(config, provider) {
148
+ const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
149
+ const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
150
+ if (Number.isFinite(idleMs) && idleMs > 0) return Math.max(60_000, Math.round(idleMs));
151
+ return autoClearIdleMsForProvider(provider);
152
+ }
153
+
154
+ export function normalizeSystemShellConfig(value = {}) {
155
+ const raw = value && typeof value === 'object' ? value : {};
156
+ const command = clean(raw.command ?? raw.path ?? raw.executable ?? raw.shell);
157
+ const envCommand = clean(process.env.MIXDOG_SHELL);
158
+ return {
159
+ command,
160
+ effective: command || envCommand || '',
161
+ source: command ? 'config' : (envCommand ? 'env' : 'auto'),
162
+ };
163
+ }
164
+
165
+ export function normalizeSystemShellCommand(value) {
166
+ const command = clean(value).replace(/^auto$/i, '').replace(/^['"](.+)['"]$/, '$1').trim();
167
+ if (!command) return '';
168
+ if (process.platform === 'win32') {
169
+ const stem = command.split(/[\\/]/).pop().toLowerCase().replace(/\.exe$/, '');
170
+ if (stem !== 'powershell' && stem !== 'pwsh') {
171
+ throw new Error('system shell command must be powershell.exe or pwsh on Windows');
172
+ }
173
+ }
174
+ return command;
175
+ }
176
+
177
+ export function normalizeAutoClearConfig(value = {}) {
178
+ const raw = value && typeof value === 'object' ? value : {};
179
+ const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
180
+ const hasExplicitIdle = Number.isFinite(idleMs) && idleMs > 0;
181
+ // idleMs: null means "no explicit override" — callers resolve the
182
+ // effective window via resolveAutoClearIdleMs/provider default. `custom`
183
+ // tells UI whether the stored idleMs is a user override (true) or the
184
+ // provider-default mode (false, idleMs null).
185
+ return {
186
+ enabled: raw.enabled !== false,
187
+ idleMs: hasExplicitIdle ? Math.max(60_000, Math.round(idleMs)) : null,
188
+ custom: hasExplicitIdle,
189
+ };
190
+ }
191
+
192
+ export function normalizeCompactTypeSetting(value, fallback = 'semantic') {
193
+ const raw = clean(value).toLowerCase().replace(/_/g, '-');
194
+ if (!raw) return fallback;
195
+ if (raw === '1' || raw === 'type1' || raw === 'type-1' || raw === 'semantic' || raw === 'summary' || raw === 'default') return 'semantic';
196
+ if (raw === '2' || raw === 'type2' || raw === 'type-2' || raw === 'recall' || raw === 'recall-fast' || raw === 'recall-fasttrack' || raw === 'recall-fast-track' || raw === 'fasttrack' || raw === 'fast-track') return 'recall-fasttrack';
197
+ return fallback;
198
+ }
199
+
200
+ export function normalizeCompactionConfig(value = {}, { memoryEnabled = true } = {}) {
201
+ const raw = value && typeof value === 'object' ? value : {};
202
+ let compactType = normalizeCompactTypeSetting(raw.compactType ?? raw.compact_type ?? raw.type, 'semantic');
203
+ // Memory is now always-on, so recall-fasttrack no longer downgrades to
204
+ // semantic. `memoryEnabled` is retained as a param for API compatibility but
205
+ // is intentionally ignored (fasttrack drains run on-demand regardless of the
206
+ // recap/background-cycle toggle).
207
+ void memoryEnabled;
208
+ return {
209
+ ...raw,
210
+ auto: raw.auto !== false && raw.enabled !== false,
211
+ type: compactType,
212
+ compactType,
213
+ };
214
+ }
215
+
216
+ export function moduleEnabled(configLike, name, fallback = true) {
217
+ const entry = configLike?.modules?.[name];
218
+ if (entry && typeof entry === 'object' && entry.enabled === false) return false;
219
+ return fallback !== false;
220
+ }
221
+
222
+ // Recap toggle: gates ONLY the background memory cycles (1/2/3). The memory
223
+ // module itself (transcript watcher/ingest, on-demand recall/fasttrack drains)
224
+ // is always-on. Default enabled. Reads the dedicated `recap` section; if a
225
+ // legacy `modules.memory === false` flag is present it is honored as recap off
226
+ // (migration folds it into `recap.enabled` on config load).
227
+ export function recapEnabled(configLike, fallback = true) {
228
+ const entry = configLike?.recap;
229
+ if (entry && typeof entry === 'object' && entry.enabled === false) return false;
230
+ // Legacy fallback: pre-migration configs may still carry modules.memory=false.
231
+ if (configLike?.modules?.memory === false) return false;
232
+ if (moduleEnabled(configLike, 'memory', true) === false) return false;
233
+ return fallback !== false;
234
+ }
235
+
236
+ export function setRecapEnabledInConfig(configLike, enabled) {
237
+ const next = { ...(configLike || {}) };
238
+ next.recap = { ...(next.recap || {}), enabled: enabled !== false };
239
+ // Drop the legacy flag if it was carrying the disabled state.
240
+ if (next.modules && next.modules.memory === false) {
241
+ const modules = { ...next.modules };
242
+ delete modules.memory;
243
+ next.modules = modules;
244
+ } else if (next.modules && next.modules.memory && typeof next.modules.memory === 'object') {
245
+ const modules = { ...next.modules };
246
+ const memoryMod = { ...modules.memory };
247
+ delete memoryMod.enabled;
248
+ if (Object.keys(memoryMod).length === 0) delete modules.memory;
249
+ else modules.memory = memoryMod;
250
+ next.modules = modules;
251
+ }
252
+ return next;
253
+ }
254
+
255
+ export function setModuleEnabledInConfig(configLike, name, enabled) {
256
+ const next = { ...(configLike || {}) };
257
+ next.modules = { ...(next.modules || {}) };
258
+ next.modules[name] = {
259
+ ...(next.modules[name] || {}),
260
+ enabled: enabled !== false,
261
+ };
262
+ return next;
263
+ }
264
+
265
+ export function formatDurationMs(ms) {
266
+ const value = Math.max(0, Number(ms) || 0);
267
+ if (value % 3_600_000 === 0) return `${value / 3_600_000}h`;
268
+ if (value % 60_000 === 0) return `${value / 60_000}m`;
269
+ return `${Math.round(value / 1000)}s`;
270
+ }
271
+
272
+ export function parseDurationMs(input) {
273
+ const text = clean(input).toLowerCase();
274
+ if (!text) return null;
275
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(text);
276
+ if (!match) return null;
277
+ const n = Number(match[1]);
278
+ if (!Number.isFinite(n) || n <= 0) return null;
279
+ const unit = match[2] || 'm';
280
+ const mult = unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : unit === 's' ? 1000 : 1;
281
+ return Math.max(60_000, Math.round(n * mult));
282
+ }
283
+
284
+ // A resolved model meta carries catalog-derived fields (contextWindow, pricing,
285
+ // capabilities, …). The lookupModelMeta() fallback for an unknown id is the
286
+ // bare shape `{ id, provider }`, so "more than id/provider" reliably tells a
287
+ // real catalog hit apart from that placeholder.
288
+ export function modelMetaLooksResolved(meta) {
289
+ if (!meta || typeof meta !== 'object') return false;
290
+ return Object.keys(meta).some((key) => key !== 'id' && key !== 'provider');
291
+ }
@@ -0,0 +1,232 @@
1
+ // config-lifecycle.mjs — config reload/save/adopt family + output-style status
2
+ // cache, extracted from mixdog-session-runtime.mjs. Dependency-injected factory:
3
+ // closes over config/searchRoute mutable state via getter/setter injection
4
+ // (getConfig/setConfig/getSearchRoute/setSearchRoute) and shared helpers, so the
5
+ // facade keeps ownership of the mutable locals while the debounce/adopt logic
6
+ // lives here.
7
+ //
8
+ // Debounce rationale (unchanged from the original inline implementation):
9
+ // persisting mixdog-config.json is heavy (cross-process lock, atomic
10
+ // temp+rename, win32 icacls owner-only ACL). Adopt in-memory IMMEDIATELY so
11
+ // same-tick readers see fresh state, and DEBOUNCE the disk write so a burst of
12
+ // toggles collapses into one persist. Three independent debounce channels:
13
+ // - config save (cfgMod.saveConfig, agent-section serialize)
14
+ // - backend save (channel-admin setBackend, file-locked RMW)
15
+ // - outputStyle (sharedCfgMod.updateConfig whole-root RMW — cfgMod.saveConfig
16
+ // only serializes agent-section fields, so a top-level
17
+ // outputStyle would never reach disk via that path)
18
+
19
+ export const CONFIG_SAVE_DEBOUNCE_MS = 150;
20
+
21
+ export function createConfigLifecycle({
22
+ // config mutable-state injection
23
+ getConfig,
24
+ setConfig,
25
+ getSearchRoute,
26
+ setSearchRoute,
27
+ getConfigHasSecrets,
28
+ setConfigHasSecrets,
29
+ getRoute,
30
+ // shared modules / helpers
31
+ cfgMod,
32
+ sharedCfgMod,
33
+ setBackend,
34
+ setConfiguredShell,
35
+ normalizeSystemShellConfig,
36
+ normalizeSearchRouteConfig,
37
+ outputStyleStatus,
38
+ LAZY_SECRET_PROVIDERS,
39
+ clean,
40
+ resolve,
41
+ performanceNow = () => performance.now(),
42
+ STANDALONE_DATA_DIR,
43
+ }) {
44
+ // --- output-style status cache (short TTL, keyed on plugin data dir) --------
45
+ let outputStyleStatusCache = null;
46
+ let outputStyleStatusCacheAt = 0;
47
+ let outputStyleStatusCacheDir = '';
48
+
49
+ const getOutputStyleStatusCached = ({ fresh = false } = {}) => {
50
+ const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
51
+ const cacheDir = resolve(dataDir);
52
+ const now = performanceNow();
53
+ if (
54
+ !fresh
55
+ && outputStyleStatusCache
56
+ && outputStyleStatusCacheDir === cacheDir
57
+ && now - outputStyleStatusCacheAt < 2500
58
+ ) {
59
+ return outputStyleStatusCache;
60
+ }
61
+ outputStyleStatusCache = outputStyleStatus(dataDir);
62
+ outputStyleStatusCacheAt = now;
63
+ outputStyleStatusCacheDir = cacheDir;
64
+ return outputStyleStatusCache;
65
+ };
66
+ const invalidateOutputStyleStatusCache = () => {
67
+ outputStyleStatusCache = null;
68
+ outputStyleStatusCacheAt = 0;
69
+ outputStyleStatusCacheDir = '';
70
+ };
71
+ // In-memory seed of the status cache after an outputStyle select (avoids a
72
+ // second forced-fresh filesystem scan during the debounce window).
73
+ const seedOutputStyleStatusCache = (status) => {
74
+ outputStyleStatusCache = status;
75
+ outputStyleStatusCacheAt = performanceNow();
76
+ outputStyleStatusCacheDir = resolve(cfgMod.getPluginData?.() || STANDALONE_DATA_DIR);
77
+ };
78
+
79
+ // --- config adopt -----------------------------------------------------------
80
+ function adoptConfig(nextConfig, { hasSecrets = getConfigHasSecrets() } = {}) {
81
+ setConfig(nextConfig);
82
+ setConfigHasSecrets(hasSecrets === true);
83
+ const config = getConfig();
84
+ setConfiguredShell(normalizeSystemShellConfig(config.shell).command);
85
+ setSearchRoute(
86
+ normalizeSearchRouteConfig(config.searchRoute)
87
+ || normalizeSearchRouteConfig(getSearchRoute()),
88
+ );
89
+ return config;
90
+ }
91
+
92
+ // --- debounced config save --------------------------------------------------
93
+ let pendingConfigToSave = null;
94
+ let configSaveTimer = null;
95
+
96
+ function flushConfigSave() {
97
+ if (configSaveTimer) {
98
+ clearTimeout(configSaveTimer);
99
+ configSaveTimer = null;
100
+ }
101
+ if (pendingConfigToSave === null) return;
102
+ const snapshot = pendingConfigToSave;
103
+ pendingConfigToSave = null;
104
+ try {
105
+ cfgMod.saveConfig(snapshot);
106
+ } catch (err) {
107
+ process.stderr.write(`[config] debounced saveConfig failed: ${err?.message || err}\n`);
108
+ }
109
+ }
110
+
111
+ function saveConfigAndAdopt(nextConfig, { hasSecrets = getConfigHasSecrets() } = {}) {
112
+ // In-memory adopt is synchronous and first so callers that read back the
113
+ // value immediately (e.g. setProfile -> getProfile) see the new state.
114
+ const adopted = adoptConfig(nextConfig, { hasSecrets });
115
+ // Persist the adopted object; coalesce rapid successive changes into one
116
+ // disk write after CONFIG_SAVE_DEBOUNCE_MS of quiet.
117
+ pendingConfigToSave = getConfig();
118
+ if (configSaveTimer) clearTimeout(configSaveTimer);
119
+ configSaveTimer = setTimeout(flushConfigSave, CONFIG_SAVE_DEBOUNCE_MS);
120
+ configSaveTimer.unref?.();
121
+ return adopted;
122
+ }
123
+
124
+ // --- debounced backend switch ----------------------------------------------
125
+ let pendingBackendName = null;
126
+ let backendSaveTimer = null;
127
+
128
+ function flushBackendSave() {
129
+ if (backendSaveTimer) {
130
+ clearTimeout(backendSaveTimer);
131
+ backendSaveTimer = null;
132
+ }
133
+ if (pendingBackendName === null) return;
134
+ const name = pendingBackendName;
135
+ pendingBackendName = null;
136
+ try {
137
+ setBackend(name);
138
+ } catch (err) {
139
+ process.stderr.write(`[channels] debounced setBackend failed: ${err?.message || err}\n`);
140
+ }
141
+ }
142
+
143
+ function scheduleBackendSave(name) {
144
+ pendingBackendName = name;
145
+ if (backendSaveTimer) clearTimeout(backendSaveTimer);
146
+ backendSaveTimer = setTimeout(flushBackendSave, CONFIG_SAVE_DEBOUNCE_MS);
147
+ backendSaveTimer.unref?.();
148
+ }
149
+
150
+ // --- debounced top-level outputStyle persist -------------------------------
151
+ let pendingOutputStyleId = null;
152
+ let outputStyleSaveTimer = null;
153
+
154
+ function flushOutputStyleSave() {
155
+ if (outputStyleSaveTimer) {
156
+ clearTimeout(outputStyleSaveTimer);
157
+ outputStyleSaveTimer = null;
158
+ }
159
+ if (pendingOutputStyleId === null) return;
160
+ const styleId = pendingOutputStyleId;
161
+ pendingOutputStyleId = null;
162
+ try {
163
+ sharedCfgMod.updateConfig((root) => {
164
+ const next = { ...(root || {}), outputStyle: styleId };
165
+ if (next.agent && typeof next.agent === 'object' && !Array.isArray(next.agent)) {
166
+ const agent = { ...next.agent };
167
+ delete agent.outputStyle;
168
+ next.agent = agent;
169
+ }
170
+ return next;
171
+ });
172
+ } catch (err) {
173
+ process.stderr.write(`[config] debounced outputStyle save failed: ${err?.message || err}\n`);
174
+ }
175
+ }
176
+
177
+ function scheduleOutputStyleSave(styleId) {
178
+ pendingOutputStyleId = styleId;
179
+ if (outputStyleSaveTimer) clearTimeout(outputStyleSaveTimer);
180
+ outputStyleSaveTimer = setTimeout(flushOutputStyleSave, CONFIG_SAVE_DEBOUNCE_MS);
181
+ outputStyleSaveTimer.unref?.();
182
+ }
183
+
184
+ // --- reload / ensure --------------------------------------------------------
185
+ function reloadFullConfig() {
186
+ // A pending debounced write holds the only copy of the latest change.
187
+ // Flush it before re-reading from disk so loadConfig() observes (and the
188
+ // subsequent adopt preserves) that change instead of reverting to a stale
189
+ // on-disk snapshot.
190
+ flushConfigSave();
191
+ return adoptConfig(cfgMod.loadConfig(), { hasSecrets: true });
192
+ }
193
+
194
+ function ensureFullConfig() {
195
+ if (getConfigHasSecrets()) return getConfig();
196
+ return reloadFullConfig();
197
+ }
198
+
199
+ function displayConfig() {
200
+ return getConfig();
201
+ }
202
+
203
+ function ensureConfigForRouteProvider() {
204
+ const config = getConfig();
205
+ const providerId = clean(getRoute().provider);
206
+ const providerCfg = config?.providers?.[providerId];
207
+ if (getConfigHasSecrets() || LAZY_SECRET_PROVIDERS.has(providerId) || providerCfg?.apiKey) {
208
+ return config;
209
+ }
210
+ return ensureFullConfig();
211
+ }
212
+
213
+ return {
214
+ // output-style cache
215
+ getOutputStyleStatusCached,
216
+ invalidateOutputStyleStatusCache,
217
+ seedOutputStyleStatusCache,
218
+ // adopt / save
219
+ adoptConfig,
220
+ saveConfigAndAdopt,
221
+ flushConfigSave,
222
+ flushBackendSave,
223
+ scheduleBackendSave,
224
+ flushOutputStyleSave,
225
+ scheduleOutputStyleSave,
226
+ // reload / ensure
227
+ reloadFullConfig,
228
+ ensureFullConfig,
229
+ displayConfig,
230
+ ensureConfigForRouteProvider,
231
+ };
232
+ }