@psiclawops/hypermem 0.5.0 → 0.5.2

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 (163) hide show
  1. package/ARCHITECTURE.md +12 -3
  2. package/README.md +30 -6
  3. package/bin/hypermem-status.mjs +166 -0
  4. package/dist/background-indexer.d.ts +132 -0
  5. package/dist/background-indexer.d.ts.map +1 -0
  6. package/dist/background-indexer.js +1044 -0
  7. package/dist/cache.d.ts +110 -0
  8. package/dist/cache.d.ts.map +1 -0
  9. package/dist/cache.js +495 -0
  10. package/dist/compaction-fence.d.ts +89 -0
  11. package/dist/compaction-fence.d.ts.map +1 -0
  12. package/dist/compaction-fence.js +153 -0
  13. package/dist/compositor.d.ts +226 -0
  14. package/dist/compositor.d.ts.map +1 -0
  15. package/dist/compositor.js +2558 -0
  16. package/dist/content-type-classifier.d.ts +41 -0
  17. package/dist/content-type-classifier.d.ts.map +1 -0
  18. package/dist/content-type-classifier.js +181 -0
  19. package/dist/cross-agent.d.ts +62 -0
  20. package/dist/cross-agent.d.ts.map +1 -0
  21. package/dist/cross-agent.js +259 -0
  22. package/dist/db.d.ts +131 -0
  23. package/dist/db.d.ts.map +1 -0
  24. package/dist/db.js +402 -0
  25. package/dist/desired-state-store.d.ts +100 -0
  26. package/dist/desired-state-store.d.ts.map +1 -0
  27. package/dist/desired-state-store.js +222 -0
  28. package/dist/doc-chunk-store.d.ts +140 -0
  29. package/dist/doc-chunk-store.d.ts.map +1 -0
  30. package/dist/doc-chunk-store.js +391 -0
  31. package/dist/doc-chunker.d.ts +99 -0
  32. package/dist/doc-chunker.d.ts.map +1 -0
  33. package/dist/doc-chunker.js +324 -0
  34. package/dist/dreaming-promoter.d.ts +86 -0
  35. package/dist/dreaming-promoter.d.ts.map +1 -0
  36. package/dist/dreaming-promoter.js +381 -0
  37. package/dist/episode-store.d.ts +49 -0
  38. package/dist/episode-store.d.ts.map +1 -0
  39. package/dist/episode-store.js +135 -0
  40. package/dist/fact-store.d.ts +75 -0
  41. package/dist/fact-store.d.ts.map +1 -0
  42. package/dist/fact-store.js +236 -0
  43. package/dist/fleet-store.d.ts +144 -0
  44. package/dist/fleet-store.d.ts.map +1 -0
  45. package/dist/fleet-store.js +276 -0
  46. package/dist/fos-mod.d.ts +178 -0
  47. package/dist/fos-mod.d.ts.map +1 -0
  48. package/dist/fos-mod.js +416 -0
  49. package/dist/hybrid-retrieval.d.ts +64 -0
  50. package/dist/hybrid-retrieval.d.ts.map +1 -0
  51. package/dist/hybrid-retrieval.js +344 -0
  52. package/dist/image-eviction.d.ts +49 -0
  53. package/dist/image-eviction.d.ts.map +1 -0
  54. package/dist/image-eviction.js +251 -0
  55. package/dist/index.d.ts +650 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +1072 -0
  58. package/dist/keystone-scorer.d.ts +51 -0
  59. package/dist/keystone-scorer.d.ts.map +1 -0
  60. package/dist/keystone-scorer.js +52 -0
  61. package/dist/knowledge-graph.d.ts +110 -0
  62. package/dist/knowledge-graph.d.ts.map +1 -0
  63. package/dist/knowledge-graph.js +305 -0
  64. package/dist/knowledge-lint.d.ts +29 -0
  65. package/dist/knowledge-lint.d.ts.map +1 -0
  66. package/dist/knowledge-lint.js +116 -0
  67. package/dist/knowledge-store.d.ts +72 -0
  68. package/dist/knowledge-store.d.ts.map +1 -0
  69. package/dist/knowledge-store.js +247 -0
  70. package/dist/library-schema.d.ts +22 -0
  71. package/dist/library-schema.d.ts.map +1 -0
  72. package/dist/library-schema.js +1038 -0
  73. package/dist/message-store.d.ts +89 -0
  74. package/dist/message-store.d.ts.map +1 -0
  75. package/dist/message-store.js +323 -0
  76. package/dist/metrics-dashboard.d.ts +114 -0
  77. package/dist/metrics-dashboard.d.ts.map +1 -0
  78. package/dist/metrics-dashboard.js +260 -0
  79. package/dist/obsidian-exporter.d.ts +57 -0
  80. package/dist/obsidian-exporter.d.ts.map +1 -0
  81. package/dist/obsidian-exporter.js +274 -0
  82. package/dist/obsidian-watcher.d.ts +147 -0
  83. package/dist/obsidian-watcher.d.ts.map +1 -0
  84. package/dist/obsidian-watcher.js +403 -0
  85. package/dist/open-domain.d.ts +46 -0
  86. package/dist/open-domain.d.ts.map +1 -0
  87. package/dist/open-domain.js +125 -0
  88. package/dist/preference-store.d.ts +54 -0
  89. package/dist/preference-store.d.ts.map +1 -0
  90. package/dist/preference-store.js +109 -0
  91. package/dist/preservation-gate.d.ts +82 -0
  92. package/dist/preservation-gate.d.ts.map +1 -0
  93. package/dist/preservation-gate.js +150 -0
  94. package/dist/proactive-pass.d.ts +63 -0
  95. package/dist/proactive-pass.d.ts.map +1 -0
  96. package/dist/proactive-pass.js +239 -0
  97. package/dist/profiles.d.ts +44 -0
  98. package/dist/profiles.d.ts.map +1 -0
  99. package/dist/profiles.js +227 -0
  100. package/dist/provider-translator.d.ts +50 -0
  101. package/dist/provider-translator.d.ts.map +1 -0
  102. package/dist/provider-translator.js +403 -0
  103. package/dist/rate-limiter.d.ts +76 -0
  104. package/dist/rate-limiter.d.ts.map +1 -0
  105. package/dist/rate-limiter.js +179 -0
  106. package/dist/repair-tool-pairs.d.ts +38 -0
  107. package/dist/repair-tool-pairs.d.ts.map +1 -0
  108. package/dist/repair-tool-pairs.js +138 -0
  109. package/dist/retrieval-policy.d.ts +51 -0
  110. package/dist/retrieval-policy.d.ts.map +1 -0
  111. package/dist/retrieval-policy.js +77 -0
  112. package/dist/schema.d.ts +15 -0
  113. package/dist/schema.d.ts.map +1 -0
  114. package/dist/schema.js +229 -0
  115. package/dist/secret-scanner.d.ts +51 -0
  116. package/dist/secret-scanner.d.ts.map +1 -0
  117. package/dist/secret-scanner.js +248 -0
  118. package/dist/seed.d.ts +108 -0
  119. package/dist/seed.d.ts.map +1 -0
  120. package/dist/seed.js +177 -0
  121. package/dist/session-flusher.d.ts +53 -0
  122. package/dist/session-flusher.d.ts.map +1 -0
  123. package/dist/session-flusher.js +69 -0
  124. package/dist/session-topic-map.d.ts +41 -0
  125. package/dist/session-topic-map.d.ts.map +1 -0
  126. package/dist/session-topic-map.js +77 -0
  127. package/dist/spawn-context.d.ts +54 -0
  128. package/dist/spawn-context.d.ts.map +1 -0
  129. package/dist/spawn-context.js +159 -0
  130. package/dist/system-store.d.ts +73 -0
  131. package/dist/system-store.d.ts.map +1 -0
  132. package/dist/system-store.js +182 -0
  133. package/dist/temporal-store.d.ts +80 -0
  134. package/dist/temporal-store.d.ts.map +1 -0
  135. package/dist/temporal-store.js +149 -0
  136. package/dist/topic-detector.d.ts +35 -0
  137. package/dist/topic-detector.d.ts.map +1 -0
  138. package/dist/topic-detector.js +249 -0
  139. package/dist/topic-store.d.ts +45 -0
  140. package/dist/topic-store.d.ts.map +1 -0
  141. package/dist/topic-store.js +136 -0
  142. package/dist/topic-synthesizer.d.ts +51 -0
  143. package/dist/topic-synthesizer.d.ts.map +1 -0
  144. package/dist/topic-synthesizer.js +315 -0
  145. package/dist/trigger-registry.d.ts +63 -0
  146. package/dist/trigger-registry.d.ts.map +1 -0
  147. package/dist/trigger-registry.js +163 -0
  148. package/dist/types.d.ts +537 -0
  149. package/dist/types.d.ts.map +1 -0
  150. package/dist/types.js +9 -0
  151. package/dist/vector-store.d.ts +170 -0
  152. package/dist/vector-store.d.ts.map +1 -0
  153. package/dist/vector-store.js +677 -0
  154. package/dist/version.d.ts +34 -0
  155. package/dist/version.d.ts.map +1 -0
  156. package/dist/version.js +34 -0
  157. package/dist/wiki-page-emitter.d.ts +65 -0
  158. package/dist/wiki-page-emitter.d.ts.map +1 -0
  159. package/dist/wiki-page-emitter.js +258 -0
  160. package/dist/work-store.d.ts +112 -0
  161. package/dist/work-store.d.ts.map +1 -0
  162. package/dist/work-store.js +273 -0
  163. package/package.json +4 -1
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Topic Synthesizer
3
+ *
4
+ * Synthesizes compiled knowledge pages (wiki-style) from stale topics.
5
+ * Heuristic-only: no LLM calls. Uses content-type classifier + keystone scoring.
6
+ *
7
+ * Architecture: Karpathy LLM Wiki Pattern adapted for hypermem.
8
+ * Raw sources (messages.db) → Wiki (knowledge table) → Compositor (compose-time)
9
+ */
10
+ import { classifyContentType } from './content-type-classifier.js';
11
+ import { KnowledgeStore } from './knowledge-store.js';
12
+ // ─── Configuration ──────────────────────────────────────────────
13
+ const SYNTHESIS_STALE_MINUTES = 30;
14
+ const SYNTHESIS_MIN_MESSAGES = 5;
15
+ const SYNTHESIS_REGROWTH_THRESHOLD = 5;
16
+ const SYNTHESIS_MAX_SUMMARY_CHARS = 800;
17
+ const SYNTHESIS_MAX_DECISIONS = 10;
18
+ const SYNTHESIS_MAX_QUESTIONS = 5;
19
+ const LINT_FREQUENCY = 10;
20
+ const LINT_STALE_DAYS = 7;
21
+ // Export so tests can reference them
22
+ export { SYNTHESIS_STALE_MINUTES, SYNTHESIS_MIN_MESSAGES, SYNTHESIS_REGROWTH_THRESHOLD, SYNTHESIS_MAX_SUMMARY_CHARS, SYNTHESIS_MAX_DECISIONS, SYNTHESIS_MAX_QUESTIONS, LINT_FREQUENCY, LINT_STALE_DAYS, };
23
+ // ─── Helpers ────────────────────────────────────────────────────
24
+ /**
25
+ * Score a message for keystone quality.
26
+ * Heuristic: count references (file paths, agent mentions, quoted content, backticks).
27
+ */
28
+ function keystoneScore(msg) {
29
+ const text = msg.text_content || '';
30
+ let score = 0;
31
+ // File path references
32
+ const pathMatches = text.match(/\/[\w./\-]+/g) || [];
33
+ score += pathMatches.length * 0.3;
34
+ // Backtick code references (inline or block)
35
+ const backtickMatches = text.match(/`[^`]+`/g) || [];
36
+ score += backtickMatches.length * 0.2;
37
+ // Agent mentions (known patterns)
38
+ const agentMentions = text.match(/\b(forge|compass|clarity|sentinel|vanguard|anvil|pylon|vigil|bastion|relay|crucible)\b/gi) || [];
39
+ score += agentMentions.length * 0.25;
40
+ // Quoted content
41
+ const quotedMatches = text.match(/"[^"]{10,}"/g) || [];
42
+ score += quotedMatches.length * 0.15;
43
+ // Decision/spec content type boosts score
44
+ const classification = classifyContentType(text);
45
+ if (classification.type === 'decision')
46
+ score += 1.0;
47
+ else if (classification.type === 'spec')
48
+ score += 0.5;
49
+ else if (classification.type === 'preference')
50
+ score += 0.3;
51
+ // Length bonus (up to 0.5 for ~500 chars)
52
+ score += Math.min(text.length / 1000, 0.5);
53
+ return score;
54
+ }
55
+ /**
56
+ * Extract file artifact paths from a message's tool_calls JSON.
57
+ */
58
+ function extractArtifacts(msg) {
59
+ if (!msg.tool_calls)
60
+ return [];
61
+ const artifacts = [];
62
+ try {
63
+ const calls = JSON.parse(msg.tool_calls);
64
+ if (!Array.isArray(calls))
65
+ return [];
66
+ for (const call of calls) {
67
+ const args = call.input || call.function?.arguments || call.arguments || {};
68
+ let parsed = args;
69
+ if (typeof parsed === 'string') {
70
+ try {
71
+ parsed = JSON.parse(parsed);
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ }
77
+ if (typeof parsed !== 'object' || !parsed)
78
+ continue;
79
+ for (const key of ['path', 'file', 'filePath', 'file_path']) {
80
+ const val = parsed[key];
81
+ if (typeof val === 'string' && val.startsWith('/')) {
82
+ artifacts.push(val);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ // Malformed JSON — skip
89
+ }
90
+ return artifacts;
91
+ }
92
+ /**
93
+ * Truncate text to maxChars with head+tail preservation.
94
+ * head = 60% of budget, tail = 40%.
95
+ */
96
+ function truncateHeadTail(text, maxChars) {
97
+ if (text.length <= maxChars)
98
+ return text;
99
+ const headLen = Math.floor(maxChars * 0.6);
100
+ const tailLen = maxChars - headLen - 5; // 5 for " ... "
101
+ return text.slice(0, headLen) + ' ... ' + text.slice(text.length - tailLen);
102
+ }
103
+ /**
104
+ * Parse the message_count stored in a knowledge source_ref or metadata.
105
+ * source_ref format: "topic:<id>" — but we need to find message_count
106
+ * from when the synthesis was stored. We store it in the content itself
107
+ * as a comment, or we can read from the knowledge row's source_ref.
108
+ *
109
+ * Strategy: embed last message count in sourceRef as "topic:<id>:mc:<count>"
110
+ */
111
+ function parseStoredMessageCount(sourceRef) {
112
+ if (!sourceRef)
113
+ return 0;
114
+ const match = sourceRef.match(/:mc:(\d+)$/);
115
+ return match ? parseInt(match[1], 10) : 0;
116
+ }
117
+ // ─── TopicSynthesizer ───────────────────────────────────────────
118
+ export class TopicSynthesizer {
119
+ libraryDb;
120
+ getMessageDb;
121
+ config;
122
+ effectiveConfig;
123
+ constructor(libraryDb, getMessageDb, config) {
124
+ this.libraryDb = libraryDb;
125
+ this.getMessageDb = getMessageDb;
126
+ this.config = config;
127
+ this.effectiveConfig = {
128
+ SYNTHESIS_STALE_MINUTES: config?.SYNTHESIS_STALE_MINUTES ?? SYNTHESIS_STALE_MINUTES,
129
+ SYNTHESIS_MIN_MESSAGES: config?.SYNTHESIS_MIN_MESSAGES ?? SYNTHESIS_MIN_MESSAGES,
130
+ SYNTHESIS_REGROWTH_THRESHOLD: config?.SYNTHESIS_REGROWTH_THRESHOLD ?? SYNTHESIS_REGROWTH_THRESHOLD,
131
+ SYNTHESIS_MAX_SUMMARY_CHARS: config?.SYNTHESIS_MAX_SUMMARY_CHARS ?? SYNTHESIS_MAX_SUMMARY_CHARS,
132
+ SYNTHESIS_MAX_DECISIONS: config?.SYNTHESIS_MAX_DECISIONS ?? SYNTHESIS_MAX_DECISIONS,
133
+ SYNTHESIS_MAX_QUESTIONS: config?.SYNTHESIS_MAX_QUESTIONS ?? SYNTHESIS_MAX_QUESTIONS,
134
+ LINT_FREQUENCY: config?.LINT_FREQUENCY ?? LINT_FREQUENCY,
135
+ LINT_STALE_DAYS: config?.LINT_STALE_DAYS ?? LINT_STALE_DAYS,
136
+ };
137
+ }
138
+ /**
139
+ * Run one synthesis pass for an agent.
140
+ * Finds stale topics, synthesizes wiki pages, writes to knowledge table.
141
+ */
142
+ tick(agentId) {
143
+ const result = {
144
+ topicsSynthesized: 0,
145
+ topicsSkipped: 0,
146
+ knowledgeEntriesWritten: 0,
147
+ };
148
+ const cfg = this.effectiveConfig;
149
+ const staleThresholdMinutes = cfg.SYNTHESIS_STALE_MINUTES;
150
+ // Query stale topics for this agent
151
+ // "Stale" = updated_at older than SYNTHESIS_STALE_MINUTES ago
152
+ let staleTopics;
153
+ try {
154
+ staleTopics = this.libraryDb.prepare(`
155
+ SELECT * FROM topics
156
+ WHERE agent_id = ?
157
+ AND message_count >= ?
158
+ AND updated_at < datetime('now', '-${staleThresholdMinutes} minutes')
159
+ ORDER BY updated_at ASC
160
+ `).all(agentId, cfg.SYNTHESIS_MIN_MESSAGES);
161
+ }
162
+ catch {
163
+ return result;
164
+ }
165
+ if (staleTopics.length === 0)
166
+ return result;
167
+ const knowledgeStore = new KnowledgeStore(this.libraryDb);
168
+ for (const topic of staleTopics) {
169
+ // Check if existing synthesis exists
170
+ const existing = knowledgeStore.get(agentId, 'topic-synthesis', topic.name);
171
+ if (existing) {
172
+ // Only re-synthesize if message_count has grown by >= threshold
173
+ const storedMc = parseStoredMessageCount(existing.sourceRef);
174
+ const growth = topic.message_count - storedMc;
175
+ if (growth < cfg.SYNTHESIS_REGROWTH_THRESHOLD) {
176
+ result.topicsSkipped++;
177
+ continue;
178
+ }
179
+ }
180
+ // Get messages for this topic from per-agent messages.db
181
+ const messageDb = this.getMessageDb(agentId);
182
+ if (!messageDb) {
183
+ result.topicsSkipped++;
184
+ continue;
185
+ }
186
+ let messages;
187
+ try {
188
+ messages = messageDb.prepare(`
189
+ SELECT * FROM messages WHERE topic_id = ? ORDER BY created_at ASC
190
+ `).all(String(topic.id));
191
+ }
192
+ catch {
193
+ result.topicsSkipped++;
194
+ continue;
195
+ }
196
+ if (messages.length === 0) {
197
+ result.topicsSkipped++;
198
+ continue;
199
+ }
200
+ // Build synthesis
201
+ const content = this.synthesizeTopic(topic, messages, cfg);
202
+ // Upsert into knowledge table
203
+ const sourceRef = `topic:${topic.id}:mc:${topic.message_count}`;
204
+ knowledgeStore.upsert(agentId, 'topic-synthesis', topic.name, content, {
205
+ sourceType: 'synthesizer',
206
+ sourceRef,
207
+ });
208
+ result.topicsSynthesized++;
209
+ result.knowledgeEntriesWritten++;
210
+ }
211
+ return result;
212
+ }
213
+ /**
214
+ * Synthesize a wiki page for a topic from its messages.
215
+ */
216
+ synthesizeTopic(topic, messages, cfg) {
217
+ // Classify + score all messages
218
+ const scored = messages.map(msg => ({
219
+ msg,
220
+ classification: classifyContentType(msg.text_content || ''),
221
+ score: keystoneScore(msg),
222
+ }));
223
+ // Extract decisions: classified as decision with confidence >= 0.7, top N
224
+ const decisions = scored
225
+ .filter(m => m.classification.type === 'decision' && m.classification.confidence >= 0.7)
226
+ .sort((a, b) => b.score - a.score)
227
+ .slice(0, cfg.SYNTHESIS_MAX_DECISIONS);
228
+ // Extract open questions: classified as discussion + ends with ?, no decision follow-up within 5 msgs
229
+ const openQuestions = [];
230
+ for (let i = 0; i < scored.length; i++) {
231
+ const item = scored[i];
232
+ const text = item.msg.text_content || '';
233
+ // Question detection: ends with ? OR is a discussion-type with question keywords
234
+ const isQuestion = /\?\s*$/.test(text.trim()) ||
235
+ (item.classification.type === 'discussion' && /\b(why|how|what|when|where|should|could|would|can|is there)\b/i.test(text));
236
+ if (!isQuestion)
237
+ continue;
238
+ // Check if there's a decision-type response within next 5 messages
239
+ const followup = scored.slice(i + 1, i + 6);
240
+ const hasDecisionFollowup = followup.some(f => f.classification.type === 'decision');
241
+ if (!hasDecisionFollowup) {
242
+ openQuestions.push(item);
243
+ if (openQuestions.length >= cfg.SYNTHESIS_MAX_QUESTIONS)
244
+ break;
245
+ }
246
+ }
247
+ // Extract artifacts from tool_calls
248
+ const allArtifacts = new Set();
249
+ for (const { msg } of scored) {
250
+ for (const artifact of extractArtifacts(msg)) {
251
+ allArtifacts.add(artifact);
252
+ }
253
+ }
254
+ // Extract participants: unique agent_ids
255
+ const participants = [...new Set(messages.map(m => m.agent_id).filter(Boolean))];
256
+ // Build summary: top-3 scored messages, concatenated and truncated
257
+ const top3 = [...scored]
258
+ .sort((a, b) => b.score - a.score)
259
+ .slice(0, 3)
260
+ .map(m => (m.msg.text_content || '').trim())
261
+ .filter(t => t.length > 0)
262
+ .join(' ');
263
+ const summary = truncateHeadTail(top3, cfg.SYNTHESIS_MAX_SUMMARY_CHARS);
264
+ // Build the wiki page markdown
265
+ const lines = [];
266
+ lines.push(`# ${topic.name}`);
267
+ lines.push('');
268
+ lines.push(`**Status:** ${topic.status}`);
269
+ lines.push(`**Last activity:** ${topic.updated_at}`);
270
+ lines.push(`**Messages:** ${topic.message_count}`);
271
+ lines.push(`**Participants:** ${participants.join(', ') || 'unknown'}`);
272
+ lines.push('');
273
+ lines.push('## Summary');
274
+ lines.push(summary || '_No summary available_');
275
+ lines.push('');
276
+ lines.push('## Key Decisions');
277
+ if (decisions.length > 0) {
278
+ for (const { msg } of decisions) {
279
+ const text = (msg.text_content || '').trim();
280
+ const first = text.split('\n')[0].slice(0, 200);
281
+ lines.push(`- ${first}`);
282
+ }
283
+ }
284
+ else {
285
+ lines.push('_No key decisions recorded_');
286
+ }
287
+ lines.push('');
288
+ lines.push('## Open Questions');
289
+ if (openQuestions.length > 0) {
290
+ for (const { msg } of openQuestions) {
291
+ const text = (msg.text_content || '').trim();
292
+ const first = text.split('\n')[0].slice(0, 200);
293
+ lines.push(`- ${first}`);
294
+ }
295
+ }
296
+ else {
297
+ lines.push('_No open questions_');
298
+ }
299
+ lines.push('');
300
+ lines.push('## Artifacts');
301
+ if (allArtifacts.size > 0) {
302
+ for (const artifact of allArtifacts) {
303
+ lines.push(`- ${artifact}`);
304
+ }
305
+ }
306
+ else {
307
+ lines.push('_No artifacts recorded_');
308
+ }
309
+ lines.push('');
310
+ lines.push('## Cross-References');
311
+ lines.push('_Auto-generated — see knowledge graph for links_');
312
+ return lines.join('\n');
313
+ }
314
+ }
315
+ //# sourceMappingURL=topic-synthesizer.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * hypermem Trigger Registry (W5)
3
+ *
4
+ * Centralizes ACA collection trigger definitions with owner/category metadata.
5
+ * Extracted from compositor.ts for independent testability and auditability.
6
+ *
7
+ * - TRIGGER_REGISTRY_VERSION: semver string for the registry schema
8
+ * - TRIGGER_REGISTRY_HASH: 12-char SHA-256 of (collection, keywords) per entry
9
+ * - logRegistryStartup(): emits version + hash on first Compositor boot
10
+ */
11
+ /**
12
+ * A trigger definition maps a collection to the conversation signals that
13
+ * indicate it should be queried. When any keyword matches the user's latest
14
+ * message, the compositor fetches relevant chunks from that collection.
15
+ *
16
+ * Centralizing trigger logic here (not in workspace stubs) means:
17
+ * - One update propagates to all agents
18
+ * - Stubs become documentation, not code
19
+ * - Trigger logic can be tested independently
20
+ *
21
+ * W5 additions: owner, category, description (all optional — backward compat).
22
+ */
23
+ export interface CollectionTrigger {
24
+ /** Collection path: governance/policy, identity/job, etc. */
25
+ collection: string;
26
+ /** Keywords that trigger this collection (case-insensitive) */
27
+ keywords: string[];
28
+ /** Max tokens to inject from this collection */
29
+ maxTokens?: number;
30
+ /** Max chunks to retrieve */
31
+ maxChunks?: number;
32
+ /** Which agent/team owns this trigger set */
33
+ owner?: string;
34
+ /** Logical grouping: 'governance' | 'identity' | 'memory' | 'operations' */
35
+ category?: string;
36
+ /** Human-readable purpose */
37
+ description?: string;
38
+ }
39
+ export declare const TRIGGER_REGISTRY_VERSION = "1.0.0";
40
+ /**
41
+ * Default trigger registry for standard ACA collections.
42
+ * Covers the core ACA offload use case from Anvil's spec.
43
+ */
44
+ export declare const TRIGGER_REGISTRY: CollectionTrigger[];
45
+ /** Backward-compat alias — same reference as TRIGGER_REGISTRY */
46
+ export declare const DEFAULT_TRIGGERS: CollectionTrigger[];
47
+ /**
48
+ * 12-char SHA-256 of the registry's (collection, keywords) pairs.
49
+ * Changes when trigger definitions change; stable across metadata-only edits.
50
+ * Computed once at module load.
51
+ */
52
+ export declare const TRIGGER_REGISTRY_HASH: string;
53
+ /**
54
+ * Match a user message against the trigger registry.
55
+ * Returns triggered collections (deduplicated, ordered by trigger specificity).
56
+ */
57
+ export declare function matchTriggers(userMessage: string, triggers: CollectionTrigger[]): CollectionTrigger[];
58
+ /**
59
+ * Emit a one-line startup log with registry version, hash, and entry count.
60
+ * Call once per process via the Compositor constructor guard.
61
+ */
62
+ export declare function logRegistryStartup(): void;
63
+ //# sourceMappingURL=trigger-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-registry.d.ts","sourceRoot":"","sources":["../src/trigger-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,EAiH/C,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,qBAAmB,CAAC;AAIjD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAGrB,CAAC;AAIhB;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,iBAAiB,EAAE,CAMrB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * hypermem Trigger Registry (W5)
3
+ *
4
+ * Centralizes ACA collection trigger definitions with owner/category metadata.
5
+ * Extracted from compositor.ts for independent testability and auditability.
6
+ *
7
+ * - TRIGGER_REGISTRY_VERSION: semver string for the registry schema
8
+ * - TRIGGER_REGISTRY_HASH: 12-char SHA-256 of (collection, keywords) per entry
9
+ * - logRegistryStartup(): emits version + hash on first Compositor boot
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ // ─── Registry ─────────────────────────────────────────────────
13
+ export const TRIGGER_REGISTRY_VERSION = '1.0.0';
14
+ /**
15
+ * Default trigger registry for standard ACA collections.
16
+ * Covers the core ACA offload use case from Anvil's spec.
17
+ */
18
+ export const TRIGGER_REGISTRY = [
19
+ {
20
+ collection: 'governance/policy',
21
+ keywords: [
22
+ 'escalat', 'policy', 'decision state', 'green', 'yellow', 'red',
23
+ 'council procedure', 'naming', 'mandate', 'compliance', 'governance',
24
+ 'override', 'human review', 'irreversible',
25
+ ],
26
+ maxTokens: 1500,
27
+ maxChunks: 3,
28
+ owner: 'council',
29
+ category: 'governance',
30
+ description: 'Governance policy: escalation triggers, decision states, naming rules, compliance mandates',
31
+ },
32
+ {
33
+ collection: 'governance/charter',
34
+ keywords: [
35
+ 'charter', 'mission', 'director', 'org', 'reporting', 'boundary',
36
+ 'delegation', 'authority', 'jurisdiction',
37
+ ],
38
+ maxTokens: 1000,
39
+ maxChunks: 2,
40
+ owner: 'council',
41
+ category: 'governance',
42
+ description: 'Organizational charter: mission, director roles, authority boundaries, delegation scope',
43
+ },
44
+ {
45
+ collection: 'governance/comms',
46
+ keywords: [
47
+ 'message', 'send', 'tier 1', 'tier 2', 'tier 3', 'async', 'dispatch',
48
+ 'sessions_send', 'inter-agent', 'protocol', 'comms', 'ping', 'notify',
49
+ ],
50
+ maxTokens: 800,
51
+ maxChunks: 2,
52
+ owner: 'council',
53
+ category: 'governance',
54
+ description: 'Inter-agent communication protocols: message tiers, dispatch rules, session messaging',
55
+ },
56
+ {
57
+ collection: 'operations/agents',
58
+ keywords: [
59
+ 'boot', 'startup', 'bootstrap', 'heartbeat', 'workqueue', 'checkpoint',
60
+ 'session start', 'roll call', 'memory recall', 'dispatch inbox',
61
+ ],
62
+ maxTokens: 800,
63
+ maxChunks: 2,
64
+ owner: 'forge',
65
+ category: 'operations',
66
+ description: 'Agent operational procedures: boot sequence, heartbeat, work queue, session startup',
67
+ },
68
+ {
69
+ collection: 'identity/job',
70
+ keywords: [
71
+ 'deliberat', 'council round', 'vote', 'response contract', 'rating',
72
+ 'first response', 'second response', 'handoff', 'floor open',
73
+ 'performance', 'output discipline', 'assessment',
74
+ ],
75
+ maxTokens: 1200,
76
+ maxChunks: 3,
77
+ owner: 'council',
78
+ category: 'identity',
79
+ description: 'Agent job definitions: council deliberation roles, response contracts, performance standards',
80
+ },
81
+ {
82
+ collection: 'identity/motivations',
83
+ keywords: [
84
+ 'motivation', 'fear', 'tension', 'why do you', 'how do you feel',
85
+ 'drives', 'values',
86
+ ],
87
+ maxTokens: 600,
88
+ maxChunks: 1,
89
+ owner: 'council',
90
+ category: 'identity',
91
+ description: 'Agent motivations and values: intrinsic drivers, tensions, emotional context',
92
+ },
93
+ {
94
+ collection: 'memory/decisions',
95
+ keywords: [
96
+ 'remember', 'decision', 'we decided', 'previously', 'last time',
97
+ 'history', 'past', 'earlier', 'recall', 'context',
98
+ ],
99
+ maxTokens: 1500,
100
+ maxChunks: 4,
101
+ owner: 'forge',
102
+ category: 'memory',
103
+ description: 'Decision history: past choices, previously agreed approaches, recalled context',
104
+ },
105
+ {
106
+ collection: 'identity/soul',
107
+ keywords: [
108
+ 'who are you', 'your role', 'your purpose', 'your domain', 'your job',
109
+ 'identity', 'soul', 'persona', 'what do you do', 'how do you work',
110
+ 'your principles', 'your values', 'your seat',
111
+ ],
112
+ maxTokens: 1200,
113
+ maxChunks: 2,
114
+ owner: 'council',
115
+ category: 'identity',
116
+ description: 'Agent soul and persona: role definition, domain ownership, core principles',
117
+ },
118
+ {
119
+ collection: 'operations/tools',
120
+ keywords: [
121
+ 'tool', 'config', 'command', 'cli', 'path', 'deploy', 'restart',
122
+ 'openclaw', 'session_status', 'model', 'plugin', 'workspace path',
123
+ 'how to', 'where is', 'which command', 'quick ref',
124
+ ],
125
+ maxTokens: 1200,
126
+ maxChunks: 3,
127
+ owner: 'forge',
128
+ category: 'operations',
129
+ description: 'Agent tooling reference: CLI commands, config paths, deployment procedures, quick reference',
130
+ },
131
+ ];
132
+ /** Backward-compat alias — same reference as TRIGGER_REGISTRY */
133
+ export const DEFAULT_TRIGGERS = TRIGGER_REGISTRY;
134
+ // ─── Registry Hash ────────────────────────────────────────────
135
+ /**
136
+ * 12-char SHA-256 of the registry's (collection, keywords) pairs.
137
+ * Changes when trigger definitions change; stable across metadata-only edits.
138
+ * Computed once at module load.
139
+ */
140
+ export const TRIGGER_REGISTRY_HASH = createHash('sha256')
141
+ .update(JSON.stringify(TRIGGER_REGISTRY.map(t => ({ collection: t.collection, keywords: t.keywords }))))
142
+ .digest('hex')
143
+ .slice(0, 12);
144
+ // ─── matchTriggers ────────────────────────────────────────────
145
+ /**
146
+ * Match a user message against the trigger registry.
147
+ * Returns triggered collections (deduplicated, ordered by trigger specificity).
148
+ */
149
+ export function matchTriggers(userMessage, triggers) {
150
+ if (!userMessage)
151
+ return [];
152
+ const lower = userMessage.toLowerCase();
153
+ return triggers.filter(t => t.keywords.some(kw => lower.includes(kw.toLowerCase())));
154
+ }
155
+ // ─── Startup Log ──────────────────────────────────────────────
156
+ /**
157
+ * Emit a one-line startup log with registry version, hash, and entry count.
158
+ * Call once per process via the Compositor constructor guard.
159
+ */
160
+ export function logRegistryStartup() {
161
+ console.log(`[hypermem:triggers] version=${TRIGGER_REGISTRY_VERSION} hash=${TRIGGER_REGISTRY_HASH} entries=${TRIGGER_REGISTRY.length}`);
162
+ }
163
+ //# sourceMappingURL=trigger-registry.js.map