openclaw-hybrid-memory 2026.5.310 → 2026.6.10

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 (535) hide show
  1. package/api/plugin-runtime.ts +2 -0
  2. package/backends/facts-db/contradictions.ts +1 -1
  3. package/cli/cmd-extract-directives.ts +225 -11
  4. package/cli/cmd-extract-proposals.ts +5 -6
  5. package/cli/cmd-extract-reinforcement.ts +71 -0
  6. package/cli/cmd-feedback.ts +15 -9
  7. package/cli/commands/manage/register-reflection-pipeline.ts +247 -13
  8. package/cli/commands/manage/register-storage-maintenance.ts +224 -15
  9. package/cli/commands/manage/storage-stats-helpers.ts +13 -2
  10. package/cli/context.ts +9 -19
  11. package/cli/distill.ts +31 -1
  12. package/cli/register.ts +28 -38
  13. package/dist/api/plugin-runtime.js.map +1 -1
  14. package/dist/backends/agent-health-store.js.map +1 -1
  15. package/dist/backends/apitap-store.js.map +1 -1
  16. package/dist/backends/audit-store.js.map +1 -1
  17. package/dist/backends/base-sqlite-store.js.map +1 -1
  18. package/dist/backends/cost-tracker.js.map +1 -1
  19. package/dist/backends/credentials-db.js +2 -3
  20. package/dist/backends/credentials-db.js.map +1 -1
  21. package/dist/backends/crystallization-store.js.map +1 -1
  22. package/dist/backends/edict-store.js.map +1 -1
  23. package/dist/backends/event-bus.js.map +1 -1
  24. package/dist/backends/event-log.js.map +1 -1
  25. package/dist/backends/facts-db/cache-manager.js.map +1 -1
  26. package/dist/backends/facts-db/clusters.js.map +1 -1
  27. package/dist/backends/facts-db/contradictions.js +1 -1
  28. package/dist/backends/facts-db/contradictions.js.map +1 -1
  29. package/dist/backends/facts-db/crud.js.map +1 -1
  30. package/dist/backends/facts-db/db-connection.js.map +1 -1
  31. package/dist/backends/facts-db/entity-autolink.js.map +1 -1
  32. package/dist/backends/facts-db/entity-layer.js.map +1 -1
  33. package/dist/backends/facts-db/episodes.js.map +1 -1
  34. package/dist/backends/facts-db/fact-queries.js.map +1 -1
  35. package/dist/backends/facts-db/fact-read-queries.js.map +1 -1
  36. package/dist/backends/facts-db/facts-db-layer1.js.map +1 -1
  37. package/dist/backends/facts-db/facts-db-layer2.js.map +1 -1
  38. package/dist/backends/facts-db/facts-db-layer3.js.map +1 -1
  39. package/dist/backends/facts-db/fts-text.js.map +1 -1
  40. package/dist/backends/facts-db/generated-skills/policy.js.map +1 -1
  41. package/dist/backends/facts-db/generated-skills.js.map +1 -1
  42. package/dist/backends/facts-db/housekeeping.js.map +1 -1
  43. package/dist/backends/facts-db/links.js.map +1 -1
  44. package/dist/backends/facts-db/maintenance.js.map +1 -1
  45. package/dist/backends/facts-db/procedures/crud.js.map +1 -1
  46. package/dist/backends/facts-db/procedures/internal.js.map +1 -1
  47. package/dist/backends/facts-db/procedures/promotion.js.map +1 -1
  48. package/dist/backends/facts-db/procedures/search.js.map +1 -1
  49. package/dist/backends/facts-db/procedures/stats.js.map +1 -1
  50. package/dist/backends/facts-db/reinforcement.js.map +1 -1
  51. package/dist/backends/facts-db/row-mapper.js.map +1 -1
  52. package/dist/backends/facts-db/scan-cursors.js.map +1 -1
  53. package/dist/backends/facts-db/schema-bootstrap.js.map +1 -1
  54. package/dist/backends/facts-db/scope-sql.js.map +1 -1
  55. package/dist/backends/facts-db/search.js.map +1 -1
  56. package/dist/backends/facts-db/stats.js.map +1 -1
  57. package/dist/backends/facts-db/types.js.map +1 -1
  58. package/dist/backends/facts-db/variants.js.map +1 -1
  59. package/dist/backends/identity-reflection-store.js.map +1 -1
  60. package/dist/backends/issue-store.js.map +1 -1
  61. package/dist/backends/learnings-db.js.map +1 -1
  62. package/dist/backends/migrations/facts-migrations.js.map +1 -1
  63. package/dist/backends/migrations/procedures.js.map +1 -1
  64. package/dist/backends/narratives-db.js.map +1 -1
  65. package/dist/backends/persona-state-store.js.map +1 -1
  66. package/dist/backends/proposals-db.js.map +1 -1
  67. package/dist/backends/scope-filter-sql.js.map +1 -1
  68. package/dist/backends/sqlite-schema-meta.js.map +1 -1
  69. package/dist/backends/tool-proposal-store.js.map +1 -1
  70. package/dist/backends/vector-db/constants.js.map +1 -1
  71. package/dist/backends/vector-db/path-utils.js.map +1 -1
  72. package/dist/backends/vector-db/runtime-locks.js.map +1 -1
  73. package/dist/backends/vector-db/vector-db-class.js.map +1 -1
  74. package/dist/backends/wal.js.map +1 -1
  75. package/dist/backends/workflow-store.js.map +1 -1
  76. package/dist/benchmark/shadow-eval.js.map +1 -1
  77. package/dist/cli/active-tasks.js.map +1 -1
  78. package/dist/cli/backup.js.map +1 -1
  79. package/dist/cli/benchmark.js.map +1 -1
  80. package/dist/cli/cmd-backfill.js.map +1 -1
  81. package/dist/cli/cmd-config.js.map +1 -1
  82. package/dist/cli/cmd-credentials.js.map +1 -1
  83. package/dist/cli/cmd-demo.js.map +1 -1
  84. package/dist/cli/cmd-distill.js.map +1 -1
  85. package/dist/cli/cmd-doctor.js.map +1 -1
  86. package/dist/cli/cmd-examples.js.map +1 -1
  87. package/dist/cli/cmd-extract-daily.js.map +1 -1
  88. package/dist/cli/cmd-extract-directives.js +141 -10
  89. package/dist/cli/cmd-extract-directives.js.map +1 -1
  90. package/dist/cli/cmd-extract-procedures.js.map +1 -1
  91. package/dist/cli/cmd-extract-proposals.js +3 -2
  92. package/dist/cli/cmd-extract-proposals.js.map +1 -1
  93. package/dist/cli/cmd-extract-reinforcement.js +39 -0
  94. package/dist/cli/cmd-extract-reinforcement.js.map +1 -1
  95. package/dist/cli/cmd-extract-sessions.js.map +1 -1
  96. package/dist/cli/cmd-feedback.js +9 -4
  97. package/dist/cli/cmd-feedback.js.map +1 -1
  98. package/dist/cli/cmd-health.js.map +1 -1
  99. package/dist/cli/cmd-providers.js.map +1 -1
  100. package/dist/cli/cmd-selfcorrection.js.map +1 -1
  101. package/dist/cli/cmd-setup.js.map +1 -1
  102. package/dist/cli/cmd-status.js.map +1 -1
  103. package/dist/cli/cmd-store.js.map +1 -1
  104. package/dist/cli/cmd-user-friendly.js.map +1 -1
  105. package/dist/cli/cmd-verify.js.map +1 -1
  106. package/dist/cli/commands/manage/bindings.js.map +1 -1
  107. package/dist/cli/commands/manage/dream-cycle-followup.js.map +1 -1
  108. package/dist/cli/commands/manage/maintenance-heartbeat.js.map +1 -1
  109. package/dist/cli/commands/manage/register-agents-audit-runall.js.map +1 -1
  110. package/dist/cli/commands/manage/register-analyze-maintenance-logs.js.map +1 -1
  111. package/dist/cli/commands/manage/register-budget-proposals.js.map +1 -1
  112. package/dist/cli/commands/manage/register-config-cli.js.map +1 -1
  113. package/dist/cli/commands/manage/register-corrections-and-pipeline.js.map +1 -1
  114. package/dist/cli/commands/manage/register-corrections.js.map +1 -1
  115. package/dist/cli/commands/manage/register-council.js.map +1 -1
  116. package/dist/cli/commands/manage/register-credentials-scope.js.map +1 -1
  117. package/dist/cli/commands/manage/register-digest.js.map +1 -1
  118. package/dist/cli/commands/manage/register-lifecycle.js.map +1 -1
  119. package/dist/cli/commands/manage/register-procedure-lifecycle.js.map +1 -1
  120. package/dist/cli/commands/manage/register-reconcile-cron-ledgers.js.map +1 -1
  121. package/dist/cli/commands/manage/register-reflection-pipeline.js +144 -7
  122. package/dist/cli/commands/manage/register-reflection-pipeline.js.map +1 -1
  123. package/dist/cli/commands/manage/register-self-correction-feedback.js.map +1 -1
  124. package/dist/cli/commands/manage/register-storage-and-stats.js.map +1 -1
  125. package/dist/cli/commands/manage/register-storage-entities-decay.js.map +1 -1
  126. package/dist/cli/commands/manage/register-storage-graph-audit.js.map +1 -1
  127. package/dist/cli/commands/manage/register-storage-maintenance.js +152 -9
  128. package/dist/cli/commands/manage/register-storage-maintenance.js.map +1 -1
  129. package/dist/cli/commands/manage/register-validate-cron-exit.js.map +1 -1
  130. package/dist/cli/commands/manage/storage-stats-helpers.js +10 -3
  131. package/dist/cli/commands/manage/storage-stats-helpers.js.map +1 -1
  132. package/dist/cli/commands/register-manage-commands.js.map +1 -1
  133. package/dist/cli/config-feature-summaries.js.map +1 -1
  134. package/dist/cli/config-output-sink.js.map +1 -1
  135. package/dist/cli/distill-session-jsonl.js.map +1 -1
  136. package/dist/cli/distill.js +10 -1
  137. package/dist/cli/distill.js.map +1 -1
  138. package/dist/cli/global-verbose.js.map +1 -1
  139. package/dist/cli/goals.js.map +1 -1
  140. package/dist/cli/hybrid-mem-commander-utils.js.map +1 -1
  141. package/dist/cli/install/config-merge.js.map +1 -1
  142. package/dist/cli/install/cron-jobs.js.map +1 -1
  143. package/dist/cli/install/embedding-detect.js.map +1 -1
  144. package/dist/cli/install/run-install.js.map +1 -1
  145. package/dist/cli/install/workspace.js.map +1 -1
  146. package/dist/cli/proposals.js.map +1 -1
  147. package/dist/cli/register.js.map +1 -1
  148. package/dist/cli/shared.js.map +1 -1
  149. package/dist/cli/skills.js.map +1 -1
  150. package/dist/cli/task-queue-status.js.map +1 -1
  151. package/dist/cli/verified.js.map +1 -1
  152. package/dist/cli/verify/fact-count.js.map +1 -1
  153. package/dist/cli/verify/openclaw-config.js.map +1 -1
  154. package/dist/cli/verify/plugin-config-credentials.js.map +1 -1
  155. package/dist/cli/verify/sections/config-cron.js.map +1 -1
  156. package/dist/cli/verify/sections/embeddings.js.map +1 -1
  157. package/dist/cli/verify/sections/infrastructure.js.map +1 -1
  158. package/dist/cli/verify/sections/llm-models.js.map +1 -1
  159. package/dist/cli/verify/sections/reconcile.js.map +1 -1
  160. package/dist/cli/verify/verify-run-state.js.map +1 -1
  161. package/dist/cli/verify-llm-azure-auth.js.map +1 -1
  162. package/dist/cli/verify.js.map +1 -1
  163. package/dist/config/hybrid-schema.js.map +1 -1
  164. package/dist/config/index.js.map +1 -1
  165. package/dist/config/maintenance-fallback-policy.js.map +1 -1
  166. package/dist/config/parsers/capture.js.map +1 -1
  167. package/dist/config/parsers/core.js.map +1 -1
  168. package/dist/config/parsers/features.js.map +1 -1
  169. package/dist/config/parsers/index.js.map +1 -1
  170. package/dist/config/parsers/maintenance.js.map +1 -1
  171. package/dist/config/parsers/retrieval.js.map +1 -1
  172. package/dist/config/parsers/sensors.js.map +1 -1
  173. package/dist/config/skill-sections.js.map +1 -1
  174. package/dist/config/skill-size-limits.js.map +1 -1
  175. package/dist/config/types/agents.js.map +1 -1
  176. package/dist/config/types/bootstrap.js.map +1 -1
  177. package/dist/config/types/core.js.map +1 -1
  178. package/dist/config/types/index.js.map +1 -1
  179. package/dist/config/utils.js.map +1 -1
  180. package/dist/index-help.js.map +1 -1
  181. package/dist/index-testing-exports.js.map +1 -1
  182. package/dist/index.d.ts +1 -1
  183. package/dist/index.js +2 -2
  184. package/dist/index.js.map +1 -1
  185. package/dist/lifecycle/hook-resolution-api.js.map +1 -1
  186. package/dist/lifecycle/hooks.js +0 -1
  187. package/dist/lifecycle/hooks.js.map +1 -1
  188. package/dist/lifecycle/resolve-agent-id.js.map +1 -1
  189. package/dist/lifecycle/session-state.js.map +1 -1
  190. package/dist/lifecycle/stage-active-task.js.map +1 -1
  191. package/dist/lifecycle/stage-auth-failure.js.map +1 -1
  192. package/dist/lifecycle/stage-capture/run-capture.js.map +1 -1
  193. package/dist/lifecycle/stage-capture.js.map +1 -1
  194. package/dist/lifecycle/stage-cleanup.js.map +1 -1
  195. package/dist/lifecycle/stage-credential-hint.js.map +1 -1
  196. package/dist/lifecycle/stage-frustration.js.map +1 -1
  197. package/dist/lifecycle/stage-goal-stewardship.js.map +1 -1
  198. package/dist/lifecycle/stage-goal-subagent.js.map +1 -1
  199. package/dist/lifecycle/stage-injection.js +1 -1
  200. package/dist/lifecycle/stage-injection.js.map +1 -1
  201. package/dist/lifecycle/stage-recall/run-recall.js.map +1 -1
  202. package/dist/lifecycle/stage-recall.js.map +1 -1
  203. package/dist/lifecycle/stage-setup.js.map +1 -1
  204. package/dist/routes/dashboard/collectors.js.map +1 -1
  205. package/dist/routes/dashboard/html.js.map +1 -1
  206. package/dist/routes/dashboard/server.js.map +1 -1
  207. package/dist/routes/dashboard-graph.js.map +1 -1
  208. package/dist/routes/graphql-resolvers.js.map +1 -1
  209. package/dist/routes/graphql-server.js.map +1 -1
  210. package/dist/services/active-task-checkpoint.js.map +1 -1
  211. package/dist/services/active-task-injection.js.map +1 -1
  212. package/dist/services/active-task.js.map +1 -1
  213. package/dist/services/adaptive-catch-up-pacing.js +25 -0
  214. package/dist/services/adaptive-catch-up-pacing.js.map +1 -0
  215. package/dist/services/adaptive-maintenance-llm.js.map +1 -1
  216. package/dist/services/adaptive-model-limits.js.map +1 -1
  217. package/dist/services/ambient-retrieval.js.map +1 -1
  218. package/dist/services/apitap-service.js.map +1 -1
  219. package/dist/services/audit-health-exit-info.js.map +1 -1
  220. package/dist/services/audit-health-json.js.map +1 -1
  221. package/dist/services/auth-failure-detect.js.map +1 -1
  222. package/dist/services/auto-capture.js.map +1 -1
  223. package/dist/services/auto-classifier.js.map +1 -1
  224. package/dist/services/auto-skills-audit.js.map +1 -1
  225. package/dist/services/bootstrap-optional.js.map +1 -1
  226. package/dist/services/bootstrap-priority.js.map +1 -1
  227. package/dist/services/bootstrap.js.map +1 -1
  228. package/dist/services/capture-provenance.js.map +1 -1
  229. package/dist/services/capture-utils.js.map +1 -1
  230. package/dist/services/chat.js +22 -3
  231. package/dist/services/chat.js.map +1 -1
  232. package/dist/services/classification-scope.js.map +1 -1
  233. package/dist/services/classification.js.map +1 -1
  234. package/dist/services/cli-sql-dump.js.map +1 -1
  235. package/dist/services/consolidation.js.map +1 -1
  236. package/dist/services/context-audit.js +1 -1
  237. package/dist/services/context-audit.js.map +1 -1
  238. package/dist/services/context-budget.js.map +1 -1
  239. package/dist/services/context-engine.js.map +1 -1
  240. package/dist/services/contextual-variants.js.map +1 -1
  241. package/dist/services/continuous-verifier.js.map +1 -1
  242. package/dist/services/contradiction-adjudicator.js.map +1 -1
  243. package/dist/services/cost-context.js.map +1 -1
  244. package/dist/services/cost-feature-labels.js.map +1 -1
  245. package/dist/services/credential-migration.js.map +1 -1
  246. package/dist/services/credential-scanner.js.map +1 -1
  247. package/dist/services/credential-validation.js.map +1 -1
  248. package/dist/services/cron-exit-validator.js.map +1 -1
  249. package/dist/services/cron-guard.js.map +1 -1
  250. package/dist/services/cron-job-bash-harness.js +52 -5
  251. package/dist/services/cron-job-bash-harness.js.map +1 -1
  252. package/dist/services/cron-maintenance-reconciler.js +1 -3
  253. package/dist/services/cron-maintenance-reconciler.js.map +1 -1
  254. package/dist/services/cross-agent-learning.js.map +1 -1
  255. package/dist/services/crystallization-proposer.js.map +1 -1
  256. package/dist/services/dedupe-policy.js.map +1 -1
  257. package/dist/services/deprecated-cron-commands.js.map +1 -1
  258. package/dist/services/directive-extract.js.map +1 -1
  259. package/dist/services/document-chunker.js.map +1 -1
  260. package/dist/services/document-grader.js.map +1 -1
  261. package/dist/services/dream-cycle.js.map +1 -1
  262. package/dist/services/embedding-migration.js.map +1 -1
  263. package/dist/services/embedding-registry.js.map +1 -1
  264. package/dist/services/embeddings/chain-provider.js.map +1 -1
  265. package/dist/services/embeddings/factory.js.map +1 -1
  266. package/dist/services/embeddings/fallback-provider.js.map +1 -1
  267. package/dist/services/embeddings/ollama-provider.js.map +1 -1
  268. package/dist/services/embeddings/onnx-provider.js.map +1 -1
  269. package/dist/services/embeddings/openai-provider.js.map +1 -1
  270. package/dist/services/embeddings/shared.js +3 -3
  271. package/dist/services/embeddings/shared.js.map +1 -1
  272. package/dist/services/embeddings/types.js.map +1 -1
  273. package/dist/services/entity-enrichment-adaptive.js +128 -0
  274. package/dist/services/entity-enrichment-adaptive.js.map +1 -0
  275. package/dist/services/entity-enrichment-cli.js +389 -42
  276. package/dist/services/entity-enrichment-cli.js.map +1 -1
  277. package/dist/services/entity-enrichment.js +31 -5
  278. package/dist/services/entity-enrichment.js.map +1 -1
  279. package/dist/services/error-reporter/noisy-errors.js.map +1 -1
  280. package/dist/services/error-reporter/sanitize.js.map +1 -1
  281. package/dist/services/error-reporter.js.map +1 -1
  282. package/dist/services/event-hub-repair.js.map +1 -1
  283. package/dist/services/export-memory.js.map +1 -1
  284. package/dist/services/fact-extraction.js.map +1 -1
  285. package/dist/services/feedback-effectiveness.js.map +1 -1
  286. package/dist/services/find-duplicates.js.map +1 -1
  287. package/dist/services/frustration-detector.js.map +1 -1
  288. package/dist/services/fts-search.js.map +1 -1
  289. package/dist/services/gap-detector.js.map +1 -1
  290. package/dist/services/generated-skill-lifecycle.js.map +1 -1
  291. package/dist/services/generated-skill-validation.js.map +1 -1
  292. package/dist/services/goal-active-task-mirror.js.map +1 -1
  293. package/dist/services/goal-circuit-breaker.js.map +1 -1
  294. package/dist/services/goal-health.js.map +1 -1
  295. package/dist/services/goal-registry.js.map +1 -1
  296. package/dist/services/goal-stewardship-heartbeat.js.map +1 -1
  297. package/dist/services/goal-stewardship-llm-triage.js.map +1 -1
  298. package/dist/services/goal-stewardship-verify-cron.js.map +1 -1
  299. package/dist/services/goal-stewardship.js.map +1 -1
  300. package/dist/services/goal-subagent.js.map +1 -1
  301. package/dist/services/graph-retrieval.js.map +1 -1
  302. package/dist/services/humanizer-score.js.map +1 -1
  303. package/dist/services/hybrid-mem-cron-default-job-steps.js.map +1 -1
  304. package/dist/services/hyde-helper.js.map +1 -1
  305. package/dist/services/identity-reflection.js.map +1 -1
  306. package/dist/services/implicit-feedback-extract.js.map +1 -1
  307. package/dist/services/index.js.map +1 -1
  308. package/dist/services/ingest-utils.js.map +1 -1
  309. package/dist/services/intent-template.js.map +1 -1
  310. package/dist/services/json-array-parser.js.map +1 -1
  311. package/dist/services/knowledge-gaps.js.map +1 -1
  312. package/dist/services/language-keywords-build.js.map +1 -1
  313. package/dist/services/lifecycle/github-adapter.js.map +1 -1
  314. package/dist/services/llm-rate-limit-headers.js +1 -2
  315. package/dist/services/llm-rate-limit-headers.js.map +1 -1
  316. package/dist/services/maintenance-auto-fix.js.map +1 -1
  317. package/dist/services/maintenance-log-analyzer.js +7 -1
  318. package/dist/services/maintenance-log-analyzer.js.map +1 -1
  319. package/dist/services/maintenance-timestamp.js.map +1 -1
  320. package/dist/services/memory-diagnostics.js.map +1 -1
  321. package/dist/services/memory-index.js.map +1 -1
  322. package/dist/services/merge-results.js.map +1 -1
  323. package/dist/services/model-capabilities.js.map +1 -1
  324. package/dist/services/model-pricing.js.map +1 -1
  325. package/dist/services/narrative-recall.js.map +1 -1
  326. package/dist/services/openclaw-session-artifact.js.map +1 -1
  327. package/dist/services/passive-observer.js.map +1 -1
  328. package/dist/services/pattern-detector-hash.js.map +1 -1
  329. package/dist/services/pattern-detector.js.map +1 -1
  330. package/dist/services/pending-autopilot/foundation.js.map +1 -1
  331. package/dist/services/pending-autopilot/redaction.js.map +1 -1
  332. package/dist/services/pending-autopilot/store.js.map +1 -1
  333. package/dist/services/pending-autopilot/types.js.map +1 -1
  334. package/dist/services/pending-digest-autopilot-cron.js.map +1 -1
  335. package/dist/services/pending-digest-autopilot.js.map +1 -1
  336. package/dist/services/pending-review-digest.js.map +1 -1
  337. package/dist/services/persona-proposal-triage.js.map +1 -1
  338. package/dist/services/persona-state-promotion.js.map +1 -1
  339. package/dist/services/post-compaction-recall.js.map +1 -1
  340. package/dist/services/pre-consolidation-flush.js.map +1 -1
  341. package/dist/services/pre-finalization-guard.js.map +1 -1
  342. package/dist/services/procedure-cluster.js.map +1 -1
  343. package/dist/services/procedure-extractor.js.map +1 -1
  344. package/dist/services/procedure-promotion/duplicate-skill-cache.js.map +1 -1
  345. package/dist/services/procedure-promotion-policy.js.map +1 -1
  346. package/dist/services/procedure-selection-metrics.js.map +1 -1
  347. package/dist/services/procedure-skill-eval.js.map +1 -1
  348. package/dist/services/procedure-skill-generator.js.map +1 -1
  349. package/dist/services/procedure-skill-recipe.js.map +1 -1
  350. package/dist/services/procedure-skill-shrink.js.map +1 -1
  351. package/dist/services/procedure-skill-workflow.js.map +1 -1
  352. package/dist/services/provenance.js.map +1 -1
  353. package/dist/services/public-export-bundle.js.map +1 -1
  354. package/dist/services/python-bridge.js.map +1 -1
  355. package/dist/services/query-expander.js.map +1 -1
  356. package/dist/services/query-validator.js.map +1 -1
  357. package/dist/services/recall-pipeline.js.map +1 -1
  358. package/dist/services/recall-timing.js.map +1 -1
  359. package/dist/services/recent-http-attempts.js.map +1 -1
  360. package/dist/services/reflection/shared.js.map +1 -1
  361. package/dist/services/reflection.js.map +1 -1
  362. package/dist/services/reinforcement-extract.js.map +1 -1
  363. package/dist/services/reranker.js.map +1 -1
  364. package/dist/services/responses-adapter.js.map +1 -1
  365. package/dist/services/retrieval-aliases.js.map +1 -1
  366. package/dist/services/retrieval-mode-policy.js.map +1 -1
  367. package/dist/services/retrieval-orchestrator/packing.js.map +1 -1
  368. package/dist/services/retrieval-orchestrator.d.ts +2 -3
  369. package/dist/services/retrieval-orchestrator.js.map +1 -1
  370. package/dist/services/rrf-fusion.js.map +1 -1
  371. package/dist/services/self-correction-extract.js.map +1 -1
  372. package/dist/services/session-observability.js.map +1 -1
  373. package/dist/services/session-pre-filter.js.map +1 -1
  374. package/dist/services/shortest-path.js.map +1 -1
  375. package/dist/services/skill-allowed-tools.js.map +1 -1
  376. package/dist/services/skill-creator-validator.js.map +1 -1
  377. package/dist/services/skill-crystallizer-helpers.js.map +1 -1
  378. package/dist/services/skill-crystallizer.js.map +1 -1
  379. package/dist/services/skill-description-builder.js.map +1 -1
  380. package/dist/services/skill-eval-synthesizer.js.map +1 -1
  381. package/dist/services/skill-examples-builder.js.map +1 -1
  382. package/dist/services/skill-frontmatter.js.map +1 -1
  383. package/dist/services/skill-name-validator.js.map +1 -1
  384. package/dist/services/skill-prompt-injection.js.map +1 -1
  385. package/dist/services/skill-reference-sidecar.js.map +1 -1
  386. package/dist/services/skill-script-bundler.js.map +1 -1
  387. package/dist/services/skill-validator.js.map +1 -1
  388. package/dist/services/startup-memory-attribution.js.map +1 -1
  389. package/dist/services/task-hygiene.js.map +1 -1
  390. package/dist/services/task-ledger/canonical.js.map +1 -1
  391. package/dist/services/task-ledger-facts.js.map +1 -1
  392. package/dist/services/task-queue-leases.js.map +1 -1
  393. package/dist/services/task-queue-watchdog.js.map +1 -1
  394. package/dist/services/tool-effectiveness.js.map +1 -1
  395. package/dist/services/tool-proposer.js.map +1 -1
  396. package/dist/services/tools-md-section.js.map +1 -1
  397. package/dist/services/topic-clusters.js.map +1 -1
  398. package/dist/services/trajectory-tracker.js.map +1 -1
  399. package/dist/services/vector-backend-observability.js.map +1 -1
  400. package/dist/services/vector-lifecycle-audit.js.map +1 -1
  401. package/dist/services/vector-maintenance.js.map +1 -1
  402. package/dist/services/vector-search.js.map +1 -1
  403. package/dist/services/verification-store.js.map +1 -1
  404. package/dist/services/verified-fact-triage.js.map +1 -1
  405. package/dist/services/wal-helpers.js.map +1 -1
  406. package/dist/services/workflow-tracker.js.map +1 -1
  407. package/dist/setup/bootstrap-databases.js.map +1 -1
  408. package/dist/setup/cli-context/cli-services.js.map +1 -1
  409. package/dist/setup/cli-context/help-text.js.map +1 -1
  410. package/dist/setup/cli-context/metadata.js.map +1 -1
  411. package/dist/setup/cli-context/register-cli-with-help.js.map +1 -1
  412. package/dist/setup/cli-context/register-full.js.map +1 -1
  413. package/dist/setup/cli-context/register-help.js.map +1 -1
  414. package/dist/setup/cost-instrumentation.js.map +1 -1
  415. package/dist/setup/hybrid-memory-generation-state.js.map +1 -1
  416. package/dist/setup/hybrid-memory-reload-coordinator.js +13 -13
  417. package/dist/setup/hybrid-memory-reload-coordinator.js.map +1 -1
  418. package/dist/setup/plugin-service.js.map +1 -1
  419. package/dist/setup/provider-router.js.map +1 -1
  420. package/dist/setup/register-context-engine.js.map +1 -1
  421. package/dist/setup/register-hooks.js.map +1 -1
  422. package/dist/setup/register-plugin.js +25 -21
  423. package/dist/setup/register-plugin.js.map +1 -1
  424. package/dist/setup/register-tools.js.map +1 -1
  425. package/dist/setup/reregister-policy.js +2 -2
  426. package/dist/setup/reregister-policy.js.map +1 -1
  427. package/dist/setup/tool-installers.js.map +1 -1
  428. package/dist/setup/workspace-bootstrap.js.map +1 -1
  429. package/dist/src/worker/narratives.js.map +1 -1
  430. package/dist/tools/apitap-tools.js.map +1 -1
  431. package/dist/tools/credential-tools.js.map +1 -1
  432. package/dist/tools/crystallization-tools.js.map +1 -1
  433. package/dist/tools/dashboard-routes.js.map +1 -1
  434. package/dist/tools/document-tools.js.map +1 -1
  435. package/dist/tools/goal-tools.js.map +1 -1
  436. package/dist/tools/graph-tools.js.map +1 -1
  437. package/dist/tools/issue-tools.js.map +1 -1
  438. package/dist/tools/memory/build-runtime.js.map +1 -1
  439. package/dist/tools/memory/helpers.js.map +1 -1
  440. package/dist/tools/memory/register-checkpoint-tools.js.map +1 -1
  441. package/dist/tools/memory/register-directory-tools.js.map +1 -1
  442. package/dist/tools/memory/register-edict-tools.js.map +1 -1
  443. package/dist/tools/memory/register-episode-tools.js.map +1 -1
  444. package/dist/tools/memory/register-recall-tools.js.map +1 -1
  445. package/dist/tools/memory/register-store-tools.js.map +1 -1
  446. package/dist/tools/memory-tools.js.map +1 -1
  447. package/dist/tools/persona-tools.js.map +1 -1
  448. package/dist/tools/provenance-tools.js.map +1 -1
  449. package/dist/tools/public-api-routes.js.map +1 -1
  450. package/dist/tools/safe-register-http-route.js.map +1 -1
  451. package/dist/tools/self-extension-tools.js.map +1 -1
  452. package/dist/tools/task-hygiene-tools.js.map +1 -1
  453. package/dist/tools/utility-tools.js.map +1 -1
  454. package/dist/tools/verification-tools.js.map +1 -1
  455. package/dist/tools/workflow-tools.js.map +1 -1
  456. package/dist/types/issue-types.js.map +1 -1
  457. package/dist/types/learnings-types.js.map +1 -1
  458. package/dist/types/memory.js.map +1 -1
  459. package/dist/utils/apim-gateway-fetch.js.map +1 -1
  460. package/dist/utils/atomic-write.js.map +1 -1
  461. package/dist/utils/auth-failover.js.map +1 -1
  462. package/dist/utils/auth.js.map +1 -1
  463. package/dist/utils/compaction-model-watchdog.js.map +1 -1
  464. package/dist/utils/consolidation-controls.js.map +1 -1
  465. package/dist/utils/constants.js.map +1 -1
  466. package/dist/utils/date-detector.js.map +1 -1
  467. package/dist/utils/dates.js.map +1 -1
  468. package/dist/utils/decay.js.map +1 -1
  469. package/dist/utils/duration.js.map +1 -1
  470. package/dist/utils/embed-call.js.map +1 -1
  471. package/dist/utils/entity-lookup-resolve.js.map +1 -1
  472. package/dist/utils/entity-mention-quality.js.map +1 -1
  473. package/dist/utils/entity-stopwords.js.map +1 -1
  474. package/dist/utils/env-manager.js.map +1 -1
  475. package/dist/utils/error-tracking.js.map +1 -1
  476. package/dist/utils/event-loop-yield.js.map +1 -1
  477. package/dist/utils/extract-last-user-message.js.map +1 -1
  478. package/dist/utils/extraction-from-template.js.map +1 -1
  479. package/dist/utils/fact-embeddings.js.map +1 -1
  480. package/dist/utils/file-snapshot.js.map +1 -1
  481. package/dist/utils/format.js.map +1 -1
  482. package/dist/utils/fs.js.map +1 -1
  483. package/dist/utils/gh-repo-arg.js.map +1 -1
  484. package/dist/utils/hybrid-mem-json-cli.js.map +1 -1
  485. package/dist/utils/language-keywords.js.map +1 -1
  486. package/dist/utils/lifecycle-generation.js.map +1 -1
  487. package/dist/utils/llm-json-array.js.map +1 -1
  488. package/dist/utils/llm-selection.js.map +1 -1
  489. package/dist/utils/logger.js.map +1 -1
  490. package/dist/utils/model-provider-family.js.map +1 -1
  491. package/dist/utils/model-tier.js.map +1 -1
  492. package/dist/utils/openclaw-agent-defaults.js.map +1 -1
  493. package/dist/utils/path.js.map +1 -1
  494. package/dist/utils/plugin-root.js.map +1 -1
  495. package/dist/utils/plugin-update-check.js.map +1 -1
  496. package/dist/utils/procedure-risk.js.map +1 -1
  497. package/dist/utils/progress-indicators.js.map +1 -1
  498. package/dist/utils/prompt-loader.js.map +1 -1
  499. package/dist/utils/provenance.js.map +1 -1
  500. package/dist/utils/provider-detection.js.map +1 -1
  501. package/dist/utils/registration-superseded.js.map +1 -1
  502. package/dist/utils/salience.js.map +1 -1
  503. package/dist/utils/sanitize-messages.js.map +1 -1
  504. package/dist/utils/scope-filter.js.map +1 -1
  505. package/dist/utils/skill-discovery.js.map +1 -1
  506. package/dist/utils/sqlite-file-perms.js.map +1 -1
  507. package/dist/utils/sqlite-outcome-compat.js.map +1 -1
  508. package/dist/utils/sqlite-transaction.js.map +1 -1
  509. package/dist/utils/stable-stringify.js.map +1 -1
  510. package/dist/utils/subagent-ended-utils.js.map +1 -1
  511. package/dist/utils/tags.js.map +1 -1
  512. package/dist/utils/text.js.map +1 -1
  513. package/dist/utils/timeout.js.map +1 -1
  514. package/dist/utils/typebox.js.map +1 -1
  515. package/dist/utils/version-check.js.map +1 -1
  516. package/dist/utils/wal-replay.js.map +1 -1
  517. package/dist/versionInfo.js.map +1 -1
  518. package/index.ts +2 -2
  519. package/lifecycle/hooks.ts +0 -1
  520. package/npm-shrinkwrap.json +487 -186
  521. package/openclaw.plugin.json +1 -1
  522. package/package.json +2 -2
  523. package/services/adaptive-catch-up-pacing.ts +28 -0
  524. package/services/chat.ts +34 -1
  525. package/services/cron-job-bash-harness.ts +52 -5
  526. package/services/embeddings/shared.ts +5 -2
  527. package/services/entity-enrichment-adaptive.ts +245 -0
  528. package/services/entity-enrichment-cli.ts +553 -47
  529. package/services/entity-enrichment.ts +43 -2
  530. package/services/llm-rate-limit-headers.ts +1 -4
  531. package/services/maintenance-log-analyzer.ts +13 -9
  532. package/services/reinforcement-extract.ts +19 -0
  533. package/setup/hybrid-memory-reload-coordinator.ts +26 -0
  534. package/setup/register-plugin.ts +62 -32
  535. package/setup/reregister-policy.ts +10 -5
