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
@@ -73,6 +73,23 @@ export function parsePeriod(period, hasQuery) {
73
73
  endMs: Date.parse(rangeMatch[2] + 'T23:59:59.999'),
74
74
  }
75
75
  }
76
+ // Time-of-day windows: 'HH:MM~HH:MM' (today) or 'YYYY-MM-DD HH:MM~HH:MM'
77
+ // (specific day). Covers "this afternoon 12:00-14:00" style recall that the
78
+ // day-granular date/range forms cannot express. End is inclusive to the
79
+ // minute (:59.999). An end at or before start returns null (invalid window)
80
+ // rather than guessing an overnight wrap.
81
+ const todMatch = period.match(/^(?:(\d{4}-\d{2}-\d{2})[ T])?(\d{1,2}):(\d{2})~(\d{1,2}):(\d{2})$/)
82
+ if (todMatch) {
83
+ const [, day, h1, m1, h2, m2] = todMatch
84
+ const base = day ? new Date(day + 'T00:00:00') : new Date()
85
+ if (Number.isNaN(base.getTime())) return null
86
+ const sh = Number(h1), sm = Number(m1), eh = Number(h2), em = Number(m2)
87
+ if (sh > 23 || eh > 23 || sm > 59 || em > 59) return null
88
+ const start = new Date(base); start.setHours(sh, sm, 0, 0)
89
+ const end = new Date(base); end.setHours(eh, em, 59, 999)
90
+ if (end.getTime() <= start.getTime()) return null
91
+ return { startMs: start.getTime(), endMs: end.getTime(), exact: true }
92
+ }
76
93
  const dateMatch = period.match(/^(\d{4}-\d{2}-\d{2})$/)
