@brianluby/agent-brain 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1820 @@
1
+ import { existsSync, statSync, readdirSync, unlinkSync, readFileSync, mkdirSync, writeFileSync, renameSync, rmSync } from 'fs';
2
+ import { relative, basename, dirname, isAbsolute, resolve, sep } from 'path';
3
+ import { tool } from '@opencode-ai/plugin';
4
+ import { mkdir, open } from 'fs/promises';
5
+ import { randomBytes } from 'crypto';
6
+ import lockfile from 'proper-lockfile';
7
+ import { tmpdir } from 'os';
8
+
9
+ // src/opencode/plugin.ts
10
+
11
+ // src/types.ts
12
+ var DEFAULT_MEMORY_PATH = ".agent-brain/mind.mv2";
13
+ var DEFAULT_CONFIG = {
14
+ memoryPath: DEFAULT_MEMORY_PATH,
15
+ maxContextObservations: 20,
16
+ maxContextTokens: 2e3,
17
+ autoCompress: true,
18
+ minConfidence: 0.6,
19
+ debug: false
20
+ };
21
+ function generateId() {
22
+ return randomBytes(8).toString("hex");
23
+ }
24
+ function estimateTokens(text) {
25
+ return Math.ceil(text.length / 4);
26
+ }
27
+ function classifyObservationType(toolName, output) {
28
+ const lowerOutput = output.toLowerCase();
29
+ if (lowerOutput.includes("error") || lowerOutput.includes("failed") || lowerOutput.includes("exception")) {
30
+ return "problem";
31
+ }
32
+ if (lowerOutput.includes("success") || lowerOutput.includes("passed") || lowerOutput.includes("completed")) {
33
+ return "success";
34
+ }
35
+ if (lowerOutput.includes("warning") || lowerOutput.includes("deprecated")) {
36
+ return "warning";
37
+ }
38
+ switch (toolName) {
39
+ case "Read":
40
+ case "Glob":
41
+ case "Grep":
42
+ return "discovery";
43
+ case "Edit":
44
+ if (lowerOutput.includes("fix") || lowerOutput.includes("bug")) {
45
+ return "bugfix";
46
+ }
47
+ return "refactor";
48
+ case "Write":
49
+ return "feature";
50
+ default:
51
+ return "discovery";
52
+ }
53
+ }
54
+ var LOCK_OPTIONS = {
55
+ stale: 3e4,
56
+ retries: {
57
+ retries: 1e3,
58
+ minTimeout: 5,
59
+ maxTimeout: 50
60
+ }
61
+ };
62
+ async function withMemvidLock(lockPath, fn) {
63
+ await mkdir(dirname(lockPath), { recursive: true });
64
+ const handle = await open(lockPath, "a");
65
+ await handle.close();
66
+ const release = await lockfile.lock(lockPath, LOCK_OPTIONS);
67
+ try {
68
+ return await fn();
69
+ } finally {
70
+ await release();
71
+ }
72
+ }
73
+ function defaultPlatformRelativePath(platform) {
74
+ const normalizedPlatform = platform.trim().toLowerCase();
75
+ const safePlatform = normalizedPlatform.replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "") || "unknown";
76
+ return `.agent-brain/mind-${safePlatform}.mv2`;
77
+ }
78
+ function resolveInsideProject(projectDir, candidatePath) {
79
+ if (isAbsolute(candidatePath)) {
80
+ return resolve(candidatePath);
81
+ }
82
+ const root = resolve(projectDir);
83
+ const resolved = resolve(root, candidatePath);
84
+ const rel = relative(root, resolved);
85
+ if (rel === ".." || rel.startsWith(`..${sep}`)) {
86
+ throw new Error("Resolved memory path must stay inside projectDir");
87
+ }
88
+ return resolved;
89
+ }
90
+ function resolveMemoryPathPolicy(input) {
91
+ const mode = input.platformOptIn ? "platform_opt_in" : "legacy_first";
92
+ const canonicalRelativePath = input.platformOptIn ? input.platformRelativePath || defaultPlatformRelativePath(input.platform) : input.defaultRelativePath;
93
+ const canonicalPath = resolveInsideProject(input.projectDir, canonicalRelativePath);
94
+ if (existsSync(canonicalPath)) {
95
+ return {
96
+ mode,
97
+ memoryPath: canonicalPath,
98
+ canonicalPath
99
+ };
100
+ }
101
+ const fallbackPaths = (input.legacyRelativePaths || []).map((relativePath) => resolveInsideProject(input.projectDir, relativePath));
102
+ for (const fallbackPath of fallbackPaths) {
103
+ if (existsSync(fallbackPath)) {
104
+ return {
105
+ mode,
106
+ memoryPath: fallbackPath,
107
+ canonicalPath,
108
+ migrationSuggestion: {
109
+ fromPath: fallbackPath,
110
+ toPath: canonicalPath
111
+ }
112
+ };
113
+ }
114
+ }
115
+ if (input.platformOptIn) {
116
+ return {
117
+ mode: "platform_opt_in",
118
+ memoryPath: canonicalPath,
119
+ canonicalPath
120
+ };
121
+ }
122
+ return {
123
+ mode: "legacy_first",
124
+ memoryPath: canonicalPath,
125
+ canonicalPath
126
+ };
127
+ }
128
+
129
+ // src/platforms/platform-detector.ts
130
+ function normalizePlatform(value) {
131
+ if (!value) return void 0;
132
+ const normalized = value.trim().toLowerCase();
133
+ return normalized.length > 0 ? normalized : void 0;
134
+ }
135
+ function detectPlatformFromEnv() {
136
+ const explicitFromEnv = normalizePlatform(process.env.MEMVID_PLATFORM);
137
+ if (explicitFromEnv) {
138
+ return explicitFromEnv;
139
+ }
140
+ if (process.env.OPENCODE === "1") {
141
+ return "opencode";
142
+ }
143
+ return "claude";
144
+ }
145
+
146
+ // src/core/mind.ts
147
+ function pruneBackups(memoryPath, keepCount) {
148
+ try {
149
+ const dir = dirname(memoryPath);
150
+ const baseName = memoryPath.split("/").pop() || "mind.mv2";
151
+ const backupPattern = new RegExp(`^${baseName.replace(".", "\\.")}\\.backup-\\d+$`);
152
+ const files = readdirSync(dir);
153
+ const backups = files.filter((f) => backupPattern.test(f)).map((f) => ({
154
+ name: f,
155
+ path: resolve(dir, f),
156
+ time: parseInt(f.split("-").pop() || "0", 10)
157
+ })).sort((a, b) => b.time - a.time);
158
+ for (let i = keepCount; i < backups.length; i++) {
159
+ try {
160
+ unlinkSync(backups[i].path);
161
+ console.error(`[memvid-mind] Pruned old backup: ${backups[i].name}`);
162
+ } catch {
163
+ }
164
+ }
165
+ } catch {
166
+ }
167
+ }
168
+ var sdkLoaded = false;
169
+ var use;
170
+ var create;
171
+ async function loadSDK() {
172
+ if (sdkLoaded) return;
173
+ const sdk = await import('@memvid/sdk');
174
+ use = sdk.use;
175
+ create = sdk.create;
176
+ sdkLoaded = true;
177
+ }
178
+ var OBSERVATION_TYPE_KEYS = [
179
+ "discovery",
180
+ "decision",
181
+ "problem",
182
+ "solution",
183
+ "pattern",
184
+ "warning",
185
+ "success",
186
+ "refactor",
187
+ "bugfix",
188
+ "feature"
189
+ ];
190
+ var OBSERVATION_TYPE_SET = new Set(OBSERVATION_TYPE_KEYS);
191
+ function emptyTypeCounts() {
192
+ return {
193
+ discovery: 0,
194
+ decision: 0,
195
+ problem: 0,
196
+ solution: 0,
197
+ pattern: 0,
198
+ warning: 0,
199
+ success: 0,
200
+ refactor: 0,
201
+ bugfix: 0,
202
+ feature: 0
203
+ };
204
+ }
205
+ var Mind = class _Mind {
206
+ memvid;
207
+ config;
208
+ memoryPath;
209
+ sessionId;
210
+ sessionStartTime;
211
+ sessionObservationCount = 0;
212
+ cachedStats = null;
213
+ cachedStatsFrameCount = -1;
214
+ initialized = false;
215
+ constructor(memvid, config, memoryPath) {
216
+ this.memvid = memvid;
217
+ this.config = config;
218
+ this.memoryPath = memoryPath;
219
+ this.sessionId = generateId();
220
+ this.sessionStartTime = Date.now();
221
+ }
222
+ /**
223
+ * Open or create a Mind instance
224
+ */
225
+ static async open(configOverrides = {}) {
226
+ await loadSDK();
227
+ const config = { ...DEFAULT_CONFIG, ...configOverrides };
228
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.env.OPENCODE_PROJECT_DIR || process.cwd();
229
+ const platform = detectPlatformFromEnv();
230
+ const optIn = process.env.MEMVID_PLATFORM_PATH_OPT_IN === "1";
231
+ const legacyFallbacks = config.memoryPath === DEFAULT_MEMORY_PATH ? [".claude/mind.mv2"] : [];
232
+ const pathPolicy = resolveMemoryPathPolicy({
233
+ projectDir,
234
+ platform,
235
+ defaultRelativePath: config.memoryPath,
236
+ legacyRelativePaths: legacyFallbacks,
237
+ platformRelativePath: process.env.MEMVID_PLATFORM_MEMORY_PATH,
238
+ platformOptIn: optIn
239
+ });
240
+ const memoryPath = pathPolicy.memoryPath;
241
+ const memoryDir = dirname(memoryPath);
242
+ await mkdir(memoryDir, { recursive: true });
243
+ let memvid;
244
+ const MAX_FILE_SIZE_MB = 100;
245
+ const lockPath = `${memoryPath}.lock`;
246
+ await withMemvidLock(lockPath, async () => {
247
+ if (!existsSync(memoryPath)) {
248
+ memvid = await create(memoryPath, "basic");
249
+ return;
250
+ }
251
+ const { statSync: statSync2, renameSync: renameSync2, unlinkSync: unlinkSync2 } = await import('fs');
252
+ const fileSize = statSync2(memoryPath).size;
253
+ const fileSizeMB = fileSize / (1024 * 1024);
254
+ if (fileSizeMB > MAX_FILE_SIZE_MB) {
255
+ console.error(`[memvid-mind] Memory file too large (${fileSizeMB.toFixed(1)}MB), likely corrupted. Creating fresh memory...`);
256
+ const backupPath = `${memoryPath}.backup-${Date.now()}`;
257
+ try {
258
+ renameSync2(memoryPath, backupPath);
259
+ } catch {
260
+ }
261
+ memvid = await create(memoryPath, "basic");
262
+ return;
263
+ }
264
+ try {
265
+ memvid = await use("basic", memoryPath);
266
+ } catch (openError) {
267
+ const errorMessage = openError instanceof Error ? openError.message : String(openError);
268
+ if (errorMessage.includes("Deserialization") || errorMessage.includes("UnexpectedVariant") || errorMessage.includes("Invalid") || errorMessage.includes("corrupt") || errorMessage.includes("validation failed") || errorMessage.includes("unable to recover") || errorMessage.includes("table of contents")) {
269
+ console.error("[memvid-mind] Memory file corrupted, creating fresh memory...");
270
+ const backupPath = `${memoryPath}.backup-${Date.now()}`;
271
+ try {
272
+ renameSync2(memoryPath, backupPath);
273
+ } catch {
274
+ try {
275
+ unlinkSync2(memoryPath);
276
+ } catch {
277
+ }
278
+ }
279
+ memvid = await create(memoryPath, "basic");
280
+ return;
281
+ }
282
+ throw openError;
283
+ }
284
+ });
285
+ const mind = new _Mind(memvid, config, memoryPath);
286
+ mind.initialized = true;
287
+ pruneBackups(memoryPath, 3);
288
+ if (config.debug) {
289
+ console.error(`[memvid-mind] Opened: ${memoryPath}`);
290
+ }
291
+ return mind;
292
+ }
293
+ async withLock(fn) {
294
+ const memoryPath = this.getMemoryPath();
295
+ const lockPath = `${memoryPath}.lock`;
296
+ return withMemvidLock(lockPath, fn);
297
+ }
298
+ /**
299
+ * Remember an observation
300
+ */
301
+ async remember(input) {
302
+ const observation = {
303
+ id: generateId(),
304
+ timestamp: Date.now(),
305
+ type: input.type,
306
+ tool: input.tool,
307
+ summary: input.summary,
308
+ content: input.content,
309
+ metadata: {
310
+ ...input.metadata,
311
+ sessionId: this.sessionId
312
+ }
313
+ };
314
+ const frameId = await this.withLock(async () => {
315
+ return this.memvid.put({
316
+ title: `[${observation.type}] ${observation.summary}`,
317
+ label: observation.type,
318
+ text: observation.content,
319
+ metadata: {
320
+ observationId: observation.id,
321
+ timestamp: observation.timestamp,
322
+ tool: observation.tool,
323
+ sessionId: this.sessionId,
324
+ ...observation.metadata
325
+ },
326
+ tags: [
327
+ observation.type,
328
+ `session:${this.sessionId}`,
329
+ observation.tool ? `tool:${observation.tool}` : void 0
330
+ ].filter(Boolean)
331
+ });
332
+ });
333
+ if (this.config.debug) {
334
+ console.error(`[memvid-mind] Remembered: ${observation.summary}`);
335
+ }
336
+ this.sessionObservationCount += 1;
337
+ this.cachedStats = null;
338
+ this.cachedStatsFrameCount = -1;
339
+ return frameId;
340
+ }
341
+ /**
342
+ * Search memories by query (uses fast lexical search)
343
+ */
344
+ async search(query, limit = 10) {
345
+ return this.withLock(async () => {
346
+ return this.searchUnlocked(query, limit);
347
+ });
348
+ }
349
+ async searchUnlocked(query, limit) {
350
+ const results = await this.memvid.find(query, { k: limit, mode: "lex" });
351
+ const frames = this.toSearchFrames(results);
352
+ return frames.map((frame) => {
353
+ const rawTags = Array.isArray(frame.tags) ? frame.tags.filter((tag) => typeof tag === "string") : [];
354
+ const prefixedToolTag = rawTags.find((tag) => tag.startsWith("tool:"));
355
+ const labels = Array.isArray(frame.labels) ? frame.labels.filter((label) => typeof label === "string") : [];
356
+ const metadata = frame.metadata && typeof frame.metadata === "object" ? frame.metadata : {};
357
+ const observationType = this.extractObservationType({
358
+ label: frame.label,
359
+ labels
360
+ }) || "discovery";
361
+ const legacyToolTag = rawTags.find((tag) => {
362
+ if (tag.startsWith("tool:") || tag.startsWith("session:")) {
363
+ return false;
364
+ }
365
+ if (!/[A-Z]/.test(tag)) {
366
+ return false;
367
+ }
368
+ return tag.toLowerCase() !== observationType;
369
+ });
370
+ const tool2 = typeof prefixedToolTag === "string" ? prefixedToolTag.replace(/^tool:/, "") : typeof metadata.tool === "string" ? metadata.tool : legacyToolTag;
371
+ const timestamp = this.normalizeTimestampMs(
372
+ metadata.timestamp || frame.timestamp || (typeof frame.created_at === "string" ? Date.parse(frame.created_at) : 0)
373
+ );
374
+ return {
375
+ observation: {
376
+ id: String(metadata.observationId || frame.frame_id || generateId()),
377
+ timestamp,
378
+ type: observationType,
379
+ tool: tool2,
380
+ summary: frame.title?.replace(/^\[.*?\]\s*/, "") || frame.snippet || "",
381
+ content: frame.text || frame.snippet || "",
382
+ metadata: {
383
+ ...metadata,
384
+ labels,
385
+ tags: rawTags
386
+ }
387
+ },
388
+ score: frame.score || 0,
389
+ snippet: frame.snippet || frame.text?.slice(0, 200) || ""
390
+ };
391
+ });
392
+ }
393
+ toTimelineFrames(timelineResult) {
394
+ return Array.isArray(timelineResult) ? timelineResult : timelineResult.frames || [];
395
+ }
396
+ toSearchFrames(searchResult) {
397
+ if (Array.isArray(searchResult?.hits)) {
398
+ return searchResult.hits;
399
+ }
400
+ if (Array.isArray(searchResult?.frames)) {
401
+ return searchResult.frames;
402
+ }
403
+ return [];
404
+ }
405
+ normalizeTimestampMs(value) {
406
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
407
+ return 0;
408
+ }
409
+ if (value < 4102444800) {
410
+ return Math.round(value * 1e3);
411
+ }
412
+ return Math.round(value);
413
+ }
414
+ parseSessionSummary(value) {
415
+ if (!value || typeof value !== "object") {
416
+ return null;
417
+ }
418
+ const candidate = value;
419
+ if (typeof candidate.id !== "string" || typeof candidate.startTime !== "number" || typeof candidate.endTime !== "number" || typeof candidate.observationCount !== "number" || typeof candidate.summary !== "string" || !Array.isArray(candidate.keyDecisions) || !Array.isArray(candidate.filesModified)) {
420
+ return null;
421
+ }
422
+ return {
423
+ id: candidate.id,
424
+ startTime: this.normalizeTimestampMs(candidate.startTime),
425
+ endTime: this.normalizeTimestampMs(candidate.endTime),
426
+ observationCount: Math.max(0, Math.trunc(candidate.observationCount)),
427
+ keyDecisions: candidate.keyDecisions.filter(
428
+ (decision) => typeof decision === "string"
429
+ ),
430
+ filesModified: candidate.filesModified.filter(
431
+ (file) => typeof file === "string"
432
+ ),
433
+ summary: candidate.summary
434
+ };
435
+ }
436
+ extractSessionSummary(frame) {
437
+ const fromMetadata = this.parseSessionSummary(frame.metadata);
438
+ if (fromMetadata) {
439
+ return fromMetadata;
440
+ }
441
+ if (typeof frame.text !== "string") {
442
+ return null;
443
+ }
444
+ try {
445
+ return this.parseSessionSummary(JSON.parse(frame.text));
446
+ } catch {
447
+ return null;
448
+ }
449
+ }
450
+ extractSessionId(frame) {
451
+ const tags = Array.isArray(frame?.tags) ? frame.tags.filter((tag) => typeof tag === "string") : [];
452
+ const sessionTag = tags.find((tag) => tag.startsWith("session:"));
453
+ if (sessionTag) {
454
+ return sessionTag.slice("session:".length);
455
+ }
456
+ const metadataSessionId = frame?.metadata?.sessionId;
457
+ if (typeof metadataSessionId === "string" && metadataSessionId.length > 0) {
458
+ return metadataSessionId;
459
+ }
460
+ if (frame?.label === "session") {
461
+ const summary = this.extractSessionSummary(frame);
462
+ if (summary) {
463
+ return summary.id;
464
+ }
465
+ }
466
+ return null;
467
+ }
468
+ extractObservationType(frame) {
469
+ if (Array.isArray(frame?.labels)) {
470
+ for (const value of frame.labels) {
471
+ if (typeof value === "string") {
472
+ const normalized = value.toLowerCase();
473
+ if (OBSERVATION_TYPE_SET.has(normalized)) {
474
+ return normalized;
475
+ }
476
+ }
477
+ }
478
+ }
479
+ const label = typeof frame?.label === "string" ? frame.label : void 0;
480
+ if (label) {
481
+ const normalized = label.toLowerCase();
482
+ if (OBSERVATION_TYPE_SET.has(normalized)) {
483
+ return normalized;
484
+ }
485
+ }
486
+ const metadataType = frame?.metadata?.type;
487
+ if (typeof metadataType === "string") {
488
+ const normalized = metadataType.toLowerCase();
489
+ if (OBSERVATION_TYPE_SET.has(normalized)) {
490
+ return normalized;
491
+ }
492
+ }
493
+ return null;
494
+ }
495
+ extractPreviewFieldValues(preview, field) {
496
+ if (typeof preview !== "string" || preview.length === 0) {
497
+ return [];
498
+ }
499
+ const match = new RegExp(`(?:^|\\n)${field}:\\s*([^\\n]*)`, "i").exec(preview);
500
+ if (!match?.[1]) {
501
+ return [];
502
+ }
503
+ return match[1].split(/[^a-z0-9:_-]+/i).map((value) => value.trim()).filter(Boolean);
504
+ }
505
+ extractObservationTypeFromPreview(preview) {
506
+ const labels = this.extractPreviewFieldValues(preview, "labels");
507
+ const fromLabels = this.extractObservationType({ labels });
508
+ if (fromLabels) {
509
+ return fromLabels;
510
+ }
511
+ if (typeof preview !== "string" || preview.length === 0) {
512
+ return null;
513
+ }
514
+ const titleMatch = /(?:^|\n)title:\s*\[([^\]]+)\]/i.exec(preview);
515
+ if (!titleMatch?.[1]) {
516
+ return null;
517
+ }
518
+ const normalized = titleMatch[1].trim().toLowerCase();
519
+ if (OBSERVATION_TYPE_SET.has(normalized)) {
520
+ return normalized;
521
+ }
522
+ return null;
523
+ }
524
+ parseLeadingJsonObject(text) {
525
+ const start = text.indexOf("{");
526
+ if (start < 0) {
527
+ return null;
528
+ }
529
+ let depth = 0;
530
+ let inString = false;
531
+ let escaped = false;
532
+ for (let i = start; i < text.length; i++) {
533
+ const ch = text[i];
534
+ if (inString) {
535
+ if (escaped) {
536
+ escaped = false;
537
+ } else if (ch === "\\") {
538
+ escaped = true;
539
+ } else if (ch === '"') {
540
+ inString = false;
541
+ }
542
+ continue;
543
+ }
544
+ if (ch === '"') {
545
+ inString = true;
546
+ continue;
547
+ }
548
+ if (ch === "{") {
549
+ depth += 1;
550
+ } else if (ch === "}") {
551
+ depth -= 1;
552
+ if (depth === 0) {
553
+ const candidate = text.slice(start, i + 1);
554
+ try {
555
+ return JSON.parse(candidate);
556
+ } catch {
557
+ return null;
558
+ }
559
+ }
560
+ }
561
+ }
562
+ return null;
563
+ }
564
+ extractSessionSummaryFromSearchHit(hit) {
565
+ if (typeof hit?.text !== "string") {
566
+ return null;
567
+ }
568
+ const parsed = this.parseLeadingJsonObject(hit.text);
569
+ return this.parseSessionSummary(parsed);
570
+ }
571
+ /**
572
+ * Ask the memory a question (uses fast lexical search)
573
+ */
574
+ async ask(question) {
575
+ return this.withLock(async () => {
576
+ const result = await this.memvid.ask(question, { k: 5, mode: "lex" });
577
+ return result.answer || "No relevant memories found.";
578
+ });
579
+ }
580
+ /**
581
+ * Get context for session start
582
+ */
583
+ async getContext(query) {
584
+ return this.withLock(async () => {
585
+ const timeline = await this.memvid.timeline({
586
+ limit: this.config.maxContextObservations,
587
+ reverse: true
588
+ });
589
+ const frames = this.toTimelineFrames(timeline);
590
+ const recentObservations = [];
591
+ const FRAME_INFO_BATCH_SIZE = 20;
592
+ for (let start = 0; start < frames.length; start += FRAME_INFO_BATCH_SIZE) {
593
+ const batch = frames.slice(start, start + FRAME_INFO_BATCH_SIZE);
594
+ const frameInfos = await Promise.all(batch.map(async (frame) => {
595
+ try {
596
+ return await this.memvid.getFrameInfo(frame.frame_id);
597
+ } catch {
598
+ return null;
599
+ }
600
+ }));
601
+ for (let index = 0; index < batch.length; index++) {
602
+ const frame = batch[index];
603
+ const frameInfo = frameInfos[index];
604
+ const labels = Array.isArray(frameInfo?.labels) ? frameInfo.labels : [];
605
+ const tags = Array.isArray(frameInfo?.tags) ? frameInfo.tags : [];
606
+ const metadata = frameInfo?.metadata && typeof frameInfo.metadata === "object" ? frameInfo.metadata : {};
607
+ const toolTag = tags.find((tag) => typeof tag === "string" && tag.startsWith("tool:"));
608
+ const ts = this.normalizeTimestampMs(frameInfo?.timestamp || frame.timestamp || 0);
609
+ const observationType = this.extractObservationType({
610
+ label: labels[0],
611
+ labels,
612
+ metadata
613
+ }) || "discovery";
614
+ recentObservations.push({
615
+ id: String(metadata.observationId || frame.metadata?.observationId || frame.frame_id),
616
+ timestamp: ts,
617
+ type: observationType,
618
+ tool: typeof toolTag === "string" ? toolTag.replace(/^tool:/, "") : typeof metadata.tool === "string" ? metadata.tool : void 0,
619
+ summary: frameInfo?.title?.replace(/^\[.*?\]\s*/, "") || frame.preview?.slice(0, 100) || "",
620
+ content: frame.preview || "",
621
+ metadata: {
622
+ ...metadata,
623
+ labels,
624
+ tags
625
+ }
626
+ });
627
+ }
628
+ }
629
+ let relevantMemories = [];
630
+ if (query) {
631
+ const searchResults = await this.searchUnlocked(query, 10);
632
+ relevantMemories = searchResults.map((r) => r.observation);
633
+ }
634
+ const summarySearch = await this.memvid.find("Session Summary", {
635
+ k: 20,
636
+ mode: "lex"
637
+ });
638
+ const summaryHits = this.toSearchFrames(summarySearch);
639
+ const seenSessionIds = /* @__PURE__ */ new Set();
640
+ const sessionSummaries = [];
641
+ for (const hit of summaryHits) {
642
+ const summary = this.extractSessionSummaryFromSearchHit(hit);
643
+ if (!summary || seenSessionIds.has(summary.id)) {
644
+ continue;
645
+ }
646
+ seenSessionIds.add(summary.id);
647
+ sessionSummaries.push(summary);
648
+ if (sessionSummaries.length >= 5) {
649
+ break;
650
+ }
651
+ }
652
+ let tokenCount = 0;
653
+ for (const obs of recentObservations) {
654
+ const text = `[${obs.type}] ${obs.summary}`;
655
+ const tokens = estimateTokens(text);
656
+ if (tokenCount + tokens > this.config.maxContextTokens) break;
657
+ tokenCount += tokens;
658
+ }
659
+ return {
660
+ recentObservations,
661
+ relevantMemories,
662
+ sessionSummaries,
663
+ tokenCount
664
+ };
665
+ });
666
+ }
667
+ /**
668
+ * Save a session summary
669
+ */
670
+ async saveSessionSummary(summary) {
671
+ return this.withLock(async () => {
672
+ const endTime = Date.now();
673
+ const sessionSummary = {
674
+ id: this.sessionId,
675
+ startTime: this.sessionStartTime,
676
+ endTime,
677
+ observationCount: this.sessionObservationCount,
678
+ keyDecisions: summary.keyDecisions.slice(0, 20),
679
+ filesModified: summary.filesModified.slice(0, 50),
680
+ summary: summary.summary
681
+ };
682
+ const frameId = await this.memvid.put({
683
+ title: `Session Summary: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
684
+ label: "session",
685
+ text: JSON.stringify(sessionSummary, null, 2),
686
+ metadata: {
687
+ ...sessionSummary,
688
+ sessionId: this.sessionId
689
+ },
690
+ tags: ["session", "summary", `session:${this.sessionId}`]
691
+ });
692
+ this.cachedStats = null;
693
+ this.cachedStatsFrameCount = -1;
694
+ return frameId;
695
+ });
696
+ }
697
+ /**
698
+ * Get memory statistics
699
+ */
700
+ async stats() {
701
+ return this.withLock(async () => {
702
+ const stats = await this.memvid.stats();
703
+ const totalFrames = Number(stats.frame_count) || 0;
704
+ if (this.cachedStats && this.cachedStatsFrameCount === totalFrames) {
705
+ return this.cachedStats;
706
+ }
707
+ const timeline = totalFrames > 0 ? await this.memvid.timeline({ limit: totalFrames, reverse: false }) : [];
708
+ const frames = this.toTimelineFrames(timeline);
709
+ const sessionIds = /* @__PURE__ */ new Set();
710
+ const topTypes = emptyTypeCounts();
711
+ let oldestMemory = 0;
712
+ let newestMemory = 0;
713
+ for (const frame of frames) {
714
+ const labels = this.extractPreviewFieldValues(frame.preview, "labels");
715
+ const tags = this.extractPreviewFieldValues(frame.preview, "tags");
716
+ const timestamp = this.normalizeTimestampMs(frame.timestamp || 0);
717
+ if (timestamp > 0) {
718
+ if (oldestMemory === 0 || timestamp < oldestMemory) {
719
+ oldestMemory = timestamp;
720
+ }
721
+ if (newestMemory === 0 || timestamp > newestMemory) {
722
+ newestMemory = timestamp;
723
+ }
724
+ }
725
+ const sessionId = this.extractSessionId({
726
+ ...frame,
727
+ labels,
728
+ tags
729
+ });
730
+ if (sessionId) {
731
+ sessionIds.add(sessionId);
732
+ }
733
+ const observationType = this.extractObservationType({
734
+ ...frame,
735
+ label: labels[0],
736
+ labels,
737
+ tags
738
+ }) || this.extractObservationTypeFromPreview(frame.preview);
739
+ if (observationType) {
740
+ topTypes[observationType] += 1;
741
+ }
742
+ }
743
+ const summarySearch = await this.memvid.find("Session Summary", {
744
+ k: 50,
745
+ mode: "lex"
746
+ });
747
+ const summaryHits = this.toSearchFrames(summarySearch);
748
+ for (const hit of summaryHits) {
749
+ const summary = this.extractSessionSummaryFromSearchHit(hit);
750
+ if (summary) {
751
+ sessionIds.add(summary.id);
752
+ }
753
+ }
754
+ const result = {
755
+ totalObservations: totalFrames,
756
+ totalSessions: sessionIds.size,
757
+ oldestMemory,
758
+ newestMemory,
759
+ fileSize: stats.size_bytes || 0,
760
+ topTypes
761
+ };
762
+ this.cachedStats = result;
763
+ this.cachedStatsFrameCount = totalFrames;
764
+ return result;
765
+ });
766
+ }
767
+ /**
768
+ * Get the session ID
769
+ */
770
+ getSessionId() {
771
+ return this.sessionId;
772
+ }
773
+ /**
774
+ * Get the memory file path
775
+ */
776
+ getMemoryPath() {
777
+ return this.memoryPath;
778
+ }
779
+ /**
780
+ * Check if initialized
781
+ */
782
+ isInitialized() {
783
+ return this.initialized;
784
+ }
785
+ };
786
+ var mindInstance = null;
787
+ async function getMind(config) {
788
+ if (!mindInstance) {
789
+ mindInstance = await Mind.open(config);
790
+ }
791
+ return mindInstance;
792
+ }
793
+
794
+ // src/utils/compression.ts
795
+ var TARGET_COMPRESSED_SIZE = 2e3;
796
+ var COMPRESSION_THRESHOLD = 3e3;
797
+ function compressToolOutput(toolName, toolInput, output) {
798
+ const originalSize = output.length;
799
+ if (originalSize <= COMPRESSION_THRESHOLD) {
800
+ return { compressed: output, wasCompressed: false, originalSize };
801
+ }
802
+ let compressed;
803
+ switch (toolName) {
804
+ case "Read":
805
+ compressed = compressFileRead(toolInput, output);
806
+ break;
807
+ case "Bash":
808
+ compressed = compressBashOutput(toolInput, output);
809
+ break;
810
+ case "Grep":
811
+ compressed = compressGrepOutput(toolInput, output);
812
+ break;
813
+ case "Glob":
814
+ compressed = compressGlobOutput(toolInput, output);
815
+ break;
816
+ case "Edit":
817
+ case "Write":
818
+ compressed = compressEditOutput(toolInput, output);
819
+ break;
820
+ default:
821
+ compressed = compressGeneric(output);
822
+ }
823
+ return {
824
+ compressed: truncateToTarget(compressed),
825
+ wasCompressed: true,
826
+ originalSize
827
+ };
828
+ }
829
+ function compressFileRead(toolInput, output) {
830
+ const filePath = toolInput?.file_path || "unknown";
831
+ const fileName = filePath.split("/").pop() || "file";
832
+ const lines = output.split("\n");
833
+ const totalLines = lines.length;
834
+ const imports = extractImports(output);
835
+ const exports$1 = extractExports(output);
836
+ const functions = extractFunctionSignatures(output);
837
+ const classes = extractClassNames(output);
838
+ const errors = extractErrorPatterns(output);
839
+ const parts = [
840
+ `\u{1F4C4} File: ${fileName} (${totalLines} lines)`
841
+ ];
842
+ if (imports.length > 0) {
843
+ parts.push(`
844
+ \u{1F4E6} Imports: ${imports.slice(0, 10).join(", ")}${imports.length > 10 ? ` (+${imports.length - 10} more)` : ""}`);
845
+ }
846
+ if (exports$1.length > 0) {
847
+ parts.push(`
848
+ \u{1F4E4} Exports: ${exports$1.slice(0, 10).join(", ")}${exports$1.length > 10 ? ` (+${exports$1.length - 10} more)` : ""}`);
849
+ }
850
+ if (functions.length > 0) {
851
+ parts.push(`
852
+ \u26A1 Functions: ${functions.slice(0, 10).join(", ")}${functions.length > 10 ? ` (+${functions.length - 10} more)` : ""}`);
853
+ }
854
+ if (classes.length > 0) {
855
+ parts.push(`
856
+ \u{1F3D7}\uFE0F Classes: ${classes.join(", ")}`);
857
+ }
858
+ if (errors.length > 0) {
859
+ parts.push(`
860
+ \u26A0\uFE0F Errors/TODOs: ${errors.slice(0, 5).join("; ")}`);
861
+ }
862
+ const contextLines = [
863
+ "\n--- First 10 lines ---",
864
+ ...lines.slice(0, 10),
865
+ "\n--- Last 5 lines ---",
866
+ ...lines.slice(-5)
867
+ ];
868
+ parts.push(contextLines.join("\n"));
869
+ return parts.join("");
870
+ }
871
+ function compressBashOutput(toolInput, output) {
872
+ const command = toolInput?.command || "command";
873
+ const shortCmd = command.split("\n")[0].slice(0, 100);
874
+ const lines = output.split("\n");
875
+ const errorLines = lines.filter(
876
+ (l) => l.toLowerCase().includes("error") || l.toLowerCase().includes("failed") || l.toLowerCase().includes("exception") || l.toLowerCase().includes("warning")
877
+ );
878
+ const successLines = lines.filter(
879
+ (l) => l.toLowerCase().includes("success") || l.toLowerCase().includes("passed") || l.toLowerCase().includes("completed") || l.toLowerCase().includes("done")
880
+ );
881
+ const parts = [`\u{1F5A5}\uFE0F Command: ${shortCmd}`];
882
+ if (errorLines.length > 0) {
883
+ parts.push(`
884
+ \u274C Errors (${errorLines.length}):`);
885
+ parts.push(errorLines.slice(0, 10).join("\n"));
886
+ }
887
+ if (successLines.length > 0) {
888
+ parts.push(`
889
+ \u2705 Success indicators:`);
890
+ parts.push(successLines.slice(0, 5).join("\n"));
891
+ }
892
+ parts.push(`
893
+ \u{1F4CA} Output: ${lines.length} lines total`);
894
+ if (lines.length > 20) {
895
+ parts.push("\n--- First 10 lines ---");
896
+ parts.push(lines.slice(0, 10).join("\n"));
897
+ parts.push("\n--- Last 5 lines ---");
898
+ parts.push(lines.slice(-5).join("\n"));
899
+ } else {
900
+ parts.push("\n--- Full output ---");
901
+ parts.push(lines.join("\n"));
902
+ }
903
+ return parts.join("");
904
+ }
905
+ function compressGrepOutput(toolInput, output) {
906
+ const pattern = toolInput?.pattern || "pattern";
907
+ const lines = output.split("\n").filter(Boolean);
908
+ const files = /* @__PURE__ */ new Set();
909
+ lines.forEach((line) => {
910
+ const match = line.match(/^([^:]+):/);
911
+ if (match) files.add(match[1]);
912
+ });
913
+ const parts = [
914
+ `\u{1F50D} Grep: "${pattern.slice(0, 50)}"`,
915
+ `\u{1F4C1} Found in ${files.size} files, ${lines.length} matches`
916
+ ];
917
+ if (files.size > 0) {
918
+ parts.push(`
919
+ \u{1F4C2} Files: ${Array.from(files).slice(0, 15).join(", ")}${files.size > 15 ? ` (+${files.size - 15} more)` : ""}`);
920
+ }
921
+ parts.push("\n--- Top matches ---");
922
+ parts.push(lines.slice(0, 10).join("\n"));
923
+ if (lines.length > 10) {
924
+ parts.push(`
925
+ ... and ${lines.length - 10} more matches`);
926
+ }
927
+ return parts.join("");
928
+ }
929
+ function compressGlobOutput(toolInput, output) {
930
+ const pattern = toolInput?.pattern || "pattern";
931
+ let files = [];
932
+ try {
933
+ const parsed = JSON.parse(output);
934
+ files = parsed.filenames || [];
935
+ } catch {
936
+ files = output.split("\n").filter(Boolean);
937
+ }
938
+ const byDir = {};
939
+ files.forEach((f) => {
940
+ const dir = f.split("/").slice(0, -1).join("/") || "/";
941
+ const file = f.split("/").pop() || f;
942
+ if (!byDir[dir]) byDir[dir] = [];
943
+ byDir[dir].push(file);
944
+ });
945
+ const parts = [
946
+ `\u{1F4C2} Glob: "${pattern.slice(0, 50)}"`,
947
+ `\u{1F4C1} Found ${files.length} files in ${Object.keys(byDir).length} directories`
948
+ ];
949
+ const topDirs = Object.entries(byDir).sort((a, b) => b[1].length - a[1].length).slice(0, 5);
950
+ parts.push("\n--- Top directories ---");
951
+ topDirs.forEach(([dir, dirFiles]) => {
952
+ const shortDir = dir.split("/").slice(-3).join("/");
953
+ parts.push(`${shortDir}/ (${dirFiles.length} files)`);
954
+ });
955
+ parts.push("\n--- Sample files ---");
956
+ parts.push(files.slice(0, 15).map((f) => f.split("/").pop()).join(", "));
957
+ return parts.join("");
958
+ }
959
+ function compressEditOutput(toolInput, output) {
960
+ const filePath = toolInput?.file_path || "unknown";
961
+ const fileName = filePath.split("/").pop() || "file";
962
+ return [
963
+ `\u270F\uFE0F Edited: ${fileName}`,
964
+ `\u{1F4DD} Changes applied successfully`,
965
+ output.slice(0, 500)
966
+ ].join("\n");
967
+ }
968
+ function compressGeneric(output) {
969
+ const lines = output.split("\n");
970
+ if (lines.length <= 30) {
971
+ return output;
972
+ }
973
+ return [
974
+ `\u{1F4CA} Output: ${lines.length} lines`,
975
+ "--- First 15 lines ---",
976
+ ...lines.slice(0, 15),
977
+ "--- Last 10 lines ---",
978
+ ...lines.slice(-10)
979
+ ].join("\n");
980
+ }
981
+ function extractImports(code) {
982
+ const imports = [];
983
+ const patterns = [
984
+ /import\s+(?:{\s*([^}]+)\s*}|(\w+))\s+from\s+['"]([^'"]+)['"]/g,
985
+ /from\s+['"]([^'"]+)['"]\s+import/g,
986
+ /require\s*\(['"]([^'"]+)['"]\)/g,
987
+ /use\s+(\w+(?:::\w+)*)/g
988
+ ];
989
+ patterns.forEach((pattern) => {
990
+ let match;
991
+ while ((match = pattern.exec(code)) !== null) {
992
+ imports.push(match[3] || match[1] || match[2] || match[0]);
993
+ }
994
+ });
995
+ return [...new Set(imports)];
996
+ }
997
+ function extractExports(code) {
998
+ const exports$1 = [];
999
+ const patterns = [
1000
+ /export\s+(?:default\s+)?(?:function|class|const|let|var)\s+(\w+)/g,
1001
+ /export\s*{\s*([^}]+)\s*}/g,
1002
+ /pub\s+(?:fn|struct|enum|trait|mod)\s+(\w+)/g
1003
+ ];
1004
+ patterns.forEach((pattern) => {
1005
+ let match;
1006
+ while ((match = pattern.exec(code)) !== null) {
1007
+ const names = (match[1] || "").split(",").map((s) => s.trim());
1008
+ exports$1.push(...names.filter(Boolean));
1009
+ }
1010
+ });
1011
+ return [...new Set(exports$1)];
1012
+ }
1013
+ function extractFunctionSignatures(code) {
1014
+ const functions = [];
1015
+ const patterns = [
1016
+ /(?:async\s+)?function\s+(\w+)/g,
1017
+ /(\w+)\s*:\s*(?:async\s+)?\([^)]*\)\s*=>/g,
1018
+ /(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g,
1019
+ /fn\s+(\w+)/g,
1020
+ /def\s+(\w+)/g
1021
+ ];
1022
+ patterns.forEach((pattern) => {
1023
+ let match;
1024
+ while ((match = pattern.exec(code)) !== null) {
1025
+ functions.push(match[1]);
1026
+ }
1027
+ });
1028
+ return [...new Set(functions)];
1029
+ }
1030
+ function extractClassNames(code) {
1031
+ const classes = [];
1032
+ const patterns = [
1033
+ /class\s+(\w+)/g,
1034
+ /struct\s+(\w+)/g,
1035
+ /interface\s+(\w+)/g,
1036
+ /type\s+(\w+)\s*=/g
1037
+ ];
1038
+ patterns.forEach((pattern) => {
1039
+ let match;
1040
+ while ((match = pattern.exec(code)) !== null) {
1041
+ classes.push(match[1]);
1042
+ }
1043
+ });
1044
+ return [...new Set(classes)];
1045
+ }
1046
+ function extractErrorPatterns(code) {
1047
+ const errors = [];
1048
+ const lines = code.split("\n");
1049
+ lines.forEach((line) => {
1050
+ if (line.includes("TODO") || line.includes("FIXME") || line.includes("HACK") || line.includes("XXX") || line.includes("BUG")) {
1051
+ errors.push(line.trim().slice(0, 100));
1052
+ }
1053
+ });
1054
+ return errors.slice(0, 10);
1055
+ }
1056
+ function truncateToTarget(text) {
1057
+ if (text.length <= TARGET_COMPRESSED_SIZE) {
1058
+ return text;
1059
+ }
1060
+ return text.slice(0, TARGET_COMPRESSED_SIZE - 20) + "\n... (compressed)";
1061
+ }
1062
+
1063
+ // src/platforms/registry.ts
1064
+ var AdapterRegistry = class {
1065
+ adapters = /* @__PURE__ */ new Map();
1066
+ register(adapter) {
1067
+ this.adapters.set(adapter.platform, adapter);
1068
+ }
1069
+ resolve(platform) {
1070
+ return this.adapters.get(platform) || null;
1071
+ }
1072
+ listPlatforms() {
1073
+ return [...this.adapters.keys()].sort();
1074
+ }
1075
+ };
1076
+
1077
+ // src/platforms/events.ts
1078
+ function createEventId() {
1079
+ return generateId();
1080
+ }
1081
+
1082
+ // src/platforms/adapters/create-adapter.ts
1083
+ var CONTRACT_VERSION = "1.0.0";
1084
+ function createAdapter(platform) {
1085
+ function projectContext(input) {
1086
+ return {
1087
+ platformProjectId: input.project_id,
1088
+ canonicalPath: input.cwd,
1089
+ cwd: input.cwd
1090
+ };
1091
+ }
1092
+ return {
1093
+ platform,
1094
+ contractVersion: CONTRACT_VERSION,
1095
+ normalizeSessionStart(input) {
1096
+ return {
1097
+ eventId: createEventId(),
1098
+ eventType: "session_start",
1099
+ platform,
1100
+ contractVersion: input.contract_version?.trim() || CONTRACT_VERSION,
1101
+ sessionId: input.session_id,
1102
+ timestamp: Date.now(),
1103
+ projectContext: projectContext(input),
1104
+ payload: {
1105
+ hookEventName: input.hook_event_name,
1106
+ permissionMode: input.permission_mode,
1107
+ transcriptPath: input.transcript_path
1108
+ }
1109
+ };
1110
+ },
1111
+ normalizeToolObservation(input) {
1112
+ if (!input.tool_name) return null;
1113
+ return {
1114
+ eventId: createEventId(),
1115
+ eventType: "tool_observation",
1116
+ platform,
1117
+ contractVersion: input.contract_version?.trim() || CONTRACT_VERSION,
1118
+ sessionId: input.session_id,
1119
+ timestamp: Date.now(),
1120
+ projectContext: projectContext(input),
1121
+ payload: {
1122
+ toolName: input.tool_name,
1123
+ toolInput: input.tool_input,
1124
+ toolResponse: input.tool_response
1125
+ }
1126
+ };
1127
+ },
1128
+ normalizeSessionStop(input) {
1129
+ return {
1130
+ eventId: createEventId(),
1131
+ eventType: "session_stop",
1132
+ platform,
1133
+ contractVersion: input.contract_version?.trim() || CONTRACT_VERSION,
1134
+ sessionId: input.session_id,
1135
+ timestamp: Date.now(),
1136
+ projectContext: projectContext(input),
1137
+ payload: {
1138
+ transcriptPath: input.transcript_path
1139
+ }
1140
+ };
1141
+ }
1142
+ };
1143
+ }
1144
+
1145
+ // src/platforms/adapters/claude.ts
1146
+ var claudeAdapter = createAdapter("claude");
1147
+
1148
+ // src/platforms/adapters/opencode.ts
1149
+ var opencodeAdapter = createAdapter("opencode");
1150
+
1151
+ // src/platforms/contract.ts
1152
+ var SUPPORTED_ADAPTER_CONTRACT_MAJOR = 1;
1153
+ var SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/;
1154
+ function parseContractMajor(version) {
1155
+ const match = SEMVER_PATTERN.exec(version.trim());
1156
+ if (!match) {
1157
+ return null;
1158
+ }
1159
+ return Number(match[1]);
1160
+ }
1161
+ function validateAdapterContractVersion(version, supportedMajor = SUPPORTED_ADAPTER_CONTRACT_MAJOR) {
1162
+ const adapterMajor = parseContractMajor(version);
1163
+ if (adapterMajor === null) {
1164
+ return {
1165
+ compatible: false,
1166
+ supportedMajor,
1167
+ adapterMajor: null,
1168
+ reason: "invalid_contract_version"
1169
+ };
1170
+ }
1171
+ if (adapterMajor !== supportedMajor) {
1172
+ return {
1173
+ compatible: false,
1174
+ supportedMajor,
1175
+ adapterMajor,
1176
+ reason: "incompatible_contract_major"
1177
+ };
1178
+ }
1179
+ return {
1180
+ compatible: true,
1181
+ supportedMajor,
1182
+ adapterMajor
1183
+ };
1184
+ }
1185
+
1186
+ // src/platforms/diagnostics.ts
1187
+ var DIAGNOSTIC_RETENTION_DAYS = 30;
1188
+ var DAY_MS = 24 * 60 * 60 * 1e3;
1189
+ var DIAGNOSTIC_FILE_NAME = "platform-diagnostics.json";
1190
+ var TEST_DIAGNOSTIC_FILE_NAME = `memvid-platform-diagnostics-${process.pid}.json`;
1191
+ function sanitizeFieldNames(fieldNames) {
1192
+ if (!fieldNames || fieldNames.length === 0) {
1193
+ return void 0;
1194
+ }
1195
+ return [...new Set(fieldNames)].slice(0, 20);
1196
+ }
1197
+ function resolveDiagnosticStorePath() {
1198
+ const explicitPath = process.env.MEMVID_DIAGNOSTIC_PATH?.trim();
1199
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
1200
+ if (explicitPath) {
1201
+ return resolve(projectDir, explicitPath);
1202
+ }
1203
+ if (process.env.VITEST) {
1204
+ return resolve(tmpdir(), TEST_DIAGNOSTIC_FILE_NAME);
1205
+ }
1206
+ return resolve(projectDir, ".claude", DIAGNOSTIC_FILE_NAME);
1207
+ }
1208
+ function isDiagnosticRecord(value) {
1209
+ if (!value || typeof value !== "object") {
1210
+ return false;
1211
+ }
1212
+ const record = value;
1213
+ return typeof record.diagnosticId === "string" && typeof record.timestamp === "number" && typeof record.platform === "string" && typeof record.errorType === "string" && (record.fieldNames === void 0 || Array.isArray(record.fieldNames) && record.fieldNames.every((name) => typeof name === "string")) && (record.severity === "warning" || record.severity === "error") && record.redacted === true && typeof record.retentionDays === "number" && typeof record.expiresAt === "number";
1214
+ }
1215
+ function pruneExpired(records, now = Date.now()) {
1216
+ return records.filter((record) => record.expiresAt > now);
1217
+ }
1218
+ var DiagnosticPersistence = class {
1219
+ filePath;
1220
+ constructor(filePath) {
1221
+ this.filePath = filePath;
1222
+ }
1223
+ append(record, now = Date.now()) {
1224
+ this.withFileLock(() => {
1225
+ const latest = this.loadFromDisk();
1226
+ const next = pruneExpired([...latest, record], now);
1227
+ this.persist(next);
1228
+ });
1229
+ }
1230
+ list(now = Date.now()) {
1231
+ return this.withFileLock(() => {
1232
+ const latest = this.loadFromDisk();
1233
+ const pruned = pruneExpired(latest, now);
1234
+ if (pruned.length !== latest.length) {
1235
+ this.persist(pruned);
1236
+ }
1237
+ return [...pruned];
1238
+ });
1239
+ }
1240
+ loadFromDisk() {
1241
+ if (!existsSync(this.filePath)) {
1242
+ return [];
1243
+ }
1244
+ try {
1245
+ const raw = readFileSync(this.filePath, "utf-8").trim();
1246
+ if (!raw) {
1247
+ return [];
1248
+ }
1249
+ const parsed = JSON.parse(raw);
1250
+ if (!Array.isArray(parsed)) {
1251
+ return [];
1252
+ }
1253
+ return parsed.filter(isDiagnosticRecord);
1254
+ } catch {
1255
+ return [];
1256
+ }
1257
+ }
1258
+ withFileLock(fn) {
1259
+ mkdirSync(dirname(this.filePath), { recursive: true });
1260
+ const release = lockfile.lockSync(this.filePath, { realpath: false });
1261
+ try {
1262
+ return fn();
1263
+ } finally {
1264
+ release();
1265
+ }
1266
+ }
1267
+ persist(records) {
1268
+ mkdirSync(dirname(this.filePath), { recursive: true });
1269
+ const tmpPath = `${this.filePath}.tmp-${process.pid}-${Date.now()}`;
1270
+ try {
1271
+ writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}
1272
+ `, "utf-8");
1273
+ try {
1274
+ renameSync(tmpPath, this.filePath);
1275
+ } catch {
1276
+ rmSync(this.filePath, { force: true });
1277
+ renameSync(tmpPath, this.filePath);
1278
+ }
1279
+ } finally {
1280
+ rmSync(tmpPath, { force: true });
1281
+ }
1282
+ }
1283
+ };
1284
+ var persistence = null;
1285
+ var persistenceFilePath = null;
1286
+ var warnedPathChange = false;
1287
+ function getDiagnosticPersistence() {
1288
+ const resolvedPath = resolveDiagnosticStorePath();
1289
+ if (!persistence) {
1290
+ persistence = new DiagnosticPersistence(resolvedPath);
1291
+ persistenceFilePath = resolvedPath;
1292
+ warnedPathChange = false;
1293
+ return persistence;
1294
+ }
1295
+ if (persistenceFilePath && persistenceFilePath !== resolvedPath && !warnedPathChange) {
1296
+ warnedPathChange = true;
1297
+ console.error(
1298
+ `[memvid-mind] Diagnostic store path changed from "${persistenceFilePath}" to "${resolvedPath}" after initialization; continuing with the original path.`
1299
+ );
1300
+ }
1301
+ return persistence;
1302
+ }
1303
+ function createRedactedDiagnostic(input) {
1304
+ const timestamp = input.now ?? Date.now();
1305
+ const diagnostic = {
1306
+ diagnosticId: generateId(),
1307
+ timestamp,
1308
+ platform: input.platform,
1309
+ errorType: input.errorType,
1310
+ fieldNames: sanitizeFieldNames(input.fieldNames),
1311
+ severity: input.severity ?? "warning",
1312
+ redacted: true,
1313
+ retentionDays: DIAGNOSTIC_RETENTION_DAYS,
1314
+ expiresAt: timestamp + DIAGNOSTIC_RETENTION_DAYS * DAY_MS
1315
+ };
1316
+ try {
1317
+ getDiagnosticPersistence().append(diagnostic);
1318
+ } catch {
1319
+ }
1320
+ return diagnostic;
1321
+ }
1322
+ function resolveCanonicalProjectPath(context) {
1323
+ if (context.canonicalPath) {
1324
+ return resolve(context.canonicalPath);
1325
+ }
1326
+ if (context.cwd) {
1327
+ return resolve(context.cwd);
1328
+ }
1329
+ return void 0;
1330
+ }
1331
+ function resolveProjectIdentityKey(context) {
1332
+ if (context.platformProjectId && context.platformProjectId.trim().length > 0) {
1333
+ return {
1334
+ key: context.platformProjectId.trim(),
1335
+ source: "platform_project_id",
1336
+ canonicalPath: resolveCanonicalProjectPath(context)
1337
+ };
1338
+ }
1339
+ const canonicalPath = resolveCanonicalProjectPath(context);
1340
+ if (canonicalPath) {
1341
+ return {
1342
+ key: canonicalPath,
1343
+ source: "canonical_path",
1344
+ canonicalPath
1345
+ };
1346
+ }
1347
+ return {
1348
+ key: null,
1349
+ source: "unresolved"
1350
+ };
1351
+ }
1352
+
1353
+ // src/platforms/pipeline.ts
1354
+ function skipWithDiagnostic(platform, errorType, fieldNames) {
1355
+ return {
1356
+ skipped: true,
1357
+ reason: errorType,
1358
+ diagnostic: createRedactedDiagnostic({
1359
+ platform,
1360
+ errorType,
1361
+ fieldNames,
1362
+ severity: "warning"
1363
+ })
1364
+ };
1365
+ }
1366
+ function processPlatformEvent(event) {
1367
+ const contractValidation = validateAdapterContractVersion(
1368
+ event.contractVersion,
1369
+ SUPPORTED_ADAPTER_CONTRACT_MAJOR
1370
+ );
1371
+ if (!contractValidation.compatible) {
1372
+ return skipWithDiagnostic(event.platform, contractValidation.reason ?? "incompatible_contract", ["contractVersion"]);
1373
+ }
1374
+ const identity = resolveProjectIdentityKey(event.projectContext);
1375
+ if (!identity.key) {
1376
+ return skipWithDiagnostic(event.platform, "missing_project_identity", [
1377
+ "platformProjectId",
1378
+ "canonicalPath",
1379
+ "cwd"
1380
+ ]);
1381
+ }
1382
+ return {
1383
+ skipped: false,
1384
+ projectIdentityKey: identity.key
1385
+ };
1386
+ }
1387
+
1388
+ // src/platforms/index.ts
1389
+ var defaultRegistry = null;
1390
+ function getDefaultAdapterRegistry() {
1391
+ if (!defaultRegistry) {
1392
+ const registry = new AdapterRegistry();
1393
+ registry.register(claudeAdapter);
1394
+ registry.register(opencodeAdapter);
1395
+ defaultRegistry = Object.freeze({
1396
+ resolve: (platform) => registry.resolve(platform),
1397
+ listPlatforms: () => registry.listPlatforms()
1398
+ });
1399
+ }
1400
+ return defaultRegistry;
1401
+ }
1402
+
1403
+ // src/opencode/plugin.ts
1404
+ var OBSERVED_TOOLS = /* @__PURE__ */ new Set([
1405
+ "Read",
1406
+ "Edit",
1407
+ "Write",
1408
+ "Update",
1409
+ "Bash",
1410
+ "Grep",
1411
+ "Glob",
1412
+ "WebFetch",
1413
+ "Task"
1414
+ ]);
1415
+ var ALWAYS_CAPTURE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "Update"]);
1416
+ var MIN_OUTPUT_LENGTH = 50;
1417
+ var MAX_OUTPUT_LENGTH = 2500;
1418
+ var MAX_SESSION_CACHE_SIZE = 500;
1419
+ var MAX_CALL_CACHE_PER_SESSION = 1e3;
1420
+ var TOOL_NAME_MAP = {
1421
+ read: "Read",
1422
+ edit: "Edit",
1423
+ write: "Write",
1424
+ update: "Update",
1425
+ apply_patch: "Update",
1426
+ bash: "Bash",
1427
+ grep: "Grep",
1428
+ glob: "Glob",
1429
+ webfetch: "WebFetch",
1430
+ task: "Task"
1431
+ };
1432
+ var seenSessionIntro = /* @__PURE__ */ new Set();
1433
+ var processedToolCallsBySession = /* @__PURE__ */ new Map();
1434
+ function addToLimitedSet(set, key, maxSize) {
1435
+ if (set.has(key)) {
1436
+ set.delete(key);
1437
+ }
1438
+ set.add(key);
1439
+ while (set.size > maxSize) {
1440
+ const oldest = set.values().next().value;
1441
+ if (typeof oldest !== "string") {
1442
+ break;
1443
+ }
1444
+ set.delete(oldest);
1445
+ }
1446
+ }
1447
+ function touchSessionCallCache(sessionID) {
1448
+ const existing = processedToolCallsBySession.get(sessionID);
1449
+ if (existing) {
1450
+ processedToolCallsBySession.delete(sessionID);
1451
+ processedToolCallsBySession.set(sessionID, existing);
1452
+ return existing;
1453
+ }
1454
+ const callSet = /* @__PURE__ */ new Set();
1455
+ processedToolCallsBySession.set(sessionID, callSet);
1456
+ while (processedToolCallsBySession.size > MAX_SESSION_CACHE_SIZE) {
1457
+ const oldestSessionID = processedToolCallsBySession.keys().next().value;
1458
+ if (typeof oldestSessionID !== "string") {
1459
+ break;
1460
+ }
1461
+ processedToolCallsBySession.delete(oldestSessionID);
1462
+ }
1463
+ return callSet;
1464
+ }
1465
+ function toCanonicalToolName(toolID) {
1466
+ return TOOL_NAME_MAP[toolID.toLowerCase()] || null;
1467
+ }
1468
+ function toToolInput(args) {
1469
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
1470
+ return void 0;
1471
+ }
1472
+ return args;
1473
+ }
1474
+ function toToolOutput(output) {
1475
+ if (typeof output === "string") {
1476
+ return output;
1477
+ }
1478
+ if (output === void 0 || output === null) {
1479
+ return "";
1480
+ }
1481
+ try {
1482
+ return JSON.stringify(output, null, 2);
1483
+ } catch {
1484
+ return String(output);
1485
+ }
1486
+ }
1487
+ function summarizeTool(toolName, toolInput, rawOutput) {
1488
+ switch (toolName) {
1489
+ case "Read": {
1490
+ const path = toolInput?.filePath || "file";
1491
+ const fileName = path.split("/").pop() || "file";
1492
+ return `Read ${fileName}`;
1493
+ }
1494
+ case "Edit":
1495
+ case "Update": {
1496
+ const path = toolInput?.filePath || "file";
1497
+ const fileName = path.split("/").pop() || "file";
1498
+ return `Edited ${fileName}`;
1499
+ }
1500
+ case "Write": {
1501
+ const path = toolInput?.filePath || "file";
1502
+ const fileName = path.split("/").pop() || "file";
1503
+ return `Created ${fileName}`;
1504
+ }
1505
+ case "Bash": {
1506
+ const cmd = toolInput?.command || "command";
1507
+ const hasError = /error|failed|exception/i.test(rawOutput);
1508
+ return hasError ? `Command failed: ${cmd.slice(0, 60)}` : `Ran: ${cmd.slice(0, 60)}`;
1509
+ }
1510
+ case "Grep":
1511
+ return `Searched pattern: ${String(toolInput?.pattern || "").slice(0, 40)}`;
1512
+ case "Glob":
1513
+ return `Matched files: ${String(toolInput?.pattern || "").slice(0, 40)}`;
1514
+ case "WebFetch":
1515
+ return `Fetched: ${String(toolInput?.url || "").slice(0, 60)}`;
1516
+ default:
1517
+ return `${toolName} completed`;
1518
+ }
1519
+ }
1520
+ function extractMetadata(toolName, toolInput, projectIdentityKey) {
1521
+ const metadata = {
1522
+ platform: "opencode",
1523
+ projectIdentityKey
1524
+ };
1525
+ if (!toolInput) {
1526
+ return metadata;
1527
+ }
1528
+ const filePath = toolInput.filePath || toolInput.file_path;
1529
+ if (typeof filePath === "string" && (toolName === "Read" || toolName === "Edit" || toolName === "Write" || toolName === "Update")) {
1530
+ metadata.files = [filePath];
1531
+ }
1532
+ if (toolName === "Bash" && typeof toolInput.command === "string") {
1533
+ metadata.command = toolInput.command.slice(0, 200);
1534
+ }
1535
+ if ((toolName === "Grep" || toolName === "Glob") && typeof toolInput.pattern === "string") {
1536
+ metadata.pattern = toolInput.pattern;
1537
+ }
1538
+ return metadata;
1539
+ }
1540
+ function getUserPromptText(parts) {
1541
+ return parts.filter((part) => {
1542
+ return part.type === "text" && typeof part.text === "string";
1543
+ }).map((part) => part.text).join("\n").trim();
1544
+ }
1545
+ function buildMigrationCommand(projectDir, fromPath, toPath) {
1546
+ const fromDisplay = relative(projectDir, fromPath) || basename(fromPath);
1547
+ const toDisplay = relative(projectDir, toPath) || basename(toPath);
1548
+ return `mkdir -p "${dirname(toDisplay)}" && mv "${fromDisplay}" "${toDisplay}"`;
1549
+ }
1550
+ function buildInjectedContext(options) {
1551
+ const lines = [];
1552
+ lines.push("<agent-brain-context>");
1553
+ lines.push("# Agent Brain Memory Context");
1554
+ lines.push("");
1555
+ lines.push(`Project: ${basename(options.projectDir)}`);
1556
+ lines.push(`Platform: opencode`);
1557
+ lines.push(`Memory: ${relative(options.projectDir, options.memoryPath) || basename(options.memoryPath)}${options.memoryExists ? ` (${options.fileSizeKB} KB)` : ""}`);
1558
+ if (options.migrationCommand) {
1559
+ lines.push("");
1560
+ lines.push("Legacy memory file detected.");
1561
+ lines.push(`Move it to the platform-agnostic path with: ${options.migrationCommand}`);
1562
+ }
1563
+ if (options.recent.length > 0) {
1564
+ lines.push("");
1565
+ lines.push("Recent memory highlights:");
1566
+ for (const item of options.recent.slice(0, 6)) {
1567
+ lines.push(`- [${item.type}] ${item.summary}`);
1568
+ }
1569
+ }
1570
+ if (options.relevant.length > 0) {
1571
+ lines.push("");
1572
+ lines.push("Relevant memories for this prompt:");
1573
+ for (const item of options.relevant.slice(0, 4)) {
1574
+ lines.push(`- [${item.type}] ${item.summary}`);
1575
+ }
1576
+ }
1577
+ lines.push("");
1578
+ lines.push("Use these memories as background context while responding.");
1579
+ lines.push("</agent-brain-context>");
1580
+ return lines.join("\n");
1581
+ }
1582
+ var AgentBrainOpenCodePlugin = async ({ directory, project }) => {
1583
+ return {
1584
+ "chat.message": async (input, output) => {
1585
+ if (seenSessionIntro.has(input.sessionID)) {
1586
+ return;
1587
+ }
1588
+ addToLimitedSet(seenSessionIntro, input.sessionID, MAX_SESSION_CACHE_SIZE);
1589
+ const pathPolicy = resolveMemoryPathPolicy({
1590
+ projectDir: directory,
1591
+ platform: "opencode",
1592
+ defaultRelativePath: ".agent-brain/mind.mv2",
1593
+ legacyRelativePaths: [".claude/mind.mv2"],
1594
+ platformRelativePath: process.env.MEMVID_PLATFORM_MEMORY_PATH,
1595
+ platformOptIn: process.env.MEMVID_PLATFORM_PATH_OPT_IN === "1"
1596
+ });
1597
+ const memoryExists = existsSync(pathPolicy.memoryPath);
1598
+ let fileSizeKB = 0;
1599
+ if (memoryExists) {
1600
+ try {
1601
+ fileSizeKB = Math.round(statSync(pathPolicy.memoryPath).size / 1024);
1602
+ } catch {
1603
+ fileSizeKB = 0;
1604
+ }
1605
+ }
1606
+ const query = getUserPromptText(output.parts);
1607
+ const mind = await getMind();
1608
+ const context = await mind.getContext(query || void 0);
1609
+ const migrationCommand = pathPolicy.migrationSuggestion ? buildMigrationCommand(
1610
+ directory,
1611
+ pathPolicy.migrationSuggestion.fromPath,
1612
+ pathPolicy.migrationSuggestion.toPath
1613
+ ) : void 0;
1614
+ const injected = buildInjectedContext({
1615
+ projectDir: directory,
1616
+ memoryPath: pathPolicy.memoryPath,
1617
+ memoryExists,
1618
+ fileSizeKB,
1619
+ recent: context.recentObservations.map((obs) => ({
1620
+ type: obs.type,
1621
+ summary: obs.summary
1622
+ })),
1623
+ relevant: context.relevantMemories.map((obs) => ({
1624
+ type: obs.type,
1625
+ summary: obs.summary
1626
+ })),
1627
+ migrationCommand
1628
+ });
1629
+ const part = {
1630
+ id: `agent-brain-context-${Date.now()}`,
1631
+ type: "text",
1632
+ text: injected,
1633
+ sessionID: input.sessionID,
1634
+ messageID: output.message.id
1635
+ };
1636
+ output.parts.unshift(part);
1637
+ },
1638
+ "tool.execute.after": async (input, output) => {
1639
+ const sessionCallCache = touchSessionCallCache(input.sessionID);
1640
+ if (sessionCallCache.has(input.callID)) {
1641
+ return;
1642
+ }
1643
+ const canonicalToolName = toCanonicalToolName(input.tool);
1644
+ if (!canonicalToolName || !OBSERVED_TOOLS.has(canonicalToolName)) {
1645
+ return;
1646
+ }
1647
+ const registry = getDefaultAdapterRegistry();
1648
+ const adapter = registry.resolve("opencode");
1649
+ if (!adapter) {
1650
+ return;
1651
+ }
1652
+ const hookInput = {
1653
+ session_id: input.sessionID,
1654
+ platform: "opencode",
1655
+ contract_version: "1.0.0",
1656
+ project_id: project.id,
1657
+ cwd: directory,
1658
+ tool_name: canonicalToolName,
1659
+ tool_input: toToolInput(input.args),
1660
+ tool_response: output.output,
1661
+ tool_use_id: input.callID
1662
+ };
1663
+ const normalized = adapter.normalizeToolObservation(hookInput);
1664
+ if (!normalized) {
1665
+ return;
1666
+ }
1667
+ const processed = processPlatformEvent(normalized);
1668
+ if (processed.skipped || !processed.projectIdentityKey) {
1669
+ return;
1670
+ }
1671
+ const rawOutput = toToolOutput(output.output);
1672
+ const alwaysCapture = ALWAYS_CAPTURE_TOOLS.has(canonicalToolName);
1673
+ if (!alwaysCapture && rawOutput.length < MIN_OUTPUT_LENGTH) {
1674
+ return;
1675
+ }
1676
+ if (rawOutput.includes("<agent-brain-context>") || rawOutput.includes("<memvid-mind-context>")) {
1677
+ return;
1678
+ }
1679
+ const toolInput = toToolInput(input.args);
1680
+ const { compressed, wasCompressed, originalSize } = compressToolOutput(
1681
+ canonicalToolName,
1682
+ toolInput,
1683
+ rawOutput
1684
+ );
1685
+ const content = compressed.length > MAX_OUTPUT_LENGTH ? `${compressed.slice(0, MAX_OUTPUT_LENGTH)}
1686
+ ... (truncated${wasCompressed ? ", compressed" : ""})` : compressed;
1687
+ const metadata = extractMetadata(
1688
+ canonicalToolName,
1689
+ toolInput,
1690
+ processed.projectIdentityKey
1691
+ );
1692
+ if (wasCompressed) {
1693
+ metadata.compressed = true;
1694
+ metadata.originalSize = originalSize;
1695
+ metadata.compressedSize = compressed.length;
1696
+ }
1697
+ const mind = await getMind();
1698
+ await mind.remember({
1699
+ type: classifyObservationType(canonicalToolName, content),
1700
+ summary: summarizeTool(canonicalToolName, toolInput, rawOutput),
1701
+ content,
1702
+ tool: canonicalToolName,
1703
+ metadata
1704
+ });
1705
+ addToLimitedSet(sessionCallCache, input.callID, MAX_CALL_CACHE_PER_SESSION);
1706
+ },
1707
+ tool: {
1708
+ mind: tool({
1709
+ description: "Query and store Agent Brain memories",
1710
+ args: {
1711
+ mode: tool.schema.enum(["search", "ask", "recent", "stats", "remember"]).describe("Operation to perform"),
1712
+ query: tool.schema.string().optional().describe("Search query or question"),
1713
+ limit: tool.schema.number().optional().describe("Result limit"),
1714
+ type: tool.schema.enum([
1715
+ "discovery",
1716
+ "decision",
1717
+ "problem",
1718
+ "solution",
1719
+ "pattern",
1720
+ "warning",
1721
+ "success",
1722
+ "refactor",
1723
+ "bugfix",
1724
+ "feature"
1725
+ ]).optional().describe("Observation type for remember mode"),
1726
+ summary: tool.schema.string().optional().describe("Short memory summary"),
1727
+ content: tool.schema.string().optional().describe("Detailed memory content")
1728
+ },
1729
+ async execute(args) {
1730
+ const mind = await getMind();
1731
+ const limit = args.limit && args.limit > 0 ? Math.min(args.limit, 25) : 10;
1732
+ if (args.mode === "search") {
1733
+ if (!args.query) {
1734
+ return JSON.stringify({ success: false, error: "query is required for search" });
1735
+ }
1736
+ const results = await mind.search(args.query, limit);
1737
+ return JSON.stringify({
1738
+ success: true,
1739
+ mode: "search",
1740
+ query: args.query,
1741
+ count: results.length,
1742
+ results: results.map((item) => ({
1743
+ score: item.score,
1744
+ summary: item.observation.summary,
1745
+ type: item.observation.type,
1746
+ tool: item.observation.tool
1747
+ }))
1748
+ });
1749
+ }
1750
+ if (args.mode === "ask") {
1751
+ if (!args.query) {
1752
+ return JSON.stringify({ success: false, error: "query is required for ask" });
1753
+ }
1754
+ const answer = await mind.ask(args.query);
1755
+ return JSON.stringify({ success: true, mode: "ask", answer });
1756
+ }
1757
+ if (args.mode === "recent") {
1758
+ const context = await mind.getContext();
1759
+ return JSON.stringify({
1760
+ success: true,
1761
+ mode: "recent",
1762
+ count: Math.min(context.recentObservations.length, limit),
1763
+ observations: context.recentObservations.slice(0, limit).map((obs) => ({
1764
+ type: obs.type,
1765
+ summary: obs.summary,
1766
+ tool: obs.tool,
1767
+ timestamp: obs.timestamp
1768
+ }))
1769
+ });
1770
+ }
1771
+ if (args.mode === "stats") {
1772
+ const stats = await mind.stats();
1773
+ return JSON.stringify({ success: true, mode: "stats", stats });
1774
+ }
1775
+ if (args.mode === "remember") {
1776
+ if (!args.summary || !args.content) {
1777
+ return JSON.stringify({
1778
+ success: false,
1779
+ error: "summary and content are required for remember"
1780
+ });
1781
+ }
1782
+ const observationType = args.type || "discovery";
1783
+ const id = await mind.remember({
1784
+ type: observationType,
1785
+ summary: args.summary,
1786
+ content: args.content,
1787
+ tool: "mind",
1788
+ metadata: {
1789
+ platform: "opencode",
1790
+ source: "manual"
1791
+ }
1792
+ });
1793
+ return JSON.stringify({ success: true, mode: "remember", id });
1794
+ }
1795
+ return JSON.stringify({
1796
+ success: false,
1797
+ error: `Unsupported mode: ${String(args.mode)}`
1798
+ });
1799
+ }
1800
+ })
1801
+ },
1802
+ event: async ({ event }) => {
1803
+ if (event.type !== "session.deleted") {
1804
+ return;
1805
+ }
1806
+ const eventData = event.properties;
1807
+ const sessionID = eventData.info?.id;
1808
+ if (!sessionID) {
1809
+ return;
1810
+ }
1811
+ seenSessionIntro.delete(sessionID);
1812
+ processedToolCallsBySession.delete(sessionID);
1813
+ }
1814
+ };
1815
+ };
1816
+ var plugin_default = AgentBrainOpenCodePlugin;
1817
+
1818
+ export { AgentBrainOpenCodePlugin, plugin_default as default };
1819
+ //# sourceMappingURL=plugin.js.map
1820
+ //# sourceMappingURL=plugin.js.map