@@ -1 +1 @@
1
- {"version":3,"file":"retrieval-orchestrator.js","names":[],"sources":["../../services/retrieval-orchestrator.ts"],"sourcesContent":["/**\n * Multi-Strategy Retrieval Orchestrator (Issue #152).\n *\n * Runs configured retrieval strategies in parallel, fuses results via RRF,\n * applies post-RRF score adjustments, and packs results into a token budget.\n *\n * Strategies:\n * - semantic: LanceDB vector similarity search\n * - fts5: SQLite FTS5 full-text search (Issue #151)\n * - graph: Graph-walk spreading activation (GraphRAG expansion)\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { ClustersConfig, RerankingConfig, RetrievalConfig } from \"../config.js\";\nimport type { MemoryEntry, SearchResult } from \"../types/memory.js\";\nimport { stableStringify } from \"../utils/stable-stringify.js\";\nimport { DocumentGrader } from \"./document-grader.js\";\nimport type { EmbeddingRegistry } from \"./embedding-registry.js\";\nimport { shouldSuppressEmbeddingError } from \"./embeddings.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { searchFts } from \"./fts-search.js\";\nimport { type GraphFactLookup, expandGraph } from \"./graph-retrieval.js\";\nimport { expandQueryWithHyde } from \"./hyde-helper.js\";\nimport type { QueryExpander } from \"./query-expander.js\";\nimport { type QueryValidationResult, validateQueryForMemoryLookup } from \"./query-validator.js\";\nimport { type ScoredFact, rerankResults } from \"./reranker.js\";\nimport { type AliasDB, searchAliasStrategy } from \"./retrieval-aliases.js\";\nimport {\n DEFAULT_INTERACTIVE_RECALL_POLICY,\n type ConstrainedRetrievalPolicy,\n type ExplicitDeepRetrievalPolicy,\n type InteractiveRecallPolicy,\n resolveConstrainedRetrievalPolicy,\n resolveExplicitDeepRetrievalPolicy,\n} from \"./retrieval-mode-policy.js\";\nimport { getCandidateIdsByStructuredFilters, hasActiveFilters } from \"../backends/facts-db/search.js\";\nimport {\n type FactMetadata,\n type FusedResult,\n RRF_K_DEFAULT,\n type RankedResult,\n applyPostRrfAdjustments,\n fuseResults,\n} from \"./rrf-fusion.js\";\nimport { type ClusterFactLookup, detectClusters } from \"./topic-clusters.js\";\nimport { estimateTokenCount, packIntoBudget, serializeFactForContext } from \"./retrieval-orchestrator/packing.js\";\nexport { estimateTokenCount, packIntoBudget, serializeFactForContext } from \"./retrieval-orchestrator/packing.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Result from the orchestrator, ready for context injection. */\ninterface OrchestratorResult {\n /** Fused and adjusted results, sorted by finalScore descending. */\n fused: FusedResult[];\n /** Serialized fact strings packed into the token budget, highest scored first. */\n packed: string[];\n /** factIds of the facts included in packed (same order as packed). */\n packedFactIds: string[];\n /** Total tokens used by the packed results (approximate: chars / 4). */\n tokensUsed: number;\n /** Resolved MemoryEntry objects for each fused result (same order as fused array). */\n entries: MemoryEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\ninterface BuildExplicitSemanticQueryVectorDeps {\n query: string;\n cfg: Pick<\n import(\"../config.js\").HybridMemoryConfig,\n \"retrieval\" | \"queryExpansion\" | \"llm\" | \"embedding\" | \"distill\" | \"reflection\"\n >;\n embeddings: import(\"./embeddings.js\").EmbeddingProvider;\n openai: import(\"openai\").default | null;\n pendingLLMWarnings: import(\"./chat.js\").PendingLLMWarnings;\n logger: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };\n policy?: ExplicitDeepRetrievalPolicy;\n}\n\nexport async function buildExplicitSemanticQueryVector({\n query,\n cfg,\n embeddings,\n openai,\n pendingLLMWarnings,\n logger,\n policy = resolveExplicitDeepRetrievalPolicy(cfg.retrieval),\n}: BuildExplicitSemanticQueryVectorDeps): Promise<{ queryVector: number[] | null; warning: string | null }> {\n if (!cfg.retrieval.strategies.includes(\"semantic\")) {\n return { queryVector: null, warning: null };\n }\n\n try {\n void import(\"./error-reporter.js\")\n .then(({ addOperationBreadcrumb }) => addOperationBreadcrumb(\"retrieval\", `${policy.mode}-vector-recall`))\n .catch((error: unknown) => {\n logger.debug?.(`memory-hybrid: failed to record retrieval breadcrumb: ${String(error)}`);\n });\n let textToEmbed = query;\n\n if (policy.allowHyde && cfg.queryExpansion.enabled) {\n textToEmbed = await expandQueryWithHyde({\n query,\n rawCfg: cfg as Parameters<typeof import(\"../config/index.js\").getCronModelConfig>[0],\n model: cfg.queryExpansion.model,\n timeoutMs: cfg.queryExpansion.timeoutMs,\n openai: openai as any,\n label: \"HyDE\",\n pendingWarnings: pendingLLMWarnings,\n logger,\n subsystem: \"retrieval\",\n operation: \"explicit-hyde-generation\",\n });\n }\n\n return { queryVector: await embeddings.embed(textToEmbed), warning: null };\n } catch (err) {\n if (!shouldSuppressEmbeddingError(err)) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"explicit-vector-embed\",\n });\n }\n logger.warn(`memory-hybrid: embedding generation failed: ${err}`);\n return {\n queryVector: null,\n warning: \"Semantic search unavailable due to embedding failure; results may be incomplete.\",\n };\n }\n}\n\nexport const DEFAULT_RETRIEVAL_CONFIG: RetrievalConfig = {\n strategies: [\"semantic\", \"fts5\", \"graph\"],\n rrf_k: RRF_K_DEFAULT,\n ambientBudgetTokens: 2000,\n explicitBudgetTokens: 4000,\n graphWalkDepth: 2,\n semanticTopK: 20,\n fts5TopK: 20,\n};\n\n// ---------------------------------------------------------------------------\n// Individual strategy runners\n// ---------------------------------------------------------------------------\n\n/**\n * Run FTS5 full-text search strategy.\n * When candidateIds is provided (constrained-recall mode), only results within\n * that candidate set are returned and ranks are reassigned within that filtered set.\n * Returns ranked results (best = rank 1).\n */\nfunction runFts5Strategy(\n db: DatabaseSync,\n query: string,\n limit: number,\n tagFilter?: string,\n includeSuperseded?: boolean,\n asOf?: number,\n candidateIds?: Set<string> | null,\n): RankedResult[] {\n const effectiveLimit = candidateIds ? Math.max(limit * 5, 200) : limit;\n const results = searchFts(db, query, { limit: effectiveLimit, tagFilter, includeSuperseded, asOf });\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.factId)) : results;\n return filtered.slice(0, limit).map((r, i) => ({\n factId: r.factId,\n rank: i + 1,\n source: \"fts5\" as const,\n }));\n}\n\n/**\n * Run semantic (vector) search strategy.\n * When candidateIds is provided (constrained-recall mode), only results within\n * that candidate set are returned and ranks are reassigned within that filtered set.\n * Returns ranked results (best = rank 1).\n */\nasync function runSemanticStrategy(\n vectorDb: VectorDB,\n queryVector: number[],\n topK: number,\n candidateIds?: Set<string> | null,\n): Promise<RankedResult[]> {\n // Use a larger topK when filtering to account for candidates being filtered out\n const effectiveTopK = candidateIds ? Math.max(topK * 5, 200) : topK;\n const results: SearchResult[] = await vectorDb.search(queryVector, effectiveTopK, 0.3);\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.entry.id)) : results;\n return filtered.slice(0, topK).map((r, i) => ({\n factId: r.entry.id,\n rank: i + 1,\n source: \"semantic\" as const,\n }));\n}\n\n/**\n * Run multi-model semantic search using EmbeddingRegistry (Issue #158).\n * Queries the fact_embeddings SQLite table for each additional model via cosine\n * similarity approximation, then returns per-model ranked results.\n *\n * This returns a Map from strategy label → RankedResult[], so each model\n * participates as a separate RRF strategy (same pattern as \"semantic\", \"fts5\").\n */\nasync function runMultiModelSemanticStrategies(\n factsDbWithEmbeddings: FactsDbWithEmbeddings,\n registry: EmbeddingRegistry,\n queryText: string,\n topK: number,\n candidateIds?: Set<string> | null,\n): Promise<Map<string, RankedResult[]>> {\n const result = new Map<string, RankedResult[]>();\n const models = registry.getModels();\n if (models.length === 0) return result;\n\n // Embed query with all additional models in parallel\n const embedTasks = models.map(async (cfg) => {\n const queryVec = await registry.embed(queryText, cfg.name);\n return { name: cfg.name, queryVec };\n });\n\n const settled = await Promise.allSettled(embedTasks);\n for (const s of settled) {\n if (s.status === \"rejected\") {\n capturePluginError(s.reason instanceof Error ? s.reason : new Error(String(s.reason)), {\n subsystem: \"retrieval\",\n operation: \"multi-model-embed\",\n });\n continue;\n }\n const { name, queryVec } = s.value;\n const effectiveTopK = candidateIds ? Math.max(topK * 5, 200) : topK;\n const maxCandidatesPerModel = Math.max(effectiveTopK * 10, 500);\n const candidates = factsDbWithEmbeddings.getEmbeddingsByModel(name, maxCandidatesPerModel);\n if (candidates.length === 0) continue;\n\n // Compute cosine similarity for the bounded candidate set (already limited at DB with ORDER BY id DESC)\n const scored = candidates\n .map(({ factId, embedding }) => ({\n factId,\n score: cosineSimilarity(queryVec, embedding),\n }))\n .filter((r) => r.score >= 0.3)\n .sort((a, b) => b.score - a.score)\n .slice(0, effectiveTopK);\n\n const filtered = candidateIds ? scored.filter((r) => candidateIds.has(r.factId)) : scored;\n const finalResults = filtered.slice(0, topK);\n\n if (finalResults.length > 0) {\n result.set(\n `semantic:${name}`,\n finalResults.map((r, i) => ({\n factId: r.factId,\n rank: i + 1,\n source: `semantic:${name}` as const,\n })),\n );\n }\n }\n return result;\n}\n\n/** Compute cosine similarity between two Float32Arrays. Returns [-1, 1]. */\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length === 0 || a.length !== b.length) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n\n/** Minimal interface for fact_embeddings access (satisfied by FactsDB). */\nexport interface FactsDbWithEmbeddings {\n getEmbeddingsByModel(model: string, limit?: number): Array<{ factId: string; embedding: Float32Array }>;\n}\n\n/**\n * Graph walk strategy using GraphRAG expansion (Issue #152).\n * Expands from seed facts found by other strategies.\n */\nfunction runGraphStrategy(\n factsDb: FactLookup,\n seeds: Array<{ factId: string; score: number; entry: MemoryEntry }>,\n maxDepth: number,\n scopeFilter?: unknown,\n asOf?: number,\n hubDegreeCap?: number | null,\n hubScorePenalty?: number | null,\n): RankedResult[] {\n if (maxDepth <= 0 || seeds.length === 0 || !hasGraphLookup(factsDb)) return [];\n const { results: expanded } = expandGraph(factsDb, seeds, {\n maxDepth,\n scopeFilter,\n asOf,\n hubDegreeCap,\n hubScorePenalty,\n });\n const bestById = new Map<string, number>();\n for (const e of expanded) {\n if (e.expansionSource !== \"graph\") continue;\n const existing = bestById.get(e.factId);\n if (existing === undefined || e.score > existing) {\n bestById.set(e.factId, e.score);\n }\n }\n const sorted = Array.from(bestById.entries()).sort((a, b) => b[1] - a[1]);\n return sorted.map(([factId], i) => ({\n factId,\n rank: i + 1,\n source: \"graph\" as const,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal interface for fact lookup during orchestration.\n * Satisfied by FactsDB.\n */\ninterface FactLookup {\n getById(id: string, options?: { asOf?: number; scopeFilter?: unknown }): MemoryEntry | null;\n /** Optional: check whether a fact has an unresolved CONTRADICTS link targeting it. */\n isContradicted?(factId: string): boolean;\n /** Optional: batch check — returns the subset of factIds involved in unresolved contradictions. */\n getContradictedIds?(factIds: string[]): Set<string>;\n /** Optional: cluster detection helpers (FactsDB implements these). */\n getAllLinkedFactIds?(): string[];\n getAllLinks?(): Array<{ sourceFactId: string; targetFactId: string }>;\n /** Optional: count of links for cache invalidation (FactsDB implements this). */\n linksCount?(): number;\n /** Optional: graph traversal helpers (FactsDB implements these). */\n getLinksFrom?(factId: string): Array<{ id: string; targetFactId: string; linkType: string; strength: number }>;\n getLinksTo?(factId: string): Array<{ id: string; sourceFactId: string; linkType: string; strength: number }>;\n}\n\nfunction hasClusterLookup(factsDb: FactLookup): factsDb is FactLookup & ClusterFactLookup {\n return typeof factsDb.getAllLinkedFactIds === \"function\" && typeof factsDb.getAllLinks === \"function\";\n}\n\nfunction hasGraphLookup(factsDb: FactLookup): factsDb is FactLookup & GraphFactLookup {\n return (\n typeof factsDb.getLinksFrom === \"function\" &&\n typeof factsDb.getLinksTo === \"function\" &&\n typeof (factsDb as GraphFactLookup).getByIds === \"function\"\n );\n}\n\ntype ClusterCacheEntry = { clusters: Map<string, string>; timestamp: number; minClusterSize: number | undefined };\n\nclass ClusterCache {\n private clusterCache: ClusterCacheEntry | null = null;\n private clusterCacheLinkCount: number | null = null;\n private readonly ttlMs: number;\n\n constructor(ttlMs = 5 * 60 * 1000) {\n this.ttlMs = ttlMs;\n }\n\n getClusterMap(factsDb: FactLookup & ClusterFactLookup, minClusterSize?: number): Map<string, string> {\n const now = Date.now();\n const linkCount = typeof factsDb.linksCount === \"function\" ? factsDb.linksCount() : null;\n if (this.clusterCache && now - this.clusterCache.timestamp < this.ttlMs) {\n if (\n (linkCount == null || linkCount === this.clusterCacheLinkCount) &&\n this.clusterCache.minClusterSize === minClusterSize\n ) {\n return this.clusterCache.clusters;\n }\n }\n\n const clusterResult = detectClusters(factsDb, { minClusterSize });\n const clusterByFact = new Map<string, string>();\n for (const cluster of clusterResult.clusters) {\n for (const id of cluster.factIds) {\n clusterByFact.set(id, cluster.id);\n }\n }\n\n this.clusterCache = { clusters: clusterByFact, timestamp: now, minClusterSize };\n this.clusterCacheLinkCount = linkCount;\n return clusterByFact;\n }\n\n invalidate(): void {\n this.clusterCache = null;\n this.clusterCacheLinkCount = null;\n }\n}\n\nconst clusterCache = new ClusterCache();\nconst DEFAULT_SEMANTIC_CACHE_TTL_MS = 5 * 60 * 1000;\nconst DEFAULT_SEMANTIC_CACHE_MIN_SIMILARITY = 0.95;\nconst MAX_REWRITE_ITERATIONS = 2;\n\nexport function invalidateClusterCache(): void {\n clusterCache.invalidate();\n}\n\ntype SemanticCacheCapableVectorDB = import(\"../backends/vector-db.js\").VectorDB;\n\nfunction describeEmbeddingRegistry(registry: EmbeddingRegistry | null | undefined): unknown {\n if (!registry) return null;\n\n const primaryModel = typeof registry.getPrimaryModel === \"function\" ? registry.getPrimaryModel() : null;\n const additionalModels =\n typeof registry.getModels === \"function\"\n ? registry\n .getModels()\n .map((model) => ({\n name: model.name,\n provider: model.provider,\n dimensions: model.dimensions,\n role: model.role ?? null,\n }))\n .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n : [];\n\n return {\n primaryModel,\n additionalModels,\n multiModel: typeof registry.isMultiModel === \"function\" ? registry.isMultiModel() : additionalModels.length > 0,\n };\n}\n\nfunction buildSemanticCacheFilterKey(config: RetrievalConfig, options: RetrievalPipelineOptions): string {\n const expanderMode =\n options.queryExpander && typeof (options.queryExpander as QueryExpander).getMode === \"function\"\n ? options.queryExpander.getMode()\n : options.queryExpander\n ? \"always\"\n : \"off\";\n const expanderThreshold =\n options.queryExpander && typeof (options.queryExpander as QueryExpander).getThreshold === \"function\"\n ? options.queryExpander.getThreshold()\n : null;\n\n return stableStringify({\n strategies: [...config.strategies].sort(),\n rrfK: config.rrf_k,\n semanticTopK: config.semanticTopK,\n fts5TopK: config.fts5TopK,\n graphWalkDepth: config.graphWalkDepth,\n tagFilter: options.tagFilter ?? null,\n includeSuperseded: options.includeSuperseded ?? false,\n scopeFilter: options.scopeFilter ?? null,\n asOf: options.asOf ?? null,\n clustersConfig: options.clustersConfig ?? null,\n rerankingAvailable: Boolean(options.rerankingConfig?.enabled && options.rerankingOpenai),\n rerankingConfig: options.rerankingConfig ?? null,\n documentGradingAvailable: Boolean(\n options.documentGrader || (options.documentGradingConfig?.enabled && options.adaptiveOpenai),\n ),\n documentGradingConfig: options.documentGradingConfig ?? null,\n documentGraderOverride: options.documentGrader ? (options.documentGrader.constructor?.name ?? \"custom\") : null,\n aliasDbEnabled: Boolean(options.aliasDb),\n queryExpansionMode: expanderMode,\n queryExpansionThreshold: expanderThreshold,\n queryExpansionContext: options.queryExpansionContext ?? null,\n embeddingRegistry: describeEmbeddingRegistry(options.embeddingRegistry),\n embeddingFactsEnabled: Boolean(options.factsDbForEmbeddings),\n constrainedFilters: options.constrainedFilters ?? null,\n graphHubDegreeCap: options.graphHubDegreeCap ?? null,\n graphHubScorePenalty: options.graphHubScorePenalty ?? null,\n });\n}\n\nfunction shouldPreferResult(\n candidate: { result: OrchestratorResult; shouldRewrite: boolean },\n incumbent: OrchestratorResult | null,\n incumbentWasIrrelevant: boolean,\n): boolean {\n if (!incumbent) return true;\n\n const candidateHasRelevantDocs = !candidate.shouldRewrite;\n const incumbentHasRelevantDocs = !incumbentWasIrrelevant;\n if (candidateHasRelevantDocs !== incumbentHasRelevantDocs) {\n return candidateHasRelevantDocs;\n }\n\n if (candidate.result.fused.length !== incumbent.fused.length) {\n return candidate.result.fused.length > incumbent.fused.length;\n }\n\n return (candidate.result.fused[0]?.finalScore ?? 0) > (incumbent.fused[0]?.finalScore ?? 0);\n}\n\nfunction collectContradictedIds(\n factsDb: FactLookup,\n orderedEntries: Array<{ factId: string; entry: MemoryEntry }>,\n): Set<string> {\n const contradictedIds = new Set<string>();\n if (factsDb.getContradictedIds) {\n const allIds = orderedEntries.map((entry) => entry.factId);\n const batch = factsDb.getContradictedIds(allIds);\n for (const id of batch) contradictedIds.add(id);\n return contradictedIds;\n }\n\n if (factsDb.isContradicted) {\n for (const { factId } of orderedEntries) {\n if (factsDb.isContradicted(factId)) contradictedIds.add(factId);\n }\n }\n\n return contradictedIds;\n}\n\nfunction buildOrchestratorResult(\n factsDb: FactLookup,\n fused: FusedResult[],\n orderedEntries: Array<{ factId: string; entry: MemoryEntry }>,\n budgetTokens: number,\n): OrchestratorResult {\n const contradictedIds = collectContradictedIds(factsDb, orderedEntries);\n const { packed, tokensUsed } = packIntoBudget(orderedEntries, budgetTokens, { contradictedIds });\n const packedFactIds = orderedEntries.slice(0, packed.length).map((entry) => entry.factId);\n return {\n fused,\n packed,\n packedFactIds,\n tokensUsed,\n entries: orderedEntries.map((entry) => entry.entry),\n };\n}\n\nfunction buildCachedResult(\n factsDb: FactLookup,\n factIds: string[],\n budgetTokens: number,\n options: { includeSuperseded?: boolean; scopeFilter?: unknown; asOf?: number; nowSec: number },\n): OrchestratorResult {\n const getByIdOpts =\n options.scopeFilter || options.asOf != null ? { scopeFilter: options.scopeFilter, asOf: options.asOf } : undefined;\n const effectiveNow = options.asOf ?? options.nowSec;\n\n const orderedEntries: Array<{ factId: string; entry: MemoryEntry }> = [];\n const fused: FusedResult[] = [];\n let acceptedCount = 0;\n\n for (const factId of factIds) {\n const entry = factsDb.getById(factId, getByIdOpts);\n if (!entry) continue;\n if (!options.includeSuperseded) {\n if (entry.supersededAt != null) continue;\n if (entry.expiresAt != null && entry.expiresAt <= effectiveNow) continue;\n }\n\n orderedEntries.push({ factId, entry });\n fused.push({\n factId,\n sources: [{ strategy: \"semantic-cache\", rank: acceptedCount + 1 }],\n finalScore: 1 / (acceptedCount + 1),\n rrfScore: 1 / (acceptedCount + 1),\n });\n acceptedCount++;\n }\n\n return buildOrchestratorResult(factsDb, fused, orderedEntries, budgetTokens);\n}\n\n/**\n * Options bag for `runRetrievalPipeline`.\n *\n * All fields are optional. Required inputs (`query`, `queryVector`, `db`,\n * `vectorDb`, `factsDb`) are kept as positional parameters because they are\n * always needed; everything else lives here so callers can pass only what they\n * actually use — without placeholder nulls — and new strategies can be added\n * without touching every call site.\n */\nexport interface RetrievalPipelineOptions {\n /** Retrieval mode shortcut. When provided, derives the policy automatically.\n * 'interactive-recall' -> interactive recall policy (disables graph expansion).\n * 'explicit-deep' -> explicit deep retrieval policy.\n * 'constrained-recall' -> constrained retrieval policy (filter-before-rank).\n * Overridden by an explicit `policy` option. */\n mode?: import(\"./retrieval-mode-policy.js\").RetrievalMode;\n /** Retrieval policy. Defaults to resolveExplicitDeepRetrievalPolicy(config). */\n policy?: ExplicitDeepRetrievalPolicy | InteractiveRecallPolicy | ConstrainedRetrievalPolicy;\n /** Retrieval pipeline configuration. Defaults to `DEFAULT_RETRIEVAL_CONFIG`. */\n config?: RetrievalConfig;\n /** Token budget for packing. Defaults to `config.explicitBudgetTokens`. */\n budgetTokens?: number;\n /** Current time as epoch seconds. Defaults to `Math.floor(Date.now() / 1000)`. */\n nowSec?: number;\n /** Optional tag constraint propagated into the FTS5 strategy. */\n tagFilter?: string;\n /** When true, superseded/expired facts are included. Default false. */\n includeSuperseded?: boolean;\n /** Scope constraints applied when resolving fused fact IDs. */\n scopeFilter?: unknown;\n /** Temporal filter applied when resolving fused fact IDs. */\n asOf?: number;\n /** Alias DB for alias-search RRF strategy (Issue #149). */\n aliasDb?: AliasDB | null;\n /** Cluster sibling-boost configuration (Issue #146). */\n clustersConfig?: ClustersConfig;\n /** Multi-model embedding registry (Issue #158). Each registered model adds its own RRF strategy. */\n embeddingRegistry?: EmbeddingRegistry | null;\n /** Access to the fact_embeddings table (Issue #158). When `embeddingRegistry` is set but this is omitted/null, multi-model strategies are silently skipped (graceful degradation). */\n factsDbForEmbeddings?: FactsDbWithEmbeddings | null;\n /** Query expander for variant-query strategies (Issue #160). */\n queryExpander?: QueryExpander | null;\n /** Embed function used to vectorise expanded query variants (Issue #160). */\n embedFn?: ((text: string) => Promise<number[]>) | null;\n /** Recent conversation context passed to the LLM query expander. */\n queryExpansionContext?: string;\n /** Re-ranking configuration (Issue #161). */\n rerankingConfig?: RerankingConfig | null;\n /** OpenAI-compatible client for re-ranking LLM calls (Issue #161). */\n rerankingOpenai?: import(\"openai\").default | null;\n /** Optional semantic cache TTL. Defaults to 5 minutes. */\n semanticCacheTtlMs?: number;\n /** Optional semantic cache minimum cosine similarity. Defaults to 0.95. */\n semanticCacheMinSimilarity?: number;\n /** Optional query validator override. */\n queryValidator?: ((query: string) => QueryValidationResult | Promise<QueryValidationResult>) | null;\n /** Optional document grader override. */\n documentGrader?: DocumentGrader | null;\n /** OpenAI-compatible client for adaptive grading/rewrite loops. */\n adaptiveOpenai?: import(\"openai\").default | null;\n /** Document grading configuration. */\n documentGradingConfig?: import(\"../config.js\").DocumentGradingConfig | null;\n /** Structured pre-filters for constrained-recall mode.\n * When set, the pipeline first retrieves candidate fact IDs via SQLite structured filters,\n * then limits semantic/FTS5 ranking to only those candidates.\n * This implements the \"filter → rank → hydrate\" pattern (Issue #1026). */\n constrainedFilters?: import(\"../backends/facts-db/search.js\").ConstrainedSearchFilters;\n /** Mirrors `graph.hubDegreeCap` for GraphRAG expansion (Issue #1192). */\n graphHubDegreeCap?: number | null;\n /** Mirrors `graph.hubScorePenalty` for GraphRAG expansion (Issue #1192). */\n graphHubScorePenalty?: number | null;\n}\n\n/**\n * Run the multi-strategy retrieval pipeline and return fused, ranked results.\n *\n * Steps:\n * 1. Run configured strategies in parallel (semantic, fts5) and optional graph expansion.\n * 2. Fuse via RRF.\n * 3. Look up fact metadata for post-RRF adjustments (applying scope + asOf filters).\n * 4. Apply post-RRF adjustments (recency, confidence, access frequency).\n * 5. Pack into token budget.\n *\n * @param query - Raw search query string.\n * @param queryVector - Pre-computed embedding vector for semantic search.\n * Pass null to skip semantic strategy.\n * @param db - node:sqlite DatabaseSync instance for FTS5 queries.\n * @param vectorDb - LanceDB VectorDB instance for semantic queries.\n * @param factsDb - FactsDB for metadata lookup.\n * @param options - Optional settings; see `RetrievalPipelineOptions`.\n */\nexport async function runExplicitDeepRetrieval(\n query: string,\n queryVector: number[] | null,\n db: DatabaseSync,\n vectorDb: VectorDB,\n factsDb: FactLookup,\n options: RetrievalPipelineOptions = {},\n): Promise<OrchestratorResult> {\n const config = options.config ?? DEFAULT_RETRIEVAL_CONFIG;\n\n // Check if constrained mode has actual filters before selecting policy\n const hasConstrainedFilters = options.constrainedFilters && hasActiveFilters(options.constrainedFilters);\n\n let resolvedPolicy: ExplicitDeepRetrievalPolicy | InteractiveRecallPolicy | ConstrainedRetrievalPolicy;\n if (options.policy) {\n resolvedPolicy = options.policy as\n | ExplicitDeepRetrievalPolicy\n | InteractiveRecallPolicy\n | ConstrainedRetrievalPolicy;\n } else if (options.mode === \"interactive-recall\") {\n resolvedPolicy = DEFAULT_INTERACTIVE_RECALL_POLICY;\n } else if (options.mode === \"constrained-recall\" && hasConstrainedFilters) {\n resolvedPolicy = resolveConstrainedRetrievalPolicy(config, options.budgetTokens);\n } else {\n resolvedPolicy = resolveExplicitDeepRetrievalPolicy(config);\n }\n const policy = resolvedPolicy;\n const budgetTokens = options.budgetTokens ?? config.explicitBudgetTokens;\n const nowSec = options.nowSec ?? Math.floor(Date.now() / 1000);\n const {\n tagFilter,\n includeSuperseded,\n scopeFilter,\n asOf,\n aliasDb,\n clustersConfig,\n embeddingRegistry,\n factsDbForEmbeddings,\n queryExpander,\n embedFn,\n queryExpansionContext,\n rerankingConfig,\n rerankingOpenai,\n semanticCacheTtlMs,\n semanticCacheMinSimilarity,\n queryValidator,\n documentGrader,\n adaptiveOpenai,\n documentGradingConfig,\n constrainedFilters,\n graphHubDegreeCap,\n graphHubScorePenalty,\n } = options;\n\n const validator = queryValidator ?? validateQueryForMemoryLookup;\n const vectorDbWithCache = vectorDb as SemanticCacheCapableVectorDB;\n const semanticCacheFilterKey = buildSemanticCacheFilterKey(config, options);\n const activeDocumentGrader =\n documentGrader ??\n (adaptiveOpenai && documentGradingConfig?.enabled\n ? new DocumentGrader(adaptiveOpenai, {\n model: documentGradingConfig.model,\n timeoutMs: documentGradingConfig.timeoutMs,\n })\n : null);\n\n // -------------------------------------------------------------------------\n // Constrained-recall: pre-filter candidate set before ranking (Issue #1026)\n // -------------------------------------------------------------------------\n let candidateIds: Set<string> | null = null;\n if (policy.mode === \"constrained-recall\" && constrainedFilters) {\n const ids = getCandidateIdsByStructuredFilters(db, constrainedFilters, { limit: 1000, nowSec });\n if (ids.length === 0) {\n return { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] };\n }\n candidateIds = new Set(ids);\n }\n\n const applyConditionalReranking = async (\n queryText: string,\n initial: OrchestratorResult,\n ): Promise<OrchestratorResult> => {\n if (!(policy as ExplicitDeepRetrievalPolicy).allowReranking || !rerankingConfig?.enabled || !rerankingOpenai)\n return initial;\n\n try {\n const rrfScoreMap = new Map(initial.fused.map((result) => [result.factId, result.finalScore]));\n const fusedEntryMap = new Map<string, MemoryEntry>(\n initial.fused\n .map((result, index) => [result.factId, initial.entries[index]] as [string, MemoryEntry])\n .filter(([, entry]) => entry != null),\n );\n const scoredFacts: ScoredFact[] = initial.fused.flatMap((result) => {\n const entry = fusedEntryMap.get(result.factId);\n if (!entry) return [];\n const storedSec = entry.sourceDate ?? entry.createdAt;\n return [\n {\n factId: result.factId,\n text: entry.text,\n confidence: entry.confidence,\n storedDate: new Date(storedSec * 1000).toISOString().slice(0, 10),\n finalScore: rrfScoreMap.get(result.factId) ?? 0,\n },\n ];\n });\n const reranked = await rerankResults(queryText, scoredFacts, rerankingConfig, rerankingOpenai);\n const orderedEntriesReranked = reranked\n .map((fact) => ({ factId: fact.factId, entry: fusedEntryMap.get(fact.factId)! }))\n .filter((entry) => entry.entry);\n const rerankedOrder = new Map(reranked.map((fact, index) => [fact.factId, index]));\n const fusedReranked = [...initial.fused]\n .filter((result) => rerankedOrder.has(result.factId))\n .sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n return buildOrchestratorResult(factsDb, fusedReranked, orderedEntriesReranked, budgetTokens);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"reranking-conditional\",\n });\n return initial;\n }\n };\n\n const runBasePipeline = async (\n queryText: string,\n currentQueryVector: number[] | null,\n expansion: {\n useLlm: boolean;\n variants: string[] | null;\n skipReranking?: boolean;\n },\n ): Promise<{ result: OrchestratorResult; shouldRewrite: boolean; fromCache: boolean }> => {\n const validation = await Promise.resolve(validator(queryText));\n if (!validation.requiresLookup) {\n return {\n result: { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] },\n shouldRewrite: false,\n fromCache: false,\n };\n }\n\n if (currentQueryVector && typeof vectorDbWithCache.getSemanticQueryCacheMatch === \"function\") {\n const cached = await vectorDbWithCache.getSemanticQueryCacheMatch(currentQueryVector, {\n ttlMs: semanticCacheTtlMs ?? DEFAULT_SEMANTIC_CACHE_TTL_MS,\n minSimilarity: semanticCacheMinSimilarity ?? DEFAULT_SEMANTIC_CACHE_MIN_SIMILARITY,\n filterKey: semanticCacheFilterKey,\n });\n if (cached) {\n const filteredFactIds = candidateIds ? cached.factIds.filter((id) => candidateIds.has(id)) : cached.factIds;\n const cachedResult = buildCachedResult(factsDb, filteredFactIds, budgetTokens, {\n includeSuperseded,\n scopeFilter,\n asOf,\n nowSec,\n });\n if (cachedResult.fused.length > 0) {\n return {\n result: cachedResult,\n shouldRewrite: false,\n fromCache: true,\n };\n }\n }\n }\n\n const k = config.rrf_k;\n const { strategies, semanticTopK, fts5TopK } = config;\n const strategyPromises: Array<Promise<[string, RankedResult[]]>> = [];\n\n const safeStrategy = (\n name: string,\n fn: () => RankedResult[] | Promise<RankedResult[]>,\n ): Promise<[string, RankedResult[]]> =>\n (async () => {\n try {\n return [name, await fn()] as [string, RankedResult[]];\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: `strategy:${name}`,\n });\n return [name, []] as [string, RankedResult[]];\n }\n })();\n\n if (strategies.includes(\"fts5\")) {\n strategyPromises.push(\n safeStrategy(\"fts5\", () =>\n runFts5Strategy(db, queryText, fts5TopK, tagFilter, includeSuperseded, asOf, candidateIds),\n ),\n );\n }\n\n if (strategies.includes(\"semantic\") && currentQueryVector) {\n strategyPromises.push(\n safeStrategy(\"semantic\", () => runSemanticStrategy(vectorDb, currentQueryVector, semanticTopK, candidateIds)),\n );\n }\n\n if ((policy as ExplicitDeepRetrievalPolicy).allowAliasExpansion && aliasDb && currentQueryVector) {\n strategyPromises.push(\n safeStrategy(\"aliases\", async () => {\n const results = await searchAliasStrategy(aliasDb, currentQueryVector, semanticTopK);\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.factId)) : results;\n return filtered.map((r, i) => ({ ...r, rank: i + 1 }));\n }),\n );\n }\n\n if (\n (policy as ExplicitDeepRetrievalPolicy).allowQueryExpansion &&\n strategies.includes(\"semantic\") &&\n currentQueryVector &&\n queryExpander &&\n embedFn\n ) {\n try {\n let additionalVariants: string[] = [];\n if (expansion.useLlm) {\n const variants = await queryExpander.expandQuery(queryText, queryExpansionContext);\n additionalVariants = variants.slice(1);\n } else if (expansion.variants && expansion.variants.length > 0) {\n additionalVariants = expansion.variants;\n }\n\n for (const [index, variantQuery] of additionalVariants.entries()) {\n const strategyName = `semantic:qe:${index}`;\n strategyPromises.push(\n safeStrategy(strategyName, async () => {\n const variantVector = await embedFn(variantQuery);\n return runSemanticStrategy(vectorDb, variantVector, semanticTopK, candidateIds);\n }),\n );\n }\n } catch {\n // Graceful degradation — expansion failure never blocks retrieval.\n }\n }\n\n let multiModelPromise: Promise<Map<string, RankedResult[]>> | null = null;\n if (strategies.includes(\"semantic\") && embeddingRegistry?.isMultiModel() && factsDbForEmbeddings) {\n multiModelPromise = runMultiModelSemanticStrategies(\n factsDbForEmbeddings,\n embeddingRegistry,\n queryText,\n semanticTopK,\n candidateIds,\n );\n }\n\n const strategySettledResults = await Promise.allSettled(strategyPromises);\n const strategyMap = new Map<string, RankedResult[]>();\n for (const settled of strategySettledResults) {\n if (settled.status === \"rejected\") continue;\n const [name, results] = settled.value;\n if (results.length > 0) strategyMap.set(name, results);\n }\n\n if (multiModelPromise) {\n try {\n const multiModelResults = await multiModelPromise;\n for (const [strategyName, results] of multiModelResults) {\n if (results.length > 0) strategyMap.set(strategyName, results);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"multi-model-semantic\",\n });\n }\n }\n\n if ((policy as ExplicitDeepRetrievalPolicy).allowGraphExpansion && strategies.includes(\"graph\")) {\n const seedScores = new Map<string, number>();\n const seedEntries = new Map<string, MemoryEntry>();\n const getByIdOpts = scopeFilter || asOf != null ? { scopeFilter, asOf } : undefined;\n\n for (const results of strategyMap.values()) {\n for (const result of results) {\n const entry = factsDb.getById(result.factId, getByIdOpts);\n if (!entry) continue;\n const score = 1 / (k + result.rank);\n const existing = seedScores.get(result.factId) ?? 0;\n seedScores.set(result.factId, existing + score);\n if (!seedEntries.has(result.factId)) seedEntries.set(result.factId, entry);\n }\n }\n\n const seeds = Array.from(seedScores.entries()).map(([factId, score]) => ({\n factId,\n score,\n entry: seedEntries.get(factId)!,\n }));\n const graphResults = runGraphStrategy(\n factsDb,\n seeds,\n config.graphWalkDepth,\n scopeFilter,\n asOf,\n graphHubDegreeCap,\n graphHubScorePenalty,\n );\n if (graphResults.length > 0) {\n strategyMap.set(\"graph\", graphResults);\n }\n }\n\n let fused: import(\"./rrf-fusion.js\").FusedResult[];\n if ((policy as ExplicitDeepRetrievalPolicy).allowRrfFusion) {\n fused = fuseResults(strategyMap, k);\n } else {\n const allResults = Array.from(strategyMap.values()).flat();\n const deduped = new Map<string, import(\"./rrf-fusion.js\").FusedResult>();\n\n for (const res of allResults) {\n if (!deduped.has(res.factId)) {\n deduped.set(res.factId, {\n factId: res.factId,\n finalScore: 1 / (k + res.rank),\n rrfScore: 1 / (k + res.rank),\n\n sources: [{ strategy: res.source || \"unknown\", rank: res.rank }],\n });\n } else {\n const existing = deduped.get(res.factId)!;\n existing.sources.push({ strategy: res.source || \"unknown\", rank: res.rank });\n if (res.source === \"semantic\" || res.source?.startsWith(\"semantic:\")) {\n const semanticScore = 1 / (k + res.rank);\n if (semanticScore > existing.finalScore) {\n existing.finalScore = semanticScore;\n existing.rrfScore = semanticScore;\n }\n }\n }\n }\n\n fused = Array.from(deduped.values()).sort((a, b) => {\n if (b.finalScore !== a.finalScore) return b.finalScore - a.finalScore;\n return a.factId.localeCompare(b.factId);\n });\n }\n if (fused.length === 0) {\n return {\n result: { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] },\n shouldRewrite: false,\n fromCache: false,\n };\n }\n\n const getByIdOpts = scopeFilter || asOf != null ? { scopeFilter, asOf } : undefined;\n const factMetaMap = new Map<string, FactMetadata>();\n let orderedEntries: Array<{ factId: string; entry: MemoryEntry }> = [];\n const effectiveNow = asOf ?? nowSec;\n\n for (const result of fused) {\n const entry = factsDb.getById(result.factId, getByIdOpts);\n if (!entry) continue;\n if (!includeSuperseded) {\n if (entry.supersededAt != null) continue;\n if (entry.expiresAt != null && entry.expiresAt <= effectiveNow) continue;\n }\n factMetaMap.set(result.factId, {\n id: entry.id,\n confidence: entry.confidence,\n lastAccessed: entry.lastAccessed ?? null,\n recallCount: entry.recallCount ?? 0,\n });\n orderedEntries.push({ factId: result.factId, entry });\n }\n\n let scopedFused = fused.filter((result) => factMetaMap.has(result.factId));\n applyPostRrfAdjustments(scopedFused, factMetaMap, nowSec);\n\n if (clustersConfig?.enabled && hasClusterLookup(factsDb)) {\n try {\n const clusterByFact = clusterCache.getClusterMap(factsDb, clustersConfig.minClusterSize);\n if (clusterByFact.size > 0) {\n const clusterToIndices = new Map<string, number[]>();\n for (let index = 0; index < scopedFused.length; index++) {\n const clusterId = clusterByFact.get(scopedFused[index].factId);\n if (!clusterId) continue;\n const list = clusterToIndices.get(clusterId) ?? [];\n list.push(index);\n clusterToIndices.set(clusterId, list);\n }\n\n const BOOST_MULTIPLIER = 1.1;\n for (const indices of clusterToIndices.values()) {\n if (indices.length < 2) continue;\n let bestIndex = indices[0];\n for (const index of indices) {\n if (scopedFused[index].finalScore > scopedFused[bestIndex].finalScore) {\n bestIndex = index;\n }\n }\n for (const index of indices) {\n if (index === bestIndex) continue;\n scopedFused[index].finalScore *= BOOST_MULTIPLIER;\n }\n }\n\n scopedFused.sort((a, b) => b.finalScore - a.finalScore);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"cluster-boost\",\n });\n }\n }\n\n const finalOrder = new Map<string, number>(scopedFused.map((result, index) => [result.factId, index]));\n orderedEntries.sort((a, b) => (finalOrder.get(a.factId) ?? 0) - (finalOrder.get(b.factId) ?? 0));\n\n if (activeDocumentGrader && orderedEntries.length > 0) {\n const grades = await activeDocumentGrader.gradeDocuments(\n queryText,\n orderedEntries.map(({ factId, entry }) => ({ factId, text: entry.text })),\n );\n if (grades.length > 0) {\n if (grades.every((grade) => !grade.relevant)) {\n return {\n result: buildOrchestratorResult(factsDb, scopedFused, orderedEntries, budgetTokens),\n shouldRewrite: true,\n fromCache: false,\n };\n }\n const relevantFactIds = new Set(grades.filter((grade) => grade.relevant).map((grade) => grade.factId));\n orderedEntries = orderedEntries.filter(({ factId }) => relevantFactIds.has(factId));\n scopedFused = scopedFused.filter((result) => relevantFactIds.has(result.factId));\n }\n }\n\n if (rerankingConfig?.enabled && rerankingOpenai && !expansion.skipReranking) {\n try {\n const rrfScoreMap = new Map<string, number>(scopedFused.map((result) => [result.factId, result.finalScore]));\n const scoredFacts: ScoredFact[] = orderedEntries.map(({ factId, entry }) => {\n const storedSec = entry.sourceDate ?? entry.createdAt;\n return {\n factId,\n text: entry.text,\n confidence: entry.confidence,\n storedDate: new Date(storedSec * 1000).toISOString().slice(0, 10),\n finalScore: rrfScoreMap.get(factId) ?? 0,\n };\n });\n\n const reranked = await rerankResults(queryText, scoredFacts, rerankingConfig, rerankingOpenai);\n const rerankedOrder = new Map(reranked.map((fact, index) => [fact.factId, index]));\n orderedEntries.sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n if (orderedEntries.length > reranked.length) {\n orderedEntries.length = reranked.length;\n }\n scopedFused.sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n if (scopedFused.length > reranked.length) {\n scopedFused.length = reranked.length;\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"reranking\",\n });\n }\n }\n\n return {\n result: buildOrchestratorResult(factsDb, scopedFused, orderedEntries, budgetTokens),\n shouldRewrite: false,\n fromCache: false,\n };\n };\n\n const executePipelineQuery = async (\n queryText: string,\n currentQueryVector: number[] | null,\n ): Promise<{ result: OrchestratorResult; shouldRewrite: boolean; fromCache: boolean }> => {\n const expanderMode =\n queryExpander && typeof (queryExpander as QueryExpander).getMode === \"function\"\n ? queryExpander.getMode()\n : queryExpander\n ? \"always\"\n : \"off\";\n\n if (expanderMode === \"conditional\") {\n const alias =\n queryExpander && typeof (queryExpander as QueryExpander).getRuleBasedAlias === \"function\"\n ? queryExpander.getRuleBasedAlias(queryText)\n : null;\n const initial = await runBasePipeline(queryText, currentQueryVector, {\n useLlm: false,\n variants: alias ? [alias] : [],\n skipReranking: true,\n });\n const threshold =\n queryExpander && typeof (queryExpander as QueryExpander).getThreshold === \"function\"\n ? queryExpander.getThreshold()\n : 0.03;\n const topScore = initial.result.fused[0]?.finalScore ?? 0;\n if (initial.shouldRewrite || topScore < threshold) {\n return runBasePipeline(queryText, currentQueryVector, { useLlm: true, variants: null, skipReranking: false });\n }\n return { ...initial, result: await applyConditionalReranking(queryText, initial.result) };\n }\n\n if (expanderMode === \"always\") {\n return runBasePipeline(queryText, currentQueryVector, { useLlm: true, variants: null });\n }\n\n return runBasePipeline(queryText, currentQueryVector, { useLlm: false, variants: [] });\n };\n\n const attemptedQueries = [query];\n let currentQuery = query;\n let currentQueryVector = queryVector;\n let bestResult: OrchestratorResult | null = null;\n let bestResultWasIrrelevant = false;\n\n for (let iteration = 0; iteration <= MAX_REWRITE_ITERATIONS; iteration++) {\n const run = await executePipelineQuery(currentQuery, currentQueryVector);\n\n if (shouldPreferResult(run, bestResult, bestResultWasIrrelevant)) {\n bestResult = run.result;\n bestResultWasIrrelevant = run.shouldRewrite;\n }\n\n if (!run.shouldRewrite || !activeDocumentGrader) {\n if (\n !run.fromCache &&\n currentQueryVector &&\n run.result.fused.length > 0 &&\n typeof vectorDbWithCache.storeSemanticQueryCache === \"function\"\n ) {\n await vectorDbWithCache.storeSemanticQueryCache({\n queryText: currentQuery,\n vector: currentQueryVector,\n factIds: run.result.fused.map((result) => result.factId),\n filterKey: semanticCacheFilterKey,\n cachedAt: nowSec,\n });\n }\n return bestResult ?? run.result;\n }\n\n if (iteration === MAX_REWRITE_ITERATIONS) {\n return bestResult ?? run.result;\n }\n\n const rewritten = await activeDocumentGrader.rewriteQuery(query, attemptedQueries);\n if (!rewritten) {\n return bestResult ?? run.result;\n }\n\n attemptedQueries.push(rewritten);\n currentQuery = rewritten;\n if (embedFn) {\n try {\n currentQueryVector = await embedFn(rewritten);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"rewrite-embed\",\n });\n currentQueryVector = null;\n }\n } else {\n currentQueryVector = null;\n }\n }\n\n return bestResult ?? { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] };\n}\n\n/** @deprecated Use runExplicitDeepRetrieval instead */\nexport const runRetrievalPipeline = runExplicitDeepRetrieval;\n"],"mappings":"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"}
1
+ {"version":3,"file":"retrieval-orchestrator.js","names":[],"sources":["../../services/retrieval-orchestrator.ts"],"sourcesContent":["/**\n * Multi-Strategy Retrieval Orchestrator (Issue #152).\n *\n * Runs configured retrieval strategies in parallel, fuses results via RRF,\n * applies post-RRF score adjustments, and packs results into a token budget.\n *\n * Strategies:\n * - semantic: LanceDB vector similarity search\n * - fts5: SQLite FTS5 full-text search (Issue #151)\n * - graph: Graph-walk spreading activation (GraphRAG expansion)\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { ClustersConfig, RerankingConfig, RetrievalConfig } from \"../config.js\";\nimport type { MemoryEntry, SearchResult } from \"../types/memory.js\";\nimport { stableStringify } from \"../utils/stable-stringify.js\";\nimport { DocumentGrader } from \"./document-grader.js\";\nimport type { EmbeddingRegistry } from \"./embedding-registry.js\";\nimport { shouldSuppressEmbeddingError } from \"./embeddings.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\nimport { searchFts } from \"./fts-search.js\";\nimport { type GraphFactLookup, expandGraph } from \"./graph-retrieval.js\";\nimport { expandQueryWithHyde } from \"./hyde-helper.js\";\nimport type { QueryExpander } from \"./query-expander.js\";\nimport { type QueryValidationResult, validateQueryForMemoryLookup } from \"./query-validator.js\";\nimport { type ScoredFact, rerankResults } from \"./reranker.js\";\nimport { type AliasDB, searchAliasStrategy } from \"./retrieval-aliases.js\";\nimport {\n DEFAULT_INTERACTIVE_RECALL_POLICY,\n type ConstrainedRetrievalPolicy,\n type ExplicitDeepRetrievalPolicy,\n type InteractiveRecallPolicy,\n resolveConstrainedRetrievalPolicy,\n resolveExplicitDeepRetrievalPolicy,\n} from \"./retrieval-mode-policy.js\";\nimport { getCandidateIdsByStructuredFilters, hasActiveFilters } from \"../backends/facts-db/search.js\";\nimport {\n type FactMetadata,\n type FusedResult,\n RRF_K_DEFAULT,\n type RankedResult,\n applyPostRrfAdjustments,\n fuseResults,\n} from \"./rrf-fusion.js\";\nimport { type ClusterFactLookup, detectClusters } from \"./topic-clusters.js\";\nimport { estimateTokenCount, packIntoBudget, serializeFactForContext } from \"./retrieval-orchestrator/packing.js\";\nexport { estimateTokenCount, packIntoBudget, serializeFactForContext } from \"./retrieval-orchestrator/packing.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Result from the orchestrator, ready for context injection. */\ninterface OrchestratorResult {\n /** Fused and adjusted results, sorted by finalScore descending. */\n fused: FusedResult[];\n /** Serialized fact strings packed into the token budget, highest scored first. */\n packed: string[];\n /** factIds of the facts included in packed (same order as packed). */\n packedFactIds: string[];\n /** Total tokens used by the packed results (approximate: chars / 4). */\n tokensUsed: number;\n /** Resolved MemoryEntry objects for each fused result (same order as fused array). */\n entries: MemoryEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\ninterface BuildExplicitSemanticQueryVectorDeps {\n query: string;\n cfg: Pick<\n import(\"../config.js\").HybridMemoryConfig,\n \"retrieval\" | \"queryExpansion\" | \"llm\" | \"embedding\" | \"distill\" | \"reflection\"\n >;\n embeddings: import(\"./embeddings.js\").EmbeddingProvider;\n openai: import(\"openai\").default | null;\n pendingLLMWarnings: import(\"./chat.js\").PendingLLMWarnings;\n logger: { info: (msg: string) => void; warn: (msg: string) => void; debug?: (msg: string) => void };\n policy?: ExplicitDeepRetrievalPolicy;\n}\n\nexport async function buildExplicitSemanticQueryVector({\n query,\n cfg,\n embeddings,\n openai,\n pendingLLMWarnings,\n logger,\n policy = resolveExplicitDeepRetrievalPolicy(cfg.retrieval),\n}: BuildExplicitSemanticQueryVectorDeps): Promise<{ queryVector: number[] | null; warning: string | null }> {\n if (!cfg.retrieval.strategies.includes(\"semantic\")) {\n return { queryVector: null, warning: null };\n }\n\n try {\n void import(\"./error-reporter.js\")\n .then(({ addOperationBreadcrumb }) => addOperationBreadcrumb(\"retrieval\", `${policy.mode}-vector-recall`))\n .catch((error: unknown) => {\n logger.debug?.(`memory-hybrid: failed to record retrieval breadcrumb: ${String(error)}`);\n });\n let textToEmbed = query;\n\n if (policy.allowHyde && cfg.queryExpansion.enabled) {\n textToEmbed = await expandQueryWithHyde({\n query,\n rawCfg: cfg as Parameters<typeof import(\"../config/index.js\").getCronModelConfig>[0],\n model: cfg.queryExpansion.model,\n timeoutMs: cfg.queryExpansion.timeoutMs,\n openai: openai as any,\n label: \"HyDE\",\n pendingWarnings: pendingLLMWarnings,\n logger,\n subsystem: \"retrieval\",\n operation: \"explicit-hyde-generation\",\n });\n }\n\n return { queryVector: await embeddings.embed(textToEmbed), warning: null };\n } catch (err) {\n if (!shouldSuppressEmbeddingError(err)) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"explicit-vector-embed\",\n });\n }\n logger.warn(`memory-hybrid: embedding generation failed: ${err}`);\n return {\n queryVector: null,\n warning: \"Semantic search unavailable due to embedding failure; results may be incomplete.\",\n };\n }\n}\n\nexport const DEFAULT_RETRIEVAL_CONFIG: RetrievalConfig = {\n strategies: [\"semantic\", \"fts5\", \"graph\"],\n rrf_k: RRF_K_DEFAULT,\n ambientBudgetTokens: 2000,\n explicitBudgetTokens: 4000,\n graphWalkDepth: 2,\n semanticTopK: 20,\n fts5TopK: 20,\n};\n\n// ---------------------------------------------------------------------------\n// Individual strategy runners\n// ---------------------------------------------------------------------------\n\n/**\n * Run FTS5 full-text search strategy.\n * When candidateIds is provided (constrained-recall mode), only results within\n * that candidate set are returned and ranks are reassigned within that filtered set.\n * Returns ranked results (best = rank 1).\n */\nfunction runFts5Strategy(\n db: DatabaseSync,\n query: string,\n limit: number,\n tagFilter?: string,\n includeSuperseded?: boolean,\n asOf?: number,\n candidateIds?: Set<string> | null,\n): RankedResult[] {\n const effectiveLimit = candidateIds ? Math.max(limit * 5, 200) : limit;\n const results = searchFts(db, query, { limit: effectiveLimit, tagFilter, includeSuperseded, asOf });\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.factId)) : results;\n return filtered.slice(0, limit).map((r, i) => ({\n factId: r.factId,\n rank: i + 1,\n source: \"fts5\" as const,\n }));\n}\n\n/**\n * Run semantic (vector) search strategy.\n * When candidateIds is provided (constrained-recall mode), only results within\n * that candidate set are returned and ranks are reassigned within that filtered set.\n * Returns ranked results (best = rank 1).\n */\nasync function runSemanticStrategy(\n vectorDb: VectorDB,\n queryVector: number[],\n topK: number,\n candidateIds?: Set<string> | null,\n): Promise<RankedResult[]> {\n // Use a larger topK when filtering to account for candidates being filtered out\n const effectiveTopK = candidateIds ? Math.max(topK * 5, 200) : topK;\n const results: SearchResult[] = await vectorDb.search(queryVector, effectiveTopK, 0.3);\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.entry.id)) : results;\n return filtered.slice(0, topK).map((r, i) => ({\n factId: r.entry.id,\n rank: i + 1,\n source: \"semantic\" as const,\n }));\n}\n\n/**\n * Run multi-model semantic search using EmbeddingRegistry (Issue #158).\n * Queries the fact_embeddings SQLite table for each additional model via cosine\n * similarity approximation, then returns per-model ranked results.\n *\n * This returns a Map from strategy label → RankedResult[], so each model\n * participates as a separate RRF strategy (same pattern as \"semantic\", \"fts5\").\n */\nasync function runMultiModelSemanticStrategies(\n factsDbWithEmbeddings: FactsDbWithEmbeddings,\n registry: EmbeddingRegistry,\n queryText: string,\n topK: number,\n candidateIds?: Set<string> | null,\n): Promise<Map<string, RankedResult[]>> {\n const result = new Map<string, RankedResult[]>();\n const models = registry.getModels();\n if (models.length === 0) return result;\n\n // Embed query with all additional models in parallel\n const embedTasks = models.map(async (cfg) => {\n const queryVec = await registry.embed(queryText, cfg.name);\n return { name: cfg.name, queryVec };\n });\n\n const settled = await Promise.allSettled(embedTasks);\n for (const s of settled) {\n if (s.status === \"rejected\") {\n capturePluginError(s.reason instanceof Error ? s.reason : new Error(String(s.reason)), {\n subsystem: \"retrieval\",\n operation: \"multi-model-embed\",\n });\n continue;\n }\n const { name, queryVec } = s.value;\n const effectiveTopK = candidateIds ? Math.max(topK * 5, 200) : topK;\n const maxCandidatesPerModel = Math.max(effectiveTopK * 10, 500);\n const candidates = factsDbWithEmbeddings.getEmbeddingsByModel(name, maxCandidatesPerModel);\n if (candidates.length === 0) continue;\n\n // Compute cosine similarity for the bounded candidate set (already limited at DB with ORDER BY id DESC)\n const scored = candidates\n .map(({ factId, embedding }) => ({\n factId,\n score: cosineSimilarity(queryVec, embedding),\n }))\n .filter((r) => r.score >= 0.3)\n .sort((a, b) => b.score - a.score)\n .slice(0, effectiveTopK);\n\n const filtered = candidateIds ? scored.filter((r) => candidateIds.has(r.factId)) : scored;\n const finalResults = filtered.slice(0, topK);\n\n if (finalResults.length > 0) {\n result.set(\n `semantic:${name}`,\n finalResults.map((r, i) => ({\n factId: r.factId,\n rank: i + 1,\n source: `semantic:${name}` as const,\n })),\n );\n }\n }\n return result;\n}\n\n/** Compute cosine similarity between two Float32Arrays. Returns [-1, 1]. */\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length === 0 || a.length !== b.length) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n return dot / denom;\n}\n\n/** Minimal interface for fact_embeddings access (satisfied by FactsDB). */\nexport interface FactsDbWithEmbeddings {\n getEmbeddingsByModel(model: string, limit?: number): Array<{ factId: string; embedding: Float32Array }>;\n}\n\n/**\n * Graph walk strategy using GraphRAG expansion (Issue #152).\n * Expands from seed facts found by other strategies.\n */\nfunction runGraphStrategy(\n factsDb: FactLookup,\n seeds: Array<{ factId: string; score: number; entry: MemoryEntry }>,\n maxDepth: number,\n scopeFilter?: unknown,\n asOf?: number,\n hubDegreeCap?: number | null,\n hubScorePenalty?: number | null,\n): RankedResult[] {\n if (maxDepth <= 0 || seeds.length === 0 || !hasGraphLookup(factsDb)) return [];\n const { results: expanded } = expandGraph(factsDb, seeds, {\n maxDepth,\n scopeFilter,\n asOf,\n hubDegreeCap,\n hubScorePenalty,\n });\n const bestById = new Map<string, number>();\n for (const e of expanded) {\n if (e.expansionSource !== \"graph\") continue;\n const existing = bestById.get(e.factId);\n if (existing === undefined || e.score > existing) {\n bestById.set(e.factId, e.score);\n }\n }\n const sorted = Array.from(bestById.entries()).sort((a, b) => b[1] - a[1]);\n return sorted.map(([factId], i) => ({\n factId,\n rank: i + 1,\n source: \"graph\" as const,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal interface for fact lookup during orchestration.\n * Satisfied by FactsDB.\n */\ninterface FactLookup {\n getById(id: string, options?: { asOf?: number; scopeFilter?: unknown }): MemoryEntry | null;\n /** Optional: check whether a fact has an unresolved CONTRADICTS link targeting it. */\n isContradicted?(factId: string): boolean;\n /** Optional: batch check — returns the subset of factIds involved in unresolved contradictions. */\n getContradictedIds?(factIds: string[]): Set<string>;\n /** Optional: cluster detection helpers (FactsDB implements these). */\n getAllLinkedFactIds?(): string[];\n getAllLinks?(): Array<{ sourceFactId: string; targetFactId: string }>;\n /** Optional: count of links for cache invalidation (FactsDB implements this). */\n linksCount?(): number;\n /** Optional: graph traversal helpers (FactsDB implements these). */\n getLinksFrom?(factId: string): Array<{ id: string; targetFactId: string; linkType: string; strength: number }>;\n getLinksTo?(factId: string): Array<{ id: string; sourceFactId: string; linkType: string; strength: number }>;\n}\n\nfunction hasClusterLookup(factsDb: FactLookup): factsDb is FactLookup & ClusterFactLookup {\n return typeof factsDb.getAllLinkedFactIds === \"function\" && typeof factsDb.getAllLinks === \"function\";\n}\n\nfunction hasGraphLookup(factsDb: FactLookup): factsDb is FactLookup & GraphFactLookup {\n return (\n typeof factsDb.getLinksFrom === \"function\" &&\n typeof factsDb.getLinksTo === \"function\" &&\n typeof (factsDb as GraphFactLookup).getByIds === \"function\"\n );\n}\n\ntype ClusterCacheEntry = { clusters: Map<string, string>; timestamp: number; minClusterSize: number | undefined };\n\nclass ClusterCache {\n private clusterCache: ClusterCacheEntry | null = null;\n private clusterCacheLinkCount: number | null = null;\n private readonly ttlMs: number;\n\n constructor(ttlMs = 5 * 60 * 1000) {\n this.ttlMs = ttlMs;\n }\n\n getClusterMap(factsDb: FactLookup & ClusterFactLookup, minClusterSize?: number): Map<string, string> {\n const now = Date.now();\n const linkCount = typeof factsDb.linksCount === \"function\" ? factsDb.linksCount() : null;\n if (this.clusterCache && now - this.clusterCache.timestamp < this.ttlMs) {\n if (\n (linkCount == null || linkCount === this.clusterCacheLinkCount) &&\n this.clusterCache.minClusterSize === minClusterSize\n ) {\n return this.clusterCache.clusters;\n }\n }\n\n const clusterResult = detectClusters(factsDb, { minClusterSize });\n const clusterByFact = new Map<string, string>();\n for (const cluster of clusterResult.clusters) {\n for (const id of cluster.factIds) {\n clusterByFact.set(id, cluster.id);\n }\n }\n\n this.clusterCache = { clusters: clusterByFact, timestamp: now, minClusterSize };\n this.clusterCacheLinkCount = linkCount;\n return clusterByFact;\n }\n\n invalidate(): void {\n this.clusterCache = null;\n this.clusterCacheLinkCount = null;\n }\n}\n\nconst clusterCache = new ClusterCache();\nconst DEFAULT_SEMANTIC_CACHE_TTL_MS = 5 * 60 * 1000;\nconst DEFAULT_SEMANTIC_CACHE_MIN_SIMILARITY = 0.95;\nconst MAX_REWRITE_ITERATIONS = 2;\n\nexport function invalidateClusterCache(): void {\n clusterCache.invalidate();\n}\n\ntype SemanticCacheCapableVectorDB = import(\"../backends/vector-db.js\").VectorDB;\n\nfunction describeEmbeddingRegistry(registry: EmbeddingRegistry | null | undefined): unknown {\n if (!registry) return null;\n\n const primaryModel = typeof registry.getPrimaryModel === \"function\" ? registry.getPrimaryModel() : null;\n const additionalModels =\n typeof registry.getModels === \"function\"\n ? registry\n .getModels()\n .map((model) => ({\n name: model.name,\n provider: model.provider,\n dimensions: model.dimensions,\n role: model.role ?? null,\n }))\n .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))\n : [];\n\n return {\n primaryModel,\n additionalModels,\n multiModel: typeof registry.isMultiModel === \"function\" ? registry.isMultiModel() : additionalModels.length > 0,\n };\n}\n\nfunction buildSemanticCacheFilterKey(config: RetrievalConfig, options: RetrievalPipelineOptions): string {\n const expanderMode =\n options.queryExpander && typeof (options.queryExpander as QueryExpander).getMode === \"function\"\n ? options.queryExpander.getMode()\n : options.queryExpander\n ? \"always\"\n : \"off\";\n const expanderThreshold =\n options.queryExpander && typeof (options.queryExpander as QueryExpander).getThreshold === \"function\"\n ? options.queryExpander.getThreshold()\n : null;\n\n return stableStringify({\n strategies: [...config.strategies].sort(),\n rrfK: config.rrf_k,\n semanticTopK: config.semanticTopK,\n fts5TopK: config.fts5TopK,\n graphWalkDepth: config.graphWalkDepth,\n tagFilter: options.tagFilter ?? null,\n includeSuperseded: options.includeSuperseded ?? false,\n scopeFilter: options.scopeFilter ?? null,\n asOf: options.asOf ?? null,\n clustersConfig: options.clustersConfig ?? null,\n rerankingAvailable: Boolean(options.rerankingConfig?.enabled && options.rerankingOpenai),\n rerankingConfig: options.rerankingConfig ?? null,\n documentGradingAvailable: Boolean(\n options.documentGrader || (options.documentGradingConfig?.enabled && options.adaptiveOpenai),\n ),\n documentGradingConfig: options.documentGradingConfig ?? null,\n documentGraderOverride: options.documentGrader ? (options.documentGrader.constructor?.name ?? \"custom\") : null,\n aliasDbEnabled: Boolean(options.aliasDb),\n queryExpansionMode: expanderMode,\n queryExpansionThreshold: expanderThreshold,\n queryExpansionContext: options.queryExpansionContext ?? null,\n embeddingRegistry: describeEmbeddingRegistry(options.embeddingRegistry),\n embeddingFactsEnabled: Boolean(options.factsDbForEmbeddings),\n constrainedFilters: options.constrainedFilters ?? null,\n graphHubDegreeCap: options.graphHubDegreeCap ?? null,\n graphHubScorePenalty: options.graphHubScorePenalty ?? null,\n });\n}\n\nfunction shouldPreferResult(\n candidate: { result: OrchestratorResult; shouldRewrite: boolean },\n incumbent: OrchestratorResult | null,\n incumbentWasIrrelevant: boolean,\n): boolean {\n if (!incumbent) return true;\n\n const candidateHasRelevantDocs = !candidate.shouldRewrite;\n const incumbentHasRelevantDocs = !incumbentWasIrrelevant;\n if (candidateHasRelevantDocs !== incumbentHasRelevantDocs) {\n return candidateHasRelevantDocs;\n }\n\n if (candidate.result.fused.length !== incumbent.fused.length) {\n return candidate.result.fused.length > incumbent.fused.length;\n }\n\n return (candidate.result.fused[0]?.finalScore ?? 0) > (incumbent.fused[0]?.finalScore ?? 0);\n}\n\nfunction collectContradictedIds(\n factsDb: FactLookup,\n orderedEntries: Array<{ factId: string; entry: MemoryEntry }>,\n): Set<string> {\n const contradictedIds = new Set<string>();\n if (factsDb.getContradictedIds) {\n const allIds = orderedEntries.map((entry) => entry.factId);\n const batch = factsDb.getContradictedIds(allIds);\n for (const id of batch) contradictedIds.add(id);\n return contradictedIds;\n }\n\n if (factsDb.isContradicted) {\n for (const { factId } of orderedEntries) {\n if (factsDb.isContradicted(factId)) contradictedIds.add(factId);\n }\n }\n\n return contradictedIds;\n}\n\nfunction buildOrchestratorResult(\n factsDb: FactLookup,\n fused: FusedResult[],\n orderedEntries: Array<{ factId: string; entry: MemoryEntry }>,\n budgetTokens: number,\n): OrchestratorResult {\n const contradictedIds = collectContradictedIds(factsDb, orderedEntries);\n const { packed, tokensUsed } = packIntoBudget(orderedEntries, budgetTokens, { contradictedIds });\n const packedFactIds = orderedEntries.slice(0, packed.length).map((entry) => entry.factId);\n return {\n fused,\n packed,\n packedFactIds,\n tokensUsed,\n entries: orderedEntries.map((entry) => entry.entry),\n };\n}\n\nfunction buildCachedResult(\n factsDb: FactLookup,\n factIds: string[],\n budgetTokens: number,\n options: { includeSuperseded?: boolean; scopeFilter?: unknown; asOf?: number; nowSec: number },\n): OrchestratorResult {\n const getByIdOpts =\n options.scopeFilter || options.asOf != null ? { scopeFilter: options.scopeFilter, asOf: options.asOf } : undefined;\n const effectiveNow = options.asOf ?? options.nowSec;\n\n const orderedEntries: Array<{ factId: string; entry: MemoryEntry }> = [];\n const fused: FusedResult[] = [];\n let acceptedCount = 0;\n\n for (const factId of factIds) {\n const entry = factsDb.getById(factId, getByIdOpts);\n if (!entry) continue;\n if (!options.includeSuperseded) {\n if (entry.supersededAt != null) continue;\n if (entry.expiresAt != null && entry.expiresAt <= effectiveNow) continue;\n }\n\n orderedEntries.push({ factId, entry });\n fused.push({\n factId,\n sources: [{ strategy: \"semantic-cache\", rank: acceptedCount + 1 }],\n finalScore: 1 / (acceptedCount + 1),\n rrfScore: 1 / (acceptedCount + 1),\n });\n acceptedCount++;\n }\n\n return buildOrchestratorResult(factsDb, fused, orderedEntries, budgetTokens);\n}\n\n/**\n * Options bag for `runRetrievalPipeline`.\n *\n * All fields are optional. Required inputs (`query`, `queryVector`, `db`,\n * `vectorDb`, `factsDb`) are kept as positional parameters because they are\n * always needed; everything else lives here so callers can pass only what they\n * actually use — without placeholder nulls — and new strategies can be added\n * without touching every call site.\n */\nexport interface RetrievalPipelineOptions {\n /** Retrieval mode shortcut. When provided, derives the policy automatically.\n * 'interactive-recall' -> interactive recall policy (disables graph expansion).\n * 'explicit-deep' -> explicit deep retrieval policy.\n * 'constrained-recall' -> constrained retrieval policy (filter-before-rank).\n * Overridden by an explicit `policy` option. */\n mode?: import(\"./retrieval-mode-policy.js\").RetrievalMode;\n /** Retrieval policy. Defaults to resolveExplicitDeepRetrievalPolicy(config). */\n policy?: ExplicitDeepRetrievalPolicy | InteractiveRecallPolicy | ConstrainedRetrievalPolicy;\n /** Retrieval pipeline configuration. Defaults to `DEFAULT_RETRIEVAL_CONFIG`. */\n config?: RetrievalConfig;\n /** Token budget for packing. Defaults to `config.explicitBudgetTokens`. */\n budgetTokens?: number;\n /** Current time as epoch seconds. Defaults to `Math.floor(Date.now() / 1000)`. */\n nowSec?: number;\n /** Optional tag constraint propagated into the FTS5 strategy. */\n tagFilter?: string;\n /** When true, superseded/expired facts are included. Default false. */\n includeSuperseded?: boolean;\n /** Scope constraints applied when resolving fused fact IDs. */\n scopeFilter?: unknown;\n /** Temporal filter applied when resolving fused fact IDs. */\n asOf?: number;\n /** Alias DB for alias-search RRF strategy (Issue #149). */\n aliasDb?: AliasDB | null;\n /** Cluster sibling-boost configuration (Issue #146). */\n clustersConfig?: ClustersConfig;\n /** Multi-model embedding registry (Issue #158). Each registered model adds its own RRF strategy. */\n embeddingRegistry?: EmbeddingRegistry | null;\n /** Access to the fact_embeddings table (Issue #158). When `embeddingRegistry` is set but this is omitted/null, multi-model strategies are silently skipped (graceful degradation). */\n factsDbForEmbeddings?: FactsDbWithEmbeddings | null;\n /** Query expander for variant-query strategies (Issue #160). */\n queryExpander?: QueryExpander | null;\n /** Embed function used to vectorise expanded query variants (Issue #160). */\n embedFn?: ((text: string) => Promise<number[]>) | null;\n /** Recent conversation context passed to the LLM query expander. */\n queryExpansionContext?: string;\n /** Re-ranking configuration (Issue #161). */\n rerankingConfig?: RerankingConfig | null;\n /** OpenAI-compatible client for re-ranking LLM calls (Issue #161). */\n rerankingOpenai?: import(\"openai\").default | null;\n /** Optional semantic cache TTL. Defaults to 5 minutes. */\n semanticCacheTtlMs?: number;\n /** Optional semantic cache minimum cosine similarity. Defaults to 0.95. */\n semanticCacheMinSimilarity?: number;\n /** Optional query validator override. */\n queryValidator?: ((query: string) => QueryValidationResult | Promise<QueryValidationResult>) | null;\n /** Optional document grader override. */\n documentGrader?: DocumentGrader | null;\n /** OpenAI-compatible client for adaptive grading/rewrite loops. */\n adaptiveOpenai?: import(\"openai\").default | null;\n /** Document grading configuration. */\n documentGradingConfig?: import(\"../config.js\").DocumentGradingConfig | null;\n /** Structured pre-filters for constrained-recall mode.\n * When set, the pipeline first retrieves candidate fact IDs via SQLite structured filters,\n * then limits semantic/FTS5 ranking to only those candidates.\n * This implements the \"filter → rank → hydrate\" pattern (Issue #1026). */\n constrainedFilters?: import(\"../backends/facts-db/search.js\").ConstrainedSearchFilters;\n /** Mirrors `graph.hubDegreeCap` for GraphRAG expansion (Issue #1192). */\n graphHubDegreeCap?: number | null;\n /** Mirrors `graph.hubScorePenalty` for GraphRAG expansion (Issue #1192). */\n graphHubScorePenalty?: number | null;\n}\n\n/**\n * Run the multi-strategy retrieval pipeline and return fused, ranked results.\n *\n * Steps:\n * 1. Run configured strategies in parallel (semantic, fts5) and optional graph expansion.\n * 2. Fuse via RRF.\n * 3. Look up fact metadata for post-RRF adjustments (applying scope + asOf filters).\n * 4. Apply post-RRF adjustments (recency, confidence, access frequency).\n * 5. Pack into token budget.\n *\n * @param query - Raw search query string.\n * @param queryVector - Pre-computed embedding vector for semantic search.\n * Pass null to skip semantic strategy.\n * @param db - node:sqlite DatabaseSync instance for FTS5 queries.\n * @param vectorDb - LanceDB VectorDB instance for semantic queries.\n * @param factsDb - FactsDB for metadata lookup.\n * @param options - Optional settings; see `RetrievalPipelineOptions`.\n */\nexport async function runExplicitDeepRetrieval(\n query: string,\n queryVector: number[] | null,\n db: DatabaseSync,\n vectorDb: VectorDB,\n factsDb: FactLookup,\n options: RetrievalPipelineOptions = {},\n): Promise<OrchestratorResult> {\n const config = options.config ?? DEFAULT_RETRIEVAL_CONFIG;\n\n // Check if constrained mode has actual filters before selecting policy\n const hasConstrainedFilters = options.constrainedFilters && hasActiveFilters(options.constrainedFilters);\n\n let resolvedPolicy: ExplicitDeepRetrievalPolicy | InteractiveRecallPolicy | ConstrainedRetrievalPolicy;\n if (options.policy) {\n resolvedPolicy = options.policy as\n | ExplicitDeepRetrievalPolicy\n | InteractiveRecallPolicy\n | ConstrainedRetrievalPolicy;\n } else if (options.mode === \"interactive-recall\") {\n resolvedPolicy = DEFAULT_INTERACTIVE_RECALL_POLICY;\n } else if (options.mode === \"constrained-recall\" && hasConstrainedFilters) {\n resolvedPolicy = resolveConstrainedRetrievalPolicy(config, options.budgetTokens);\n } else {\n resolvedPolicy = resolveExplicitDeepRetrievalPolicy(config);\n }\n const policy = resolvedPolicy;\n const budgetTokens = options.budgetTokens ?? config.explicitBudgetTokens;\n const nowSec = options.nowSec ?? Math.floor(Date.now() / 1000);\n const {\n tagFilter,\n includeSuperseded,\n scopeFilter,\n asOf,\n aliasDb,\n clustersConfig,\n embeddingRegistry,\n factsDbForEmbeddings,\n queryExpander,\n embedFn,\n queryExpansionContext,\n rerankingConfig,\n rerankingOpenai,\n semanticCacheTtlMs,\n semanticCacheMinSimilarity,\n queryValidator,\n documentGrader,\n adaptiveOpenai,\n documentGradingConfig,\n constrainedFilters,\n graphHubDegreeCap,\n graphHubScorePenalty,\n } = options;\n\n const validator = queryValidator ?? validateQueryForMemoryLookup;\n const vectorDbWithCache = vectorDb as SemanticCacheCapableVectorDB;\n const semanticCacheFilterKey = buildSemanticCacheFilterKey(config, options);\n const activeDocumentGrader =\n documentGrader ??\n (adaptiveOpenai && documentGradingConfig?.enabled\n ? new DocumentGrader(adaptiveOpenai, {\n model: documentGradingConfig.model,\n timeoutMs: documentGradingConfig.timeoutMs,\n })\n : null);\n\n // -------------------------------------------------------------------------\n // Constrained-recall: pre-filter candidate set before ranking (Issue #1026)\n // -------------------------------------------------------------------------\n let candidateIds: Set<string> | null = null;\n if (policy.mode === \"constrained-recall\" && constrainedFilters) {\n const ids = getCandidateIdsByStructuredFilters(db, constrainedFilters, { limit: 1000, nowSec });\n if (ids.length === 0) {\n return { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] };\n }\n candidateIds = new Set(ids);\n }\n\n const applyConditionalReranking = async (\n queryText: string,\n initial: OrchestratorResult,\n ): Promise<OrchestratorResult> => {\n if (!(policy as ExplicitDeepRetrievalPolicy).allowReranking || !rerankingConfig?.enabled || !rerankingOpenai)\n return initial;\n\n try {\n const rrfScoreMap = new Map(initial.fused.map((result) => [result.factId, result.finalScore]));\n const fusedEntryMap = new Map<string, MemoryEntry>(\n initial.fused\n .map((result, index) => [result.factId, initial.entries[index]] as [string, MemoryEntry])\n .filter(([, entry]) => entry != null),\n );\n const scoredFacts: ScoredFact[] = initial.fused.flatMap((result) => {\n const entry = fusedEntryMap.get(result.factId);\n if (!entry) return [];\n const storedSec = entry.sourceDate ?? entry.createdAt;\n return [\n {\n factId: result.factId,\n text: entry.text,\n confidence: entry.confidence,\n storedDate: new Date(storedSec * 1000).toISOString().slice(0, 10),\n finalScore: rrfScoreMap.get(result.factId) ?? 0,\n },\n ];\n });\n const reranked = await rerankResults(queryText, scoredFacts, rerankingConfig, rerankingOpenai);\n const orderedEntriesReranked = reranked\n .map((fact) => ({ factId: fact.factId, entry: fusedEntryMap.get(fact.factId)! }))\n .filter((entry) => entry.entry);\n const rerankedOrder = new Map(reranked.map((fact, index) => [fact.factId, index]));\n const fusedReranked = [...initial.fused]\n .filter((result) => rerankedOrder.has(result.factId))\n .sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n return buildOrchestratorResult(factsDb, fusedReranked, orderedEntriesReranked, budgetTokens);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"reranking-conditional\",\n });\n return initial;\n }\n };\n\n const runBasePipeline = async (\n queryText: string,\n currentQueryVector: number[] | null,\n expansion: {\n useLlm: boolean;\n variants: string[] | null;\n skipReranking?: boolean;\n },\n ): Promise<{ result: OrchestratorResult; shouldRewrite: boolean; fromCache: boolean }> => {\n const validation = await Promise.resolve(validator(queryText));\n if (!validation.requiresLookup) {\n return {\n result: { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] },\n shouldRewrite: false,\n fromCache: false,\n };\n }\n\n if (currentQueryVector && typeof vectorDbWithCache.getSemanticQueryCacheMatch === \"function\") {\n const cached = await vectorDbWithCache.getSemanticQueryCacheMatch(currentQueryVector, {\n ttlMs: semanticCacheTtlMs ?? DEFAULT_SEMANTIC_CACHE_TTL_MS,\n minSimilarity: semanticCacheMinSimilarity ?? DEFAULT_SEMANTIC_CACHE_MIN_SIMILARITY,\n filterKey: semanticCacheFilterKey,\n });\n if (cached) {\n const filteredFactIds = candidateIds ? cached.factIds.filter((id) => candidateIds.has(id)) : cached.factIds;\n const cachedResult = buildCachedResult(factsDb, filteredFactIds, budgetTokens, {\n includeSuperseded,\n scopeFilter,\n asOf,\n nowSec,\n });\n if (cachedResult.fused.length > 0) {\n return {\n result: cachedResult,\n shouldRewrite: false,\n fromCache: true,\n };\n }\n }\n }\n\n const k = config.rrf_k;\n const { strategies, semanticTopK, fts5TopK } = config;\n const strategyPromises: Array<Promise<[string, RankedResult[]]>> = [];\n\n const safeStrategy = (\n name: string,\n fn: () => RankedResult[] | Promise<RankedResult[]>,\n ): Promise<[string, RankedResult[]]> =>\n (async () => {\n try {\n return [name, await fn()] as [string, RankedResult[]];\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: `strategy:${name}`,\n });\n return [name, []] as [string, RankedResult[]];\n }\n })();\n\n if (strategies.includes(\"fts5\")) {\n strategyPromises.push(\n safeStrategy(\"fts5\", () =>\n runFts5Strategy(db, queryText, fts5TopK, tagFilter, includeSuperseded, asOf, candidateIds),\n ),\n );\n }\n\n if (strategies.includes(\"semantic\") && currentQueryVector) {\n strategyPromises.push(\n safeStrategy(\"semantic\", () => runSemanticStrategy(vectorDb, currentQueryVector, semanticTopK, candidateIds)),\n );\n }\n\n if ((policy as ExplicitDeepRetrievalPolicy).allowAliasExpansion && aliasDb && currentQueryVector) {\n strategyPromises.push(\n safeStrategy(\"aliases\", async () => {\n const results = await searchAliasStrategy(aliasDb, currentQueryVector, semanticTopK);\n const filtered = candidateIds ? results.filter((r) => candidateIds.has(r.factId)) : results;\n return filtered.map((r, i) => ({ ...r, rank: i + 1 }));\n }),\n );\n }\n\n if (\n (policy as ExplicitDeepRetrievalPolicy).allowQueryExpansion &&\n strategies.includes(\"semantic\") &&\n currentQueryVector &&\n queryExpander &&\n embedFn\n ) {\n try {\n let additionalVariants: string[] = [];\n if (expansion.useLlm) {\n const variants = await queryExpander.expandQuery(queryText, queryExpansionContext);\n additionalVariants = variants.slice(1);\n } else if (expansion.variants && expansion.variants.length > 0) {\n additionalVariants = expansion.variants;\n }\n\n for (const [index, variantQuery] of additionalVariants.entries()) {\n const strategyName = `semantic:qe:${index}`;\n strategyPromises.push(\n safeStrategy(strategyName, async () => {\n const variantVector = await embedFn(variantQuery);\n return runSemanticStrategy(vectorDb, variantVector, semanticTopK, candidateIds);\n }),\n );\n }\n } catch {\n // Graceful degradation — expansion failure never blocks retrieval.\n }\n }\n\n let multiModelPromise: Promise<Map<string, RankedResult[]>> | null = null;\n if (strategies.includes(\"semantic\") && embeddingRegistry?.isMultiModel() && factsDbForEmbeddings) {\n multiModelPromise = runMultiModelSemanticStrategies(\n factsDbForEmbeddings,\n embeddingRegistry,\n queryText,\n semanticTopK,\n candidateIds,\n );\n }\n\n const strategySettledResults = await Promise.allSettled(strategyPromises);\n const strategyMap = new Map<string, RankedResult[]>();\n for (const settled of strategySettledResults) {\n if (settled.status === \"rejected\") continue;\n const [name, results] = settled.value;\n if (results.length > 0) strategyMap.set(name, results);\n }\n\n if (multiModelPromise) {\n try {\n const multiModelResults = await multiModelPromise;\n for (const [strategyName, results] of multiModelResults) {\n if (results.length > 0) strategyMap.set(strategyName, results);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"multi-model-semantic\",\n });\n }\n }\n\n if ((policy as ExplicitDeepRetrievalPolicy).allowGraphExpansion && strategies.includes(\"graph\")) {\n const seedScores = new Map<string, number>();\n const seedEntries = new Map<string, MemoryEntry>();\n const getByIdOpts = scopeFilter || asOf != null ? { scopeFilter, asOf } : undefined;\n\n for (const results of strategyMap.values()) {\n for (const result of results) {\n const entry = factsDb.getById(result.factId, getByIdOpts);\n if (!entry) continue;\n const score = 1 / (k + result.rank);\n const existing = seedScores.get(result.factId) ?? 0;\n seedScores.set(result.factId, existing + score);\n if (!seedEntries.has(result.factId)) seedEntries.set(result.factId, entry);\n }\n }\n\n const seeds = Array.from(seedScores.entries()).map(([factId, score]) => ({\n factId,\n score,\n entry: seedEntries.get(factId)!,\n }));\n const graphResults = runGraphStrategy(\n factsDb,\n seeds,\n config.graphWalkDepth,\n scopeFilter,\n asOf,\n graphHubDegreeCap,\n graphHubScorePenalty,\n );\n if (graphResults.length > 0) {\n strategyMap.set(\"graph\", graphResults);\n }\n }\n\n let fused: import(\"./rrf-fusion.js\").FusedResult[];\n if ((policy as ExplicitDeepRetrievalPolicy).allowRrfFusion) {\n fused = fuseResults(strategyMap, k);\n } else {\n const allResults = Array.from(strategyMap.values()).flat();\n const deduped = new Map<string, import(\"./rrf-fusion.js\").FusedResult>();\n\n for (const res of allResults) {\n if (!deduped.has(res.factId)) {\n deduped.set(res.factId, {\n factId: res.factId,\n finalScore: 1 / (k + res.rank),\n rrfScore: 1 / (k + res.rank),\n\n sources: [{ strategy: res.source || \"unknown\", rank: res.rank }],\n });\n } else {\n const existing = deduped.get(res.factId)!;\n existing.sources.push({ strategy: res.source || \"unknown\", rank: res.rank });\n if (res.source === \"semantic\" || res.source?.startsWith(\"semantic:\")) {\n const semanticScore = 1 / (k + res.rank);\n if (semanticScore > existing.finalScore) {\n existing.finalScore = semanticScore;\n existing.rrfScore = semanticScore;\n }\n }\n }\n }\n\n fused = Array.from(deduped.values()).sort((a, b) => {\n if (b.finalScore !== a.finalScore) return b.finalScore - a.finalScore;\n return a.factId.localeCompare(b.factId);\n });\n }\n if (fused.length === 0) {\n return {\n result: { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] },\n shouldRewrite: false,\n fromCache: false,\n };\n }\n\n const getByIdOpts = scopeFilter || asOf != null ? { scopeFilter, asOf } : undefined;\n const factMetaMap = new Map<string, FactMetadata>();\n let orderedEntries: Array<{ factId: string; entry: MemoryEntry }> = [];\n const effectiveNow = asOf ?? nowSec;\n\n for (const result of fused) {\n const entry = factsDb.getById(result.factId, getByIdOpts);\n if (!entry) continue;\n if (!includeSuperseded) {\n if (entry.supersededAt != null) continue;\n if (entry.expiresAt != null && entry.expiresAt <= effectiveNow) continue;\n }\n factMetaMap.set(result.factId, {\n id: entry.id,\n confidence: entry.confidence,\n lastAccessed: entry.lastAccessed ?? null,\n recallCount: entry.recallCount ?? 0,\n });\n orderedEntries.push({ factId: result.factId, entry });\n }\n\n let scopedFused = fused.filter((result) => factMetaMap.has(result.factId));\n applyPostRrfAdjustments(scopedFused, factMetaMap, nowSec);\n\n if (clustersConfig?.enabled && hasClusterLookup(factsDb)) {\n try {\n const clusterByFact = clusterCache.getClusterMap(factsDb, clustersConfig.minClusterSize);\n if (clusterByFact.size > 0) {\n const clusterToIndices = new Map<string, number[]>();\n for (let index = 0; index < scopedFused.length; index++) {\n const clusterId = clusterByFact.get(scopedFused[index].factId);\n if (!clusterId) continue;\n const list = clusterToIndices.get(clusterId) ?? [];\n list.push(index);\n clusterToIndices.set(clusterId, list);\n }\n\n const BOOST_MULTIPLIER = 1.1;\n for (const indices of clusterToIndices.values()) {\n if (indices.length < 2) continue;\n let bestIndex = indices[0];\n for (const index of indices) {\n if (scopedFused[index].finalScore > scopedFused[bestIndex].finalScore) {\n bestIndex = index;\n }\n }\n for (const index of indices) {\n if (index === bestIndex) continue;\n scopedFused[index].finalScore *= BOOST_MULTIPLIER;\n }\n }\n\n scopedFused.sort((a, b) => b.finalScore - a.finalScore);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"cluster-boost\",\n });\n }\n }\n\n const finalOrder = new Map<string, number>(scopedFused.map((result, index) => [result.factId, index]));\n orderedEntries.sort((a, b) => (finalOrder.get(a.factId) ?? 0) - (finalOrder.get(b.factId) ?? 0));\n\n if (activeDocumentGrader && orderedEntries.length > 0) {\n const grades = await activeDocumentGrader.gradeDocuments(\n queryText,\n orderedEntries.map(({ factId, entry }) => ({ factId, text: entry.text })),\n );\n if (grades.length > 0) {\n if (grades.every((grade) => !grade.relevant)) {\n return {\n result: buildOrchestratorResult(factsDb, scopedFused, orderedEntries, budgetTokens),\n shouldRewrite: true,\n fromCache: false,\n };\n }\n const relevantFactIds = new Set(grades.filter((grade) => grade.relevant).map((grade) => grade.factId));\n orderedEntries = orderedEntries.filter(({ factId }) => relevantFactIds.has(factId));\n scopedFused = scopedFused.filter((result) => relevantFactIds.has(result.factId));\n }\n }\n\n if (rerankingConfig?.enabled && rerankingOpenai && !expansion.skipReranking) {\n try {\n const rrfScoreMap = new Map<string, number>(scopedFused.map((result) => [result.factId, result.finalScore]));\n const scoredFacts: ScoredFact[] = orderedEntries.map(({ factId, entry }) => {\n const storedSec = entry.sourceDate ?? entry.createdAt;\n return {\n factId,\n text: entry.text,\n confidence: entry.confidence,\n storedDate: new Date(storedSec * 1000).toISOString().slice(0, 10),\n finalScore: rrfScoreMap.get(factId) ?? 0,\n };\n });\n\n const reranked = await rerankResults(queryText, scoredFacts, rerankingConfig, rerankingOpenai);\n const rerankedOrder = new Map(reranked.map((fact, index) => [fact.factId, index]));\n orderedEntries.sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n if (orderedEntries.length > reranked.length) {\n orderedEntries.length = reranked.length;\n }\n scopedFused.sort(\n (a, b) =>\n (rerankedOrder.get(a.factId) ?? Number.POSITIVE_INFINITY) -\n (rerankedOrder.get(b.factId) ?? Number.POSITIVE_INFINITY),\n );\n if (scopedFused.length > reranked.length) {\n scopedFused.length = reranked.length;\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"reranking\",\n });\n }\n }\n\n return {\n result: buildOrchestratorResult(factsDb, scopedFused, orderedEntries, budgetTokens),\n shouldRewrite: false,\n fromCache: false,\n };\n };\n\n const executePipelineQuery = async (\n queryText: string,\n currentQueryVector: number[] | null,\n ): Promise<{ result: OrchestratorResult; shouldRewrite: boolean; fromCache: boolean }> => {\n const expanderMode =\n queryExpander && typeof (queryExpander as QueryExpander).getMode === \"function\"\n ? queryExpander.getMode()\n : queryExpander\n ? \"always\"\n : \"off\";\n\n if (expanderMode === \"conditional\") {\n const alias =\n queryExpander && typeof (queryExpander as QueryExpander).getRuleBasedAlias === \"function\"\n ? queryExpander.getRuleBasedAlias(queryText)\n : null;\n const initial = await runBasePipeline(queryText, currentQueryVector, {\n useLlm: false,\n variants: alias ? [alias] : [],\n skipReranking: true,\n });\n const threshold =\n queryExpander && typeof (queryExpander as QueryExpander).getThreshold === \"function\"\n ? queryExpander.getThreshold()\n : 0.03;\n const topScore = initial.result.fused[0]?.finalScore ?? 0;\n if (initial.shouldRewrite || topScore < threshold) {\n return runBasePipeline(queryText, currentQueryVector, { useLlm: true, variants: null, skipReranking: false });\n }\n return { ...initial, result: await applyConditionalReranking(queryText, initial.result) };\n }\n\n if (expanderMode === \"always\") {\n return runBasePipeline(queryText, currentQueryVector, { useLlm: true, variants: null });\n }\n\n return runBasePipeline(queryText, currentQueryVector, { useLlm: false, variants: [] });\n };\n\n const attemptedQueries = [query];\n let currentQuery = query;\n let currentQueryVector = queryVector;\n let bestResult: OrchestratorResult | null = null;\n let bestResultWasIrrelevant = false;\n\n for (let iteration = 0; iteration <= MAX_REWRITE_ITERATIONS; iteration++) {\n const run = await executePipelineQuery(currentQuery, currentQueryVector);\n\n if (shouldPreferResult(run, bestResult, bestResultWasIrrelevant)) {\n bestResult = run.result;\n bestResultWasIrrelevant = run.shouldRewrite;\n }\n\n if (!run.shouldRewrite || !activeDocumentGrader) {\n if (\n !run.fromCache &&\n currentQueryVector &&\n run.result.fused.length > 0 &&\n typeof vectorDbWithCache.storeSemanticQueryCache === \"function\"\n ) {\n await vectorDbWithCache.storeSemanticQueryCache({\n queryText: currentQuery,\n vector: currentQueryVector,\n factIds: run.result.fused.map((result) => result.factId),\n filterKey: semanticCacheFilterKey,\n cachedAt: nowSec,\n });\n }\n return bestResult ?? run.result;\n }\n\n if (iteration === MAX_REWRITE_ITERATIONS) {\n return bestResult ?? run.result;\n }\n\n const rewritten = await activeDocumentGrader.rewriteQuery(query, attemptedQueries);\n if (!rewritten) {\n return bestResult ?? run.result;\n }\n\n attemptedQueries.push(rewritten);\n currentQuery = rewritten;\n if (embedFn) {\n try {\n currentQueryVector = await embedFn(rewritten);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"retrieval\",\n operation: \"rewrite-embed\",\n });\n currentQueryVector = null;\n }\n } else {\n currentQueryVector = null;\n }\n }\n\n return bestResult ?? { fused: [], packed: [], packedFactIds: [], tokensUsed: 0, entries: [] };\n}\n\n/** @deprecated Use runExplicitDeepRetrieval instead */\nexport const runRetrievalPipeline = runExplicitDeepRetrieval;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rrf-fusion.js","names":[],"sources":["../../services/rrf-fusion.ts"],"sourcesContent":["/**\n * RRF Fusion Module (Issue #152).\n *\n * Implements Reciprocal Rank Fusion (Cormack et al., 2009) for combining ranked\n * results from multiple retrieval strategies (semantic, FTS5, graph walk).\n *\n * After fusion, applies post-RRF score adjustments for recency, confidence,\n * and access frequency using fact metadata from the facts table.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A result from one retrieval strategy, with a 1-based rank within that list. */\nexport interface RankedResult {\n /** UUID of the matching fact. */\n factId: string;\n /** 1-based rank within the strategy's result list (1 = best). */\n rank: number;\n /** Which strategy produced this result. Additional strategies (e.g. multi-model semantic) use string keys. */\n source: \"semantic\" | \"fts5\" | \"graph\" | \"aliases\" | (string & {});\n}\n\n/** A fused result after RRF combining and post-RRF score adjustments. */\nexport interface FusedResult {\n /** UUID of the fact. */\n factId: string;\n /** Raw RRF score before post-RRF adjustments: Σ 1/(k + rank_i). */\n rrfScore: number;\n /** Which strategies contributed to this result and at what rank. */\n sources: Array<{ strategy: string; rank: number }>;\n /** Final score after post-RRF multipliers (recency, confidence, access frequency). */\n finalScore: number;\n}\n\n/**\n * Minimal fact metadata needed for post-RRF adjustments.\n * Matches the subset of MemoryEntry that is always available.\n */\nexport interface FactMetadata {\n /** Fact UUID. */\n id: string;\n /** Confidence score 0-1. Default 1.0. */\n confidence: number;\n /** Unix epoch seconds when the fact was last accessed. Null if never. */\n lastAccessed?: number | null;\n /** Number of times the fact has been recalled. Default 0. */\n recallCount?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default RRF k constant (Cormack et al., 2009). Higher = less rank-position sensitivity. */\nexport const RRF_K_DEFAULT = 60;\n\n// ---------------------------------------------------------------------------\n// fuseResults\n// ---------------------------------------------------------------------------\n\n/**\n * Fuse ranked results from multiple retrieval strategies using Reciprocal Rank Fusion.\n *\n * RRF formula: `score(fact) = Σ 1/(k + rank_i)` summed over all strategies\n * where the fact appears. Facts appearing in multiple strategies receive higher\n * combined scores than those from a single strategy.\n *\n * @param strategyResults - Map from strategy name to its ranked result list.\n * Each list should already be sorted best-first; rank is assigned by list position.\n * @param k - RRF constant (default 60). Higher = more lenient toward lower-ranked items.\n * @returns Fused results sorted by rrfScore descending. finalScore equals rrfScore\n * until applyPostRrfAdjustments() is called.\n */\nexport function fuseResults(strategyResults: Map<string, RankedResult[]>, k: number = RRF_K_DEFAULT): FusedResult[] {\n if (!Number.isFinite(k) || k <= 0) {\n throw new Error(`RRF k must be a positive finite number (got ${k})`);\n }\n // factId -> { rrfScore, sources }\n const accumulator = new Map<string, { rrfScore: number; sources: Array<{ strategy: string; rank: number }> }>();\n\n for (const [strategy, results] of strategyResults) {\n for (const result of results) {\n if (!Number.isFinite(result.rank) || result.rank < 1) {\n continue;\n }\n const existing = accumulator.get(result.factId);\n const contribution = 1 / (k + result.rank);\n if (existing) {\n existing.rrfScore += contribution;\n existing.sources.push({ strategy, rank: result.rank });\n } else {\n accumulator.set(result.factId, {\n rrfScore: contribution,\n sources: [{ strategy, rank: result.rank }],\n });\n }\n }\n }\n\n const fused: FusedResult[] = [];\n for (const [factId, { rrfScore, sources }] of accumulator) {\n fused.push({ factId, rrfScore, sources, finalScore: rrfScore });\n }\n\n // Sort by RRF score descending\n fused.sort((a, b) => b.rrfScore - a.rrfScore);\n return fused;\n}\n\n// ---------------------------------------------------------------------------\n// applyPostRrfAdjustments\n// ---------------------------------------------------------------------------\n\n/**\n * Apply post-RRF score adjustments to a list of fused results.\n *\n * Adjustments are multiplicative and applied to `rrfScore` to produce `finalScore`:\n *\n * - **Recency**: `score *= 1 + log(days_since_last_access + 1) * -0.01`\n * Slightly penalises facts that haven't been accessed in a long time.\n * A fact accessed today has multiplier ≈ 1.0; accessed 30 days ago ≈ 0.965.\n *\n * - **Confidence**: `score *= confidence`\n * Low-confidence facts are down-weighted proportionally.\n *\n * - **Access frequency**: `score *= 1 + min(recallCount * 0.02, 0.2)`\n * Facts recalled more often get a small boost (capped at +20%).\n *\n * @param results - Fused results list (mutated in place).\n * @param facts - Map from factId to fact metadata. Facts not found in the map\n * get neutral adjustments (confidence=1, no recency penalty, no frequency boost).\n * @param nowSec - Current time as Unix epoch seconds (default: Date.now()/1000).\n * @returns The same array, mutated, re-sorted by finalScore descending.\n */\nexport function applyPostRrfAdjustments(\n results: FusedResult[],\n facts: Map<string, FactMetadata>,\n nowSec: number = Math.floor(Date.now() / 1000),\n): FusedResult[] {\n const SECONDS_PER_DAY = 86_400;\n\n for (const result of results) {\n const fact = facts.get(result.factId);\n let score = result.rrfScore;\n\n // Recency adjustment\n const lastAccessedRaw = fact?.lastAccessed;\n const lastAccessedSec = Number.isFinite(lastAccessedRaw ?? Number.NaN) ? (lastAccessedRaw as number) : null;\n const daysSince = lastAccessedSec != null ? Math.max(0, (nowSec - lastAccessedSec) / SECONDS_PER_DAY) : 0; // no access record → treat as fresh (neutral)\n score *= 1 + Math.log(daysSince + 1) * -0.01;\n\n // Confidence adjustment\n const confidenceRaw = fact?.confidence;\n const confidence = Number.isFinite(confidenceRaw ?? Number.NaN) ? (confidenceRaw as number) : 1.0;\n score *= Math.max(0, Math.min(1, confidence));\n\n // Access frequency adjustment\n const rawRecall = fact?.recallCount;\n const recallCount = Number.isFinite(rawRecall ?? Number.NaN) ? Math.max(0, rawRecall as number) : 0;\n score *= 1 + Math.min(recallCount * 0.02, 0.2);\n\n result.finalScore = score;\n }\n\n results.sort((a, b) => b.finalScore - a.finalScore);\n return results;\n}\n"],"mappings":";;;;;;;;;;;;;AA2EA,SAAgB,YAAY,iBAA8C,IAAA,IAA0C;CAClH,IAAI,CAAC,OAAO,SAAS,EAAE,IAAI,KAAK,GAC9B,MAAM,IAAI,MAAM,+CAA+C,EAAE,GAAG;CAGtE,MAAM,8BAAc,IAAI,KAAuF;CAE/G,KAAK,MAAM,CAAC,UAAU,YAAY,iBAChC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,CAAC,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,OAAO,GACjD;EAEF,MAAM,WAAW,YAAY,IAAI,OAAO,OAAO;EAC/C,MAAM,eAAe,KAAK,IAAI,OAAO;EACrC,IAAI,UAAU;GACZ,SAAS,YAAY;GACrB,SAAS,QAAQ,KAAK;IAAE;IAAU,MAAM,OAAO;IAAM,CAAC;SAEtD,YAAY,IAAI,OAAO,QAAQ;GAC7B,UAAU;GACV,SAAS,CAAC;IAAE;IAAU,MAAM,OAAO;IAAM,CAAC;GAC3C,CAAC;;CAKR,MAAM,QAAuB,EAAE;CAC/B,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,cAAc,aAC5C,MAAM,KAAK;EAAE;EAAQ;EAAU;EAAS,YAAY;EAAU,CAAC;CAIjE,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;CAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;AA4BT,SAAgB,wBACd,SACA,OACA,SAAiB,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,EAC/B;CACf,MAAM,kBAAkB;CAExB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,MAAM,IAAI,OAAO,OAAO;EACrC,IAAI,QAAQ,OAAO;EAGnB,MAAM,kBAAkB,MAAM;EAC9B,MAAM,kBAAkB,OAAO,SAAS,mBAAmB,IAAW,GAAI,kBAA6B;EACvG,MAAM,YAAY,mBAAmB,OAAO,KAAK,IAAI,IAAI,SAAS,mBAAmB,gBAAgB,GAAG;EACxG,SAAS,IAAI,KAAK,IAAI,YAAY,EAAE,GAAG;EAGvC,MAAM,gBAAgB,MAAM;EAE5B,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GADX,OAAO,SAAS,iBAAiB,IAAW,GAAI,gBAA2B,EAClD,CAAC;EAG7C,MAAM,YAAY,MAAM;EAExB,SAAS,IAAI,KAAK,KADE,OAAO,SAAS,aAAa,IAAW,GAAG,KAAK,IAAI,GAAG,UAAoB,GAAG,KAC9D,KAAM,GAAI;EAE9C,OAAO,aAAa;;CAGtB,QAAQ,MAAM,GAAG,MAAM,EAAE,aAAa,EAAE,WAAW;CACnD,OAAO"}
1
+ {"version":3,"file":"rrf-fusion.js","names":[],"sources":["../../services/rrf-fusion.ts"],"sourcesContent":["/**\n * RRF Fusion Module (Issue #152).\n *\n * Implements Reciprocal Rank Fusion (Cormack et al., 2009) for combining ranked\n * results from multiple retrieval strategies (semantic, FTS5, graph walk).\n *\n * After fusion, applies post-RRF score adjustments for recency, confidence,\n * and access frequency using fact metadata from the facts table.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A result from one retrieval strategy, with a 1-based rank within that list. */\nexport interface RankedResult {\n /** UUID of the matching fact. */\n factId: string;\n /** 1-based rank within the strategy's result list (1 = best). */\n rank: number;\n /** Which strategy produced this result. Additional strategies (e.g. multi-model semantic) use string keys. */\n source: \"semantic\" | \"fts5\" | \"graph\" | \"aliases\" | (string & {});\n}\n\n/** A fused result after RRF combining and post-RRF score adjustments. */\nexport interface FusedResult {\n /** UUID of the fact. */\n factId: string;\n /** Raw RRF score before post-RRF adjustments: Σ 1/(k + rank_i). */\n rrfScore: number;\n /** Which strategies contributed to this result and at what rank. */\n sources: Array<{ strategy: string; rank: number }>;\n /** Final score after post-RRF multipliers (recency, confidence, access frequency). */\n finalScore: number;\n}\n\n/**\n * Minimal fact metadata needed for post-RRF adjustments.\n * Matches the subset of MemoryEntry that is always available.\n */\nexport interface FactMetadata {\n /** Fact UUID. */\n id: string;\n /** Confidence score 0-1. Default 1.0. */\n confidence: number;\n /** Unix epoch seconds when the fact was last accessed. Null if never. */\n lastAccessed?: number | null;\n /** Number of times the fact has been recalled. Default 0. */\n recallCount?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default RRF k constant (Cormack et al., 2009). Higher = less rank-position sensitivity. */\nexport const RRF_K_DEFAULT = 60;\n\n// ---------------------------------------------------------------------------\n// fuseResults\n// ---------------------------------------------------------------------------\n\n/**\n * Fuse ranked results from multiple retrieval strategies using Reciprocal Rank Fusion.\n *\n * RRF formula: `score(fact) = Σ 1/(k + rank_i)` summed over all strategies\n * where the fact appears. Facts appearing in multiple strategies receive higher\n * combined scores than those from a single strategy.\n *\n * @param strategyResults - Map from strategy name to its ranked result list.\n * Each list should already be sorted best-first; rank is assigned by list position.\n * @param k - RRF constant (default 60). Higher = more lenient toward lower-ranked items.\n * @returns Fused results sorted by rrfScore descending. finalScore equals rrfScore\n * until applyPostRrfAdjustments() is called.\n */\nexport function fuseResults(strategyResults: Map<string, RankedResult[]>, k: number = RRF_K_DEFAULT): FusedResult[] {\n if (!Number.isFinite(k) || k <= 0) {\n throw new Error(`RRF k must be a positive finite number (got ${k})`);\n }\n // factId -> { rrfScore, sources }\n const accumulator = new Map<string, { rrfScore: number; sources: Array<{ strategy: string; rank: number }> }>();\n\n for (const [strategy, results] of strategyResults) {\n for (const result of results) {\n if (!Number.isFinite(result.rank) || result.rank < 1) {\n continue;\n }\n const existing = accumulator.get(result.factId);\n const contribution = 1 / (k + result.rank);\n if (existing) {\n existing.rrfScore += contribution;\n existing.sources.push({ strategy, rank: result.rank });\n } else {\n accumulator.set(result.factId, {\n rrfScore: contribution,\n sources: [{ strategy, rank: result.rank }],\n });\n }\n }\n }\n\n const fused: FusedResult[] = [];\n for (const [factId, { rrfScore, sources }] of accumulator) {\n fused.push({ factId, rrfScore, sources, finalScore: rrfScore });\n }\n\n // Sort by RRF score descending\n fused.sort((a, b) => b.rrfScore - a.rrfScore);\n return fused;\n}\n\n// ---------------------------------------------------------------------------\n// applyPostRrfAdjustments\n// ---------------------------------------------------------------------------\n\n/**\n * Apply post-RRF score adjustments to a list of fused results.\n *\n * Adjustments are multiplicative and applied to `rrfScore` to produce `finalScore`:\n *\n * - **Recency**: `score *= 1 + log(days_since_last_access + 1) * -0.01`\n * Slightly penalises facts that haven't been accessed in a long time.\n * A fact accessed today has multiplier ≈ 1.0; accessed 30 days ago ≈ 0.965.\n *\n * - **Confidence**: `score *= confidence`\n * Low-confidence facts are down-weighted proportionally.\n *\n * - **Access frequency**: `score *= 1 + min(recallCount * 0.02, 0.2)`\n * Facts recalled more often get a small boost (capped at +20%).\n *\n * @param results - Fused results list (mutated in place).\n * @param facts - Map from factId to fact metadata. Facts not found in the map\n * get neutral adjustments (confidence=1, no recency penalty, no frequency boost).\n * @param nowSec - Current time as Unix epoch seconds (default: Date.now()/1000).\n * @returns The same array, mutated, re-sorted by finalScore descending.\n */\nexport function applyPostRrfAdjustments(\n results: FusedResult[],\n facts: Map<string, FactMetadata>,\n nowSec: number = Math.floor(Date.now() / 1000),\n): FusedResult[] {\n const SECONDS_PER_DAY = 86_400;\n\n for (const result of results) {\n const fact = facts.get(result.factId);\n let score = result.rrfScore;\n\n // Recency adjustment\n const lastAccessedRaw = fact?.lastAccessed;\n const lastAccessedSec = Number.isFinite(lastAccessedRaw ?? Number.NaN) ? (lastAccessedRaw as number) : null;\n const daysSince = lastAccessedSec != null ? Math.max(0, (nowSec - lastAccessedSec) / SECONDS_PER_DAY) : 0; // no access record → treat as fresh (neutral)\n score *= 1 + Math.log(daysSince + 1) * -0.01;\n\n // Confidence adjustment\n const confidenceRaw = fact?.confidence;\n const confidence = Number.isFinite(confidenceRaw ?? Number.NaN) ? (confidenceRaw as number) : 1.0;\n score *= Math.max(0, Math.min(1, confidence));\n\n // Access frequency adjustment\n const rawRecall = fact?.recallCount;\n const recallCount = Number.isFinite(rawRecall ?? Number.NaN) ? Math.max(0, rawRecall as number) : 0;\n score *= 1 + Math.min(recallCount * 0.02, 0.2);\n\n result.finalScore = score;\n }\n\n results.sort((a, b) => b.finalScore - a.finalScore);\n return results;\n}\n"],"mappings":";;;;;;;;;;;;;AA2EA,SAAgB,YAAY,iBAA8C,IAAA,IAA0C;CAClH,IAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,GAC9B,MAAM,IAAI,MAAM,+CAA+C,EAAE,EAAE;CAGrE,MAAM,8BAAc,IAAI,IAAsF;CAE9G,KAAK,MAAM,CAAC,UAAU,YAAY,iBAChC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,CAAC,OAAO,SAAS,OAAO,IAAI,KAAK,OAAO,OAAO,GACjD;EAEF,MAAM,WAAW,YAAY,IAAI,OAAO,MAAM;EAC9C,MAAM,eAAe,KAAK,IAAI,OAAO;EACrC,IAAI,UAAU;GACZ,SAAS,YAAY;GACrB,SAAS,QAAQ,KAAK;IAAE;IAAU,MAAM,OAAO;GAAK,CAAC;EACvD,OACE,YAAY,IAAI,OAAO,QAAQ;GAC7B,UAAU;GACV,SAAS,CAAC;IAAE;IAAU,MAAM,OAAO;GAAK,CAAC;EAC3C,CAAC;CAEL;CAGF,MAAM,QAAuB,CAAC;CAC9B,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,cAAc,aAC5C,MAAM,KAAK;EAAE;EAAQ;EAAU;EAAS,YAAY;CAAS,CAAC;CAIhE,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAC5C,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,wBACd,SACA,OACA,SAAiB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GAC9B;CACf,MAAM,kBAAkB;CAExB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,MAAM,IAAI,OAAO,MAAM;EACpC,IAAI,QAAQ,OAAO;EAGnB,MAAM,kBAAkB,MAAM;EAC9B,MAAM,kBAAkB,OAAO,SAAS,mBAAmB,GAAU,IAAK,kBAA6B;EACvG,MAAM,YAAY,mBAAmB,OAAO,KAAK,IAAI,IAAI,SAAS,mBAAmB,eAAe,IAAI;EACxG,SAAS,IAAI,KAAK,IAAI,YAAY,CAAC,IAAI;EAGvC,MAAM,gBAAgB,MAAM;EAE5B,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GADX,OAAO,SAAS,iBAAiB,GAAU,IAAK,gBAA2B,CACnD,CAAC;EAG5C,MAAM,YAAY,MAAM;EAExB,SAAS,IAAI,KAAK,KADE,OAAO,SAAS,aAAa,GAAU,IAAI,KAAK,IAAI,GAAG,SAAmB,IAAI,KAC9D,KAAM,EAAG;EAE7C,OAAO,aAAa;CACtB;CAEA,QAAQ,MAAM,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;CAClD,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"self-correction-extract.js","names":[],"sources":["../../services/self-correction-extract.ts"],"sourcesContent":["/**\n * Self-correction extraction: scan session JSONL for user messages\n * that look like corrections/nudges, using multi-language correction signals\n * from .language-keywords.json (after openclaw hybrid-mem build-languages).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { extractMessageText, timestampFromFilename, truncate } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type CorrectionIncident = {\n userMessage: string;\n precedingAssistant: string;\n followingAssistant: string;\n timestamp?: string;\n sessionFile: string;\n};\n\nexport type SelfCorrectionExtractResult = {\n incidents: CorrectionIncident[];\n sessionsScanned: number;\n};\n\nconst MAX_USER_MSG = 800;\nconst MAX_ASSISTANT_MSG = 500;\n\n/** Patterns that indicate a user message should be skipped (heartbeat, cron, system, etc.). */\nconst SKIP_PATTERNS = [\n /heartbeat/i,\n /cron\\s+job|cronjob|schedule.*run|run\\s+the\\s+nightly|run\\s+the\\s+weekly/i,\n /compact|pre-compaction|compaction\\s+flush/i,\n /sub-?agent|subagent\\s+announce/i,\n /NO_REPLY|no\\s+reply\\s+needed/i,\n /^\\s*\\{.*\"schedule\"/m, // JSON cron definition\n];\n\nfunction shouldSkipUserMessage(text: string): boolean {\n if (!text || text.length < 25) return true;\n const t = text.trim();\n if (t.length < 25) return true;\n for (const re of SKIP_PATTERNS) {\n if (re.test(t)) return true;\n }\n return false;\n}\n\ntype RunSelfCorrectionExtractOpts = {\n filePaths: string[];\n correctionRegex: RegExp;\n};\n\n/**\n * Scan session JSONL files for user messages matching correction signals.\n * Uses the provided regex (from getCorrectionSignalRegex() after setKeywordsPath)\n * so that all languages from .language-keywords.json are included.\n */\nexport function runSelfCorrectionExtract(opts: RunSelfCorrectionExtractOpts): SelfCorrectionExtractResult {\n const { filePaths, correctionRegex } = opts;\n const incidents: CorrectionIncident[] = [];\n\n for (const filePath of filePaths) {\n let lines: string[];\n try {\n lines = readFileSync(filePath, \"utf-8\").split(\"\\n\");\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"read-session-file\",\n severity: \"info\",\n subsystem: \"self-correction-extract\",\n });\n continue;\n }\n\n const messages: Array<{ role: string; text: string }> = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const obj = JSON.parse(trimmed) as { type?: string; message?: { role?: string; content?: unknown } };\n if (obj.type !== \"message\" || !obj.message) continue;\n const msg = obj.message;\n const role = msg.role === \"user\" || msg.role === \"assistant\" ? msg.role : null;\n if (!role) continue;\n const text = extractMessageText(msg.content);\n messages.push({ role, text });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"self-correction-extract\",\n });\n // skip malformed lines\n }\n }\n\n const sessionName = basename(filePath);\n const ts = timestampFromFilename(sessionName);\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"user\") continue;\n const userText = messages[i].text;\n if (!correctionRegex.test(userText)) continue;\n if (shouldSkipUserMessage(userText)) continue;\n\n const precedingAssistant = i > 0 && messages[i - 1].role === \"assistant\" ? messages[i - 1].text : \"\";\n const followingAssistant =\n i + 1 < messages.length && messages[i + 1].role === \"assistant\" ? messages[i + 1].text : \"\";\n\n incidents.push({\n userMessage: truncate(userText, MAX_USER_MSG),\n precedingAssistant: truncate(precedingAssistant, MAX_ASSISTANT_MSG),\n followingAssistant: truncate(followingAssistant, MAX_ASSISTANT_MSG),\n timestamp: ts,\n sessionFile: sessionName,\n });\n }\n }\n\n return { incidents, sessionsScanned: filePaths.length };\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAM,eAAe;AACrB,MAAM,oBAAoB;;AAG1B,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAS,sBAAsB,MAAuB;CACpD,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO;CACtC,MAAM,IAAI,KAAK,MAAM;CACrB,IAAI,EAAE,SAAS,IAAI,OAAO;CAC1B,KAAK,MAAM,MAAM,eACf,IAAI,GAAG,KAAK,EAAE,EAAE,OAAO;CAEzB,OAAO;;;;;;;AAaT,SAAgB,yBAAyB,MAAiE;CACxG,MAAM,EAAE,WAAW,oBAAoB;CACvC,MAAM,YAAkC,EAAE;CAE1C,KAAK,MAAM,YAAY,WAAW;EAChC,IAAI;EACJ,IAAI;GACF,QAAQ,aAAa,UAAU,QAAQ,CAAC,MAAM,KAAK;WAC5C,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;IACtE,WAAW;IACX,UAAU;IACV,WAAW;IACZ,CAAC;GACF;;EAGF,MAAM,WAAkD,EAAE;EAC1D,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,MAAM;GAC3B,IAAI,CAAC,SAAS;GACd,IAAI;IACF,MAAM,MAAM,KAAK,MAAM,QAAQ;IAC/B,IAAI,IAAI,SAAS,aAAa,CAAC,IAAI,SAAS;IAC5C,MAAM,MAAM,IAAI;IAChB,MAAM,OAAO,IAAI,SAAS,UAAU,IAAI,SAAS,cAAc,IAAI,OAAO;IAC1E,IAAI,CAAC,MAAM;IACX,MAAM,OAAO,mBAAmB,IAAI,QAAQ;IAC5C,SAAS,KAAK;KAAE;KAAM;KAAM,CAAC;YACtB,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;KACtE,WAAW;KACX,UAAU;KACV,WAAW;KACZ,CAAC;;;EAKN,MAAM,cAAc,SAAS,SAAS;EACtC,MAAM,KAAK,sBAAsB,YAAY;EAE7C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,IAAI,SAAS,GAAG,SAAS,QAAQ;GACjC,MAAM,WAAW,SAAS,GAAG;GAC7B,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE;GACrC,IAAI,sBAAsB,SAAS,EAAE;GAErC,MAAM,qBAAqB,IAAI,KAAK,SAAS,IAAI,GAAG,SAAS,cAAc,SAAS,IAAI,GAAG,OAAO;GAClG,MAAM,qBACJ,IAAI,IAAI,SAAS,UAAU,SAAS,IAAI,GAAG,SAAS,cAAc,SAAS,IAAI,GAAG,OAAO;GAE3F,UAAU,KAAK;IACb,aAAa,SAAS,UAAU,aAAa;IAC7C,oBAAoB,SAAS,oBAAoB,kBAAkB;IACnE,oBAAoB,SAAS,oBAAoB,kBAAkB;IACnE,WAAW;IACX,aAAa;IACd,CAAC;;;CAIN,OAAO;EAAE;EAAW,iBAAiB,UAAU;EAAQ"}
1
+ {"version":3,"file":"self-correction-extract.js","names":[],"sources":["../../services/self-correction-extract.ts"],"sourcesContent":["/**\n * Self-correction extraction: scan session JSONL for user messages\n * that look like corrections/nudges, using multi-language correction signals\n * from .language-keywords.json (after openclaw hybrid-mem build-languages).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { extractMessageText, timestampFromFilename, truncate } from \"../utils/text.js\";\nimport { capturePluginError } from \"./error-reporter.js\";\n\nexport type CorrectionIncident = {\n userMessage: string;\n precedingAssistant: string;\n followingAssistant: string;\n timestamp?: string;\n sessionFile: string;\n};\n\nexport type SelfCorrectionExtractResult = {\n incidents: CorrectionIncident[];\n sessionsScanned: number;\n};\n\nconst MAX_USER_MSG = 800;\nconst MAX_ASSISTANT_MSG = 500;\n\n/** Patterns that indicate a user message should be skipped (heartbeat, cron, system, etc.). */\nconst SKIP_PATTERNS = [\n /heartbeat/i,\n /cron\\s+job|cronjob|schedule.*run|run\\s+the\\s+nightly|run\\s+the\\s+weekly/i,\n /compact|pre-compaction|compaction\\s+flush/i,\n /sub-?agent|subagent\\s+announce/i,\n /NO_REPLY|no\\s+reply\\s+needed/i,\n /^\\s*\\{.*\"schedule\"/m, // JSON cron definition\n];\n\nfunction shouldSkipUserMessage(text: string): boolean {\n if (!text || text.length < 25) return true;\n const t = text.trim();\n if (t.length < 25) return true;\n for (const re of SKIP_PATTERNS) {\n if (re.test(t)) return true;\n }\n return false;\n}\n\ntype RunSelfCorrectionExtractOpts = {\n filePaths: string[];\n correctionRegex: RegExp;\n};\n\n/**\n * Scan session JSONL files for user messages matching correction signals.\n * Uses the provided regex (from getCorrectionSignalRegex() after setKeywordsPath)\n * so that all languages from .language-keywords.json are included.\n */\nexport function runSelfCorrectionExtract(opts: RunSelfCorrectionExtractOpts): SelfCorrectionExtractResult {\n const { filePaths, correctionRegex } = opts;\n const incidents: CorrectionIncident[] = [];\n\n for (const filePath of filePaths) {\n let lines: string[];\n try {\n lines = readFileSync(filePath, \"utf-8\").split(\"\\n\");\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"read-session-file\",\n severity: \"info\",\n subsystem: \"self-correction-extract\",\n });\n continue;\n }\n\n const messages: Array<{ role: string; text: string }> = [];\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const obj = JSON.parse(trimmed) as { type?: string; message?: { role?: string; content?: unknown } };\n if (obj.type !== \"message\" || !obj.message) continue;\n const msg = obj.message;\n const role = msg.role === \"user\" || msg.role === \"assistant\" ? msg.role : null;\n if (!role) continue;\n const text = extractMessageText(msg.content);\n messages.push({ role, text });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n operation: \"parse-session-line\",\n severity: \"info\",\n subsystem: \"self-correction-extract\",\n });\n // skip malformed lines\n }\n }\n\n const sessionName = basename(filePath);\n const ts = timestampFromFilename(sessionName);\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"user\") continue;\n const userText = messages[i].text;\n if (!correctionRegex.test(userText)) continue;\n if (shouldSkipUserMessage(userText)) continue;\n\n const precedingAssistant = i > 0 && messages[i - 1].role === \"assistant\" ? messages[i - 1].text : \"\";\n const followingAssistant =\n i + 1 < messages.length && messages[i + 1].role === \"assistant\" ? messages[i + 1].text : \"\";\n\n incidents.push({\n userMessage: truncate(userText, MAX_USER_MSG),\n precedingAssistant: truncate(precedingAssistant, MAX_ASSISTANT_MSG),\n followingAssistant: truncate(followingAssistant, MAX_ASSISTANT_MSG),\n timestamp: ts,\n sessionFile: sessionName,\n });\n }\n }\n\n return { incidents, sessionsScanned: filePaths.length };\n}\n"],"mappings":";;;;;;;;;;AAwBA,MAAM,eAAe;AACrB,MAAM,oBAAoB;;AAG1B,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,sBAAsB,MAAuB;CACpD,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO;CACtC,MAAM,IAAI,KAAK,KAAK;CACpB,IAAI,EAAE,SAAS,IAAI,OAAO;CAC1B,KAAK,MAAM,MAAM,eACf,IAAI,GAAG,KAAK,CAAC,GAAG,OAAO;CAEzB,OAAO;AACT;;;;;;AAYA,SAAgB,yBAAyB,MAAiE;CACxG,MAAM,EAAE,WAAW,oBAAoB;CACvC,MAAM,YAAkC,CAAC;CAEzC,KAAK,MAAM,YAAY,WAAW;EAChC,IAAI;EACJ,IAAI;GACF,QAAQ,aAAa,UAAU,OAAO,EAAE,MAAM,IAAI;EACpD,SAAS,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;IACtE,WAAW;IACX,UAAU;IACV,WAAW;GACb,CAAC;GACD;EACF;EAEA,MAAM,WAAkD,CAAC;EACzD,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,UAAU,KAAK,KAAK;GAC1B,IAAI,CAAC,SAAS;GACd,IAAI;IACF,MAAM,MAAM,KAAK,MAAM,OAAO;IAC9B,IAAI,IAAI,SAAS,aAAa,CAAC,IAAI,SAAS;IAC5C,MAAM,MAAM,IAAI;IAChB,MAAM,OAAO,IAAI,SAAS,UAAU,IAAI,SAAS,cAAc,IAAI,OAAO;IAC1E,IAAI,CAAC,MAAM;IACX,MAAM,OAAO,mBAAmB,IAAI,OAAO;IAC3C,SAAS,KAAK;KAAE;KAAM;IAAK,CAAC;GAC9B,SAAS,KAAK;IACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,GAAG;KACtE,WAAW;KACX,UAAU;KACV,WAAW;IACb,CAAC;GAEH;EACF;EAEA,MAAM,cAAc,SAAS,QAAQ;EACrC,MAAM,KAAK,sBAAsB,WAAW;EAE5C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GACxC,IAAI,SAAS,GAAG,SAAS,QAAQ;GACjC,MAAM,WAAW,SAAS,GAAG;GAC7B,IAAI,CAAC,gBAAgB,KAAK,QAAQ,GAAG;GACrC,IAAI,sBAAsB,QAAQ,GAAG;GAErC,MAAM,qBAAqB,IAAI,KAAK,SAAS,IAAI,GAAG,SAAS,cAAc,SAAS,IAAI,GAAG,OAAO;GAClG,MAAM,qBACJ,IAAI,IAAI,SAAS,UAAU,SAAS,IAAI,GAAG,SAAS,cAAc,SAAS,IAAI,GAAG,OAAO;GAE3F,UAAU,KAAK;IACb,aAAa,SAAS,UAAU,YAAY;IAC5C,oBAAoB,SAAS,oBAAoB,iBAAiB;IAClE,oBAAoB,SAAS,oBAAoB,iBAAiB;IAClE,WAAW;IACX,aAAa;GACf,CAAC;EACH;CACF;CAEA,OAAO;EAAE;EAAW,iBAAiB,UAAU;CAAO;AACxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-observability.js","names":[],"sources":["../../services/session-observability.ts"],"sourcesContent":["/**\n * Session Observability Service — Issue #1025\n *\n * Builds a unified session observability report from existing stores:\n * - Event log (episodes, user turns, agent actions)\n * - Audit store (recall, injection, capture, skipped writes)\n * - Facts DB (captured facts/entities for this session)\n * - Narratives DB (session narrative summaries)\n * - Context audit (injected prompt content)\n *\n * Output structure:\n * - timeline — merged chronological view\n * - capture — what was stored / suppressed during capture\n * - recall — why memories were recalled / not recalled\n * - injection — what entered prompt context and why\n * - suppressions — writes suppressed by policy/guards/errors\n * - summary — human-readable single-paragraph summary\n */\n\nimport type { AuditStore } from \"../backends/audit-store.js\";\nimport type { EventLog } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SessionTimelineEntry {\n /** ISO timestamp */\n timestamp: string;\n /** Event kind for display / filtering */\n kind:\n | \"user_turn\"\n | \"agent_action\"\n | \"memory_captured\"\n | \"memory_recalled\"\n | \"memory_injected\"\n | \"memory_suppressed\"\n | \"episode_recorded\"\n | \"narrative_generated\"\n | \"audit_event\"\n | \"recall_explanation\"\n | \"injection_summary\"\n | \"capture_summary\";\n /** Short label */\n label: string;\n /** What happened */\n description: string;\n /** Optional detail payload (structured) */\n detail?: unknown;\n /** Score or rank when applicable */\n score?: number;\n /** Outcome for audit/episode events */\n outcome?: \"success\" | \"partial\" | \"failed\" | \"skipped\";\n}\n\nexport interface CaptureSummary {\n factsStored: number;\n factsUpdated: number;\n duplicatesSuppressed: number;\n noopSkipped: number;\n errorsEncountered: number;\n entitiesExtracted: number;\n episodesRecorded: number;\n proceduresLearned: number;\n entries: SessionTimelineEntry[];\n}\n\nexport interface RecallExplanation {\n query: string;\n candidatesFound: number;\n injectedCount: number;\n omittedCount: number;\n strategies: string[];\n directiveMatches: string[];\n suppressionReasons: string[];\n entries: SessionTimelineEntry[];\n}\n\nexport interface InjectionSummary {\n totalChars: number;\n totalTokensEstimate: number;\n blocksInjected: number;\n budgetTokens: number;\n budgetUsedFraction: number;\n prependContext?: string;\n entries: SessionTimelineEntry[];\n}\n\nexport interface SuppressionEntry {\n timestamp: string;\n reason: string;\n detail?: string;\n outcome: \"skipped\" | \"no_op\" | \"error\";\n}\n\nexport interface SessionObservabilityReport {\n sessionId: string | null;\n agentId: string | null;\n /** Session window */\n windowStart: string | null;\n windowEnd: string | null;\n /** Merged chronological timeline */\n timeline: SessionTimelineEntry[];\n capture: CaptureSummary;\n recall: RecallExplanation;\n injection: InjectionSummary;\n suppressions: SuppressionEntry[];\n /** Human-readable summary paragraph */\n summary: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatTs(ms: number): string {\n return new Date(ms).toISOString();\n}\n\nfunction makeEntry(\n ts: number,\n kind: SessionTimelineEntry[\"kind\"],\n label: string,\n description: string,\n detail?: unknown,\n opts?: { score?: number; outcome?: SessionTimelineEntry[\"outcome\"] },\n): SessionTimelineEntry {\n return {\n timestamp: formatTs(ts),\n kind,\n label,\n description,\n detail,\n ...(opts?.score !== undefined ? { score: opts.score } : {}),\n ...(opts?.outcome ? { outcome: opts.outcome } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core report builder\n// ---------------------------------------------------------------------------\n\nexport interface SessionObservabilityDeps {\n factsDb: FactsDB;\n eventLog: EventLog | null;\n narrativesDb: NarrativesDB | null;\n auditStore: AuditStore | null;\n /** SQLite path — used to scope facts to session via source_sessions join */\n sqlitePath?: string;\n sessionId?: string | null;\n agentId?: string | null;\n /** Max timeline entries per section (default 50) */\n limit?: number;\n}\n\nexport async function buildSessionObservabilityReport(\n deps: SessionObservabilityDeps,\n): Promise<SessionObservabilityReport> {\n const { factsDb, eventLog, narrativesDb, auditStore, sessionId, agentId, limit = 50 } = deps;\n const now = Date.now();\n\n // ---------------------------------------------------------------------------\n // 1. Collect audit events for this session\n // ---------------------------------------------------------------------------\n const auditEntries: SessionTimelineEntry[] = [];\n const suppressionEntries: SuppressionEntry[] = [];\n\n if (auditStore) {\n const auditRows = auditStore.query({\n sessionId: sessionId ?? undefined,\n agentId: agentId ?? undefined,\n limit,\n });\n\n for (const row of auditRows) {\n const label = row.action;\n const desc = row.target ? `${row.action} → ${row.target}` : row.action;\n\n if (\n row.outcome === \"failed\" ||\n row.action.includes(\"suppressed\") ||\n row.action.includes(\"skip\") ||\n row.action.includes(\"noop\") ||\n row.action.includes(\"guard\")\n ) {\n suppressionEntries.push({\n timestamp: formatTs(row.timestamp),\n reason: row.action,\n detail: row.error ?? row.target ?? undefined,\n outcome: row.outcome === \"failed\" ? \"error\" : \"skipped\",\n });\n }\n\n auditEntries.push(\n makeEntry(\n row.timestamp,\n \"audit_event\",\n label,\n desc,\n {\n outcome: row.outcome,\n durationMs: row.durationMs,\n error: row.error,\n contextKeys: row.context ? Object.keys(row.context) : [],\n tokens: row.tokens,\n },\n { outcome: row.outcome as SessionTimelineEntry[\"outcome\"] },\n ),\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // 2. Collect events from the event log for this session\n // ---------------------------------------------------------------------------\n const eventEntries: SessionTimelineEntry[] = [];\n if (eventLog) {\n // NOTE: eventLog.getSessionEvents or equivalent — check available methods\n // We query via factsDb since episodes table is accessible there\n // Only include events in the session window (24h lookback by default)\n const _sinceMs = now - 24 * 3600 * 1000;\n try {\n // episodes are stored in facts DB; we use factsDb to retrieve them\n const episodes =\n (factsDb as { getEpisodesBySession?(sid: string, lim: number): unknown[] })?.getEpisodesBySession?.(\n sessionId ?? \"recent\",\n limit,\n ) ?? [];\n\n for (const ep of episodes as Array<{\n id?: string;\n event?: string;\n outcome?: string;\n timestamp?: number | string;\n context?: string;\n }>) {\n const ts = typeof ep.timestamp === \"number\" ? ep.timestamp : Date.parse(String(ep.timestamp ?? \"0\"));\n eventEntries.push(\n makeEntry(\n ts,\n \"episode_recorded\",\n ep.event ?? \"episode\",\n ep.context ?? ep.event ?? \"\",\n { outcome: ep.outcome },\n { outcome: ep.outcome as SessionTimelineEntry[\"outcome\"] },\n ),\n );\n }\n } catch {\n // eventLog may not expose session-scoped episode query; fall back silently\n }\n }\n\n // ---------------------------------------------------------------------------\n // 3. Capture summary — what was stored this session\n // ---------------------------------------------------------------------------\n // Use audit rows with action patterns that indicate store outcomes\n const storeAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return (\n a.includes(\"store\") ||\n a.includes(\"capture\") ||\n a.includes(\"write\") ||\n a.includes(\"classify\") ||\n a.includes(\"duplicate\") ||\n a.includes(\"noop\")\n );\n });\n\n const captureEntries: SessionTimelineEntry[] = [];\n let factsStored = 0;\n let factsUpdated = 0;\n let duplicatesSuppressed = 0;\n let noopSkipped = 0;\n let errorsEncountered = 0;\n\n for (const entry of storeAuditActions) {\n captureEntries.push(entry);\n const a = entry.label.toLowerCase();\n if (entry.outcome === \"failed\" || a.includes(\"error\")) {\n errorsEncountered++;\n } else if (a.includes(\"duplicate\")) {\n duplicatesSuppressed++;\n } else if (a.includes(\"noop\") || a.includes(\"skip\")) {\n noopSkipped++;\n } else if (a.includes(\"update\")) {\n factsUpdated++;\n } else if (a.includes(\"delete\")) {\n // Delete operations should not be counted as stored facts\n } else if (a.includes(\"store\") || a.includes(\"capture\")) {\n factsStored++;\n }\n }\n\n const captureSummary: CaptureSummary = {\n factsStored,\n factsUpdated,\n duplicatesSuppressed,\n noopSkipped,\n errorsEncountered,\n entitiesExtracted: captureEntries.filter((e) => e.label.includes(\"entity\")).length,\n episodesRecorded: eventEntries.filter((e) => e.kind === \"episode_recorded\").length,\n proceduresLearned: captureEntries.filter((e) => e.label.includes(\"procedure\")).length,\n entries: captureEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 4. Recall explanation — build from audit + facts\n // ---------------------------------------------------------------------------\n const recallAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return (\n a.includes(\"recall\") ||\n a.includes(\"search\") ||\n a.includes(\"fts\") ||\n a.includes(\"vector\") ||\n a.includes(\"ambient\") ||\n a.includes(\"directive\")\n );\n });\n\n const recallEntries: SessionTimelineEntry[] = [...recallAuditActions];\n const strategies: string[] = [];\n const directiveMatches: string[] = [];\n const suppressionReasons: string[] = [];\n\n for (const e of recallEntries) {\n const detail = e.detail as Record<string, unknown> | undefined;\n if (detail?.strategies) {\n for (const s of Array.isArray(detail.strategies) ? detail.strategies : []) {\n if (!strategies.includes(String(s))) strategies.push(String(s));\n }\n }\n if (e.label.toLowerCase().includes(\"directive\")) {\n directiveMatches.push(e.label);\n }\n if (e.outcome === \"failed\" || e.outcome === \"skipped\") {\n suppressionReasons.push(e.description);\n }\n }\n\n const recallExplanation: RecallExplanation = {\n query: \"\",\n candidatesFound: recallEntries.filter((e) => e.label.includes(\"search\") || e.label.includes(\"recall\")).length,\n injectedCount: auditEntries.filter((e) => e.label.includes(\"inject\")).length,\n omittedCount: Math.max(0, recallEntries.length - auditEntries.filter((e) => e.label.includes(\"inject\")).length),\n strategies: strategies.slice(0, 5),\n directiveMatches: directiveMatches.slice(0, 10),\n suppressionReasons: suppressionReasons.slice(0, 10),\n entries: recallEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 5. Injection summary\n // ---------------------------------------------------------------------------\n const injectionAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return a.includes(\"inject\") || a.includes(\"prepend\") || a.includes(\"context\");\n });\n\n const injectionEntries: SessionTimelineEntry[] = [...injectionAuditActions];\n const injectionDetail = injectionAuditActions[0]?.detail as Record<string, unknown> | undefined;\n\n const budgetTokens = (injectionDetail?.budgetTokens as number) ?? 0;\n const injectionSummary: InjectionSummary = {\n totalChars: injectionEntries.reduce((s, e) => s + (e.description.length ?? 0), 0),\n totalTokensEstimate: Math.ceil(injectionEntries.reduce((s, e) => s + (e.description.length ?? 0), 0) / 4),\n blocksInjected: injectionEntries.length,\n budgetTokens,\n budgetUsedFraction: budgetTokens ? Math.min(1, (injectionEntries.length * 200) / (budgetTokens || 1)) : 0,\n prependContext: (injectionDetail?.prependContext as string) ?? undefined,\n entries: injectionEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 6. Merge timeline\n // ---------------------------------------------------------------------------\n const allEntries: SessionTimelineEntry[] = [\n ...eventEntries,\n ...captureEntries.map((e) => ({ ...e, kind: \"memory_captured\" as const })),\n ...recallEntries.map((e) => ({ ...e, kind: \"memory_recalled\" as const })),\n ...injectionEntries.map((e) => ({ ...e, kind: \"memory_injected\" as const })),\n ];\n\n // Sort by timestamp ascending\n allEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n // Deduplicate by timestamp+kind+label\n const seen = new Set<string>();\n const timeline: SessionTimelineEntry[] = [];\n for (const e of allEntries) {\n const key = `${e.timestamp}::${e.kind}::${e.label}`;\n if (!seen.has(key)) {\n seen.add(key);\n timeline.push(e);\n }\n }\n\n // ---------------------------------------------------------------------------\n // 7. Build human-readable summary\n // ---------------------------------------------------------------------------\n const parts: string[] = [];\n\n if (factsStored > 0 || factsUpdated > 0) {\n parts.push(\n `Captured ${factsStored} new fact${factsStored !== 1 ? \"s\" : \"\"}${factsUpdated > 0 ? ` and updated ${factsUpdated} existing` : \"\"}.`,\n );\n }\n if (duplicatesSuppressed > 0) {\n parts.push(`${duplicatesSuppressed} duplicate${duplicatesSuppressed !== 1 ? \"s were\" : \" was\"} suppressed.`);\n }\n if (noopSkipped > 0) {\n parts.push(`${noopSkipped} write${noopSkipped !== 1 ? \"s were\" : \" was\"} skipped (no-op).`);\n }\n if (recallEntries.length > 0) {\n parts.push(\n `${recallEntries.length} recall event${recallEntries.length !== 1 ? \"s\" : \"\"} logged, ${strategies.slice(0, 3).join(\", \") || \"unknown\"} strategy used.`,\n );\n }\n if (injectionEntries.length > 0) {\n parts.push(\n `${injectionEntries.length} injection block${injectionEntries.length !== 1 ? \"s\" : \"\"} added to prompt.`,\n );\n }\n if (suppressionEntries.length > 0) {\n parts.push(`${suppressionEntries.length} suppression${suppressionEntries.length !== 1 ? \"s\" : \"\"} recorded.`);\n }\n\n const summary = parts.length > 0 ? parts.join(\" \") : \"No significant memory activity was recorded for this session.\";\n\n // ---------------------------------------------------------------------------\n // 8. Window start/end from audit / event log bounds\n // ---------------------------------------------------------------------------\n let windowStart: string | null = null;\n let windowEnd: string | null = null;\n\n if (timeline.length > 0) {\n windowStart = timeline[0].timestamp;\n windowEnd = timeline[timeline.length - 1].timestamp;\n }\n\n return {\n sessionId: sessionId ?? null,\n agentId: agentId ?? null,\n windowStart,\n windowEnd,\n timeline: timeline.slice(0, limit * 3),\n capture: captureSummary,\n recall: recallExplanation,\n injection: injectionSummary,\n suppressions: suppressionEntries.slice(0, limit),\n summary,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-observability.js","names":[],"sources":["../../services/session-observability.ts"],"sourcesContent":["/**\n * Session Observability Service — Issue #1025\n *\n * Builds a unified session observability report from existing stores:\n * - Event log (episodes, user turns, agent actions)\n * - Audit store (recall, injection, capture, skipped writes)\n * - Facts DB (captured facts/entities for this session)\n * - Narratives DB (session narrative summaries)\n * - Context audit (injected prompt content)\n *\n * Output structure:\n * - timeline — merged chronological view\n * - capture — what was stored / suppressed during capture\n * - recall — why memories were recalled / not recalled\n * - injection — what entered prompt context and why\n * - suppressions — writes suppressed by policy/guards/errors\n * - summary — human-readable single-paragraph summary\n */\n\nimport type { AuditStore } from \"../backends/audit-store.js\";\nimport type { EventLog } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SessionTimelineEntry {\n /** ISO timestamp */\n timestamp: string;\n /** Event kind for display / filtering */\n kind:\n | \"user_turn\"\n | \"agent_action\"\n | \"memory_captured\"\n | \"memory_recalled\"\n | \"memory_injected\"\n | \"memory_suppressed\"\n | \"episode_recorded\"\n | \"narrative_generated\"\n | \"audit_event\"\n | \"recall_explanation\"\n | \"injection_summary\"\n | \"capture_summary\";\n /** Short label */\n label: string;\n /** What happened */\n description: string;\n /** Optional detail payload (structured) */\n detail?: unknown;\n /** Score or rank when applicable */\n score?: number;\n /** Outcome for audit/episode events */\n outcome?: \"success\" | \"partial\" | \"failed\" | \"skipped\";\n}\n\nexport interface CaptureSummary {\n factsStored: number;\n factsUpdated: number;\n duplicatesSuppressed: number;\n noopSkipped: number;\n errorsEncountered: number;\n entitiesExtracted: number;\n episodesRecorded: number;\n proceduresLearned: number;\n entries: SessionTimelineEntry[];\n}\n\nexport interface RecallExplanation {\n query: string;\n candidatesFound: number;\n injectedCount: number;\n omittedCount: number;\n strategies: string[];\n directiveMatches: string[];\n suppressionReasons: string[];\n entries: SessionTimelineEntry[];\n}\n\nexport interface InjectionSummary {\n totalChars: number;\n totalTokensEstimate: number;\n blocksInjected: number;\n budgetTokens: number;\n budgetUsedFraction: number;\n prependContext?: string;\n entries: SessionTimelineEntry[];\n}\n\nexport interface SuppressionEntry {\n timestamp: string;\n reason: string;\n detail?: string;\n outcome: \"skipped\" | \"no_op\" | \"error\";\n}\n\nexport interface SessionObservabilityReport {\n sessionId: string | null;\n agentId: string | null;\n /** Session window */\n windowStart: string | null;\n windowEnd: string | null;\n /** Merged chronological timeline */\n timeline: SessionTimelineEntry[];\n capture: CaptureSummary;\n recall: RecallExplanation;\n injection: InjectionSummary;\n suppressions: SuppressionEntry[];\n /** Human-readable summary paragraph */\n summary: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatTs(ms: number): string {\n return new Date(ms).toISOString();\n}\n\nfunction makeEntry(\n ts: number,\n kind: SessionTimelineEntry[\"kind\"],\n label: string,\n description: string,\n detail?: unknown,\n opts?: { score?: number; outcome?: SessionTimelineEntry[\"outcome\"] },\n): SessionTimelineEntry {\n return {\n timestamp: formatTs(ts),\n kind,\n label,\n description,\n detail,\n ...(opts?.score !== undefined ? { score: opts.score } : {}),\n ...(opts?.outcome ? { outcome: opts.outcome } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core report builder\n// ---------------------------------------------------------------------------\n\nexport interface SessionObservabilityDeps {\n factsDb: FactsDB;\n eventLog: EventLog | null;\n narrativesDb: NarrativesDB | null;\n auditStore: AuditStore | null;\n /** SQLite path — used to scope facts to session via source_sessions join */\n sqlitePath?: string;\n sessionId?: string | null;\n agentId?: string | null;\n /** Max timeline entries per section (default 50) */\n limit?: number;\n}\n\nexport async function buildSessionObservabilityReport(\n deps: SessionObservabilityDeps,\n): Promise<SessionObservabilityReport> {\n const { factsDb, eventLog, narrativesDb, auditStore, sessionId, agentId, limit = 50 } = deps;\n const now = Date.now();\n\n // ---------------------------------------------------------------------------\n // 1. Collect audit events for this session\n // ---------------------------------------------------------------------------\n const auditEntries: SessionTimelineEntry[] = [];\n const suppressionEntries: SuppressionEntry[] = [];\n\n if (auditStore) {\n const auditRows = auditStore.query({\n sessionId: sessionId ?? undefined,\n agentId: agentId ?? undefined,\n limit,\n });\n\n for (const row of auditRows) {\n const label = row.action;\n const desc = row.target ? `${row.action} → ${row.target}` : row.action;\n\n if (\n row.outcome === \"failed\" ||\n row.action.includes(\"suppressed\") ||\n row.action.includes(\"skip\") ||\n row.action.includes(\"noop\") ||\n row.action.includes(\"guard\")\n ) {\n suppressionEntries.push({\n timestamp: formatTs(row.timestamp),\n reason: row.action,\n detail: row.error ?? row.target ?? undefined,\n outcome: row.outcome === \"failed\" ? \"error\" : \"skipped\",\n });\n }\n\n auditEntries.push(\n makeEntry(\n row.timestamp,\n \"audit_event\",\n label,\n desc,\n {\n outcome: row.outcome,\n durationMs: row.durationMs,\n error: row.error,\n contextKeys: row.context ? Object.keys(row.context) : [],\n tokens: row.tokens,\n },\n { outcome: row.outcome as SessionTimelineEntry[\"outcome\"] },\n ),\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // 2. Collect events from the event log for this session\n // ---------------------------------------------------------------------------\n const eventEntries: SessionTimelineEntry[] = [];\n if (eventLog) {\n // NOTE: eventLog.getSessionEvents or equivalent — check available methods\n // We query via factsDb since episodes table is accessible there\n // Only include events in the session window (24h lookback by default)\n const _sinceMs = now - 24 * 3600 * 1000;\n try {\n // episodes are stored in facts DB; we use factsDb to retrieve them\n const episodes =\n (factsDb as { getEpisodesBySession?(sid: string, lim: number): unknown[] })?.getEpisodesBySession?.(\n sessionId ?? \"recent\",\n limit,\n ) ?? [];\n\n for (const ep of episodes as Array<{\n id?: string;\n event?: string;\n outcome?: string;\n timestamp?: number | string;\n context?: string;\n }>) {\n const ts = typeof ep.timestamp === \"number\" ? ep.timestamp : Date.parse(String(ep.timestamp ?? \"0\"));\n eventEntries.push(\n makeEntry(\n ts,\n \"episode_recorded\",\n ep.event ?? \"episode\",\n ep.context ?? ep.event ?? \"\",\n { outcome: ep.outcome },\n { outcome: ep.outcome as SessionTimelineEntry[\"outcome\"] },\n ),\n );\n }\n } catch {\n // eventLog may not expose session-scoped episode query; fall back silently\n }\n }\n\n // ---------------------------------------------------------------------------\n // 3. Capture summary — what was stored this session\n // ---------------------------------------------------------------------------\n // Use audit rows with action patterns that indicate store outcomes\n const storeAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return (\n a.includes(\"store\") ||\n a.includes(\"capture\") ||\n a.includes(\"write\") ||\n a.includes(\"classify\") ||\n a.includes(\"duplicate\") ||\n a.includes(\"noop\")\n );\n });\n\n const captureEntries: SessionTimelineEntry[] = [];\n let factsStored = 0;\n let factsUpdated = 0;\n let duplicatesSuppressed = 0;\n let noopSkipped = 0;\n let errorsEncountered = 0;\n\n for (const entry of storeAuditActions) {\n captureEntries.push(entry);\n const a = entry.label.toLowerCase();\n if (entry.outcome === \"failed\" || a.includes(\"error\")) {\n errorsEncountered++;\n } else if (a.includes(\"duplicate\")) {\n duplicatesSuppressed++;\n } else if (a.includes(\"noop\") || a.includes(\"skip\")) {\n noopSkipped++;\n } else if (a.includes(\"update\")) {\n factsUpdated++;\n } else if (a.includes(\"delete\")) {\n // Delete operations should not be counted as stored facts\n } else if (a.includes(\"store\") || a.includes(\"capture\")) {\n factsStored++;\n }\n }\n\n const captureSummary: CaptureSummary = {\n factsStored,\n factsUpdated,\n duplicatesSuppressed,\n noopSkipped,\n errorsEncountered,\n entitiesExtracted: captureEntries.filter((e) => e.label.includes(\"entity\")).length,\n episodesRecorded: eventEntries.filter((e) => e.kind === \"episode_recorded\").length,\n proceduresLearned: captureEntries.filter((e) => e.label.includes(\"procedure\")).length,\n entries: captureEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 4. Recall explanation — build from audit + facts\n // ---------------------------------------------------------------------------\n const recallAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return (\n a.includes(\"recall\") ||\n a.includes(\"search\") ||\n a.includes(\"fts\") ||\n a.includes(\"vector\") ||\n a.includes(\"ambient\") ||\n a.includes(\"directive\")\n );\n });\n\n const recallEntries: SessionTimelineEntry[] = [...recallAuditActions];\n const strategies: string[] = [];\n const directiveMatches: string[] = [];\n const suppressionReasons: string[] = [];\n\n for (const e of recallEntries) {\n const detail = e.detail as Record<string, unknown> | undefined;\n if (detail?.strategies) {\n for (const s of Array.isArray(detail.strategies) ? detail.strategies : []) {\n if (!strategies.includes(String(s))) strategies.push(String(s));\n }\n }\n if (e.label.toLowerCase().includes(\"directive\")) {\n directiveMatches.push(e.label);\n }\n if (e.outcome === \"failed\" || e.outcome === \"skipped\") {\n suppressionReasons.push(e.description);\n }\n }\n\n const recallExplanation: RecallExplanation = {\n query: \"\",\n candidatesFound: recallEntries.filter((e) => e.label.includes(\"search\") || e.label.includes(\"recall\")).length,\n injectedCount: auditEntries.filter((e) => e.label.includes(\"inject\")).length,\n omittedCount: Math.max(0, recallEntries.length - auditEntries.filter((e) => e.label.includes(\"inject\")).length),\n strategies: strategies.slice(0, 5),\n directiveMatches: directiveMatches.slice(0, 10),\n suppressionReasons: suppressionReasons.slice(0, 10),\n entries: recallEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 5. Injection summary\n // ---------------------------------------------------------------------------\n const injectionAuditActions = auditEntries.filter((e) => {\n const a = e.label.toLowerCase();\n return a.includes(\"inject\") || a.includes(\"prepend\") || a.includes(\"context\");\n });\n\n const injectionEntries: SessionTimelineEntry[] = [...injectionAuditActions];\n const injectionDetail = injectionAuditActions[0]?.detail as Record<string, unknown> | undefined;\n\n const budgetTokens = (injectionDetail?.budgetTokens as number) ?? 0;\n const injectionSummary: InjectionSummary = {\n totalChars: injectionEntries.reduce((s, e) => s + (e.description.length ?? 0), 0),\n totalTokensEstimate: Math.ceil(injectionEntries.reduce((s, e) => s + (e.description.length ?? 0), 0) / 4),\n blocksInjected: injectionEntries.length,\n budgetTokens,\n budgetUsedFraction: budgetTokens ? Math.min(1, (injectionEntries.length * 200) / (budgetTokens || 1)) : 0,\n prependContext: (injectionDetail?.prependContext as string) ?? undefined,\n entries: injectionEntries.slice(0, limit),\n };\n\n // ---------------------------------------------------------------------------\n // 6. Merge timeline\n // ---------------------------------------------------------------------------\n const allEntries: SessionTimelineEntry[] = [\n ...eventEntries,\n ...captureEntries.map((e) => ({ ...e, kind: \"memory_captured\" as const })),\n ...recallEntries.map((e) => ({ ...e, kind: \"memory_recalled\" as const })),\n ...injectionEntries.map((e) => ({ ...e, kind: \"memory_injected\" as const })),\n ];\n\n // Sort by timestamp ascending\n allEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());\n\n // Deduplicate by timestamp+kind+label\n const seen = new Set<string>();\n const timeline: SessionTimelineEntry[] = [];\n for (const e of allEntries) {\n const key = `${e.timestamp}::${e.kind}::${e.label}`;\n if (!seen.has(key)) {\n seen.add(key);\n timeline.push(e);\n }\n }\n\n // ---------------------------------------------------------------------------\n // 7. Build human-readable summary\n // ---------------------------------------------------------------------------\n const parts: string[] = [];\n\n if (factsStored > 0 || factsUpdated > 0) {\n parts.push(\n `Captured ${factsStored} new fact${factsStored !== 1 ? \"s\" : \"\"}${factsUpdated > 0 ? ` and updated ${factsUpdated} existing` : \"\"}.`,\n );\n }\n if (duplicatesSuppressed > 0) {\n parts.push(`${duplicatesSuppressed} duplicate${duplicatesSuppressed !== 1 ? \"s were\" : \" was\"} suppressed.`);\n }\n if (noopSkipped > 0) {\n parts.push(`${noopSkipped} write${noopSkipped !== 1 ? \"s were\" : \" was\"} skipped (no-op).`);\n }\n if (recallEntries.length > 0) {\n parts.push(\n `${recallEntries.length} recall event${recallEntries.length !== 1 ? \"s\" : \"\"} logged, ${strategies.slice(0, 3).join(\", \") || \"unknown\"} strategy used.`,\n );\n }\n if (injectionEntries.length > 0) {\n parts.push(\n `${injectionEntries.length} injection block${injectionEntries.length !== 1 ? \"s\" : \"\"} added to prompt.`,\n );\n }\n if (suppressionEntries.length > 0) {\n parts.push(`${suppressionEntries.length} suppression${suppressionEntries.length !== 1 ? \"s\" : \"\"} recorded.`);\n }\n\n const summary = parts.length > 0 ? parts.join(\" \") : \"No significant memory activity was recorded for this session.\";\n\n // ---------------------------------------------------------------------------\n // 8. Window start/end from audit / event log bounds\n // ---------------------------------------------------------------------------\n let windowStart: string | null = null;\n let windowEnd: string | null = null;\n\n if (timeline.length > 0) {\n windowStart = timeline[0].timestamp;\n windowEnd = timeline[timeline.length - 1].timestamp;\n }\n\n return {\n sessionId: sessionId ?? null,\n agentId: agentId ?? null,\n windowStart,\n windowEnd,\n timeline: timeline.slice(0, limit * 3),\n capture: captureSummary,\n recall: recallExplanation,\n injection: injectionSummary,\n suppressions: suppressionEntries.slice(0, limit),\n summary,\n };\n}\n"],"mappings":"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"}