@memclaw/memclaw 0.9.16 → 0.9.18

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.
@@ -4,7 +4,10 @@
4
4
  *
5
5
  * Provides layered semantic memory for OpenClaw with:
6
6
  * - Automatic service startup
7
- * - Memory tools (search, recall, add, list, close)
7
+ * - Memory tools (search, recall, add, close)
8
+ * - Tiered access (L0/L1/L2)
9
+ * - Filesystem browsing
10
+ * - Smart exploration
8
11
  * - Migration from OpenClaw native memory
9
12
  */
10
13
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -18,12 +21,18 @@ const toolSchemas = {
18
21
  cortex_search: {
19
22
  name: 'cortex_search',
20
23
  description: `Layered semantic search across memory using L0/L1/L2 tiered retrieval.
21
- Returns relevant memories ranked by relevance score.
22
24
 
23
- Use this tool when you need to:
24
- - Find past conversations or decisions
25
- - Search for specific information across all sessions
26
- - Discover related memories by semantic similarity`,
25
+ **Key Features:**
26
+ - Tiered retrieval: L0 (abstract) -> L1 (overview) -> L2 (full content)
27
+ - Token-efficient: Control exactly which layers to return
28
+
29
+ **Parameters:**
30
+ - return_layers: ["L0"] (default, ~100 tokens), ["L0","L1"] (~2100 tokens), ["L0","L1","L2"] (full)
31
+
32
+ **When to use:**
33
+ - Finding past conversations or decisions
34
+ - Searching across all sessions
35
+ - Discovering related memories by semantic similarity`,
27
36
  inputSchema: {
28
37
  type: 'object',
29
38
  properties: {
@@ -44,6 +53,15 @@ Use this tool when you need to:
44
53
  type: 'number',
45
54
  description: 'Minimum relevance score threshold (0-1, default: 0.6)',
46
55
  default: 0.6
56
+ },
57
+ return_layers: {
58
+ type: 'array',
59
+ items: {
60
+ type: 'string',
61
+ enum: ['L0', 'L1', 'L2']
62
+ },
63
+ description: 'Which layers to return. Default: ["L0"]. Use ["L0","L1"] for more context, ["L0","L1","L2"] for full content.',
64
+ default: ['L0']
47
65
  }
48
66
  },
49
67
  required: ['query']
@@ -51,16 +69,10 @@ Use this tool when you need to:
51
69
  },
52
70
  cortex_recall: {
53
71
  name: 'cortex_recall',
54
- description: `Recall memories using L0/L1/L2 tiered retrieval.
55
-
56
- The search engine internally performs tiered retrieval:
57
- - L0 (Abstract): Quick filtering by summary
58
- - L1 (Overview): Context refinement
59
- - L2 (Full): Precise matching with full content
72
+ description: `Recall memories with full context (L0 snippet + L2 content).
60
73
 
61
- Returns results with snippet (summary) and content (if available).
62
-
63
- Use this when you need memories with more context than a simple search.`,
74
+ This is a convenience wrapper that returns both abstract and full content.
75
+ Use cortex_search with return_layers=["L0","L2"] for more control.`,
64
76
  inputSchema: {
65
77
  type: 'object',
66
78
  properties: {
@@ -84,10 +96,14 @@ Use this when you need memories with more context than a simple search.`,
84
96
  cortex_add_memory: {
85
97
  name: 'cortex_add_memory',
86
98
  description: `Add a message to memory for a specific session.
99
+
87
100
  This stores the message and automatically triggers:
88
101
  - Vector embedding for semantic search
89
102
  - L0/L1 layer generation (async)
90
103
 
104
+ **Metadata support:**
105
+ You can attach metadata like tags, importance, or custom fields.
106
+
91
107
  Use this to persist important information that should be searchable later.`,
92
108
  inputSchema: {
93
109
  type: 'object',
@@ -105,20 +121,16 @@ Use this to persist important information that should be searchable later.`,
105
121
  session_id: {
106
122
  type: 'string',
107
123
  description: 'Session/thread ID (uses default if not specified)'
124
+ },
125
+ metadata: {
126
+ type: 'object',
127
+ description: 'Optional metadata (tags, importance, custom fields)',
128
+ additionalProperties: true
108
129
  }
109
130
  },
110
131
  required: ['content']
111
132
  }
112
133
  },
113
- cortex_list_sessions: {
114
- name: 'cortex_list_sessions',
115
- description: `List all memory sessions with their status.
116
- Shows session IDs, message counts, and creation/update times.`,
117
- inputSchema: {
118
- type: 'object',
119
- properties: {}
120
- }
121
- },
122
134
  cortex_close_session: {
123
135
  name: 'cortex_close_session',
124
136
  description: `Trigger memory extraction and archival for accumulated conversation content.
@@ -154,6 +166,154 @@ This triggers the complete memory processing pipeline:
154
166
  }
155
167
  }
156
168
  },
169
+ // ==================== Filesystem Tools ====================
170
+ cortex_ls: {
171
+ name: 'cortex_ls',
172
+ description: `List directory contents to browse the memory space like a virtual filesystem.
173
+
174
+ This allows you to explore the hierarchical structure of memories:
175
+ - cortex://session - List all sessions
176
+ - cortex://session/{session_id} - Browse a specific session's contents
177
+ - cortex://session/{session_id}/timeline - View timeline messages
178
+ - cortex://session/{session_id}/memories - View extracted memories
179
+
180
+ **Parameters:**
181
+ - recursive: List all subdirectories recursively
182
+ - include_abstracts: Show L0 abstracts for each file (for quick preview)
183
+
184
+ Use this when:
185
+ - Semantic search doesn't find what you need
186
+ - You want to understand the overall memory layout
187
+ - You need to manually navigate to find specific information`,
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ uri: {
192
+ type: 'string',
193
+ description: 'Directory URI to list (default: cortex://session)',
194
+ default: 'cortex://session'
195
+ },
196
+ recursive: {
197
+ type: 'boolean',
198
+ description: 'Whether to recursively list subdirectories',
199
+ default: false
200
+ },
201
+ include_abstracts: {
202
+ type: 'boolean',
203
+ description: 'Whether to include L0 abstracts for each file',
204
+ default: false
205
+ }
206
+ }
207
+ }
208
+ },
209
+ // ==================== Tiered Access Tools ====================
210
+ cortex_get_abstract: {
211
+ name: 'cortex_get_abstract',
212
+ description: `Get L0 abstract layer (~100 tokens) for quick relevance checking.
213
+
214
+ Abstracts are short summaries ideal for quickly determining if content is relevant
215
+ before committing to reading more. Use this to minimize token consumption.
216
+
217
+ Use when:
218
+ - You found a URI from cortex_ls and want to quickly check relevance
219
+ - You need to filter many candidates before deep reading
220
+ - You want the most token-efficient preview`,
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ uri: {
225
+ type: 'string',
226
+ description: 'Content URI (file or directory)'
227
+ }
228
+ },
229
+ required: ['uri']
230
+ }
231
+ },
232
+ cortex_get_overview: {
233
+ name: 'cortex_get_overview',
234
+ description: `Get L1 overview layer (~2000 tokens) with core information and context.
235
+
236
+ Overviews contain key points and contextual information. Use this when:
237
+ - The abstract was relevant but you need more details
238
+ - You want to understand the gist without full content
239
+ - You need moderate detail for decision making`,
240
+ inputSchema: {
241
+ type: 'object',
242
+ properties: {
243
+ uri: {
244
+ type: 'string',
245
+ description: 'Content URI (file or directory)'
246
+ }
247
+ },
248
+ required: ['uri']
249
+ }
250
+ },
251
+ cortex_get_content: {
252
+ name: 'cortex_get_content',
253
+ description: `Get L2 full content layer - the complete original content.
254
+
255
+ Use this ONLY when you need the complete, unprocessed content.
256
+ This returns the full content which may be large.
257
+
258
+ Use when:
259
+ - You need exact details or quotes
260
+ - Abstract and overview don't provide enough information
261
+ - You need to see the original, unsummarized content`,
262
+ inputSchema: {
263
+ type: 'object',
264
+ properties: {
265
+ uri: {
266
+ type: 'string',
267
+ description: 'Content URI (file only)'
268
+ }
269
+ },
270
+ required: ['uri']
271
+ }
272
+ },
273
+ // ==================== Exploration Tool ====================
274
+ cortex_explore: {
275
+ name: 'cortex_explore',
276
+ description: `Smart exploration of memory space, combining search and browsing.
277
+
278
+ This tool performs a guided exploration:
279
+ 1. Searches within a specified scope (start_uri)
280
+ 2. Returns an exploration path showing relevance scores
281
+ 3. Returns matching results with requested layers
282
+
283
+ **When to use:**
284
+ - When you need to "wander" through memories with a purpose
285
+ - When you want to discover related content in a specific area
286
+ - When combining keyword hints with semantic discovery
287
+
288
+ **Parameters:**
289
+ - start_uri: Where to begin exploration (default: cortex://session)
290
+ - return_layers: Which layers to include in matches`,
291
+ inputSchema: {
292
+ type: 'object',
293
+ properties: {
294
+ query: {
295
+ type: 'string',
296
+ description: 'Exploration query - what to look for'
297
+ },
298
+ start_uri: {
299
+ type: 'string',
300
+ description: 'Starting URI for exploration',
301
+ default: 'cortex://session'
302
+ },
303
+ return_layers: {
304
+ type: 'array',
305
+ items: {
306
+ type: 'string',
307
+ enum: ['L0', 'L1', 'L2']
308
+ },
309
+ description: 'Which layers to return in matches',
310
+ default: ['L0']
311
+ }
312
+ },
313
+ required: ['query']
314
+ }
315
+ },
316
+ // ==================== Migration & Maintenance ====================
157
317
  cortex_migrate: {
158
318
  name: 'cortex_migrate',
159
319
  description: `Migrate memories from OpenClaw's native memory system to MemClaw.
@@ -229,8 +389,8 @@ function createPlugin(api) {
229
389
  log(`Created configuration file: ${configPath}`);
230
390
  log('Opening configuration file for editing...');
231
391
  (0, config_js_1.openConfigFile)(configPath).catch((err) => {
232
- api.logger.warn(`Could not open config file: ${err}`);
233
- api.logger.warn(`Please manually edit: ${configPath}`);
392
+ api.logger.warn(`[memclaw] Could not open config file: ${err}`);
393
+ api.logger.warn(`[memclaw] Please manually edit: ${configPath}`);
234
394
  });
235
395
  api.logger.info(`
236
396
  ╔══════════════════════════════════════════════════════════╗
@@ -286,8 +446,8 @@ function createPlugin(api) {
286
446
  const mergedConfig = (0, config_js_1.mergeConfigWithPlugin)(fileConfig, pluginProvidedConfig);
287
447
  const validation = (0, config_js_1.validateConfig)(mergedConfig);
288
448
  if (!validation.valid) {
289
- api.logger.warn(`Configuration incomplete: ${validation.errors.join(', ')}`);
290
- api.logger.warn(`Please configure LLM/Embedding API keys in OpenClaw plugin settings or edit: ${configPath}`);
449
+ api.logger.warn(`[memclaw] Configuration incomplete: ${validation.errors.join(', ')}`);
450
+ api.logger.warn(`[memclaw] Please configure LLM/Embedding API keys in OpenClaw plugin settings or edit: ${configPath}`);
291
451
  return;
292
452
  }
293
453
  // Start services
@@ -303,7 +463,7 @@ function createPlugin(api) {
303
463
  maintenanceTimer = setInterval(async () => {
304
464
  try {
305
465
  log('Running scheduled maintenance...');
306
- const configPath = (0, config_js_1.getConfigPath)();
466
+ const currentConfigPath = (0, config_js_1.getConfigPath)();
307
467
  // Run maintenance commands
308
468
  const commands = [
309
469
  ['vector', 'prune'],
@@ -311,7 +471,7 @@ function createPlugin(api) {
311
471
  ['layers', 'ensure-all']
312
472
  ];
313
473
  for (const cmd of commands) {
314
- const result = await (0, binaries_js_1.executeCliCommand)(cmd, configPath, tenantId, 300000);
474
+ const result = await (0, binaries_js_1.executeCliCommand)(cmd, currentConfigPath, tenantId, 300000);
315
475
  if (!result.success) {
316
476
  log(`Maintenance command '${cmd.join(' ')}' failed: ${result.stderr}`);
317
477
  }
@@ -325,8 +485,8 @@ function createPlugin(api) {
325
485
  log('Maintenance timer started (runs every 3 hours)');
326
486
  }
327
487
  catch (err) {
328
- api.logger.error(`Failed to start services: ${err}`);
329
- api.logger.warn('Memory features may not work correctly');
488
+ api.logger.error(`[memclaw] Failed to start services: ${err}`);
489
+ api.logger.warn('[memclaw] Memory features may not work correctly');
330
490
  }
331
491
  },
332
492
  stop: async () => {
@@ -349,7 +509,7 @@ function createPlugin(api) {
349
509
  }
350
510
  }
351
511
  };
352
- // Register tools
512
+ // ==================== Register Tools ====================
353
513
  // cortex_search
354
514
  api.registerTool({
355
515
  name: toolSchemas.cortex_search.name,
@@ -363,24 +523,40 @@ function createPlugin(api) {
363
523
  query: input.query,
364
524
  thread: input.scope,
365
525
  limit: input.limit ?? searchLimit,
366
- min_score: input.min_score ?? minScore
526
+ min_score: input.min_score ?? minScore,
527
+ return_layers: input.return_layers ?? ['L0']
367
528
  });
368
529
  const formatted = results
369
- .map((r, i) => `${i + 1}. [Score: ${r.score.toFixed(2)}] ${r.snippet}\n URI: ${r.uri}`)
370
- .join('\n\n');
530
+ .map((r, i) => {
531
+ let content = `${i + 1}. [Score: ${r.score.toFixed(2)}] URI: ${r.uri}\n`;
532
+ content += ` Layers: ${r.layers.join(', ')}\n`;
533
+ content += ` Snippet: ${r.snippet}\n`;
534
+ if (r.overview) {
535
+ content += ` Overview: ${r.overview.substring(0, 200)}...\n`;
536
+ }
537
+ if (r.content) {
538
+ const preview = r.content.length > 200 ? r.content.substring(0, 200) + '...' : r.content;
539
+ content += ` Content: ${preview}\n`;
540
+ }
541
+ return content;
542
+ })
543
+ .join('\n');
371
544
  return {
372
545
  content: `Found ${results.length} results for "${input.query}":\n\n${formatted}`,
373
546
  results: results.map((r) => ({
374
547
  uri: r.uri,
375
548
  score: r.score,
376
- snippet: r.snippet
549
+ snippet: r.snippet,
550
+ overview: r.overview,
551
+ content: r.content,
552
+ layers: r.layers
377
553
  })),
378
554
  total: results.length
379
555
  };
380
556
  }
381
557
  catch (error) {
382
558
  const message = error instanceof Error ? error.message : String(error);
383
- api.logger.error(`cortex_search failed: ${message}`);
559
+ api.logger.error(`[memclaw] cortex_search failed: ${message}`);
384
560
  return { error: `Search failed: ${message}` };
385
561
  }
386
562
  }
@@ -414,7 +590,7 @@ function createPlugin(api) {
414
590
  }
415
591
  catch (error) {
416
592
  const message = error instanceof Error ? error.message : String(error);
417
- api.logger.error(`cortex_recall failed: ${message}`);
593
+ api.logger.error(`[memclaw] cortex_recall failed: ${message}`);
418
594
  return { error: `Recall failed: ${message}` };
419
595
  }
420
596
  }
@@ -431,7 +607,8 @@ function createPlugin(api) {
431
607
  const sessionId = input.session_id ?? defaultSessionId;
432
608
  const result = await client.addMessage(sessionId, {
433
609
  role: (input.role ?? 'user'),
434
- content: input.content
610
+ content: input.content,
611
+ metadata: input.metadata
435
612
  });
436
613
  return {
437
614
  content: `Memory stored successfully in session "${sessionId}".\nResult: ${result}`,
@@ -441,71 +618,213 @@ function createPlugin(api) {
441
618
  }
442
619
  catch (error) {
443
620
  const message = error instanceof Error ? error.message : String(error);
444
- api.logger.error(`cortex_add_memory failed: ${message}`);
621
+ api.logger.error(`[memclaw] cortex_add_memory failed: ${message}`);
445
622
  return { error: `Failed to add memory: ${message}` };
446
623
  }
447
624
  }
448
625
  });
449
- // cortex_list_sessions
626
+ // cortex_close_session
450
627
  api.registerTool({
451
- name: toolSchemas.cortex_list_sessions.name,
452
- description: toolSchemas.cortex_list_sessions.description,
453
- parameters: toolSchemas.cortex_list_sessions.inputSchema,
454
- execute: async (_id, _params) => {
628
+ name: toolSchemas.cortex_close_session.name,
629
+ description: toolSchemas.cortex_close_session.description,
630
+ parameters: toolSchemas.cortex_close_session.inputSchema,
631
+ execute: async (_id, params) => {
632
+ const input = params;
633
+ try {
634
+ await ensureServicesReady();
635
+ const sessionId = input.session_id ?? defaultSessionId;
636
+ const result = await client.closeSession(sessionId);
637
+ return {
638
+ content: `Session "${sessionId}" closed successfully.\nStatus: ${result.status}, Messages: ${result.message_count}\n\nMemory extraction pipeline triggered.`,
639
+ success: true,
640
+ session: {
641
+ thread_id: result.thread_id,
642
+ status: result.status,
643
+ message_count: result.message_count
644
+ }
645
+ };
646
+ }
647
+ catch (error) {
648
+ const message = error instanceof Error ? error.message : String(error);
649
+ api.logger.error(`[memclaw] cortex_close_session failed: ${message}`);
650
+ return { error: `Failed to close session: ${message}` };
651
+ }
652
+ }
653
+ });
654
+ // cortex_ls
655
+ api.registerTool({
656
+ name: toolSchemas.cortex_ls.name,
657
+ description: toolSchemas.cortex_ls.description,
658
+ parameters: toolSchemas.cortex_ls.inputSchema,
659
+ execute: async (_id, params) => {
660
+ const input = params;
455
661
  try {
456
662
  await ensureServicesReady();
457
- const sessions = await client.listSessions();
458
- if (sessions.length === 0) {
459
- return { content: 'No sessions found.' };
663
+ const result = await client.ls({
664
+ uri: input.uri ?? 'cortex://session',
665
+ recursive: input.recursive ?? false,
666
+ include_abstracts: input.include_abstracts ?? false
667
+ });
668
+ if (result.entries.length === 0) {
669
+ return { content: `Directory "${result.uri}" is empty or does not exist.` };
460
670
  }
461
- const formatted = sessions
462
- .map((s, i) => {
463
- const created = new Date(s.created_at).toLocaleDateString();
464
- return `${i + 1}. ${s.thread_id} (${s.status}, ${s.message_count} messages, created ${created})`;
671
+ const formatted = result.entries
672
+ .map((e, i) => {
673
+ let content = `${i + 1}. ${e.is_directory ? '📁' : '📄'} ${e.name}\n`;
674
+ content += ` URI: ${e.uri}\n`;
675
+ if (e.is_directory) {
676
+ content += ` Type: Directory\n`;
677
+ }
678
+ else {
679
+ content += ` Size: ${e.size} bytes\n`;
680
+ }
681
+ if (e.abstract_text) {
682
+ const preview = e.abstract_text.length > 100
683
+ ? e.abstract_text.substring(0, 100) + '...'
684
+ : e.abstract_text;
685
+ content += ` Abstract: ${preview}\n`;
686
+ }
687
+ return content;
465
688
  })
466
689
  .join('\n');
467
690
  return {
468
- content: `Found ${sessions.length} sessions:\n\n${formatted}`,
469
- sessions: sessions.map((s) => ({
470
- thread_id: s.thread_id,
471
- status: s.status,
472
- message_count: s.message_count,
473
- created_at: s.created_at
474
- }))
691
+ content: `Directory "${result.uri}" (${result.total} entries):\n\n${formatted}`,
692
+ entries: result.entries,
693
+ total: result.total
475
694
  };
476
695
  }
477
696
  catch (error) {
478
697
  const message = error instanceof Error ? error.message : String(error);
479
- api.logger.error(`cortex_list_sessions failed: ${message}`);
480
- return { error: `Failed to list sessions: ${message}` };
698
+ api.logger.error(`[memclaw] cortex_ls failed: ${message}`);
699
+ return { error: `List directory failed: ${message}` };
481
700
  }
482
701
  }
483
702
  });
484
- // cortex_close_session
703
+ // cortex_get_abstract
485
704
  api.registerTool({
486
- name: toolSchemas.cortex_close_session.name,
487
- description: toolSchemas.cortex_close_session.description,
488
- parameters: toolSchemas.cortex_close_session.inputSchema,
705
+ name: toolSchemas.cortex_get_abstract.name,
706
+ description: toolSchemas.cortex_get_abstract.description,
707
+ parameters: toolSchemas.cortex_get_abstract.inputSchema,
489
708
  execute: async (_id, params) => {
490
709
  const input = params;
491
710
  try {
492
711
  await ensureServicesReady();
493
- const sessionId = input.session_id ?? defaultSessionId;
494
- const result = await client.closeSession(sessionId);
712
+ const result = await client.getAbstract(input.uri);
495
713
  return {
496
- content: `Session "${sessionId}" closed successfully.\nStatus: ${result.status}, Messages: ${result.message_count}\n\nMemory extraction pipeline triggered.`,
497
- success: true,
498
- session: {
499
- thread_id: result.thread_id,
500
- status: result.status,
501
- message_count: result.message_count
714
+ content: `L0 Abstract for "${result.uri}" (~${result.token_count} tokens):\n\n${result.content}`,
715
+ uri: result.uri,
716
+ abstract: result.content,
717
+ token_count: result.token_count,
718
+ layer: result.layer
719
+ };
720
+ }
721
+ catch (error) {
722
+ const message = error instanceof Error ? error.message : String(error);
723
+ api.logger.error(`[memclaw] cortex_get_abstract failed: ${message}`);
724
+ return { error: `Get abstract failed: ${message}` };
725
+ }
726
+ }
727
+ });
728
+ // cortex_get_overview
729
+ api.registerTool({
730
+ name: toolSchemas.cortex_get_overview.name,
731
+ description: toolSchemas.cortex_get_overview.description,
732
+ parameters: toolSchemas.cortex_get_overview.inputSchema,
733
+ execute: async (_id, params) => {
734
+ const input = params;
735
+ try {
736
+ await ensureServicesReady();
737
+ const result = await client.getOverview(input.uri);
738
+ return {
739
+ content: `L1 Overview for "${result.uri}" (~${result.token_count} tokens):\n\n${result.content}`,
740
+ uri: result.uri,
741
+ overview: result.content,
742
+ token_count: result.token_count,
743
+ layer: result.layer
744
+ };
745
+ }
746
+ catch (error) {
747
+ const message = error instanceof Error ? error.message : String(error);
748
+ api.logger.error(`[memclaw] cortex_get_overview failed: ${message}`);
749
+ return { error: `Get overview failed: ${message}` };
750
+ }
751
+ }
752
+ });
753
+ // cortex_get_content
754
+ api.registerTool({
755
+ name: toolSchemas.cortex_get_content.name,
756
+ description: toolSchemas.cortex_get_content.description,
757
+ parameters: toolSchemas.cortex_get_content.inputSchema,
758
+ execute: async (_id, params) => {
759
+ const input = params;
760
+ try {
761
+ await ensureServicesReady();
762
+ const result = await client.getContent(input.uri);
763
+ return {
764
+ content: `L2 Full Content for "${result.uri}" (~${result.token_count} tokens):\n\n${result.content}`,
765
+ uri: result.uri,
766
+ full_content: result.content,
767
+ token_count: result.token_count,
768
+ layer: result.layer
769
+ };
770
+ }
771
+ catch (error) {
772
+ const message = error instanceof Error ? error.message : String(error);
773
+ api.logger.error(`[memclaw] cortex_get_content failed: ${message}`);
774
+ return { error: `Get content failed: ${message}` };
775
+ }
776
+ }
777
+ });
778
+ // cortex_explore
779
+ api.registerTool({
780
+ name: toolSchemas.cortex_explore.name,
781
+ description: toolSchemas.cortex_explore.description,
782
+ parameters: toolSchemas.cortex_explore.inputSchema,
783
+ execute: async (_id, params) => {
784
+ const input = params;
785
+ try {
786
+ await ensureServicesReady();
787
+ const result = await client.explore({
788
+ query: input.query,
789
+ start_uri: input.start_uri ?? 'cortex://session',
790
+ return_layers: input.return_layers ?? ['L0']
791
+ });
792
+ // Format exploration path
793
+ const pathFormatted = result.exploration_path
794
+ .map((item, i) => {
795
+ let content = `${i + 1}. [${item.relevance_score.toFixed(2)}] ${item.uri}\n`;
796
+ if (item.abstract_text) {
797
+ const preview = item.abstract_text.length > 80
798
+ ? item.abstract_text.substring(0, 80) + '...'
799
+ : item.abstract_text;
800
+ content += ` Abstract: ${preview}\n`;
502
801
  }
802
+ return content;
803
+ })
804
+ .join('\n');
805
+ // Format matches
806
+ const matchesFormatted = result.matches
807
+ .map((m, i) => {
808
+ let content = `${i + 1}. [${m.score.toFixed(2)}] ${m.uri}\n`;
809
+ content += ` Layers: ${m.layers.join(', ')}\n`;
810
+ content += ` Snippet: ${m.snippet}\n`;
811
+ return content;
812
+ })
813
+ .join('\n');
814
+ return {
815
+ content: `Exploration for "${input.query}" starting from "${input.start_uri ?? 'cortex://session'}":\n\n` +
816
+ `**Exploration Path** (${result.total_explored} items):\n${pathFormatted}\n\n` +
817
+ `**Matches** (${result.total_matches} found):\n${matchesFormatted}`,
818
+ exploration_path: result.exploration_path,
819
+ matches: result.matches,
820
+ total_explored: result.total_explored,
821
+ total_matches: result.total_matches
503
822
  };
504
823
  }
505
824
  catch (error) {
506
825
  const message = error instanceof Error ? error.message : String(error);
507
- api.logger.error(`cortex_close_session failed: ${message}`);
508
- return { error: `Failed to close session: ${message}` };
826
+ api.logger.error(`[memclaw] cortex_explore failed: ${message}`);
827
+ return { error: `Explore failed: ${message}` };
509
828
  }
510
829
  }
511
830
  });
@@ -577,7 +896,7 @@ function createPlugin(api) {
577
896
  output: result.stdout || result.stderr
578
897
  });
579
898
  if (!result.success) {
580
- api.logger.warn(`[maintenance] ${description} failed: ${result.stderr}`);
899
+ api.logger.warn(`[memclaw] [maintenance] ${description} failed: ${result.stderr}`);
581
900
  }
582
901
  }
583
902
  catch (error) {