mixdog 0.7.1

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 (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,299 @@
1
+ import { embedText, getEmbeddingModelId } from './embedding-provider.mjs'
2
+ import { embeddingToSql } from './memory.mjs'
3
+ import { createHash } from 'crypto'
4
+ import os from 'os'
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+
8
+ // Restart-survivable embedding dedup cache (DDL created on first flush).
9
+ // Keyed per-db handle so a second DB instance in the same process re-runs the
10
+ // IF NOT EXISTS DDL on its own connection rather than skipping based on a
11
+ // flag set by a different DB.
12
+ const _embCacheReady = new WeakSet()
13
+
14
+ export function inferChunkProjectId(members) {
15
+ const storedIds = new Set()
16
+ for (const m of members) {
17
+ if (m.project_id != null) storedIds.add(m.project_id)
18
+ }
19
+ if (storedIds.size === 1) return [...storedIds][0]
20
+ return null
21
+ }
22
+
23
+ const _flushInFlight = new WeakMap()
24
+
25
+ const _rawTimeout = Number(process.env.MIXDOG_EMBED_FLUSH_TIMEOUT_MS)
26
+ const EMBED_FLUSH_TIMEOUT_MS = (Number.isFinite(_rawTimeout) && _rawTimeout > 0) ? _rawTimeout : 30_000
27
+
28
+ const BATCH_SIZE = 32
29
+
30
+ function throwIfAborted(signal) {
31
+ if (signal?.aborted) throw signal.reason ?? new Error('aborted')
32
+ }
33
+
34
+ async function ensureEmbCacheTable(db) {
35
+ if (_embCacheReady.has(db)) return
36
+ await db.query(`
37
+ CREATE TABLE IF NOT EXISTS memory.embedding_cache (
38
+ model_id text NOT NULL,
39
+ text_hash bytea NOT NULL,
40
+ vector halfvec NOT NULL,
41
+ PRIMARY KEY (model_id, text_hash)
42
+ )
43
+ `)
44
+ _embCacheReady.add(db)
45
+ }
46
+
47
+ // Batch variant: resolve cache hits in one SELECT WHERE hash IN (...),
48
+ // embed misses in parallel, then bulk-INSERT new entries.
49
+ export async function cachedEmbedTextBatch(db, texts, options = {}) {
50
+ const signal = options?.signal
51
+ throwIfAborted(signal)
52
+ if (texts.length === 0) return []
53
+ await ensureEmbCacheTable(db)
54
+ throwIfAborted(signal)
55
+ // Key cache rows by the provider's real model id, not the env override —
56
+ // env may be unset while the provider still has a concrete model, and an
57
+ // env value that doesn't match the provider would tag vectors with a
58
+ // stale/wrong label across model/env changes.
59
+ const modelId = getEmbeddingModelId() || process.env.MIXDOG_EMBED_MODEL || 'default'
60
+ const entries = texts.map(t => ({
61
+ text: t,
62
+ hash: Buffer.from(createHash('sha256').update(t).digest()),
63
+ }))
64
+
65
+ // Single SELECT for all hashes
66
+ const hashBufs = entries.map(e => e.hash)
67
+ const hits = (await db.query(
68
+ `SELECT text_hash, vector FROM memory.embedding_cache WHERE model_id=$1 AND text_hash = ANY($2::bytea[])`,
69
+ [modelId, hashBufs],
70
+ )).rows
71
+ throwIfAborted(signal)
72
+ // Issue 6: pgvector/halfvec returns a text string like "[0.1,0.2,...]".
73
+ // Array.from on a string yields individual characters — parse to numeric array.
74
+ const hitMap = new Map(hits.map(r => [
75
+ r.text_hash.toString('hex'),
76
+ typeof r.vector === 'string'
77
+ ? r.vector.replace(/^\[|\]$/g, '').split(',').map(Number)
78
+ : Array.from(r.vector),
79
+ ]))
80
+
81
+ // Embed misses in parallel
82
+ const misses = entries.filter(e => !hitMap.has(e.hash.toString('hex')))
83
+ if (misses.length > 0) {
84
+ throwIfAborted(signal)
85
+ await Promise.all(misses.map(async (e) => {
86
+ e.vector = await embedText(e.text)
87
+ }))
88
+ throwIfAborted(signal)
89
+ // Bulk INSERT misses
90
+ await db.query(
91
+ `INSERT INTO memory.embedding_cache (model_id, text_hash, vector)
92
+ SELECT $1, unnest($2::bytea[]), unnest($3::text[])::halfvec
93
+ ON CONFLICT (model_id, text_hash) DO NOTHING`,
94
+ [
95
+ modelId,
96
+ misses.map(e => e.hash),
97
+ misses.map(e => embeddingToSql(e.vector)),
98
+ ],
99
+ )
100
+ throwIfAborted(signal)
101
+ for (const e of misses) hitMap.set(e.hash.toString('hex'), e.vector)
102
+ }
103
+
104
+ return entries.map(e => hitMap.get(e.hash.toString('hex')) ?? null)
105
+ }
106
+
107
+ export async function flushEmbeddingDirty(db, options = {}) {
108
+ const signal = options?.signal
109
+ throwIfAborted(signal)
110
+ // Coalesce concurrent flush calls per db handle.
111
+ const inFlight = _flushInFlight.get(db)
112
+ if (inFlight) return inFlight
113
+ const p = (async () => {
114
+ let totalAttempted = 0
115
+ let totalSucceeded = 0
116
+ let timedOut = false
117
+ const allFailed = []
118
+ const deadline = Date.now() + EMBED_FLUSH_TIMEOUT_MS
119
+ let cursor = 0
120
+ await ensureEmbCacheTable(db)
121
+ throwIfAborted(signal)
122
+
123
+ while (true) {
124
+ throwIfAborted(signal)
125
+ if (Date.now() >= deadline) {
126
+ process.stderr.write(
127
+ `[embed] flush timed out after ${EMBED_FLUSH_TIMEOUT_MS / 1000}s; proceeding with partial state\n`,
128
+ )
129
+ timedOut = true
130
+ break
131
+ }
132
+
133
+ // Claim a disjoint batch via SKIP LOCKED on a dedicated connection.
134
+ // Hold the transaction open across the embedding write so another flush
135
+ // (different process / db handle) cannot re-claim the same ids mid-flight.
136
+ // The protected work routes through `client` so the UPDATE runs on the
137
+ // same connection that owns the row locks (a different pool connection
138
+ // would block on FOR UPDATE).
139
+ const client = await db._pool.connect()
140
+ let ids
141
+ let claimOk = false
142
+ try {
143
+ throwIfAborted(signal)
144
+ await client.query('BEGIN')
145
+ const res = await client.query(
146
+ `SELECT id FROM memory.entries
147
+ WHERE is_root = 1 AND embedding IS NULL
148
+ AND (element IS NOT NULL OR summary IS NOT NULL)
149
+ AND id > $2
150
+ ORDER BY id
151
+ LIMIT $1
152
+ FOR UPDATE SKIP LOCKED`,
153
+ [BATCH_SIZE, cursor],
154
+ )
155
+ ids = res.rows.map(r => Number(r.id))
156
+ claimOk = true
157
+ throwIfAborted(signal)
158
+ } catch (err) {
159
+ try { await client.query('ROLLBACK') } catch {}
160
+ client.release()
161
+ if (signal?.aborted) throw signal.reason ?? err
162
+ process.stderr.write(`[embed] flush SKIP LOCKED claim failed: ${err.message}\n`)
163
+ break
164
+ }
165
+
166
+ if (ids.length === 0) {
167
+ try { await client.query('COMMIT') } catch {}
168
+ client.release()
169
+ break
170
+ }
171
+
172
+ cursor = ids[ids.length - 1]
173
+ totalAttempted += ids.length
174
+ let batchDone = false
175
+ try {
176
+ // One embedding flush batch owns row locks until COMMIT/ROLLBACK;
177
+ // cancellation is checked before each batch and after its locked work.
178
+ throwIfAborted(signal)
179
+ const writtenIds = await syncBatchEmbeddings(client, ids, { signal })
180
+ totalSucceeded += writtenIds.length
181
+ if (writtenIds.length < ids.length) {
182
+ // Track per-id: only the ids that did NOT receive an embedding
183
+ // (no text, dim mismatch, stale write) count as failed.
184
+ const writtenSet = new Set(writtenIds)
185
+ for (const id of ids) {
186
+ if (!writtenSet.has(id)) allFailed.push(id)
187
+ }
188
+ }
189
+ batchDone = true
190
+ } catch (err) {
191
+ if (signal?.aborted) throw signal.reason ?? err
192
+ process.stderr.write(`[embed] batch failed (ids=${ids[0]}..${ids[ids.length-1]}): ${err.message}\n`)
193
+ for (const id of ids) allFailed.push(id)
194
+ } finally {
195
+ try {
196
+ if (batchDone) await client.query('COMMIT')
197
+ else await client.query('ROLLBACK')
198
+ } catch {}
199
+ client.release()
200
+ }
201
+
202
+ if (ids.length < BATCH_SIZE) break
203
+ }
204
+
205
+ return { attempted: totalAttempted, succeeded: totalSucceeded, failed: allFailed, timedOut }
206
+ })()
207
+ _flushInFlight.set(db, p)
208
+ try {
209
+ return await p
210
+ } finally {
211
+ _flushInFlight.delete(db)
212
+ }
213
+ }
214
+
215
+ // Batch-embed all ids in one DB round-trip for fetching, one API call per unique text,
216
+ // then one VALUES UPDATE for all results.
217
+ // Returns the list of ids that were actually UPDATEd with a fresh embedding.
218
+ // Empty result means no ids landed (e.g. all had empty text, dim mismatches,
219
+ // or text changed since read). Callers may compare result.length to ids.length
220
+ // for per-id success tracking.
221
+ async function syncBatchEmbeddings(db, ids, options = {}) {
222
+ const signal = options?.signal
223
+ throwIfAborted(signal)
224
+ // 1. Fetch element+summary for all ids in one query
225
+ const rows = (await db.query(
226
+ `SELECT id, element, summary FROM memory.entries WHERE id = ANY($1::bigint[]) AND is_root = 1`,
227
+ [ids],
228
+ )).rows
229
+ throwIfAborted(signal)
230
+ if (rows.length === 0) return []
231
+
232
+ // 2. Fetch dims from meta once
233
+ const dimsRow = (await db.query(`SELECT value FROM memory.meta WHERE key = 'embedding.current_dims'`)).rows[0]
234
+ throwIfAborted(signal)
235
+ const expected = Number(dimsRow?.value ?? 0)
236
+
237
+ // 3. Embed each row via batch cache lookup (one SELECT + one INSERT for misses)
238
+ const texts = rows.map(row => [row.element, row.summary].filter(Boolean).join(' — ').trim())
239
+ const vectors = await cachedEmbedTextBatch(db, texts.filter(Boolean), { signal })
240
+ throwIfAborted(signal)
241
+ // Re-map: only non-empty texts were passed to cachedEmbedTextBatch
242
+ let vecIdx = 0
243
+ const updates = [] // { id, element, summary, vector }
244
+ for (let i = 0; i < rows.length; i++) {
245
+ throwIfAborted(signal)
246
+ const row = rows[i]
247
+ if (!texts[i]) continue
248
+ const vector = vectors[vecIdx++]
249
+ if (!Array.isArray(vector) || vector.length === 0) continue
250
+ if (Number.isFinite(expected) && expected > 0 && vector.length !== expected) {
251
+ process.stderr.write(`[embed] dim mismatch (id=${row.id} got=${vector.length} expected=${expected})\n`)
252
+ continue
253
+ }
254
+ updates.push({ id: Number(row.id), element: row.element, summary: row.summary, vector })
255
+ }
256
+
257
+ if (updates.length === 0) return []
258
+
259
+ // 4. Bulk UPDATE using VALUES list
260
+ // The VALUES update is one SQL batch; do not split it with an abort checkpoint.
261
+ const valClauses = updates.map((u, i) => {
262
+ const base = i * 4
263
+ return `($${base+1}::bigint, $${base+2}::halfvec, $${base+3}::text, $${base+4}::text)`
264
+ })
265
+ const params = []
266
+ for (const u of updates) {
267
+ params.push(u.id, embeddingToSql(u.vector), u.element, u.summary)
268
+ }
269
+ const res = await db.query(
270
+ `UPDATE memory.entries AS e
271
+ SET embedding = t.vector
272
+ FROM (VALUES ${valClauses.join(',')}) AS t(id, vector, element, summary)
273
+ WHERE e.id = t.id AND e.is_root = 1
274
+ AND e.embedding IS NULL
275
+ AND e.element IS NOT DISTINCT FROM t.element
276
+ AND e.summary IS NOT DISTINCT FROM t.summary
277
+ RETURNING e.id`,
278
+ params,
279
+ )
280
+ throwIfAborted(signal)
281
+ const writtenIds = (res.rows || []).map(r => Number(r.id))
282
+ if (writtenIds.length < updates.length) {
283
+ process.stderr.write(`[embed-sync] ${updates.length - writtenIds.length} stale-write(s) skipped — text changed since read\n`)
284
+ }
285
+ return writtenIds
286
+ }
287
+
288
+ // Kept for external callers that still import syncRootEmbedding directly.
289
+ export async function syncRootEmbedding(db, rootId, options = {}) {
290
+ const writtenIds = await syncBatchEmbeddings(db, [rootId], options)
291
+ return writtenIds.length > 0
292
+ }
293
+
294
+ export async function deleteRootEmbedding(db, rootId) {
295
+ await db.transaction(async (tx) => {
296
+ await tx.query(`UPDATE entries SET embedding = NULL WHERE id = $1 AND is_root = 1`, [rootId])
297
+ })
298
+ return true
299
+ }
@@ -0,0 +1,5 @@
1
+ // Single source of truth: lib/text-utils.cjs (also required by hooks/session-start.cjs).
2
+ import { createRequire } from 'module'
3
+ const _require = createRequire(import.meta.url)
4
+ const { cleanMemoryText: _cleanMemoryText } = _require('../../../lib/text-utils.cjs')
5
+ export const cleanMemoryText = _cleanMemoryText
@@ -0,0 +1,32 @@
1
+ export async function pruneOldEntries(db, maxAgeDays) {
2
+ const days = Number(maxAgeDays)
3
+ if (!Number.isFinite(days) || days <= 0) {
4
+ throw new Error(`pruneOldEntries: maxAgeDays must be positive, got ${maxAgeDays}`)
5
+ }
6
+ const cutoffMs = Date.now() - days * 86_400_000
7
+ const result = await db.query(
8
+ `DELETE FROM entries
9
+ WHERE ts < $1
10
+ AND (
11
+ chunk_root IS NULL
12
+ OR (is_root = 0 AND chunk_root = id AND status = 'archived')
13
+ )`,
14
+ [cutoffMs],
15
+ )
16
+ // Cross-schema FK is intentionally absent (trace_events is partitioned and
17
+ // self-FKs are fragile there). After deleting from memory.entries, NULL out
18
+ // the dangling entry_id references on trace_events so cross-schema joins
19
+ // (recall ↔ trace correlation) do not surface stale ids. Same PG instance
20
+ // → fully-qualified names work from either schema's connection.
21
+ let orphanedTraceRefs = 0
22
+ try {
23
+ const orphan = await db.query(
24
+ `UPDATE trace.trace_events SET entry_id = NULL
25
+ WHERE entry_id IS NOT NULL
26
+ AND NOT EXISTS (SELECT 1 FROM memory.entries WHERE id = trace.trace_events.entry_id)`,
27
+ [],
28
+ )
29
+ orphanedTraceRefs = Number(orphan.rowCount ?? 0)
30
+ } catch { /* trace schema may be absent in early boot — no-op */ }
31
+ return { deleted: Number(result.rowCount ?? 0), cutoffMs, orphanedTraceRefs }
32
+ }
@@ -0,0 +1,190 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+
5
+ const DEFAULT_OPS_POLICY = {
6
+ features: {
7
+ temporalParser: false,
8
+ },
9
+ startup: {
10
+ // Startup catch-up disabled by default: was running inline embeddings
11
+ // ~5s after server start, causing perceptible lag right after user typed.
12
+ // Pending work is still handled by the regular 5-min cycle1 interval.
13
+ cycle1CatchUp: {
14
+ mode: 'off',
15
+ delayMs: 5000,
16
+ minPendingCandidates: 8,
17
+ requireDue: false,
18
+ },
19
+ cycle2CatchUp: {
20
+ mode: 'off',
21
+ delayMs: 5000,
22
+ requireDue: true,
23
+ },
24
+ },
25
+ scheduler: {
26
+ checkIntervalMs: 60_000,
27
+ },
28
+ }
29
+
30
+ function coercePositiveInt(value, fallback) {
31
+ const parsed = Number(value)
32
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback
33
+ return Math.floor(parsed)
34
+ }
35
+
36
+ function normalizeBackfillWindow(value) {
37
+ const normalized = String(value ?? 'all').trim().toLowerCase()
38
+ if (['none', 'off', 'disabled', '0'].includes(normalized)) return 'none'
39
+ if (['1d', '1day', '1-day', '1 day', 'day', 'today'].includes(normalized)) return '1d'
40
+ if (['3d', '3days', '3-day', '3 day'].includes(normalized)) return '3d'
41
+ if (['7d', '7days', '7-day', '7 day', 'week'].includes(normalized)) return '7d'
42
+ if (['30d', '30days', '30-day', '30 day', 'month'].includes(normalized)) return '30d'
43
+ return 'all'
44
+ }
45
+
46
+ function normalizeCatchUpMode(value, fallback = 'light') {
47
+ const normalized = String(value ?? fallback).trim().toLowerCase()
48
+ if (['off', 'none', 'disabled'].includes(normalized)) return 'off'
49
+ if (['full', 'all', 'aggressive'].includes(normalized)) return 'full'
50
+ return 'light'
51
+ }
52
+
53
+ function normalizeBackfillScope(value) {
54
+ const normalized = String(value ?? 'all').trim().toLowerCase()
55
+ if (['workspace', 'project', 'current'].includes(normalized)) return 'workspace'
56
+ return 'all'
57
+ }
58
+
59
+ function envFlag(value, fallback = false) {
60
+ if (value == null || value === '') return fallback
61
+ const normalized = String(value).trim().toLowerCase()
62
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
63
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false
64
+ return fallback
65
+ }
66
+
67
+ export function resolveBackfillSinceMs(windowValue, now = Date.now()) {
68
+ const normalized = normalizeBackfillWindow(windowValue)
69
+ if (normalized === '1d') return now - (1 * 24 * 60 * 60 * 1000)
70
+ if (normalized === '3d') return now - (3 * 24 * 60 * 60 * 1000)
71
+ if (normalized === '7d') return now - (7 * 24 * 60 * 60 * 1000)
72
+ if (normalized === '30d') return now - (30 * 24 * 60 * 60 * 1000)
73
+ return null
74
+ }
75
+
76
+ export async function countUnclassified(db) {
77
+ if (!db) return 0
78
+ try {
79
+ const row = (await db.query(`SELECT COUNT(*) c FROM entries WHERE chunk_root IS NULL`, [])).rows[0]
80
+ return Number(row?.c ?? 0)
81
+ } catch {
82
+ return 0
83
+ }
84
+ }
85
+
86
+ export function selectBackfillTranscripts({ sinceMs = null, limit = null, projectsRoot = null } = {}) {
87
+ const root = projectsRoot || path.join(os.homedir(), '.claude', 'projects')
88
+ if (!fs.existsSync(root)) return []
89
+ const files = []
90
+ for (const d of fs.readdirSync(root)) {
91
+ if (d.includes('tmp') || d.includes('cache') || d.includes('plugins')) continue
92
+ const full = path.join(root, d)
93
+ try {
94
+ for (const f of fs.readdirSync(full)) {
95
+ if (!f.endsWith('.jsonl') || f.startsWith('agent-')) continue
96
+ const fp = path.join(full, f)
97
+ let mtime
98
+ try { mtime = fs.statSync(fp).mtimeMs } catch { continue }
99
+ if (sinceMs != null && mtime < sinceMs) continue
100
+ files.push({ path: fp, mtime })
101
+ }
102
+ } catch {}
103
+ }
104
+ files.sort((a, b) => b.mtime - a.mtime)
105
+ const capped = (limit != null && Number(limit) > 0) ? files.slice(0, Number(limit)) : files
106
+ return capped.map(f => f.path).reverse()
107
+ }
108
+
109
+ const FULL_BACKFILL_MAX_ITERS = 30
110
+ const BACKFILL_CONCURRENCY = 3
111
+
112
+ export async function runFullBackfill(db, {
113
+ window = '7d',
114
+ scope = 'all',
115
+ limit = null,
116
+ config = {},
117
+ dataDir = null,
118
+ ingestTranscriptFile,
119
+ cwdFromTranscriptPath,
120
+ runCycle1,
121
+ runCycle2,
122
+ now = Date.now(),
123
+ projectsRoot = null,
124
+ } = {}) {
125
+ if (typeof ingestTranscriptFile !== 'function') {
126
+ throw new Error('runFullBackfill: ingestTranscriptFile required')
127
+ }
128
+ if (typeof runCycle1 !== 'function' || typeof runCycle2 !== 'function') {
129
+ throw new Error('runFullBackfill: runCycle1/runCycle2 required')
130
+ }
131
+
132
+ const normalizedWindow = normalizeBackfillWindow(window)
133
+ const normalizedScope = normalizeBackfillScope(scope)
134
+ const sinceMs = resolveBackfillSinceMs(normalizedWindow, now)
135
+ const selected = selectBackfillTranscripts({ sinceMs, limit, projectsRoot })
136
+
137
+ let ingested = 0
138
+ let cursor = 0
139
+ const workers = Array.from({ length: BACKFILL_CONCURRENCY }, async () => {
140
+ while (cursor < selected.length) {
141
+ const idx = cursor++
142
+ const fp = selected[idx]
143
+ try {
144
+ const cwd = typeof cwdFromTranscriptPath === 'function' ? cwdFromTranscriptPath(fp) : undefined
145
+ const n = Number(await ingestTranscriptFile(fp, { cwd }) ?? 0)
146
+ ingested += n
147
+ } catch (err) {
148
+ process.stderr.write(`[backfill] ingest failed (${fp}): ${err.message}\n`)
149
+ }
150
+ }
151
+ })
152
+ await Promise.all(workers)
153
+
154
+ let cycle1Iters = 0
155
+ let prevUnclassified = await countUnclassified(db)
156
+ while (prevUnclassified > 0 && cycle1Iters < FULL_BACKFILL_MAX_ITERS) {
157
+ let result
158
+ try {
159
+ result = await runCycle1(db, config?.cycle1 || {}, {}, dataDir)
160
+ } catch (err) {
161
+ process.stderr.write(`[backfill] cycle1 error (iter=${cycle1Iters}): ${err.message}\n`)
162
+ break
163
+ }
164
+ cycle1Iters += 1
165
+ if (Number(result?.processed ?? 0) === 0) break
166
+ const nextUnclassified = await countUnclassified(db)
167
+ if (nextUnclassified >= prevUnclassified) break
168
+ prevUnclassified = nextUnclassified
169
+ }
170
+
171
+ let promoted = 0
172
+ try {
173
+ const c2 = await runCycle2(db, config?.cycle2 || {}, {}, dataDir)
174
+ promoted = Number(c2?.promoted ?? 0)
175
+ } catch (err) {
176
+ process.stderr.write(`[backfill] cycle2 error: ${err.message}\n`)
177
+ }
178
+
179
+ const unclassified = await countUnclassified(db)
180
+ return {
181
+ window: normalizedWindow,
182
+ scope: normalizedScope,
183
+ files: selected.length,
184
+ ingested,
185
+ cycle1_iters: cycle1Iters,
186
+ promoted,
187
+ unclassified,
188
+ }
189
+ }
190
+
@@ -0,0 +1,15 @@
1
+ // Helper: scoped id recall query (used from index handleSearch id mode).
2
+ import { buildRecallScopeFilter } from './memory-recall-scope-filter.mjs'
3
+ import { recallReadQuery } from './memory-recall-read-query.mjs'
4
+
5
+ export async function fetchEntriesByIdsScoped(db, ids, scopeOptions) {
6
+ const { clause, params } = buildRecallScopeFilter(2, scopeOptions)
7
+ const { rows } = await recallReadQuery(
8
+ db,
9
+ `SELECT id, ts, role, content, session_id, source_turn, chunk_root, is_root,
10
+ element, category, summary, project_id, status, score, last_seen_at
11
+ FROM entries WHERE id = ANY($1::bigint[]) ${clause}`,
12
+ [ids, ...params],
13
+ )
14
+ return rows
15
+ }
@@ -0,0 +1,7 @@
1
+ // Recall read path: bounded query time via SET LOCAL (requires a transaction).
2
+ export async function recallReadQuery(db, sql, params) {
3
+ return db.transaction(async (tx) => {
4
+ await tx.query(`SET LOCAL statement_timeout = '30s'`)
5
+ return tx.query(sql, params)
6
+ })
7
+ }
@@ -0,0 +1,63 @@
1
+ import { VALID_CATEGORY } from './memory.mjs'
2
+
3
+ export function buildCategoryFilterClause(offset, categories, { tableAlias = '' } = {}) {
4
+ const cats = (Array.isArray(categories) ? categories : [categories])
5
+ .map(c => String(c ?? '').trim().toLowerCase())
6
+ .filter(c => VALID_CATEGORY.has(c))
7
+ if (cats.length === 0) return { clause: '', params: [] }
8
+ const outerRef = tableAlias || 'entries'
9
+ const p = `${outerRef}.`
10
+ const ph = cats.map((_, i) => `$${offset + i}`).join(', ')
11
+ const inner = `(
12
+ (${p}is_root = 1 AND ${p}category IN (${ph}))
13
+ OR (${p}is_root = 0 AND ${p}chunk_root IS NOT NULL AND ${p}chunk_root <> ${p}id AND EXISTS (
14
+ SELECT 1 FROM entries r WHERE r.id = ${p}chunk_root AND r.is_root = 1 AND r.category IN (${ph})
15
+ ))
16
+ OR (${p}is_root = 0 AND (${p}chunk_root IS NULL OR ${p}chunk_root = ${p}id) AND ${p}category IN (${ph}))
17
+ )`
18
+ return { clause: `AND (${inner})`, params: [...cats] }
19
+ }
20
+
21
+ export function buildRecallScopeFilter(offset, options = {}, tableAlias = '') {
22
+ const outerRef = tableAlias || 'entries'
23
+ const p = `${outerRef}.`
24
+ const clauses = [
25
+ `NOT (${p}is_root = 0 AND ${p}chunk_root IS NOT DISTINCT FROM ${p}id AND ${p}status IS NOT DISTINCT FROM 'archived')`,
26
+ ]
27
+ const params = []
28
+ let next = offset
29
+ const tsFrom = Number.isFinite(Number(options.ts_from)) ? Number(options.ts_from) : null
30
+ const tsTo = Number.isFinite(Number(options.ts_to)) ? Number(options.ts_to) : null
31
+ if (tsFrom != null) { clauses.push(`${p}ts >= $${next++}`); params.push(tsFrom) }
32
+ if (tsTo != null) { clauses.push(`${p}ts <= $${next++}`); params.push(tsTo) }
33
+ const excludeStatuses = Array.isArray(options.excludeStatuses)
34
+ ? options.excludeStatuses.filter(s => typeof s === 'string' && s.trim()).map(s => s.trim().toLowerCase())
35
+ : []
36
+ if (excludeStatuses.length > 0) {
37
+ const ph = excludeStatuses.map(() => `$${next++}`).join(', ')
38
+ const statusPred = `(${p}status IS NULL OR ${p}status NOT IN (${ph}))`
39
+ clauses.push(`(
40
+ (${p}is_root = 1 AND ${statusPred})
41
+ OR (${p}is_root = 0 AND ${p}chunk_root IS NOT NULL AND ${p}chunk_root <> ${p}id AND EXISTS (
42
+ SELECT 1 FROM entries r WHERE r.id = ${p}chunk_root AND r.is_root = 1
43
+ AND (r.status IS NULL OR r.status NOT IN (${ph}))
44
+ ))
45
+ OR (${p}is_root = 0 AND (${p}chunk_root IS NULL OR ${p}chunk_root = ${p}id) AND ${statusPred})
46
+ )`)
47
+ params.push(...excludeStatuses)
48
+ }
49
+ const categories = (Array.isArray(options.category) ? options.category : [options.category])
50
+ .map(c => String(c ?? '').trim().toLowerCase())
51
+ .filter(c => VALID_CATEGORY.has(c))
52
+ if (categories.length > 0) {
53
+ const { clause: catClause, params: catParams } = buildCategoryFilterClause(next, categories, { tableAlias })
54
+ if (catClause) { clauses.push(catClause.replace(/^AND /, '')); params.push(...catParams); next += catParams.length }
55
+ }
56
+ const projectScope = typeof options.projectScope === 'string' ? options.projectScope : null
57
+ if (projectScope === 'common') clauses.push(`${p}project_id IS NULL`)
58
+ else if (projectScope && projectScope !== 'all') {
59
+ clauses.push(`(${p}project_id IS NULL OR ${p}project_id = $${next++})`)
60
+ params.push(projectScope)
61
+ }
62
+ return { clause: clauses.length > 0 ? `AND ${clauses.join(' AND ')}` : '', params }
63
+ }