clawvault 3.3.0 → 3.4.1

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.
@@ -0,0 +1,2223 @@
1
+ import {
2
+ buildRecallResult
3
+ } from "./chunk-RL2L6I6K.js";
4
+ import {
5
+ synthesizeEntityProfiles
6
+ } from "./chunk-NSXYM6EZ.js";
7
+ import {
8
+ normalizeForDedup,
9
+ similarityScore
10
+ } from "./chunk-35JCYSRR.js";
11
+ import {
12
+ FactStore,
13
+ extractFactsRuleBased
14
+ } from "./chunk-BSJ6RIT7.js";
15
+ import {
16
+ ClawVault
17
+ } from "./chunk-ECGJYWNA.js";
18
+ import {
19
+ hasQmd
20
+ } from "./chunk-PTWPPVC7.js";
21
+ import {
22
+ resolveVaultPath
23
+ } from "./chunk-GJO3CFUN.js";
24
+
25
+ // src/capture/extractor.ts
26
+ var MEMORY_NOTE_PATTERN = /<memory_note([^>]*)>([\s\S]*?)<\/memory_note>/gi;
27
+ var WIKI_LINK_PATTERN = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
28
+ var SENTENCE_SPLIT_PATTERN = /\r?\n|(?<=[.?!])\s+/;
29
+ var CLASSIFIER_RULES = [
30
+ { type: "decision", confidence: 0.74, pattern: /\b(decid(?:e|ed|ing|ion)|chose|selected|opted|agreed|we will|i will)\b/i },
31
+ { type: "preference", confidence: 0.72, pattern: /\b(prefer(?:ence|red|s)?|like(?:s|d)?|dislike|always use|never use|default to)\b/i },
32
+ { type: "lesson", confidence: 0.76, pattern: /\b(learn(?:ed|ing|t)|lesson|insight|takeaway|next time|mistake|realized)\b/i },
33
+ { type: "relationship", confidence: 0.69, pattern: /\b(works with|reports to|collaborates with|partnered with|depends on|related to)\b/i },
34
+ { type: "episode", confidence: 0.64, pattern: /\b(today|yesterday|this morning|this afternoon|during|after|before|in the meeting)\b/i },
35
+ { type: "entity", confidence: 0.62, pattern: /\[\[[^\]]+\]\]/i }
36
+ ];
37
+ function normalizeWhitespace(value) {
38
+ return value.replace(/\s+/g, " ").trim();
39
+ }
40
+ function parseNoteAttributes(raw) {
41
+ const attributes = {};
42
+ const attributePattern = /([a-zA-Z_][\w-]*)\s*=\s*"([^"]*)"/g;
43
+ let match = attributePattern.exec(raw);
44
+ while (match) {
45
+ attributes[match[1]] = match[2];
46
+ match = attributePattern.exec(raw);
47
+ }
48
+ return attributes;
49
+ }
50
+ function sanitizeType(value) {
51
+ if (!value) return null;
52
+ const normalized = value.trim().toLowerCase();
53
+ const allowed = [
54
+ "fact",
55
+ "preference",
56
+ "decision",
57
+ "lesson",
58
+ "entity",
59
+ "episode",
60
+ "relationship"
61
+ ];
62
+ return allowed.includes(normalized) ? normalized : null;
63
+ }
64
+ function parseConfidence(value, fallback = 0.85) {
65
+ if (!value) return fallback;
66
+ const parsed = Number.parseFloat(value);
67
+ if (!Number.isFinite(parsed)) return fallback;
68
+ return Math.min(1, Math.max(0, parsed));
69
+ }
70
+ function extractEntities(text) {
71
+ const entities = /* @__PURE__ */ new Set();
72
+ WIKI_LINK_PATTERN.lastIndex = 0;
73
+ let linkMatch = WIKI_LINK_PATTERN.exec(text);
74
+ while (linkMatch) {
75
+ entities.add(linkMatch[1].trim());
76
+ linkMatch = WIKI_LINK_PATTERN.exec(text);
77
+ }
78
+ const properNounPattern = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,2})\b/g;
79
+ let nounMatch = properNounPattern.exec(text);
80
+ while (nounMatch) {
81
+ const value = nounMatch[1].trim();
82
+ if (value.length >= 3) {
83
+ entities.add(value);
84
+ }
85
+ nounMatch = properNounPattern.exec(text);
86
+ }
87
+ return [...entities];
88
+ }
89
+ function inferTypeFromSentence(sentence) {
90
+ for (const rule of CLASSIFIER_RULES) {
91
+ if (rule.pattern.test(sentence)) {
92
+ return { type: rule.type, confidence: rule.confidence };
93
+ }
94
+ }
95
+ return { type: "fact", confidence: 0.55 };
96
+ }
97
+ function titleFromContent(type, content) {
98
+ const words = normalizeWhitespace(content).split(" ").filter(Boolean).slice(0, 8);
99
+ const stem = words.join(" ");
100
+ return `${type}: ${stem}`.slice(0, 90);
101
+ }
102
+ function dedupeCandidates(candidates) {
103
+ const seen = /* @__PURE__ */ new Set();
104
+ const deduped = [];
105
+ for (const candidate of candidates) {
106
+ const key = normalizeWhitespace(candidate.content).toLowerCase();
107
+ if (!key || seen.has(key)) {
108
+ continue;
109
+ }
110
+ seen.add(key);
111
+ deduped.push(candidate);
112
+ }
113
+ return deduped;
114
+ }
115
+ function extractTaggedMemoryNotes(text) {
116
+ const candidates = [];
117
+ MEMORY_NOTE_PATTERN.lastIndex = 0;
118
+ let match = MEMORY_NOTE_PATTERN.exec(text);
119
+ while (match) {
120
+ const attributes = parseNoteAttributes(match[1] ?? "");
121
+ const body = normalizeWhitespace(match[2] ?? "");
122
+ if (!body) {
123
+ match = MEMORY_NOTE_PATTERN.exec(text);
124
+ continue;
125
+ }
126
+ const type = sanitizeType(attributes.type) ?? inferTypeFromSentence(body).type;
127
+ const confidence = parseConfidence(attributes.confidence, 0.9);
128
+ const entities = extractEntities(body);
129
+ candidates.push({
130
+ content: body,
131
+ type,
132
+ confidence,
133
+ title: attributes.title || titleFromContent(type, body),
134
+ tags: attributes.tags ? attributes.tags.split(",").map((item) => item.trim()).filter(Boolean) : void 0,
135
+ entities,
136
+ source: "memory_note",
137
+ metadata: {
138
+ taggedType: attributes.type,
139
+ rawAttributes: attributes
140
+ }
141
+ });
142
+ match = MEMORY_NOTE_PATTERN.exec(text);
143
+ }
144
+ return dedupeCandidates(candidates);
145
+ }
146
+ function stripMemoryNotes(text) {
147
+ return text.replace(MEMORY_NOTE_PATTERN, " ");
148
+ }
149
+ function extractHeuristicMemories(text) {
150
+ const sanitized = stripMemoryNotes(text);
151
+ const sentences = sanitized.split(SENTENCE_SPLIT_PATTERN).map((line) => normalizeWhitespace(line)).filter((line) => line.length >= 20 && line.length <= 400);
152
+ const candidates = [];
153
+ for (const sentence of sentences) {
154
+ const classification = inferTypeFromSentence(sentence);
155
+ const entities = extractEntities(sentence);
156
+ const confidenceBoost = entities.length > 0 ? 0.08 : 0;
157
+ candidates.push({
158
+ content: sentence,
159
+ type: classification.type,
160
+ confidence: Math.min(1, classification.confidence + confidenceBoost),
161
+ title: titleFromContent(classification.type, sentence),
162
+ entities,
163
+ source: "heuristic"
164
+ });
165
+ }
166
+ return dedupeCandidates(candidates);
167
+ }
168
+ function extractMemoriesFromAssistantResponse(text) {
169
+ const fromTags = extractTaggedMemoryNotes(text);
170
+ const fromHeuristics = extractHeuristicMemories(text);
171
+ return dedupeCandidates([...fromTags, ...fromHeuristics]);
172
+ }
173
+
174
+ // src/capture/quality.ts
175
+ var SECRET_PATTERNS = [
176
+ /sk-[A-Za-z0-9]{20,}/,
177
+ /AKIA[0-9A-Z]{16}/,
178
+ /-----BEGIN (?:RSA|EC|OPENSSH|PGP|DSA) PRIVATE KEY-----/,
179
+ /(?:api[-_ ]?key|access[-_ ]?token|secret)(?:\s*[:=]\s*|\s+)[A-Za-z0-9_\-]{12,}/i
180
+ ];
181
+ var NOISE_PATTERNS = [
182
+ /^\s*[{[][\s\S]{30,}[}\]]\s*$/m,
183
+ /^\s*\$?\s*(?:npm|pnpm|yarn|bun|git|docker|kubectl|curl|node)\b/m,
184
+ /(?:stdout|stderr|exit code|stack trace|traceback|at\s+[A-Za-z0-9_.$]+\s+\()/i,
185
+ /^\s*[|+]{2,}[-+| ]+\s*$/m
186
+ ];
187
+ function countAlphaWords(value) {
188
+ const words = value.match(/[A-Za-z][A-Za-z0-9'-]*/g) ?? [];
189
+ return words.length;
190
+ }
191
+ function symbolRatio(value) {
192
+ if (!value) return 1;
193
+ const symbols = value.replace(/[A-Za-z0-9\s]/g, "").length;
194
+ return symbols / value.length;
195
+ }
196
+ function lineDensity(value) {
197
+ const lines = value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
198
+ if (lines.length === 0) return 0;
199
+ const averageLength = lines.reduce((sum, line) => sum + line.length, 0) / lines.length;
200
+ return averageLength;
201
+ }
202
+ function isLikelyJunkMemory(content) {
203
+ if (!content || content.trim().length < 12) {
204
+ return true;
205
+ }
206
+ if (SECRET_PATTERNS.some((pattern) => pattern.test(content))) {
207
+ return true;
208
+ }
209
+ if (NOISE_PATTERNS.some((pattern) => pattern.test(content))) {
210
+ return true;
211
+ }
212
+ if (symbolRatio(content) > 0.3) {
213
+ return true;
214
+ }
215
+ if (lineDensity(content) > 180) {
216
+ return true;
217
+ }
218
+ return false;
219
+ }
220
+ function plausibilityScore(content) {
221
+ const trimmed = content.trim();
222
+ if (trimmed.length === 0) return 0;
223
+ const alphaWords = countAlphaWords(trimmed);
224
+ const hasSentencePunctuation = /[.!?]$/.test(trimmed) || trimmed.length > 40;
225
+ const hasVerbLikeWord = /\b(is|are|was|were|has|have|will|should|decided|prefer|learned|met|works|uses)\b/i.test(trimmed);
226
+ const hasBalancedLength = trimmed.length >= 20 && trimmed.length <= 400;
227
+ let score = 0;
228
+ if (alphaWords >= 4) score += 0.3;
229
+ if (alphaWords >= 8) score += 0.2;
230
+ if (hasSentencePunctuation) score += 0.2;
231
+ if (hasVerbLikeWord) score += 0.2;
232
+ if (hasBalancedLength) score += 0.1;
233
+ return Math.min(1, score);
234
+ }
235
+ function maxJaccardSimilarity(candidate, existingContents) {
236
+ if (existingContents.length === 0) return 0;
237
+ let best = 0;
238
+ const normalizedCandidate = normalizeForDedup(candidate);
239
+ for (const entry of existingContents) {
240
+ const score = similarityScore(normalizedCandidate, normalizeForDedup(entry));
241
+ if (score > best) {
242
+ best = score;
243
+ }
244
+ }
245
+ return best;
246
+ }
247
+ function evaluateCandidateQuality(candidate, existingContents, stagedContents = [], options = {}) {
248
+ const minConfidence = options.minConfidence ?? 0.4;
249
+ const minQualityScore = options.minQualityScore ?? 0.4;
250
+ const dedupThreshold = options.dedupThreshold ?? 0.82;
251
+ if (candidate.confidence < minConfidence) {
252
+ return {
253
+ accepted: false,
254
+ reason: `confidence below threshold (${candidate.confidence.toFixed(2)} < ${minConfidence.toFixed(2)})`,
255
+ qualityScore: candidate.confidence,
256
+ plausibilityScore: 0,
257
+ confidenceScore: candidate.confidence,
258
+ maxSimilarity: 0
259
+ };
260
+ }
261
+ if (isLikelyJunkMemory(candidate.content)) {
262
+ return {
263
+ accepted: false,
264
+ reason: "candidate flagged as junk/tool noise",
265
+ qualityScore: 0,
266
+ plausibilityScore: 0,
267
+ confidenceScore: candidate.confidence,
268
+ maxSimilarity: 0
269
+ };
270
+ }
271
+ const plausibility = plausibilityScore(candidate.content);
272
+ const maxSimilarity = maxJaccardSimilarity(candidate.content, [...existingContents, ...stagedContents]);
273
+ if (maxSimilarity >= dedupThreshold) {
274
+ return {
275
+ accepted: false,
276
+ reason: `candidate too similar to existing memory (similarity=${maxSimilarity.toFixed(2)})`,
277
+ qualityScore: 0,
278
+ plausibilityScore: plausibility,
279
+ confidenceScore: candidate.confidence,
280
+ maxSimilarity
281
+ };
282
+ }
283
+ const qualityScore = candidate.confidence * 0.6 + plausibility * 0.4;
284
+ if (qualityScore < minQualityScore) {
285
+ return {
286
+ accepted: false,
287
+ reason: `quality score below threshold (${qualityScore.toFixed(2)} < ${minQualityScore.toFixed(2)})`,
288
+ qualityScore,
289
+ plausibilityScore: plausibility,
290
+ confidenceScore: candidate.confidence,
291
+ maxSimilarity
292
+ };
293
+ }
294
+ return {
295
+ accepted: true,
296
+ qualityScore,
297
+ plausibilityScore: plausibility,
298
+ confidenceScore: candidate.confidence,
299
+ maxSimilarity
300
+ };
301
+ }
302
+
303
+ // src/capture/service.ts
304
+ var CATEGORY_BY_MEMORY_TYPE = {
305
+ fact: "facts",
306
+ preference: "preferences",
307
+ decision: "decisions",
308
+ lesson: "lessons",
309
+ entity: "people",
310
+ episode: "transcripts",
311
+ relationship: "people"
312
+ };
313
+ function normalizeMessageContent(content) {
314
+ if (typeof content === "string") {
315
+ return content;
316
+ }
317
+ if (Array.isArray(content)) {
318
+ const parts = content.map((item) => {
319
+ if (typeof item === "string") return item;
320
+ if (!item || typeof item !== "object") return "";
321
+ const record = item;
322
+ if (typeof record.text === "string") return record.text;
323
+ if (typeof record.content === "string") return record.content;
324
+ return "";
325
+ }).filter(Boolean);
326
+ return parts.join("\n").trim();
327
+ }
328
+ if (content && typeof content === "object") {
329
+ const record = content;
330
+ if (typeof record.text === "string") return record.text;
331
+ if (typeof record.content === "string") return record.content;
332
+ }
333
+ return "";
334
+ }
335
+ function normalizeIncomingMessage(value) {
336
+ if (!value || typeof value !== "object") {
337
+ return null;
338
+ }
339
+ const record = value;
340
+ const roleCandidate = record.role ?? record.authorRole ?? record.speaker ?? record.type;
341
+ const role = typeof roleCandidate === "string" ? roleCandidate.toLowerCase() : "assistant";
342
+ const content = normalizeMessageContent(record.content ?? record.text ?? record.message);
343
+ if (!content.trim()) {
344
+ return null;
345
+ }
346
+ const timestamp = typeof record.timestamp === "string" ? record.timestamp : typeof record.createdAt === "string" ? record.createdAt : void 0;
347
+ return { role, content, timestamp };
348
+ }
349
+ function titleForCandidate(candidate) {
350
+ if (candidate.title && candidate.title.trim()) {
351
+ return candidate.title.trim();
352
+ }
353
+ const stem = candidate.content.replace(/\s+/g, " ").trim().split(" ").slice(0, 8).join(" ");
354
+ return `${candidate.type}: ${stem}`.slice(0, 90);
355
+ }
356
+ function withCollisionSuffix(title) {
357
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
358
+ return `${title} ${stamp}`;
359
+ }
360
+ var LiveCaptureService = class {
361
+ async captureTurn(messages, options = {}) {
362
+ const normalizedMessages = messages.map((message) => normalizeIncomingMessage(message)).filter((message) => Boolean(message)).filter((message) => message.role === "assistant");
363
+ if (normalizedMessages.length === 0) {
364
+ return {
365
+ stored: 0,
366
+ rejected: 0,
367
+ storedDocuments: [],
368
+ acceptedCandidates: [],
369
+ rejectedCandidates: []
370
+ };
371
+ }
372
+ const vaultPath = resolveVaultPath({
373
+ explicitPath: options.vaultPath,
374
+ agentId: options.agentId,
375
+ pluginConfig: options.pluginConfig
376
+ });
377
+ const vault = new ClawVault(vaultPath);
378
+ await vault.load();
379
+ const extractedCandidates = normalizedMessages.flatMap(
380
+ (message) => extractMemoriesFromAssistantResponse(message.content)
381
+ );
382
+ if (extractedCandidates.length === 0) {
383
+ return {
384
+ stored: 0,
385
+ rejected: 0,
386
+ storedDocuments: [],
387
+ acceptedCandidates: [],
388
+ rejectedCandidates: []
389
+ };
390
+ }
391
+ const maxPerTurn = options.maxPerTurn ?? 8;
392
+ const existingDocs = await vault.list();
393
+ const existingContents = existingDocs.map((doc) => doc.content);
394
+ const stagedAcceptedContents = [];
395
+ const acceptedCandidates = [];
396
+ const rejectedCandidates = [];
397
+ for (const candidate of extractedCandidates) {
398
+ if (acceptedCandidates.length >= maxPerTurn) {
399
+ break;
400
+ }
401
+ const quality = evaluateCandidateQuality(
402
+ candidate,
403
+ existingContents,
404
+ stagedAcceptedContents,
405
+ {
406
+ minConfidence: options.minConfidence,
407
+ dedupThreshold: options.dedupThreshold
408
+ }
409
+ );
410
+ if (!quality.accepted) {
411
+ rejectedCandidates.push({
412
+ candidate,
413
+ reason: quality.reason ?? "quality gate rejected candidate"
414
+ });
415
+ continue;
416
+ }
417
+ stagedAcceptedContents.push(candidate.content);
418
+ acceptedCandidates.push(candidate);
419
+ }
420
+ const storedDocuments = [];
421
+ for (const candidate of acceptedCandidates) {
422
+ const doc = await this.persistCandidate(vault, candidate, options);
423
+ storedDocuments.push(doc.id);
424
+ }
425
+ const hasEntityMentions = acceptedCandidates.some((candidate) => (candidate.entities?.length ?? 0) > 0);
426
+ if (hasEntityMentions) {
427
+ await synthesizeEntityProfiles(vaultPath, { writeFiles: true });
428
+ }
429
+ return {
430
+ stored: storedDocuments.length,
431
+ rejected: rejectedCandidates.length,
432
+ storedDocuments,
433
+ acceptedCandidates,
434
+ rejectedCandidates
435
+ };
436
+ }
437
+ async persistCandidate(vault, candidate, options) {
438
+ const category = CATEGORY_BY_MEMORY_TYPE[candidate.type] ?? "inbox";
439
+ const title = titleForCandidate(candidate);
440
+ const frontmatter = {
441
+ memoryType: candidate.type,
442
+ captureSource: candidate.source,
443
+ confidence: Number(candidate.confidence.toFixed(3)),
444
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
445
+ };
446
+ if (candidate.tags && candidate.tags.length > 0) {
447
+ frontmatter.tags = candidate.tags;
448
+ }
449
+ if (candidate.entities && candidate.entities.length > 0) {
450
+ frontmatter.entities = candidate.entities;
451
+ }
452
+ if (options.sourceSessionId) {
453
+ frontmatter.sessionId = options.sourceSessionId;
454
+ }
455
+ if (candidate.metadata) {
456
+ frontmatter.captureMetadata = candidate.metadata;
457
+ }
458
+ try {
459
+ return await vault.store({
460
+ category,
461
+ title,
462
+ content: candidate.content,
463
+ frontmatter
464
+ });
465
+ } catch (err) {
466
+ const message = err instanceof Error ? err.message : String(err);
467
+ if (!message.includes("Document already exists")) {
468
+ throw err;
469
+ }
470
+ return vault.store({
471
+ category,
472
+ title: withCollisionSuffix(title),
473
+ content: candidate.content,
474
+ frontmatter
475
+ });
476
+ }
477
+ }
478
+ };
479
+
480
+ // src/plugin/slot.ts
481
+ var CATEGORY_BY_TYPE = {
482
+ fact: "facts",
483
+ preference: "preferences",
484
+ decision: "decisions",
485
+ lesson: "lessons",
486
+ entity: "people",
487
+ episode: "transcripts",
488
+ relationship: "people"
489
+ };
490
+ function resolveSlotVaultPath(defaults, options) {
491
+ return resolveVaultPath({
492
+ explicitPath: options?.vaultPath ?? defaults.vaultPath,
493
+ pluginConfig: options?.pluginConfig ?? defaults.pluginConfig,
494
+ agentId: options?.agentId ?? defaults.agentId
495
+ });
496
+ }
497
+ function normalizeTitle(content, fallbackPrefix = "memory") {
498
+ const stem = content.replace(/\s+/g, " ").trim().split(" ").slice(0, 8).join(" ");
499
+ const value = stem.length > 0 ? stem : `${fallbackPrefix}-${Date.now()}`;
500
+ return value.slice(0, 90);
501
+ }
502
+ function createMemorySlot(defaults = {}) {
503
+ const captureService = new LiveCaptureService();
504
+ async function getVault(options) {
505
+ const vaultPath = resolveSlotVaultPath(defaults, options);
506
+ const vault = new ClawVault(vaultPath);
507
+ await vault.load();
508
+ return vault;
509
+ }
510
+ return {
511
+ async search(query, options = {}) {
512
+ const vault = await getVault(options);
513
+ const { vaultPath: _vaultPath, pluginConfig: _pluginConfig, agentId: _agentId, ...searchOptions } = options;
514
+ return vault.find(query, searchOptions);
515
+ },
516
+ async recall(query, options = {}) {
517
+ const vault = await getVault(options);
518
+ const result = await buildRecallResult(vault, query, {
519
+ ...options,
520
+ includeSources: options.includeSources ?? true
521
+ });
522
+ return result.context;
523
+ },
524
+ async capture(messages, options = {}) {
525
+ return captureService.captureTurn(messages, {
526
+ ...options,
527
+ vaultPath: options.vaultPath ?? defaults.vaultPath,
528
+ pluginConfig: options.pluginConfig ?? defaults.pluginConfig,
529
+ agentId: options.agentId ?? defaults.agentId
530
+ });
531
+ },
532
+ async store(content, metadata = {}) {
533
+ const vault = await getVault(metadata);
534
+ const category = metadata.category ?? (metadata.type ? CATEGORY_BY_TYPE[metadata.type] : "inbox");
535
+ const title = metadata.title ?? normalizeTitle(content, metadata.type ?? "memory");
536
+ const frontmatter = {
537
+ ...metadata.frontmatter ?? {}
538
+ };
539
+ if (metadata.tags && metadata.tags.length > 0) {
540
+ frontmatter.tags = metadata.tags;
541
+ }
542
+ if (metadata.type) {
543
+ frontmatter.memoryType = metadata.type;
544
+ }
545
+ return vault.store({
546
+ category,
547
+ title,
548
+ content,
549
+ frontmatter
550
+ });
551
+ }
552
+ };
553
+ }
554
+ function createMemorySlotPlugin(defaults = {}) {
555
+ return {
556
+ plugins: {
557
+ slots: {
558
+ memory: createMemorySlot(defaults)
559
+ }
560
+ }
561
+ };
562
+ }
563
+ function registerMemorySlot(registry, defaults = {}) {
564
+ if (!registry.plugins || typeof registry.plugins !== "object") {
565
+ registry.plugins = {};
566
+ }
567
+ if (!registry.plugins.slots || typeof registry.plugins.slots !== "object") {
568
+ registry.plugins.slots = {};
569
+ }
570
+ registry.plugins.slots.memory = createMemorySlot(defaults);
571
+ }
572
+
573
+ // src/plugin/config.ts
574
+ import * as fs from "fs";
575
+ import * as os from "os";
576
+ import * as path from "path";
577
+ var SESSION_KEY_RE = /^agent:[a-zA-Z0-9_-]+:[a-zA-Z0-9:_-]+$/;
578
+ var AGENT_ID_RE = /^[a-zA-Z0-9_-]{1,100}$/;
579
+ function isRecord(value) {
580
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
581
+ }
582
+ function readPluginConfig(api) {
583
+ if (!isRecord(api.pluginConfig)) {
584
+ return {};
585
+ }
586
+ return api.pluginConfig;
587
+ }
588
+ function isOptInEnabled(pluginConfig, ...keys) {
589
+ return keys.some((key) => pluginConfig[key] === true);
590
+ }
591
+ function isFeatureEnabled(pluginConfig, key, defaultValue) {
592
+ const value = pluginConfig[key];
593
+ if (typeof value === "boolean") return value;
594
+ return defaultValue;
595
+ }
596
+ function allowsEnvAccess(pluginConfig) {
597
+ return isOptInEnabled(pluginConfig, "allowEnvAccess");
598
+ }
599
+ function sanitizeSessionKey(value) {
600
+ if (typeof value !== "string") return "";
601
+ const trimmed = value.trim();
602
+ if (!SESSION_KEY_RE.test(trimmed)) return "";
603
+ return trimmed.slice(0, 200);
604
+ }
605
+ function sanitizeAgentId(value) {
606
+ if (typeof value !== "string") return "";
607
+ const trimmed = value.trim();
608
+ if (!AGENT_ID_RE.test(trimmed)) return "";
609
+ return trimmed;
610
+ }
611
+ function extractAgentIdFromSessionKey(sessionKey) {
612
+ const match = /^agent:([^:]+):/.exec(sessionKey);
613
+ if (!match?.[1]) return "";
614
+ return sanitizeAgentId(match[1]);
615
+ }
616
+ function resolveAgentId(ctx, pluginConfig) {
617
+ const fromContext = sanitizeAgentId(ctx.agentId);
618
+ if (fromContext) return fromContext;
619
+ const fromSessionKey = extractAgentIdFromSessionKey(sanitizeSessionKey(ctx.sessionKey));
620
+ if (fromSessionKey) return fromSessionKey;
621
+ if (allowsEnvAccess(pluginConfig)) {
622
+ const fromEnv = sanitizeAgentId(process.env.OPENCLAW_AGENT_ID);
623
+ if (fromEnv) return fromEnv;
624
+ }
625
+ return "main";
626
+ }
627
+ function getConfiguredExecutablePath(pluginConfig) {
628
+ if (typeof pluginConfig.clawvaultBinaryPath !== "string") return null;
629
+ const trimmed = pluginConfig.clawvaultBinaryPath.trim();
630
+ return trimmed || null;
631
+ }
632
+ function getConfiguredExecutableSha256(pluginConfig) {
633
+ if (typeof pluginConfig.clawvaultBinarySha256 !== "string") return null;
634
+ const trimmed = pluginConfig.clawvaultBinarySha256.trim().toLowerCase();
635
+ return trimmed || null;
636
+ }
637
+ function normalizeAbsoluteEnvPath(value) {
638
+ if (typeof value !== "string") return null;
639
+ const trimmed = value.trim();
640
+ if (!trimmed) return null;
641
+ const resolved = path.resolve(trimmed);
642
+ if (!path.isAbsolute(resolved)) return null;
643
+ return resolved;
644
+ }
645
+ function getOpenClawAgentsDir(pluginConfig) {
646
+ if (allowsEnvAccess(pluginConfig)) {
647
+ const stateDir = normalizeAbsoluteEnvPath(process.env.OPENCLAW_STATE_DIR);
648
+ if (stateDir) {
649
+ return path.join(stateDir, "agents");
650
+ }
651
+ const openClawHome = normalizeAbsoluteEnvPath(process.env.OPENCLAW_HOME);
652
+ if (openClawHome) {
653
+ return path.join(openClawHome, "agents");
654
+ }
655
+ }
656
+ return path.join(os.homedir(), ".openclaw", "agents");
657
+ }
658
+ function validateVaultPath(vaultPath) {
659
+ if (!vaultPath || typeof vaultPath !== "string") return null;
660
+ const resolved = path.resolve(vaultPath);
661
+ if (!path.isAbsolute(resolved)) return null;
662
+ try {
663
+ const stat = fs.statSync(resolved);
664
+ if (!stat.isDirectory()) return null;
665
+ } catch {
666
+ return null;
667
+ }
668
+ const configPath = path.join(resolved, ".clawvault.json");
669
+ if (!fs.existsSync(configPath)) return null;
670
+ return resolved;
671
+ }
672
+ function resolveAgentVaultPath(pluginConfig, agentId) {
673
+ if (!agentId) return null;
674
+ if (!isRecord(pluginConfig.agentVaults)) return null;
675
+ const configured = pluginConfig.agentVaults[agentId];
676
+ if (typeof configured !== "string") return null;
677
+ return validateVaultPath(configured);
678
+ }
679
+ function findVaultPath(pluginConfig, options = {}) {
680
+ const agentId = sanitizeAgentId(options.agentId);
681
+ if (agentId) {
682
+ const perAgent = resolveAgentVaultPath(pluginConfig, agentId);
683
+ if (perAgent) return perAgent;
684
+ }
685
+ const configured = validateVaultPath(pluginConfig.vaultPath ?? null);
686
+ if (configured) return configured;
687
+ if (allowsEnvAccess(pluginConfig)) {
688
+ const fromPluginEnv = validateVaultPath(process.env.OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH ?? null);
689
+ if (fromPluginEnv) return fromPluginEnv;
690
+ const fromClawVaultEnv = validateVaultPath(process.env.CLAWVAULT_PATH ?? null);
691
+ if (fromClawVaultEnv) return fromClawVaultEnv;
692
+ }
693
+ let dir = path.resolve(options.cwd ?? process.cwd());
694
+ const root = path.parse(dir).root;
695
+ while (dir !== root) {
696
+ const direct = validateVaultPath(dir);
697
+ if (direct) return direct;
698
+ const memorySubdir = validateVaultPath(path.join(dir, "memory"));
699
+ if (memorySubdir) return memorySubdir;
700
+ dir = path.dirname(dir);
701
+ }
702
+ return null;
703
+ }
704
+
705
+ // src/plugin/memory-manager.ts
706
+ import * as fs4 from "fs";
707
+ import * as path4 from "path";
708
+
709
+ // src/plugin/clawvault-cli.ts
710
+ import { execFileSync } from "child_process";
711
+ import * as fs3 from "fs";
712
+ import * as path3 from "path";
713
+
714
+ // src/plugin/integrity.ts
715
+ import { createHash } from "crypto";
716
+ import * as fs2 from "fs";
717
+ import * as path2 from "path";
718
+ var WINDOWS_PATHEXT_DEFAULT = ".EXE;.CMD;.BAT;.COM";
719
+ function splitPathEnv(pathEnv) {
720
+ if (typeof pathEnv !== "string" || !pathEnv.trim()) {
721
+ return [];
722
+ }
723
+ return pathEnv.split(path2.delimiter).map((entry) => entry.trim()).filter(Boolean);
724
+ }
725
+ function listExecutableCandidates(commandName) {
726
+ if (process.platform !== "win32") {
727
+ return [commandName];
728
+ }
729
+ const ext = path2.extname(commandName);
730
+ if (ext) {
731
+ return [commandName];
732
+ }
733
+ const pathext = splitPathEnv(process.env.PATHEXT || WINDOWS_PATHEXT_DEFAULT).map((candidate) => candidate.toLowerCase());
734
+ if (pathext.length === 0) {
735
+ return [commandName];
736
+ }
737
+ return pathext.map((candidate) => `${commandName}${candidate}`);
738
+ }
739
+ function isExecutablePath(candidatePath) {
740
+ try {
741
+ const stats = fs2.statSync(candidatePath);
742
+ if (!stats.isFile()) return false;
743
+ if (process.platform === "win32") return true;
744
+ return (stats.mode & 73) !== 0;
745
+ } catch {
746
+ return false;
747
+ }
748
+ }
749
+ function resolveFromPath(commandName, pathEnv) {
750
+ const candidates = listExecutableCandidates(commandName);
751
+ const directories = splitPathEnv(pathEnv);
752
+ for (const directory of directories) {
753
+ for (const candidate of candidates) {
754
+ const absoluteCandidate = path2.resolve(directory, candidate);
755
+ if (isExecutablePath(absoluteCandidate)) {
756
+ return absoluteCandidate;
757
+ }
758
+ }
759
+ }
760
+ return null;
761
+ }
762
+ function resolveExecutablePath(commandName, options = {}) {
763
+ if (typeof commandName !== "string" || !commandName.trim()) {
764
+ return null;
765
+ }
766
+ const explicitPath = typeof options.explicitPath === "string" ? options.explicitPath.trim() : "";
767
+ if (explicitPath) {
768
+ const absolute = path2.resolve(explicitPath);
769
+ return isExecutablePath(absolute) ? absolute : null;
770
+ }
771
+ if (commandName.includes(path2.sep)) {
772
+ const absolute = path2.resolve(commandName);
773
+ return isExecutablePath(absolute) ? absolute : null;
774
+ }
775
+ return resolveFromPath(commandName, process.env.PATH);
776
+ }
777
+ function sanitizeExecArgs(args) {
778
+ if (!Array.isArray(args)) {
779
+ throw new Error("Arguments must be an array");
780
+ }
781
+ return args.map((value, index) => {
782
+ if (typeof value !== "string") {
783
+ throw new Error(`Argument ${index} is not a string`);
784
+ }
785
+ if (value.includes("\0")) {
786
+ throw new Error(`Argument ${index} contains a null byte`);
787
+ }
788
+ return value;
789
+ });
790
+ }
791
+ function verifyExecutableIntegrity(executablePath, expectedSha256) {
792
+ if (typeof expectedSha256 !== "string" || !expectedSha256.trim()) {
793
+ return { ok: true, actualSha256: null };
794
+ }
795
+ const normalizedExpected = expectedSha256.trim().toLowerCase();
796
+ if (!/^[a-f0-9]{64}$/.test(normalizedExpected)) {
797
+ return { ok: false, actualSha256: null };
798
+ }
799
+ const payload = fs2.readFileSync(executablePath);
800
+ const actualSha256 = createHash("sha256").update(payload).digest("hex").toLowerCase();
801
+ return {
802
+ ok: actualSha256 === normalizedExpected,
803
+ actualSha256
804
+ };
805
+ }
806
+
807
+ // src/plugin/clawvault-cli.ts
808
+ var MAX_CONTEXT_PROMPT_LENGTH = 500;
809
+ var MAX_CONTEXT_SNIPPET_LENGTH = 220;
810
+ var MAX_RECAP_SNIPPET_LENGTH = 220;
811
+ var CLAWVAULT_EXECUTABLE = "clawvault";
812
+ var ONE_KIB = 1024;
813
+ var ONE_MIB = ONE_KIB * ONE_KIB;
814
+ var SMALL_SESSION_THRESHOLD_BYTES = 50 * ONE_KIB;
815
+ var MEDIUM_SESSION_THRESHOLD_BYTES = 150 * ONE_KIB;
816
+ var LARGE_SESSION_THRESHOLD_BYTES = 300 * ONE_KIB;
817
+ var OBSERVE_CURSOR_FILE = "observe-cursors.json";
818
+ function sanitizeForDisplay(value) {
819
+ if (typeof value !== "string") return "";
820
+ return value.replace(/[\x00-\x1f\x7f]/g, "").replace(/[`*_~\[\]]/g, "\\$&").slice(0, 240);
821
+ }
822
+ function sanitizePromptForContext(value) {
823
+ if (typeof value !== "string") return "";
824
+ return value.replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_CONTEXT_PROMPT_LENGTH);
825
+ }
826
+ function truncateSnippet(snippet) {
827
+ const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
828
+ if (safe.length <= MAX_CONTEXT_SNIPPET_LENGTH) return safe;
829
+ return `${safe.slice(0, MAX_CONTEXT_SNIPPET_LENGTH - 3).trimEnd()}...`;
830
+ }
831
+ function truncateRecapSnippet(snippet) {
832
+ const safe = sanitizeForDisplay(snippet).replace(/\s+/g, " ").trim();
833
+ if (safe.length <= MAX_RECAP_SNIPPET_LENGTH) return safe;
834
+ return `${safe.slice(0, MAX_RECAP_SNIPPET_LENGTH - 3).trimEnd()}...`;
835
+ }
836
+ function isRecord2(value) {
837
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
838
+ }
839
+ function parseTopLevelJson(output) {
840
+ const text = output.trim();
841
+ if (!text) return null;
842
+ const tryParse = (candidate) => {
843
+ try {
844
+ const parsed = JSON.parse(candidate);
845
+ return isRecord2(parsed) ? parsed : null;
846
+ } catch {
847
+ return null;
848
+ }
849
+ };
850
+ const direct = tryParse(text);
851
+ if (direct) return direct;
852
+ const findJsonEnd = (start) => {
853
+ const open = text[start];
854
+ if (open !== "{" && open !== "[") return -1;
855
+ const stack = [open];
856
+ let inString = false;
857
+ let escaped = false;
858
+ for (let i = start + 1; i < text.length; i += 1) {
859
+ const ch = text[i];
860
+ if (inString) {
861
+ if (escaped) {
862
+ escaped = false;
863
+ continue;
864
+ }
865
+ if (ch === "\\") {
866
+ escaped = true;
867
+ continue;
868
+ }
869
+ if (ch === '"') {
870
+ inString = false;
871
+ }
872
+ continue;
873
+ }
874
+ if (ch === '"') {
875
+ inString = true;
876
+ continue;
877
+ }
878
+ if (ch === "{" || ch === "[") {
879
+ stack.push(ch);
880
+ continue;
881
+ }
882
+ if (ch === "}" || ch === "]") {
883
+ const expected = ch === "}" ? "{" : "[";
884
+ const top = stack[stack.length - 1];
885
+ if (top !== expected) return -1;
886
+ stack.pop();
887
+ if (stack.length === 0) {
888
+ return i;
889
+ }
890
+ }
891
+ }
892
+ return -1;
893
+ };
894
+ for (let start = 0; start < text.length; start += 1) {
895
+ const ch = text[start];
896
+ if (ch !== "{" && ch !== "[") continue;
897
+ const end = findJsonEnd(start);
898
+ if (end < 0) continue;
899
+ const parsed = tryParse(text.slice(start, end + 1));
900
+ if (parsed) return parsed;
901
+ }
902
+ return null;
903
+ }
904
+ function resolveEntryArray(parsed, keys) {
905
+ for (const key of keys) {
906
+ const value = parsed[key];
907
+ if (!Array.isArray(value)) continue;
908
+ return value.filter((item) => isRecord2(item));
909
+ }
910
+ return [];
911
+ }
912
+ function parseContextJson(output, maxResults) {
913
+ const parsed = parseTopLevelJson(output);
914
+ if (!parsed) {
915
+ return [];
916
+ }
917
+ const rows = resolveEntryArray(parsed, ["context", "results", "entries", "memories"]);
918
+ return rows.slice(0, maxResults).map((entry) => {
919
+ const nestedDocument = isRecord2(entry.document) ? entry.document : null;
920
+ const title = sanitizeForDisplay(
921
+ entry.title ?? nestedDocument?.title ?? nestedDocument?.id ?? entry.path ?? "Untitled"
922
+ );
923
+ const resolvedPath = sanitizeForDisplay(
924
+ entry.path ?? entry.relPath ?? nestedDocument?.path ?? ""
925
+ );
926
+ const resolvedAge = sanitizeForDisplay(entry.age ?? entry.modified ?? "unknown age");
927
+ const snippetSource = String(
928
+ entry.snippet ?? entry.text ?? entry.content ?? nestedDocument?.snippet ?? nestedDocument?.content ?? ""
929
+ );
930
+ return {
931
+ title,
932
+ path: resolvedPath,
933
+ age: resolvedAge,
934
+ snippet: truncateSnippet(snippetSource),
935
+ score: Number.isFinite(Number(entry.score)) ? Number(entry.score) : 0
936
+ };
937
+ }).filter((entry) => entry.snippet.length > 0);
938
+ }
939
+ function parseSessionRecapJson(output, maxResults) {
940
+ const parsed = parseTopLevelJson(output);
941
+ if (!parsed) {
942
+ return [];
943
+ }
944
+ const rows = resolveEntryArray(parsed, ["messages", "turns", "recap"]);
945
+ return rows.map((entry) => {
946
+ const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
947
+ const normalizedRole = role === "user" || role === "human" ? "User" : role === "assistant" || role === "ai" ? "Assistant" : "";
948
+ if (!normalizedRole) return null;
949
+ const text = truncateRecapSnippet(
950
+ typeof entry.text === "string" ? entry.text : typeof entry.content === "string" ? entry.content : ""
951
+ );
952
+ if (!text) return null;
953
+ return {
954
+ role: normalizedRole,
955
+ text
956
+ };
957
+ }).filter((entry) => Boolean(entry)).slice(-maxResults);
958
+ }
959
+ function formatSessionContextInjection(recapEntries, memoryEntries) {
960
+ const lines = [
961
+ "[ClawVault] Session context restored:",
962
+ "",
963
+ "Recent conversation:"
964
+ ];
965
+ if (recapEntries.length === 0) {
966
+ lines.push("- No recent user/assistant turns found for this session.");
967
+ } else {
968
+ for (const entry of recapEntries) {
969
+ lines.push(`- ${entry.role}: ${entry.text}`);
970
+ }
971
+ }
972
+ lines.push("", "Relevant memories:");
973
+ if (memoryEntries.length === 0) {
974
+ lines.push("- No relevant vault memories found for the current prompt.");
975
+ } else {
976
+ for (const entry of memoryEntries) {
977
+ const pathSuffix = entry.path ? ` [${entry.path}]` : "";
978
+ lines.push(`- ${entry.title} (${entry.age}${pathSuffix}): ${entry.snippet}`);
979
+ }
980
+ }
981
+ return lines.join("\n");
982
+ }
983
+ function resolveVaultPathForAgent(pluginConfig, options = {}) {
984
+ return findVaultPath(pluginConfig, options);
985
+ }
986
+ function runClawvault(args, pluginConfig, options = {}) {
987
+ if (!isOptInEnabled(pluginConfig, "allowClawvaultExec")) {
988
+ return {
989
+ success: false,
990
+ skipped: true,
991
+ output: "ClawVault CLI execution is disabled. Set allowClawvaultExec=true to enable.",
992
+ code: 0
993
+ };
994
+ }
995
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1e3, Number(options.timeoutMs)) : 15e3;
996
+ const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
997
+ explicitPath: getConfiguredExecutablePath(pluginConfig)
998
+ });
999
+ if (!executablePath) {
1000
+ return {
1001
+ success: false,
1002
+ output: "Unable to resolve clawvault executable path.",
1003
+ code: 1
1004
+ };
1005
+ }
1006
+ const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
1007
+ const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
1008
+ if (!integrityResult.ok) {
1009
+ return {
1010
+ success: false,
1011
+ output: `Executable integrity verification failed for ${executablePath}.`,
1012
+ code: 1
1013
+ };
1014
+ }
1015
+ let sanitizedArgs;
1016
+ try {
1017
+ sanitizedArgs = sanitizeExecArgs(args);
1018
+ } catch (error) {
1019
+ return {
1020
+ success: false,
1021
+ output: error instanceof Error ? error.message : "Invalid command arguments",
1022
+ code: 1
1023
+ };
1024
+ }
1025
+ try {
1026
+ const output = execFileSync(executablePath, sanitizedArgs, {
1027
+ encoding: "utf-8",
1028
+ timeout: timeoutMs,
1029
+ stdio: ["pipe", "pipe", "pipe"],
1030
+ shell: false
1031
+ });
1032
+ return {
1033
+ success: true,
1034
+ output: output.trim(),
1035
+ code: 0
1036
+ };
1037
+ } catch (error) {
1038
+ const details = error;
1039
+ return {
1040
+ success: false,
1041
+ output: details.stderr?.toString() || details.message || "unknown error",
1042
+ code: details.status || 1
1043
+ };
1044
+ }
1045
+ }
1046
+ function parseRecoveryOutput(output) {
1047
+ if (!output || typeof output !== "string") {
1048
+ return { hadDeath: false, workingOn: null };
1049
+ }
1050
+ const hadDeath = output.includes("Context death detected") || output.includes("died") || output.includes("\u26A0\uFE0F");
1051
+ if (!hadDeath) {
1052
+ return { hadDeath: false, workingOn: null };
1053
+ }
1054
+ const workingOnLine = output.split("\n").find((line) => line.toLowerCase().includes("working on"));
1055
+ if (!workingOnLine) {
1056
+ return { hadDeath: true, workingOn: null };
1057
+ }
1058
+ const parts = workingOnLine.split(":");
1059
+ const workingOn = parts.length > 1 ? sanitizeForDisplay(parts.slice(1).join(":").trim()) : null;
1060
+ return { hadDeath: true, workingOn: workingOn || null };
1061
+ }
1062
+ function getObserveCursorPath(vaultPath) {
1063
+ return path3.join(vaultPath, ".clawvault", OBSERVE_CURSOR_FILE);
1064
+ }
1065
+ function loadObserveCursors(vaultPath) {
1066
+ const cursorPath = getObserveCursorPath(vaultPath);
1067
+ if (!fs3.existsSync(cursorPath)) {
1068
+ return {};
1069
+ }
1070
+ try {
1071
+ const parsed = JSON.parse(fs3.readFileSync(cursorPath, "utf-8"));
1072
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1073
+ return {};
1074
+ }
1075
+ return parsed;
1076
+ } catch {
1077
+ return {};
1078
+ }
1079
+ }
1080
+ function getScaledObservationThresholdBytes(fileSizeBytes) {
1081
+ if (!Number.isFinite(fileSizeBytes) || fileSizeBytes <= 0) {
1082
+ return SMALL_SESSION_THRESHOLD_BYTES;
1083
+ }
1084
+ if (fileSizeBytes < ONE_MIB) {
1085
+ return SMALL_SESSION_THRESHOLD_BYTES;
1086
+ }
1087
+ if (fileSizeBytes <= 5 * ONE_MIB) {
1088
+ return MEDIUM_SESSION_THRESHOLD_BYTES;
1089
+ }
1090
+ return LARGE_SESSION_THRESHOLD_BYTES;
1091
+ }
1092
+ function parseSessionIndex(agentId, pluginConfig) {
1093
+ const sessionsDir = path3.join(getOpenClawAgentsDir(pluginConfig), agentId, "sessions");
1094
+ const sessionsJsonPath = path3.join(sessionsDir, "sessions.json");
1095
+ if (!fs3.existsSync(sessionsJsonPath)) {
1096
+ return { sessionsDir, index: {} };
1097
+ }
1098
+ try {
1099
+ const parsed = JSON.parse(fs3.readFileSync(sessionsJsonPath, "utf-8"));
1100
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1101
+ return { sessionsDir, index: {} };
1102
+ }
1103
+ return {
1104
+ sessionsDir,
1105
+ index: parsed
1106
+ };
1107
+ } catch {
1108
+ return { sessionsDir, index: {} };
1109
+ }
1110
+ }
1111
+ function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
1112
+ const cursors = loadObserveCursors(vaultPath);
1113
+ const { sessionsDir, index } = parseSessionIndex(agentId, pluginConfig);
1114
+ const entries = Object.entries(index);
1115
+ if (entries.length === 0) {
1116
+ return false;
1117
+ }
1118
+ for (const [sessionKey, value] of entries) {
1119
+ const sessionId = typeof value?.sessionId === "string" ? value.sessionId.trim() : "";
1120
+ if (!/^[a-zA-Z0-9._-]{1,200}$/.test(sessionId)) continue;
1121
+ const filePath = path3.join(sessionsDir, `${sessionId}.jsonl`);
1122
+ let stat;
1123
+ try {
1124
+ stat = fs3.statSync(filePath);
1125
+ } catch {
1126
+ continue;
1127
+ }
1128
+ if (!stat.isFile()) continue;
1129
+ const fileSize = stat.size;
1130
+ const cursorEntry = cursors[sessionId];
1131
+ const previousOffset = Number.isFinite(cursorEntry?.lastObservedOffset) ? Math.max(0, Number(cursorEntry.lastObservedOffset)) : 0;
1132
+ const startOffset = previousOffset <= fileSize ? previousOffset : 0;
1133
+ const newBytes = Math.max(0, fileSize - startOffset);
1134
+ const thresholdBytes = getScaledObservationThresholdBytes(fileSize);
1135
+ if (newBytes >= thresholdBytes) {
1136
+ return true;
1137
+ }
1138
+ if (sessionKey === "main" && newBytes > 0) {
1139
+ }
1140
+ }
1141
+ return false;
1142
+ }
1143
+ function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
1144
+ const args = ["observe", "--cron", "--agent", agentId, "-v", vaultPath];
1145
+ if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
1146
+ args.push("--min-new", String(Math.floor(Number(options.minNewBytes))));
1147
+ }
1148
+ const result = runClawvault(args, pluginConfig, { timeoutMs: 12e4 });
1149
+ return !result.skipped && result.success;
1150
+ }
1151
+ function resolveSessionKey(input) {
1152
+ return sanitizeSessionKey(input);
1153
+ }
1154
+
1155
+ // src/plugin/memory-manager.ts
1156
+ var DEFAULT_MAX_RESULTS = 6;
1157
+ var DEFAULT_MIN_SCORE = 0.2;
1158
+ function clamp(value, min, max) {
1159
+ return Math.max(min, Math.min(max, value));
1160
+ }
1161
+ function normalizeRelPath(relPath) {
1162
+ return relPath.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
1163
+ }
1164
+ function estimateLineRange(content, snippet) {
1165
+ const cleanedSnippet = snippet.replace(/\s+/g, " ").trim();
1166
+ if (!cleanedSnippet) {
1167
+ return { startLine: 1, endLine: 1 };
1168
+ }
1169
+ const normalizedContent = content.replace(/\s+/g, " ");
1170
+ const index = normalizedContent.toLowerCase().indexOf(cleanedSnippet.toLowerCase());
1171
+ if (index < 0) {
1172
+ return { startLine: 1, endLine: Math.max(1, cleanedSnippet.split(/\r?\n/).length) };
1173
+ }
1174
+ const upToIndex = normalizedContent.slice(0, index);
1175
+ const startLine = upToIndex.split(/\r?\n/).length;
1176
+ const endLine = startLine + Math.max(1, cleanedSnippet.split(/\r?\n/).length) - 1;
1177
+ return { startLine, endLine };
1178
+ }
1179
+ function mapSearchResult(vaultPath, result) {
1180
+ const relPath = normalizeRelPath(path4.relative(vaultPath, result.document.path));
1181
+ const { startLine, endLine } = estimateLineRange(result.document.content, result.snippet);
1182
+ const source = relPath === "MEMORY.md" || relPath.startsWith("memory/") ? "memory" : "sessions";
1183
+ return {
1184
+ path: relPath || path4.basename(result.document.path),
1185
+ startLine,
1186
+ endLine,
1187
+ score: result.score,
1188
+ snippet: result.snippet,
1189
+ source,
1190
+ citation: `${relPath || path4.basename(result.document.path)}#L${startLine}-L${endLine}`
1191
+ };
1192
+ }
1193
+ function countMarkdownFiles(root) {
1194
+ if (!fs4.existsSync(root)) return 0;
1195
+ let count = 0;
1196
+ const stack = [root];
1197
+ while (stack.length > 0) {
1198
+ const current = stack.pop();
1199
+ if (!current) continue;
1200
+ let entries;
1201
+ try {
1202
+ entries = fs4.readdirSync(current, { withFileTypes: true });
1203
+ } catch {
1204
+ continue;
1205
+ }
1206
+ for (const entry of entries) {
1207
+ const fullPath = path4.join(current, entry.name);
1208
+ if (entry.isDirectory()) {
1209
+ stack.push(fullPath);
1210
+ continue;
1211
+ }
1212
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
1213
+ count += 1;
1214
+ }
1215
+ }
1216
+ }
1217
+ return count;
1218
+ }
1219
+ function toSafeFilePath(vaultPath, relPath) {
1220
+ const normalized = normalizeRelPath(relPath);
1221
+ const mapped = normalized.startsWith("qmd/") ? normalized.split("/").slice(2).join("/") : normalized;
1222
+ if (!mapped || mapped.includes("..")) {
1223
+ throw new Error("Invalid memory path");
1224
+ }
1225
+ if (!mapped.toLowerCase().endsWith(".md")) {
1226
+ throw new Error("memory_get only allows Markdown note paths inside the vault");
1227
+ }
1228
+ const absolute = path4.resolve(vaultPath, mapped);
1229
+ const vaultRootWithSep = vaultPath.endsWith(path4.sep) ? vaultPath : `${vaultPath}${path4.sep}`;
1230
+ if (absolute !== vaultPath && !absolute.startsWith(vaultRootWithSep)) {
1231
+ throw new Error("Path escapes vault root");
1232
+ }
1233
+ return absolute;
1234
+ }
1235
+ function resolveManagerVaultPath(options, sessionKey) {
1236
+ const derivedAgentId = sessionKey ? extractAgentIdFromSessionKey(sessionKey) : "";
1237
+ const agentId = derivedAgentId || options.defaultAgentId;
1238
+ return resolveVaultPathForAgent(options.pluginConfig, {
1239
+ agentId,
1240
+ cwd: options.workspaceDir
1241
+ });
1242
+ }
1243
+ var ClawVaultMemoryManager = class {
1244
+ options;
1245
+ constructor(options) {
1246
+ this.options = options;
1247
+ }
1248
+ async search(query, opts = {}) {
1249
+ const normalizedQuery = sanitizePromptForContext(query);
1250
+ if (!normalizedQuery) return [];
1251
+ const vaultPath = resolveManagerVaultPath(this.options, opts.sessionKey);
1252
+ if (!vaultPath) return [];
1253
+ const maxResults = Number.isFinite(opts.maxResults) ? clamp(Math.floor(Number(opts.maxResults)), 1, 20) : DEFAULT_MAX_RESULTS;
1254
+ const minScore = Number.isFinite(opts.minScore) ? clamp(Number(opts.minScore), 0, 1) : DEFAULT_MIN_SCORE;
1255
+ try {
1256
+ const vault = new ClawVault(vaultPath);
1257
+ await vault.load();
1258
+ const results = await vault.find(normalizedQuery, {
1259
+ limit: maxResults,
1260
+ minScore,
1261
+ temporalBoost: true
1262
+ });
1263
+ return results.map((result) => mapSearchResult(vaultPath, result));
1264
+ } catch (error) {
1265
+ this.options.logger?.warn(
1266
+ `[clawvault] memory_search fallback error: ${error instanceof Error ? error.message : String(error)}`
1267
+ );
1268
+ return [];
1269
+ }
1270
+ }
1271
+ async readFile(params) {
1272
+ const vaultPath = resolveManagerVaultPath(this.options);
1273
+ const normalizedPath = normalizeRelPath(params.relPath);
1274
+ if (!vaultPath) {
1275
+ return { text: "", path: normalizedPath };
1276
+ }
1277
+ let absolutePath;
1278
+ try {
1279
+ absolutePath = toSafeFilePath(vaultPath, normalizedPath);
1280
+ } catch (error) {
1281
+ throw new Error(error instanceof Error ? error.message : "Invalid memory path");
1282
+ }
1283
+ if (!fs4.existsSync(absolutePath)) {
1284
+ return { text: "", path: normalizedPath };
1285
+ }
1286
+ const raw = fs4.readFileSync(absolutePath, "utf-8");
1287
+ if (!Number.isFinite(params.from) && !Number.isFinite(params.lines)) {
1288
+ return { text: raw, path: normalizedPath };
1289
+ }
1290
+ const from = Number.isFinite(params.from) ? Math.max(1, Math.floor(Number(params.from))) : 1;
1291
+ const lines = Number.isFinite(params.lines) ? Math.max(1, Math.floor(Number(params.lines))) : 120;
1292
+ const chunks = raw.split(/\r?\n/);
1293
+ const startIndex = from - 1;
1294
+ const sliced = chunks.slice(startIndex, startIndex + lines);
1295
+ return {
1296
+ text: sliced.join("\n"),
1297
+ path: normalizedPath
1298
+ };
1299
+ }
1300
+ status() {
1301
+ const vaultPath = resolveManagerVaultPath(this.options);
1302
+ const markdownFiles = vaultPath ? countMarkdownFiles(path4.join(vaultPath, "memory")) : 0;
1303
+ return {
1304
+ backend: "builtin",
1305
+ provider: "clawvault",
1306
+ workspaceDir: vaultPath ?? this.options.workspaceDir,
1307
+ files: markdownFiles,
1308
+ sources: ["memory", "sessions"],
1309
+ vector: {
1310
+ enabled: true,
1311
+ available: hasQmd()
1312
+ }
1313
+ };
1314
+ }
1315
+ async sync(params) {
1316
+ params?.progress?.({ completed: 0, total: 1, label: "syncing" });
1317
+ const vaultPath = resolveManagerVaultPath(this.options);
1318
+ if (vaultPath) {
1319
+ const vault = new ClawVault(vaultPath);
1320
+ await vault.load();
1321
+ }
1322
+ params?.progress?.({ completed: 1, total: 1, label: "done" });
1323
+ }
1324
+ async probeEmbeddingAvailability() {
1325
+ try {
1326
+ const sample = await this.search("health probe", { maxResults: 1, minScore: 0 });
1327
+ if (sample.length >= 0) {
1328
+ return { ok: true };
1329
+ }
1330
+ return { ok: true };
1331
+ } catch (error) {
1332
+ return {
1333
+ ok: false,
1334
+ error: error instanceof Error ? error.message : String(error)
1335
+ };
1336
+ }
1337
+ }
1338
+ async probeVectorAvailability() {
1339
+ return hasQmd();
1340
+ }
1341
+ async close() {
1342
+ return Promise.resolve();
1343
+ }
1344
+ };
1345
+ function buildToolSchema(properties, required = []) {
1346
+ return {
1347
+ type: "object",
1348
+ properties,
1349
+ required,
1350
+ additionalProperties: false
1351
+ };
1352
+ }
1353
+ function resolveToolInput(toolCallIdOrInput, maybeInput) {
1354
+ if (maybeInput && typeof maybeInput === "object" && !Array.isArray(maybeInput)) {
1355
+ return maybeInput;
1356
+ }
1357
+ if (toolCallIdOrInput && typeof toolCallIdOrInput === "object" && !Array.isArray(toolCallIdOrInput)) {
1358
+ return toolCallIdOrInput;
1359
+ }
1360
+ return {};
1361
+ }
1362
+ function createMemorySearchToolFactory(memoryManager) {
1363
+ return (_toolContext) => {
1364
+ const inputSchema = buildToolSchema({
1365
+ query: {
1366
+ type: "string",
1367
+ description: "Natural-language query for memory recall."
1368
+ },
1369
+ maxResults: {
1370
+ type: "number",
1371
+ minimum: 1,
1372
+ maximum: 20,
1373
+ description: "Maximum number of snippets to return."
1374
+ },
1375
+ minScore: {
1376
+ type: "number",
1377
+ minimum: 0,
1378
+ maximum: 1,
1379
+ description: "Minimum score threshold."
1380
+ },
1381
+ sessionKey: {
1382
+ type: "string",
1383
+ description: "Optional OpenClaw session key for scoped recall."
1384
+ }
1385
+ }, ["query"]);
1386
+ const execute = async (toolCallIdOrInput, maybeInput) => {
1387
+ const input = resolveToolInput(toolCallIdOrInput, maybeInput);
1388
+ const query = typeof input.query === "string" ? input.query : "";
1389
+ if (!query.trim()) {
1390
+ return { query, count: 0, results: [] };
1391
+ }
1392
+ const results = await memoryManager.search(query, {
1393
+ maxResults: Number.isFinite(Number(input.maxResults)) ? Number(input.maxResults) : void 0,
1394
+ minScore: Number.isFinite(Number(input.minScore)) ? Number(input.minScore) : void 0,
1395
+ sessionKey: typeof input.sessionKey === "string" ? input.sessionKey : void 0
1396
+ });
1397
+ return {
1398
+ query,
1399
+ count: results.length,
1400
+ results
1401
+ };
1402
+ };
1403
+ return {
1404
+ label: "Memory Search",
1405
+ name: "memory_search",
1406
+ description: "Search ClawVault memory for relevant snippets before answering.",
1407
+ inputSchema,
1408
+ input_schema: inputSchema,
1409
+ parameters: inputSchema,
1410
+ execute,
1411
+ run: execute,
1412
+ handler: execute
1413
+ };
1414
+ };
1415
+ }
1416
+ function createMemoryGetToolFactory(memoryManager) {
1417
+ return (_toolContext) => {
1418
+ const inputSchema = buildToolSchema({
1419
+ path: {
1420
+ type: "string",
1421
+ description: "Relative path from memory_search result (for OpenClaw compatibility)."
1422
+ },
1423
+ relPath: {
1424
+ type: "string",
1425
+ description: "Alias of path (e.g. memory/2026-01-01.md)."
1426
+ },
1427
+ from: {
1428
+ type: "number",
1429
+ minimum: 1,
1430
+ description: "Optional start line (1-indexed)."
1431
+ },
1432
+ lines: {
1433
+ type: "number",
1434
+ minimum: 1,
1435
+ maximum: 400,
1436
+ description: "Optional number of lines to read."
1437
+ }
1438
+ });
1439
+ inputSchema.anyOf = [{ required: ["path"] }, { required: ["relPath"] }];
1440
+ const execute = async (toolCallIdOrInput, maybeInput) => {
1441
+ const input = resolveToolInput(toolCallIdOrInput, maybeInput);
1442
+ const relPath = typeof input.path === "string" ? input.path : typeof input.relPath === "string" ? input.relPath : "";
1443
+ if (!relPath.trim()) {
1444
+ return { path: relPath, text: "" };
1445
+ }
1446
+ return memoryManager.readFile({
1447
+ relPath,
1448
+ from: Number.isFinite(Number(input.from)) ? Number(input.from) : void 0,
1449
+ lines: Number.isFinite(Number(input.lines)) ? Number(input.lines) : void 0
1450
+ });
1451
+ };
1452
+ return {
1453
+ label: "Memory Get",
1454
+ name: "memory_get",
1455
+ description: "Read a specific memory file or line range from ClawVault.",
1456
+ inputSchema,
1457
+ input_schema: inputSchema,
1458
+ parameters: inputSchema,
1459
+ execute,
1460
+ run: execute,
1461
+ handler: execute
1462
+ };
1463
+ };
1464
+ }
1465
+
1466
+ // src/plugin/runtime-state.ts
1467
+ var ClawVaultPluginRuntimeState = class {
1468
+ startupRecoveryNotice = null;
1469
+ sessionContextByKey = /* @__PURE__ */ new Map();
1470
+ lastWeeklyReflectionWeekKey = null;
1471
+ setStartupRecoveryNotice(message) {
1472
+ const trimmed = message.trim();
1473
+ this.startupRecoveryNotice = trimmed || null;
1474
+ }
1475
+ consumeStartupRecoveryNotice() {
1476
+ const notice = this.startupRecoveryNotice;
1477
+ this.startupRecoveryNotice = null;
1478
+ return notice;
1479
+ }
1480
+ setSessionRecap(sessionKey, recapText) {
1481
+ const normalizedSessionKey = sessionKey.trim();
1482
+ if (!normalizedSessionKey) return;
1483
+ this.sessionContextByKey.set(normalizedSessionKey, {
1484
+ recapText: recapText.trim(),
1485
+ initializedAt: (/* @__PURE__ */ new Date()).toISOString(),
1486
+ recapInjected: false
1487
+ });
1488
+ }
1489
+ getSessionRecap(sessionKey) {
1490
+ if (!sessionKey) return null;
1491
+ return this.sessionContextByKey.get(sessionKey) ?? null;
1492
+ }
1493
+ markSessionRecapInjected(sessionKey) {
1494
+ const current = this.sessionContextByKey.get(sessionKey);
1495
+ if (!current) return;
1496
+ this.sessionContextByKey.set(sessionKey, { ...current, recapInjected: true });
1497
+ }
1498
+ clearSession(sessionKey) {
1499
+ if (!sessionKey) return;
1500
+ this.sessionContextByKey.delete(sessionKey);
1501
+ }
1502
+ shouldRunWeeklyReflection(weekKey) {
1503
+ return this.lastWeeklyReflectionWeekKey !== weekKey;
1504
+ }
1505
+ markWeeklyReflectionRun(weekKey) {
1506
+ this.lastWeeklyReflectionWeekKey = weekKey;
1507
+ }
1508
+ };
1509
+
1510
+ // src/plugin/communication-protocol.ts
1511
+ var BANNED_PHRASE_PATTERNS = [
1512
+ { id: "good-catch", regex: /\bgood catch\b[:,]?\s*/gi, replacement: "" },
1513
+ { id: "great-question", regex: /\bgreat question\b[:,]?\s*/gi, replacement: "" },
1514
+ { id: "right-to-call-out", regex: /\byou(?:'|’)re right to call that out\b[:,]?\s*/gi, replacement: "" }
1515
+ ];
1516
+ var RABBIT_HOLE_PATTERNS = [
1517
+ /\bif (?:you(?:'|’)d like|you want(?: me)?(?: to)?|that would help),?\s*i can\b[^.!?]*(?:[.!?]|$)/gi,
1518
+ /\blet me know if you(?:'|’)d like\b[^.!?]*(?:[.!?]|$)/gi
1519
+ ];
1520
+ var QUESTION_OPENERS = /\b(what|why|who|where|when|which|can you|could you|do you|did you|would you|should we)\b/i;
1521
+ function buildCommunicationProtocolAppendix() {
1522
+ return [
1523
+ "ClawVault Communication Protocol:",
1524
+ `- Never say: "good catch", "great question", or "you're right to call that out".`,
1525
+ `- Never offer rabbit-hole phrasing such as "if you'd like I can ...".`,
1526
+ "- Do not ask questions when memory already contains the answer.",
1527
+ "- Use memory tools proactively before answering memory-sensitive prompts."
1528
+ ].join("\n");
1529
+ }
1530
+ function normalizeWhitespace2(value) {
1531
+ return value.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]{2,}/g, " ").trim();
1532
+ }
1533
+ function rewriteOutboundMessage(content) {
1534
+ let rewritten = content;
1535
+ const violations = [];
1536
+ for (const pattern of BANNED_PHRASE_PATTERNS) {
1537
+ if (pattern.regex.test(rewritten)) {
1538
+ violations.push(pattern.id);
1539
+ rewritten = rewritten.replace(pattern.regex, pattern.replacement);
1540
+ }
1541
+ }
1542
+ for (const regex of RABBIT_HOLE_PATTERNS) {
1543
+ if (regex.test(rewritten)) {
1544
+ violations.push("rabbit-hole-offer");
1545
+ rewritten = rewritten.replace(regex, "");
1546
+ }
1547
+ }
1548
+ rewritten = rewritten.replace(/^[,\-:; ]+/, "").replace(/[ ]+([,.!?;:])/g, "$1");
1549
+ rewritten = normalizeWhitespace2(rewritten);
1550
+ if (!rewritten) {
1551
+ rewritten = "Understood.";
1552
+ }
1553
+ return { content: rewritten, violations };
1554
+ }
1555
+ function containsQuestion(content) {
1556
+ if (!content.includes("?")) return false;
1557
+ return QUESTION_OPENERS.test(content.toLowerCase()) || content.trim().endsWith("?");
1558
+ }
1559
+ function rewriteQuestionWithMemoryEvidence(originalContent, memoryHits) {
1560
+ const cleaned = originalContent.replace(/\?/g, ".").replace(/\s+\./g, ".").trim();
1561
+ if (memoryHits.length === 0) {
1562
+ return cleaned || "Proceeding with the available context.";
1563
+ }
1564
+ const topHits = memoryHits.slice(0, 2).map((hit) => {
1565
+ const citation = hit.citation ? ` (${hit.citation})` : "";
1566
+ return `- ${hit.snippet}${citation}`;
1567
+ });
1568
+ const summary = [
1569
+ cleaned || "I checked ClawVault memory before responding.",
1570
+ "",
1571
+ "Memory already contains relevant details:",
1572
+ ...topHits
1573
+ ].join("\n");
1574
+ return normalizeWhitespace2(summary);
1575
+ }
1576
+
1577
+ // src/plugin/vault-context-injector.ts
1578
+ var DEFAULT_MAX_CONTEXT_RESULTS = 4;
1579
+ var DEFAULT_MAX_RECAP_RESULTS = 6;
1580
+ async function fetchSessionRecapEntries(options) {
1581
+ const sessionKey = resolveSessionKey(options.sessionKey);
1582
+ if (!sessionKey) return [];
1583
+ const recapArgs = ["session-recap", sessionKey, "--format", "json"];
1584
+ if (options.agentId) {
1585
+ recapArgs.push("--agent", options.agentId);
1586
+ }
1587
+ const recapResult = runClawvault(recapArgs, options.pluginConfig, { timeoutMs: 2e4 });
1588
+ if (!recapResult.success) {
1589
+ return [];
1590
+ }
1591
+ return parseSessionRecapJson(recapResult.output, DEFAULT_MAX_RECAP_RESULTS);
1592
+ }
1593
+ async function fetchMemoryContextEntries(options) {
1594
+ const prompt = sanitizePromptForContext(options.prompt);
1595
+ if (!prompt) {
1596
+ return { entries: [], vaultPath: null };
1597
+ }
1598
+ const vaultPath = resolveVaultPathForAgent(options.pluginConfig, {
1599
+ agentId: options.agentId,
1600
+ cwd: options.workspaceDir
1601
+ });
1602
+ if (!vaultPath) {
1603
+ return { entries: [], vaultPath: null };
1604
+ }
1605
+ const maxResults = Number.isFinite(options.maxResults) ? Math.max(1, Math.min(20, Number(options.maxResults))) : DEFAULT_MAX_CONTEXT_RESULTS;
1606
+ const profile = options.contextProfile ?? options.pluginConfig.contextProfile ?? "auto";
1607
+ const contextArgs = [
1608
+ "context",
1609
+ prompt,
1610
+ "--format",
1611
+ "json",
1612
+ "--profile",
1613
+ profile,
1614
+ "--limit",
1615
+ String(maxResults),
1616
+ "-v",
1617
+ vaultPath
1618
+ ];
1619
+ const contextResult = runClawvault(contextArgs, options.pluginConfig, { timeoutMs: 25e3 });
1620
+ if (contextResult.success) {
1621
+ return {
1622
+ entries: parseContextJson(contextResult.output, maxResults),
1623
+ vaultPath
1624
+ };
1625
+ }
1626
+ const fallbackSearchArgs = [
1627
+ "search",
1628
+ prompt,
1629
+ "--json",
1630
+ "-n",
1631
+ String(maxResults),
1632
+ "-v",
1633
+ vaultPath
1634
+ ];
1635
+ const fallbackSearchResult = runClawvault(fallbackSearchArgs, options.pluginConfig, { timeoutMs: 25e3 });
1636
+ if (!fallbackSearchResult.success) {
1637
+ return { entries: [], vaultPath };
1638
+ }
1639
+ return {
1640
+ entries: parseContextJson(fallbackSearchResult.output, maxResults),
1641
+ vaultPath
1642
+ };
1643
+ }
1644
+ async function buildVaultContextInjection(options) {
1645
+ const [recapEntries, memoryResult] = await Promise.all([
1646
+ fetchSessionRecapEntries(options),
1647
+ fetchMemoryContextEntries(options)
1648
+ ]);
1649
+ if (recapEntries.length === 0 && memoryResult.entries.length === 0) {
1650
+ return {
1651
+ prependSystemContext: "",
1652
+ memoryEntries: [],
1653
+ recapEntries: [],
1654
+ vaultPath: memoryResult.vaultPath
1655
+ };
1656
+ }
1657
+ return {
1658
+ prependSystemContext: formatSessionContextInjection(recapEntries, memoryResult.entries),
1659
+ memoryEntries: memoryResult.entries,
1660
+ recapEntries,
1661
+ vaultPath: memoryResult.vaultPath
1662
+ };
1663
+ }
1664
+
1665
+ // src/plugin/hooks/before-prompt-build.ts
1666
+ var MEMORY_RECALL_MANDATE = [
1667
+ "ClawVault Memory Recall Policy:",
1668
+ "- Before answering anything about prior work, people, decisions, preferences, todos, or historical context, call memory_search first.",
1669
+ "- If memory_search returns relevant snippets, ground your answer in those snippets and use memory_get when details are needed.",
1670
+ "- Do not guess from stale context when memory lookup is available."
1671
+ ].join("\n");
1672
+ function appendSection(target, section) {
1673
+ if (!section) return;
1674
+ const trimmed = section.trim();
1675
+ if (!trimmed) return;
1676
+ target.push(trimmed);
1677
+ }
1678
+ function createBeforePromptBuildHandler(dependencies) {
1679
+ const contextInjector = dependencies.contextInjector ?? buildVaultContextInjection;
1680
+ return async (event, ctx) => {
1681
+ const prependSections = [];
1682
+ const appendSections = [];
1683
+ const recallEnabled = isFeatureEnabled(dependencies.pluginConfig, "enableBeforePromptRecall", true);
1684
+ const protocolEnabled = isFeatureEnabled(dependencies.pluginConfig, "enforceCommunicationProtocol", true);
1685
+ const contextInjectionEnabled = isFeatureEnabled(dependencies.pluginConfig, "enableSessionContextInjection", true);
1686
+ if (recallEnabled) {
1687
+ prependSections.push(MEMORY_RECALL_MANDATE);
1688
+ }
1689
+ const startupNotice = dependencies.runtimeState.consumeStartupRecoveryNotice();
1690
+ appendSection(prependSections, startupNotice ? `[ClawVault Recovery]
1691
+ ${startupNotice}` : "");
1692
+ if (ctx.sessionKey) {
1693
+ const sessionCacheEntry = dependencies.runtimeState.getSessionRecap(ctx.sessionKey);
1694
+ if (sessionCacheEntry?.recapText && !sessionCacheEntry.recapInjected) {
1695
+ appendSection(prependSections, sessionCacheEntry.recapText);
1696
+ dependencies.runtimeState.markSessionRecapInjected(ctx.sessionKey);
1697
+ }
1698
+ }
1699
+ if (contextInjectionEnabled) {
1700
+ const injection = await contextInjector({
1701
+ prompt: event.prompt,
1702
+ sessionKey: ctx.sessionKey,
1703
+ agentId: ctx.agentId,
1704
+ workspaceDir: ctx.workspaceDir,
1705
+ pluginConfig: dependencies.pluginConfig,
1706
+ contextProfile: dependencies.pluginConfig.contextProfile,
1707
+ maxResults: dependencies.pluginConfig.maxContextResults
1708
+ });
1709
+ appendSection(prependSections, injection.prependSystemContext);
1710
+ }
1711
+ if (protocolEnabled) {
1712
+ appendSections.push(buildCommunicationProtocolAppendix());
1713
+ }
1714
+ if (prependSections.length === 0 && appendSections.length === 0) {
1715
+ return;
1716
+ }
1717
+ return {
1718
+ prependSystemContext: prependSections.join("\n\n"),
1719
+ appendSystemContext: appendSections.join("\n\n")
1720
+ };
1721
+ };
1722
+ }
1723
+
1724
+ // src/plugin/hooks/message-sending.ts
1725
+ var DEFAULT_QUESTION_RECALL_MIN_SCORE = 0.35;
1726
+ function normalizeScoreThreshold(config) {
1727
+ const raw = config.minQuestionRecallScore;
1728
+ if (!Number.isFinite(raw)) return DEFAULT_QUESTION_RECALL_MIN_SCORE;
1729
+ return Math.max(0, Math.min(1, Number(raw)));
1730
+ }
1731
+ function createMessageSendingHandler(dependencies) {
1732
+ return async (event, _ctx) => {
1733
+ const filterEnabled = isFeatureEnabled(dependencies.pluginConfig, "enableMessageSendingFilter", true);
1734
+ if (!filterEnabled) {
1735
+ return;
1736
+ }
1737
+ const rewritten = rewriteOutboundMessage(event.content);
1738
+ let content = rewritten.content;
1739
+ let shouldCancel = false;
1740
+ if (containsQuestion(content)) {
1741
+ const hits = await dependencies.memoryManager.search(content, {
1742
+ maxResults: 2,
1743
+ minScore: normalizeScoreThreshold(dependencies.pluginConfig)
1744
+ });
1745
+ if (hits.length > 0) {
1746
+ content = rewriteQuestionWithMemoryEvidence(content, hits);
1747
+ if (containsQuestion(content)) {
1748
+ shouldCancel = true;
1749
+ }
1750
+ }
1751
+ }
1752
+ if (!shouldCancel && content === event.content) {
1753
+ return;
1754
+ }
1755
+ if (shouldCancel) {
1756
+ return { cancel: true };
1757
+ }
1758
+ return { content };
1759
+ };
1760
+ }
1761
+
1762
+ // src/plugin/fact-extractor.ts
1763
+ import * as fs5 from "fs";
1764
+ import * as path5 from "path";
1765
+ import { createHash as createHash2 } from "crypto";
1766
+ var FACTS_FILE = "facts.jsonl";
1767
+ var ENTITY_GRAPH_FILE = "entity-graph.json";
1768
+ var MAX_TEXT_LENGTH = 6e3;
1769
+ function ensureClawVaultDir(vaultPath) {
1770
+ const dir = path5.join(vaultPath, ".clawvault");
1771
+ if (!fs5.existsSync(dir)) {
1772
+ fs5.mkdirSync(dir, { recursive: true });
1773
+ }
1774
+ return dir;
1775
+ }
1776
+ function sanitizeText(value, maxLength = MAX_TEXT_LENGTH) {
1777
+ if (typeof value !== "string") return "";
1778
+ return value.replace(/[\x00-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim().slice(0, maxLength);
1779
+ }
1780
+ function collectTextFragments(target, input, depth = 0) {
1781
+ if (depth > 3 || input === null || input === void 0) return;
1782
+ if (typeof input === "string") {
1783
+ const cleaned = sanitizeText(input);
1784
+ if (cleaned) target.push(cleaned);
1785
+ return;
1786
+ }
1787
+ if (Array.isArray(input)) {
1788
+ for (const item of input) {
1789
+ collectTextFragments(target, item, depth + 1);
1790
+ }
1791
+ return;
1792
+ }
1793
+ if (typeof input !== "object") return;
1794
+ const record = input;
1795
+ const directKeys = ["text", "message", "content", "rawText", "prompt", "observation"];
1796
+ for (const key of directKeys) {
1797
+ const cleaned = sanitizeText(record[key]);
1798
+ if (cleaned) target.push(cleaned);
1799
+ }
1800
+ const nestedKeys = ["messages", "history", "entries", "items", "events", "payload", "context"];
1801
+ for (const key of nestedKeys) {
1802
+ collectTextFragments(target, record[key], depth + 1);
1803
+ }
1804
+ }
1805
+ function dedupeTexts(items) {
1806
+ const seen = /* @__PURE__ */ new Set();
1807
+ const deduped = [];
1808
+ for (const item of items) {
1809
+ const cleaned = sanitizeText(item);
1810
+ if (!cleaned || seen.has(cleaned)) continue;
1811
+ seen.add(cleaned);
1812
+ deduped.push(cleaned);
1813
+ }
1814
+ return deduped;
1815
+ }
1816
+ function collectObservedTextsForFactExtraction(event) {
1817
+ const collected = [];
1818
+ collectTextFragments(collected, event);
1819
+ return dedupeTexts(collected);
1820
+ }
1821
+ function extractTimestamp(event) {
1822
+ if (!event || typeof event !== "object") {
1823
+ return (/* @__PURE__ */ new Date()).toISOString();
1824
+ }
1825
+ const record = event;
1826
+ const candidates = [
1827
+ record.timestamp,
1828
+ record.scheduledAt,
1829
+ record.time,
1830
+ record.context?.timestamp
1831
+ ];
1832
+ for (const candidate of candidates) {
1833
+ if (!candidate) continue;
1834
+ const date = new Date(String(candidate));
1835
+ if (!Number.isNaN(date.getTime())) {
1836
+ return date.toISOString();
1837
+ }
1838
+ }
1839
+ return (/* @__PURE__ */ new Date()).toISOString();
1840
+ }
1841
+ function buildEntityGraph(facts) {
1842
+ const nodes = /* @__PURE__ */ new Map();
1843
+ const edges = [];
1844
+ for (const fact of facts) {
1845
+ const sourceId = `entity:${fact.entityNorm || fact.entity.toLowerCase()}`;
1846
+ const sourceNode = nodes.get(sourceId) ?? {
1847
+ id: sourceId,
1848
+ name: fact.entityNorm || fact.entity.toLowerCase(),
1849
+ displayName: fact.entity,
1850
+ type: "person",
1851
+ attributes: { entityNorm: fact.entityNorm || fact.entity.toLowerCase() },
1852
+ lastSeen: fact.validFrom
1853
+ };
1854
+ if (new Date(fact.validFrom).getTime() > new Date(sourceNode.lastSeen).getTime()) {
1855
+ sourceNode.lastSeen = fact.validFrom;
1856
+ }
1857
+ nodes.set(sourceId, sourceNode);
1858
+ const normalizedValue = fact.value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1859
+ const targetId = `value:${fact.relation}:${normalizedValue || "unknown"}`;
1860
+ const targetNode = nodes.get(targetId) ?? {
1861
+ id: targetId,
1862
+ name: normalizedValue || "unknown",
1863
+ displayName: fact.value,
1864
+ type: "attribute",
1865
+ attributes: { relation: fact.relation },
1866
+ lastSeen: fact.validFrom
1867
+ };
1868
+ if (new Date(fact.validFrom).getTime() > new Date(targetNode.lastSeen).getTime()) {
1869
+ targetNode.lastSeen = fact.validFrom;
1870
+ }
1871
+ nodes.set(targetId, targetNode);
1872
+ const edgeHash = createHash2("sha1").update(`${fact.id}|${sourceId}|${targetId}|${fact.validFrom}`).digest("hex").slice(0, 18);
1873
+ edges.push({
1874
+ id: `edge:${edgeHash}`,
1875
+ source: sourceId,
1876
+ target: targetId,
1877
+ relation: fact.relation,
1878
+ validFrom: fact.validFrom,
1879
+ validUntil: fact.validUntil,
1880
+ confidence: Math.max(0, Math.min(1, fact.confidence ?? 0.7))
1881
+ });
1882
+ }
1883
+ return {
1884
+ version: 1,
1885
+ nodes: [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id)),
1886
+ edges: edges.sort((a, b) => a.id.localeCompare(b.id))
1887
+ };
1888
+ }
1889
+ function ensureFactsLogFile(vaultPath) {
1890
+ const filePath = path5.join(ensureClawVaultDir(vaultPath), FACTS_FILE);
1891
+ if (!fs5.existsSync(filePath)) {
1892
+ fs5.writeFileSync(filePath, "", "utf-8");
1893
+ }
1894
+ }
1895
+ function persistFactsAndGraph(vaultPath, extractedFacts) {
1896
+ ensureFactsLogFile(vaultPath);
1897
+ const store = new FactStore(vaultPath);
1898
+ store.load();
1899
+ const conflictsResolved = store.addFacts(extractedFacts);
1900
+ store.save();
1901
+ const allFacts = store.getAllFacts();
1902
+ const graph = buildEntityGraph(allFacts);
1903
+ const graphPath = path5.join(ensureClawVaultDir(vaultPath), ENTITY_GRAPH_FILE);
1904
+ fs5.writeFileSync(graphPath, `${JSON.stringify(graph, null, 2)}
1905
+ `, "utf-8");
1906
+ return {
1907
+ extracted: extractedFacts.length,
1908
+ added: Math.max(0, extractedFacts.length - conflictsResolved),
1909
+ conflictsResolved,
1910
+ totalFacts: allFacts.length
1911
+ };
1912
+ }
1913
+ function runFactExtractionForEvent(vaultPath, event, sourceLabel) {
1914
+ const observedTexts = collectObservedTextsForFactExtraction(event);
1915
+ if (observedTexts.length === 0) {
1916
+ return { extracted: 0, added: 0, conflictsResolved: 0, totalFacts: 0 };
1917
+ }
1918
+ const validFrom = extractTimestamp(event);
1919
+ const source = `hook:${sourceLabel}`;
1920
+ const facts = [];
1921
+ for (const text of observedTexts) {
1922
+ facts.push(...extractFactsRuleBased(text, source, validFrom));
1923
+ }
1924
+ if (facts.length === 0) {
1925
+ return { extracted: 0, added: 0, conflictsResolved: 0, totalFacts: 0 };
1926
+ }
1927
+ return persistFactsAndGraph(vaultPath, facts);
1928
+ }
1929
+
1930
+ // src/plugin/hooks/session-lifecycle.ts
1931
+ function getWeekKey(date) {
1932
+ const year = date.getUTCFullYear();
1933
+ const start = Date.UTC(year, 0, 1);
1934
+ const days = Math.floor((date.getTime() - start) / (24 * 60 * 60 * 1e3));
1935
+ const week = Math.floor(days / 7) + 1;
1936
+ return `${year}-W${String(week).padStart(2, "0")}`;
1937
+ }
1938
+ function isSundayMidnightUtc(date) {
1939
+ return date.getUTCDay() === 0 && date.getUTCHours() === 0 && date.getUTCMinutes() === 0;
1940
+ }
1941
+ async function runWeeklyReflectionIfNeeded(deps, agentId, workspaceDir) {
1942
+ if (!isOptInEnabled(deps.pluginConfig, "enableWeeklyReflection", "weeklyReflection")) {
1943
+ return;
1944
+ }
1945
+ const now = /* @__PURE__ */ new Date();
1946
+ if (!isSundayMidnightUtc(now)) {
1947
+ return;
1948
+ }
1949
+ const weekKey = getWeekKey(now);
1950
+ if (!deps.runtimeState.shouldRunWeeklyReflection(weekKey)) {
1951
+ return;
1952
+ }
1953
+ const vaultPath = resolveVaultPathForAgent(deps.pluginConfig, {
1954
+ agentId,
1955
+ cwd: workspaceDir
1956
+ });
1957
+ if (!vaultPath) {
1958
+ return;
1959
+ }
1960
+ const result = runClawvault(["reflect", "-v", vaultPath], deps.pluginConfig, {
1961
+ timeoutMs: 12e4
1962
+ });
1963
+ if (result.success) {
1964
+ deps.runtimeState.markWeeklyReflectionRun(weekKey);
1965
+ deps.logger?.info("[clawvault] Weekly reflection complete");
1966
+ } else if (!result.skipped) {
1967
+ deps.logger?.warn("[clawvault] Weekly reflection failed");
1968
+ }
1969
+ }
1970
+ async function handleGatewayStart(event, ctx, deps) {
1971
+ const shouldRecover = isOptInEnabled(deps.pluginConfig, "enableStartupRecovery");
1972
+ if (!shouldRecover) {
1973
+ return;
1974
+ }
1975
+ const vaultPath = resolveVaultPathForAgent(deps.pluginConfig, { cwd: process.cwd(), agentId: "main" });
1976
+ if (!vaultPath) {
1977
+ deps.logger?.warn("[clawvault] No vault found, skipping startup recovery");
1978
+ return;
1979
+ }
1980
+ const result = runClawvault(["recover", "--clear", "-v", vaultPath], deps.pluginConfig, {
1981
+ timeoutMs: 2e4
1982
+ });
1983
+ if (result.skipped) {
1984
+ return;
1985
+ }
1986
+ if (!result.success) {
1987
+ deps.logger?.warn("[clawvault] Startup recovery command failed");
1988
+ return;
1989
+ }
1990
+ const parsed = parseRecoveryOutput(result.output);
1991
+ if (parsed.hadDeath) {
1992
+ const message = parsed.workingOn ? `[ClawVault] Context death detected. Last working on: ${parsed.workingOn}. Run \`clawvault wake\` for full recovery context.` : "[ClawVault] Context death detected. Run `clawvault wake` for full recovery context.";
1993
+ deps.runtimeState.setStartupRecoveryNotice(message);
1994
+ deps.logger?.warn("[clawvault] Context death detected at startup");
1995
+ }
1996
+ if (ctx.port || event.port) {
1997
+ }
1998
+ }
1999
+ async function handleSessionStart(event, ctx, deps) {
2000
+ const sessionKey = sanitizeSessionKey(ctx.sessionKey ?? event.sessionKey);
2001
+ const agentId = resolveAgentId({ agentId: ctx.agentId, sessionKey }, deps.pluginConfig);
2002
+ if (isOptInEnabled(deps.pluginConfig, "enableSessionContextInjection")) {
2003
+ const recapEntries = await fetchSessionRecapEntries({
2004
+ sessionKey,
2005
+ agentId: extractAgentIdFromSessionKey(sessionKey) || agentId,
2006
+ pluginConfig: deps.pluginConfig
2007
+ });
2008
+ if (recapEntries.length > 0) {
2009
+ const recapInjection = formatSessionContextInjection(recapEntries, []);
2010
+ deps.runtimeState.setSessionRecap(sessionKey, recapInjection);
2011
+ }
2012
+ }
2013
+ await runWeeklyReflectionIfNeeded(deps, agentId, void 0);
2014
+ }
2015
+ function sanitizeForCheckpoint(value, maxLength) {
2016
+ if (typeof value !== "string") return "unknown";
2017
+ const cleaned = value.replace(/[^a-zA-Z0-9:_ -]/g, "").trim();
2018
+ return cleaned.slice(0, maxLength) || "unknown";
2019
+ }
2020
+ async function handleBeforeReset(event, ctx, deps) {
2021
+ const autoCheckpointEnabled = isOptInEnabled(deps.pluginConfig, "enableAutoCheckpoint", "autoCheckpoint");
2022
+ const observerOnResetEnabled = isOptInEnabled(deps.pluginConfig, "enableObserveOnNew");
2023
+ const factExtractionEnabled = isOptInEnabled(deps.pluginConfig, "enableFactExtraction");
2024
+ if (!autoCheckpointEnabled && !observerOnResetEnabled && !factExtractionEnabled) {
2025
+ return;
2026
+ }
2027
+ const sessionKey = sanitizeSessionKey(ctx.sessionKey);
2028
+ const agentId = resolveAgentId(ctx, deps.pluginConfig);
2029
+ const vaultPath = resolveVaultPathForAgent(deps.pluginConfig, {
2030
+ agentId,
2031
+ cwd: ctx.workspaceDir
2032
+ });
2033
+ if (!vaultPath) {
2034
+ return;
2035
+ }
2036
+ if (autoCheckpointEnabled) {
2037
+ const safeSessionKey = sanitizeForCheckpoint(sessionKey, 120);
2038
+ const safeReason = sanitizeForCheckpoint(event.reason ?? "before_reset", 80);
2039
+ const checkpointResult = runClawvault([
2040
+ "checkpoint",
2041
+ "--working-on",
2042
+ `Session reset via ${safeReason}`,
2043
+ "--focus",
2044
+ `Pre-reset checkpoint, session: ${safeSessionKey}`,
2045
+ "-v",
2046
+ vaultPath
2047
+ ], deps.pluginConfig, { timeoutMs: 3e4 });
2048
+ if (!checkpointResult.success && !checkpointResult.skipped) {
2049
+ deps.logger?.warn("[clawvault] Auto-checkpoint before reset failed");
2050
+ }
2051
+ }
2052
+ if (observerOnResetEnabled) {
2053
+ runObserverCron(vaultPath, agentId, deps.pluginConfig, {
2054
+ minNewBytes: 1,
2055
+ reason: "before_reset"
2056
+ });
2057
+ }
2058
+ if (factExtractionEnabled) {
2059
+ runFactExtractionForEvent(vaultPath, event, "before_reset");
2060
+ }
2061
+ }
2062
+ async function handleSessionEnd(event, ctx, deps) {
2063
+ deps.runtimeState.clearSession(ctx.sessionKey ?? event.sessionKey);
2064
+ }
2065
+
2066
+ // src/plugin/hooks/observation.ts
2067
+ async function handleAgentEndHeartbeat(event, ctx, deps) {
2068
+ if (!isOptInEnabled(deps.pluginConfig, "enableHeartbeatObservation", "observeOnHeartbeat")) {
2069
+ return;
2070
+ }
2071
+ const agentId = resolveAgentId(ctx, deps.pluginConfig);
2072
+ const vaultPath = resolveVaultPathForAgent(deps.pluginConfig, {
2073
+ agentId,
2074
+ cwd: ctx.workspaceDir
2075
+ });
2076
+ if (!vaultPath) {
2077
+ return;
2078
+ }
2079
+ if (!shouldObserveActiveSessions(vaultPath, agentId, deps.pluginConfig)) {
2080
+ return;
2081
+ }
2082
+ const observed = runObserverCron(vaultPath, agentId, deps.pluginConfig, {
2083
+ reason: "agent_end heartbeat"
2084
+ });
2085
+ if (!observed) {
2086
+ deps.logger?.warn("[clawvault] Heartbeat observation trigger failed");
2087
+ }
2088
+ if (!event.success && event.error) {
2089
+ deps.logger?.info(`[clawvault] Agent ended with error: ${event.error}`);
2090
+ }
2091
+ }
2092
+ async function handleBeforeCompactionObservation(event, ctx, deps) {
2093
+ const compactionObserveEnabled = isOptInEnabled(deps.pluginConfig, "enableCompactionObservation");
2094
+ const factExtractionEnabled = isOptInEnabled(deps.pluginConfig, "enableFactExtraction");
2095
+ if (!compactionObserveEnabled && !factExtractionEnabled) {
2096
+ return;
2097
+ }
2098
+ const agentId = resolveAgentId(ctx, deps.pluginConfig);
2099
+ const vaultPath = resolveVaultPathForAgent(deps.pluginConfig, {
2100
+ agentId,
2101
+ cwd: ctx.workspaceDir
2102
+ });
2103
+ if (!vaultPath) {
2104
+ return;
2105
+ }
2106
+ if (compactionObserveEnabled) {
2107
+ runObserverCron(vaultPath, agentId, deps.pluginConfig, {
2108
+ minNewBytes: 1,
2109
+ reason: "before_compaction"
2110
+ });
2111
+ }
2112
+ if (factExtractionEnabled) {
2113
+ runFactExtractionForEvent(vaultPath, event, "before_compaction");
2114
+ }
2115
+ }
2116
+
2117
+ // src/openclaw-plugin.ts
2118
+ function isOpenClawPluginApi(value) {
2119
+ if (!value || typeof value !== "object") return false;
2120
+ const record = value;
2121
+ return typeof record.on === "function" && typeof record.registerTool === "function" && typeof record.logger === "object";
2122
+ }
2123
+ async function registerOpenClawPlugin(api) {
2124
+ const pluginConfig = readPluginConfig(api);
2125
+ const runtimeState = new ClawVaultPluginRuntimeState();
2126
+ const memoryManager = new ClawVaultMemoryManager({
2127
+ pluginConfig,
2128
+ defaultAgentId: "main",
2129
+ logger: {
2130
+ debug: api.logger.debug,
2131
+ warn: api.logger.warn
2132
+ }
2133
+ });
2134
+ const memorySearchTool = createMemorySearchToolFactory(memoryManager)();
2135
+ const memoryGetTool = createMemoryGetToolFactory(memoryManager)();
2136
+ api.registerTool(memorySearchTool, { name: "memory_search" });
2137
+ api.registerTool(memoryGetTool, { name: "memory_get" });
2138
+ api.on("before_prompt_build", createBeforePromptBuildHandler({
2139
+ pluginConfig,
2140
+ runtimeState
2141
+ }), { priority: 30 });
2142
+ api.on("message_sending", createMessageSendingHandler({
2143
+ pluginConfig,
2144
+ memoryManager
2145
+ }), { priority: 20 });
2146
+ api.on("gateway_start", async (event, ctx) => {
2147
+ await handleGatewayStart(event, ctx, {
2148
+ pluginConfig,
2149
+ runtimeState,
2150
+ logger: api.logger
2151
+ });
2152
+ });
2153
+ api.on("session_start", async (event, ctx) => {
2154
+ await handleSessionStart(event, ctx, {
2155
+ pluginConfig,
2156
+ runtimeState,
2157
+ logger: api.logger
2158
+ });
2159
+ });
2160
+ api.on("session_end", async (event, ctx) => {
2161
+ await handleSessionEnd(event, ctx, {
2162
+ pluginConfig,
2163
+ runtimeState,
2164
+ logger: api.logger
2165
+ });
2166
+ });
2167
+ api.on("before_reset", async (event, ctx) => {
2168
+ await handleBeforeReset(event, ctx, {
2169
+ pluginConfig,
2170
+ runtimeState,
2171
+ logger: api.logger
2172
+ });
2173
+ });
2174
+ api.on("before_compaction", async (event, ctx) => {
2175
+ await handleBeforeCompactionObservation(event, ctx, {
2176
+ pluginConfig,
2177
+ logger: api.logger
2178
+ });
2179
+ });
2180
+ api.on("agent_end", async (event, ctx) => {
2181
+ await handleAgentEndHeartbeat(event, ctx, {
2182
+ pluginConfig,
2183
+ logger: api.logger
2184
+ });
2185
+ });
2186
+ return {
2187
+ plugins: {
2188
+ slots: {
2189
+ memory: memoryManager
2190
+ }
2191
+ }
2192
+ };
2193
+ }
2194
+ var clawvaultPlugin = {
2195
+ id: "clawvault",
2196
+ name: "ClawVault",
2197
+ kind: "memory",
2198
+ description: "Structured memory system for AI agents with proactive recall and protocol-safe messaging",
2199
+ async register(apiOrRuntime) {
2200
+ if (isOpenClawPluginApi(apiOrRuntime)) {
2201
+ return registerOpenClawPlugin(apiOrRuntime);
2202
+ }
2203
+ if (apiOrRuntime && typeof apiOrRuntime === "object") {
2204
+ registerMemorySlot(apiOrRuntime);
2205
+ }
2206
+ return createMemorySlotPlugin();
2207
+ }
2208
+ };
2209
+ var openclaw_plugin_default = clawvaultPlugin;
2210
+
2211
+ export {
2212
+ extractTaggedMemoryNotes,
2213
+ extractHeuristicMemories,
2214
+ extractMemoriesFromAssistantResponse,
2215
+ isLikelyJunkMemory,
2216
+ plausibilityScore,
2217
+ evaluateCandidateQuality,
2218
+ LiveCaptureService,
2219
+ createMemorySlot,
2220
+ createMemorySlotPlugin,
2221
+ registerMemorySlot,
2222
+ openclaw_plugin_default
2223
+ };