@kinqs/brainrouter-mcp-server 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/.env.example +144 -0
  2. package/README.md +56 -0
  3. package/agents/README.md +120 -0
  4. package/agents/code-reviewer.md +97 -0
  5. package/agents/security-auditor.md +101 -0
  6. package/agents/test-engineer.md +95 -0
  7. package/dist/__tests__/agent_mode.test.d.ts +1 -0
  8. package/dist/__tests__/api-routes.test.d.ts +1 -0
  9. package/dist/__tests__/api-routes.test.js +170 -0
  10. package/dist/__tests__/crypto.test.d.ts +1 -0
  11. package/dist/__tests__/crypto.test.js +28 -0
  12. package/dist/__tests__/host-integrations.test.d.ts +1 -0
  13. package/dist/__tests__/host-integrations.test.js +82 -0
  14. package/dist/__tests__/integration.test.d.ts +1 -0
  15. package/dist/__tests__/integration.test.js +50 -0
  16. package/dist/__tests__/loader.test.d.ts +1 -0
  17. package/dist/__tests__/loader.test.js +89 -0
  18. package/dist/__tests__/neural-spark.test.d.ts +1 -0
  19. package/dist/__tests__/neural-spark.test.js +112 -0
  20. package/dist/__tests__/pagination.test.d.ts +1 -0
  21. package/dist/__tests__/pagination.test.js +23 -0
  22. package/dist/__tests__/redaction.test.d.ts +1 -0
  23. package/dist/__tests__/redaction.test.js +17 -0
  24. package/dist/__tests__/registry.test.d.ts +1 -0
  25. package/dist/__tests__/registry.test.js +56 -0
  26. package/dist/__tests__/retry.test.d.ts +1 -0
  27. package/dist/__tests__/retry.test.js +30 -0
  28. package/dist/__tests__/skill-activation.test.d.ts +1 -0
  29. package/dist/__tests__/skill-activation.test.js +112 -0
  30. package/dist/__tests__/working-memory.test.d.ts +1 -0
  31. package/dist/__tests__/working-memory.test.js +200 -0
  32. package/dist/__tests__/workspace-paths.test.d.ts +1 -0
  33. package/dist/__tests__/workspace-paths.test.js +56 -0
  34. package/dist/__tests__/writer.test.d.ts +1 -0
  35. package/dist/__tests__/writer.test.js +94 -0
  36. package/dist/api/auth/crypto.d.ts +4 -0
  37. package/dist/api/auth/crypto.js +54 -0
  38. package/dist/api/middleware/auth.d.ts +12 -0
  39. package/dist/api/middleware/auth.js +90 -0
  40. package/dist/api/pagination.d.ts +18 -0
  41. package/dist/api/pagination.js +32 -0
  42. package/dist/api/routes/auth.d.ts +1 -0
  43. package/dist/api/routes/auth.js +130 -0
  44. package/dist/api/routes/chat-completions.d.ts +7 -0
  45. package/dist/api/routes/chat-completions.js +474 -0
  46. package/dist/api/routes/contradictions.d.ts +1 -0
  47. package/dist/api/routes/contradictions.js +28 -0
  48. package/dist/api/routes/evidence.d.ts +1 -0
  49. package/dist/api/routes/evidence.js +59 -0
  50. package/dist/api/routes/governance.d.ts +1 -0
  51. package/dist/api/routes/governance.js +95 -0
  52. package/dist/api/routes/graph.d.ts +1 -0
  53. package/dist/api/routes/graph.js +25 -0
  54. package/dist/api/routes/hooks.d.ts +1 -0
  55. package/dist/api/routes/hooks.js +88 -0
  56. package/dist/api/routes/memories.d.ts +1 -0
  57. package/dist/api/routes/memories.js +92 -0
  58. package/dist/api/routes/persona.d.ts +1 -0
  59. package/dist/api/routes/persona.js +9 -0
  60. package/dist/api/routes/scenes.d.ts +1 -0
  61. package/dist/api/routes/scenes.js +35 -0
  62. package/dist/api/routes/skills.d.ts +1 -0
  63. package/dist/api/routes/skills.js +14 -0
  64. package/dist/api/routes/stats.d.ts +1 -0
  65. package/dist/api/routes/stats.js +8 -0
  66. package/dist/api/routes/users.d.ts +1 -0
  67. package/dist/api/routes/users.js +82 -0
  68. package/dist/api/routes/working.d.ts +1 -0
  69. package/dist/api/routes/working.js +88 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +492 -0
  72. package/dist/integrations/claude-code.d.ts +12 -0
  73. package/dist/integrations/claude-code.js +35 -0
  74. package/dist/integrations/codex.d.ts +12 -0
  75. package/dist/integrations/codex.js +34 -0
  76. package/dist/integrations/generic-mcp.d.ts +52 -0
  77. package/dist/integrations/generic-mcp.js +118 -0
  78. package/dist/loader.d.ts +29 -0
  79. package/dist/loader.js +200 -0
  80. package/dist/memory/capture.d.ts +35 -0
  81. package/dist/memory/capture.js +230 -0
  82. package/dist/memory/config.d.ts +2 -0
  83. package/dist/memory/config.js +3 -0
  84. package/dist/memory/engine.d.ts +203 -0
  85. package/dist/memory/engine.js +626 -0
  86. package/dist/memory/llm-semaphore.d.ts +41 -0
  87. package/dist/memory/llm-semaphore.js +81 -0
  88. package/dist/memory/memory-type-config.d.ts +11 -0
  89. package/dist/memory/memory-type-config.js +65 -0
  90. package/dist/memory/pipeline/cognitive-contradiction.d.ts +7 -0
  91. package/dist/memory/pipeline/cognitive-contradiction.js +59 -0
  92. package/dist/memory/pipeline/cognitive-dedup.d.ts +23 -0
  93. package/dist/memory/pipeline/cognitive-dedup.js +38 -0
  94. package/dist/memory/pipeline/cognitive-extractor.d.ts +21 -0
  95. package/dist/memory/pipeline/cognitive-extractor.js +183 -0
  96. package/dist/memory/pipeline/contextual-focus-builder.d.ts +13 -0
  97. package/dist/memory/pipeline/contextual-focus-builder.js +135 -0
  98. package/dist/memory/pipeline/focus-direction-shift.d.ts +10 -0
  99. package/dist/memory/pipeline/focus-direction-shift.js +27 -0
  100. package/dist/memory/pipeline/graph-builder.d.ts +11 -0
  101. package/dist/memory/pipeline/graph-builder.js +88 -0
  102. package/dist/memory/pipeline/graph-recall.d.ts +13 -0
  103. package/dist/memory/pipeline/graph-recall.js +55 -0
  104. package/dist/memory/pipeline/identity-distiller.d.ts +15 -0
  105. package/dist/memory/pipeline/identity-distiller.js +40 -0
  106. package/dist/memory/pipeline/l1-contradiction.d.ts +7 -0
  107. package/dist/memory/pipeline/l1-contradiction.js +66 -0
  108. package/dist/memory/pipeline/l1-dedup.d.ts +23 -0
  109. package/dist/memory/pipeline/l1-dedup.js +39 -0
  110. package/dist/memory/pipeline/l1-extractor.d.ts +21 -0
  111. package/dist/memory/pipeline/l1-extractor.js +180 -0
  112. package/dist/memory/pipeline/l2-direction-shift.d.ts +10 -0
  113. package/dist/memory/pipeline/l2-direction-shift.js +27 -0
  114. package/dist/memory/pipeline/l2-scene.d.ts +15 -0
  115. package/dist/memory/pipeline/l2-scene.js +140 -0
  116. package/dist/memory/pipeline/l3-distiller.d.ts +15 -0
  117. package/dist/memory/pipeline/l3-distiller.js +40 -0
  118. package/dist/memory/pipeline/neural-spark.d.ts +27 -0
  119. package/dist/memory/pipeline/neural-spark.js +78 -0
  120. package/dist/memory/pipeline/skill-prewarm.d.ts +63 -0
  121. package/dist/memory/pipeline/skill-prewarm.js +127 -0
  122. package/dist/memory/pipeline/task-queue.d.ts +54 -0
  123. package/dist/memory/pipeline/task-queue.js +117 -0
  124. package/dist/memory/prompts/cognitive-contradiction.d.ts +1 -0
  125. package/dist/memory/prompts/cognitive-contradiction.js +25 -0
  126. package/dist/memory/prompts/cognitive-extraction.d.ts +10 -0
  127. package/dist/memory/prompts/cognitive-extraction.js +114 -0
  128. package/dist/memory/prompts/core-identity.d.ts +6 -0
  129. package/dist/memory/prompts/core-identity.js +60 -0
  130. package/dist/memory/prompts/focus-direction-shift.d.ts +5 -0
  131. package/dist/memory/prompts/focus-direction-shift.js +32 -0
  132. package/dist/memory/prompts/focus-scene-cluster.d.ts +2 -0
  133. package/dist/memory/prompts/focus-scene-cluster.js +33 -0
  134. package/dist/memory/prompts/focus-scene.d.ts +7 -0
  135. package/dist/memory/prompts/focus-scene.js +40 -0
  136. package/dist/memory/prompts/graph-extraction-batch.d.ts +14 -0
  137. package/dist/memory/prompts/graph-extraction-batch.js +54 -0
  138. package/dist/memory/prompts/graph-extraction.d.ts +2 -0
  139. package/dist/memory/prompts/graph-extraction.js +53 -0
  140. package/dist/memory/prompts/l1-contradiction-batch.d.ts +16 -0
  141. package/dist/memory/prompts/l1-contradiction-batch.js +47 -0
  142. package/dist/memory/prompts/l1-contradiction.d.ts +1 -0
  143. package/dist/memory/prompts/l1-contradiction.js +25 -0
  144. package/dist/memory/prompts/l1-extraction.d.ts +10 -0
  145. package/dist/memory/prompts/l1-extraction.js +114 -0
  146. package/dist/memory/prompts/l2-direction-shift.d.ts +5 -0
  147. package/dist/memory/prompts/l2-direction-shift.js +32 -0
  148. package/dist/memory/prompts/l2-scene-cluster.d.ts +2 -0
  149. package/dist/memory/prompts/l2-scene-cluster.js +33 -0
  150. package/dist/memory/prompts/l2-scene.d.ts +7 -0
  151. package/dist/memory/prompts/l2-scene.js +40 -0
  152. package/dist/memory/prompts/l3-persona.d.ts +6 -0
  153. package/dist/memory/prompts/l3-persona.js +60 -0
  154. package/dist/memory/recall.d.ts +47 -0
  155. package/dist/memory/recall.js +427 -0
  156. package/dist/memory/redaction.d.ts +1 -0
  157. package/dist/memory/redaction.js +24 -0
  158. package/dist/memory/retry.d.ts +13 -0
  159. package/dist/memory/retry.js +53 -0
  160. package/dist/memory/scheduler.d.ts +9 -0
  161. package/dist/memory/scheduler.js +16 -0
  162. package/dist/memory/skill-hints-loader.d.ts +30 -0
  163. package/dist/memory/skill-hints-loader.js +100 -0
  164. package/dist/memory/store/embedding.d.ts +16 -0
  165. package/dist/memory/store/embedding.js +68 -0
  166. package/dist/memory/store/reranker.d.ts +24 -0
  167. package/dist/memory/store/reranker.js +83 -0
  168. package/dist/memory/store/sqlite.d.ts +167 -0
  169. package/dist/memory/store/sqlite.js +1816 -0
  170. package/dist/memory/store/types.d.ts +101 -0
  171. package/dist/memory/store/types.js +1 -0
  172. package/dist/memory/types.d.ts +207 -0
  173. package/dist/memory/types.js +7 -0
  174. package/dist/memory/validation.d.ts +441 -0
  175. package/dist/memory/validation.js +129 -0
  176. package/dist/memory/working/canvas.d.ts +5 -0
  177. package/dist/memory/working/canvas.js +43 -0
  178. package/dist/memory/working/offload.d.ts +71 -0
  179. package/dist/memory/working/offload.js +211 -0
  180. package/dist/memory/working/step-log.d.ts +16 -0
  181. package/dist/memory/working/step-log.js +35 -0
  182. package/dist/registry.d.ts +34 -0
  183. package/dist/registry.js +305 -0
  184. package/dist/resolver.d.ts +17 -0
  185. package/dist/resolver.js +126 -0
  186. package/dist/scripts/validate-foreign-workspace-path.d.ts +1 -0
  187. package/dist/scripts/validate-foreign-workspace-path.js +39 -0
  188. package/dist/tools/agent_memory_tools.d.ts +485 -0
  189. package/dist/tools/agent_memory_tools.js +793 -0
  190. package/dist/tools/create_skill.d.ts +46 -0
  191. package/dist/tools/create_skill.js +46 -0
  192. package/dist/tools/get_doc.d.ts +21 -0
  193. package/dist/tools/get_doc.js +24 -0
  194. package/dist/tools/get_persona.d.ts +15 -0
  195. package/dist/tools/get_persona.js +20 -0
  196. package/dist/tools/get_reference.d.ts +15 -0
  197. package/dist/tools/get_reference.js +20 -0
  198. package/dist/tools/get_skill.d.ts +34 -0
  199. package/dist/tools/get_skill.js +65 -0
  200. package/dist/tools/get_template_doc.d.ts +21 -0
  201. package/dist/tools/get_template_doc.js +24 -0
  202. package/dist/tools/list_docs.d.ts +15 -0
  203. package/dist/tools/list_docs.js +16 -0
  204. package/dist/tools/list_skills.d.ts +18 -0
  205. package/dist/tools/list_skills.js +17 -0
  206. package/dist/tools/list_template_docs.d.ts +15 -0
  207. package/dist/tools/list_template_docs.js +16 -0
  208. package/dist/tools/memory-engineering.d.ts +225 -0
  209. package/dist/tools/memory-engineering.js +284 -0
  210. package/dist/tools/memory-explain.d.ts +34 -0
  211. package/dist/tools/memory-explain.js +109 -0
  212. package/dist/tools/memory-governance.d.ts +171 -0
  213. package/dist/tools/memory-governance.js +224 -0
  214. package/dist/tools/memory-hooks.d.ts +67 -0
  215. package/dist/tools/memory-hooks.js +102 -0
  216. package/dist/tools/memory-working.d.ts +98 -0
  217. package/dist/tools/memory-working.js +101 -0
  218. package/dist/tools/memory_capture_turn.d.ts +66 -0
  219. package/dist/tools/memory_capture_turn.js +85 -0
  220. package/dist/tools/memory_consolidate.d.ts +55 -0
  221. package/dist/tools/memory_consolidate.js +176 -0
  222. package/dist/tools/memory_contradictions.d.ts +53 -0
  223. package/dist/tools/memory_contradictions.js +52 -0
  224. package/dist/tools/memory_graph_query.d.ts +51 -0
  225. package/dist/tools/memory_graph_query.js +35 -0
  226. package/dist/tools/memory_mark_cited.d.ts +43 -0
  227. package/dist/tools/memory_mark_cited.js +63 -0
  228. package/dist/tools/memory_recall.d.ts +77 -0
  229. package/dist/tools/memory_recall.js +81 -0
  230. package/dist/tools/memory_register_skill_hints.d.ts +49 -0
  231. package/dist/tools/memory_register_skill_hints.js +55 -0
  232. package/dist/tools/memory_resolve_session.d.ts +24 -0
  233. package/dist/tools/memory_resolve_session.js +133 -0
  234. package/dist/tools/memory_search.d.ts +146 -0
  235. package/dist/tools/memory_search.js +84 -0
  236. package/dist/tools/search_skills.d.ts +18 -0
  237. package/dist/tools/search_skills.js +17 -0
  238. package/dist/tools/update_doc.d.ts +24 -0
  239. package/dist/tools/update_doc.js +35 -0
  240. package/dist/tools/update_skill.d.ts +30 -0
  241. package/dist/tools/update_skill.js +80 -0
  242. package/dist/types.d.ts +81 -0
  243. package/dist/types.js +4 -0
  244. package/dist/writer.d.ts +30 -0
  245. package/dist/writer.js +220 -0
  246. package/docs/TEMPLATE ONLY +1 -0
  247. package/docs/api/API.md +64 -0
  248. package/docs/api/security/SECURITY.md +58 -0
  249. package/docs/deployment/DockerDeployment.md +30 -0
  250. package/docs/design/Design.md +59 -0
  251. package/docs/design/themes/apple.md +101 -0
  252. package/docs/design/themes/dieter-grid.md +100 -0
  253. package/docs/design/themes/gallery-white.md +100 -0
  254. package/docs/design/themes/pinterest.md +101 -0
  255. package/docs/design/themes/realty-open-house.md +101 -0
  256. package/docs/design/themes/vodafone.md +101 -0
  257. package/docs/hooks/Hooks.md +30 -0
  258. package/docs/schema/Schema.md +35 -0
  259. package/docs/strategy/ScalingStrategy.md +19 -0
  260. package/package.json +88 -0
  261. package/references/accessibility-checklist.md +160 -0
  262. package/references/orchestration-patterns.md +370 -0
  263. package/references/performance-checklist.md +153 -0
  264. package/references/security-checklist.md +134 -0
  265. package/references/testing-patterns.md +236 -0
  266. package/skills/agent/adr-skill/SKILL.md +299 -0
  267. package/skills/agent/agentic-engineering-workflow/SKILL.md +95 -0
  268. package/skills/agent/bootstrap-skill/SKILL.md +103 -0
  269. package/skills/agent/context-engineering/SKILL.md +307 -0
  270. package/skills/agent/debugging-and-error-recovery/SKILL.md +308 -0
  271. package/skills/agent/developer-growth-analysis/SKILL.md +328 -0
  272. package/skills/agent/doubt-driven-skill/SKILL.md +249 -0
  273. package/skills/agent/handover-skill/SKILL.md +112 -0
  274. package/skills/agent/idea-refine-skill/SKILL.md +185 -0
  275. package/skills/agent/idea-refine-skill/examples.md +238 -0
  276. package/skills/agent/idea-refine-skill/frameworks.md +99 -0
  277. package/skills/agent/idea-refine-skill/refinement-criteria.md +113 -0
  278. package/skills/agent/interview-skill/SKILL.md +226 -0
  279. package/skills/agent/planning-skill/SKILL.md +270 -0
  280. package/skills/agent/skill-authoring/SKILL.md +189 -0
  281. package/skills/agent/source-driven-skill/SKILL.md +197 -0
  282. package/skills/agent/spec-driven-skill/SKILL.md +221 -0
  283. package/skills/agent/sync-skill/SKILL.md +92 -0
  284. package/skills/agent/using-agent-skills/SKILL.md +189 -0
  285. package/skills/api/a11y-skill/SKILL.md +88 -0
  286. package/skills/api/api-skill/SKILL.md +123 -0
  287. package/skills/api/auth-skill/SKILL.md +80 -0
  288. package/skills/api/debug-skill/SKILL.md +535 -0
  289. package/skills/api/performance-skill/SKILL.md +100 -0
  290. package/skills/api/testing-skill/SKILL.md +100 -0
  291. package/skills/codebase/code-review-and-quality/SKILL.md +228 -0
  292. package/skills/codebase/code-simplification/SKILL.md +352 -0
  293. package/skills/codebase/code-structure-cleanup/SKILL.md +142 -0
  294. package/skills/codebase/concerns-skill/SKILL.md +89 -0
  295. package/skills/codebase/conventions-skill/SKILL.md +95 -0
  296. package/skills/codebase/doc-management-skill/SKILL.md +47 -0
  297. package/skills/codebase/git-workflow-skill/SKILL.md +312 -0
  298. package/skills/communication/1-3-1-rule/SKILL.md +120 -0
  299. package/skills/design/brutalist-skill/SKILL.md +131 -0
  300. package/skills/design/concept-diagrams/SKILL.md +387 -0
  301. package/skills/design/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  302. package/skills/design/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  303. package/skills/design/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  304. package/skills/design/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  305. package/skills/design/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  306. package/skills/design/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  307. package/skills/design/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  308. package/skills/design/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  309. package/skills/design/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  310. package/skills/design/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  311. package/skills/design/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  312. package/skills/design/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  313. package/skills/design/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  314. package/skills/design/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  315. package/skills/design/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  316. package/skills/design/concept-diagrams/references/dashboard-patterns.md +43 -0
  317. package/skills/design/concept-diagrams/references/infrastructure-patterns.md +144 -0
  318. package/skills/design/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  319. package/skills/design/concept-diagrams/templates/template.html +174 -0
  320. package/skills/design/gpt-tasteskill/SKILL.md +114 -0
  321. package/skills/design/minimalist-skill/SKILL.md +116 -0
  322. package/skills/design/output-skill/SKILL.md +87 -0
  323. package/skills/design/redesign-skill/SKILL.md +213 -0
  324. package/skills/design/soft-skill/SKILL.md +132 -0
  325. package/skills/design/stitch-skill/EXAMPLE.md +121 -0
  326. package/skills/design/stitch-skill/SKILL.md +222 -0
  327. package/skills/design/taste-skill/SKILL.md +269 -0
  328. package/skills/devops/ci-cd-skill/SKILL.md +402 -0
  329. package/skills/devops/docker-skill/SKILL.md +297 -0
  330. package/skills/devops/domain-skill/SKILL.md +234 -0
  331. package/skills/lifecycle/changelog-generator/SKILL.md +135 -0
  332. package/skills/lifecycle/incremental-skill/SKILL.md +257 -0
  333. package/skills/lifecycle/migration-skill/SKILL.md +218 -0
  334. package/skills/lifecycle/shipping-skill/SKILL.md +321 -0
  335. package/skills/memory/agent-memory/SKILL.md +122 -0
  336. package/skills/qa/browser-testing-skill/SKILL.md +314 -0
  337. package/skills/ux/adversarial-ux-skill/SKILL.md +168 -0
