mixdog 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +7 -3
  2. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  3. package/scripts/bench/lead-review-tasks.json +20 -0
  4. package/scripts/bench/r4-mixed-tasks.json +20 -0
  5. package/scripts/bench/review-tasks.json +20 -0
  6. package/scripts/bench/round-codex.json +114 -0
  7. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  8. package/scripts/bench/round-mixdog-lead.json +269 -0
  9. package/scripts/bench/round-mixdog.json +126 -0
  10. package/scripts/bench/round-r10-bigsample.json +679 -0
  11. package/scripts/bench/round-r11-codexalign.json +257 -0
  12. package/scripts/bench/round-r4-codex.json +114 -0
  13. package/scripts/bench/round-r4-mixed.json +225 -0
  14. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  15. package/scripts/bench/round-r6-codex.json +114 -0
  16. package/scripts/bench/round-r6-solo.json +257 -0
  17. package/scripts/bench/round-r7-full.json +254 -0
  18. package/scripts/bench/round-r8-fulldefault.json +255 -0
  19. package/scripts/bench-run.mjs +215 -29
  20. package/scripts/freevar-smoke.mjs +95 -0
  21. package/scripts/internal-comms-bench.mjs +1 -0
  22. package/scripts/internal-comms-smoke.mjs +10 -9
  23. package/scripts/mouse-probe.mjs +45 -0
  24. package/scripts/output-style-bench.mjs +13 -6
  25. package/scripts/output-style-smoke.mjs +4 -4
  26. package/scripts/provider-toolcall-test.mjs +7 -3
  27. package/scripts/recall-usecase-cases.json +18 -0
  28. package/scripts/recall-usecase-probe.json +6 -0
  29. package/scripts/session-bench.mjs +152 -6
  30. package/scripts/tool-smoke.mjs +23 -63
  31. package/scripts/tui-render-smoke.mjs +90 -0
  32. package/scripts/webhook-smoke.mjs +208 -0
  33. package/src/agents/debugger/AGENT.md +4 -1
  34. package/src/agents/heavy-worker/AGENT.md +6 -5
  35. package/src/agents/maintainer/AGENT.md +4 -0
  36. package/src/agents/reviewer/AGENT.md +2 -1
  37. package/src/agents/worker/AGENT.md +8 -4
  38. package/src/lib/rules-builder.cjs +4 -0
  39. package/src/mixdog-session-runtime.mjs +632 -2042
  40. package/src/output-styles/default.md +34 -9
  41. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  42. package/src/output-styles/minimal.md +4 -1
  43. package/src/output-styles/simple.md +22 -7
  44. package/src/rules/agent/00-common.md +2 -0
  45. package/src/rules/lead/lead-brief.md +12 -0
  46. package/src/rules/lead/lead-tool.md +0 -11
  47. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  48. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  49. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  50. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  51. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  52. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  53. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  54. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  55. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  60. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  61. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  62. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  63. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  64. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  65. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  66. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  67. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  68. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
  77. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  78. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  79. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  80. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  82. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  83. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  84. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  85. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  86. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  87. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  88. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  89. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  90. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  91. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  93. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  94. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  95. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  97. package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
  98. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  99. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  100. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  101. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  102. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  103. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  104. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  105. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  106. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  109. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
  110. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  115. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  118. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  119. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  120. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  125. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  126. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  128. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  130. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  131. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  132. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  133. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  134. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  136. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  137. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  138. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  139. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  140. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  141. package/src/runtime/channels/backends/discord.mjs +12 -292
  142. package/src/runtime/channels/index.mjs +229 -663
  143. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  144. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  145. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  146. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  147. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  148. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  149. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  150. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  151. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  152. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  153. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  154. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  155. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  156. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  157. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  158. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  159. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  160. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  161. package/src/runtime/channels/lib/webhook.mjs +36 -570
  162. package/src/runtime/channels/tool-defs.mjs +11 -130
  163. package/src/runtime/memory/index.mjs +201 -1948
  164. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  165. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  166. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  167. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  168. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  169. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  170. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  171. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  172. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  173. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  174. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  175. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  177. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  178. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  179. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  180. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  181. package/src/runtime/memory/tool-defs.mjs +5 -13
  182. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  183. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  184. package/src/runtime/search/lib/web-tools.mjs +24 -602
  185. package/src/runtime/shared/atomic-file.mjs +26 -1
  186. package/src/runtime/shared/launcher-control.mjs +2 -2
  187. package/src/runtime/shared/tool-primitives.mjs +308 -0
  188. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  189. package/src/runtime/shared/tool-surface.mjs +80 -898
  190. package/src/runtime/shared/transcript-writer.mjs +23 -0
  191. package/src/runtime/shared/update-checker.mjs +7 -4
  192. package/src/session-runtime/config-helpers.mjs +84 -2
  193. package/src/session-runtime/config-lifecycle.mjs +232 -0
  194. package/src/session-runtime/cwd-plugins.mjs +226 -0
  195. package/src/session-runtime/mcp-glue.mjs +177 -0
  196. package/src/session-runtime/model-recency.mjs +111 -0
  197. package/src/session-runtime/native-search.mjs +247 -0
  198. package/src/session-runtime/output-styles.mjs +11 -9
  199. package/src/session-runtime/prewarm.mjs +142 -0
  200. package/src/session-runtime/provider-models.mjs +278 -0
  201. package/src/session-runtime/provider-usage.mjs +120 -0
  202. package/src/session-runtime/quick-model-rows.mjs +170 -0
  203. package/src/session-runtime/quick-search-models.mjs +46 -0
  204. package/src/session-runtime/session-hooks.mjs +93 -0
  205. package/src/session-runtime/settings-api.mjs +319 -0
  206. package/src/session-runtime/tool-catalog.mjs +29 -29
  207. package/src/session-runtime/tool-defs.mjs +84 -0
  208. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  209. package/src/standalone/agent-tool/helpers.mjs +237 -0
  210. package/src/standalone/agent-tool/notify.mjs +107 -0
  211. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  212. package/src/standalone/agent-tool/render.mjs +152 -0
  213. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  214. package/src/standalone/agent-tool.mjs +110 -671
  215. package/src/standalone/channel-worker.mjs +4 -7
  216. package/src/standalone/explore-tool.mjs +30 -9
  217. package/src/standalone/hook-bus/config.mjs +207 -0
  218. package/src/standalone/hook-bus/constants.mjs +90 -0
  219. package/src/standalone/hook-bus/handlers.mjs +481 -0
  220. package/src/standalone/hook-bus/payload.mjs +31 -0
  221. package/src/standalone/hook-bus/rules.mjs +77 -0
  222. package/src/standalone/hook-bus.mjs +77 -870
  223. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  224. package/src/standalone/opencode-go-login.mjs +5 -1
  225. package/src/standalone/provider-admin.mjs +1 -16
  226. package/src/standalone/usage-dashboard.mjs +3 -1
  227. package/src/tui/App.jsx +945 -8094
  228. package/src/tui/app/app-format.mjs +206 -0
  229. package/src/tui/app/channel-pickers.mjs +510 -0
  230. package/src/tui/app/clipboard.mjs +67 -0
  231. package/src/tui/app/core-memory-picker.mjs +210 -0
  232. package/src/tui/app/extension-pickers.mjs +506 -0
  233. package/src/tui/app/input-parsers.mjs +193 -0
  234. package/src/tui/app/maintenance-pickers.mjs +324 -0
  235. package/src/tui/app/model-options.mjs +330 -0
  236. package/src/tui/app/model-picker.mjs +365 -0
  237. package/src/tui/app/onboarding-steps.mjs +400 -0
  238. package/src/tui/app/project-picker.mjs +247 -0
  239. package/src/tui/app/provider-setup-picker.mjs +580 -0
  240. package/src/tui/app/resume-picker.mjs +55 -0
  241. package/src/tui/app/route-pickers.mjs +419 -0
  242. package/src/tui/app/settings-picker.mjs +490 -0
  243. package/src/tui/app/slash-commands.mjs +101 -0
  244. package/src/tui/app/slash-dispatch.mjs +427 -0
  245. package/src/tui/app/text-layout.mjs +46 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  247. package/src/tui/app/transcript-window.mjs +671 -0
  248. package/src/tui/app/use-mouse-input.mjs +460 -0
  249. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  250. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  251. package/src/tui/app/use-transcript-window.mjs +589 -0
  252. package/src/tui/components/ConfirmBar.jsx +1 -1
  253. package/src/tui/components/Picker.jsx +32 -4
  254. package/src/tui/components/PromptInput.jsx +23 -101
  255. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  256. package/src/tui/components/StatusLine.jsx +63 -12
  257. package/src/tui/components/TextEntryPanel.jsx +11 -0
  258. package/src/tui/components/ToolExecution.jsx +52 -594
  259. package/src/tui/components/TranscriptItem.jsx +105 -0
  260. package/src/tui/components/UsagePanel.jsx +18 -4
  261. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  262. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  263. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  264. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  265. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  266. package/src/tui/display-width.mjs +20 -3
  267. package/src/tui/dist/index.mjs +19652 -18630
  268. package/src/tui/engine/agent-job-feed.mjs +133 -0
  269. package/src/tui/engine/notification-plan.mjs +76 -0
  270. package/src/tui/engine/render-timing.mjs +17 -0
  271. package/src/tui/engine/tool-approval.mjs +94 -0
  272. package/src/tui/engine/tool-card-results.mjs +234 -0
  273. package/src/tui/engine/tool-result-status.mjs +135 -0
  274. package/src/tui/engine.mjs +122 -562
  275. package/src/tui/figures.mjs +5 -0
  276. package/src/tui/index.jsx +105 -0
  277. package/src/tui/input-editing.mjs +2 -2
  278. package/src/tui/markdown/format-token.mjs +4 -1
  279. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  280. package/src/tui/theme.mjs +6 -0
  281. package/src/ui/statusline-agents.mjs +213 -0
  282. package/src/ui/statusline-format.mjs +146 -0
  283. package/src/ui/statusline-segments.mjs +148 -0
  284. package/src/ui/statusline.mjs +67 -501
  285. package/src/ui/tool-card.mjs +0 -1
  286. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  287. package/src/workflows/default/WORKFLOW.md +1 -1
  288. package/src/workflows/sequential/WORKFLOW.md +1 -1
  289. package/vendor/ink/build/display-width.js +19 -3
  290. package/vendor/ink/build/ink.js +103 -6
  291. package/vendor/ink/build/log-update.js +17 -3
  292. package/vendor/ink/build/wrap-text.js +125 -0
  293. package/scripts/_test-folder-dialog.mjs +0 -30
  294. package/scripts/fix-brief-fn.mjs +0 -35
  295. package/scripts/fix-format-tool-surface.mjs +0 -24
  296. package/scripts/fix-tool-exec-visible.mjs +0 -42
  297. package/scripts/patch-agent-brief.mjs +0 -48
  298. package/scripts/patch-app.mjs +0 -21
  299. package/scripts/patch-app2.mjs +0 -18
  300. package/scripts/patch-dist-brief.mjs +0 -96
  301. package/scripts/patch-tool-exec.mjs +0 -70
  302. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  303. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  304. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  305. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  306. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  307. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  308. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  309. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  310. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  311. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  312. package/src/standalone/agent-task-status.test.mjs +0 -76
  313. package/src/tui/components/tool-output-format.test.mjs +0 -399
  314. package/src/tui/display-width.test.mjs +0 -35
  315. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  316. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  317. package/src/tui/input-editing.selection.test.mjs +0 -75
  318. package/src/tui/markdown/format-token.test.mjs +0 -354
  319. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  320. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  321. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  322. package/src/tui/paste-fix.test.mjs +0 -119
  323. package/src/tui/prompt-history-store.test.mjs +0 -52
  324. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  325. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  326. package/src/ui/markdown.test.mjs +0 -70
  327. package/src/ui/statusline-context-label.test.mjs +0 -15
  328. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  329. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -0,0 +1,58 @@
