@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,14 @@
1
+ import { Provider } from "@graphorin/core";
2
+
3
+ //#region src/internal/contextualize.d.ts
4
+
5
+ /**
6
+ * Contextual-retrieval mode. `'late-chunk'` is the offline default;
7
+ * `'llm'` is the opt-in, consolidator-only enrichment. P1-3.
8
+ *
9
+ * @stable
10
+ */
11
+ type ContextualRetrievalMode = 'off' | 'late-chunk' | 'llm';
12
+ //#endregion
13
+ export { ContextualRetrievalMode };
14
+ //# sourceMappingURL=contextualize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextualize.d.ts","names":[],"sources":["../../src/internal/contextualize.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;KA8BY,uBAAA"}
@@ -0,0 +1,110 @@
1
+ //#region src/internal/contextualize.ts
2
+ const SITUATING_CONTEXT_SYSTEM_PROMPT = [
3
+ "You are a situating-context assistant for a long-running memory store.",
4
+ "Given one memory and any structured hints, write a single short sentence (≤ 25 words) that situates it — the entities, timeframe, and topic it concerns — so it can be retrieved later by a vaguely-worded question.",
5
+ "Do not restate the memory verbatim and do not add facts that are not implied. Return only the sentence, no preamble."
6
+ ].join(" ");
7
+ /**
8
+ * Build the deterministic situating-context prefix from a candidate's
9
+ * structured signals. Returns `''` when nothing useful is available so
10
+ * the caller can short-circuit to the canonical text.
11
+ *
12
+ * @internal
13
+ */
14
+ function buildSituatingContext(input) {
15
+ const parts = [];
16
+ const subject = trimToUndefined(input.subject);
17
+ const predicate = trimToUndefined(input.predicate);
18
+ const object = trimToUndefined(input.object);
19
+ if (subject !== void 0 && predicate !== void 0 && object !== void 0) parts.push(`${subject} ${predicate} ${object}`);
20
+ else {
21
+ if (subject !== void 0) parts.push(subject);
22
+ if (object !== void 0) parts.push(object);
23
+ }
24
+ const validFrom = trimToUndefined(input.validFrom);
25
+ if (validFrom !== void 0) parts.push(`as of ${validFrom.length >= 10 ? validFrom.slice(0, 10) : validFrom}`);
26
+ const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
27
+ if (tags.length > 0) parts.push(`topics: ${tags.join(", ")}`);
28
+ if (parts.length === 0) return "";
29
+ return `[Context: ${parts.join("; ")}]`;
30
+ }
31
+ /**
32
+ * Late-chunking contextualizer (offline default). Prepends the
33
+ * deterministic situating context to the canonical text; a no-op when
34
+ * the candidate carries no structured signals. Pure — never calls a
35
+ * provider.
36
+ *
37
+ * @internal
38
+ */
39
+ function contextualize(input) {
40
+ const ctx = buildSituatingContext(input);
41
+ return ctx === "" ? input.text : `${ctx}\n${input.text}`;
42
+ }
43
+ const ZERO_USAGE = Object.freeze({
44
+ promptTokens: 0,
45
+ completionTokens: 0,
46
+ totalTokens: 0
47
+ });
48
+ /**
49
+ * LLM-context contextualizer (opt-in, consolidator-only). Spends one
50
+ * cheap provider call to write a situating prefix, then prepends it to
51
+ * the canonical text. Resilient by design: an empty completion or a
52
+ * provider error degrades to {@link contextualize} (deterministic
53
+ * late-chunk) so the enrichment can never wedge a write. Returns the
54
+ * usage so the caller can record it against the budget.
55
+ *
56
+ * @internal
57
+ */
58
+ async function contextualizeWithLlm(input, provider, opts = {}) {
59
+ const request = {
60
+ messages: [{
61
+ role: "user",
62
+ content: buildSituatingUserPrompt(input)
63
+ }],
64
+ systemMessage: SITUATING_CONTEXT_SYSTEM_PROMPT,
65
+ temperature: 0,
66
+ maxTokens: 256,
67
+ ...opts.signal !== void 0 ? { signal: opts.signal } : {}
68
+ };
69
+ try {
70
+ const response = await provider.generate(request);
71
+ const prefix = (response.text ?? "").trim();
72
+ if (prefix.length === 0) return {
73
+ indexText: contextualize(input),
74
+ usage: response.usage
75
+ };
76
+ return {
77
+ indexText: `${prefix}\n${input.text}`,
78
+ usage: response.usage
79
+ };
80
+ } catch {
81
+ return {
82
+ indexText: contextualize(input),
83
+ usage: ZERO_USAGE
84
+ };
85
+ }
86
+ }
87
+ function buildSituatingUserPrompt(input) {
88
+ const hints = [];
89
+ const subject = trimToUndefined(input.subject);
90
+ const predicate = trimToUndefined(input.predicate);
91
+ const object = trimToUndefined(input.object);
92
+ if (subject !== void 0) hints.push(`subject=${subject}`);
93
+ if (predicate !== void 0) hints.push(`predicate=${predicate}`);
94
+ if (object !== void 0) hints.push(`object=${object}`);
95
+ const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
96
+ if (tags.length > 0) hints.push(`tags=${tags.join(", ")}`);
97
+ const validFrom = trimToUndefined(input.validFrom);
98
+ if (validFrom !== void 0) hints.push(`validFrom=${validFrom}`);
99
+ const hintLine = hints.length > 0 ? `\nStructured hints: ${hints.join("; ")}` : "";
100
+ return `Memory: ${input.text}${hintLine}`;
101
+ }
102
+ function trimToUndefined(value) {
103
+ if (value === void 0) return void 0;
104
+ const trimmed = value.trim();
105
+ return trimmed.length === 0 ? void 0 : trimmed;
106
+ }
107
+
108
+ //#endregion
109
+ export { contextualize, contextualizeWithLlm };
110
+ //# sourceMappingURL=contextualize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextualize.js","names":["parts: string[]","ZERO_USAGE: Usage","request: ProviderRequest","hints: string[]"],"sources":["../../src/internal/contextualize.ts"],"sourcesContent":["/**\n * Contextual retrieval (P1-3). Before a memory is embedded / FTS-indexed\n * the framework can prepend a short *situating context* (entities,\n * timeframe, topics) so a terse fact like \"moved there in March\" stays\n * findable by \"Where did Anna relocate?\". This mirrors Anthropic's\n * Contextual Retrieval, applied to both the vector embedding *and* the\n * lexical index, while the canonical `text` shown to the user / audit\n * trail is left untouched.\n *\n * Three modes:\n * - `'off'` — index the bare canonical text (pre-P1-3 behaviour).\n * - `'late-chunk'` — the **offline default**: prepend a context derived\n * deterministically from the memory's own structured metadata. No LLM\n * call, so the local-first write path is unchanged in cost.\n * - `'llm'` — an opt-in, **consolidator-only** enrichment: one cheap\n * provider call writes a 1–2 sentence situating prefix. Confined to the\n * background consolidator (never the hot write path); a failure or empty\n * completion degrades to {@link contextualize} so a write never wedges.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, ProviderRequest, Usage } from '@graphorin/core';\n\n/**\n * Contextual-retrieval mode. `'late-chunk'` is the offline default;\n * `'llm'` is the opt-in, consolidator-only enrichment. P1-3.\n *\n * @stable\n */\nexport type ContextualRetrievalMode = 'off' | 'late-chunk' | 'llm';\n\n/**\n * Structural subset of a fact / candidate consumed by the\n * contextualizers. Only **author / extraction-supplied** signals are\n * used — never framework-defaulted fields (e.g. a `validFrom` defaulted\n * to write-time) and never opaque scope ids (random uuids would only\n * add noise to the embedding). A candidate with no structured signals\n * yields an empty context, so plain `remember({ text })` writes are\n * left byte-identical.\n *\n * @internal\n */\nexport interface ContextualizeInput {\n readonly text: string;\n readonly subject?: string;\n readonly predicate?: string;\n readonly object?: string;\n readonly tags?: ReadonlyArray<string>;\n /** Author-set validity start (ISO-8601). Framework-defaulted values are *not* passed. */\n readonly validFrom?: string;\n}\n\nconst SITUATING_CONTEXT_SYSTEM_PROMPT = [\n 'You are a situating-context assistant for a long-running memory store.',\n 'Given one memory and any structured hints, write a single short sentence (≤ 25 words) that situates it — the entities, timeframe, and topic it concerns — so it can be retrieved later by a vaguely-worded question.',\n 'Do not restate the memory verbatim and do not add facts that are not implied. Return only the sentence, no preamble.',\n].join(' ');\n\n/**\n * Build the deterministic situating-context prefix from a candidate's\n * structured signals. Returns `''` when nothing useful is available so\n * the caller can short-circuit to the canonical text.\n *\n * @internal\n */\nexport function buildSituatingContext(input: ContextualizeInput): string {\n const parts: string[] = [];\n const subject = trimToUndefined(input.subject);\n const predicate = trimToUndefined(input.predicate);\n const object = trimToUndefined(input.object);\n if (subject !== undefined && predicate !== undefined && object !== undefined) {\n parts.push(`${subject} ${predicate} ${object}`);\n } else {\n if (subject !== undefined) parts.push(subject);\n if (object !== undefined) parts.push(object);\n }\n const validFrom = trimToUndefined(input.validFrom);\n if (validFrom !== undefined) {\n parts.push(`as of ${validFrom.length >= 10 ? validFrom.slice(0, 10) : validFrom}`);\n }\n const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);\n if (tags.length > 0) {\n parts.push(`topics: ${tags.join(', ')}`);\n }\n if (parts.length === 0) return '';\n return `[Context: ${parts.join('; ')}]`;\n}\n\n/**\n * Late-chunking contextualizer (offline default). Prepends the\n * deterministic situating context to the canonical text; a no-op when\n * the candidate carries no structured signals. Pure — never calls a\n * provider.\n *\n * @internal\n */\nexport function contextualize(input: ContextualizeInput): string {\n const ctx = buildSituatingContext(input);\n return ctx === '' ? input.text : `${ctx}\\n${input.text}`;\n}\n\nconst ZERO_USAGE: Usage = Object.freeze({\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n});\n\n/** Result of the LLM-context enrichment — the index text plus call usage for budgeting. */\nexport interface ContextualizeLlmResult {\n readonly indexText: string;\n readonly usage: Usage;\n}\n\n/**\n * LLM-context contextualizer (opt-in, consolidator-only). Spends one\n * cheap provider call to write a situating prefix, then prepends it to\n * the canonical text. Resilient by design: an empty completion or a\n * provider error degrades to {@link contextualize} (deterministic\n * late-chunk) so the enrichment can never wedge a write. Returns the\n * usage so the caller can record it against the budget.\n *\n * @internal\n */\nexport async function contextualizeWithLlm(\n input: ContextualizeInput,\n provider: Provider,\n opts: { readonly signal?: AbortSignal } = {},\n): Promise<ContextualizeLlmResult> {\n const request: ProviderRequest = {\n messages: [{ role: 'user', content: buildSituatingUserPrompt(input) }],\n systemMessage: SITUATING_CONTEXT_SYSTEM_PROMPT,\n temperature: 0,\n // MCON-14: per-call output cap — a one-sentence situating prefix.\n maxTokens: 256,\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n };\n try {\n const response = await provider.generate(request);\n const prefix = (response.text ?? '').trim();\n if (prefix.length === 0) {\n return { indexText: contextualize(input), usage: response.usage };\n }\n return { indexText: `${prefix}\\n${input.text}`, usage: response.usage };\n } catch {\n // Contextualization is an enhancement, not a correctness requirement —\n // a provider failure must never wedge the underlying write. Fall back\n // to the deterministic late-chunk prefix at zero cost.\n return { indexText: contextualize(input), usage: ZERO_USAGE };\n }\n}\n\nfunction buildSituatingUserPrompt(input: ContextualizeInput): string {\n const hints: string[] = [];\n const subject = trimToUndefined(input.subject);\n const predicate = trimToUndefined(input.predicate);\n const object = trimToUndefined(input.object);\n if (subject !== undefined) hints.push(`subject=${subject}`);\n if (predicate !== undefined) hints.push(`predicate=${predicate}`);\n if (object !== undefined) hints.push(`object=${object}`);\n const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);\n if (tags.length > 0) hints.push(`tags=${tags.join(', ')}`);\n const validFrom = trimToUndefined(input.validFrom);\n if (validFrom !== undefined) hints.push(`validFrom=${validFrom}`);\n const hintLine = hints.length > 0 ? `\\nStructured hints: ${hints.join('; ')}` : '';\n return `Memory: ${input.text}${hintLine}`;\n}\n\nfunction trimToUndefined(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n return trimmed.length === 0 ? undefined : trimmed;\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ //#region src/internal/embedder-binding.ts
2
+ /**
3
+ * Infer the canonical adapter family from the embedder id. The
4
+ * canonical id format is `'<adapter>:<model>@<dim>'`; we slice the
5
+ * adapter name off the front so the registry can index by family
6
+ * without needing the embedder to expose an extra property.
7
+ *
8
+ * @internal
9
+ */
10
+ function inferAdapterKind(id) {
11
+ const colonIndex = id.indexOf(":");
12
+ if (colonIndex < 0) return "custom";
13
+ return id.slice(0, colonIndex);
14
+ }
15
+ /**
16
+ * Infer the model id portion of a canonical embedder id.
17
+ *
18
+ * @internal
19
+ */
20
+ function inferModelName(id) {
21
+ const colonIndex = id.indexOf(":");
22
+ const atIndex = id.lastIndexOf("@");
23
+ if (colonIndex < 0) return id;
24
+ if (atIndex < 0 || atIndex < colonIndex) return id.slice(colonIndex + 1);
25
+ return id.slice(colonIndex + 1, atIndex);
26
+ }
27
+ /**
28
+ * Register the supplied embedder with the storage layer's
29
+ * `embedding_meta` registry. Returns the canonical id surfaced to the
30
+ * memory writers. Idempotent — a registry that already knows the id
31
+ * with a matching `configHash` returns the existing row.
32
+ *
33
+ * @internal
34
+ */
35
+ function bindEmbedder(embedder, registry) {
36
+ const id = embedder.id();
37
+ registry.registerOrReturn({
38
+ id,
39
+ embedderKind: inferAdapterKind(id),
40
+ model: inferModelName(id),
41
+ dim: embedder.dim(),
42
+ configHash: embedder.configHash(),
43
+ distanceMetric: "cosine"
44
+ });
45
+ return id;
46
+ }
47
+
48
+ //#endregion
49
+ export { bindEmbedder };
50
+ //# sourceMappingURL=embedder-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder-binding.js","names":[],"sources":["../../src/internal/embedder-binding.ts"],"sourcesContent":["import type { EmbedderProvider } from '@graphorin/core';\nimport type { EmbeddingMetaRegistryLike } from './storage-adapter.js';\n\n/**\n * Infer the canonical adapter family from the embedder id. The\n * canonical id format is `'<adapter>:<model>@<dim>'`; we slice the\n * adapter name off the front so the registry can index by family\n * without needing the embedder to expose an extra property.\n *\n * @internal\n */\nexport function inferAdapterKind(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex < 0) return 'custom';\n return id.slice(0, colonIndex);\n}\n\n/**\n * Infer the model id portion of a canonical embedder id.\n *\n * @internal\n */\nexport function inferModelName(id: string): string {\n const colonIndex = id.indexOf(':');\n const atIndex = id.lastIndexOf('@');\n if (colonIndex < 0) return id;\n if (atIndex < 0 || atIndex < colonIndex) return id.slice(colonIndex + 1);\n return id.slice(colonIndex + 1, atIndex);\n}\n\n/**\n * Register the supplied embedder with the storage layer's\n * `embedding_meta` registry. Returns the canonical id surfaced to the\n * memory writers. Idempotent — a registry that already knows the id\n * with a matching `configHash` returns the existing row.\n *\n * @internal\n */\nexport function bindEmbedder(\n embedder: EmbedderProvider,\n registry: EmbeddingMetaRegistryLike,\n): string {\n const id = embedder.id();\n registry.registerOrReturn({\n id,\n embedderKind: inferAdapterKind(id),\n model: inferModelName(id),\n dim: embedder.dim(),\n configHash: embedder.configHash(),\n distanceMetric: 'cosine',\n });\n return id;\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,iBAAiB,IAAoB;CACnD,MAAM,aAAa,GAAG,QAAQ,IAAI;AAClC,KAAI,aAAa,EAAG,QAAO;AAC3B,QAAO,GAAG,MAAM,GAAG,WAAW;;;;;;;AAQhC,SAAgB,eAAe,IAAoB;CACjD,MAAM,aAAa,GAAG,QAAQ,IAAI;CAClC,MAAM,UAAU,GAAG,YAAY,IAAI;AACnC,KAAI,aAAa,EAAG,QAAO;AAC3B,KAAI,UAAU,KAAK,UAAU,WAAY,QAAO,GAAG,MAAM,aAAa,EAAE;AACxE,QAAO,GAAG,MAAM,aAAa,GAAG,QAAQ;;;;;;;;;;AAW1C,SAAgB,aACd,UACA,UACQ;CACR,MAAM,KAAK,SAAS,IAAI;AACxB,UAAS,iBAAiB;EACxB;EACA,cAAc,iBAAiB,GAAG;EAClC,OAAO,eAAe,GAAG;EACzB,KAAK,SAAS,KAAK;EACnB,YAAY,SAAS,YAAY;EACjC,gBAAgB;EACjB,CAAC;AACF,QAAO"}
@@ -0,0 +1,24 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ //#region src/internal/id.ts
4
+ /**
5
+ * Stable id generator. Combines `Date.now()` (millisecond resolution),
6
+ * a per-process monotonic counter (resists collision inside the same
7
+ * millisecond), and a 36-bit crypto-quality random suffix (ties the id
8
+ * to the host so different runs do not collide either).
9
+ *
10
+ * The format is intentionally NOT a UUID — UUIDs are 16 bytes once
11
+ * decoded; these ids are 18 chars on average which is what the
12
+ * `audit.db` hash chain (Phase 03b) was sized for.
13
+ *
14
+ * @stable
15
+ */
16
+ let counter = 0;
17
+ function newMemoryId(prefix) {
18
+ counter = counter + 1 & 65535;
19
+ return `${prefix}_${Date.now().toString(36)}${counter.toString(36).padStart(3, "0")}${randomBytes(4).toString("hex")}`;
20
+ }
21
+
22
+ //#endregion
23
+ export { newMemoryId };
24
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","names":[],"sources":["../../src/internal/id.ts"],"sourcesContent":["/**\n * Stable id generator. Combines `Date.now()` (millisecond resolution),\n * a per-process monotonic counter (resists collision inside the same\n * millisecond), and a 36-bit crypto-quality random suffix (ties the id\n * to the host so different runs do not collide either).\n *\n * The format is intentionally NOT a UUID — UUIDs are 16 bytes once\n * decoded; these ids are 18 chars on average which is what the\n * `audit.db` hash chain (Phase 03b) was sized for.\n *\n * @stable\n */\nimport { randomBytes } from 'node:crypto';\n\nlet counter = 0;\n\nexport function newMemoryId(prefix: string): string {\n counter = (counter + 1) & 0xffff;\n const time = Date.now().toString(36);\n const c = counter.toString(36).padStart(3, '0');\n const rand = randomBytes(4).toString('hex');\n return `${prefix}_${time}${c}${rand}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAI,UAAU;AAEd,SAAgB,YAAY,QAAwB;AAClD,WAAW,UAAU,IAAK;AAI1B,QAAO,GAAG,OAAO,GAHJ,KAAK,KAAK,CAAC,SAAS,GAAG,GAC1B,QAAQ,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAClC,YAAY,EAAE,CAAC,SAAS,MAAM"}
@@ -0,0 +1,64 @@
1
+ //#region src/internal/injection-heuristics.ts
2
+ /**
3
+ * Default English injection-marker rules. High-precision: each requires
4
+ * injection-characteristic structure so benign facts do not trip them.
5
+ *
6
+ * @stable
7
+ */
8
+ const DEFAULT_INJECTION_RULES = Object.freeze([
9
+ {
10
+ label: "ignore-previous-instructions",
11
+ pattern: /\bignore\s+(?:all\s+|any\s+)?(?:previous|prior|earlier|the\s+above|preceding)\s+(?:instructions?|prompts?|messages?|commands?|directions?|context|rules?)\b/i
12
+ },
13
+ {
14
+ label: "disregard-instructions",
15
+ pattern: /\bdisregard\s+(?:all\s+|any\s+|the\s+)?(?:previous|prior|earlier|above|preceding|prior\s+)?(?:instructions?|prompts?|rules?|guidelines?|guardrails?)\b/i
16
+ },
17
+ {
18
+ label: "override-directive",
19
+ pattern: /\boverride\s+(?:the\s+)?(?:system|safety|security|previous|prior|default)\b/i
20
+ },
21
+ {
22
+ label: "system-prompt-exfil",
23
+ pattern: /\b(?:reveal|show|print|repeat|leak|expose|dump)\b[^.?!\n]{0,40}\b(?:system\s+prompt|system\s+message|initial\s+instructions|hidden\s+(?:instructions|prompt))\b/i
24
+ },
25
+ {
26
+ label: "role-markup-injection",
27
+ pattern: /<\/?(?:system|im_start|im_end|tool_call|function_call)\b|\[\/?(?:INST|SYS)\]/i
28
+ },
29
+ {
30
+ label: "secrecy-directive",
31
+ pattern: /\b(?:do\s+not|don['’]?t|never)\s+(?:tell|inform|notify|alert|mention\s+(?:this\s+)?to|reveal\s+(?:this\s+)?to)\s+(?:the\s+)?(?:user|human|operator|owner)\b|\bwithout\s+(?:telling|informing|notifying|alerting)\s+(?:the\s+)?(?:user|human|operator)\b/i
32
+ },
33
+ {
34
+ label: "exfiltration-directive",
35
+ pattern: /\b(?:send|forward|email|transmit|upload|post|exfiltrate|leak)\b[^.?!\n]{0,60}\b(?:password|secret|api[\s_-]?keys?|credentials?|private\s+keys?|access\s+tokens?|auth\s+tokens?)\b/i
36
+ },
37
+ {
38
+ label: "persistent-override",
39
+ pattern: /\bfrom\s+now\s+on\b[^.?!\n]{0,40}\b(?:always|never|you\s+must|ignore|disregard|do\s+not)\b/i
40
+ }
41
+ ]);
42
+ /**
43
+ * Scan candidate memory text for obvious injection markers. Pure,
44
+ * offline, allocation-light. Returns the matched rule labels so callers
45
+ * can record *why* a candidate was quarantined.
46
+ *
47
+ * @stable
48
+ */
49
+ function detectMemoryInjection(text, options = {}) {
50
+ const rules = options.rules ?? DEFAULT_INJECTION_RULES;
51
+ const markers = [];
52
+ for (const rule of rules) if (rule.pattern.test(text)) markers.push(rule.label);
53
+ if (options.extraRules !== void 0) {
54
+ for (const rule of options.extraRules) if (!markers.includes(rule.label) && rule.pattern.test(text)) markers.push(rule.label);
55
+ }
56
+ return {
57
+ flagged: markers.length > 0,
58
+ markers: Object.freeze(markers)
59
+ };
60
+ }
61
+
62
+ //#endregion
63
+ export { detectMemoryInjection };
64
+ //# sourceMappingURL=injection-heuristics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injection-heuristics.js","names":["DEFAULT_INJECTION_RULES: ReadonlyArray<InjectionRule>","markers: string[]"],"sources":["../../src/internal/injection-heuristics.ts"],"sourcesContent":["/**\n * Offline, dependency-free heuristics that flag obvious memory-injection\n * markers in candidate memory text (P1-4). Long-lived memory is the prime\n * target for poisoning: an injected instruction planted today fires weeks\n * later when semantically recalled, defeating session-scoped prompt-injection\n * defenses (MINJA arXiv:2601.05504; MemoryGraft arXiv:2512.16962). Flagged\n * candidates are quarantined at write time so they never enter\n * action-driving recall until a human validates them.\n *\n * The detector is deliberately *high-precision*: every rule targets\n * injection-characteristic structure (imperative override of prior\n * instructions, role-markup smuggling, secrecy/exfiltration directives), not\n * lone common verbs, so benign first-party facts (\"I always run in the\n * mornings\", \"lives in Lisbon\") are not quarantined. It is locale-aware via\n * pluggable {@link InjectionRule} lists and performs no network or model\n * calls — pure regex over the candidate string.\n *\n * @packageDocumentation\n */\n\n/**\n * A single named injection-detection rule. The `label` is a stable,\n * audit-friendly identifier surfaced in {@link InjectionScan.markers};\n * the `pattern` MUST NOT carry the `g` flag (stateful `RegExp.test`).\n *\n * @stable\n */\nexport interface InjectionRule {\n readonly label: string;\n readonly pattern: RegExp;\n}\n\n/**\n * Result of scanning a candidate string. `flagged` is `true` when at\n * least one rule matched; `markers` lists the matched rule labels\n * (deduped, in rule order) for the audit trail.\n *\n * @stable\n */\nexport interface InjectionScan {\n readonly flagged: boolean;\n readonly markers: ReadonlyArray<string>;\n}\n\n/**\n * Options for {@link detectMemoryInjection}.\n *\n * @stable\n */\nexport interface InjectionHeuristicOptions {\n /**\n * Replace the default English rule set wholesale (advanced — for a\n * fully custom locale). When omitted, {@link DEFAULT_INJECTION_RULES}\n * is used.\n */\n readonly rules?: ReadonlyArray<InjectionRule>;\n /**\n * Additional locale-specific rules merged *after* the active rule set.\n * The common way to make the detector locale-aware without discarding\n * the English defaults.\n */\n readonly extraRules?: ReadonlyArray<InjectionRule>;\n}\n\n/**\n * Default English injection-marker rules. High-precision: each requires\n * injection-characteristic structure so benign facts do not trip them.\n *\n * @stable\n */\nexport const DEFAULT_INJECTION_RULES: ReadonlyArray<InjectionRule> = Object.freeze([\n {\n label: 'ignore-previous-instructions',\n pattern:\n /\\bignore\\s+(?:all\\s+|any\\s+)?(?:previous|prior|earlier|the\\s+above|preceding)\\s+(?:instructions?|prompts?|messages?|commands?|directions?|context|rules?)\\b/i,\n },\n {\n label: 'disregard-instructions',\n pattern:\n /\\bdisregard\\s+(?:all\\s+|any\\s+|the\\s+)?(?:previous|prior|earlier|above|preceding|prior\\s+)?(?:instructions?|prompts?|rules?|guidelines?|guardrails?)\\b/i,\n },\n {\n label: 'override-directive',\n pattern: /\\boverride\\s+(?:the\\s+)?(?:system|safety|security|previous|prior|default)\\b/i,\n },\n {\n label: 'system-prompt-exfil',\n pattern:\n /\\b(?:reveal|show|print|repeat|leak|expose|dump)\\b[^.?!\\n]{0,40}\\b(?:system\\s+prompt|system\\s+message|initial\\s+instructions|hidden\\s+(?:instructions|prompt))\\b/i,\n },\n {\n label: 'role-markup-injection',\n pattern: /<\\/?(?:system|im_start|im_end|tool_call|function_call)\\b|\\[\\/?(?:INST|SYS)\\]/i,\n },\n {\n label: 'secrecy-directive',\n pattern:\n /\\b(?:do\\s+not|don['’]?t|never)\\s+(?:tell|inform|notify|alert|mention\\s+(?:this\\s+)?to|reveal\\s+(?:this\\s+)?to)\\s+(?:the\\s+)?(?:user|human|operator|owner)\\b|\\bwithout\\s+(?:telling|informing|notifying|alerting)\\s+(?:the\\s+)?(?:user|human|operator)\\b/i,\n },\n {\n label: 'exfiltration-directive',\n pattern:\n /\\b(?:send|forward|email|transmit|upload|post|exfiltrate|leak)\\b[^.?!\\n]{0,60}\\b(?:password|secret|api[\\s_-]?keys?|credentials?|private\\s+keys?|access\\s+tokens?|auth\\s+tokens?)\\b/i,\n },\n {\n label: 'persistent-override',\n pattern:\n /\\bfrom\\s+now\\s+on\\b[^.?!\\n]{0,40}\\b(?:always|never|you\\s+must|ignore|disregard|do\\s+not)\\b/i,\n },\n]);\n\n/**\n * Scan candidate memory text for obvious injection markers. Pure,\n * offline, allocation-light. Returns the matched rule labels so callers\n * can record *why* a candidate was quarantined.\n *\n * @stable\n */\nexport function detectMemoryInjection(\n text: string,\n options: InjectionHeuristicOptions = {},\n): InjectionScan {\n const rules = options.rules ?? DEFAULT_INJECTION_RULES;\n const markers: string[] = [];\n for (const rule of rules) {\n if (rule.pattern.test(text)) markers.push(rule.label);\n }\n if (options.extraRules !== undefined) {\n for (const rule of options.extraRules) {\n if (!markers.includes(rule.label) && rule.pattern.test(text)) markers.push(rule.label);\n }\n }\n return { flagged: markers.length > 0, markers: Object.freeze(markers) };\n}\n"],"mappings":";;;;;;;AAsEA,MAAaA,0BAAwD,OAAO,OAAO;CACjF;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SAAS;EACV;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SAAS;EACV;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACF,CAAC;;;;;;;;AASF,SAAgB,sBACd,MACA,UAAqC,EAAE,EACxB;CACf,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAMC,UAAoB,EAAE;AAC5B,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,QAAQ,KAAK,KAAK,CAAE,SAAQ,KAAK,KAAK,MAAM;AAEvD,KAAI,QAAQ,eAAe,QACzB;OAAK,MAAM,QAAQ,QAAQ,WACzB,KAAI,CAAC,QAAQ,SAAS,KAAK,MAAM,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAE,SAAQ,KAAK,KAAK,MAAM;;AAG1F,QAAO;EAAE,SAAS,QAAQ,SAAS;EAAG,SAAS,OAAO,OAAO,QAAQ;EAAE"}
@@ -0,0 +1,35 @@
1
+ //#region src/internal/spans.ts
2
+ /**
3
+ * Sanitize a `SessionScope` for span attributes — the raw user id is
4
+ * surfaced as-is (it is a stable opaque identifier per DEC-005), but
5
+ * session and agent ids are emitted only when defined to avoid
6
+ * polluting the trace with `null` strings.
7
+ *
8
+ * @internal
9
+ */
10
+ function scopeAttrs(scope) {
11
+ const attrs = { "memory.scope.user_id": scope.userId };
12
+ if (scope.sessionId !== void 0) attrs["memory.scope.session_id"] = scope.sessionId;
13
+ if (scope.agentId !== void 0) attrs["memory.scope.agent_id"] = scope.agentId;
14
+ return Object.freeze(attrs);
15
+ }
16
+ /**
17
+ * Run an async function inside a `memory.*` span with the standard
18
+ * scope attributes already populated. Adds the span result count and
19
+ * status automatically.
20
+ *
21
+ * @internal
22
+ */
23
+ async function withMemorySpan(tracer, type, scope, extras, fn) {
24
+ return tracer.span({
25
+ type,
26
+ attrs: {
27
+ ...scopeAttrs(scope),
28
+ ...extras
29
+ }
30
+ }, fn);
31
+ }
32
+
33
+ //#endregion
34
+ export { withMemorySpan };
35
+ //# sourceMappingURL=spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.js","names":["attrs: Record<string, string>"],"sources":["../../src/internal/spans.ts"],"sourcesContent":["import type { SessionScope, SpanAttributes, SpanType, Tracer } from '@graphorin/core';\n\n/**\n * Sanitize a `SessionScope` for span attributes — the raw user id is\n * surfaced as-is (it is a stable opaque identifier per DEC-005), but\n * session and agent ids are emitted only when defined to avoid\n * polluting the trace with `null` strings.\n *\n * @internal\n */\nexport function scopeAttrs(scope: SessionScope): SpanAttributes {\n const attrs: Record<string, string> = {\n 'memory.scope.user_id': scope.userId,\n };\n if (scope.sessionId !== undefined) attrs['memory.scope.session_id'] = scope.sessionId;\n if (scope.agentId !== undefined) attrs['memory.scope.agent_id'] = scope.agentId;\n return Object.freeze(attrs);\n}\n\n/**\n * Run an async function inside a `memory.*` span with the standard\n * scope attributes already populated. Adds the span result count and\n * status automatically.\n *\n * @internal\n */\nexport async function withMemorySpan<T extends SpanType, R>(\n tracer: Tracer,\n type: T,\n scope: SessionScope,\n extras: SpanAttributes,\n fn: (span: import('@graphorin/core').AISpan<T>) => Promise<R>,\n): Promise<R> {\n return tracer.span({ type, attrs: { ...scopeAttrs(scope), ...extras } }, fn);\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,WAAW,OAAqC;CAC9D,MAAMA,QAAgC,EACpC,wBAAwB,MAAM,QAC/B;AACD,KAAI,MAAM,cAAc,OAAW,OAAM,6BAA6B,MAAM;AAC5E,KAAI,MAAM,YAAY,OAAW,OAAM,2BAA2B,MAAM;AACxE,QAAO,OAAO,OAAO,MAAM;;;;;;;;;AAU7B,eAAsB,eACpB,QACA,MACA,OACA,QACA,IACY;AACZ,QAAO,OAAO,KAAK;EAAE;EAAM,OAAO;GAAE,GAAG,WAAW,MAAM;GAAE,GAAG;GAAQ;EAAE,EAAE,GAAG"}