@graphorin/memory 0.6.1 → 0.7.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 (227) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +1 -1
  3. package/dist/conflict/index.js +0 -1
  4. package/dist/conflict/pipeline.js +0 -1
  5. package/dist/conflict/pipeline.js.map +1 -1
  6. package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
  7. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
  8. package/dist/conflict/types.d.ts +1 -1
  9. package/dist/conflict/types.js.map +1 -1
  10. package/dist/consolidator/index.js +2 -0
  11. package/dist/consolidator/index.js.map +1 -1
  12. package/dist/consolidator/lock.js +7 -1
  13. package/dist/consolidator/lock.js.map +1 -1
  14. package/dist/consolidator/phases/deep.js +19 -12
  15. package/dist/consolidator/phases/deep.js.map +1 -1
  16. package/dist/consolidator/phases/induce.d.ts.map +1 -1
  17. package/dist/consolidator/phases/induce.js +2 -3
  18. package/dist/consolidator/phases/induce.js.map +1 -1
  19. package/dist/consolidator/phases/reflect.js +35 -8
  20. package/dist/consolidator/phases/reflect.js.map +1 -1
  21. package/dist/consolidator/phases/standard.js +199 -28
  22. package/dist/consolidator/phases/standard.js.map +1 -1
  23. package/dist/consolidator/reconcile.js +16 -15
  24. package/dist/consolidator/reconcile.js.map +1 -1
  25. package/dist/consolidator/runtime.d.ts.map +1 -1
  26. package/dist/consolidator/runtime.js +129 -47
  27. package/dist/consolidator/runtime.js.map +1 -1
  28. package/dist/consolidator/scheduler.d.ts +17 -3
  29. package/dist/consolidator/scheduler.d.ts.map +1 -1
  30. package/dist/consolidator/scheduler.js.map +1 -1
  31. package/dist/consolidator/types.d.ts +30 -1
  32. package/dist/consolidator/types.d.ts.map +1 -1
  33. package/dist/consolidator/types.js +10 -0
  34. package/dist/consolidator/types.js.map +1 -1
  35. package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
  36. package/dist/context-engine/compaction/clear-tool-results.js +1 -1
  37. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
  38. package/dist/context-engine/compaction/compactor.d.ts +1 -0
  39. package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
  40. package/dist/context-engine/compaction/compactor.js +32 -8
  41. package/dist/context-engine/compaction/compactor.js.map +1 -1
  42. package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
  43. package/dist/context-engine/compaction/index.d.ts +1 -0
  44. package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
  45. package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
  46. package/dist/context-engine/compaction/templates/marker.js +34 -0
  47. package/dist/context-engine/compaction/templates/marker.js.map +1 -0
  48. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
  49. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
  50. package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
  51. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
  52. package/dist/context-engine/engine.d.ts +7 -176
  53. package/dist/context-engine/engine.d.ts.map +1 -1
  54. package/dist/context-engine/engine.js +0 -2
  55. package/dist/context-engine/engine.js.map +1 -1
  56. package/dist/context-engine/index.d.ts +8 -49
  57. package/dist/context-engine/io-types.d.ts +144 -0
  58. package/dist/context-engine/io-types.d.ts.map +1 -0
  59. package/dist/context-engine/locale-packs/types.d.ts +1 -1
  60. package/dist/context-engine/locale-packs/types.js.map +1 -1
  61. package/dist/context-engine/privacy-filter.d.ts +1 -2
  62. package/dist/context-engine/privacy-filter.d.ts.map +1 -1
  63. package/dist/context-engine/privacy-filter.js +1 -2
  64. package/dist/context-engine/privacy-filter.js.map +1 -1
  65. package/dist/errors/index.d.ts +2 -2
  66. package/dist/errors/index.js +2 -2
  67. package/dist/errors/index.js.map +1 -1
  68. package/dist/facade.d.ts +43 -52
  69. package/dist/facade.d.ts.map +1 -1
  70. package/dist/facade.js +5 -3
  71. package/dist/facade.js.map +1 -1
  72. package/dist/graph/entity-resolver.js +1 -1
  73. package/dist/graph/entity-resolver.js.map +1 -1
  74. package/dist/index.d.ts +16 -13
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2 -5
  77. package/dist/index.js.map +1 -1
  78. package/dist/internal/envelope.js +57 -0
  79. package/dist/internal/envelope.js.map +1 -0
  80. package/dist/internal/injection-heuristics.js +0 -0
  81. package/dist/internal/injection-heuristics.js.map +1 -1
  82. package/dist/internal/llm-json.js +40 -0
  83. package/dist/internal/llm-json.js.map +1 -0
  84. package/dist/internal/storage-adapter.d.ts +23 -10
  85. package/dist/internal/storage-adapter.d.ts.map +1 -1
  86. package/dist/memory-interface.d.ts +131 -0
  87. package/dist/memory-interface.d.ts.map +1 -0
  88. package/dist/package.js +1 -1
  89. package/dist/package.js.map +1 -1
  90. package/dist/search/iterative.d.ts.map +1 -1
  91. package/dist/search/iterative.js +2 -3
  92. package/dist/search/iterative.js.map +1 -1
  93. package/dist/search/query-transform.d.ts.map +1 -1
  94. package/dist/search/query-transform.js +2 -3
  95. package/dist/search/query-transform.js.map +1 -1
  96. package/dist/tiers/episodic-memory.js +3 -3
  97. package/dist/tiers/episodic-memory.js.map +1 -1
  98. package/dist/tiers/index.d.ts +2 -2
  99. package/dist/tiers/insight-memory.js +1 -1
  100. package/dist/tiers/insight-memory.js.map +1 -1
  101. package/dist/tiers/procedural-memory.d.ts +1 -1
  102. package/dist/tiers/procedural-memory.js +2 -2
  103. package/dist/tiers/procedural-memory.js.map +1 -1
  104. package/dist/tiers/semantic-memory.d.ts +58 -4
  105. package/dist/tiers/semantic-memory.d.ts.map +1 -1
  106. package/dist/tiers/semantic-memory.js +103 -20
  107. package/dist/tiers/semantic-memory.js.map +1 -1
  108. package/dist/tools/block-tools.d.ts +19 -57
  109. package/dist/tools/block-tools.d.ts.map +1 -1
  110. package/dist/tools/block-tools.js.map +1 -1
  111. package/dist/tools/fact-tools.d.ts +53 -214
  112. package/dist/tools/fact-tools.d.ts.map +1 -1
  113. package/dist/tools/fact-tools.js +3 -2
  114. package/dist/tools/fact-tools.js.map +1 -1
  115. package/dist/tools/recall-tools.d.ts +34 -175
  116. package/dist/tools/recall-tools.d.ts.map +1 -1
  117. package/dist/tools/recall-tools.js +5 -4
  118. package/dist/tools/recall-tools.js.map +1 -1
  119. package/dist/tools/runbook-tools.d.ts +15 -58
  120. package/dist/tools/runbook-tools.d.ts.map +1 -1
  121. package/dist/tools/runbook-tools.js.map +1 -1
  122. package/dist/tools/types.d.ts +1 -1
  123. package/package.json +18 -17
  124. package/src/conflict/index.ts +29 -0
  125. package/src/conflict/locale-packs/en.ts +142 -0
  126. package/src/conflict/locale-packs/index.ts +17 -0
  127. package/src/conflict/locale-packs/types.ts +138 -0
  128. package/src/conflict/pipeline.ts +371 -0
  129. package/src/conflict/stages/helpers.ts +65 -0
  130. package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
  131. package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
  132. package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
  133. package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
  134. package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
  135. package/src/conflict/types.ts +263 -0
  136. package/src/consolidator/budget.ts +312 -0
  137. package/src/consolidator/decay.ts +246 -0
  138. package/src/consolidator/dlq.ts +61 -0
  139. package/src/consolidator/errors.ts +87 -0
  140. package/src/consolidator/idempotency.ts +27 -0
  141. package/src/consolidator/index.ts +269 -0
  142. package/src/consolidator/lock.ts +141 -0
  143. package/src/consolidator/noise-filter.ts +235 -0
  144. package/src/consolidator/phases/deep.ts +308 -0
  145. package/src/consolidator/phases/induce.ts +423 -0
  146. package/src/consolidator/phases/learned-context.ts +233 -0
  147. package/src/consolidator/phases/light.ts +166 -0
  148. package/src/consolidator/phases/reflect.ts +504 -0
  149. package/src/consolidator/phases/standard.ts +1002 -0
  150. package/src/consolidator/reconcile.ts +273 -0
  151. package/src/consolidator/runtime.ts +1049 -0
  152. package/src/consolidator/scheduler.ts +182 -0
  153. package/src/consolidator/triggers.ts +134 -0
  154. package/src/consolidator/types.ts +622 -0
  155. package/src/context-engine/annotations.ts +161 -0
  156. package/src/context-engine/auto-recall.ts +92 -0
  157. package/src/context-engine/compaction/clear-tool-results.ts +232 -0
  158. package/src/context-engine/compaction/compactor.ts +403 -0
  159. package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
  160. package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
  161. package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
  162. package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
  163. package/src/context-engine/compaction/hooks/types.ts +68 -0
  164. package/src/context-engine/compaction/index.ts +60 -0
  165. package/src/context-engine/compaction/templates/marker.ts +33 -0
  166. package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
  167. package/src/context-engine/compaction/thresholds.ts +107 -0
  168. package/src/context-engine/compaction/types.ts +255 -0
  169. package/src/context-engine/engine.ts +970 -0
  170. package/src/context-engine/index.ts +118 -0
  171. package/src/context-engine/io-types.ts +162 -0
  172. package/src/context-engine/locale-packs/en.ts +107 -0
  173. package/src/context-engine/locale-packs/index.ts +19 -0
  174. package/src/context-engine/locale-packs/resolver.ts +197 -0
  175. package/src/context-engine/locale-packs/types.ts +177 -0
  176. package/src/context-engine/metadata.ts +114 -0
  177. package/src/context-engine/preambles/inbound-en.ts +19 -0
  178. package/src/context-engine/privacy-filter.ts +190 -0
  179. package/src/context-engine/templates/base-en.ts +19 -0
  180. package/src/context-engine/templates/composer.ts +91 -0
  181. package/src/context-engine/token-budget.ts +319 -0
  182. package/src/context-engine/token-counter.ts +157 -0
  183. package/src/errors/index.ts +273 -0
  184. package/src/facade.ts +750 -0
  185. package/src/graph/entity-resolver.ts +364 -0
  186. package/src/graph/index.ts +24 -0
  187. package/src/index.ts +259 -0
  188. package/src/internal/contextualize.ts +173 -0
  189. package/src/internal/embedder-binding.ts +53 -0
  190. package/src/internal/envelope.ts +70 -0
  191. package/src/internal/id.ts +23 -0
  192. package/src/internal/injection-heuristics.ts +0 -0
  193. package/src/internal/llm-json.ts +38 -0
  194. package/src/internal/spans.ts +35 -0
  195. package/src/internal/storage-adapter.ts +963 -0
  196. package/src/memory-interface.ts +158 -0
  197. package/src/migration/embedder-migration.ts +237 -0
  198. package/src/migration/index.ts +13 -0
  199. package/src/search/explain.ts +123 -0
  200. package/src/search/fit-weights.ts +109 -0
  201. package/src/search/graph-ppr.ts +38 -0
  202. package/src/search/index.ts +55 -0
  203. package/src/search/iterative.ts +658 -0
  204. package/src/search/query-transform.ts +285 -0
  205. package/src/search/rrf.ts +225 -0
  206. package/src/search/trust.ts +55 -0
  207. package/src/search/types.ts +50 -0
  208. package/src/tiers/episodic-memory.ts +463 -0
  209. package/src/tiers/index.ts +50 -0
  210. package/src/tiers/insight-memory.ts +208 -0
  211. package/src/tiers/procedural-memory.ts +482 -0
  212. package/src/tiers/semantic-memory.ts +1729 -0
  213. package/src/tiers/session-memory.ts +271 -0
  214. package/src/tiers/shared-memory.ts +62 -0
  215. package/src/tiers/working-memory.ts +368 -0
  216. package/src/tools/block-tools.ts +172 -0
  217. package/src/tools/fact-tools.ts +473 -0
  218. package/src/tools/index.ts +109 -0
  219. package/src/tools/recall-tools.ts +303 -0
  220. package/src/tools/runbook-tools.ts +107 -0
  221. package/src/tools/taint.ts +39 -0
  222. package/src/tools/types.ts +37 -0
  223. package/dist/conflict/locale-packs/index.js +0 -4
  224. package/dist/context-engine/compaction/index.js +0 -10
  225. package/dist/context-engine/index.d.ts.map +0 -1
  226. package/dist/context-engine/index.js +0 -25
  227. package/dist/context-engine/locale-packs/index.js +0 -4
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Bridge helpers - wire a {@link Consolidator} into the
3
+ * `@graphorin/triggers` Scheduler so trigger declarations fire on
4
+ * the documented schedule (DEC-150 - "same code path lib + server").
5
+ *
6
+ * The bridge is structural: any object that exposes the same shape
7
+ * as `@graphorin/triggers`'s `Scheduler` (a `register(declaration)`
8
+ * method) works. The structural import keeps `@graphorin/memory`
9
+ * dependency-free at the type level - the workspace consumer
10
+ * supplies the actual scheduler at runtime.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ import type { SessionScope } from '@graphorin/core';
16
+ import { type ParsedTrigger, parseTriggerSpec, reasonFromTrigger } from './triggers.js';
17
+ import type { ConsolidatorTriggerReason, ConsolidatorTriggerSpec, PhaseOutcome } from './types.js';
18
+
19
+ /**
20
+ * Catch-up policy applied when a trigger missed one or more fires
21
+ * while the scheduler was offline. Mirrors
22
+ * `@graphorin/triggers`'s `CatchupPolicy` - duplicated here so the
23
+ * memory package stays import-free at the type level.
24
+ *
25
+ * @stable
26
+ */
27
+ export type ConsolidatorCatchupPolicy = 'none' | 'last' | 'all';
28
+
29
+ /**
30
+ * Subset of the `Consolidator` runtime surface the bridge needs.
31
+ * Defined structurally - mirroring {@link SchedulerLike} - so this
32
+ * module never imports `./runtime.js` (which imports the bridge) and
33
+ * the consolidator module graph stays acyclic (issue #22). The full
34
+ * `Consolidator` is assignable as-is.
35
+ *
36
+ * @stable
37
+ */
38
+ export interface ConsolidatorLike {
39
+ trigger(reason: ConsolidatorTriggerReason, scope: SessionScope): Promise<PhaseOutcome | null>;
40
+ config(): { readonly triggers: ReadonlyArray<ConsolidatorTriggerSpec> };
41
+ }
42
+
43
+ /**
44
+ * Subset of the `@graphorin/triggers` Scheduler surface the bridge
45
+ * needs. Defined structurally so consumers can inject mocks in
46
+ * tests without taking the package dependency.
47
+ *
48
+ * @stable
49
+ */
50
+ export interface SchedulerLike {
51
+ register(declaration: TriggerDeclarationLike): Promise<unknown>;
52
+ unregister?(id: string): Promise<void>;
53
+ }
54
+
55
+ /**
56
+ * Subset of `@graphorin/triggers`'s `TriggerDeclaration` accepted
57
+ * by {@link SchedulerLike.register}. Field names mirror the
58
+ * upstream shape exactly so the structural type is interchangeable
59
+ * with the real export.
60
+ *
61
+ * @stable
62
+ */
63
+ export interface TriggerDeclarationLike {
64
+ readonly id: string;
65
+ readonly kind: 'cron' | 'interval' | 'idle' | 'event';
66
+ readonly spec: string;
67
+ readonly callback: (payload?: unknown) => void | Promise<void>;
68
+ readonly options: {
69
+ readonly catchupPolicy?: ConsolidatorCatchupPolicy;
70
+ readonly maxCatchupRuns?: number;
71
+ readonly catchupWindowMs?: number;
72
+ readonly tags?: ReadonlyArray<string>;
73
+ readonly acknowledgeLibMode?: boolean;
74
+ };
75
+ }
76
+
77
+ /** Options accepted by {@link registerConsolidatorTriggers}. */
78
+ export interface RegisterTriggersOptions {
79
+ /**
80
+ * Scope passed to `consolidator.trigger(...)` for every fire. The
81
+ * consolidator only operates on a single user/session at a time
82
+ * (DEC-005 single-user-per-process); multi-tenant deployments
83
+ * register one scheduler-trigger pair per scope.
84
+ */
85
+ readonly scope: SessionScope;
86
+ /**
87
+ * Catch-up policy applied to every registered trigger. Defaults
88
+ * to `'none'` per DEC-150 - safest for personal-assistant
89
+ * scenarios.
90
+ */
91
+ readonly catchupPolicy?: ConsolidatorCatchupPolicy;
92
+ /** Suppress the per-process library-mode WARN. Defaults to `true`. */
93
+ readonly acknowledgeLibMode?: boolean;
94
+ /** Optional id prefix - useful when multiple scopes share a scheduler. */
95
+ readonly idPrefix?: string;
96
+ /** Optional tags forwarded to the scheduler. */
97
+ readonly tags?: ReadonlyArray<string>;
98
+ /**
99
+ * Filter - only register the supplied subset of triggers. By
100
+ * default every parseable trigger declared on the consolidator is
101
+ * registered (turn / event triggers are skipped because the
102
+ * Scheduler has no way to fire them on its own).
103
+ */
104
+ readonly include?: ReadonlyArray<ConsolidatorTriggerSpec>;
105
+ }
106
+
107
+ /**
108
+ * Outcome of {@link registerConsolidatorTriggers}.
109
+ *
110
+ * @stable
111
+ */
112
+ export interface RegisterTriggersResult {
113
+ readonly registered: ReadonlyArray<{
114
+ readonly id: string;
115
+ readonly kind: ParsedTrigger['kind'];
116
+ readonly raw: string;
117
+ }>;
118
+ readonly skipped: ReadonlyArray<{
119
+ readonly raw: string;
120
+ readonly reason: 'unsupported-by-scheduler' | 'filtered-out';
121
+ }>;
122
+ }
123
+
124
+ /**
125
+ * Register every cron / idle / interval trigger declared on the
126
+ * supplied consolidator with the scheduler. Each trigger fires
127
+ * `consolidator.trigger(reason, scope)` so the lib-mode + server
128
+ * paths converge on the same handler.
129
+ *
130
+ * Turn triggers (`turn:N`) and event triggers (`event:NAME`) are
131
+ * skipped - the scheduler cannot count user turns autonomously, and
132
+ * event triggers fire from the consumer's emit path. The runtime
133
+ * caller is responsible for those (e.g. invoking
134
+ * `consolidator.trigger(...)` from the agent loop).
135
+ *
136
+ * @stable
137
+ */
138
+ export async function registerConsolidatorTriggers(
139
+ consolidator: ConsolidatorLike,
140
+ scheduler: SchedulerLike,
141
+ options: RegisterTriggersOptions,
142
+ ): Promise<RegisterTriggersResult> {
143
+ const config = consolidator.config();
144
+ const include = options.include ?? config.triggers;
145
+ const acknowledgeLibMode = options.acknowledgeLibMode ?? true;
146
+ const catchupPolicy: ConsolidatorCatchupPolicy = options.catchupPolicy ?? 'none';
147
+ const registered: Array<{ id: string; kind: ParsedTrigger['kind']; raw: string }> = [];
148
+ const skipped: Array<{ raw: string; reason: 'unsupported-by-scheduler' | 'filtered-out' }> = [];
149
+ const seenSet = new Set(include);
150
+ for (const spec of config.triggers) {
151
+ if (!seenSet.has(spec)) {
152
+ skipped.push({ raw: spec, reason: 'filtered-out' });
153
+ continue;
154
+ }
155
+ const parsed = parseTriggerSpec(spec);
156
+ if (parsed.kind === 'turn' || parsed.kind === 'event' || parsed.kind === 'budget') {
157
+ skipped.push({ raw: parsed.raw, reason: 'unsupported-by-scheduler' });
158
+ continue;
159
+ }
160
+ const id = `${options.idPrefix ?? 'consolidator'}:${parsed.raw}`;
161
+ const kind: 'cron' | 'idle' | 'interval' = parsed.kind === 'cron' ? 'cron' : 'idle';
162
+ const declaration: TriggerDeclarationLike = {
163
+ id,
164
+ kind,
165
+ spec: parsed.kind === 'cron' ? parsed.expression : String(parsed.idleMs),
166
+ async callback() {
167
+ await consolidator.trigger(reasonFromTrigger(parsed), options.scope);
168
+ },
169
+ options: {
170
+ catchupPolicy,
171
+ acknowledgeLibMode,
172
+ ...(options.tags !== undefined ? { tags: options.tags } : {}),
173
+ },
174
+ };
175
+ await scheduler.register(declaration);
176
+ registered.push({ id, kind: parsed.kind, raw: parsed.raw });
177
+ }
178
+ return Object.freeze({
179
+ registered: Object.freeze(registered),
180
+ skipped: Object.freeze(skipped),
181
+ });
182
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Trigger spec parser. Translates the human-friendly
3
+ * `'turn:N'` / `'idle:Xm'` / `'cron:EXPR'` / `'event:NAME'` /
4
+ * `'budget:N'` declarations into the runtime structure used by the
5
+ * consolidator.
6
+ *
7
+ * The parser is exhaustive: every shape recognised here must be
8
+ * matched in {@link parseTriggerSpec}; unrecognised shapes throw a
9
+ * `TypeError` so misconfigured triggers fail at registration time
10
+ * rather than silently never firing.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ import type { ConsolidatorTriggerReason, ConsolidatorTriggerSpec } from './types.js';
16
+
17
+ /**
18
+ * Parsed trigger declaration. The `kind` discriminator drives the
19
+ * runtime dispatch; the `value` carries the spec-specific argument
20
+ * already converted to a `number` (turns / millis / threshold) or a
21
+ * string (cron expr / event name).
22
+ *
23
+ * @stable
24
+ */
25
+ export type ParsedTrigger =
26
+ | { readonly kind: 'turn'; readonly count: number; readonly raw: string }
27
+ | { readonly kind: 'idle'; readonly idleMs: number; readonly raw: string }
28
+ | { readonly kind: 'cron'; readonly expression: string; readonly raw: string }
29
+ | { readonly kind: 'event'; readonly name: string; readonly raw: string }
30
+ | { readonly kind: 'budget'; readonly threshold: number; readonly raw: string };
31
+
32
+ const IDLE_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/u;
33
+
34
+ /** Parse a {@link ConsolidatorTriggerSpec} into a {@link ParsedTrigger}. */
35
+ export function parseTriggerSpec(spec: ConsolidatorTriggerSpec): ParsedTrigger {
36
+ if (typeof spec !== 'string' || spec.length === 0) {
37
+ throw new TypeError(
38
+ `[graphorin/memory] consolidator trigger spec must be a non-empty string, got ${typeof spec}`,
39
+ );
40
+ }
41
+ const colon = spec.indexOf(':');
42
+ if (colon < 0) {
43
+ throw new TypeError(
44
+ `[graphorin/memory] invalid trigger spec '${spec}' - expected 'kind:value'`,
45
+ );
46
+ }
47
+ const kind = spec.slice(0, colon);
48
+ const value = spec.slice(colon + 1);
49
+
50
+ switch (kind) {
51
+ case 'turn': {
52
+ const count = Number.parseInt(value, 10);
53
+ if (!Number.isFinite(count) || count <= 0) {
54
+ throw new TypeError(
55
+ `[graphorin/memory] invalid turn trigger '${spec}' - expected positive integer`,
56
+ );
57
+ }
58
+ return { kind: 'turn', count, raw: spec };
59
+ }
60
+ case 'idle': {
61
+ const match = IDLE_PATTERN.exec(value);
62
+ if (match === null) {
63
+ throw new TypeError(
64
+ `[graphorin/memory] invalid idle trigger '${spec}' - expected '5m' / '30s' / '1h'`,
65
+ );
66
+ }
67
+ const num = Number.parseFloat(match[1] ?? 'NaN');
68
+ const unit = match[2] ?? 'ms';
69
+ const idleMs = toMs(num, unit);
70
+ if (!Number.isFinite(idleMs) || idleMs <= 0) {
71
+ throw new TypeError(
72
+ `[graphorin/memory] invalid idle trigger '${spec}' - non-positive duration`,
73
+ );
74
+ }
75
+ return { kind: 'idle', idleMs, raw: spec };
76
+ }
77
+ case 'cron': {
78
+ if (value.trim().length === 0) {
79
+ throw new TypeError(`[graphorin/memory] invalid cron trigger '${spec}' - empty expression`);
80
+ }
81
+ return { kind: 'cron', expression: value, raw: spec };
82
+ }
83
+ case 'event': {
84
+ if (value.length === 0) {
85
+ throw new TypeError(
86
+ `[graphorin/memory] invalid event trigger '${spec}' - empty event name`,
87
+ );
88
+ }
89
+ return { kind: 'event', name: value, raw: spec };
90
+ }
91
+ case 'budget': {
92
+ const threshold = Number.parseFloat(value);
93
+ if (!Number.isFinite(threshold) || threshold <= 0 || threshold >= 1) {
94
+ throw new TypeError(
95
+ `[graphorin/memory] invalid budget trigger '${spec}' - expected fraction between 0 and 1`,
96
+ );
97
+ }
98
+ return { kind: 'budget', threshold, raw: spec };
99
+ }
100
+ default:
101
+ throw new TypeError(`[graphorin/memory] unknown consolidator trigger kind '${kind}'`);
102
+ }
103
+ }
104
+
105
+ /** Build a {@link ConsolidatorTriggerReason} from a parsed trigger. */
106
+ export function reasonFromTrigger(trigger: ParsedTrigger): ConsolidatorTriggerReason {
107
+ switch (trigger.kind) {
108
+ case 'turn':
109
+ return { kind: 'turn', value: trigger.count };
110
+ case 'idle':
111
+ return { kind: 'idle', value: trigger.raw };
112
+ case 'cron':
113
+ return { kind: 'cron', value: trigger.expression };
114
+ case 'event':
115
+ return { kind: 'event', value: trigger.name };
116
+ case 'budget':
117
+ return { kind: 'budget', value: trigger.threshold };
118
+ }
119
+ }
120
+
121
+ function toMs(value: number, unit: string): number {
122
+ switch (unit) {
123
+ case 'ms':
124
+ return value;
125
+ case 's':
126
+ return value * 1000;
127
+ case 'm':
128
+ return value * 60 * 1000;
129
+ case 'h':
130
+ return value * 60 * 60 * 1000;
131
+ default:
132
+ return Number.NaN;
133
+ }
134
+ }