77
94
  if (dateMatch) {
78
95
  return {
@@ -181,3 +198,41 @@ export function renderEntryLines(rows) {
181
198
  if (_capped) lines.push(`[recall truncated — showing first ${RECALL_LINE_CAP} lines; narrow the query (limit/period/projectScope) for the rest]`)
182
199
  return lines.join('\n')
183
200
  }
201
+
202
+ // Compact session label for group headers: keep short ids verbatim, shorten
203
+ // long ones to a recognizable tail (ids are typically unique in the suffix —
204
+ // timestamp/counter — not the prefix).
205
+ function shortSessionLabel(sid) {
206
+ const s = String(sid || '').trim()
207
+ if (s.length <= 20) return s
208
+ return `…${s.slice(-16)}`
209
+ }
210
+
211
+ // Session-grouped rendering for the GLOBAL query-less browse ("what did we
212
+ // work on recently") when the window spans multiple sessions. A flat ts-desc
213
+ // list interleaves sessions into one indistinguishable stream and lets one
214
+ // chatty session crowd the page; grouping keeps each work-unit readable.
215
+ // Group order = newest activity first (rows arrive ts-desc, first-seen wins).
216
+ // The caller's own session (currentSessionId hint) is marked "(current)".
217
+ // Single-session (or session-less) result sets fall through to the flat
218
+ // renderer — no headers when grouping adds nothing.
219
+ export function renderSessionGroupedLines(rows, { currentSessionId } = {}) {
220
+ if (!rows || rows.length === 0) return '(no results)'
221
+ const groups = new Map()
222
+ for (const r of rows) {
223
+ const sid = String(r?.session_id || '').trim()
224
+ const key = sid || '(no session)'
225
+ if (!groups.has(key)) groups.set(key, [])
226
+ groups.get(key).push(r)
227
+ }
228
+ if (groups.size <= 1) return renderEntryLines(rows)
229
+ const current = String(currentSessionId || '').trim()
230
+ const parts = []
231
+ for (const [sid, groupRows] of groups) {
232
+ const mark = current && sid === current ? ' (current)' : ''
233
+ const label = sid === '(no session)' ? sid : `session ${shortSessionLabel(sid)}`
234
+ parts.push(`## ${label}${mark}`)
235
+ parts.push(renderEntryLines(groupRows))
236
+ }
237
+ return parts.join('\n')
238
+ }
@@ -75,13 +75,18 @@ function isUsableAsset(asset) {
75
75
  // ---------------------------------------------------------------------------
76
76
 
77
77
  async function loadManifest(dataDir) {
78
+ // Bundled manifest FIRST: it always matches the installed mixdog version.
79
+ // A cached <dataDir>/runtime/manifest.json from an older install must never
80
+ // shadow it — stale caches caused sha256 mismatches on already-installed
81
+ // machines after an upgrade. The cache is only a fallback for the rare case
82
+ // where the bundled file is missing (e.g. a stripped checkout).
83
+ if (existsSync(BUNDLED_MANIFEST_PATH)) {
84
+ return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
85
+ }
78
86
  const runtimeManifestPath = join(dataDir, 'runtime', 'manifest.json')
79
87
  if (existsSync(runtimeManifestPath)) {
80
88
  try { return JSON.parse(readFileSync(runtimeManifestPath, 'utf8')) } catch {}
81
89
  }
82
- if (existsSync(BUNDLED_MANIFEST_PATH)) {
83
- return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
84
- }
85
90
  const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) })
86
91
  if (!res.ok) throw new Error(`[runtime-fetcher] manifest fetch failed: ${res.status} ${res.statusText}`)
87
92
  const manifest = await res.json()
@@ -0,0 +1,425 @@
1
+ // Transcript ingest cluster, extracted from index.mjs (pass 2).
2
+ //
3
+ // Owns: transcript offset load/persist, JSONL tail ingestion, cwd extraction,
4
+ // timestamp coercion, and the cross-platform transcript watcher. Everything
5
+ // that touched module-level state in index.mjs (db handle, mainConfig-derived
6
+ // data dir, the _transcriptOffsets / _ingestTranscriptTails maps, the persist
7
+ // tail promise) is now closed over inside createTranscriptIngest via injected
8
+ // accessors, so index.mjs keeps ownership of the live db/config lifecycle and
9
+ // this module stays a pure factory with no import-time side effects.
10
+ import fs from 'node:fs'
11
+ import path from 'node:path'
12
+
13
+ // Pure: coerce a transcript timestamp (seconds, ms, or ISO string) to ms.
14
+ export function parseTsToMs(value) {
15
+ if (typeof value === 'number' && Number.isFinite(value)) return value < 1e12 ? value * 1000 : value
16
+ const parsed = Date.parse(String(value))
17
+ return Number.isFinite(parsed) ? parsed : Date.now()
18
+ }
19
+
20
+ // Pure: extract cwd from the transcript file's JSONL rows. Mixdog embeds the
21
+ // session cwd as a top-level `cwd` field on every message row, so scanning the
22
+ // first few lines is reliable on all platforms without slug-decoding ambiguity.
23
+ // Returns undefined when no cwd is found or the extracted path does not exist
24
+ // on disk (falls back to COMMON).
25
+ export function cwdFromTranscriptPath(fp) {
26
+ let fd
27
+ try {
28
+ fd = fs.openSync(fp, 'r')
29
+ const buf = Buffer.alloc(Math.min(fs.fstatSync(fd).size, 100 * 1024))
30
+ fs.readSync(fd, buf, 0, buf.length, 0)
31
+ fs.closeSync(fd)
32
+ fd = undefined
33
+ const lines = buf.toString('utf8').split('\n')
34
+ for (let i = 0; i < Math.min(lines.length, 5); i++) {
35
+ const line = lines[i].trim()
36
+ if (!line) continue
37
+ try {
38
+ const obj = JSON.parse(line)
39
+ if (typeof obj.cwd === 'string' && obj.cwd) {
40
+ const candidate = obj.cwd
41
+ try { if (fs.statSync(candidate).isDirectory()) return candidate } catch {}
42
+ }
43
+ } catch {}
44
+ }
45
+ } catch {} finally {
46
+ if (fd != null) { try { fs.closeSync(fd) } catch {} }
47
+ }
48
+ return undefined
49
+ }
50
+
51
+ // Factory. All live-state coupling is injected:
52
+ // getDb() -> current pg-shim db handle (or null pre-init)
53
+ // loadMeta() -> Promise<string> raw transcript-offsets meta value
54
+ // persistMeta(json) -> Promise<void> writes serialized offsets to meta
55
+ // projectsRoot() -> string, mixdogHome()/projects
56
+ // resolveProjectId(cwd) -> project id | null
57
+ // firstTextContent / cleanMemoryText -> shared ingest text helpers
58
+ // log(msg) -> stderr logger
59
+ //
60
+ // Returns the same surface index.mjs previously exposed as module functions,
61
+ // plus resetOffsets() for stop()'s teardown.
62
+ export function createTranscriptIngest({
63
+ getDb,
64
+ loadMeta,
65
+ persistMeta,
66
+ projectsRoot,
67
+ resolveProjectId,
68
+ firstTextContent,
69
+ cleanMemoryText,
70
+ log = () => {},
71
+ }) {
72
+ let _transcriptOffsets = new Map()
73
+ /** @type {Map<string, Promise<unknown>>} */
74
+ const _ingestTranscriptTails = new Map()
75
+ let _transcriptOffsetsPersistTail = Promise.resolve()
76
+
77
+ async function loadTranscriptOffsets() {
78
+ try {
79
+ const raw = await loadMeta()
80
+ const obj = JSON.parse(raw)
81
+ _transcriptOffsets = new Map(Object.entries(obj))
82
+ } catch {
83
+ _transcriptOffsets = new Map()
84
+ }
85
+ }
86
+
87
+ async function persistTranscriptOffsets() {
88
+ const run = _transcriptOffsetsPersistTail.catch(() => {}).then(async () => {
89
+ try {
90
+ const obj = Object.fromEntries(_transcriptOffsets)
91
+ await persistMeta(JSON.stringify(obj))
92
+ } catch (e) {
93
+ log(`[memory] persist transcript offsets failed: ${e.message}\n`)
94
+ }
95
+ })
96
+ _transcriptOffsetsPersistTail = run.catch(() => {})
97
+ return run
98
+ }
99
+
100
+ function runTranscriptIngestSerialized(transcriptPath, fn) {
101
+ const key = path.resolve(transcriptPath)
102
+ const prev = _ingestTranscriptTails.get(key) ?? Promise.resolve()
103
+ const run = prev.catch(() => {}).then(fn)
104
+ _ingestTranscriptTails.set(key, run.catch(() => {}))
105
+ return run
106
+ }
107
+
108
+ function snapshotTranscriptOffset(transcriptPath) {
109
+ const stored = _transcriptOffsets.get(transcriptPath)
110
+ if (!stored) return { bytes: 0, lineIndex: 0 }
111
+ return { bytes: Number(stored.bytes) || 0, lineIndex: Number(stored.lineIndex) || 0 }
112
+ }
113
+
114
+ async function ingestTranscriptFileImpl(transcriptPath, { cwd } = {}) {
115
+ const db = getDb()
116
+ let stat
117
+ try { stat = await fs.promises.stat(transcriptPath) } catch { return 0 }
118
+ const sessionUuid = path.basename(transcriptPath, '.jsonl')
119
+ const prev = snapshotTranscriptOffset(transcriptPath)
120
+ if (stat.size < prev.bytes) {
121
+ prev.bytes = 0
122
+ prev.lineIndex = 0
123
+ }
124
+ if (stat.size <= prev.bytes) return 0
125
+
126
+ const fh = await fs.promises.open(transcriptPath, 'r')
127
+ const buf = Buffer.alloc(stat.size - prev.bytes)
128
+ try {
129
+ await fh.read(buf, 0, buf.length, prev.bytes)
130
+ } finally {
131
+ await fh.close()
132
+ }
133
+ const text = buf.toString('utf8')
134
+
135
+ const resolvedCwd = typeof cwd === 'string' && cwd ? cwd : cwdFromTranscriptPath(transcriptPath)
136
+ // No cwd resolved -> classify as COMMON (project_id NULL). Falling back to
137
+ // process.cwd() would misclassify rows under the service/plugin cwd.
138
+ const projectId = resolvedCwd ? resolveProjectId(resolvedCwd) : null
139
+
140
+ let count = 0
141
+ let index = prev.lineIndex
142
+ // Track the byte boundary of the LAST line we fully consumed (parsed +
143
+ // either inserted or intentionally skipped). On parse failure or
144
+ // transient insert error we stop and leave the boundary untouched so the
145
+ // next sweep retries from the same position. This prevents malformed
146
+ // trailing JSONL (mid-write partial lines) and DB hiccups from being
147
+ // silently consumed forever.
148
+ let lastGoodBytes = prev.bytes
149
+ let lastGoodLineIndex = prev.lineIndex
150
+ let cursor = 0
151
+ while (cursor < text.length) {
152
+ const nl = text.indexOf('\n', cursor)
153
+ // No trailing newline -> partial line still being written; stop here
154
+ // without advancing so the rest is re-read once the writer flushes.
155
+ if (nl === -1) break
156
+ const rawLine = text.slice(cursor, nl)
157
+ const consumedBytes = Buffer.byteLength(rawLine, 'utf8') + 1
158
+ cursor = nl + 1
159
+ const line = rawLine.replace(/\r$/, '')
160
+ if (!line) {
161
+ lastGoodBytes += consumedBytes
162
+ continue
163
+ }
164
+ index += 1
165
+ let parsed
166
+ try { parsed = JSON.parse(line) } catch {
167
+ // Malformed line: do not advance past it; retry on next sweep.
168
+ index -= 1
169
+ break
170
+ }
171
+ // Transcript lines carry the role either as message.role (legacy) or
172
+ // as the top-level `type` field ({"type":"assistant","message":{...}}
173
+ // — the current session-runtime writer). Reading only message.role
174
+ // silently skipped EVERY line of current-format transcripts, so the
175
+ // background watcher ingested 0 rows forever.
176
+ const role = parsed.message?.role
177
+ ?? ((parsed.type === 'user' || parsed.type === 'assistant') ? parsed.type : undefined)
178
+ if (role !== 'user' && role !== 'assistant') {
179
+ lastGoodBytes += consumedBytes
180
+ lastGoodLineIndex = index
181
+ continue
182
+ }
183
+ const content = firstTextContent(parsed.message?.content)
184
+ if (!content || !content.trim()) {
185
+ lastGoodBytes += consumedBytes
186
+ lastGoodLineIndex = index
187
+ continue
188
+ }
189
+ const cleaned = cleanMemoryText(content)
190
+ if (!cleaned) {
191
+ lastGoodBytes += consumedBytes
192
+ lastGoodLineIndex = index
193
+ continue
194
+ }
195
+ const tsMs = parseTsToMs(parsed.timestamp ?? parsed.ts ?? Date.now())
196
+ const sourceRef = `transcript:${sessionUuid}#${index}`
197
+ try {
198
+ const result = await db.query(
199
+ `INSERT INTO entries(ts, role, content, source_ref, session_id, source_turn, project_id)
200
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
201
+ ON CONFLICT DO NOTHING`,
202
+ [tsMs, role, cleaned, sourceRef, sessionUuid, index, projectId]
203
+ )
204
+ if (Number(result.rowCount ?? result.affectedRows ?? 0) > 0) count += 1
205
+ lastGoodBytes += consumedBytes
206
+ lastGoodLineIndex = index
207
+ } catch (e) {
208
+ log(`[transcript-watch] insert error (${sourceRef}): ${e.message}\n`)
209
+ // Transient insert failure: leave the boundary before this line so
210
+ // the next sweep retries it. Roll back the line counter too.
211
+ index -= 1
212
+ break
213
+ }
214
+ }
215
+ _transcriptOffsets.set(transcriptPath, {
216
+ bytes: lastGoodBytes,
217
+ lineIndex: lastGoodLineIndex,
218
+ })
219
+ await persistTranscriptOffsets()
220
+ return count
221
+ }
222
+
223
+ async function ingestTranscriptFile(transcriptPath, options = {}) {
224
+ return runTranscriptIngestSerialized(transcriptPath, () => ingestTranscriptFileImpl(transcriptPath, options))
225
+ }
226
+
227
+ function getOffset(fp) {
228
+ return _transcriptOffsets.get(fp)
229
+ }
230
+
231
+ function resetOffsets() {
232
+ _transcriptOffsets = new Map()
233
+ }
234
+
235
+ function initTranscriptWatcher() {
236
+ const root = projectsRoot()
237
+ const SAFETY_POLL_MS = 5 * 60_000
238
+ const DEBOUNCE_MS = 500
239
+ const watchedFiles = new Map()
240
+ const pendingByFile = new Map()
241
+ const watchers = []
242
+ const intervals = []
243
+ const polledFiles = new Set()
244
+ let safetySweepTimeout = null
245
+
246
+ function isWatchable(relOrBase) {
247
+ const base = path.basename(relOrBase)
248
+ if (!base.endsWith('.jsonl') || base.startsWith('agent-')) return false
249
+ if (relOrBase.includes('tmp') || relOrBase.includes('cache') || relOrBase.includes('plugins')) return false
250
+ return true
251
+ }
252
+
253
+ async function ingestOne(fp) {
254
+ try {
255
+ if (!fs.existsSync(fp)) return
256
+ const stat = fs.statSync(fp)
257
+ const mtime = stat.mtimeMs
258
+ const prev = watchedFiles.get(fp)
259
+ if (prev && prev >= mtime) return
260
+ const n = await ingestTranscriptFile(fp, { cwd: cwdFromTranscriptPath(fp) })
261
+ // Only mark this mtime as 'consumed' once the persisted offset has
262
+ // fully advanced past the observed file size. On a transient insert
263
+ // error (or a malformed trailing line) ingestTranscriptFile leaves
264
+ // the persisted offset before the failed line for retry; caching
265
+ // the new mtime unconditionally would suppress the next sweep until
266
+ // the file mutated again, losing the retry. Leave the cache
267
+ // untouched on partial advance so the next sweep re-ingests.
268
+ const off = _transcriptOffsets.get(fp)
269
+ if (off && off.bytes >= stat.size) {
270
+ watchedFiles.set(fp, mtime)
271
+ }
272
+ if (n > 0) {
273
+ log(`[transcript-watch] ingested ${n} entries from ${path.basename(fp)}\n`)
274
+ }
275
+ } catch (e) {
276
+ log(`[transcript-watch] ingest error: ${e.message}\n`)
277
+ }
278
+ }
279
+
280
+ function scheduleIngest(fp) {
281
+ const existing = pendingByFile.get(fp)
282
+ if (existing) clearTimeout(existing)
283
+ const timer = setTimeout(() => {
284
+ pendingByFile.delete(fp)
285
+ ingestOne(fp)
286
+ }, DEBOUNCE_MS)
287
+ pendingByFile.set(fp, timer)
288
+ }
289
+
290
+ async function discoverActiveTranscripts() {
291
+ let topLevel
292
+ try { topLevel = await fs.promises.readdir(root) }
293
+ catch { return [] }
294
+ const files = []
295
+ for (const d of topLevel) {
296
+ if (d.includes('tmp') || d.includes('cache') || d.includes('plugins')) continue
297
+ const full = path.join(root, d)
298
+ let inner
299
+ try { inner = await fs.promises.readdir(full) } catch { continue }
300
+ for (const f of inner) {
301
+ if (!f.endsWith('.jsonl') || f.startsWith('agent-')) continue
302
+ const fp = path.join(full, f)
303
+ try {
304
+ const stat = await fs.promises.stat(fp)
305
+ files.push({ path: fp, mtime: stat.mtimeMs })
306
+ } catch {}
307
+ }
308
+ }
309
+ const cutoff = Date.now() - 30 * 60_000
310
+ return files.filter(f => f.mtime > cutoff)
311
+ }
312
+
313
+ async function safetySweep() {
314
+ try {
315
+ const active = await discoverActiveTranscripts()
316
+ for (const { path: fp } of active) ingestOne(fp)
317
+ } catch (e) {
318
+ log(`[transcript-watch] safety sweep error: ${e.message}\n`)
319
+ }
320
+ }
321
+
322
+ safetySweepTimeout = setTimeout(safetySweep, 3_000)
323
+
324
+ // fs.watch({recursive}) is only reliable on win32.
325
+ // darwin: recursive option unreliable — use flat watch per-entry (glob dirs at start).
326
+ // linux/WSL: recursive not supported — use fs.watchFile polling per file found via
327
+ // the safety sweep, or fall back entirely to safety sweep.
328
+ if (process.platform === 'win32') {
329
+ try {
330
+ const watcher = fs.watch(root, { recursive: true, persistent: true }, (_event, filename) => {
331
+ if (!filename) return
332
+ if (!isWatchable(filename)) return
333
+ const fp = path.join(root, filename)
334
+ scheduleIngest(fp)
335
+ })
336
+ watcher.on('error', (err) => {
337
+ log(`[transcript-watch] fs.watch error: ${err.message}\n`)
338
+ })
339
+ watchers.push(watcher)
340
+ log(`[transcript-watch] fs.watch(recursive) active on ${root}\n`)
341
+ } catch (e) {
342
+ log(`[transcript-watch] fs.watch setup failed: ${e.message} — relying on safety sweep only\n`)
343
+ }
344
+ intervals.push(setInterval(safetySweep, SAFETY_POLL_MS))
345
+ } else if (process.platform === 'darwin') {
346
+ // Flat watch: register a non-recursive watcher on each immediate subdirectory.
347
+ // New subdirs are picked up on the next safety sweep cycle.
348
+ try {
349
+ const registerFlat = (dir) => {
350
+ try {
351
+ const w = fs.watch(dir, { persistent: true }, (_event, filename) => {
352
+ if (!filename) return
353
+ const fp = path.join(dir, filename)
354
+ if (!isWatchable(fp)) return
355
+ scheduleIngest(fp)
356
+ })
357
+ w.on('error', () => { /* ignore individual dir errors */ })
358
+ watchers.push(w)
359
+ } catch { /* dir may not exist yet */ }
360
+ }
361
+ registerFlat(root)
362
+ try {
363
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
364
+ if (entry.isDirectory()) registerFlat(path.join(root, entry.name))
365
+ }
366
+ } catch { /* best effort */ }
367
+ log(`[transcript-watch] flat fs.watch active on ${root} (darwin)\n`)
368
+ } catch (e) {
369
+ log(`[transcript-watch] flat watch setup failed: ${e.message} — relying on safety sweep only\n`)
370
+ }
371
+ intervals.push(setInterval(safetySweep, SAFETY_POLL_MS))
372
+ } else {
373
+ // linux/WSL: fs.watch recursive is unsupported. Use fs.watchFile polling for
374
+ // individual files surfaced by the safety sweep, in addition to the sweep itself.
375
+ log(`[transcript-watch] linux/WSL — using safety sweep + fs.watchFile polling (no recursive watch)\n`)
376
+ // Wrap by reassigning the closure-captured reference is not possible here;
377
+ // instead, register watchFile inside the safety sweep callback by intercepting
378
+ // active file list after each sweep. The interval already calls safetySweep
379
+ // which calls ingestOne; watchFile additions happen as a side-effect of the sweep.
380
+ const _patchedSweep = async () => {
381
+ try {
382
+ const active = await discoverActiveTranscripts()
383
+ for (const { path: fp } of active) {
384
+ if (!polledFiles.has(fp)) {
385
+ polledFiles.add(fp)
386
+ fs.watchFile(fp, { persistent: false, interval: 2000 }, () => {
387
+ if (isWatchable(fp)) scheduleIngest(fp)
388
+ })
389
+ }
390
+ ingestOne(fp)
391
+ }
392
+ } catch (e) {
393
+ log(`[transcript-watch] linux sweep error: ${e.message}\n`)
394
+ }
395
+ }
396
+ // Replace the safety sweep interval with the patched version.
397
+ intervals.push(setInterval(_patchedSweep, SAFETY_POLL_MS))
398
+ }
399
+
400
+ return {
401
+ stop() {
402
+ if (safetySweepTimeout) { clearTimeout(safetySweepTimeout); safetySweepTimeout = null }
403
+ for (const t of pendingByFile.values()) { try { clearTimeout(t) } catch {} }
404
+ pendingByFile.clear()
405
+ for (const i of intervals) { try { clearInterval(i) } catch {} }
406
+ intervals.length = 0
407
+ for (const w of watchers) { try { w.close() } catch {} }
408
+ watchers.length = 0
409
+ for (const fp of polledFiles) { try { fs.unwatchFile(fp) } catch {} }
410
+ polledFiles.clear()
411
+ },
412
+ }
413
+ }
414
+
415
+ return {
416
+ loadTranscriptOffsets,
417
+ persistTranscriptOffsets,
418
+ ingestTranscriptFile,
419
+ cwdFromTranscriptPath,
420
+ parseTsToMs,
421
+ initTranscriptWatcher,
422
+ getOffset,
423
+ resetOffsets,
424
+ }
425
+ }
@@ -8,27 +8,19 @@ export const TOOL_DEFS = [
8
8
  name: 'memory',
9
9
  title: 'Memory Cycle',
10
10
  annotations: { title: 'Memory Cycle', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
11
- description: 'Memory status/lifecycle or explicit mutation. Use recall for retrieval. When the user explicitly asks to remember something (e.g. "remember this", a stated preference, or a decision), persist it immediately with action:"core" op:"add" — do not wait for a cycle. Destructive jobs require exact confirm strings.',
11
+ description: 'Core-memory mutation and status. Use recall for retrieval. When the user explicitly asks to remember something (e.g. "remember this", a stated preference, or a decision), persist it immediately with action:"core" op:"add" — do not wait for a cycle.',
12
12
  inputSchema: {
13
13
  type: 'object',
14
14
  properties: {
15
- action: { type: 'string', enum: ['core','manage','status','sleep','cycle1','cycle2','cycle3','flush','backfill','prune','rebuild','purge','retro_eval_active'], description: 'Operation.' },
15
+ action: { type: 'string', enum: ['core','status'], description: 'Operation.' },
16
16
  op: { type: 'string', enum: ['add','edit','delete','list','candidates','promote','dismiss'], description: 'Mutation op. candidates/promote/dismiss drive core-memory proposal approval.' },
17
17
  id: { type: 'number', description: 'Exact memory id.' },
18
18
  element: { type: 'string', description: 'Memory key/title.' },
19
19
  summary: { type: 'string', description: 'Memory content.' },
20
20
  category: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'], description: 'Category.' },
21
21
  status: { type: 'string', enum: ['pending','active','archived'], description: 'Lifecycle status.' },
22
- maxDays: { type: 'number', description: 'Prune age days.' },
23
- window: { type: 'string', description: 'Window, e.g. 24h/7d/30d.' },
24
22
  limit: { type: 'number', description: 'Max rows/items.' },
25
- concurrency: { type: 'number', description: 'Worker concurrency.' },
26
- sessionId: { type: 'string', description: 'Scoped session id.' },
27
- session_id: { type: 'string', description: 'Alias for sessionId.' },
28
- window_size: { type: 'number', description: 'cycle1 rows/window.' },
29
- rows_per_session: { type: 'number', description: 'cycle1 rows/session.' },
30
23
  confirm: { type: 'string', description: 'Exact confirmation phrase for destructive actions.' },
31
- cycle3Mode: { type: 'string', enum: ['proposal','conservative'], description: 'cycle3 mode.' },
32
24
  project_id: { type: 'string', description: 'Core pool: common, slug, or *.' },
33
25
  },
34
26
  required: ['action'],
@@ -38,13 +30,13 @@ export const TOOL_DEFS = [
38
30
  name: 'recall',
39
31
  title: 'Recall',
40
32
  annotations: { title: 'Recall', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41
- description: 'Retrieve stored memory/session history. Use for earlier work, resumes, or possibly decided items; when in doubt, recall first. For "what did we just discuss": pass sessionId with no query to browse the current session (includes in-progress content); period:"last" without sessionId browses the previous completed session only.',
33
+ description: 'Retrieve stored memory/session history; when in doubt, recall first. Query is topic/semantic search (hybrid embedding+keyword), not regex. Common patterns — previous conversation: period:"last" (all history newest-first, grouped per session); current session: sessionId with no query; recent window: period:"3h"/"30m"/"24h"; specific timeframe: period:"2026-07-03" / "2026-07-01~2026-07-03" / "12:00~14:00" (today) / "2026-07-03 12:00~14:00"; topic: query alone or query+period; exact follow-up: id from a prior result.',
42
34
  inputSchema: {
43
35
  type: 'object',
44
36
  properties: {
45
37
  query: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Search text, or array for independent fan-out queries.' },
46
38
  id: { anyOf: [{ type: 'number' }, { type: 'array', items: { type: 'number' }, minItems: 1 }], description: 'Exact #id(s) from recall. Do not invent ids.' },
47
- period: { type: 'string', description: "last (previous completed session; pair with sessionId instead to include the current one), 24h/7d/30d, all, date, or range." },
39
+ period: { type: 'string', description: "last (all history newest-first), Nm/Nh/Nd (rolling), today/yesterday/this_week/last_week, all, YYYY-MM-DD, YYYY-MM-DD~YYYY-MM-DD, HH:MM~HH:MM (today), or 'YYYY-MM-DD HH:MM~HH:MM'." },
48
40
  limit: { type: 'number', description: 'Max entries.' },
49
41
  offset: { type: 'number', description: 'Skip entries.' },
50
42
  sort: { type: 'string', enum: ['importance', 'date'], description: 'importance or date.' },
@@ -71,7 +63,7 @@ export const TOOL_DEFS = [
71
63
  type: 'object',
72
64
  properties: {
73
65
  query: { type: 'string', description: 'Search text.' },
74
- period: { type: 'string', description: 'last, 24h/7d/30d, all, date, or range.' },
66
+ period: { type: 'string', description: "last, Nm/Nh/Nd, today/yesterday/this_week/last_week, all, YYYY-MM-DD, date range, HH:MM~HH:MM (today), or 'YYYY-MM-DD HH:MM~HH:MM'." },
75
67
  sort: { type: 'string', enum: ['date', 'importance'], description: 'date or importance.' },
76
68
  category: { anyOf: [{ type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, { type: 'array', items: { type: 'string', enum: ['rule','constraint','decision','fact','goal','preference','task','issue'] }, minItems: 1 }], description: 'Category filter.' },
77
69
  limit: { type: 'number', default: 30, description: 'Max entries.' },