@@ -0,0 +1,140 @@
1
+ import { L2_SCENE_SYSTEM_PROMPT, formatL2ScenePrompt } from "../prompts/l2-scene.js";
2
+ import { L2_SCENE_CLUSTER_SYSTEM_PROMPT, formatSceneClusterPrompt } from "../prompts/l2-scene-cluster.js";
3
+ import { L2_MAX_SCENES } from "../scheduler.js";
4
+ import crypto from "node:crypto";
5
+ async function canonicalizeSceneNames(params) {
6
+ const { userId, store, llmRunner } = params;
7
+ const sceneNames = store.getDistinctSceneNames(userId);
8
+ if (sceneNames.length < 2)
9
+ return;
10
+ try {
11
+ const rawCluster = await llmRunner.run({
12
+ prompt: formatSceneClusterPrompt(sceneNames),
13
+ systemPrompt: L2_SCENE_CLUSTER_SYSTEM_PROMPT,
14
+ taskId: "l2-scene-clustering",
15
+ timeoutMs: 45_000,
16
+ });
17
+ const jsonMatch = rawCluster.match(/\[[\s\S]*\]/);
18
+ if (!jsonMatch)
19
+ return;
20
+ const clusters = JSON.parse(jsonMatch[0]);
21
+ if (!Array.isArray(clusters))
22
+ return;
23
+ for (const cluster of clusters) {
24
+ const canonical = String(cluster.canonical || "").trim();
25
+ const aliases = Array.isArray(cluster.aliases) ? cluster.aliases.map((a) => String(a).trim()) : [];
26
+ if (!canonical || aliases.length === 0)
27
+ continue;
28
+ for (const alias of aliases) {
29
+ if (alias === canonical)
30
+ continue;
31
+ store.renameSceneInL1Records(userId, alias, canonical);
32
+ }
33
+ }
34
+ }
35
+ catch (err) {
36
+ console.error(`[BrainRouter] Scene canonicalization failed for "${userId}":`, err.message);
37
+ }
38
+ }
39
+ /**
40
+ * L2 Scene Pipeline
41
+ * Groups L1 memories by scene_name and asks the LLM to produce
42
+ * a Markdown summary for each scene. Updates heat scores.
43
+ */
44
+ export async function distillScenes(params) {
45
+ const { userId, store, llmRunner } = params;
46
+ // Run scene canonicalization/clustering pass to prevent cold-start fragmentation
47
+ await canonicalizeSceneNames({ userId, store, llmRunner });
48
+ // Decay all existing heat scores (each distillation cycle = time passing)
49
+ store.decayL2HeatScores(userId);
50
+ const sceneNames = store.getDistinctSceneNames(userId);
51
+ if (sceneNames.length === 0) {
52
+ return { scenesDistilled: 0, sceneNames: [] };
53
+ }
54
+ const now = new Date().toISOString();
55
+ const distilled = [];
56
+ // Fetch existing L2 scene names up-front so the prompt can avoid near-duplicates
57
+ const existingL2SceneNames = store.getTopL2Scenes(userId, 50).map(s => s.sceneName);
58
+ for (const sceneName of sceneNames) {
59
+ const l1s = store.getL1sByScene(userId, sceneName, 30);
60
+ if (l1s.length === 0)
61
+ continue;
62
+ let summaryMd;
63
+ try {
64
+ summaryMd = await llmRunner.run({
65
+ prompt: formatL2ScenePrompt(sceneName, l1s, existingL2SceneNames.filter(n => n !== sceneName)),
66
+ systemPrompt: L2_SCENE_SYSTEM_PROMPT,
67
+ taskId: "l2-scene-distillation",
68
+ timeoutMs: 60_000,
69
+ });
70
+ }
71
+ catch (err) {
72
+ console.error(`[BrainRouter] L2 scene distillation failed for "${sceneName}":`, err.message);
73
+ continue;
74
+ }
75
+ const existing = store.getL2SceneByName(userId, sceneName);
76
+ const record = {
77
+ id: existing?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
78
+ userId,
79
+ sceneName,
80
+ summaryMd: summaryMd.trim(),
81
+ heatScore: existing ? Math.min(100, existing.heatScore + 30) : 100,
82
+ lastActiveTime: now,
83
+ createdTime: existing?.createdTime ?? now,
84
+ updatedTime: now,
85
+ };
86
+ store.upsertL2Scene(record);
87
+ distilled.push(sceneName);
88
+ }
89
+ // Auto-merge cold scenes if we exceed the max threshold
90
+ await mergeScenes({ userId, store, llmRunner });
91
+ console.error(`[BrainRouter] L2 distilled ${distilled.length} scene(s) for user "${userId}".`);
92
+ return { scenesDistilled: distilled.length, sceneNames: distilled };
93
+ }
94
+ async function mergeScenes(params) {
95
+ const { userId, store, llmRunner } = params;
96
+ const sceneCount = store.getL2SceneCount(userId);
97
+ if (sceneCount < L2_MAX_SCENES)
98
+ return;
99
+ const overflow = sceneCount - L2_MAX_SCENES + 1;
100
+ const coldScenes = store.getColdL2Scenes(userId, overflow + 3);
101
+ // Need at least 2 cold scenes to meaningfully merge
102
+ if (coldScenes.length < 2)
103
+ return;
104
+ // Skip any existing [Archived] scene from the merge input — we'll update it separately
105
+ const archiveSceneName = "[Archived]";
106
+ const scenesToMerge = coldScenes.filter(s => s.sceneName !== archiveSceneName);
107
+ if (scenesToMerge.length < 2)
108
+ return;
109
+ const sceneSummaries = scenesToMerge.map(s => `## ${s.sceneName}\n${s.summaryMd}`).join("\n\n");
110
+ let unifiedSummary;
111
+ try {
112
+ unifiedSummary = await llmRunner.run({
113
+ prompt: `Merge the following old scene summaries into a single concise "Archived Scenes" overview.\n\n${sceneSummaries}\n\nOutput only the Markdown summary. Be concise — no preamble.`,
114
+ systemPrompt: L2_SCENE_SYSTEM_PROMPT,
115
+ taskId: "l2-scene-merge",
116
+ timeoutMs: 60_000,
117
+ });
118
+ }
119
+ catch (err) {
120
+ console.error(`[BrainRouter] L2 scene merge failed for "${userId}":`, err.message);
121
+ return;
122
+ }
123
+ const now = new Date().toISOString();
124
+ // Look up any pre-existing [Archived] scene
125
+ const existingArchive = store.getL2SceneByName(userId, archiveSceneName);
126
+ const record = {
127
+ id: existingArchive?.id ?? `l2_${crypto.randomBytes(6).toString("hex")}`,
128
+ userId,
129
+ sceneName: archiveSceneName,
130
+ summaryMd: unifiedSummary.trim(),
131
+ heatScore: 10,
132
+ lastActiveTime: now,
133
+ createdTime: existingArchive?.createdTime ?? now,
134
+ updatedTime: now,
135
+ };
136
+ store.upsertL2Scene(record);
137
+ const idsToDelete = scenesToMerge.map(s => s.id);
138
+ store.deleteL2Scenes(userId, idsToDelete);
139
+ console.error(`[BrainRouter] L2 auto-merge: merged ${idsToDelete.length} cold scenes into "${archiveSceneName}".`);
140
+ }
@@ -0,0 +1,15 @@
1
+ import type { IMemoryStore } from "@brainrouter/types";
2
+ import type { LLMRunner } from "@brainrouter/types";
3
+ /**
4
+ * L3 Persona Distillation Pipeline
5
+ * Scans ALL persona + instruction L1 memories across all sessions
6
+ * for a user and synthesizes a durable Narrative Profile.
7
+ */
8
+ export declare function distillPersona(params: {
9
+ userId: string;
10
+ store: IMemoryStore;
11
+ llmRunner: LLMRunner;
12
+ }): Promise<{
13
+ success: boolean;
14
+ personaMd?: string;
15
+ }>;
@@ -0,0 +1,40 @@
1
+ import { L3_PERSONA_SYSTEM_PROMPT, formatL3PersonaPrompt } from "../prompts/l3-persona.js";
2
+ /**
3
+ * L3 Persona Distillation Pipeline
4
+ * Scans ALL persona + instruction L1 memories across all sessions
5
+ * for a user and synthesizes a durable Narrative Profile.
6
+ */
7
+ export async function distillPersona(params) {
8
+ const { userId, store, llmRunner } = params;
9
+ // Cross-session: fetch all persona + instruction L1s for this user
10
+ const memories = store.getPersonaAndInstructionL1s(userId, 100);
11
+ if (memories.length === 0) {
12
+ console.error(`[BrainRouter] L3 skipped for "${userId}" — no persona/instruction memories yet.`);
13
+ return { success: false };
14
+ }
15
+ let personaMd;
16
+ try {
17
+ personaMd = await llmRunner.run({
18
+ prompt: formatL3PersonaPrompt(memories),
19
+ systemPrompt: L3_PERSONA_SYSTEM_PROMPT,
20
+ taskId: "l3-persona-distillation",
21
+ timeoutMs: 90_000,
22
+ });
23
+ }
24
+ catch (err) {
25
+ console.error(`[BrainRouter] L3 persona distillation failed for "${userId}":`, err.message);
26
+ return { success: false };
27
+ }
28
+ const now = new Date().toISOString();
29
+ const existing = store.getL3Persona(userId);
30
+ const record = {
31
+ userId,
32
+ personaMd: personaMd.trim(),
33
+ l1CountAtGeneration: memories.length,
34
+ createdTime: existing?.createdTime ?? now,
35
+ updatedTime: now,
36
+ };
37
+ store.upsertL3Persona(record);
38
+ console.error(`[BrainRouter] L3 persona updated for "${userId}" (${memories.length} L1s).`);
39
+ return { success: true, personaMd: personaMd.trim() };
40
+ }
@@ -0,0 +1,27 @@
1
+ import type { IMemoryStore } from "@kinqs/brainrouter-types";
2
+ export interface SparkNode {
3
+ id: string;
4
+ potential: number;
5
+ fired: boolean;
6
+ }
7
+ export declare class NeuralSparkEngine {
8
+ private store;
9
+ private readonly threshold;
10
+ private readonly ltpStep;
11
+ private readonly decayFactor;
12
+ private readonly pruneThreshold;
13
+ constructor(store: IMemoryStore);
14
+ /**
15
+ * Propagates potentials from fired nodes to their neighbors.
16
+ * Runs a 2-hop BFS.
17
+ */
18
+ propagateSparks(userId: string, initialNodes: SparkNode[]): SparkNode[];
19
+ /**
20
+ * Hebbian Spine updates (LTP) for cited pairs
21
+ */
22
+ strengthenSpines(userId: string, citedIds: string[]): void;
23
+ /**
24
+ * Synaptic decay & pruning (LTD)
25
+ */
26
+ decayAndPrune(userId: string): void;
27
+ }
@@ -0,0 +1,78 @@
1
+ export class NeuralSparkEngine {
2
+ store;
3
+ threshold = 0.70;
4
+ ltpStep = 0.15;
5
+ decayFactor = 0.90; // Default decay multiplier (LTD)
6
+ pruneThreshold = 0.10;
7
+ constructor(store) {
8
+ this.store = store;
9
+ }
10
+ /**
11
+ * Propagates potentials from fired nodes to their neighbors.
12
+ * Runs a 2-hop BFS.
13
+ */
14
+ propagateSparks(userId, initialNodes) {
15
+ const activeNodes = new Map(initialNodes.map(node => [node.id, { ...node, fired: false }]));
16
+ const queue = [];
17
+ // 1. Detect initial nodes crossing threshold
18
+ for (const node of initialNodes) {
19
+ if (node.potential >= this.threshold) {
20
+ const active = activeNodes.get(node.id);
21
+ active.fired = true;
22
+ queue.push(node.id);
23
+ }
24
+ }
25
+ // 2. Propagate potential (2-hop limit)
26
+ let hops = 0;
27
+ while (queue.length > 0 && hops < 2) {
28
+ const currentSize = queue.length;
29
+ for (let i = 0; i < currentSize; i++) {
30
+ const sourceId = queue.shift();
31
+ const sourceNode = activeNodes.get(sourceId);
32
+ // Retrieve connections (dendritic spines) from store
33
+ const connections = this.store.getConnectionsForSource(userId, sourceId);
34
+ for (const conn of connections) {
35
+ let target = activeNodes.get(conn.targetId);
36
+ if (!target) {
37
+ target = { id: conn.targetId, potential: 0.0, fired: false };
38
+ activeNodes.set(conn.targetId, target);
39
+ }
40
+ // Refractory check: if it already fired, don't excite it again
41
+ if (target.fired)
42
+ continue;
43
+ // Propagate post-synaptic potential: source_potential * W_ij
44
+ const psp = sourceNode.potential * conn.weight;
45
+ target.potential = Math.min(1.0, target.potential + psp);
46
+ // Firing check
47
+ if (target.potential >= this.threshold) {
48
+ target.fired = true;
49
+ queue.push(target.id);
50
+ }
51
+ }
52
+ }
53
+ hops++;
54
+ }
55
+ return Array.from(activeNodes.values());
56
+ }
57
+ /**
58
+ * Hebbian Spine updates (LTP) for cited pairs
59
+ */
60
+ strengthenSpines(userId, citedIds) {
61
+ if (citedIds.length < 2)
62
+ return;
63
+ const pairs = [];
64
+ for (let i = 0; i < citedIds.length; i++) {
65
+ for (let j = i + 1; j < citedIds.length; j++) {
66
+ pairs.push({ source: citedIds[i], target: citedIds[j] });
67
+ }
68
+ }
69
+ this.store.strengthenConnectionsBatch(userId, pairs, this.ltpStep);
70
+ }
71
+ /**
72
+ * Synaptic decay & pruning (LTD)
73
+ */
74
+ decayAndPrune(userId) {
75
+ this.store.decayConnections(userId, this.decayFactor);
76
+ this.store.pruneConnections(userId, this.pruneThreshold);
77
+ }
78
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Skill Pre-warming Pipeline
3
+ *
4
+ * Tracks persistent skill activation potential. When a skill potential crosses
5
+ * the configured threshold, its registered extraction hints are proactively
6
+ * injected into `appendSystemContext` as a `<skill-prewarm>` block.
7
+ *
8
+ * This is opt-in via BRAINROUTER_PREWARM_ENABLED=true (disabled by default).
9
+ */
10
+ import type { IMemoryStore } from "@kinqs/brainrouter-types";
11
+ export interface PrewarmResult {
12
+ skillName: string;
13
+ potential: number;
14
+ hints: string;
15
+ }
16
+ export interface SkillActivationConfig {
17
+ halfLifeMinutes: number;
18
+ minTurnDecay: number;
19
+ threshold: number;
20
+ spikeAmount: number;
21
+ maxPotential: number;
22
+ }
23
+ export declare function decayPotential(params: {
24
+ potential: number;
25
+ lastDecayTime: string;
26
+ now?: Date;
27
+ halfLifeMinutes?: number;
28
+ minTurnDecay?: number;
29
+ }): number;
30
+ /**
31
+ * Increase a skill's activation potential after explicit skill use.
32
+ *
33
+ * The existing potential is decayed up to `now`, then the spike is applied and
34
+ * capped. The result is persisted so activation survives process restarts.
35
+ */
36
+ export declare function spikeSkill(params: {
37
+ userId: string;
38
+ skillName: string;
39
+ store: IMemoryStore;
40
+ now?: Date;
41
+ config?: Partial<SkillActivationConfig>;
42
+ }): {
43
+ skillName: string;
44
+ potential: number;
45
+ lastDecayTime: string;
46
+ } | null;
47
+ /**
48
+ * Detect which skills qualify for pre-warming based on persistent activation potential.
49
+ * Returns an array of skill names and their hints, sorted by potential.
50
+ */
51
+ export declare function detectPrewarmSkills(params: {
52
+ userId: string;
53
+ store: IMemoryStore;
54
+ threshold?: number;
55
+ excludeSkill?: string;
56
+ now?: Date;
57
+ config?: Partial<SkillActivationConfig>;
58
+ }): PrewarmResult[];
59
+ /**
60
+ * Build the `<skill-prewarm>` block for injection into `appendSystemContext`.
61
+ * Returns an empty string if no skills qualify.
62
+ */
63
+ export declare function buildPrewarmBlock(prewarmResults: PrewarmResult[]): string;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Skill Pre-warming Pipeline
3
+ *
4
+ * Tracks persistent skill activation potential. When a skill potential crosses
5
+ * the configured threshold, its registered extraction hints are proactively
6
+ * injected into `appendSystemContext` as a `<skill-prewarm>` block.
7
+ *
8
+ * This is opt-in via BRAINROUTER_PREWARM_ENABLED=true (disabled by default).
9
+ */
10
+ function parseNumber(value, fallback) {
11
+ if (value === undefined)
12
+ return fallback;
13
+ const parsed = Number(value);
14
+ return Number.isFinite(parsed) ? parsed : fallback;
15
+ }
16
+ function getActivationConfig(overrides = {}) {
17
+ const halfLifeMinutes = overrides.halfLifeMinutes
18
+ ?? parseNumber(process.env.BRAINROUTER_SKILL_HALF_LIFE_MINUTES, 10);
19
+ const minTurnDecay = overrides.minTurnDecay
20
+ ?? parseNumber(process.env.BRAINROUTER_SKILL_MIN_TURN_DECAY, 0.05);
21
+ const threshold = overrides.threshold
22
+ ?? parseNumber(process.env.BRAINROUTER_SKILL_PREWARM_THRESHOLD, 0.3);
23
+ const spikeAmount = overrides.spikeAmount
24
+ ?? parseNumber(process.env.BRAINROUTER_SKILL_SPIKE_AMOUNT, 1.0);
25
+ const maxPotential = overrides.maxPotential
26
+ ?? parseNumber(process.env.BRAINROUTER_SKILL_MAX_POTENTIAL, 4.0);
27
+ return {
28
+ halfLifeMinutes: halfLifeMinutes > 0 ? halfLifeMinutes : 10,
29
+ minTurnDecay: minTurnDecay >= 0 && minTurnDecay < 1 ? minTurnDecay : 0.05,
30
+ threshold: threshold >= 0 ? threshold : 0.3,
31
+ spikeAmount: spikeAmount >= 0 ? spikeAmount : 1.0,
32
+ maxPotential: maxPotential > 0 ? maxPotential : 4.0,
33
+ };
34
+ }
35
+ export function decayPotential(params) {
36
+ const config = getActivationConfig({
37
+ halfLifeMinutes: params.halfLifeMinutes,
38
+ minTurnDecay: params.minTurnDecay,
39
+ });
40
+ const lastDecayMs = Date.parse(params.lastDecayTime);
41
+ if (!Number.isFinite(params.potential) || params.potential <= 0)
42
+ return 0;
43
+ const nowMs = (params.now ?? new Date()).getTime();
44
+ const elapsedMinutes = Number.isFinite(lastDecayMs)
45
+ ? Math.max(0, (nowMs - lastDecayMs) / 60_000)
46
+ : 0;
47
+ const lambda = Math.log(2) / config.halfLifeMinutes;
48
+ const timeDecayed = params.potential * Math.exp(-lambda * elapsedMinutes);
49
+ const turnDecayed = params.potential * (1 - config.minTurnDecay);
50
+ return Math.max(0, Math.min(timeDecayed, turnDecayed));
51
+ }
52
+ /**
53
+ * Increase a skill's activation potential after explicit skill use.
54
+ *
55
+ * The existing potential is decayed up to `now`, then the spike is applied and
56
+ * capped. The result is persisted so activation survives process restarts.
57
+ */
58
+ export function spikeSkill(params) {
59
+ const skillName = params.skillName.trim();
60
+ if (!skillName)
61
+ return null;
62
+ const config = getActivationConfig(params.config);
63
+ const now = params.now ?? new Date();
64
+ const nowIso = now.toISOString();
65
+ const existing = params.store
66
+ .getSkillActivations(params.userId)
67
+ .find((record) => record.skillName === skillName);
68
+ const decayed = existing
69
+ ? decayPotential({
70
+ potential: existing.potential,
71
+ lastDecayTime: existing.lastDecayTime,
72
+ now,
73
+ halfLifeMinutes: config.halfLifeMinutes,
74
+ minTurnDecay: config.minTurnDecay,
75
+ })
76
+ : 0;
77
+ const potential = Math.min(config.maxPotential, decayed + config.spikeAmount);
78
+ const activation = { skillName, potential, lastDecayTime: nowIso };
79
+ params.store.upsertSkillActivations(params.userId, [activation]);
80
+ return activation;
81
+ }
82
+ /**
83
+ * Detect which skills qualify for pre-warming based on persistent activation potential.
84
+ * Returns an array of skill names and their hints, sorted by potential.
85
+ */
86
+ export function detectPrewarmSkills(params) {
87
+ const { userId, store, excludeSkill, } = params;
88
+ const config = getActivationConfig({ ...params.config, threshold: params.threshold });
89
+ const now = params.now ?? new Date();
90
+ const activations = store.getSkillActivations(userId);
91
+ if (activations.length === 0)
92
+ return [];
93
+ const decayedActivations = activations.map((activation) => ({
94
+ skillName: activation.skillName,
95
+ potential: decayPotential({
96
+ potential: activation.potential,
97
+ lastDecayTime: activation.lastDecayTime,
98
+ now,
99
+ halfLifeMinutes: config.halfLifeMinutes,
100
+ minTurnDecay: config.minTurnDecay,
101
+ }),
102
+ lastDecayTime: activation.lastDecayTime,
103
+ }));
104
+ const results = [];
105
+ for (const activation of decayedActivations) {
106
+ if (activation.skillName === excludeSkill)
107
+ continue;
108
+ if (activation.potential < config.threshold)
109
+ continue;
110
+ const hints = store.getSkillHints(activation.skillName);
111
+ if (!hints)
112
+ continue; // No hints registered — nothing useful to inject
113
+ results.push({ skillName: activation.skillName, potential: activation.potential, hints });
114
+ }
115
+ results.sort((a, b) => b.potential - a.potential || a.skillName.localeCompare(b.skillName));
116
+ return results;
117
+ }
118
+ /**
119
+ * Build the `<skill-prewarm>` block for injection into `appendSystemContext`.
120
+ * Returns an empty string if no skills qualify.
121
+ */
122
+ export function buildPrewarmBlock(prewarmResults) {
123
+ if (prewarmResults.length === 0)
124
+ return "";
125
+ const sections = prewarmResults.map(({ skillName, potential, hints }) => ` [${skillName}] (activation ${potential.toFixed(2)})\n ${hints.split("\n").join("\n ")}`);
126
+ return `<skill-prewarm>\n Skills detected as currently active — hints pre-loaded:\n\n${sections.join("\n\n---\n\n")}\n</skill-prewarm>`;
127
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * BrainRouter — Background Task Queue (Optimization C)
3
+ *
4
+ * Decouples L2 scene distillation and L3 persona distillation from the hot
5
+ * path of capture(). These are the most expensive, non-latency-critical steps.
6
+ *
7
+ * Graph extraction (Opt B) fires immediately as fire-and-forget — it is NOT queued.
8
+ * Contradiction detection (Opt A) also fires immediately as fire-and-forget.
9
+ *
10
+ * Flush triggers:
11
+ * 1. Lazy — called by recall() when the user is idle (lastCapture > idleThresholdMs)
12
+ * 2. Explicit — engine.flush(userId) called directly
13
+ * 3. Disabled — BRAINROUTER_ASYNC_PIPELINE=false keeps old fire-and-forget behaviour
14
+ */
15
+ import type { SqliteMemoryStore } from "../store/sqlite.js";
16
+ import type { LLMRunner } from "../types.js";
17
+ export type TaskType = "l2_scene" | "l3_persona";
18
+ export interface PendingTask {
19
+ id: string;
20
+ userId: string;
21
+ taskType: TaskType;
22
+ /** JSON-encoded payload specific to the task type */
23
+ payload: string;
24
+ createdAt: string;
25
+ }
26
+ /** Is the async pipeline enabled? Default: true. Set to "false" to use old fire-and-forget. */
27
+ export declare function isAsyncPipelineEnabled(): boolean;
28
+ /**
29
+ * Enqueue a task in the persistent SQLite queue instead of firing it immediately.
30
+ * Safe to call from the capture hot path — synchronous SQLite insert, no I/O wait.
31
+ */
32
+ export declare function enqueueTask(store: SqliteMemoryStore, task: {
33
+ userId: string;
34
+ taskType: TaskType;
35
+ payload: Record<string, unknown>;
36
+ }): void;
37
+ export interface FlushResult {
38
+ userId: string;
39
+ tasksProcessed: number;
40
+ tasksFailed: number;
41
+ durationMs: number;
42
+ }
43
+ /**
44
+ * Process all pending L2/L3 tasks for a user in FIFO order.
45
+ *
46
+ * Re-entrant safe: if a flush is already in progress for this userId, the existing
47
+ * promise is returned instead of starting a second flush.
48
+ */
49
+ export declare function flushPendingTasks(userId: string, store: SqliteMemoryStore, synthesisRunner: LLMRunner): Promise<FlushResult>;
50
+ /**
51
+ * Returns true if the user has been idle long enough that pre-recall flushing
52
+ * won't noticeably delay the response.
53
+ */
54
+ export declare function isUserIdle(store: SqliteMemoryStore, userId: string): boolean;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * BrainRouter — Background Task Queue (Optimization C)
3
+ *
4
+ * Decouples L2 scene distillation and L3 persona distillation from the hot
5
+ * path of capture(). These are the most expensive, non-latency-critical steps.
6
+ *
7
+ * Graph extraction (Opt B) fires immediately as fire-and-forget — it is NOT queued.
8
+ * Contradiction detection (Opt A) also fires immediately as fire-and-forget.
9
+ *
10
+ * Flush triggers:
11
+ * 1. Lazy — called by recall() when the user is idle (lastCapture > idleThresholdMs)
12
+ * 2. Explicit — engine.flush(userId) called directly
13
+ * 3. Disabled — BRAINROUTER_ASYNC_PIPELINE=false keeps old fire-and-forget behaviour
14
+ */
15
+ import { distillScenes } from "./l2-scene.js";
16
+ import { distillPersona } from "./l3-distiller.js";
17
+ // ─── Config ───────────────────────────────────────────────────────────────────
18
+ /** Flush pending tasks before recall if last capture was more than this many ms ago. */
19
+ const IDLE_THRESHOLD_MS = parseInt(process.env.BRAINROUTER_FLUSH_IDLE_MS ?? String(30_000), // default: 30s idle
20
+ 10);
21
+ /** Is the async pipeline enabled? Default: true. Set to "false" to use old fire-and-forget. */
22
+ export function isAsyncPipelineEnabled() {
23
+ return process.env.BRAINROUTER_ASYNC_PIPELINE !== "false";
24
+ }
25
+ // ─── Enqueue ──────────────────────────────────────────────────────────────────
26
+ /**
27
+ * Enqueue a task in the persistent SQLite queue instead of firing it immediately.
28
+ * Safe to call from the capture hot path — synchronous SQLite insert, no I/O wait.
29
+ */
30
+ export function enqueueTask(store, task) {
31
+ const id = `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
32
+ store.upsertPendingTask({
33
+ id,
34
+ userId: task.userId,
35
+ taskType: task.taskType,
36
+ payload: JSON.stringify(task.payload),
37
+ createdAt: new Date().toISOString(),
38
+ });
39
+ }
40
+ // ─── Flush ────────────────────────────────────────────────────────────────────
41
+ /** Active flush promises per userId to prevent concurrent flushes */
42
+ const flushInProgress = new Map();
43
+ /**
44
+ * Process all pending L2/L3 tasks for a user in FIFO order.
45
+ *
46
+ * Re-entrant safe: if a flush is already in progress for this userId, the existing
47
+ * promise is returned instead of starting a second flush.
48
+ */
49
+ export async function flushPendingTasks(userId, store, synthesisRunner) {
50
+ const existing = flushInProgress.get(userId);
51
+ if (existing)
52
+ return existing;
53
+ const flushPromise = _doFlush(userId, store, synthesisRunner);
54
+ flushInProgress.set(userId, flushPromise);
55
+ try {
56
+ return await flushPromise;
57
+ }
58
+ finally {
59
+ flushInProgress.delete(userId);
60
+ }
61
+ }
62
+ async function _doFlush(userId, store, synthesisRunner) {
63
+ const start = Date.now();
64
+ const tasks = store.getPendingTasks(userId);
65
+ if (tasks.length === 0) {
66
+ return { userId, tasksProcessed: 0, tasksFailed: 0, durationMs: 0 };
67
+ }
68
+ let processed = 0;
69
+ let failed = 0;
70
+ // Process l2_scene and l3_persona in FIFO order.
71
+ // Deduplicate consecutive same-type tasks (e.g., 10 queued l2_scene → run once).
72
+ let lastTaskType = null;
73
+ for (const task of tasks) {
74
+ try {
75
+ if (task.taskType === lastTaskType) {
76
+ // Skip duplicate consecutive task of same type — one distillation run is sufficient
77
+ store.deletePendingTask(task.id);
78
+ continue;
79
+ }
80
+ switch (task.taskType) {
81
+ case "l2_scene":
82
+ await distillScenes({ userId, store, llmRunner: synthesisRunner });
83
+ break;
84
+ case "l3_persona":
85
+ await distillPersona({ userId, store, llmRunner: synthesisRunner });
86
+ break;
87
+ }
88
+ lastTaskType = task.taskType;
89
+ processed++;
90
+ }
91
+ catch (err) {
92
+ console.error(`[BrainRouter] Flush: task ${task.taskType} failed:`, err.message);
93
+ failed++;
94
+ }
95
+ finally {
96
+ store.deletePendingTask(task.id);
97
+ }
98
+ }
99
+ return {
100
+ userId,
101
+ tasksProcessed: processed,
102
+ tasksFailed: failed,
103
+ durationMs: Date.now() - start,
104
+ };
105
+ }
106
+ // ─── Idle Check ───────────────────────────────────────────────────────────────
107
+ /**
108
+ * Returns true if the user has been idle long enough that pre-recall flushing
109
+ * won't noticeably delay the response.
110
+ */
111
+ export function isUserIdle(store, userId) {
112
+ const state = store.getSchedulerState(userId);
113
+ if (!state.lastCaptureAt)
114
+ return true; // never captured = trivially idle
115
+ const idleMs = Date.now() - new Date(state.lastCaptureAt).getTime();
116
+ return idleMs >= IDLE_THRESHOLD_MS;
117
+ }
@@ -0,0 +1 @@
1
+ export declare const COGNITIVE_CONTRADICTION_PROMPT = "\nYou are the Contradiction & Temporal Update Detector for a dual-process cognitive memory engine.\nYour task is to compare a NEWLY EXTRACTED memory with an EXISTING RELEVANT memory and determine if they contradict, supersede, or update each other.\n\nNEW MEMORY:\n\"{{newContent}}\"\n\nEXISTING MEMORY:\n\"{{existingContent}}\"\n\nRULES:\n1. Determine if they overlap or conflict. If there is no overlap/conflict, set \"isContradiction\" to false.\n2. If they conflict or override, classify the relationship into one of two kinds:\n - \"temporal_update\": The new memory is a deliberate update, change of mind, transition, or correction to the old memory (e.g. \"User lives in SF\" vs \"User moved to NYC\", or \"User wants pnpm\" vs \"User changed package manager to regular npm\"). This means the new memory actively supersedes the old one.\n - \"genuine_conflict\": The statements are logically incompatible, absolute claims that cannot both be true, and it is NOT a simple update or transition (e.g. \"User was born in 1990\" vs \"User was born in 1995\", or conflicting rules without a clear indication that one was meant to update the other).\n3. Provide a clear reason and a confidence score.\n\nRespond ONLY with a JSON object:\n{\n \"isContradiction\": boolean,\n \"kind\": \"temporal_update\" | \"genuine_conflict\" | null,\n \"reason\": \"Brief explanation if isContradiction is true, else null\",\n \"confidence\": number (0.0 to 1.0)\n}\n";