@graphorin/memory 0.5.0

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 (287) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +401 -0
  4. package/dist/conflict/index.d.ts +3 -0
  5. package/dist/conflict/index.js +7 -0
  6. package/dist/conflict/locale-packs/en.js +262 -0
  7. package/dist/conflict/locale-packs/en.js.map +1 -0
  8. package/dist/conflict/locale-packs/index.js +4 -0
  9. package/dist/conflict/locale-packs/types.d.ts +103 -0
  10. package/dist/conflict/locale-packs/types.d.ts.map +1 -0
  11. package/dist/conflict/locale-packs/types.js +55 -0
  12. package/dist/conflict/locale-packs/types.js.map +1 -0
  13. package/dist/conflict/pipeline.d.ts +35 -0
  14. package/dist/conflict/pipeline.d.ts.map +1 -0
  15. package/dist/conflict/pipeline.js +257 -0
  16. package/dist/conflict/pipeline.js.map +1 -0
  17. package/dist/conflict/stages/helpers.js +66 -0
  18. package/dist/conflict/stages/helpers.js.map +1 -0
  19. package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
  20. package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
  21. package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
  22. package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
  23. package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
  24. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
  25. package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
  26. package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
  27. package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
  28. package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
  29. package/dist/conflict/types.d.ts +222 -0
  30. package/dist/conflict/types.d.ts.map +1 -0
  31. package/dist/conflict/types.js +50 -0
  32. package/dist/conflict/types.js.map +1 -0
  33. package/dist/consolidator/budget.d.ts +1 -0
  34. package/dist/consolidator/budget.js +214 -0
  35. package/dist/consolidator/budget.js.map +1 -0
  36. package/dist/consolidator/decay.d.ts +35 -0
  37. package/dist/consolidator/decay.d.ts.map +1 -0
  38. package/dist/consolidator/decay.js +91 -0
  39. package/dist/consolidator/decay.js.map +1 -0
  40. package/dist/consolidator/dlq.js +58 -0
  41. package/dist/consolidator/dlq.js.map +1 -0
  42. package/dist/consolidator/errors.d.ts +54 -0
  43. package/dist/consolidator/errors.d.ts.map +1 -0
  44. package/dist/consolidator/errors.js +67 -0
  45. package/dist/consolidator/errors.js.map +1 -0
  46. package/dist/consolidator/idempotency.d.ts +1 -0
  47. package/dist/consolidator/idempotency.js +22 -0
  48. package/dist/consolidator/idempotency.js.map +1 -0
  49. package/dist/consolidator/index.d.ts +34 -0
  50. package/dist/consolidator/index.d.ts.map +1 -0
  51. package/dist/consolidator/index.js +163 -0
  52. package/dist/consolidator/index.js.map +1 -0
  53. package/dist/consolidator/lock.d.ts +2 -0
  54. package/dist/consolidator/lock.js +89 -0
  55. package/dist/consolidator/lock.js.map +1 -0
  56. package/dist/consolidator/noise-filter.d.ts +2 -0
  57. package/dist/consolidator/noise-filter.js +173 -0
  58. package/dist/consolidator/noise-filter.js.map +1 -0
  59. package/dist/consolidator/phases/deep.d.ts +4 -0
  60. package/dist/consolidator/phases/deep.js +198 -0
  61. package/dist/consolidator/phases/deep.js.map +1 -0
  62. package/dist/consolidator/phases/induce.d.ts +154 -0
  63. package/dist/consolidator/phases/induce.d.ts.map +1 -0
  64. package/dist/consolidator/phases/induce.js +253 -0
  65. package/dist/consolidator/phases/induce.js.map +1 -0
  66. package/dist/consolidator/phases/light.d.ts +4 -0
  67. package/dist/consolidator/phases/light.js +99 -0
  68. package/dist/consolidator/phases/light.js.map +1 -0
  69. package/dist/consolidator/phases/reflect.js +287 -0
  70. package/dist/consolidator/phases/reflect.js.map +1 -0
  71. package/dist/consolidator/phases/standard.d.ts +8 -0
  72. package/dist/consolidator/phases/standard.js +499 -0
  73. package/dist/consolidator/phases/standard.js.map +1 -0
  74. package/dist/consolidator/reconcile.js +192 -0
  75. package/dist/consolidator/reconcile.js.map +1 -0
  76. package/dist/consolidator/runtime.d.ts +62 -0
  77. package/dist/consolidator/runtime.d.ts.map +1 -0
  78. package/dist/consolidator/runtime.js +580 -0
  79. package/dist/consolidator/runtime.js.map +1 -0
  80. package/dist/consolidator/scheduler.d.ts +111 -0
  81. package/dist/consolidator/scheduler.d.ts.map +1 -0
  82. package/dist/consolidator/scheduler.js +71 -0
  83. package/dist/consolidator/scheduler.js.map +1 -0
  84. package/dist/consolidator/triggers.d.ts +40 -0
  85. package/dist/consolidator/triggers.d.ts.map +1 -0
  86. package/dist/consolidator/triggers.js +94 -0
  87. package/dist/consolidator/triggers.js.map +1 -0
  88. package/dist/consolidator/types.d.ts +397 -0
  89. package/dist/consolidator/types.d.ts.map +1 -0
  90. package/dist/consolidator/types.js +117 -0
  91. package/dist/consolidator/types.js.map +1 -0
  92. package/dist/context-engine/annotations.d.ts +118 -0
  93. package/dist/context-engine/annotations.d.ts.map +1 -0
  94. package/dist/context-engine/annotations.js +102 -0
  95. package/dist/context-engine/annotations.js.map +1 -0
  96. package/dist/context-engine/auto-recall.d.ts +52 -0
  97. package/dist/context-engine/auto-recall.d.ts.map +1 -0
  98. package/dist/context-engine/auto-recall.js +41 -0
  99. package/dist/context-engine/auto-recall.js.map +1 -0
  100. package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
  101. package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
  102. package/dist/context-engine/compaction/clear-tool-results.js +97 -0
  103. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
  104. package/dist/context-engine/compaction/compactor.d.ts +52 -0
  105. package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
  106. package/dist/context-engine/compaction/compactor.js +196 -0
  107. package/dist/context-engine/compaction/compactor.js.map +1 -0
  108. package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
  109. package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
  110. package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
  111. package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
  112. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
  113. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
  114. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
  115. package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
  116. package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
  117. package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
  118. package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
  119. package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
  120. package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
  121. package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
  122. package/dist/context-engine/compaction/index.d.ts +9 -0
  123. package/dist/context-engine/compaction/index.js +9 -0
  124. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
  125. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
  126. package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
  127. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
  128. package/dist/context-engine/compaction/thresholds.d.ts +68 -0
  129. package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
  130. package/dist/context-engine/compaction/thresholds.js +63 -0
  131. package/dist/context-engine/compaction/thresholds.js.map +1 -0
  132. package/dist/context-engine/compaction/types.d.ts +202 -0
  133. package/dist/context-engine/compaction/types.d.ts.map +1 -0
  134. package/dist/context-engine/engine.d.ts +258 -0
  135. package/dist/context-engine/engine.d.ts.map +1 -0
  136. package/dist/context-engine/engine.js +424 -0
  137. package/dist/context-engine/engine.js.map +1 -0
  138. package/dist/context-engine/index.d.ts +68 -0
  139. package/dist/context-engine/index.d.ts.map +1 -0
  140. package/dist/context-engine/index.js +24 -0
  141. package/dist/context-engine/locale-packs/en.d.ts +14 -0
  142. package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
  143. package/dist/context-engine/locale-packs/en.js +87 -0
  144. package/dist/context-engine/locale-packs/en.js.map +1 -0
  145. package/dist/context-engine/locale-packs/index.js +4 -0
  146. package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
  147. package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
  148. package/dist/context-engine/locale-packs/resolver.js +119 -0
  149. package/dist/context-engine/locale-packs/resolver.js.map +1 -0
  150. package/dist/context-engine/locale-packs/types.d.ts +119 -0
  151. package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
  152. package/dist/context-engine/locale-packs/types.js +24 -0
  153. package/dist/context-engine/locale-packs/types.js.map +1 -0
  154. package/dist/context-engine/metadata.d.ts +40 -0
  155. package/dist/context-engine/metadata.d.ts.map +1 -0
  156. package/dist/context-engine/metadata.js +66 -0
  157. package/dist/context-engine/metadata.js.map +1 -0
  158. package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
  159. package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
  160. package/dist/context-engine/preambles/inbound-en.js +23 -0
  161. package/dist/context-engine/preambles/inbound-en.js.map +1 -0
  162. package/dist/context-engine/privacy-filter.d.ts +108 -0
  163. package/dist/context-engine/privacy-filter.d.ts.map +1 -0
  164. package/dist/context-engine/privacy-filter.js +113 -0
  165. package/dist/context-engine/privacy-filter.js.map +1 -0
  166. package/dist/context-engine/templates/base-en.d.ts +19 -0
  167. package/dist/context-engine/templates/base-en.d.ts.map +1 -0
  168. package/dist/context-engine/templates/base-en.js +22 -0
  169. package/dist/context-engine/templates/base-en.js.map +1 -0
  170. package/dist/context-engine/templates/composer.d.ts +55 -0
  171. package/dist/context-engine/templates/composer.d.ts.map +1 -0
  172. package/dist/context-engine/templates/composer.js +51 -0
  173. package/dist/context-engine/templates/composer.js.map +1 -0
  174. package/dist/context-engine/token-budget.d.ts +99 -0
  175. package/dist/context-engine/token-budget.d.ts.map +1 -0
  176. package/dist/context-engine/token-budget.js +211 -0
  177. package/dist/context-engine/token-budget.js.map +1 -0
  178. package/dist/context-engine/token-counter.d.ts +54 -0
  179. package/dist/context-engine/token-counter.d.ts.map +1 -0
  180. package/dist/context-engine/token-counter.js +81 -0
  181. package/dist/context-engine/token-counter.js.map +1 -0
  182. package/dist/errors/index.d.ts +171 -0
  183. package/dist/errors/index.d.ts.map +1 -0
  184. package/dist/errors/index.js +206 -0
  185. package/dist/errors/index.js.map +1 -0
  186. package/dist/facade.d.ts +278 -0
  187. package/dist/facade.d.ts.map +1 -0
  188. package/dist/facade.js +265 -0
  189. package/dist/facade.js.map +1 -0
  190. package/dist/graph/entity-resolver.d.ts +135 -0
  191. package/dist/graph/entity-resolver.d.ts.map +1 -0
  192. package/dist/graph/entity-resolver.js +202 -0
  193. package/dist/graph/entity-resolver.js.map +1 -0
  194. package/dist/graph/index.d.ts +1 -0
  195. package/dist/index.d.ts +103 -0
  196. package/dist/index.d.ts.map +1 -0
  197. package/dist/index.js +95 -0
  198. package/dist/index.js.map +1 -0
  199. package/dist/internal/contextualize.d.ts +14 -0
  200. package/dist/internal/contextualize.d.ts.map +1 -0
  201. package/dist/internal/contextualize.js +110 -0
  202. package/dist/internal/contextualize.js.map +1 -0
  203. package/dist/internal/embedder-binding.js +50 -0
  204. package/dist/internal/embedder-binding.js.map +1 -0
  205. package/dist/internal/id.js +24 -0
  206. package/dist/internal/id.js.map +1 -0
  207. package/dist/internal/injection-heuristics.js +64 -0
  208. package/dist/internal/injection-heuristics.js.map +1 -0
  209. package/dist/internal/spans.js +35 -0
  210. package/dist/internal/spans.js.map +1 -0
  211. package/dist/internal/storage-adapter.d.ts +742 -0
  212. package/dist/internal/storage-adapter.d.ts.map +1 -0
  213. package/dist/migration/embedder-migration.d.ts +124 -0
  214. package/dist/migration/embedder-migration.d.ts.map +1 -0
  215. package/dist/migration/embedder-migration.js +109 -0
  216. package/dist/migration/embedder-migration.js.map +1 -0
  217. package/dist/migration/index.d.ts +2 -0
  218. package/dist/migration/index.js +3 -0
  219. package/dist/search/explain.d.ts +74 -0
  220. package/dist/search/explain.d.ts.map +1 -0
  221. package/dist/search/explain.js +50 -0
  222. package/dist/search/explain.js.map +1 -0
  223. package/dist/search/index.d.ts +6 -0
  224. package/dist/search/index.js +6 -0
  225. package/dist/search/iterative.d.ts +223 -0
  226. package/dist/search/iterative.d.ts.map +1 -0
  227. package/dist/search/iterative.js +330 -0
  228. package/dist/search/iterative.js.map +1 -0
  229. package/dist/search/query-transform.d.ts +112 -0
  230. package/dist/search/query-transform.d.ts.map +1 -0
  231. package/dist/search/query-transform.js +184 -0
  232. package/dist/search/query-transform.js.map +1 -0
  233. package/dist/search/rrf.d.ts +95 -0
  234. package/dist/search/rrf.d.ts.map +1 -0
  235. package/dist/search/rrf.js +166 -0
  236. package/dist/search/rrf.js.map +1 -0
  237. package/dist/search/types.d.ts +50 -0
  238. package/dist/search/types.d.ts.map +1 -0
  239. package/dist/tiers/episodic-memory.d.ts +142 -0
  240. package/dist/tiers/episodic-memory.d.ts.map +1 -0
  241. package/dist/tiers/episodic-memory.js +254 -0
  242. package/dist/tiers/episodic-memory.js.map +1 -0
  243. package/dist/tiers/index.d.ts +8 -0
  244. package/dist/tiers/index.js +9 -0
  245. package/dist/tiers/insight-memory.d.ts +71 -0
  246. package/dist/tiers/insight-memory.d.ts.map +1 -0
  247. package/dist/tiers/insight-memory.js +133 -0
  248. package/dist/tiers/insight-memory.js.map +1 -0
  249. package/dist/tiers/procedural-memory.d.ts +147 -0
  250. package/dist/tiers/procedural-memory.d.ts.map +1 -0
  251. package/dist/tiers/procedural-memory.js +267 -0
  252. package/dist/tiers/procedural-memory.js.map +1 -0
  253. package/dist/tiers/semantic-memory.d.ts +461 -0
  254. package/dist/tiers/semantic-memory.d.ts.map +1 -0
  255. package/dist/tiers/semantic-memory.js +631 -0
  256. package/dist/tiers/semantic-memory.js.map +1 -0
  257. package/dist/tiers/session-memory.d.ts +117 -0
  258. package/dist/tiers/session-memory.d.ts.map +1 -0
  259. package/dist/tiers/session-memory.js +163 -0
  260. package/dist/tiers/session-memory.js.map +1 -0
  261. package/dist/tiers/shared-memory.d.ts +29 -0
  262. package/dist/tiers/shared-memory.d.ts.map +1 -0
  263. package/dist/tiers/shared-memory.js +58 -0
  264. package/dist/tiers/shared-memory.js.map +1 -0
  265. package/dist/tiers/working-memory.d.ts +129 -0
  266. package/dist/tiers/working-memory.d.ts.map +1 -0
  267. package/dist/tiers/working-memory.js +229 -0
  268. package/dist/tiers/working-memory.js.map +1 -0
  269. package/dist/tools/block-tools.d.ts +98 -0
  270. package/dist/tools/block-tools.d.ts.map +1 -0
  271. package/dist/tools/block-tools.js +114 -0
  272. package/dist/tools/block-tools.js.map +1 -0
  273. package/dist/tools/fact-tools.d.ts +294 -0
  274. package/dist/tools/fact-tools.d.ts.map +1 -0
  275. package/dist/tools/fact-tools.js +293 -0
  276. package/dist/tools/fact-tools.js.map +1 -0
  277. package/dist/tools/index.d.ts +35 -0
  278. package/dist/tools/index.d.ts.map +1 -0
  279. package/dist/tools/index.js +36 -0
  280. package/dist/tools/index.js.map +1 -0
  281. package/dist/tools/recall-tools.d.ts +209 -0
  282. package/dist/tools/recall-tools.d.ts.map +1 -0
  283. package/dist/tools/recall-tools.js +170 -0
  284. package/dist/tools/recall-tools.js.map +1 -0
  285. package/dist/tools/types.d.ts +40 -0
  286. package/dist/tools/types.d.ts.map +1 -0
  287. package/package.json +119 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decay.js","names":["DEFAULT_SALIENCE_WEIGHTS: SalienceWeights"],"sources":["../../src/consolidator/decay.ts"],"sourcesContent":["/**\n * Ebbinghaus retention curve for the light phase. Pure functions so\n * the formula is testable without touching storage.\n *\n * @packageDocumentation\n */\n\n/**\n * Default time constant for the retention curve. The plan calls for\n * `tau = 7` days; consumers may override per-consolidator via\n * `decayTauDays`.\n *\n * @stable\n */\nexport const DEFAULT_DECAY_TAU_DAYS = 7;\n\n/**\n * Default archive threshold — facts whose retention falls below this\n * value are soft-archived in the light phase. `0.05` matches the\n * documented forgetting policy for the memory system.\n *\n * @stable\n */\nexport const DEFAULT_DECAY_ARCHIVE_THRESHOLD = 0.05;\n\n/**\n * Compute the retention score for a fact given its age, last-access\n * recency, and accumulated `strength`. Larger `strength` means the\n * fact has been accessed more often, so the retention curve flattens\n * — `score = base * exp(-elapsedDays / (tau * strength))`.\n *\n * @stable\n */\nexport function retention(args: {\n /** Reference time (ms epoch). */\n readonly now: number;\n /** Last access timestamp (ms epoch); falls back to `createdAt` when null. */\n readonly lastAccessedAt: number | null;\n /** Creation timestamp (ms epoch). */\n readonly createdAt: number;\n /** Strength multiplier (default 1.0). */\n readonly strength: number;\n /** Time constant in days. */\n readonly tauDays: number;\n}): number {\n const reference = args.lastAccessedAt ?? args.createdAt;\n const elapsedMs = Math.max(0, args.now - reference);\n const elapsedDays = elapsedMs / (1000 * 60 * 60 * 24);\n const tau = Math.max(0.1, args.tauDays * Math.max(0.5, args.strength));\n return Math.exp(-elapsedDays / tau);\n}\n\n/**\n * `true` when the fact's retention curve has fallen below the\n * archive threshold.\n *\n * @stable\n */\nexport function shouldArchive(args: {\n readonly now: number;\n readonly lastAccessedAt: number | null;\n readonly createdAt: number;\n readonly strength: number;\n readonly tauDays: number;\n readonly archiveThreshold: number;\n}): boolean {\n const score = retention({\n now: args.now,\n lastAccessedAt: args.lastAccessedAt,\n createdAt: args.createdAt,\n strength: args.strength,\n tauDays: args.tauDays,\n });\n return score < args.archiveThreshold;\n}\n\n/**\n * Neutral importance used when a fact carries no importance hint —\n * the midpoint of the `[0, 1]` range, so an unscored fact contributes\n * an importance factor of exactly `1.0` (`salience === retention`).\n *\n * @stable\n */\nexport const NEUTRAL_IMPORTANCE = 0.5;\n\n/**\n * Tunable weights for the multi-signal {@link salience} score (X-1).\n * Each weight is the *magnitude* of the corresponding signal's pull on\n * the retention curve; all default to values chosen so the ordering is\n * sensible without ever inverting it.\n *\n * @stable\n */\nexport interface SalienceWeights {\n /**\n * How strongly importance (P1-2) stretches retention. At the default\n * `0.6`, importance `1.0` multiplies retention by `1.3` and importance\n * `0.0` by `0.7`; neutral importance leaves it unchanged.\n */\n readonly importance: number;\n /**\n * Penalty applied to a **quarantined** fact (P1-4) — the explicit\n * security-risk negative term. At the default `0.7`, a quarantined\n * fact keeps only `0.3` of its retention, so it is evicted first under\n * capacity pressure. Never a hard delete: the fact is archived,\n * recoverable, and quarantine still gates it out of recall meanwhile.\n */\n readonly quarantine: number;\n /**\n * Mild penalty for a fact with non-first-party provenance (P1-4) —\n * e.g. `'tool'` / `'imported'` content that did not originate with the\n * user. At the default `0.2` such a fact keeps `0.8` of its retention.\n */\n readonly foreignProvenance: number;\n}\n\n/**\n * Default {@link SalienceWeights}. Chosen so that an active,\n * first-party, unscored fact has `salience === retention` (the X-1\n * change is invisible until a fact carries an importance hint, is\n * quarantined, or has foreign provenance).\n *\n * @stable\n */\nexport const DEFAULT_SALIENCE_WEIGHTS: SalienceWeights = Object.freeze({\n importance: 0.6,\n quarantine: 0.7,\n foreignProvenance: 0.2,\n});\n\n/**\n * Multi-signal salience for capacity-bounded forgetting (X-1). Combines\n * the Ebbinghaus {@link retention} curve (temporal relevance + access\n * frequency via `strength`) with the P1-2 importance hint and a P1-4\n * security-risk negative term:\n *\n * ```\n * salience = retention × importanceFactor × securityFactor\n * ```\n *\n * Sold as **cost / staleness control, not accuracy**: it only orders\n * what gets archived first when storage is bounded — it never gates\n * recall. With neutral importance, an active fact, and first-party\n * provenance the factors are all `1`, so `salience === retention`.\n *\n * @stable\n */\nexport function salience(args: {\n readonly now: number;\n readonly lastAccessedAt: number | null;\n readonly createdAt: number;\n readonly strength: number;\n readonly tauDays: number;\n /** Importance hint in `[0, 1]`; `null` → {@link NEUTRAL_IMPORTANCE}. */\n readonly importance: number | null;\n /** P1-4: a quarantined fact gets the {@link SalienceWeights.quarantine} penalty. */\n readonly quarantined: boolean;\n /** P1-4: a non-first-party fact gets the {@link SalienceWeights.foreignProvenance} penalty. */\n readonly foreignProvenance: boolean;\n /** Defaults to {@link DEFAULT_SALIENCE_WEIGHTS}. */\n readonly weights?: SalienceWeights;\n}): number {\n const weights = args.weights ?? DEFAULT_SALIENCE_WEIGHTS;\n const base = retention({\n now: args.now,\n lastAccessedAt: args.lastAccessedAt,\n createdAt: args.createdAt,\n strength: args.strength,\n tauDays: args.tauDays,\n });\n const importance = clamp01(args.importance ?? NEUTRAL_IMPORTANCE);\n const importanceFactor = Math.max(0, 1 + weights.importance * (importance - NEUTRAL_IMPORTANCE));\n const securityFactor = args.quarantined\n ? Math.max(0, 1 - weights.quarantine)\n : args.foreignProvenance\n ? Math.max(0, 1 - weights.foreignProvenance)\n : 1;\n return Math.max(0, base * importanceFactor * securityFactor);\n}\n\n/**\n * Capacity-bounded eviction selector (X-1). Given facts scored by\n * {@link salience}, return the ids of the lowest-salience ones to\n * archive so that at most `capacity` remain. Pure and deterministic:\n * ties break by id so a given batch always evicts the same set.\n *\n * Returns `[]` when the batch already fits (`length <= capacity`).\n * `capacity <= 0` evicts the whole batch.\n *\n * @stable\n */\nexport function selectForCapacityEviction(\n scored: ReadonlyArray<{ readonly id: string; readonly salience: number }>,\n capacity: number,\n): ReadonlyArray<string> {\n const evictCount = scored.length - Math.max(0, capacity);\n if (evictCount <= 0) return [];\n return [...scored]\n .sort((a, b) => a.salience - b.salience || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n .slice(0, evictCount)\n .map((row) => row.id);\n}\n\nfunction clamp01(value: number): number {\n if (Number.isNaN(value)) return NEUTRAL_IMPORTANCE;\n return Math.min(1, Math.max(0, value));\n}\n"],"mappings":";;;;;;;;;AAiCA,SAAgB,UAAU,MAWf;CACT,MAAM,YAAY,KAAK,kBAAkB,KAAK;CAE9C,MAAM,cADY,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,IAClB,MAAO,KAAK,KAAK;CAClD,MAAM,MAAM,KAAK,IAAI,IAAK,KAAK,UAAU,KAAK,IAAI,IAAK,KAAK,SAAS,CAAC;AACtE,QAAO,KAAK,IAAI,CAAC,cAAc,IAAI;;;;;;;;;AAkCrC,MAAa,qBAAqB;;;;;;;;;AAyClC,MAAaA,2BAA4C,OAAO,OAAO;CACrE,YAAY;CACZ,YAAY;CACZ,mBAAmB;CACpB,CAAC;;;;;;;;;;;;;;;;;;AAmBF,SAAgB,SAAS,MAcd;CACT,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,OAAO,UAAU;EACrB,KAAK,KAAK;EACV,gBAAgB,KAAK;EACrB,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,SAAS,KAAK;EACf,CAAC;CACF,MAAM,aAAa,QAAQ,KAAK,cAAc,mBAAmB;CACjE,MAAM,mBAAmB,KAAK,IAAI,GAAG,IAAI,QAAQ,cAAc,aAAa,oBAAoB;CAChG,MAAM,iBAAiB,KAAK,cACxB,KAAK,IAAI,GAAG,IAAI,QAAQ,WAAW,GACnC,KAAK,oBACH,KAAK,IAAI,GAAG,IAAI,QAAQ,kBAAkB,GAC1C;AACN,QAAO,KAAK,IAAI,GAAG,OAAO,mBAAmB,eAAe;;;;;;;;;;;;;AAc9D,SAAgB,0BACd,QACA,UACuB;CACvB,MAAM,aAAa,OAAO,SAAS,KAAK,IAAI,GAAG,SAAS;AACxD,KAAI,cAAc,EAAG,QAAO,EAAE;AAC9B,QAAO,CAAC,GAAG,OAAO,CACf,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,CACnF,MAAM,GAAG,WAAW,CACpB,KAAK,QAAQ,IAAI,GAAG;;AAGzB,SAAS,QAAQ,OAAuB;AACtC,KAAI,OAAO,MAAM,MAAM,CAAE,QAAO;AAChC,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC"}
@@ -0,0 +1,58 @@
1
+ //#region src/consolidator/dlq.ts
2
+ /**
3
+ * Dead-letter queue helpers — pure functions used by the standard
4
+ * + deep phases. The actual persistence lives in the storage
5
+ * adapter's `enqueueFailedBatch / claimReadyBatches / ...` surface.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Compute the next retry delay given the failure count + retry
11
+ * policy. Implements full-jitter exponential backoff capped by
12
+ * `maxBackoffMs`.
13
+ *
14
+ * The formula is `delay = random(0, min(maxBackoffMs, baseMs * 2^retryCount))`
15
+ * where `random` is the optional `jitter` callback (defaults to
16
+ * `Math.random()`).
17
+ *
18
+ * @stable
19
+ */
20
+ function nextBackoffMs(args) {
21
+ const cap = Math.min(args.maxMs, args.baseMs * 2 ** Math.max(0, args.retryCount));
22
+ const jitter = args.jitter ?? Math.random;
23
+ return Math.max(0, Math.floor(cap * jitter()));
24
+ }
25
+ /**
26
+ * Classify a thrown error into the lowercase `error_kind` value
27
+ * recorded against `consolidator_failed_batches`. Used by the phases
28
+ * so the DLQ row carries a stable discriminator instead of free-form
29
+ * prose.
30
+ *
31
+ * @stable
32
+ */
33
+ function classifyError(err) {
34
+ if (err === null || err === void 0) return "unknown";
35
+ const message = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
36
+ if (message.includes("rate limit") || message.includes("429")) return "rate_limit";
37
+ if (message.includes("5xx") || message.includes("500") || message.includes("502")) return "5xx";
38
+ if (message.includes("timeout")) return "timeout";
39
+ if (message.includes("embedder") || message.includes("embedding")) return "embedder_failed";
40
+ if (message.includes("invalid") || message.includes("parse")) return "invalid_response";
41
+ if (message.includes("budget")) return "budget";
42
+ return "unknown";
43
+ }
44
+ /**
45
+ * Best-effort error message extraction.
46
+ *
47
+ * @internal
48
+ */
49
+ function describeError(err) {
50
+ if (err === null || err === void 0) return "unknown error";
51
+ if (err instanceof Error) return err.message;
52
+ if (typeof err === "string") return err;
53
+ return String(err);
54
+ }
55
+
56
+ //#endregion
57
+ export { classifyError, describeError, nextBackoffMs };
58
+ //# sourceMappingURL=dlq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dlq.js","names":[],"sources":["../../src/consolidator/dlq.ts"],"sourcesContent":["/**\n * Dead-letter queue helpers — pure functions used by the standard\n * + deep phases. The actual persistence lives in the storage\n * adapter's `enqueueFailedBatch / claimReadyBatches / ...` surface.\n *\n * @packageDocumentation\n */\n\n/**\n * Compute the next retry delay given the failure count + retry\n * policy. Implements full-jitter exponential backoff capped by\n * `maxBackoffMs`.\n *\n * The formula is `delay = random(0, min(maxBackoffMs, baseMs * 2^retryCount))`\n * where `random` is the optional `jitter` callback (defaults to\n * `Math.random()`).\n *\n * @stable\n */\nexport function nextBackoffMs(args: {\n readonly retryCount: number;\n readonly baseMs: number;\n readonly maxMs: number;\n readonly jitter?: () => number;\n}): number {\n const cap = Math.min(args.maxMs, args.baseMs * 2 ** Math.max(0, args.retryCount));\n const jitter = args.jitter ?? Math.random;\n return Math.max(0, Math.floor(cap * jitter()));\n}\n\n/**\n * Classify a thrown error into the lowercase `error_kind` value\n * recorded against `consolidator_failed_batches`. Used by the phases\n * so the DLQ row carries a stable discriminator instead of free-form\n * prose.\n *\n * @stable\n */\nexport function classifyError(err: unknown): string {\n if (err === null || err === undefined) return 'unknown';\n const message = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();\n if (message.includes('rate limit') || message.includes('429')) return 'rate_limit';\n if (message.includes('5xx') || message.includes('500') || message.includes('502')) return '5xx';\n if (message.includes('timeout')) return 'timeout';\n if (message.includes('embedder') || message.includes('embedding')) return 'embedder_failed';\n if (message.includes('invalid') || message.includes('parse')) return 'invalid_response';\n if (message.includes('budget')) return 'budget';\n return 'unknown';\n}\n\n/**\n * Best-effort error message extraction.\n *\n * @internal\n */\nexport function describeError(err: unknown): string {\n if (err === null || err === undefined) return 'unknown error';\n if (err instanceof Error) return err.message;\n if (typeof err === 'string') return err;\n return String(err);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,MAKnB;CACT,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,KAAK,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,WAAW,CAAC;CACjF,MAAM,SAAS,KAAK,UAAU,KAAK;AACnC,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,QAAQ,CAAC,CAAC;;;;;;;;;;AAWhD,SAAgB,cAAc,KAAsB;AAClD,KAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;CAC9C,MAAM,UAAU,eAAe,QAAQ,IAAI,QAAQ,aAAa,GAAG,OAAO,IAAI,CAAC,aAAa;AAC5F,KAAI,QAAQ,SAAS,aAAa,IAAI,QAAQ,SAAS,MAAM,CAAE,QAAO;AACtE,KAAI,QAAQ,SAAS,MAAM,IAAI,QAAQ,SAAS,MAAM,IAAI,QAAQ,SAAS,MAAM,CAAE,QAAO;AAC1F,KAAI,QAAQ,SAAS,UAAU,CAAE,QAAO;AACxC,KAAI,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,YAAY,CAAE,QAAO;AAC1E,KAAI,QAAQ,SAAS,UAAU,IAAI,QAAQ,SAAS,QAAQ,CAAE,QAAO;AACrE,KAAI,QAAQ,SAAS,SAAS,CAAE,QAAO;AACvC,QAAO;;;;;;;AAQT,SAAgB,cAAc,KAAsB;AAClD,KAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,KAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAO,OAAO,IAAI"}
@@ -0,0 +1,54 @@
1
+ import { GraphorinMemoryError } from "../errors/index.js";
2
+ import { ConsolidatorPhase } from "./types.js";
3
+
4
+ //#region src/consolidator/errors.d.ts
5
+
6
+ /**
7
+ * Raised when the consolidator's daily budget envelope is exhausted
8
+ * and `onExceed: 'throw'` is in effect.
9
+ *
10
+ * @stable
11
+ */
12
+ declare class BudgetExceededError extends GraphorinMemoryError {
13
+ readonly name = "BudgetExceededError";
14
+ readonly kind: "budget-exceeded";
15
+ readonly phase: ConsolidatorPhase;
16
+ readonly budget: number;
17
+ readonly actual: number;
18
+ readonly resource: 'tokens' | 'cost';
19
+ constructor(args: {
20
+ phase: ConsolidatorPhase;
21
+ budget: number;
22
+ actual: number;
23
+ resource: 'tokens' | 'cost';
24
+ });
25
+ }
26
+ /**
27
+ * Raised when `tier: 'custom'` is selected without explicit
28
+ * `ceilings.maxTokensPerDay` / `ceilings.maxCostPerDay`. Custom is
29
+ * an explicit-only escape hatch.
30
+ *
31
+ * @stable
32
+ */
33
+ declare class CustomTierMisconfiguredError extends GraphorinMemoryError {
34
+ readonly name = "CustomTierMisconfiguredError";
35
+ readonly kind: "custom-tier-misconfigured";
36
+ readonly missing: ReadonlyArray<string>;
37
+ constructor(missing: ReadonlyArray<string>);
38
+ }
39
+ /**
40
+ * Raised when the standard / deep phase attempts an LLM call without
41
+ * a configured provider. Surfaces as a typed failure that the DLQ
42
+ * pipeline can attribute correctly.
43
+ *
44
+ * @stable
45
+ */
46
+ declare class ProviderNotConfiguredError extends GraphorinMemoryError {
47
+ readonly name = "ProviderNotConfiguredError";
48
+ readonly kind: "provider-not-configured";
49
+ readonly phase: ConsolidatorPhase;
50
+ constructor(phase: ConsolidatorPhase);
51
+ }
52
+ //#endregion
53
+ export { BudgetExceededError, CustomTierMisconfiguredError, ProviderNotConfiguredError };
54
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/consolidator/errors.ts"],"sourcesContent":[],"mappings":";;;;;AAiDA;;;;;AAuBA;AAGkB,cA1DL,mBAAA,SAA4B,oBAAA,CA0DvB;EAEG,SAAA,IAAA,GAAA,qBAAA;EAL2B,SAAA,IAAA,EAAA,iBAAA;EAAoB,SAAA,KAAA,EApDlD,iBAoDkD;;;;;WA9CzD;;;;;;;;;;;;;cAuBE,4BAAA,SAAqC,oBAAA;;;oBAG9B;uBAEG;;;;;;;;;cAkBV,0BAAA,SAAmC,oBAAA;;;kBAG9B;qBAEG"}
@@ -0,0 +1,67 @@
1
+ import { GraphorinMemoryError } from "../errors/index.js";
2
+
3
+ //#region src/consolidator/errors.ts
4
+ /**
5
+ * Typed errors raised by the Phase 10c consolidator runtime. Every
6
+ * error carries a stable lowercase `kind` discriminator so downstream
7
+ * tooling can pattern-match without parsing the prose.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * Raised when the consolidator's daily budget envelope is exhausted
13
+ * and `onExceed: 'throw'` is in effect.
14
+ *
15
+ * @stable
16
+ */
17
+ var BudgetExceededError = class extends GraphorinMemoryError {
18
+ name = "BudgetExceededError";
19
+ kind = "budget-exceeded";
20
+ phase;
21
+ budget;
22
+ actual;
23
+ resource;
24
+ constructor(args) {
25
+ super(`[graphorin/memory] consolidator budget exceeded — phase=${args.phase} resource=${args.resource} actual=${args.actual} budget=${args.budget}`, { hint: "Pass `onExceed: \"log\"` to keep running, or upgrade the tier ceilings." });
26
+ this.phase = args.phase;
27
+ this.budget = args.budget;
28
+ this.actual = args.actual;
29
+ this.resource = args.resource;
30
+ }
31
+ };
32
+ /**
33
+ * Raised when `tier: 'custom'` is selected without explicit
34
+ * `ceilings.maxTokensPerDay` / `ceilings.maxCostPerDay`. Custom is
35
+ * an explicit-only escape hatch.
36
+ *
37
+ * @stable
38
+ */
39
+ var CustomTierMisconfiguredError = class extends GraphorinMemoryError {
40
+ name = "CustomTierMisconfiguredError";
41
+ kind = "custom-tier-misconfigured";
42
+ missing;
43
+ constructor(missing) {
44
+ super(`[graphorin/memory] tier='custom' requires explicit ceilings — missing: ${missing.join(", ")}`, { hint: "Set `ceilings.maxTokensPerDay` + `ceilings.maxCostPerDay` (and `cheapModel` / `deepModel` if those phases are enabled)." });
45
+ this.missing = Object.freeze([...missing]);
46
+ }
47
+ };
48
+ /**
49
+ * Raised when the standard / deep phase attempts an LLM call without
50
+ * a configured provider. Surfaces as a typed failure that the DLQ
51
+ * pipeline can attribute correctly.
52
+ *
53
+ * @stable
54
+ */
55
+ var ProviderNotConfiguredError = class extends GraphorinMemoryError {
56
+ name = "ProviderNotConfiguredError";
57
+ kind = "provider-not-configured";
58
+ phase;
59
+ constructor(phase) {
60
+ super(`[graphorin/memory] consolidator phase '${phase}' requires a provider but none was supplied.`, { hint: "Pass `provider` to createConsolidator({...}) or pin tier=\"free\" to skip LLM phases." });
61
+ this.phase = phase;
62
+ }
63
+ };
64
+
65
+ //#endregion
66
+ export { BudgetExceededError, CustomTierMisconfiguredError, ProviderNotConfiguredError };
67
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/consolidator/errors.ts"],"sourcesContent":["/**\n * Typed errors raised by the Phase 10c consolidator runtime. Every\n * error carries a stable lowercase `kind` discriminator so downstream\n * tooling can pattern-match without parsing the prose.\n *\n * @packageDocumentation\n */\n\nimport { GraphorinMemoryError } from '../errors/index.js';\nimport type { ConsolidatorPhase } from './types.js';\n\n/**\n * Raised when the consolidator's daily budget envelope is exhausted\n * and `onExceed: 'throw'` is in effect.\n *\n * @stable\n */\nexport class BudgetExceededError extends GraphorinMemoryError {\n override readonly name = 'BudgetExceededError';\n readonly kind = 'budget-exceeded' as const;\n readonly phase: ConsolidatorPhase;\n readonly budget: number;\n readonly actual: number;\n readonly resource: 'tokens' | 'cost';\n\n constructor(args: {\n phase: ConsolidatorPhase;\n budget: number;\n actual: number;\n resource: 'tokens' | 'cost';\n }) {\n super(\n `[graphorin/memory] consolidator budget exceeded — phase=${args.phase} resource=${args.resource} actual=${args.actual} budget=${args.budget}`,\n { hint: 'Pass `onExceed: \"log\"` to keep running, or upgrade the tier ceilings.' },\n );\n this.phase = args.phase;\n this.budget = args.budget;\n this.actual = args.actual;\n this.resource = args.resource;\n }\n}\n\n/**\n * Raised when `tier: 'custom'` is selected without explicit\n * `ceilings.maxTokensPerDay` / `ceilings.maxCostPerDay`. Custom is\n * an explicit-only escape hatch.\n *\n * @stable\n */\nexport class CustomTierMisconfiguredError extends GraphorinMemoryError {\n override readonly name = 'CustomTierMisconfiguredError';\n readonly kind = 'custom-tier-misconfigured' as const;\n readonly missing: ReadonlyArray<string>;\n\n constructor(missing: ReadonlyArray<string>) {\n super(\n `[graphorin/memory] tier='custom' requires explicit ceilings — missing: ${missing.join(', ')}`,\n {\n hint: 'Set `ceilings.maxTokensPerDay` + `ceilings.maxCostPerDay` (and `cheapModel` / `deepModel` if those phases are enabled).',\n },\n );\n this.missing = Object.freeze([...missing]);\n }\n}\n\n/**\n * Raised when the standard / deep phase attempts an LLM call without\n * a configured provider. Surfaces as a typed failure that the DLQ\n * pipeline can attribute correctly.\n *\n * @stable\n */\nexport class ProviderNotConfiguredError extends GraphorinMemoryError {\n override readonly name = 'ProviderNotConfiguredError';\n readonly kind = 'provider-not-configured' as const;\n readonly phase: ConsolidatorPhase;\n\n constructor(phase: ConsolidatorPhase) {\n super(\n `[graphorin/memory] consolidator phase '${phase}' requires a provider but none was supplied.`,\n {\n hint: 'Pass `provider` to createConsolidator({...}) or pin tier=\"free\" to skip LLM phases.',\n },\n );\n this.phase = phase;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,IAAa,sBAAb,cAAyC,qBAAqB;CAC5D,AAAkB,OAAO;CACzB,AAAS,OAAO;CAChB,AAAS;CACT,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,MAKT;AACD,QACE,2DAA2D,KAAK,MAAM,YAAY,KAAK,SAAS,UAAU,KAAK,OAAO,UAAU,KAAK,UACrI,EAAE,MAAM,2EAAyE,CAClF;AACD,OAAK,QAAQ,KAAK;AAClB,OAAK,SAAS,KAAK;AACnB,OAAK,SAAS,KAAK;AACnB,OAAK,WAAW,KAAK;;;;;;;;;;AAWzB,IAAa,+BAAb,cAAkD,qBAAqB;CACrE,AAAkB,OAAO;CACzB,AAAS,OAAO;CAChB,AAAS;CAET,YAAY,SAAgC;AAC1C,QACE,0EAA0E,QAAQ,KAAK,KAAK,IAC5F,EACE,MAAM,2HACP,CACF;AACD,OAAK,UAAU,OAAO,OAAO,CAAC,GAAG,QAAQ,CAAC;;;;;;;;;;AAW9C,IAAa,6BAAb,cAAgD,qBAAqB;CACnE,AAAkB,OAAO;CACzB,AAAS,OAAO;CAChB,AAAS;CAET,YAAY,OAA0B;AACpC,QACE,0CAA0C,MAAM,+CAChD,EACE,MAAM,yFACP,CACF;AACD,OAAK,QAAQ"}
@@ -0,0 +1 @@
1
+ import "../internal/storage-adapter.js";
@@ -0,0 +1,22 @@
1
+ //#region src/consolidator/idempotency.ts
2
+ /**
3
+ * Advance the cursor to the highest sequence number observed in the
4
+ * batch. Returns `null` when the batch is empty.
5
+ *
6
+ * @stable
7
+ */
8
+ function tipMessageId(batch) {
9
+ if (batch.length === 0) return null;
10
+ let bestIdx = 0;
11
+ for (let i = 1; i < batch.length; i++) {
12
+ const candidate = batch[i];
13
+ const champion = batch[bestIdx];
14
+ if (candidate === void 0 || champion === void 0) continue;
15
+ if (candidate.sequence > champion.sequence) bestIdx = i;
16
+ }
17
+ return batch[bestIdx]?.id ?? null;
18
+ }
19
+
20
+ //#endregion
21
+ export { tipMessageId };
22
+ //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.js","names":[],"sources":["../../src/consolidator/idempotency.ts"],"sourcesContent":["/**\n * Idempotency cursor helpers. The runtime reads the persisted state\n * row directly via {@link ConsolidatorMemoryStoreExt.getState}; this\n * file centralises the pure helpers around the cursor advance.\n *\n * @packageDocumentation\n */\n\nimport type { SessionMessageRecord } from '../internal/storage-adapter.js';\n\n/**\n * Advance the cursor to the highest sequence number observed in the\n * batch. Returns `null` when the batch is empty.\n *\n * @stable\n */\nexport function tipMessageId(batch: ReadonlyArray<SessionMessageRecord>): string | null {\n if (batch.length === 0) return null;\n let bestIdx = 0;\n for (let i = 1; i < batch.length; i++) {\n const candidate = batch[i];\n const champion = batch[bestIdx];\n if (candidate === undefined || champion === undefined) continue;\n if (candidate.sequence > champion.sequence) bestIdx = i;\n }\n return batch[bestIdx]?.id ?? null;\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,aAAa,OAA2D;AACtF,KAAI,MAAM,WAAW,EAAG,QAAO;CAC/B,IAAI,UAAU;AACd,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,YAAY,MAAM;EACxB,MAAM,WAAW,MAAM;AACvB,MAAI,cAAc,UAAa,aAAa,OAAW;AACvD,MAAI,UAAU,WAAW,SAAS,SAAU,WAAU;;AAExD,QAAO,MAAM,UAAU,MAAM"}
@@ -0,0 +1,34 @@
1
+ import { SalienceWeights } from "./decay.js";
2
+ import { CONSOLIDATOR_TIER_DEFAULTS, ConsolidatorBudgetSnapshot, ConsolidatorCeilings, ConsolidatorConfig, ConsolidatorLastRuns, ConsolidatorPhase, ConsolidatorStatus, ConsolidatorTier, ConsolidatorTriggerReason, ConsolidatorTriggerSpec, CreateConsolidatorOptions, OnBudgetExceed, PhaseListener, PhaseOutcome } from "./types.js";
3
+ import { ParsedTrigger, parseTriggerSpec, reasonFromTrigger } from "./triggers.js";
4
+ import { ConsolidatorCatchupPolicy, RegisterTriggersOptions, RegisterTriggersResult, SchedulerLike, TriggerDeclarationLike, registerConsolidatorTriggers } from "./scheduler.js";
5
+ import { Consolidator, createConsolidator } from "./runtime.js";
6
+ import "./budget.js";
7
+ import { BudgetExceededError, CustomTierMisconfiguredError, ProviderNotConfiguredError } from "./errors.js";
8
+ import "./idempotency.js";
9
+ import "./lock.js";
10
+ import "./noise-filter.js";
11
+ import "./phases/deep.js";
12
+ import { DEFAULT_INDUCTION_MAX_TOKENS, INDUCTION_SYSTEM_PROMPT, InducedProcedure, MAX_PROCEDURE_STEPS, MAX_TRAJECTORY_STEPS_SHOWN, Trajectory, TrajectoryStep, VerificationResult, WorkflowInducer, WorkflowInductionOptions, buildInductionRequest, checkSuccessCriteria, createProviderWorkflowInducer, normalizeInducedProcedure, parseInducedProcedure, runWorkflowInduction, trajectoryFromRunState } from "./phases/induce.js";
13
+ import "./phases/light.js";
14
+ import "./phases/standard.js";
15
+
16
+ //#region src/consolidator/index.d.ts
17
+
18
+ /**
19
+ * Build a no-op consolidator that honours the full {@link Consolidator}
20
+ * interface but performs no background work. Useful for consumers
21
+ * that want the typed shape (e.g., unit tests of higher tiers) but
22
+ * do not pay the runtime cost of triggers / locking / DLQ.
23
+ *
24
+ * Phase 10c's {@link createConsolidator} is the production factory.
25
+ *
26
+ * @stable
27
+ */
28
+ declare function createConsolidatorPlaceholder(options?: {
29
+ readonly triggers?: ReadonlyArray<ConsolidatorTriggerSpec>;
30
+ readonly tier?: ConsolidatorTier;
31
+ }): Consolidator;
32
+ //#endregion
33
+ export { createConsolidatorPlaceholder };
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/consolidator/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8IgB,6BAAA;sBAEQ,cAAc;kBAClB;IAEjB"}
@@ -0,0 +1,163 @@
1
+ import { DEFAULT_SALIENCE_WEIGHTS, NEUTRAL_IMPORTANCE, retention, salience, selectForCapacityEviction } from "./decay.js";
2
+ import { BudgetExceededError, CustomTierMisconfiguredError, ProviderNotConfiguredError } from "./errors.js";
3
+ import { BudgetTracker, bucketStart, nextBucketStart } from "./budget.js";
4
+ import { classifyError, describeError, nextBackoffMs } from "./dlq.js";
5
+ import { tipMessageId } from "./idempotency.js";
6
+ import { LockManager } from "./lock.js";
7
+ import { applyNoiseFilters } from "./noise-filter.js";
8
+ import { runDeepPhase } from "./phases/deep.js";
9
+ import { DEFAULT_INDUCTION_MAX_TOKENS, INDUCTION_SYSTEM_PROMPT, MAX_PROCEDURE_STEPS, MAX_TRAJECTORY_STEPS_SHOWN, buildInductionRequest, checkSuccessCriteria, createProviderWorkflowInducer, normalizeInducedProcedure, parseInducedProcedure, runWorkflowInduction, trajectoryFromRunState } from "./phases/induce.js";
10
+ import { runLightPhase } from "./phases/light.js";
11
+ import { parseExtraction, runStandardPhase } from "./phases/standard.js";
12
+ import { parseTriggerSpec, reasonFromTrigger } from "./triggers.js";
13
+ import { registerConsolidatorTriggers } from "./scheduler.js";
14
+ import { CONSOLIDATOR_TIER_DEFAULTS } from "./types.js";
15
+ import { createConsolidator } from "./runtime.js";
16
+
17
+ //#region src/consolidator/index.ts
18
+ /**
19
+ * `@graphorin/memory/consolidator` — Phase 10c runtime that turns
20
+ * raw conversation turns into long-lived facts and episodes. The
21
+ * module ships:
22
+ *
23
+ * - {@link createConsolidator} — the production factory that wires
24
+ * triggers, the budget envelope, the wait-then-defer lock, the
25
+ * idempotent cursor, the DLQ, and the three phases (light /
26
+ * standard / deep) into a single `Consolidator` handle.
27
+ * - {@link createConsolidatorPlaceholder} — kept for back-compat
28
+ * with consumers that wired the no-op stub during Phase 10a; it
29
+ * honours the same shape but does no background work.
30
+ * - Pure helpers (`noise-filter`, `decay`, `budget`, `lock`, `dlq`,
31
+ * `idempotency`, `triggers`) and the per-phase orchestrators —
32
+ * exported so tests + the standalone server (Phase 14) can compose
33
+ * them without re-implementing the algorithm.
34
+ *
35
+ * The `Consolidator` interface is **stable** — Phase 14 wires the
36
+ * runtime into the lifecycle hooks, and Phase 15 surfaces every
37
+ * method through `graphorin consolidator …` CLI commands.
38
+ *
39
+ * @packageDocumentation
40
+ */
41
+ /**
42
+ * Build a no-op consolidator that honours the full {@link Consolidator}
43
+ * interface but performs no background work. Useful for consumers
44
+ * that want the typed shape (e.g., unit tests of higher tiers) but
45
+ * do not pay the runtime cost of triggers / locking / DLQ.
46
+ *
47
+ * Phase 10c's {@link createConsolidator} is the production factory.
48
+ *
49
+ * @stable
50
+ */
51
+ function createConsolidatorPlaceholder(options = {}) {
52
+ let running = false;
53
+ let paused = false;
54
+ let tier = options.tier ?? "free";
55
+ const triggers = Object.freeze([...options.triggers ?? ["idle:5m", "cron:0 4 * * *"]]);
56
+ const listeners = /* @__PURE__ */ new Set();
57
+ const config = Object.freeze({
58
+ triggers,
59
+ tier,
60
+ phases: Object.freeze(["light"]),
61
+ ceilings: {
62
+ maxTokensPerDay: 0,
63
+ maxCostPerDay: 0,
64
+ maxConcurrentRuns: 1,
65
+ maxRunDurationMs: 300 * 1e3,
66
+ cooldownMs: 6e4
67
+ },
68
+ onExceed: "pause",
69
+ cheapModel: null,
70
+ deepModel: null,
71
+ budgetResetSemantics: "utc",
72
+ noiseFilters: Object.freeze(["default"]),
73
+ lockWaitMs: 3e4,
74
+ decayTauDays: 7,
75
+ decayArchiveThreshold: .05,
76
+ decayCapacity: null,
77
+ salienceWeights: DEFAULT_SALIENCE_WEIGHTS,
78
+ maxStandardBatchSize: 50,
79
+ maxDeepConflictsPerRun: 20,
80
+ dlqMaxRetries: 5,
81
+ dlqBaseBackoffMs: 6e4,
82
+ dlqMaxBackoffMs: 3600 * 1e3,
83
+ formEpisodes: false,
84
+ importanceScoring: false,
85
+ autoPromoteExtraction: false,
86
+ reflection: false,
87
+ importanceThreshold: 3,
88
+ reflectionMaxQuestions: 3,
89
+ contextualRetrieval: "late-chunk"
90
+ });
91
+ const status = async () => Object.freeze({
92
+ tier,
93
+ triggers,
94
+ phases: config.phases,
95
+ running,
96
+ paused,
97
+ pendingConflicts: 0,
98
+ queueDepth: 0,
99
+ dlqSize: 0,
100
+ deferredRuns: 0,
101
+ emptyExtractions: 0,
102
+ lastRuns: Object.freeze({}),
103
+ budget: {
104
+ tokensUsedToday: 0,
105
+ costUsedToday: 0,
106
+ tokensRemaining: 0,
107
+ costRemaining: 0,
108
+ resetAt: new Date(Date.now() + 864e5).toISOString()
109
+ },
110
+ budgetRemaining: {
111
+ tokens: 0,
112
+ costUsd: 0
113
+ }
114
+ });
115
+ return {
116
+ async start() {
117
+ running = true;
118
+ },
119
+ async stop() {
120
+ running = false;
121
+ },
122
+ async trigger() {
123
+ return null;
124
+ },
125
+ async fireNow() {
126
+ return null;
127
+ },
128
+ async setTier(next) {
129
+ tier = next;
130
+ },
131
+ async pause() {
132
+ paused = true;
133
+ },
134
+ async resume() {
135
+ paused = false;
136
+ },
137
+ recordExternalSpend() {},
138
+ onPhaseFinished(listener) {
139
+ listeners.add(listener);
140
+ return () => listeners.delete(listener);
141
+ },
142
+ config() {
143
+ return config;
144
+ },
145
+ async registerWithScheduler() {
146
+ return Object.freeze({
147
+ registered: Object.freeze([]),
148
+ skipped: Object.freeze([])
149
+ });
150
+ },
151
+ isFree() {
152
+ return tier === "free";
153
+ },
154
+ async drainDlq() {
155
+ return 0;
156
+ },
157
+ status
158
+ };
159
+ }
160
+
161
+ //#endregion
162
+ export { createConsolidatorPlaceholder };
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["tier: ConsolidatorTier","config: ConsolidatorConfig"],"sources":["../../src/consolidator/index.ts"],"sourcesContent":["/**\n * `@graphorin/memory/consolidator` — Phase 10c runtime that turns\n * raw conversation turns into long-lived facts and episodes. The\n * module ships:\n *\n * - {@link createConsolidator} — the production factory that wires\n * triggers, the budget envelope, the wait-then-defer lock, the\n * idempotent cursor, the DLQ, and the three phases (light /\n * standard / deep) into a single `Consolidator` handle.\n * - {@link createConsolidatorPlaceholder} — kept for back-compat\n * with consumers that wired the no-op stub during Phase 10a; it\n * honours the same shape but does no background work.\n * - Pure helpers (`noise-filter`, `decay`, `budget`, `lock`, `dlq`,\n * `idempotency`, `triggers`) and the per-phase orchestrators —\n * exported so tests + the standalone server (Phase 14) can compose\n * them without re-implementing the algorithm.\n *\n * The `Consolidator` interface is **stable** — Phase 14 wires the\n * runtime into the lifecycle hooks, and Phase 15 surfaces every\n * method through `graphorin consolidator …` CLI commands.\n *\n * @packageDocumentation\n */\n\nimport { DEFAULT_SALIENCE_WEIGHTS } from './decay.js';\nimport type { Consolidator } from './runtime.js';\nimport type {\n ConsolidatorConfig,\n ConsolidatorPhase,\n ConsolidatorStatus,\n ConsolidatorTier,\n ConsolidatorTriggerSpec,\n PhaseListener,\n PhaseOutcome,\n} from './types.js';\n\nexport {\n type BudgetCheck,\n type BudgetSnapshot,\n BudgetTracker,\n type BudgetTrackerOptions,\n bucketStart,\n nextBucketStart,\n} from './budget.js';\nexport {\n DEFAULT_DECAY_ARCHIVE_THRESHOLD,\n DEFAULT_DECAY_TAU_DAYS,\n DEFAULT_SALIENCE_WEIGHTS,\n NEUTRAL_IMPORTANCE,\n retention,\n type SalienceWeights,\n salience,\n selectForCapacityEviction,\n shouldArchive,\n} from './decay.js';\nexport {\n classifyError,\n describeError,\n nextBackoffMs,\n} from './dlq.js';\nexport {\n BudgetExceededError,\n CustomTierMisconfiguredError,\n ProviderNotConfiguredError,\n} from './errors.js';\nexport { tipMessageId } from './idempotency.js';\nexport { LockManager, type LockManagerOptions, type LockOutcome } from './lock.js';\nexport {\n applyNoiseFilters,\n type NoiseFilterPreset,\n type NoiseFilterResult,\n} from './noise-filter.js';\nexport { type DeepPhaseDeps, runDeepPhase } from './phases/deep.js';\nexport {\n buildInductionRequest,\n checkSuccessCriteria,\n createProviderWorkflowInducer,\n DEFAULT_INDUCTION_MAX_TOKENS,\n INDUCTION_SYSTEM_PROMPT,\n type InducedProcedure,\n MAX_PROCEDURE_STEPS,\n MAX_TRAJECTORY_STEPS_SHOWN,\n normalizeInducedProcedure,\n parseInducedProcedure,\n runWorkflowInduction,\n type Trajectory,\n type TrajectoryStep,\n trajectoryFromRunState,\n type VerificationResult,\n type WorkflowInducer,\n type WorkflowInductionOptions,\n} from './phases/induce.js';\nexport {\n type LightPhaseDeps,\n runLightPhase,\n} from './phases/light.js';\nexport {\n parseExtraction,\n runStandardPhase,\n type StandardPhaseDeps,\n} from './phases/standard.js';\nexport { type Consolidator, createConsolidator } from './runtime.js';\nexport {\n type ConsolidatorCatchupPolicy,\n type RegisterTriggersOptions,\n type RegisterTriggersResult,\n registerConsolidatorTriggers,\n type SchedulerLike,\n type TriggerDeclarationLike,\n} from './scheduler.js';\nexport {\n type ParsedTrigger,\n parseTriggerSpec,\n reasonFromTrigger,\n} from './triggers.js';\nexport type {\n ConsolidatorBudgetSnapshot,\n ConsolidatorCeilings,\n ConsolidatorConfig,\n ConsolidatorLastRuns,\n ConsolidatorPhase,\n ConsolidatorStatus,\n ConsolidatorTier,\n ConsolidatorTriggerReason,\n ConsolidatorTriggerSpec,\n CreateConsolidatorOptions,\n OnBudgetExceed,\n PhaseListener,\n PhaseOutcome,\n} from './types.js';\nexport { CONSOLIDATOR_TIER_DEFAULTS } from './types.js';\n\n/**\n * Build a no-op consolidator that honours the full {@link Consolidator}\n * interface but performs no background work. Useful for consumers\n * that want the typed shape (e.g., unit tests of higher tiers) but\n * do not pay the runtime cost of triggers / locking / DLQ.\n *\n * Phase 10c's {@link createConsolidator} is the production factory.\n *\n * @stable\n */\nexport function createConsolidatorPlaceholder(\n options: {\n readonly triggers?: ReadonlyArray<ConsolidatorTriggerSpec>;\n readonly tier?: ConsolidatorTier;\n } = {},\n): Consolidator {\n let running = false;\n let paused = false;\n let tier: ConsolidatorTier = options.tier ?? 'free';\n const triggers = Object.freeze([\n // Matches the real runtime default (MCON-4): idle drives light/standard,\n // the daily cron makes the deep phase reachable; turn:N is consumer-emitted\n // so it is not a scheduler default.\n ...(options.triggers ?? (['idle:5m', 'cron:0 4 * * *'] as ConsolidatorTriggerSpec[])),\n ]) as ReadonlyArray<ConsolidatorTriggerSpec>;\n const listeners = new Set<PhaseListener>();\n\n const config: ConsolidatorConfig = Object.freeze({\n triggers,\n tier,\n phases: Object.freeze(['light' as ConsolidatorPhase]),\n ceilings: {\n maxTokensPerDay: 0,\n maxCostPerDay: 0,\n maxConcurrentRuns: 1,\n maxRunDurationMs: 5 * 60 * 1000,\n cooldownMs: 60_000,\n },\n onExceed: 'pause',\n cheapModel: null,\n deepModel: null,\n budgetResetSemantics: 'utc',\n noiseFilters: Object.freeze(['default' as const]),\n lockWaitMs: 30_000,\n decayTauDays: 7,\n decayArchiveThreshold: 0.05,\n decayCapacity: null,\n salienceWeights: DEFAULT_SALIENCE_WEIGHTS,\n maxStandardBatchSize: 50,\n maxDeepConflictsPerRun: 20,\n dlqMaxRetries: 5,\n dlqBaseBackoffMs: 60_000,\n dlqMaxBackoffMs: 60 * 60 * 1000,\n formEpisodes: false,\n importanceScoring: false,\n autoPromoteExtraction: false,\n reflection: false,\n importanceThreshold: 3,\n reflectionMaxQuestions: 3,\n contextualRetrieval: 'late-chunk',\n });\n\n const status = async (): Promise<ConsolidatorStatus> =>\n Object.freeze({\n tier,\n triggers,\n phases: config.phases,\n running,\n paused,\n pendingConflicts: 0,\n queueDepth: 0,\n dlqSize: 0,\n deferredRuns: 0,\n emptyExtractions: 0,\n lastRuns: Object.freeze({}),\n budget: {\n tokensUsedToday: 0,\n costUsedToday: 0,\n tokensRemaining: 0,\n costRemaining: 0,\n resetAt: new Date(Date.now() + 86_400_000).toISOString(),\n },\n budgetRemaining: {\n tokens: 0,\n costUsd: 0,\n },\n });\n\n return {\n async start() {\n running = true;\n },\n async stop() {\n running = false;\n },\n async trigger() {\n return null;\n },\n async fireNow(): Promise<PhaseOutcome | null> {\n return null;\n },\n async setTier(next: ConsolidatorTier) {\n tier = next;\n },\n async pause() {\n paused = true;\n },\n async resume() {\n paused = false;\n },\n recordExternalSpend(): void {\n // Placeholder — no budget to record against.\n },\n onPhaseFinished(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n config() {\n return config;\n },\n async registerWithScheduler() {\n // No-op consolidator: nothing to schedule.\n return Object.freeze({ registered: Object.freeze([]), skipped: Object.freeze([]) });\n },\n isFree() {\n return tier === 'free';\n },\n async drainDlq() {\n return 0;\n },\n status,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8IA,SAAgB,8BACd,UAGI,EAAE,EACQ;CACd,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAIA,OAAyB,QAAQ,QAAQ;CAC7C,MAAM,WAAW,OAAO,OAAO,CAI7B,GAAI,QAAQ,YAAa,CAAC,WAAW,iBAAiB,CACvD,CAAC;CACF,MAAM,4BAAY,IAAI,KAAoB;CAE1C,MAAMC,SAA6B,OAAO,OAAO;EAC/C;EACA;EACA,QAAQ,OAAO,OAAO,CAAC,QAA6B,CAAC;EACrD,UAAU;GACR,iBAAiB;GACjB,eAAe;GACf,mBAAmB;GACnB,kBAAkB,MAAS;GAC3B,YAAY;GACb;EACD,UAAU;EACV,YAAY;EACZ,WAAW;EACX,sBAAsB;EACtB,cAAc,OAAO,OAAO,CAAC,UAAmB,CAAC;EACjD,YAAY;EACZ,cAAc;EACd,uBAAuB;EACvB,eAAe;EACf,iBAAiB;EACjB,sBAAsB;EACtB,wBAAwB;EACxB,eAAe;EACf,kBAAkB;EAClB,iBAAiB,OAAU;EAC3B,cAAc;EACd,mBAAmB;EACnB,uBAAuB;EACvB,YAAY;EACZ,qBAAqB;EACrB,wBAAwB;EACxB,qBAAqB;EACtB,CAAC;CAEF,MAAM,SAAS,YACb,OAAO,OAAO;EACZ;EACA;EACA,QAAQ,OAAO;EACf;EACA;EACA,kBAAkB;EAClB,YAAY;EACZ,SAAS;EACT,cAAc;EACd,kBAAkB;EAClB,UAAU,OAAO,OAAO,EAAE,CAAC;EAC3B,QAAQ;GACN,iBAAiB;GACjB,eAAe;GACf,iBAAiB;GACjB,eAAe;GACf,SAAS,IAAI,KAAK,KAAK,KAAK,GAAG,MAAW,CAAC,aAAa;GACzD;EACD,iBAAiB;GACf,QAAQ;GACR,SAAS;GACV;EACF,CAAC;AAEJ,QAAO;EACL,MAAM,QAAQ;AACZ,aAAU;;EAEZ,MAAM,OAAO;AACX,aAAU;;EAEZ,MAAM,UAAU;AACd,UAAO;;EAET,MAAM,UAAwC;AAC5C,UAAO;;EAET,MAAM,QAAQ,MAAwB;AACpC,UAAO;;EAET,MAAM,QAAQ;AACZ,YAAS;;EAEX,MAAM,SAAS;AACb,YAAS;;EAEX,sBAA4B;EAG5B,gBAAgB,UAAU;AACxB,aAAU,IAAI,SAAS;AACvB,gBAAa,UAAU,OAAO,SAAS;;EAEzC,SAAS;AACP,UAAO;;EAET,MAAM,wBAAwB;AAE5B,UAAO,OAAO,OAAO;IAAE,YAAY,OAAO,OAAO,EAAE,CAAC;IAAE,SAAS,OAAO,OAAO,EAAE,CAAC;IAAE,CAAC;;EAErF,SAAS;AACP,UAAO,SAAS;;EAElB,MAAM,WAAW;AACf,UAAO;;EAET;EACD"}
@@ -0,0 +1,2 @@
1
+ import "../internal/storage-adapter.js";
2
+ import { SessionScope } from "@graphorin/core";
@@ -0,0 +1,89 @@
1
+ //#region src/consolidator/lock.ts
2
+ const DEFAULT_POLL_MS = 100;
3
+ /** @stable */
4
+ var LockManager = class {
5
+ #store;
6
+ #waitMs;
7
+ #maxAge;
8
+ #now;
9
+ #sleep;
10
+ #randomId;
11
+ #localLocks = /* @__PURE__ */ new Map();
12
+ constructor(opts) {
13
+ this.#store = opts.store;
14
+ this.#waitMs = Math.max(0, opts.waitMs);
15
+ this.#maxAge = Math.max(0, opts.maxRunDurationMs);
16
+ this.#now = opts.now ?? Date.now;
17
+ this.#sleep = opts.sleep ?? ((ms) => new Promise((resolve) => globalThis.setTimeout(resolve, ms)));
18
+ this.#randomId = opts.randomId ?? (() => {
19
+ return `run_${Math.floor(Math.random() * 1e9).toString(36)}${Math.floor(Math.random() * 1e9).toString(36)}`;
20
+ });
21
+ }
22
+ /**
23
+ * Try to acquire the per-scope lock. Returns the run id on success
24
+ * or `'deferred'` after the wait window elapses.
25
+ *
26
+ * @stable
27
+ */
28
+ async acquire(scope) {
29
+ const runId = this.#randomId();
30
+ const start = this.#now();
31
+ const pollMs = Math.min(DEFAULT_POLL_MS, Math.max(10, this.#waitMs / 5 || DEFAULT_POLL_MS));
32
+ while (true) {
33
+ if (await this.#tryAcquire(scope, runId)) return {
34
+ kind: "acquired",
35
+ runId
36
+ };
37
+ if (this.#now() - start >= this.#waitMs) return {
38
+ kind: "deferred",
39
+ heldBy: await this.#heldBy(scope)
40
+ };
41
+ await this.#sleep(pollMs);
42
+ }
43
+ }
44
+ /**
45
+ * Release the lock. Idempotent — releasing an already-released
46
+ * lock is a no-op.
47
+ *
48
+ * @stable
49
+ */
50
+ async release(scope, runId) {
51
+ if (this.#store !== null) await this.#store.releaseLock(scope, runId);
52
+ const key = scopeKey(scope);
53
+ const local = this.#localLocks.get(key);
54
+ if (local !== void 0 && local.runId === runId) this.#localLocks.delete(key);
55
+ }
56
+ async #tryAcquire(scope, runId) {
57
+ const now = this.#now();
58
+ if (this.#store !== null) return this.#store.acquireLock(scope, runId, now, this.#maxAge);
59
+ const key = scopeKey(scope);
60
+ const existing = this.#localLocks.get(key);
61
+ if (existing === void 0) {
62
+ this.#localLocks.set(key, {
63
+ runId,
64
+ acquiredAt: now
65
+ });
66
+ return true;
67
+ }
68
+ if (existing.runId === runId) return true;
69
+ if (this.#maxAge > 0 && now - existing.acquiredAt > this.#maxAge) {
70
+ this.#localLocks.set(key, {
71
+ runId,
72
+ acquiredAt: now
73
+ });
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ async #heldBy(scope) {
79
+ if (this.#store !== null) return (await this.#store.getState(scope))?.activeLockHeldBy ?? null;
80
+ return this.#localLocks.get(scopeKey(scope))?.runId ?? null;
81
+ }
82
+ };
83
+ function scopeKey(scope) {
84
+ return `${scope.userId}|${scope.sessionId ?? ""}|${scope.agentId ?? ""}`;
85
+ }
86
+
87
+ //#endregion
88
+ export { LockManager };
89
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","names":["#store","#waitMs","#maxAge","#now","#sleep","#randomId","#localLocks","#tryAcquire","#heldBy"],"sources":["../../src/consolidator/lock.ts"],"sourcesContent":["/**\n * Wait-then-defer lock manager. The lock state lives in the storage\n * layer's `consolidator_state` row so concurrent processes (lib +\n * server) coordinate through SQLite. The manager waits up to\n * `lockWaitMs` for the lock to clear; if the wait expires it returns\n * `'deferred'` so the trigger is recorded but no phase runs.\n *\n * The lock honours DEC-114 (\"memory does not block the agent loop\")\n * by capping the wait — the agent loop never observes a memory\n * blocker.\n *\n * @packageDocumentation\n */\n\nimport type { SessionScope } from '@graphorin/core';\nimport type { ConsolidatorMemoryStoreExt } from '../internal/storage-adapter.js';\n\n/** Outcome surfaced by {@link LockManager.acquire}. */\nexport type LockOutcome =\n | { readonly kind: 'acquired'; readonly runId: string }\n | { readonly kind: 'deferred'; readonly heldBy: string | null };\n\n/** Options accepted by {@link LockManager}. */\nexport interface LockManagerOptions {\n readonly store: ConsolidatorMemoryStoreExt | null;\n readonly waitMs: number;\n readonly maxRunDurationMs: number;\n readonly now?: () => number;\n readonly sleep?: (ms: number) => Promise<void>;\n readonly randomId?: () => string;\n}\n\nconst DEFAULT_POLL_MS = 100;\n\n/** @stable */\nexport class LockManager {\n readonly #store: ConsolidatorMemoryStoreExt | null;\n readonly #waitMs: number;\n readonly #maxAge: number;\n readonly #now: () => number;\n readonly #sleep: (ms: number) => Promise<void>;\n readonly #randomId: () => string;\n // Per-process fallback when the storage adapter does not expose a\n // consolidator surface (lib mode + in-memory test doubles).\n readonly #localLocks = new Map<string, { runId: string; acquiredAt: number }>();\n\n constructor(opts: LockManagerOptions) {\n this.#store = opts.store;\n this.#waitMs = Math.max(0, opts.waitMs);\n this.#maxAge = Math.max(0, opts.maxRunDurationMs);\n this.#now = opts.now ?? Date.now;\n this.#sleep =\n opts.sleep ??\n ((ms): Promise<void> => new Promise((resolve) => globalThis.setTimeout(resolve, ms)));\n this.#randomId =\n opts.randomId ??\n ((): string => {\n const a = Math.floor(Math.random() * 1e9).toString(36);\n const b = Math.floor(Math.random() * 1e9).toString(36);\n return `run_${a}${b}`;\n });\n }\n\n /**\n * Try to acquire the per-scope lock. Returns the run id on success\n * or `'deferred'` after the wait window elapses.\n *\n * @stable\n */\n async acquire(scope: SessionScope): Promise<LockOutcome> {\n const runId = this.#randomId();\n const start = this.#now();\n const pollMs = Math.min(DEFAULT_POLL_MS, Math.max(10, this.#waitMs / 5 || DEFAULT_POLL_MS));\n while (true) {\n const ok = await this.#tryAcquire(scope, runId);\n if (ok) return { kind: 'acquired', runId };\n const elapsed = this.#now() - start;\n if (elapsed >= this.#waitMs) {\n const heldBy = await this.#heldBy(scope);\n return { kind: 'deferred', heldBy };\n }\n await this.#sleep(pollMs);\n }\n }\n\n /**\n * Release the lock. Idempotent — releasing an already-released\n * lock is a no-op.\n *\n * @stable\n */\n async release(scope: SessionScope, runId: string): Promise<void> {\n if (this.#store !== null) {\n await this.#store.releaseLock(scope, runId);\n }\n const key = scopeKey(scope);\n const local = this.#localLocks.get(key);\n if (local !== undefined && local.runId === runId) {\n this.#localLocks.delete(key);\n }\n }\n\n async #tryAcquire(scope: SessionScope, runId: string): Promise<boolean> {\n const now = this.#now();\n if (this.#store !== null) {\n return this.#store.acquireLock(scope, runId, now, this.#maxAge);\n }\n // Fall back to per-process map.\n const key = scopeKey(scope);\n const existing = this.#localLocks.get(key);\n if (existing === undefined) {\n this.#localLocks.set(key, { runId, acquiredAt: now });\n return true;\n }\n if (existing.runId === runId) return true;\n if (this.#maxAge > 0 && now - existing.acquiredAt > this.#maxAge) {\n this.#localLocks.set(key, { runId, acquiredAt: now });\n return true;\n }\n return false;\n }\n\n async #heldBy(scope: SessionScope): Promise<string | null> {\n if (this.#store !== null) {\n const state = await this.#store.getState(scope);\n return state?.activeLockHeldBy ?? null;\n }\n const local = this.#localLocks.get(scopeKey(scope));\n return local?.runId ?? null;\n }\n}\n\nfunction scopeKey(scope: SessionScope): string {\n return `${scope.userId}|${scope.sessionId ?? ''}|${scope.agentId ?? ''}`;\n}\n"],"mappings":";AAgCA,MAAM,kBAAkB;;AAGxB,IAAa,cAAb,MAAyB;CACvB,CAASA;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CACT,CAASC;CAGT,CAASC,6BAAc,IAAI,KAAoD;CAE/E,YAAY,MAA0B;AACpC,QAAKN,QAAS,KAAK;AACnB,QAAKC,SAAU,KAAK,IAAI,GAAG,KAAK,OAAO;AACvC,QAAKC,SAAU,KAAK,IAAI,GAAG,KAAK,iBAAiB;AACjD,QAAKC,MAAO,KAAK,OAAO,KAAK;AAC7B,QAAKC,QACH,KAAK,WACH,OAAsB,IAAI,SAAS,YAAY,WAAW,WAAW,SAAS,GAAG,CAAC;AACtF,QAAKC,WACH,KAAK,mBACU;AAGb,UAAO,OAFG,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC,SAAS,GAAG,GAC5C,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC,SAAS,GAAG;;;;;;;;;CAW5D,MAAM,QAAQ,OAA2C;EACvD,MAAM,QAAQ,MAAKA,UAAW;EAC9B,MAAM,QAAQ,MAAKF,KAAM;EACzB,MAAM,SAAS,KAAK,IAAI,iBAAiB,KAAK,IAAI,IAAI,MAAKF,SAAU,KAAK,gBAAgB,CAAC;AAC3F,SAAO,MAAM;AAEX,OADW,MAAM,MAAKM,WAAY,OAAO,MAAM,CACvC,QAAO;IAAE,MAAM;IAAY;IAAO;AAE1C,OADgB,MAAKJ,KAAM,GAAG,SACf,MAAKF,OAElB,QAAO;IAAE,MAAM;IAAY,QADZ,MAAM,MAAKO,OAAQ,MAAM;IACL;AAErC,SAAM,MAAKJ,MAAO,OAAO;;;;;;;;;CAU7B,MAAM,QAAQ,OAAqB,OAA8B;AAC/D,MAAI,MAAKJ,UAAW,KAClB,OAAM,MAAKA,MAAO,YAAY,OAAO,MAAM;EAE7C,MAAM,MAAM,SAAS,MAAM;EAC3B,MAAM,QAAQ,MAAKM,WAAY,IAAI,IAAI;AACvC,MAAI,UAAU,UAAa,MAAM,UAAU,MACzC,OAAKA,WAAY,OAAO,IAAI;;CAIhC,OAAMC,WAAY,OAAqB,OAAiC;EACtE,MAAM,MAAM,MAAKJ,KAAM;AACvB,MAAI,MAAKH,UAAW,KAClB,QAAO,MAAKA,MAAO,YAAY,OAAO,OAAO,KAAK,MAAKE,OAAQ;EAGjE,MAAM,MAAM,SAAS,MAAM;EAC3B,MAAM,WAAW,MAAKI,WAAY,IAAI,IAAI;AAC1C,MAAI,aAAa,QAAW;AAC1B,SAAKA,WAAY,IAAI,KAAK;IAAE;IAAO,YAAY;IAAK,CAAC;AACrD,UAAO;;AAET,MAAI,SAAS,UAAU,MAAO,QAAO;AACrC,MAAI,MAAKJ,SAAU,KAAK,MAAM,SAAS,aAAa,MAAKA,QAAS;AAChE,SAAKI,WAAY,IAAI,KAAK;IAAE;IAAO,YAAY;IAAK,CAAC;AACrD,UAAO;;AAET,SAAO;;CAGT,OAAME,OAAQ,OAA6C;AACzD,MAAI,MAAKR,UAAW,KAElB,SADc,MAAM,MAAKA,MAAO,SAAS,MAAM,GACjC,oBAAoB;AAGpC,SADc,MAAKM,WAAY,IAAI,SAAS,MAAM,CAAC,EACrC,SAAS;;;AAI3B,SAAS,SAAS,OAA6B;AAC7C,QAAO,GAAG,MAAM,OAAO,GAAG,MAAM,aAAa,GAAG,GAAG,MAAM,WAAW"}
@@ -0,0 +1,2 @@
1
+ import "../internal/storage-adapter.js";
2
+ import { Message } from "@graphorin/core";