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
@@ -30,7 +30,8 @@ const VALID_CAT = new Set([
30
30
  const SIM_RECALL = 0.65
31
31
  const CORE_DEDUP_TOP_K = 5
32
32
 
33
- export const CORE_SUMMARY_MAX = 120
33
+ export const CORE_SUMMARY_MAX = 100
34
+ export const CORE_ELEMENT_MAX = 40
34
35
 
35
36
  function trimOrNull(v) {
36
37
  if (v == null) return null
@@ -204,6 +205,9 @@ export async function addCore(dataDir, { element, summary, category }, projectId
204
205
  const el = trimOrNull(element)
205
206
  const sm = trimOrNull(summary) ?? el
206
207
  if (!el || !sm) throw new Error('add requires element and summary')
208
+ if (el.length > CORE_ELEMENT_MAX) {
209
+ throw new Error(`core element too long (${el.length} chars, max ${CORE_ELEMENT_MAX}) — element is a short key/title, not content.`)
210
+ }
207
211
  if (sm.length > CORE_SUMMARY_MAX) {
208
212
  throw new Error(`core summary too long (${sm.length} chars, max ${CORE_SUMMARY_MAX}) — core memory must be 1 fact in 1-2 sentences; procedures, multi-step, or code belong in recap or docs. Compress and retry.`)
209
213
  }
@@ -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
+ }