mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  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/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -144,10 +144,33 @@ export function createTranscriptWriter({ mixdogHome, sessionId, cwd, pid } = {})
144
144
  });
145
145
  }
146
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
+
147
169
  return {
148
170
  transcriptPath,
149
171
  sessionRecordPath,
150
172
  writeSessionRecord,
173
+ ensureTranscriptFile,
151
174
  refresh,
152
175
  appendAssistant,
153
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
  }
@@ -112,6 +112,79 @@ export function ensureProviderEnabled(config, provider) {
112
112
 
113
113
  const AUTO_CLEAR_DEFAULT_IDLE_MS = 60 * 60 * 1000;
114
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 follows the BP4
118
+ // messages-tail TTL (5m default cache_control window; BP1~3 stay on the 1h
119
+ // extended TTL and are prefix-shared, so reaping at 5m only forfeits an
120
+ // already-cold tail — see resolveLeadMessagesTtl). Gemini, xAI and Mistral
121
+ // caches run ~1h, Groq's implicit cache persists ~2h, OpenAI/DeepSeek
122
+ // key-prefix/implicit caches persist far longer (~24h), and OpenAI OAuth's
123
+ // in-memory server cache is short-lived (~5-10min), so we round up slightly
124
+ // to 10m to avoid sweeping a still-warm cache. Unknown/unrecognized
125
+ // providers fall back to the 'default' 1h entry.
126
+ export const AUTO_CLEAR_PROVIDER_IDLE_MS = Object.freeze({
127
+ 'anthropic': 5 * 60 * 1000,
128
+ 'anthropic-oauth': 5 * 60 * 1000,
129
+ 'gemini': 60 * 60 * 1000,
130
+ 'groq': 2 * 60 * 60 * 1000,
131
+ 'openai': 24 * 60 * 60 * 1000,
132
+ 'deepseek': 24 * 60 * 60 * 1000,
133
+ 'xai': 60 * 60 * 1000,
134
+ 'openai-oauth': 10 * 60 * 1000,
135
+ 'mistral': 60 * 60 * 1000,
136
+ 'default': AUTO_CLEAR_DEFAULT_IDLE_MS,
137
+ });
138
+ export const AUTO_CLEAR_PROVIDER_IDS = Object.freeze(Object.keys(AUTO_CLEAR_PROVIDER_IDLE_MS));
139
+ export const AUTO_CLEAR_UNKNOWN_PROVIDER_IDLE_MS = AUTO_CLEAR_PROVIDER_IDLE_MS.default;
140
+
141
+ export function normalizeAutoClearProviderIdleMs(value = {}) {
142
+ const raw = value && typeof value === 'object' ? value : {};
143
+ const out = {};
144
+ for (const [key, ms] of Object.entries(raw)) {
145
+ const provider = clean(key).toLowerCase();
146
+ const idleMs = Number(ms);
147
+ if (!provider || !Number.isFinite(idleMs) || idleMs <= 0) continue;
148
+ out[provider] = Math.max(60_000, Math.round(idleMs));
149
+ }
150
+ return out;
151
+ }
152
+
153
+ // Resolve the auto-clear idle-sweep threshold for a given provider id.
154
+ // Unknown/missing providers fall back to the 'default' 1h entry (above).
155
+ export function autoClearIdleMsForProvider(provider, providerIdleMs = null) {
156
+ const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
157
+ const key = clean(provider).toLowerCase();
158
+ if (overrides[key]) return overrides[key];
159
+ if (key && AUTO_CLEAR_PROVIDER_IDLE_MS[key]) return AUTO_CLEAR_PROVIDER_IDLE_MS[key];
160
+ if (overrides.default) return overrides.default;
161
+ return AUTO_CLEAR_PROVIDER_IDLE_MS[key] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
162
+ }
163
+
164
+ export function autoClearProviderDefaults(providerIdleMs = null) {
165
+ const overrides = normalizeAutoClearProviderIdleMs(providerIdleMs);
166
+ return AUTO_CLEAR_PROVIDER_IDS.map((provider) => {
167
+ const builtInMs = AUTO_CLEAR_PROVIDER_IDLE_MS[provider] ?? AUTO_CLEAR_PROVIDER_IDLE_MS.default;
168
+ const idleMs = overrides[provider] ?? builtInMs;
169
+ return {
170
+ provider,
171
+ idleMs,
172
+ builtInMs,
173
+ custom: overrides[provider] != null,
174
+ };
175
+ });
176
+ }
177
+
178
+ // Resolve the effective auto-clear idle window for a config + provider:
179
+ // an explicit user-set idleMs (config.autoClear.idleMs) always wins; else
180
+ // fall back to the provider's default; else the global 1h default.
181
+ export function resolveAutoClearIdleMs(config, provider) {
182
+ const raw = config?.autoClear && typeof config.autoClear === 'object' ? config.autoClear : {};
183
+ const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
184
+ if (Number.isFinite(idleMs) && idleMs > 0) return Math.max(60_000, Math.round(idleMs));
185
+ return autoClearIdleMsForProvider(provider, raw.providerIdleMs ?? raw.providerDefaults ?? raw.providers);
186
+ }
187
+
115
188
  export function normalizeSystemShellConfig(value = {}) {
116
189
  const raw = value && typeof value === 'object' ? value : {};
117
190
  const command = clean(raw.command ?? raw.path ?? raw.executable ?? raw.shell);
@@ -138,9 +211,16 @@ export function normalizeSystemShellCommand(value) {
138
211
  export function normalizeAutoClearConfig(value = {}) {
139
212
  const raw = value && typeof value === 'object' ? value : {};
140
213
  const idleMs = Number(raw.idleMs ?? raw.thresholdMs ?? raw.idleMillis);
214
+ const hasExplicitIdle = Number.isFinite(idleMs) && idleMs > 0;
215
+ // idleMs: null means "no explicit override" — callers resolve the
216
+ // effective window via resolveAutoClearIdleMs/provider default. `custom`
217
+ // tells UI whether the stored idleMs is a user override (true) or the
218
+ // provider-default mode (false, idleMs null).
141
219
  return {
142
220
  enabled: raw.enabled !== false,
143
- idleMs: Number.isFinite(idleMs) && idleMs > 0 ? Math.max(60_000, Math.round(idleMs)) : AUTO_CLEAR_DEFAULT_IDLE_MS,
221
+ idleMs: hasExplicitIdle ? Math.max(60_000, Math.round(idleMs)) : null,
222
+ custom: hasExplicitIdle,
223
+ providerIdleMs: normalizeAutoClearProviderIdleMs(raw.providerIdleMs ?? raw.providerDefaults ?? raw.providers),
144
224
  };
145
225
  }
146
226
 
@@ -155,7 +235,11 @@ export function normalizeCompactTypeSetting(value, fallback = 'semantic') {
155
235
  export function normalizeCompactionConfig(value = {}, { memoryEnabled = true } = {}) {
156
236
  const raw = value && typeof value === 'object' ? value : {};
157
237
  let compactType = normalizeCompactTypeSetting(raw.compactType ?? raw.compact_type ?? raw.type, 'semantic');
158
- if (compactType === 'recall-fasttrack' && memoryEnabled === false) compactType = 'semantic';
238
+ // Memory is now always-on, so recall-fasttrack no longer downgrades to
239
+ // semantic. `memoryEnabled` is retained as a param for API compatibility but
240
+ // is intentionally ignored (fasttrack drains run on-demand regardless of the
241
+ // recap/background-cycle toggle).
242
+ void memoryEnabled;
159
243
  return {
160
244
  ...raw,
161
245
  auto: raw.auto !== false && raw.enabled !== false,
@@ -170,6 +254,39 @@ export function moduleEnabled(configLike, name, fallback = true) {
170
254
  return fallback !== false;
171
255
  }
172
256
 
257
+ // Recap toggle: gates ONLY the background memory cycles (1/2/3). The memory
258
+ // module itself (transcript watcher/ingest, on-demand recall/fasttrack drains)
259
+ // is always-on. Default enabled. Reads the dedicated `recap` section; if a
260
+ // legacy `modules.memory === false` flag is present it is honored as recap off
261
+ // (migration folds it into `recap.enabled` on config load).
262
+ export function recapEnabled(configLike, fallback = true) {
263
+ const entry = configLike?.recap;
264
+ if (entry && typeof entry === 'object' && entry.enabled === false) return false;
265
+ // Legacy fallback: pre-migration configs may still carry modules.memory=false.
266
+ if (configLike?.modules?.memory === false) return false;
267
+ if (moduleEnabled(configLike, 'memory', true) === false) return false;
268
+ return fallback !== false;
269
+ }
270
+
271
+ export function setRecapEnabledInConfig(configLike, enabled) {
272
+ const next = { ...(configLike || {}) };
273
+ next.recap = { ...(next.recap || {}), enabled: enabled !== false };
274
+ // Drop the legacy flag if it was carrying the disabled state.
275
+ if (next.modules && next.modules.memory === false) {
276
+ const modules = { ...next.modules };
277
+ delete modules.memory;
278
+ next.modules = modules;
279
+ } else if (next.modules && next.modules.memory && typeof next.modules.memory === 'object') {
280
+ const modules = { ...next.modules };
281
+ const memoryMod = { ...modules.memory };
282
+ delete memoryMod.enabled;
283
+ if (Object.keys(memoryMod).length === 0) delete modules.memory;
284
+ else modules.memory = memoryMod;
285
+ next.modules = modules;
286
+ }
287
+ return next;
288
+ }
289
+
173
290
  export function setModuleEnabledInConfig(configLike, name, enabled) {
174
291
  const next = { ...(configLike || {}) };
175
292
  next.modules = { ...(next.modules || {}) };
@@ -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
+ }
@@ -0,0 +1,226 @@
1
+ // cwd-plugins.mjs — cwd resolution/apply + plugins-status + core-memory context,
2
+ // extracted from mixdog-session-runtime.mjs. Dependency-injected factory that
3
+ // closes over the facade's mutable cwd/config/session state via getter/setter
4
+ // injection (getCurrentCwd/setCurrentCwd/getConfig/getSession/...) plus the MCP
5
+ // glue + prewarm callbacks. The facade keeps ownership of the mutable locals;
6
+ // this module owns the pure logic that was previously inline.
7
+
8
+ export function createCwdPlugins({
9
+ // mutable-state injection
10
+ getCurrentCwd,
11
+ setCurrentCwd,
12
+ getConfig,
13
+ getSession,
14
+ getRoute,
15
+ getLastProjectMcpKey,
16
+ setLastProjectMcpKey,
17
+ isCodeGraphPrewarmLazy,
18
+ isCodeGraphFirstTurnPrewarmDone,
19
+ getCodeGraphPrewarmDelayMs,
20
+ setSessionNeedsCwdRefresh,
21
+ // callbacks / deps
22
+ connectConfiguredMcp,
23
+ invalidatePreSessionToolSurface,
24
+ scheduleCodeGraphPrewarm,
25
+ hooks,
26
+ hookCommonPayload,
27
+ bootProfile,
28
+ getMemoryModule,
29
+ // channel-admin / registry helpers
30
+ listRegisteredPlugins,
31
+ pluginAdminStatus,
32
+ pluginManifest,
33
+ pluginMcpServerName,
34
+ mcpScriptForPlugin,
35
+ countSkillFiles,
36
+ readProjectMcpServers,
37
+ writeLastSessionCwd,
38
+ // shared utils
39
+ clean,
40
+ resolve,
41
+ statSync,
42
+ existsSync,
43
+ cfgMod,
44
+ STANDALONE_DATA_DIR,
45
+ }) {
46
+ function resolveCwdPath(value) {
47
+ const raw = clean(value);
48
+ if (!raw) throw new Error('cwd: path is required for action=set');
49
+ const next = resolve(getCurrentCwd() || process.cwd(), raw);
50
+ const stat = statSync(next);
51
+ if (!stat.isDirectory()) throw new Error(`cwd: not a directory: ${next}`);
52
+ return next;
53
+ }
54
+
55
+ function applyResolvedCwd(nextCwd, { markRefresh = true } = {}) {
56
+ const resolved = resolve(nextCwd);
57
+ const stat = statSync(resolved);
58
+ if (!stat.isDirectory()) throw new Error(`cwd: not a directory: ${resolved}`);
59
+ const changed = resolve(getCurrentCwd()) !== resolved;
60
+ setCurrentCwd(resolved);
61
+ const currentCwd = resolved;
62
+ process.env.MIXDOG_SESSION_CWD = currentCwd;
63
+ writeLastSessionCwd(currentCwd);
64
+ const session = getSession();
65
+ if (session) session.cwd = currentCwd;
66
+ // cwd changes NEVER recreate the session: a mid-conversation cwd switch must
67
+ // preserve the full message history (and the BP1–BP3 prompt cache). We only
68
+ // retarget the live session's cwd in place; tool execution already reads the
69
+ // current cwd per turn. `cwd` is intentionally absent from the prompt
70
+ // context (see composeSystemPrompt), so there is nothing prompt-side to
71
+ // refresh either. `markRefresh`/`changed` are kept only for signature
72
+ // compatibility with existing callers.
73
+ void markRefresh;
74
+ // Lazy mode: before the first turn (e.g. the initial project-selection
75
+ // cwd set), do NOT prewarm — that is exactly the post-first-frame freeze
76
+ // we are avoiding. Once a turn has run, an in-session cwd switch DOES
77
+ // prewarm the new dir, since a lookup there is now likely.
78
+ if (isCodeGraphPrewarmLazy() && !isCodeGraphFirstTurnPrewarmDone()) {
79
+ bootProfile('code-graph:prewarm-lazy', { reason: 'cwd-deferred-to-first-turn' });
80
+ } else {
81
+ const delay = getCodeGraphPrewarmDelayMs();
82
+ scheduleCodeGraphPrewarm(changed ? 0 : delay, changed ? 'cwd-change' : 'cwd');
83
+ }
84
+ // Project-local `.mcp.json` follows the cwd: when the effective project MCP
85
+ // set changes, reconnect in the background (never await — this stays sync,
86
+ // and the session is preserved). Guarded so a no-op cwd change does not
87
+ // churn connections.
88
+ if (changed) {
89
+ try {
90
+ const nextKey = resolved + '\u0000' + JSON.stringify(readProjectMcpServers(resolved));
91
+ if (nextKey !== getLastProjectMcpKey()) {
92
+ setLastProjectMcpKey(nextKey);
93
+ void connectConfiguredMcp({ reset: true })
94
+ .then(() => invalidatePreSessionToolSurface())
95
+ .catch(() => {});
96
+ }
97
+ } catch {}
98
+ }
99
+ // CwdChanged: bridge an effective cwd switch to the standard hook bus.
100
+ // No matcher event — payload is minimal { cwd }. Fire-and-forget.
101
+ if (changed) {
102
+ try { void hooks.dispatch('CwdChanged', hookCommonPayload({ cwd: currentCwd })); } catch {}
103
+ }
104
+ return currentCwd;
105
+ }
106
+
107
+ async function refreshSessionForCwdIfNeeded(reason = 'cwd-change') {
108
+ // No-op: cwd changes are applied in place by applyResolvedCwd and never
109
+ // tear down the session. Retained as a stable hook for ask()'s pre-turn
110
+ // call so the surrounding turn flow is unchanged.
111
+ void reason;
112
+ setSessionNeedsCwdRefresh(false);
113
+ return getSession();
114
+ }
115
+
116
+ function pluginsStatus() {
117
+ const config = getConfig();
118
+ const dataDir = cfgMod.getPluginData?.();
119
+ const configuredMcp = config?.mcpServers && typeof config.mcpServers === 'object'
120
+ ? config.mcpServers
121
+ : {};
122
+ const plugins = [];
123
+ const addRegisteredPlugin = (entry) => {
124
+ const root = clean(entry.root);
125
+ if (!root || !existsSync(root)) return;
126
+ const manifest = pluginManifest(root);
127
+ const name = clean(manifest.name) || clean(manifest.id) || clean(entry.name) || root.split(/[\\/]/).pop() || root;
128
+ const plugin = {
129
+ id: clean(entry.id) || name,
130
+ name,
131
+ title: clean(manifest.title) || clean(manifest.displayName) || clean(entry.title) || name,
132
+ version: clean(manifest.version) || clean(entry.version) || null,
133
+ description: clean(manifest.description) || clean(entry.description),
134
+ marketplace: null,
135
+ source: clean(entry.sourceType) === 'local' ? 'local' : 'registry',
136
+ sourceUrl: clean(entry.source),
137
+ sourceType: clean(entry.sourceType) || 'git',
138
+ managed: entry.managed !== false,
139
+ root,
140
+ installedAt: entry.installedAt || null,
141
+ updatedAt: entry.updatedAt || null,
142
+ skillCount: countSkillFiles(root),
143
+ mcpScript: mcpScriptForPlugin(root),
144
+ };
145
+ plugin.mcpServerName = pluginMcpServerName(plugin);
146
+ plugin.mcpEnabled = Object.prototype.hasOwnProperty.call(configuredMcp, plugin.mcpServerName)
147
+ || Object.keys(configuredMcp).some((k) => k.startsWith(`${plugin.mcpServerName}--`));
148
+ plugins.push(plugin);
149
+ };
150
+
151
+ for (const entry of listRegisteredPlugins({ dataDir })) addRegisteredPlugin(entry);
152
+
153
+ plugins.sort((a, b) => {
154
+ if (a.source !== b.source) return a.source.localeCompare(b.source);
155
+ return a.name.localeCompare(b.name);
156
+ });
157
+ const admin = pluginAdminStatus({ dataDir });
158
+ return {
159
+ count: plugins.length,
160
+ plugins,
161
+ roots: {
162
+ registry: admin.registryPath,
163
+ installed: admin.installRoot,
164
+ },
165
+ };
166
+ }
167
+
168
+ function formatCoreMemoryLines(payload = {}) {
169
+ const seen = new Set();
170
+ const lines = [];
171
+ for (const value of [
172
+ ...(Array.isArray(payload.userLines) ? payload.userLines : []),
173
+ ...(Array.isArray(payload.dbLines) ? payload.dbLines : []),
174
+ ]) {
175
+ const text = clean(value).replace(/\s+/g, ' ');
176
+ if (!text) continue;
177
+ const key = text.toLowerCase();
178
+ if (seen.has(key)) continue;
179
+ seen.add(key);
180
+ lines.push(`- ${text}`);
181
+ if (lines.length >= 40) break;
182
+ }
183
+ const out = lines.join('\n');
184
+ const maxChars = 6000;
185
+ return out.length > maxChars ? `${out.slice(0, maxChars).replace(/\s+\S*$/, '')}\n- ...` : out;
186
+ }
187
+
188
+ async function loadCoreMemoryContext() {
189
+ // Boot should not pay for memory/PG startup unless explicitly requested.
190
+ // Recall and memory tools still initialize the memory service on first use.
191
+ if (process.env.MIXDOG_BOOT_CORE_MEMORY !== '1') {
192
+ bootProfile('core-memory:skipped');
193
+ return '';
194
+ }
195
+ const startedAt = performance.now();
196
+ let timer = null;
197
+ const timeout = new Promise((resolveTimeout) => {
198
+ timer = setTimeout(() => resolveTimeout(''), 2000);
199
+ timer.unref?.();
200
+ });
201
+ try {
202
+ return await Promise.race([
203
+ (async () => {
204
+ const memoryMod = await getMemoryModule();
205
+ if (typeof memoryMod?.buildSessionCoreMemoryPayload !== 'function') return '';
206
+ return formatCoreMemoryLines(await memoryMod.buildSessionCoreMemoryPayload(getCurrentCwd()));
207
+ })(),
208
+ timeout,
209
+ ]);
210
+ } catch {
211
+ return '';
212
+ } finally {
213
+ if (timer) clearTimeout(timer);
214
+ bootProfile('core-memory:done', { ms: (performance.now() - startedAt).toFixed(1) });
215
+ }
216
+ }
217
+
218
+ return {
219
+ resolveCwdPath,
220
+ applyResolvedCwd,
221
+ refreshSessionForCwdIfNeeded,
222
+ pluginsStatus,
223
+ formatCoreMemoryLines,
224
+ loadCoreMemoryContext,
225
+ };
226
+ }