1
+ // Cycle LLM dispatch adapters, extracted from index.mjs (pass 3).
2
+ //
3
+ // Each cycleN scheduler/handler routes its LLM work through an in-process
4
+ // makeAgentDispatch() adapter (the memory daemon runs the provider registry
5
+ // locally, so the dead IPC agent path is never used). The factory built per
6
+ // cycle is fixed (role/taskType), so it is memoized in a single-slot closure
7
+ // and reshaped to the cycle call signature:
8
+ // callLlm({ role, taskType, mode, preset, timeout, cwd }, userMessage) -> string
9
+ // while makeAgentDispatch's function takes a single { prompt } object. The
10
+ // adapter maps the two — preset/cwd resolution is handled inside
11
+ // makeAgentDispatch via role.
12
+ //
13
+ // Pure factory: index.mjs injects makeAgentDispatch. No import-time side
14
+ // effects, no db/config coupling.
15
+ export function createCycleLlmAdapters({ makeAgentDispatch }) {
16
+ function buildAdapter(agent) {
17
+ let dispatch = null
18
+ return async (opts = {}, userMessage) => {
19
+ if (!dispatch) {
20
+ dispatch = makeAgentDispatch({
21
+ agent,
22
+ taskType: 'maintenance',
23
+ sourceType: 'memory-cycle',
24
+ // The cycle agents parse the full raw line-format response; the
25
+ // agent brief cap (12KB) would truncate a large valid response and
26
+ // append prose, causing partial parsing / omitted / invalid chunks.
27
+ // Opt out so the no-truncation contract is preserved.
28
+ brief: false,
29
+ })
30
+ }
31
+ // Preserve the cycle timeout contract: the caller derives opts.timeout
32
+ // from config / caller deadline and expects it to bound the call.
33
+ // makeAgentDispatch takes it as a per-call idleTimeoutMs (stale
34
+ // watchdog). Map it through; omit when absent/0 so agent defaults apply.
35
+ const callTimeout = Number(opts?.timeout)
36
+ return dispatch({
37
+ prompt: String(userMessage ?? ''),
38
+ preset: opts?.preset || undefined,
39
+ ...(Number.isFinite(callTimeout) && callTimeout > 0 ? { idleTimeoutMs: callTimeout } : {}),
40
+ })
41
+ }
42
+ }
43
+
44
+ // Callers (cycle-scheduler `callLlm: getCycle1CallLlm()`, index.mjs bench)
45
+ // treat these as FACTORIES: call once to obtain the callLlm adapter.
46
+ // Returning the adapter directly here made `getCycle1CallLlm()` execute an
47
+ // LLM dispatch with no arguments — empty prompt → agent-dispatch throw →
48
+ // unhandled rejection that killed the memory daemon. Keep the factory
49
+ // contract: each getter returns the (memoized) adapter function.
50
+ const cycle1 = buildAdapter('cycle1-agent')
51
+ const cycle2 = buildAdapter('cycle2-agent')
52
+ const cycle3 = buildAdapter('cycle3-agent')
53
+ return {
54
+ getCycle1CallLlm: () => cycle1,
55
+ getCycle2CallLlm: () => cycle2,
56
+ getCycle3CallLlm: () => cycle3,
57
+ }
58
+ }
@@ -0,0 +1,497 @@
1
+ // Background cycle scheduling cluster, extracted from index.mjs (pass 3).
2
+ //
3
+ // This owns the mutually-referential cycle machinery that pass-2 flagged as
4
+ // entangled: cycle-health ledger (_cycleHealth), the run-state file, the
5
+ // cycle1 outer coalesce layer (_startCycle1Run/_awaitCycle1Run), the scheduled
6
+ // enqueue/retry paths for cycle1/2/3, checkCycles(), and the self-rescheduling
7
+ // tick loop. index.mjs keeps lifecycle ownership by injecting live getters
8
+ // (getDb/getConfig/setConfig) plus the cycle runners and LLM adapters.
9
+ //
10
+ // Factory contract — everything the extracted functions closed over in the
11
+ // facade is passed in:
12
+ // getDb() -> live db handle (null before _initStore)
13
+ // getConfig() -> live mainConfig
14
+ // setConfig(cfg) -> checkCycles re-reads config each tick (poll-on-use)
15
+ // dataDir -> DATA_DIR
16
+ // log -> __mixdogMemoryLog
17
+ // getCycleLastRun / setCycleLastRun -> meta-backed cycle timestamps
18
+ // readMainConfig / memoryCyclesEnabled -> config-flag helpers
19
+ // getCycle{1,2,3}CallLlm -> in-process LLM adapters
20
+ // runCycle1 / runCycle2 / runCycle3 / parseInterval / flushRawEmbeddings
21
+ // getInFlightCycle1 -> inner cycle1 guard handle (rebuild drain)
22
+ // claimAndMarkScheduledCycle / resolveCoalesceMaxRetries /
23
+ // scheduleCoalescedCycleRetry -> coalesced queue primitives
24
+ // scheduledCycle{1,2,3}Signature -> queue signatures
25
+ // cycleStateFile -> path to memory-cycle-state.json
26
+ import fs from 'node:fs'
27
+
28
+ const CYCLE1_HEALTH_OVERDUE_MS = 5 * 60_000
29
+ const CYCLE1_AUTO_RESTART_COOLDOWN_MS = 5 * 60_000
30
+ const BACKLOG_WARN_COOLDOWN_MS = 10 * 60_000
31
+ const BACKLOG_WARN_PENDING = 500
32
+ const BACKLOG_WARN_FAILURES = 5
33
+
34
+ export function createCycleScheduler(deps) {
35
+ const {
36
+ getDb,
37
+ getConfig,
38
+ setConfig,
39
+ dataDir,
40
+ log = () => {},
41
+ getCycleLastRun,
42
+ setCycleLastRun,
43
+ readMainConfig,
44
+ memoryCyclesEnabled,
45
+ getCycle1CallLlm,
46
+ getCycle2CallLlm,
47
+ getCycle3CallLlm,
48
+ runCycle1,
49
+ runCycle2,
50
+ runCycle3,
51
+ parseInterval,
52
+ flushRawEmbeddings,
53
+ getInFlightCycle1,
54
+ claimAndMarkScheduledCycle,
55
+ resolveCoalesceMaxRetries,
56
+ scheduleCoalescedCycleRetry,
57
+ scheduledCycle1Signature,
58
+ scheduledCycle2Signature,
59
+ scheduledCycle3Signature,
60
+ cycleStateFile,
61
+ } = deps
62
+
63
+ // ── Cycle health state ────────────────────────────────────────────────────
64
+ const _cycleHealth = {
65
+ cycle1: { last_success_at: 0, last_error_at: 0, last_error: null, consecutive_failures: 0 },
66
+ cycle2: { last_success_at: 0, last_error_at: 0, last_error: null, consecutive_failures: 0 },
67
+ cycle3: { last_success_at: 0, last_error_at: 0, last_error: null, consecutive_failures: 0 },
68
+ }
69
+ let _cycleRunning = null // { cycle, started_at }
70
+ let _cycleBacklogSnapshot = { unchunked: 0, cycle2_pending: 0, at: 0 }
71
+ let _lastBacklogWarnAt = 0
72
+
73
+ // ── Cycle1 outer coalesce layer + tick loop state ─────────────────────────
74
+ let _cycle1InFlight = null
75
+ let _cycle2InFlight = false
76
+ let _cycle3InFlight = false
77
+ let _rawEmbedFlushInFlight = false
78
+ let _checkCyclesInFlight = false
79
+ let _cyclesActive = false
80
+ let _cycleInterval = null
81
+ let _startupTimeout = null
82
+
83
+ function _writeCycleStateFile() {
84
+ try {
85
+ fs.writeFileSync(cycleStateFile, JSON.stringify({
86
+ running: _cycleRunning,
87
+ backlog: _cycleBacklogSnapshot,
88
+ cycles: _cycleHealth,
89
+ updatedAt: Date.now(),
90
+ }))
91
+ } catch { /* best-effort; statusline just shows nothing */ }
92
+ }
93
+
94
+ function markCycleRunning(cycle) {
95
+ _cycleRunning = { cycle, started_at: Date.now() }
96
+ _writeCycleStateFile()
97
+ }
98
+
99
+ function markCycleDone(cycle, ok, err = null) {
100
+ const h = _cycleHealth[cycle]
101
+ if (h) {
102
+ const now = Date.now()
103
+ if (ok) { h.last_success_at = now; h.consecutive_failures = 0; h.last_error = null }
104
+ else { h.last_error_at = now; h.consecutive_failures += 1; h.last_error = String(err || 'unknown').slice(0, 200) }
105
+ if (!ok && h.consecutive_failures >= BACKLOG_WARN_FAILURES) {
106
+ _warnCycleHealth(`${cycle} failing repeatedly (consecutive=${h.consecutive_failures}, last="${h.last_error}")`)
107
+ }
108
+ }
109
+ if (_cycleRunning?.cycle === cycle) _cycleRunning = null
110
+ _writeCycleStateFile()
111
+ }
112
+
113
+ function _warnCycleHealth(msg) {
114
+ const now = Date.now()
115
+ if (now - _lastBacklogWarnAt < BACKLOG_WARN_COOLDOWN_MS) return
116
+ _lastBacklogWarnAt = now
117
+ log(`[cycle-health] WARN ${msg}\n`)
118
+ }
119
+
120
+ async function recordCycle1Result(result) {
121
+ const now = Date.now()
122
+ await setCycleLastRun('cycle1_heartbeat', now)
123
+ const skipped = result?.skippedInFlight === true
124
+ const coalescedNoop = result?.coalescedRetryNoop === true
125
+ const allFailed = !skipped
126
+ && Number(result?.chunks ?? 0) === 0
127
+ && Number(result?.processed ?? 0) === 0
128
+ && Number(result?.skipped ?? 0) > 0
129
+ if (!skipped && !coalescedNoop && !allFailed) {
130
+ await setCycleLastRun('cycle1', now)
131
+ }
132
+ if (!skipped && !coalescedNoop) markCycleDone('cycle1', !allFailed, allFailed ? 'all rows skipped' : null)
133
+ }
134
+
135
+ function _startCycle1Run(config = {}, options = {}) {
136
+ if (typeof options?.callLlm !== 'function') {
137
+ options = { ...options, callLlm: getCycle1CallLlm() }
138
+ }
139
+ markCycleRunning('cycle1')
140
+ _cycle1InFlight = (async () => {
141
+ try {
142
+ const result = await runCycle1(getDb(), config, options, dataDir)
143
+ if (typeof options?.onCoalescedSuccess !== 'function') {
144
+ await recordCycle1Result(result)
145
+ }
146
+ return result
147
+ } catch (err) {
148
+ markCycleDone('cycle1', false, err?.message || err)
149
+ throw err
150
+ } finally {
151
+ if (_cycleRunning?.cycle === 'cycle1') { _cycleRunning = null; _writeCycleStateFile() }
152
+ if (_cycle1InFlight === promise) _cycle1InFlight = null
153
+ }
154
+ })()
155
+ const promise = _cycle1InFlight
156
+ return _cycle1InFlight
157
+ }
158
+
159
+ async function _awaitCycle1Run(config = {}, options = {}) {
160
+ const target = _cycle1InFlight || _startCycle1Run(config, options)
161
+ const callerDeadlineMs = Number(options.callerDeadlineMs) || 0
162
+ if (callerDeadlineMs <= 0) return await target
163
+ let timer
164
+ const deadlinePromise = new Promise((resolve) => {
165
+ timer = setTimeout(() => {
166
+ resolve({
167
+ processed: 0,
168
+ chunks: 0,
169
+ skipped: 0,
170
+ sessions: 0,
171
+ skippedInFlight: true,
172
+ timedOutWaiting: true,
173
+ callerDeadlineMs,
174
+ })
175
+ }, callerDeadlineMs)
176
+ })
177
+ try {
178
+ return await Promise.race([target, deadlinePromise])
179
+ } finally {
180
+ clearTimeout(timer)
181
+ }
182
+ }
183
+
184
+ function periodicCycle1Config() {
185
+ return {
186
+ min_batch: 20,
187
+ session_cap: 2,
188
+ batch_size: 50,
189
+ concurrency: 2,
190
+ ...(getConfig()?.cycle1 || {}),
191
+ }
192
+ }
193
+
194
+ async function enqueueScheduledCycle(kind, intervalMs, signature) {
195
+ const claim = await claimAndMarkScheduledCycle(getDb(), kind, intervalMs, signature, { reason: 'scheduled' })
196
+ return claim.claimed === true
197
+ }
198
+
199
+ async function enqueueScheduledCycle1(intervalMs, _reason = 'scheduled') {
200
+ const config = periodicCycle1Config()
201
+ const signature = scheduledCycle1Signature(config)
202
+ if (await enqueueScheduledCycle('cycle1', intervalMs, signature)) {
203
+ scheduleScheduledCycle1(config, signature)
204
+ }
205
+ }
206
+
207
+ async function enqueueScheduledCycle2(intervalMs, _reason = 'scheduled') {
208
+ const config = getConfig()?.cycle2 || {}
209
+ const signature = scheduledCycle2Signature(config)
210
+ if (await enqueueScheduledCycle('cycle2', intervalMs, signature)) {
211
+ scheduleScheduledCycle2(config, signature)
212
+ }
213
+ }
214
+
215
+ async function enqueueScheduledCycle3(intervalMs, _reason = 'scheduled') {
216
+ const config = getConfig() || {}
217
+ const signature = scheduledCycle3Signature(config)
218
+ if (await enqueueScheduledCycle('cycle3', intervalMs, signature)) {
219
+ scheduleScheduledCycle3(config, signature)
220
+ }
221
+ }
222
+
223
+ function scheduleScheduledCycle1(config, signature, attempt = 0) {
224
+ const maxRetries = resolveCoalesceMaxRetries(config, 3)
225
+ if (attempt > maxRetries) {
226
+ log('[cycle1] scheduled queue retry cap reached\n')
227
+ return
228
+ }
229
+ scheduleCoalescedCycleRetry(getDb(), 'cycle1', async () => {
230
+ if (!memoryCyclesEnabled()) return
231
+ if (_cycle1InFlight) {
232
+ scheduleScheduledCycle1(config, signature, attempt + 1)
233
+ return
234
+ }
235
+ const result = await _awaitCycle1Run(config, {
236
+ coalescedRetry: true,
237
+ onCoalescedSuccess: recordCycle1Result,
238
+ })
239
+ if (result?.skippedInFlight) scheduleScheduledCycle1(config, signature, attempt + 1)
240
+ }, config, signature)
241
+ }
242
+
243
+ function scheduleScheduledCycle2(config, signature, attempt = 0) {
244
+ const maxRetries = resolveCoalesceMaxRetries(config, 3)
245
+ if (attempt > maxRetries) {
246
+ log('[cycle2] scheduled queue retry cap reached\n')
247
+ return
248
+ }
249
+ scheduleCoalescedCycleRetry(getDb(), 'cycle2', async () => {
250
+ if (!memoryCyclesEnabled()) return
251
+ if (_cycle2InFlight) {
252
+ scheduleScheduledCycle2(config, signature, attempt + 1)
253
+ return
254
+ }
255
+ _cycle2InFlight = true
256
+ markCycleRunning('cycle2')
257
+ try {
258
+ let c2Options = {
259
+ coalescedRetry: true,
260
+ onCoalescedSuccess: _finalizeCycle2Run,
261
+ }
262
+ if (typeof c2Options?.callLlm !== 'function') {
263
+ c2Options = { ...c2Options, callLlm: getCycle2CallLlm() }
264
+ }
265
+ const result = await runCycle2(getDb(), config, c2Options, dataDir)
266
+ if (result?.skippedInFlight) {
267
+ scheduleScheduledCycle2(config, signature, attempt + 1)
268
+ } else if (result?.coalescedRetryNoop) {
269
+ log('[cycle2] scheduled queue noop\n')
270
+ } else if (result?.ok === false) {
271
+ await _finalizeCycle2Run(result)
272
+ }
273
+ } catch (err) {
274
+ log(`[cycle2] scheduled queue failed: ${err?.message || err}\n`)
275
+ markCycleDone('cycle2', false, err?.message || err)
276
+ } finally {
277
+ _cycle2InFlight = false
278
+ if (_cycleRunning?.cycle === 'cycle2') { _cycleRunning = null; _writeCycleStateFile() }
279
+ }
280
+ }, config, signature)
281
+ }
282
+
283
+ function scheduleScheduledCycle3(config, signature, attempt = 0) {
284
+ const retryConfig = config?.cycle3 || config
285
+ const maxRetries = resolveCoalesceMaxRetries(retryConfig, 3)
286
+ if (attempt > maxRetries) {
287
+ log('[cycle3] scheduled queue retry cap reached\n')
288
+ return
289
+ }
290
+ scheduleCoalescedCycleRetry(getDb(), 'cycle3', async () => {
291
+ if (!memoryCyclesEnabled()) return
292
+ if (_cycle3InFlight) {
293
+ scheduleScheduledCycle3(config, signature, attempt + 1)
294
+ return
295
+ }
296
+ _cycle3InFlight = true
297
+ markCycleRunning('cycle3')
298
+ try {
299
+ let c3Options = {
300
+ coalescedRetry: true,
301
+ onCoalescedSuccess: async () => { await setCycleLastRun('cycle3', Date.now()); markCycleDone('cycle3', true) },
302
+ }
303
+ if (typeof c3Options?.callLlm !== 'function') {
304
+ c3Options = { ...c3Options, callLlm: getCycle3CallLlm() }
305
+ }
306
+ const result = await runCycle3(getDb(), config, dataDir, c3Options)
307
+ if (result?.skippedInFlight) {
308
+ scheduleScheduledCycle3(config, signature, attempt + 1)
309
+ } else if (result?.coalescedRetryNoop) {
310
+ log('[cycle3] scheduled queue noop\n')
311
+ }
312
+ } catch (err) {
313
+ log(`[cycle3] scheduled queue failed: ${err?.message || err}\n`)
314
+ markCycleDone('cycle3', false, err?.message || err)
315
+ } finally {
316
+ _cycle3InFlight = false
317
+ if (_cycleRunning?.cycle === 'cycle3') { _cycleRunning = null; _writeCycleStateFile() }
318
+ }
319
+ }, retryConfig, signature)
320
+ }
321
+
322
+ async function _finalizeCycle2Run(result) {
323
+ if (result?.skippedInFlight) {
324
+ log('[cycle2] skipped: in flight\n')
325
+ return
326
+ }
327
+ if (result.ok) {
328
+ await setCycleLastRun('cycle2', Date.now())
329
+ await setCycleLastRun('cycle2_last_error', '')
330
+ log('[cycle2] completed\n')
331
+ markCycleDone('cycle2', true)
332
+ } else {
333
+ await setCycleLastRun('cycle2_last_error', result.error || 'unknown error')
334
+ log(`[cycle2] failed: ${result.error}\n`)
335
+ markCycleDone('cycle2', false, result.error || 'unknown error')
336
+ }
337
+ }
338
+
339
+ async function checkCycles() {
340
+ const mainConfig = readMainConfig()
341
+ setConfig(mainConfig)
342
+ const cyclesOn = memoryCyclesEnabled()
343
+ const db = getDb()
344
+
345
+ const cycle1Ms = parseInterval(mainConfig?.cycle1?.interval || '10m')
346
+ const cycle2Ms = parseInterval(mainConfig?.cycle2?.interval || '1h')
347
+ const cycle3Ms = parseInterval(mainConfig?.cycle3?.interval || '24h')
348
+
349
+ const now = Date.now()
350
+ const last = await getCycleLastRun()
351
+
352
+ if (cyclesOn) {
353
+ const cycle1OverdueMs = last.cycle1 > 0
354
+ ? Math.max(0, now - last.cycle1 - cycle1Ms)
355
+ : 0
356
+ if (cycle1OverdueMs > CYCLE1_HEALTH_OVERDUE_MS) {
357
+ const lastSeen = last.cycle1 ? new Date(last.cycle1).toISOString() : 'never'
358
+ log(
359
+ `[cycle1] overdue by ${Math.floor(cycle1OverdueMs / 60_000)}min `
360
+ + `(last=${lastSeen}). Pool B Anthropic shard may be cold.\n`
361
+ )
362
+ const lastAutoRestart = last.cycle1_autoRestart || 0
363
+ if (now - lastAutoRestart >= CYCLE1_AUTO_RESTART_COOLDOWN_MS) {
364
+ await setCycleLastRun('cycle1_autoRestart_attempt', now)
365
+ try {
366
+ const result = await _awaitCycle1Run(periodicCycle1Config())
367
+ await setCycleLastRun('cycle1_autoRestart', Date.now())
368
+ log(
369
+ `[cycle1] auto-restart completed chunks=${result?.chunks ?? 0} processed=${result?.processed ?? 0}\n`
370
+ )
371
+ return
372
+ } catch (e) {
373
+ log(`[cycle1] auto-restart error: ${e.message}\n`)
374
+ return
375
+ }
376
+ }
377
+ }
378
+
379
+ if (now - last.cycle1 >= cycle1Ms) {
380
+ await enqueueScheduledCycle1(cycle1Ms, 'scheduled')
381
+ }
382
+
383
+ if (now - last.cycle2 >= cycle2Ms) {
384
+ await enqueueScheduledCycle2(cycle2Ms, 'scheduled')
385
+ }
386
+
387
+ if (now - last.cycle3 >= cycle3Ms) {
388
+ await enqueueScheduledCycle3(cycle3Ms, 'scheduled')
389
+ }
390
+ }
391
+
392
+ try {
393
+ const unchunked = Number((await db.query(
394
+ `SELECT COUNT(*) c FROM entries WHERE chunk_root IS NULL AND NULLIF(btrim(session_id), '') IS NOT NULL`,
395
+ )).rows[0]?.c ?? 0)
396
+ const cycle2Pending = Number((await db.query(
397
+ `SELECT COUNT(*) c FROM entries WHERE is_root = 1 AND status = 'pending'`,
398
+ )).rows[0]?.c ?? 0)
399
+ _cycleBacklogSnapshot = { unchunked, cycle2_pending: cycle2Pending, at: now }
400
+ _writeCycleStateFile()
401
+ if (unchunked > BACKLOG_WARN_PENDING || cycle2Pending > BACKLOG_WARN_PENDING) {
402
+ _warnCycleHealth(`backlog unchunked=${unchunked} cycle2_pending=${cycle2Pending}`)
403
+ }
404
+ if (unchunked > 0 && !_rawEmbedFlushInFlight) {
405
+ _rawEmbedFlushInFlight = true
406
+ flushRawEmbeddings(db, { limit: 200 })
407
+ .then((r) => {
408
+ if (r.attempted > 0) log(`[embed] raw fallback flush attempted=${r.attempted} embedded=${r.embedded}\n`)
409
+ })
410
+ .catch((err) => log(`[embed] raw fallback flush failed: ${err?.message || err}\n`))
411
+ .finally(() => { _rawEmbedFlushInFlight = false })
412
+ }
413
+ const SELF_KICK_STALE_MS = 2 * 3600_000
414
+ const c1Stale = (_cycleHealth.cycle1.last_success_at || last.cycle1 || 0) < now - SELF_KICK_STALE_MS
415
+ const c2Stale = (_cycleHealth.cycle2.last_success_at || last.cycle2 || 0) < now - SELF_KICK_STALE_MS
416
+ if (cyclesOn && c1Stale && unchunked > BACKLOG_WARN_PENDING) {
417
+ _warnCycleHealth(`cycle1 self-kick: no success 2h+, unchunked=${unchunked}`)
418
+ await enqueueScheduledCycle1(0, 'self-kick')
419
+ } else if (cyclesOn && c2Stale && cycle2Pending > BACKLOG_WARN_PENDING) {
420
+ _warnCycleHealth(`cycle2 self-kick: no success 2h+, pending=${cycle2Pending}`)
421
+ await enqueueScheduledCycle2(0, 'self-kick')
422
+ }
423
+ } catch { /* counts are best-effort; never fail the tick */ }
424
+ }
425
+
426
+ async function _runCheckCyclesGuarded() {
427
+ if (_checkCyclesInFlight) return
428
+ _checkCyclesInFlight = true
429
+ try { await checkCycles() }
430
+ catch (e) { log(`[cycle-tick] error: ${e.message}\n`) }
431
+ finally { _checkCyclesInFlight = false }
432
+ }
433
+
434
+ function _scheduleNextCheck() {
435
+ _cycleInterval = setTimeout(async () => {
436
+ _cycleInterval = null
437
+ try {
438
+ await _runCheckCyclesGuarded()
439
+ } catch (e) {
440
+ log(`[cycle-tick] re-arm guard caught: ${e?.message || e}\n`)
441
+ } finally {
442
+ if (_cyclesActive) _scheduleNextCheck()
443
+ }
444
+ }, 60_000)
445
+ }
446
+
447
+ function startCycles() {
448
+ if (_cyclesActive) return
449
+ _cyclesActive = true
450
+ // Boot reset: a previous daemon that crashed mid-run leaves the state
451
+ // file's `running` marker set, and the statusline keeps showing a
452
+ // phantom "Memory cycle running" spinner until its 10-minute stale
453
+ // guard kicks in. No cycle can be running when this scheduler starts,
454
+ // so clear the marker (health/backlog reset to this process's state).
455
+ _cycleRunning = null
456
+ _writeCycleStateFile()
457
+ _scheduleNextCheck()
458
+ _startupTimeout = setTimeout(() => { void _runCheckCyclesGuarded() }, 30_000)
459
+ }
460
+
461
+ function stopCycles() {
462
+ _cyclesActive = false
463
+ if (_cycleInterval) { clearTimeout(_cycleInterval); _cycleInterval = null }
464
+ if (_startupTimeout) { clearTimeout(_startupTimeout); _startupTimeout = null }
465
+ }
466
+
467
+ // Full-shutdown reset — baseline stop() cleared these module-level flags so
468
+ // a later init() starts from a clean slate instead of coalescing onto (or
469
+ // skipping behind) pre-stop in-flight work / stale running state.
470
+ function resetInFlight() {
471
+ _cycle1InFlight = null
472
+ _cycle2InFlight = false
473
+ _cycle3InFlight = false
474
+ _checkCyclesInFlight = false
475
+ _rawEmbedFlushInFlight = false
476
+ _cycleRunning = null
477
+ }
478
+
479
+ return {
480
+ // health/state accessors (HTTP /health, statusline)
481
+ getCycleHealth: () => _cycleHealth,
482
+ getCycleRunning: () => _cycleRunning,
483
+ getCycleBacklogSnapshot: () => _cycleBacklogSnapshot,
484
+ // cycle1 in-flight handle (rebuild drain in index.mjs)
485
+ getCycle1InFlight: () => _cycle1InFlight,
486
+ // run primitives used by MCP action handlers
487
+ startCycle1Run: _startCycle1Run,
488
+ awaitCycle1Run: _awaitCycle1Run,
489
+ finalizeCycle2Run: _finalizeCycle2Run,
490
+ periodicCycle1Config,
491
+ // lifecycle
492
+ startCycles,
493
+ stopCycles,
494
+ resetInFlight,
495
+ checkCycles,
496
+ }
497
+ }
@@ -0,0 +1,58 @@
1
+ // Boot-edge embedding warmup queue, extracted from index.mjs (pass 2).
2
+ //
3
+ // ONNX session creation on the embedding worker thread is CPU-heavy, so it must
4
+ // not overlap the worker's own init (DB open, schema, cycle wiring). The warmup
5
+ // is queued during _initStore and fired at the _initRuntime completion edge, so
6
+ // it starts the instant boot's CPU-heavy work is done — no magic-number delay.
7
+ //
8
+ // This is a small pure factory: index.mjs owns the DATA_DIR-derived meta path
9
+ // and the embedding-provider imports, injecting them here as callbacks. No
10
+ // import-time side effects.
11
+ export function createEmbeddingWarmup({
12
+ canStart,
13
+ warmup,
14
+ getDims,
15
+ persistMeta,
16
+ log = () => {},
17
+ }) {
18
+ let _pending = null
19
+
20
+ // Queue the warmup; fireDeferred() runs it once boot completes. Re-queuing
21
+ // overwrites any prior pending closure (matches the original single-slot
22
+ // module var). MIXDOG_EMBED_WARMUP=0 (checked via canStart) disables it.
23
+ function schedule(metaPath, metaKey) {
24
+ if (!canStart()) {
25
+ // Silent skip here previously made "warmup never ran" boots
26
+ // indistinguishable from failed warmups. One line, boot-edge only.
27
+ log('[memory-service] embedding warmup skipped (disabled by env/secondary mode) — recall stays lexical until first embed\n')
28
+ return
29
+ }
30
+ _pending = () => {
31
+ warmup()
32
+ .then(() => {
33
+ const measured = Number(getDims())
34
+ try {
35
+ persistMeta(metaPath, { ...metaKey, dims: measured })
36
+ } catch (e) {
37
+ log(`[memory-service] could not persist embedding-meta: ${e?.message || e}\n`)
38
+ }
39
+ })
40
+ .catch(err => {
41
+ log(`[memory-service] background warmup failed: ${err?.message || err}\n`)
42
+ })
43
+ }
44
+ }
45
+
46
+ function fireDeferred() {
47
+ const fire = _pending
48
+ if (!fire) return
49
+ _pending = null
50
+ fire()
51
+ }
52
+
53
+ function reset() {
54
+ _pending = null
55
+ }
56
+
57
+ return { schedule, fireDeferred, reset }
58
+ }