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
@@ -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. Persist with action:"core" op:"add" when: user corrects/confirms your approach, states a durable preference, or says "remember". Only what code/git/docs cannot derive; include the why. Never transient task state. No prior recall dedup needed — the cycle dedups.',
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. Use only to check prior conversation or past events: resumes, prior work, earlier decisions or messages. Do not call as a reflexive pre-step, to verify a just-made decision, or before storing memory. Query is topic/semantic search, not regex. Patterns: period:"last" for previous conversation; sessionId without query for current session; period:"3h"/"30m"/"24h" for recent; YYYY-MM-DD, date ranges, or HH:MM~HH:MM for time windows; id for exact follow-up.',
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.' },