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,93 @@
1
+ /**
2
+ * agent-ipc.mjs — IPC client for bridge LLM calls from the memory worker.
3
+ *
4
+ * The memory worker runs in its own fork, while the bridge / provider
5
+ * registry lives in-process in the parent server. cycle1 / cycle2 can't
6
+ * call makeBridgeLlm() locally (provider map is empty here), so we route
7
+ * every LLM call over IPC:
8
+ *
9
+ * memory → parent : { type: 'agent_ipc_request', callId, tool, params }
10
+ * parent → memory : { type: 'agent_ipc_response', callId, ok, result | error }
11
+ *
12
+ * Uses a module-level pending map keyed by callId. Parent-side handler
13
+ * lives in server.mjs spawnWorker's message listener.
14
+ */
15
+
16
+ const pending = new Map()
17
+ let listenerInstalled = false
18
+ let _idSeq = 0
19
+
20
+ function installListener() {
21
+ if (listenerInstalled) return
22
+ listenerInstalled = true
23
+ process.on('message', (msg) => {
24
+ if (!msg || msg.type !== 'agent_ipc_response' || !msg.callId) return
25
+ const entry = pending.get(msg.callId)
26
+ if (!entry) return
27
+ pending.delete(msg.callId)
28
+ if (msg.ok) entry.resolve(msg.result)
29
+ else entry.reject(new Error(msg.error || 'agent_ipc_response error'))
30
+ })
31
+ }
32
+
33
+ function nextCallId() {
34
+ _idSeq += 1
35
+ return `mem-${process.pid}-${Date.now()}-${_idSeq}`
36
+ }
37
+
38
+ /**
39
+ * Send an agent-bridge LLM request to the parent. Throws if IPC is
40
+ * unavailable (worker not forked) or the parent reports an error.
41
+ *
42
+ * @param {object} opts bridge-llm construction options
43
+ * @param {string} [opts.role]
44
+ * @param {string} [opts.taskType]
45
+ * @param {string} [opts.mode]
46
+ * @param {string} [opts.preset] preset id/name (passed at call time)
47
+ * @param {number} [opts.timeout] ms, defaults 600000
48
+ * @param {string} [opts.cwd]
49
+ * @param {string} prompt user message
50
+ * @returns {Promise<string>} raw assistant content
51
+ */
52
+ export function callBridgeLlm(opts = {}, prompt) {
53
+ if (!process.send) {
54
+ return Promise.reject(new Error('agent-ipc: process.send unavailable (not running as worker)'))
55
+ }
56
+ installListener()
57
+ const callId = nextCallId()
58
+ const timeoutMs = Math.max(1000, Number(opts.timeout ?? 600000))
59
+ return new Promise((resolve, reject) => {
60
+ const timer = setTimeout(() => {
61
+ if (!pending.has(callId)) return
62
+ pending.delete(callId)
63
+ try {
64
+ process.send({ type: 'agent_ipc_cancel', callId })
65
+ } catch {}
66
+ reject(new Error(`agent-ipc: timed out after ${timeoutMs}ms`))
67
+ }, timeoutMs)
68
+ pending.set(callId, {
69
+ resolve: (v) => { clearTimeout(timer); resolve(v) },
70
+ reject: (e) => { clearTimeout(timer); reject(e) },
71
+ })
72
+ try {
73
+ process.send({
74
+ type: 'agent_ipc_request',
75
+ callId,
76
+ tool: 'bridge_llm',
77
+ params: {
78
+ role: opts.role || null,
79
+ taskType: opts.taskType || null,
80
+ mode: opts.mode || null,
81
+ preset: opts.preset || null,
82
+ cwd: opts.cwd || null,
83
+ prompt: String(prompt ?? ''),
84
+ timeout: timeoutMs,
85
+ },
86
+ })
87
+ } catch (e) {
88
+ pending.delete(callId)
89
+ clearTimeout(timer)
90
+ reject(e)
91
+ }
92
+ })
93
+ }
@@ -0,0 +1,120 @@
1
+ // bridge-trace-queries.mjs — SQL helpers for the bridge analytic tables.
2
+ // All functions accept a `db` handle from openTraceDatabase() and return
3
+ // plain row arrays. Query parameters are always $N — no interpolation.
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // topSessionsByIteration(db, hours, limit)
7
+ // Sessions with the highest max_iteration seen in the last N hours.
8
+ // ---------------------------------------------------------------------------
9
+ export async function topSessionsByIteration(db, hours = 24, limit = 20) {
10
+ const { rows } = await db.query(`
11
+ SELECT s.session_id,
12
+ s.role,
13
+ s.model,
14
+ s.max_iteration,
15
+ s.tool_calls,
16
+ s.llm_calls,
17
+ s.total_input_tokens,
18
+ s.total_output_tokens,
19
+ s.started_at,
20
+ s.last_seen_at
21
+ FROM bridge_sessions s
22
+ WHERE s.last_seen_at >= now() - ($1 || ' hours')::interval
23
+ ORDER BY s.max_iteration DESC, s.tool_calls DESC
24
+ LIMIT $2
25
+ `, [String(hours), limit])
26
+ return rows
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // repeatToolCalls(db, sessionId)
31
+ // Tool calls whose (tool_name, md5(tool_args::text)) combo appears ≥3 times
32
+ // in a single session — strong signal for a tool-loop.
33
+ // ---------------------------------------------------------------------------
34
+ export async function repeatToolCalls(db, sessionId) {
35
+ const { rows } = await db.query(`
36
+ SELECT tool_name,
37
+ md5(tool_args::text) AS args_hash,
38
+ COUNT(*) AS call_count,
39
+ MIN(ts) AS first_at,
40
+ MAX(ts) AS last_at,
41
+ (array_agg(tool_args ORDER BY ts))[1] AS sample_args
42
+ FROM bridge_calls
43
+ WHERE session_id = $1
44
+ AND tool_name IS NOT NULL
45
+ GROUP BY tool_name, md5(tool_args::text)
46
+ HAVING COUNT(*) >= 3
47
+ ORDER BY call_count DESC, tool_name
48
+ `, [sessionId])
49
+ return rows
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // mixedToolPattern(db, sessionId)
54
+ // Sliding 3-tool window where grep and read alternate AND share a path token.
55
+ // Returns the centre-row of each such window.
56
+ // ---------------------------------------------------------------------------
57
+ export async function mixedToolPattern(db, sessionId) {
58
+ const { rows } = await db.query(`
59
+ WITH ordered AS (
60
+ SELECT id, ts, tool_name,
61
+ tool_args->>'path' AS path,
62
+ -- basename: last segment after splitting on '/'
63
+ regexp_replace(tool_args->>'path', '^.*/', '') AS basename,
64
+ LAG (tool_name, 1) OVER (ORDER BY ts, id) AS prev1,
65
+ LAG (tool_name, 2) OVER (ORDER BY ts, id) AS prev2,
66
+ LEAD(tool_name, 1) OVER (ORDER BY ts, id) AS next1,
67
+ LAG (tool_args->>'path', 1) OVER (ORDER BY ts, id) AS prev_path,
68
+ LEAD(tool_args->>'path', 1) OVER (ORDER BY ts, id) AS next_path,
69
+ -- basenames of neighbours
70
+ regexp_replace(LAG (tool_args->>'path', 1) OVER (ORDER BY ts, id), '^.*/', '') AS prev_base,
71
+ regexp_replace(LEAD(tool_args->>'path', 1) OVER (ORDER BY ts, id), '^.*/', '') AS next_base
72
+ FROM bridge_calls
73
+ WHERE session_id = $1
74
+ AND tool_name IN ('grep','read')
75
+ )
76
+ SELECT id, ts, tool_name, path
77
+ FROM ordered
78
+ WHERE -- true 3-tool alternation: centre plus BOTH flanking neighbours (window size = 3)
79
+ (
80
+ (tool_name = 'read' AND prev1 = 'grep' AND next1 = 'grep')
81
+ OR (tool_name = 'grep' AND prev1 = 'read' AND next1 = 'read')
82
+ )
83
+ -- path-token sharing: basename or exact path matches across all 3 slots
84
+ AND basename IS NOT NULL
85
+ AND (
86
+ -- exact path shared with both neighbours
87
+ (path = prev_path AND path = next_path)
88
+ -- or basename shared with both neighbours
89
+ OR (basename = prev_base AND basename = next_base)
90
+ -- or mixed: exact on one side, basename on other
91
+ OR (path = prev_path AND basename = next_base)
92
+ OR (path = next_path AND basename = prev_base)
93
+ )
94
+ ORDER BY ts, id
95
+ `, [sessionId])
96
+ return rows
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // tokenUsageByRole(db, hours)
101
+ // Sum of input/output tokens grouped by role over the last N hours.
102
+ // ---------------------------------------------------------------------------
103
+ export async function tokenUsageByRole(db, hours = 24) {
104
+ const { rows } = await db.query(`
105
+ SELECT s.role,
106
+ COUNT(DISTINCT l.session_id) AS sessions,
107
+ SUM(l.input_tokens) AS total_input,
108
+ SUM(l.output_tokens) AS total_output,
109
+ SUM(l.cached_tokens) AS total_cached,
110
+ SUM(l.cache_write_tokens) AS total_cache_write,
111
+ AVG(l.input_tokens)::int AS avg_input_per_call,
112
+ AVG(l.output_tokens)::int AS avg_output_per_call
113
+ FROM bridge_llm l
114
+ JOIN bridge_sessions s USING (session_id)
115
+ WHERE l.ts >= now() - ($1 || ' hours')::interval
116
+ GROUP BY s.role
117
+ ORDER BY total_input DESC NULLS LAST
118
+ `, [String(hours)])
119
+ return rows
120
+ }
@@ -0,0 +1,330 @@
1
+ // User-curated core memory store — native PG-backed via core_entries table.
2
+ // Per-project entries distinguished by project_id column (NULL = COMMON).
3
+ // addCore / editCore generate an embedding for each row and run a cosine-sim
4
+ // lookup against existing rows in the same project pool: candidates at or
5
+ // above SIM_RECALL go through an LLM "merge or distinct" judge — only the
6
+ // LLM's verdict, not the embedding score, decides whether the prior row is
7
+ // superseded in place. Below the threshold the row is INSERTed fresh.
8
+ // cycle2 reads core_entries via the {{USER_CORE}} prompt slot to avoid
9
+ // re-promoting entries that already overlap a user-curated row.
10
+
11
+ import { getDatabase, embeddingToSql } from './memory.mjs'
12
+ import { cachedEmbedTextBatch } from './memory-embed.mjs'
13
+ import { callBridgeLlm } from './agent-ipc.mjs'
14
+ import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
15
+ import { checkedConnect } from './pg/adapter.mjs'
16
+
17
+ const VALID_CAT = new Set([
18
+ 'rule', 'constraint', 'decision', 'fact', 'goal', 'preference', 'task', 'issue',
19
+ ])
20
+
21
+ // Embedding sim threshold for surfacing a candidate to the LLM judge. Wider
22
+ // than cycle2's tier-1 (0.78) on purpose: LLM verdict is authoritative so
23
+ // the recall side can afford broader recall.
24
+ const SIM_RECALL = 0.65
25
+ const CORE_DEDUP_TOP_K = 5
26
+
27
+ export const CORE_SUMMARY_MAX = 120
28
+
29
+ function trimOrNull(v) {
30
+ if (v == null) return null
31
+ const s = String(v).trim()
32
+ return s === '' ? null : s
33
+ }
34
+
35
+ function _getDb(dataDir) {
36
+ if (!dataDir) throw new Error('core-memory: dataDir required')
37
+ const db = getDatabase(dataDir)
38
+ if (!db) throw new Error('core-memory: database not open — call openDatabase first')
39
+ return db
40
+ }
41
+
42
+ async function _embedFor(db, element, summary) {
43
+ const text = `${element}\n${summary || ''}`.trim()
44
+ if (!text) return null
45
+ const [vec] = await cachedEmbedTextBatch(db, [text])
46
+ return Array.isArray(vec) ? vec : null
47
+ }
48
+
49
+ // Lazy repair of NULL embeddings on existing rows. Runs once per boot or
50
+ // whenever a NULL slips back in via direct SQL. SELECT WHERE embedding IS NULL
51
+ // returns 0 rows on a fully-populated table, so this is a fast no-op.
52
+ function throwIfAborted(signal) {
53
+ if (signal?.aborted) throw signal.reason ?? new Error('aborted')
54
+ }
55
+
56
+ async function _backfillNullEmbeddings(db, options = {}) {
57
+ const signal = options?.signal
58
+ throwIfAborted(signal)
59
+ const r = await db.query(`SELECT id, element, summary FROM core_entries WHERE embedding IS NULL`)
60
+ if (r.rows.length === 0) return 0
61
+ let filled = 0
62
+ for (const row of r.rows) {
63
+ throwIfAborted(signal)
64
+ const vec = await _embedFor(db, row.element, row.summary)
65
+ throwIfAborted(signal)
66
+ if (!vec) continue
67
+ await db.query(
68
+ `UPDATE core_entries SET embedding = $1::halfvec WHERE id = $2 AND embedding IS NULL`,
69
+ [embeddingToSql(vec), row.id],
70
+ )
71
+ filled++
72
+ }
73
+ if (filled > 0) {
74
+ process.stderr.write(`[core-memory] backfilled ${filled} NULL embedding(s) on core_entries\n`)
75
+ }
76
+ return filled
77
+ }
78
+
79
+ export async function backfillCoreEmbeddings(dataDir, options = {}) {
80
+ const db = _getDb(dataDir)
81
+ return await _backfillNullEmbeddings(db, options)
82
+ }
83
+
84
+ // Boot-time invariant restoration: core_entries.id must always be the
85
+ // contiguous sequence 1..N. SERIAL only ever increments, so deleting a row
86
+ // leaves a permanent gap (e.g. 1,2,4,5 after deleting 3). This closes those
87
+ // gaps by resequencing every row globally (across all project_id pools) to
88
+ // 1..N in id order, then realigns the serial so the next INSERT continues from
89
+ // N+1. Deterministic invariant restore — no heuristic, no fallback branch.
90
+ export async function compactCoreIds(dataDir) {
91
+ const db = _getDb(dataDir)
92
+ // Fast no-op guard: a contiguous 1..N table has COUNT == MAX(id). This also
93
+ // covers the empty table (n=0, mx=0) — return before any write.
94
+ const g = await db.query(`SELECT COUNT(*) AS n, COALESCE(MAX(id),0) AS mx FROM core_entries`)
95
+ const n = Number(g.rows[0].n)
96
+ const mx = Number(g.rows[0].mx)
97
+ if (n === mx) return 0
98
+
99
+ // BEGIN transaction on ONE checked-out client — the pool wrapper db.query
100
+ // releases the client per call, so BEGIN/COMMIT must run on one client
101
+ // (same pattern as addCore).
102
+ const client = await checkedConnect(db._pool, 'memory')
103
+ try {
104
+ await client.query('BEGIN')
105
+ // (a) Vacate the low range so shifted ids can't collide with the 1..N
106
+ // target. Offset by current MAX(id): every id becomes id+mx, which is
107
+ // strictly greater than N (N <= mx), so the resequence below is safe.
108
+ await client.query(`UPDATE core_entries SET id = id + $1`, [mx])
109
+ // (b) Resequence ALL rows globally to 1..N preserving id order.
110
+ await client.query(`
111
+ WITH ordered AS (
112
+ SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM core_entries
113
+ )
114
+ UPDATE core_entries c SET id = o.rn FROM ordered o WHERE c.id = o.id`)
115
+ // (c) Realign the serial so the next INSERT continues from N+1. Guarded by
116
+ // the n===mx return above, so MAX(id) is always > 0 here — setval(...,0,true)
117
+ // is invalid and is never reached.
118
+ await client.query(
119
+ `SELECT setval(pg_get_serial_sequence('core_entries','id'),
120
+ (SELECT COALESCE(MAX(id),0) FROM core_entries), true)`)
121
+ await client.query('COMMIT')
122
+ } catch (err) {
123
+ try { await client.query('ROLLBACK') } catch {}
124
+ throw err
125
+ } finally {
126
+ client.release()
127
+ }
128
+ process.stderr.write(`[core-memory] compacted ${n} core id(s)\n`)
129
+ return n
130
+ }
131
+
132
+ async function _findTopKCore(db, projectId, embedding, excludeId, { forUpdate = false } = {}) {
133
+ if (!embedding) return []
134
+ const exclusion = excludeId == null ? '' : 'AND id != $3'
135
+ const sql = `
136
+ SELECT id, element, summary, category, 1 - (embedding <=> $1::halfvec) AS sim
137
+ FROM core_entries
138
+ WHERE embedding IS NOT NULL
139
+ AND project_id IS NOT DISTINCT FROM $2
140
+ ${exclusion}
141
+ ORDER BY embedding <=> $1::halfvec
142
+ LIMIT ${CORE_DEDUP_TOP_K}${forUpdate ? ' FOR UPDATE' : ''}`
143
+ const params = excludeId == null
144
+ ? [embeddingToSql(embedding), projectId]
145
+ : [embeddingToSql(embedding), projectId, excludeId]
146
+ const r = await db.query(sql, params)
147
+ return r.rows.filter(row => Number(row.sim) >= SIM_RECALL)
148
+ }
149
+
150
+ async function _resolveMergeTarget(candidates, incoming) {
151
+ for (const c of candidates) {
152
+ if (await _llmJudgeMerge(c, incoming)) return c
153
+ }
154
+ return null
155
+ }
156
+
157
+ // LLM judge for "is this incoming entry a restatement of the existing one?"
158
+ // One-word reply: merge | distinct. Errors fall back to distinct so a flaky
159
+ // LLM never silently absorbs a fresh registration into an unrelated row.
160
+ async function _llmJudgeMerge(existing, incoming) {
161
+ const prompt =
162
+ `Two user-curated core memory entries below. Are they restating the same rule, fact, or preference (just different wording)? Reply ONE WORD: merge or distinct.\n\n` +
163
+ `EXISTING: ${existing.element} — ${String(existing.summary || '')}\n` +
164
+ `INCOMING: ${incoming.element} — ${String(incoming.summary || '')}`
165
+ try {
166
+ const raw = await callBridgeLlm({
167
+ role: 'cycle2-agent',
168
+ taskType: 'maintenance',
169
+ mode: 'core-merge-judge',
170
+ preset: resolveMaintenancePreset('cycle2'),
171
+ timeout: 30_000,
172
+ cwd: null,
173
+ }, prompt)
174
+ return String(raw ?? '').trim().toLowerCase().startsWith('merge')
175
+ } catch (err) {
176
+ process.stderr.write(`[core-memory] LLM merge judge failed: ${err.message}\n`)
177
+ return false
178
+ }
179
+ }
180
+
181
+ export async function listCore(dataDir, projectId = null) {
182
+ const db = _getDb(dataDir)
183
+ const cols = `id, element, summary, category, project_id, created_at, updated_at`
184
+ if (projectId === '*') {
185
+ const r = await db.query(`SELECT ${cols} FROM core_entries ORDER BY project_id NULLS FIRST, id ASC`)
186
+ return r.rows
187
+ }
188
+ if (projectId === null) {
189
+ const r = await db.query(`SELECT ${cols} FROM core_entries WHERE project_id IS NULL ORDER BY id ASC`)
190
+ return r.rows
191
+ }
192
+ const r = await db.query(`SELECT ${cols} FROM core_entries WHERE project_id = $1 ORDER BY id ASC`, [projectId])
193
+ return r.rows
194
+ }
195
+
196
+ export async function addCore(dataDir, { element, summary, category }, projectId) {
197
+ if (projectId === undefined) throw new Error('addCore: projectId required — pass null for COMMON pool, or slug string for scoped pool')
198
+ const el = trimOrNull(element)
199
+ const sm = trimOrNull(summary) ?? el
200
+ if (!el || !sm) throw new Error('add requires element and summary')
201
+ if (sm.length > CORE_SUMMARY_MAX) {
202
+ throw new Error(`core summary too long (${sm.length} chars, max ${CORE_SUMMARY_MAX}) — core memory must be 1 fact in 1-2 sentences; procedures, multi-step, or code belong in recap or docs. Compress and retry.`)
203
+ }
204
+ const cat = (trimOrNull(category) ?? 'fact').toLowerCase()
205
+ if (!VALID_CAT.has(cat)) {
206
+ throw new Error(`invalid category "${cat}". Valid: ${[...VALID_CAT].join(', ')}`)
207
+ }
208
+ const db = _getDb(dataDir)
209
+ const now = Date.now()
210
+ await _backfillNullEmbeddings(db)
211
+ const embedding = await _embedFor(db, el, sm)
212
+
213
+ const client = await checkedConnect(db._pool, 'memory')
214
+ try {
215
+ await client.query('BEGIN')
216
+ await client.query(`SET LOCAL lock_timeout = '5s'`)
217
+ const poolKey = `core:${projectId == null ? 'COMMON' : projectId}`
218
+ await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [poolKey])
219
+ const candidates = await _findTopKCore(client, projectId, embedding, null, { forUpdate: true })
220
+ const mergeTarget = await _resolveMergeTarget(candidates, { element: el, summary: sm })
221
+ if (mergeTarget) {
222
+ const r = await client.query(
223
+ `UPDATE core_entries
224
+ SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5
225
+ WHERE id = $6
226
+ RETURNING id, element, summary, category, project_id, created_at, updated_at`,
227
+ [el, sm, cat, embedding ? embeddingToSql(embedding) : null, now, mergeTarget.id],
228
+ )
229
+ await client.query('COMMIT')
230
+ const row = r.rows[0]
231
+ return { ...row, merged_with: mergeTarget.id, sim: Number(mergeTarget.sim).toFixed(3) }
232
+ }
233
+ let r
234
+ try {
235
+ r = await client.query(
236
+ `INSERT INTO core_entries(element, summary, category, project_id, embedding, created_at, updated_at)
237
+ VALUES ($1, $2, $3, $4, $5::halfvec, $6, $7)
238
+ RETURNING id, element, summary, category, project_id, created_at, updated_at`,
239
+ [el, sm, cat, projectId, embedding ? embeddingToSql(embedding) : null, now, now],
240
+ )
241
+ } catch (err) {
242
+ if (err.code === '23505') {
243
+ throw new Error(`core entry already exists: project=${projectId ?? 'COMMON'} element=${JSON.stringify(el.slice(0, 200))}`)
244
+ }
245
+ throw err
246
+ }
247
+ await client.query('COMMIT')
248
+ return r.rows[0]
249
+ } catch (err) {
250
+ try { await client.query('ROLLBACK') } catch {}
251
+ throw err
252
+ } finally {
253
+ client.release()
254
+ }
255
+ }
256
+
257
+ export async function editCore(dataDir, id, patch) {
258
+ const numId = Number(id)
259
+ if (!Number.isInteger(numId) || numId <= 0) throw new Error('integer id > 0 required')
260
+ const db = _getDb(dataDir)
261
+ const cur = (await db.query(`SELECT * FROM core_entries WHERE id = $1`, [numId])).rows[0]
262
+ if (!cur) throw new Error(`no entry with id=${numId}`)
263
+ const newElement = trimOrNull(patch.element) ?? cur.element
264
+ const newSummary = trimOrNull(patch.summary) ?? cur.summary
265
+ const newCategoryRaw = trimOrNull(patch.category)
266
+ const newCategory = newCategoryRaw ? newCategoryRaw.toLowerCase() : cur.category
267
+ if (!VALID_CAT.has(newCategory)) {
268
+ throw new Error(`invalid category "${newCategory}". Valid: ${[...VALID_CAT].join(', ')}`)
269
+ }
270
+ if (newElement === cur.element && newSummary === cur.summary && newCategory === cur.category) {
271
+ throw new Error('no change')
272
+ }
273
+ if (newSummary && newSummary.length > CORE_SUMMARY_MAX) {
274
+ throw new Error(`core summary too long (${newSummary.length} chars, max ${CORE_SUMMARY_MAX}) — core memory must be 1 fact in 1-2 sentences; procedures, multi-step, or code belong in recap or docs. Compress and retry.`)
275
+ }
276
+ const now = Date.now()
277
+ const textChanged = newElement !== cur.element || newSummary !== cur.summary
278
+ if (!textChanged) {
279
+ await db.query(
280
+ `UPDATE core_entries SET category = $1, updated_at = $2 WHERE id = $3`,
281
+ [newCategory, now, numId],
282
+ )
283
+ return { ...cur, element: newElement, summary: newSummary, category: newCategory, updated_at: now }
284
+ }
285
+
286
+ await _backfillNullEmbeddings(db)
287
+ const embedding = await _embedFor(db, newElement, newSummary)
288
+ const client = await checkedConnect(db._pool, 'memory')
289
+ try {
290
+ await client.query('BEGIN')
291
+ await client.query(`SET LOCAL lock_timeout = '5s'`)
292
+ const poolKey = `core:${cur.project_id == null ? 'COMMON' : cur.project_id}`
293
+ await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [poolKey])
294
+ const candidates = await _findTopKCore(client, cur.project_id, embedding, numId, { forUpdate: true })
295
+ const mergeTarget = await _resolveMergeTarget(candidates, { element: newElement, summary: newSummary })
296
+ if (mergeTarget) {
297
+ const r = await client.query(
298
+ `UPDATE core_entries
299
+ SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5
300
+ WHERE id = $6
301
+ RETURNING id, element, summary, category, project_id, created_at, updated_at`,
302
+ [newElement, newSummary, newCategory, embedding ? embeddingToSql(embedding) : null, now, mergeTarget.id],
303
+ )
304
+ await client.query(`DELETE FROM core_entries WHERE id = $1`, [numId])
305
+ await client.query('COMMIT')
306
+ const row = r.rows[0]
307
+ return { ...row, merged_from: numId, merged_with: mergeTarget.id, sim: Number(mergeTarget.sim).toFixed(3) }
308
+ }
309
+ await client.query(
310
+ `UPDATE core_entries SET element = $1, summary = $2, category = $3, embedding = $4::halfvec, updated_at = $5 WHERE id = $6`,
311
+ [newElement, newSummary, newCategory, embedding ? embeddingToSql(embedding) : null, now, numId],
312
+ )
313
+ await client.query('COMMIT')
314
+ return { ...cur, element: newElement, summary: newSummary, category: newCategory, updated_at: now }
315
+ } catch (err) {
316
+ try { await client.query('ROLLBACK') } catch {}
317
+ throw err
318
+ } finally {
319
+ client.release()
320
+ }
321
+ }
322
+
323
+ export async function deleteCore(dataDir, id) {
324
+ const numId = Number(id)
325
+ if (!Number.isInteger(numId) || numId <= 0) throw new Error('integer id > 0 required')
326
+ const db = _getDb(dataDir)
327
+ const r = await db.query(`DELETE FROM core_entries WHERE id = $1 RETURNING *`, [numId])
328
+ if (r.rows.length === 0) throw new Error(`no entry with id=${numId}`)
329
+ return r.rows[0]
330
+ }