@ubundi/openclaw-cortex 2.0.1 → 2.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.
Files changed (142) hide show
  1. package/dist/{adapters/cortex → cortex}/client.d.ts +41 -5
  2. package/dist/cortex/client.d.ts.map +1 -0
  3. package/dist/{adapters/cortex → cortex}/client.js +1 -0
  4. package/dist/cortex/client.js.map +1 -0
  5. package/dist/features/capture/filter.d.ts +2 -1
  6. package/dist/features/capture/filter.d.ts.map +1 -1
  7. package/dist/features/capture/filter.js +11 -0
  8. package/dist/features/capture/filter.js.map +1 -1
  9. package/dist/features/capture/handler.d.ts +4 -4
  10. package/dist/features/capture/handler.d.ts.map +1 -1
  11. package/dist/features/capture/handler.js +10 -2
  12. package/dist/features/capture/handler.js.map +1 -1
  13. package/dist/features/capture/index.d.ts +3 -0
  14. package/dist/features/capture/index.d.ts.map +1 -0
  15. package/dist/features/capture/index.js +3 -0
  16. package/dist/features/capture/index.js.map +1 -0
  17. package/dist/features/checkpoint/handler.d.ts +3 -3
  18. package/dist/features/checkpoint/handler.d.ts.map +1 -1
  19. package/dist/features/heartbeat/handler.d.ts +2 -2
  20. package/dist/features/heartbeat/handler.d.ts.map +1 -1
  21. package/dist/features/index.d.ts +7 -0
  22. package/dist/features/index.d.ts.map +1 -0
  23. package/dist/features/index.js +7 -0
  24. package/dist/features/index.js.map +1 -0
  25. package/dist/features/recall/context-profile.d.ts +1 -1
  26. package/dist/features/recall/context-profile.d.ts.map +1 -1
  27. package/dist/features/recall/formatter.d.ts +1 -1
  28. package/dist/features/recall/formatter.d.ts.map +1 -1
  29. package/dist/features/recall/formatter.js +10 -5
  30. package/dist/features/recall/formatter.js.map +1 -1
  31. package/dist/features/recall/handler.d.ts +4 -4
  32. package/dist/features/recall/handler.d.ts.map +1 -1
  33. package/dist/features/recall/handler.js +3 -2
  34. package/dist/features/recall/handler.js.map +1 -1
  35. package/dist/features/recall/index.d.ts +4 -0
  36. package/dist/features/recall/index.d.ts.map +1 -0
  37. package/dist/features/recall/index.js +4 -0
  38. package/dist/features/recall/index.js.map +1 -0
  39. package/dist/features/sync/daily-logs-sync.d.ts +3 -3
  40. package/dist/features/sync/daily-logs-sync.d.ts.map +1 -1
  41. package/dist/features/sync/daily-logs-sync.js +1 -1
  42. package/dist/features/sync/daily-logs-sync.js.map +1 -1
  43. package/dist/features/sync/index.d.ts +5 -0
  44. package/dist/features/sync/index.d.ts.map +1 -0
  45. package/dist/features/sync/index.js +5 -0
  46. package/dist/features/sync/index.js.map +1 -0
  47. package/dist/features/sync/memory-md-sync.d.ts +3 -3
  48. package/dist/features/sync/memory-md-sync.d.ts.map +1 -1
  49. package/dist/features/sync/memory-md-sync.js +1 -1
  50. package/dist/features/sync/memory-md-sync.js.map +1 -1
  51. package/dist/features/sync/transcripts-sync.d.ts +3 -3
  52. package/dist/features/sync/transcripts-sync.d.ts.map +1 -1
  53. package/dist/features/sync/transcripts-sync.js +2 -2
  54. package/dist/features/sync/transcripts-sync.js.map +1 -1
  55. package/dist/features/sync/watcher.d.ts +3 -3
  56. package/dist/features/sync/watcher.d.ts.map +1 -1
  57. package/dist/index.d.ts +5 -5
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +5 -5
  60. package/dist/index.js.map +1 -1
  61. package/dist/internal/api-key.d.ts.map +1 -0
  62. package/dist/internal/api-key.js.map +1 -0
  63. package/dist/internal/audit-logger.d.ts.map +1 -0
  64. package/dist/internal/audit-logger.js.map +1 -0
  65. package/dist/internal/{transcript/cleaner.d.ts → cleaner.d.ts} +1 -1
  66. package/dist/internal/cleaner.d.ts.map +1 -0
  67. package/dist/internal/cleaner.js.map +1 -0
  68. package/dist/internal/index.d.ts +11 -0
  69. package/dist/internal/index.d.ts.map +1 -0
  70. package/dist/internal/index.js +11 -0
  71. package/dist/internal/index.js.map +1 -0
  72. package/dist/internal/latency-metrics.d.ts.map +1 -0
  73. package/dist/internal/latency-metrics.js.map +1 -0
  74. package/dist/internal/retry-queue.d.ts.map +1 -0
  75. package/dist/internal/retry-queue.js.map +1 -0
  76. package/dist/internal/safe-path.d.ts.map +1 -0
  77. package/dist/internal/safe-path.js.map +1 -0
  78. package/dist/internal/session-state.d.ts.map +1 -0
  79. package/dist/internal/session-state.js.map +1 -0
  80. package/dist/internal/user-id.d.ts.map +1 -0
  81. package/dist/internal/user-id.js.map +1 -0
  82. package/dist/plugin/cli.d.ts +33 -0
  83. package/dist/plugin/cli.d.ts.map +1 -0
  84. package/dist/plugin/cli.js +278 -0
  85. package/dist/plugin/cli.js.map +1 -0
  86. package/dist/plugin/commands.d.ts +21 -0
  87. package/dist/plugin/commands.d.ts.map +1 -0
  88. package/dist/plugin/commands.js +94 -0
  89. package/dist/plugin/commands.js.map +1 -0
  90. package/dist/plugin/{config/schema.d.ts → config.d.ts} +1 -1
  91. package/dist/plugin/config.d.ts.map +1 -0
  92. package/dist/plugin/{config/schema.js → config.js} +1 -1
  93. package/dist/plugin/config.js.map +1 -0
  94. package/dist/plugin/index.d.ts +1 -87
  95. package/dist/plugin/index.d.ts.map +1 -1
  96. package/dist/plugin/index.js +54 -586
  97. package/dist/plugin/index.js.map +1 -1
  98. package/dist/plugin/tools.d.ts +31 -0
  99. package/dist/plugin/tools.d.ts.map +1 -0
  100. package/dist/plugin/tools.js +235 -0
  101. package/dist/plugin/tools.js.map +1 -0
  102. package/dist/plugin/types.d.ts +83 -0
  103. package/dist/plugin/types.d.ts.map +1 -0
  104. package/dist/plugin/types.js +3 -0
  105. package/dist/plugin/types.js.map +1 -0
  106. package/openclaw.plugin.json +1 -1
  107. package/package.json +1 -1
  108. package/dist/adapters/cortex/client.d.ts.map +0 -1
  109. package/dist/adapters/cortex/client.js.map +0 -1
  110. package/dist/internal/audit/audit-logger.d.ts.map +0 -1
  111. package/dist/internal/audit/audit-logger.js.map +0 -1
  112. package/dist/internal/fs/safe-path.d.ts.map +0 -1
  113. package/dist/internal/fs/safe-path.js.map +0 -1
  114. package/dist/internal/identity/api-key.d.ts.map +0 -1
  115. package/dist/internal/identity/api-key.js.map +0 -1
  116. package/dist/internal/identity/user-id.d.ts.map +0 -1
  117. package/dist/internal/identity/user-id.js.map +0 -1
  118. package/dist/internal/metrics/latency-metrics.d.ts.map +0 -1
  119. package/dist/internal/metrics/latency-metrics.js.map +0 -1
  120. package/dist/internal/queue/retry-queue.d.ts.map +0 -1
  121. package/dist/internal/queue/retry-queue.js.map +0 -1
  122. package/dist/internal/session/session-state.d.ts.map +0 -1
  123. package/dist/internal/session/session-state.js.map +0 -1
  124. package/dist/internal/transcript/cleaner.d.ts.map +0 -1
  125. package/dist/internal/transcript/cleaner.js.map +0 -1
  126. package/dist/plugin/config/schema.d.ts.map +0 -1
  127. package/dist/plugin/config/schema.js.map +0 -1
  128. /package/dist/internal/{identity/api-key.d.ts → api-key.d.ts} +0 -0
  129. /package/dist/internal/{identity/api-key.js → api-key.js} +0 -0
  130. /package/dist/internal/{audit/audit-logger.d.ts → audit-logger.d.ts} +0 -0
  131. /package/dist/internal/{audit/audit-logger.js → audit-logger.js} +0 -0
  132. /package/dist/internal/{transcript/cleaner.js → cleaner.js} +0 -0
  133. /package/dist/internal/{metrics/latency-metrics.d.ts → latency-metrics.d.ts} +0 -0
  134. /package/dist/internal/{metrics/latency-metrics.js → latency-metrics.js} +0 -0
  135. /package/dist/internal/{queue/retry-queue.d.ts → retry-queue.d.ts} +0 -0
  136. /package/dist/internal/{queue/retry-queue.js → retry-queue.js} +0 -0
  137. /package/dist/internal/{fs/safe-path.d.ts → safe-path.d.ts} +0 -0
  138. /package/dist/internal/{fs/safe-path.js → safe-path.js} +0 -0
  139. /package/dist/internal/{session/session-state.d.ts → session-state.d.ts} +0 -0
  140. /package/dist/internal/{session/session-state.js → session-state.js} +0 -0
  141. /package/dist/internal/{identity/user-id.d.ts → user-id.d.ts} +0 -0
  142. /package/dist/internal/{identity/user-id.js → user-id.js} +0 -0
@@ -3,22 +3,23 @@ import { createHash, randomUUID } from "node:crypto";
3
3
  import { readFileSync, writeFileSync, statSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import packageJson from "../../package.json" with { type: "json" };
6
- import { CortexConfigSchema, configSchema } from "./config/schema.js";
7
- import { CortexClient } from "../adapters/cortex/client.js";
6
+ import { CortexConfigSchema, configSchema } from "./config.js";
7
+ import { CortexClient } from "../cortex/client.js";
8
8
  import { createRecallHandler } from "../features/recall/handler.js";
9
9
  import { createCaptureHandler } from "../features/capture/handler.js";
10
10
  import { FileSyncWatcher } from "../features/sync/watcher.js";
11
- import { RetryQueue } from "../internal/queue/retry-queue.js";
12
- import { LatencyMetrics } from "../internal/metrics/latency-metrics.js";
13
- import { loadOrCreateUserId } from "../internal/identity/user-id.js";
14
- import { BAKED_API_KEY } from "../internal/identity/api-key.js";
15
- import { formatMemories } from "../features/recall/formatter.js";
16
- import { AuditLogger } from "../internal/audit/audit-logger.js";
11
+ import { RetryQueue } from "../internal/retry-queue.js";
12
+ import { LatencyMetrics } from "../internal/latency-metrics.js";
13
+ import { loadOrCreateUserId } from "../internal/user-id.js";
14
+ import { BAKED_API_KEY } from "../internal/api-key.js";
15
+ import { AuditLogger } from "../internal/audit-logger.js";
17
16
  import { RecentSaves } from "../internal/dedupe.js";
18
17
  import { injectAgentInstructions } from "../internal/agent-instructions.js";
19
- import { createCheckpointHandler } from "../features/checkpoint/handler.js";
20
18
  import { createHeartbeatHandler } from "../features/heartbeat/handler.js";
21
- import { buildSessionSummaryFromMessages, formatRecoveryContext, SessionStateStore, } from "../internal/session/session-state.js";
19
+ import { buildSessionSummaryFromMessages, formatRecoveryContext, SessionStateStore, } from "../internal/session-state.js";
20
+ import { registerCliCommands } from "./cli.js";
21
+ import { buildSearchMemoryTool, buildSaveMemoryTool } from "./tools.js";
22
+ import { buildCommands } from "./commands.js";
22
23
  const version = packageJson.version;
23
24
  const STATS_FILE = join(homedir(), ".openclaw", "cortex-session-stats.json");
24
25
  function persistStats(stats) {
@@ -321,323 +322,46 @@ const plugin = {
321
322
  recallMemoriesTotal: 0,
322
323
  recallDuplicatesCollapsed: 0,
323
324
  };
324
- // Reset persisted stats for this new session
325
- persistStats(sessionStats);
325
+ // Don't eagerly reset the stats file here the dual-instance runtime
326
+ // means [plugins] would clobber stats that [gateway] already persisted.
327
+ // In-memory counters start at zero and get persisted on first real activity.
326
328
  // --- Agent Tools ---
327
329
  const recentSaves = config.dedupeWindowMinutes > 0
328
330
  ? new RecentSaves(config.dedupeWindowMinutes)
329
331
  : null;
330
332
  if (api.registerTool) {
331
- api.registerTool({
332
- name: "cortex_search_memory",
333
- description: "Search long-term memory for facts, preferences, and past context. Use when you need to recall something the user mentioned before or retrieve stored knowledge. Use 'mode' to narrow results to a specific category.",
334
- parameters: {
335
- type: "object",
336
- properties: {
337
- query: {
338
- type: "string",
339
- description: "Natural language search query for memory retrieval",
340
- },
341
- limit: {
342
- type: "number",
343
- description: "Maximum number of memories to return (1-50)",
344
- default: 10,
345
- },
346
- mode: {
347
- type: "string",
348
- enum: ["all", "decisions", "preferences", "facts", "recent"],
349
- description: "Filter memories by category. 'all' returns everything (default), 'decisions' returns architectural/design choices, 'preferences' returns user likes/settings, 'facts' returns durable knowledge, 'recent' prioritizes recency over relevance.",
350
- },
351
- },
352
- required: ["query"],
353
- },
354
- async execute(_id, params) {
355
- const query = String(params.query ?? "");
356
- const limit = Math.min(Math.max(Number(params.limit) || 10, 1), 50);
357
- const mode = typeof params.mode === "string" ? params.mode : "all";
358
- await userIdReady;
359
- // Augment query based on mode to improve retrieval precision
360
- let effectiveQuery = query;
361
- let queryType = "combined";
362
- switch (mode) {
363
- case "decisions":
364
- effectiveQuery = `[type:decision] ${query}`;
365
- queryType = "factual";
366
- break;
367
- case "preferences":
368
- effectiveQuery = `[type:preference] ${query}`;
369
- queryType = "factual";
370
- break;
371
- case "facts":
372
- effectiveQuery = `[type:fact] ${query}`;
373
- queryType = "factual";
374
- break;
375
- case "recent":
376
- queryType = "combined";
377
- break;
378
- }
379
- api.logger.debug?.(`Cortex search: "${effectiveQuery.slice(0, 80)}" (limit=${limit}, mode=${mode})`);
380
- sessionStats.searches++;
381
- persistStats(sessionStats);
382
- void auditLoggerProxy.log({
383
- feature: "tool-search-memory",
384
- method: "POST",
385
- endpoint: "/v1/recall",
386
- payload: effectiveQuery,
387
- userId,
388
- });
389
- try {
390
- const doRecall = async (attempt = 0) => {
391
- try {
392
- return await client.recall(effectiveQuery, config.toolTimeoutMs, {
393
- limit,
394
- userId: userId,
395
- queryType,
396
- });
397
- }
398
- catch (err) {
399
- if (attempt < 1 && /50[23]/.test(String(err))) {
400
- await new Promise((r) => setTimeout(r, 1500));
401
- return doRecall(attempt + 1);
402
- }
403
- throw err;
404
- }
405
- };
406
- const response = await doRecall();
407
- if (!response.memories?.length) {
408
- return { content: [{ type: "text", text: "No memories found matching that query." }] };
409
- }
410
- api.logger.debug?.(`Cortex search returned ${response.memories.length} memories`);
411
- const formatted = formatMemories(response.memories, config.recallTopK);
412
- return { content: [{ type: "text", text: formatted }] };
413
- }
414
- catch (err) {
415
- api.logger.warn(`Cortex search failed: ${String(err)}`);
416
- return { content: [{ type: "text", text: `Memory search failed: ${String(err)}` }] };
417
- }
418
- },
419
- });
420
- api.registerTool({
421
- name: "cortex_save_memory",
422
- description: "Explicitly save a fact, preference, or piece of information to long-term memory. Use when the user asks you to remember something specific. Provide type and importance to help organize memories for better retrieval.",
423
- parameters: {
424
- type: "object",
425
- properties: {
426
- text: {
427
- type: "string",
428
- description: "The information to save to memory (a fact, preference, or context)",
429
- },
430
- type: {
431
- type: "string",
432
- enum: ["preference", "decision", "fact", "transient"],
433
- description: "Category of this memory. 'preference' for user likes/dislikes/settings, 'decision' for architectural or design choices, 'fact' for durable knowledge, 'transient' for temporary state that may change soon.",
434
- },
435
- importance: {
436
- type: "string",
437
- enum: ["high", "normal", "low"],
438
- description: "How important this memory is for future recall. 'high' for critical preferences or decisions, 'normal' for general facts, 'low' for minor context.",
439
- },
440
- checkNovelty: {
441
- type: "boolean",
442
- description: "When true, checks if a similar memory already exists before saving. Skips the save if a near-duplicate is found. Defaults to false.",
443
- },
444
- },
445
- required: ["text"],
446
- },
447
- async execute(_id, params) {
448
- const text = String(params.text ?? "");
449
- if (!text || text.length < 5) {
450
- return { content: [{ type: "text", text: "Text too short to save as a memory." }] };
451
- }
452
- const memoryType = typeof params.type === "string" ? params.type : undefined;
453
- const importance = typeof params.importance === "string" ? params.importance : undefined;
454
- const checkNovelty = params.checkNovelty === true;
455
- await userIdReady;
456
- if (!userId) {
457
- api.logger.warn("Cortex save: missing user_id");
458
- return { content: [{ type: "text", text: "Failed to save memory: Cortex ingest requires user_id." }] };
459
- }
460
- // Prepend metadata tags so they're stored with the memory text
461
- const metaTags = [];
462
- if (memoryType)
463
- metaTags.push(`[type:${memoryType}]`);
464
- if (importance)
465
- metaTags.push(`[importance:${importance}]`);
466
- const enrichedText = metaTags.length > 0 ? `${metaTags.join(" ")} ${text}` : text;
467
- // Item 5: Client-side dedupe — skip if near-duplicate was saved recently
468
- if (recentSaves?.isDuplicate(text)) {
469
- api.logger.debug?.(`Cortex save skipped (duplicate within window): "${text.slice(0, 60)}"`);
470
- sessionStats.savesSkippedDedupe++;
471
- persistStats(sessionStats);
472
- return {
473
- content: [{ type: "text", text: "This memory is very similar to one saved recently. Skipped to avoid duplication." }],
474
- };
475
- }
476
- // Item 7: Novelty check — query existing memories to see if this is already stored
477
- if (checkNovelty) {
478
- try {
479
- const existing = await client.retrieve(text, 1, "fast", config.toolTimeoutMs, "factual", { userId });
480
- const topScore = existing.results?.[0]?.score ?? 0;
481
- if (topScore >= config.noveltyThreshold) {
482
- api.logger.debug?.(`Cortex save skipped (not novel, score=${topScore.toFixed(2)}): "${text.slice(0, 60)}"`);
483
- sessionStats.savesSkippedNovelty++;
484
- persistStats(sessionStats);
485
- recentSaves?.record(text);
486
- return {
487
- content: [{
488
- type: "text",
489
- text: `This memory already exists (similarity ${(topScore * 100).toFixed(0)}%). Skipped to avoid duplication.`,
490
- }],
491
- };
492
- }
493
- }
494
- catch (err) {
495
- // Novelty check is best-effort — proceed with save on failure
496
- api.logger.debug?.(`Cortex novelty check failed, proceeding with save: ${String(err)}`);
497
- }
498
- }
499
- api.logger.debug?.(`Cortex save: "${enrichedText.slice(0, 80)}"`);
500
- void auditLoggerProxy.log({
501
- feature: "tool-save-memory",
502
- method: "POST",
503
- endpoint: "/v1/remember",
504
- payload: enrichedText,
505
- sessionId,
506
- userId,
507
- });
508
- try {
509
- const now = new Date();
510
- const referenceDate = now.toISOString().slice(0, 10);
511
- await client.remember(enrichedText, sessionId, config.toolTimeoutMs, referenceDate, userId, "openclaw", "OpenClaw");
512
- if (knowledgeState) {
513
- knowledgeState.hasMemories = true;
514
- }
515
- recentSaves?.record(text);
516
- sessionStats.saves++;
517
- persistStats(sessionStats);
518
- api.logger.debug?.("Cortex remember accepted");
519
- return {
520
- content: [{
521
- type: "text",
522
- text: "Memory submitted for processing. It should be available shortly.",
523
- }],
524
- };
525
- }
526
- catch (err) {
527
- api.logger.warn(`Cortex save failed, falling back to async ingest: ${String(err)}`);
528
- try {
529
- const referenceDate = new Date().toISOString();
530
- const job = await client.submitIngest(enrichedText, sessionId, referenceDate, userId, "openclaw", "OpenClaw");
531
- if (knowledgeState) {
532
- knowledgeState.hasMemories = true;
533
- }
534
- recentSaves?.record(text);
535
- sessionStats.saves++;
536
- persistStats(sessionStats);
537
- return {
538
- content: [{
539
- type: "text",
540
- text: `Memory save queued (job ${job.job_id}, status=${job.status}). It should be available shortly.`,
541
- }],
542
- };
543
- }
544
- catch (fallbackErr) {
545
- api.logger.warn(`Cortex save fallback failed: ${String(fallbackErr)}`);
546
- return { content: [{ type: "text", text: `Failed to save memory: ${String(err)}` }] };
547
- }
548
- }
549
- },
550
- });
333
+ const toolsDeps = {
334
+ client,
335
+ config,
336
+ logger: api.logger,
337
+ getUserId: () => userId,
338
+ userIdReady,
339
+ sessionId,
340
+ sessionStats,
341
+ persistStats,
342
+ auditLoggerProxy,
343
+ knowledgeState,
344
+ recentSaves,
345
+ };
346
+ api.registerTool(buildSearchMemoryTool(toolsDeps));
347
+ api.registerTool(buildSaveMemoryTool(toolsDeps));
551
348
  api.logger.debug?.("Cortex tools registered: cortex_search_memory, cortex_save_memory");
552
349
  }
553
350
  // --- Auto-Reply Commands ---
554
351
  if (api.registerCommand) {
555
- const checkpointHandler = createCheckpointHandler(client, config, api.logger, () => userId, userIdReady, () => lastMessages, sessionId, auditLoggerProxy);
556
- api.registerCommand({
557
- name: "audit",
558
- description: "Toggle or check Cortex audit log (records all data sent to Cortex)",
559
- acceptsArgs: true,
560
- handler: (ctx) => {
561
- const arg = ctx.args?.trim().toLowerCase();
562
- if (arg === "on") {
563
- if (!workspaceDirResolved) {
564
- return { text: "Cannot enable audit log — no workspace directory available. The plugin must be started with a workspace first." };
565
- }
566
- if (auditLoggerInner) {
567
- return { text: `Audit log is already enabled.\nAll Cortex API calls are being recorded at:\n\`${workspaceDirResolved}/.cortex/audit/\`` };
568
- }
569
- auditLoggerInner = new AuditLogger(workspaceDirResolved, api.logger);
570
- api.logger.info(`Cortex audit log enabled via command: ${workspaceDirResolved}/.cortex/audit/`);
571
- return {
572
- text: [
573
- `**Audit log enabled.**`,
574
- ``,
575
- `All data sent to and received from Cortex will be recorded locally.`,
576
- `Log path: \`${workspaceDirResolved}/.cortex/audit/\``,
577
- ``,
578
- `Turn off with \`/audit off\`. Log files are preserved when disabled.`,
579
- ].join("\n"),
580
- };
581
- }
582
- if (arg === "off") {
583
- if (!auditLoggerInner) {
584
- return { text: "Audit log is already off. No data is being recorded." };
585
- }
586
- auditLoggerInner = undefined;
587
- api.logger.info("Cortex audit log disabled via command");
588
- return {
589
- text: [
590
- `**Audit log disabled.**`,
591
- ``,
592
- `Cortex API calls are no longer being recorded.`,
593
- `Existing log files are preserved and can be reviewed at:`,
594
- `\`${workspaceDirResolved}/.cortex/audit/\``,
595
- ].join("\n"),
596
- };
597
- }
598
- // No args — show status
599
- const status = auditLoggerInner ? "on" : "off";
600
- const lines = [
601
- `**Cortex Audit Log**`,
602
- ``,
603
- `The audit log records all data sent to and received from the Cortex API, stored locally for inspection.`,
604
- ``,
605
- `- Status: **${status}**`,
606
- `- Config default: ${config.auditLog ? "on" : "off"}`,
607
- ];
608
- if (workspaceDirResolved) {
609
- lines.push(`- Log path: \`${workspaceDirResolved}/.cortex/audit/\``);
610
- }
611
- lines.push("", "Toggle: `/audit on` · `/audit off`");
612
- return { text: lines.join("\n") };
613
- },
614
- });
615
- api.registerCommand({
616
- name: "checkpoint",
617
- description: "Save a session checkpoint to Cortex before resetting",
618
- acceptsArgs: true,
619
- handler: checkpointHandler,
620
- });
621
- api.registerCommand({
622
- name: "sleep",
623
- description: "Mark the current session as cleanly ended (clears recovery warning state)",
624
- acceptsArgs: false,
625
- handler: async () => {
626
- try {
627
- await sessionState.clear();
628
- return {
629
- text: [
630
- `**Session ended cleanly.**`,
631
- ``,
632
- `Cortex will not show a recovery warning when you start your next session.`,
633
- `Use \`/checkpoint\` before \`/sleep\` if you want to save a summary of what you were working on.`,
634
- ].join("\n"),
635
- };
636
- }
637
- catch (err) {
638
- return { text: `Failed to mark session clean: ${String(err)}` };
639
- }
640
- },
352
+ buildCommands(api.registerCommand.bind(api), {
353
+ client,
354
+ config,
355
+ logger: api.logger,
356
+ getUserId: () => userId,
357
+ userIdReady,
358
+ getLastMessages: () => lastMessages,
359
+ sessionId,
360
+ auditLoggerProxy,
361
+ sessionState,
362
+ getWorkspaceDir: () => workspaceDirResolved,
363
+ getAuditLoggerInner: () => auditLoggerInner,
364
+ setAuditLoggerInner: (l) => { auditLoggerInner = l; },
641
365
  });
642
366
  api.logger.debug?.("Cortex commands registered: /audit, /checkpoint, /sleep");
643
367
  }
@@ -670,274 +394,18 @@ const plugin = {
670
394
  }
671
395
  // --- CLI Commands (terminal-level) ---
672
396
  if (api.registerCli) {
673
- api.registerCli(({ program }) => {
674
- const cortex = program.command("cortex").description("Cortex memory CLI commands");
675
- cortex
676
- .command("status")
677
- .description("Check Cortex API health and show memory status")
678
- .action(async () => {
679
- await userIdReady;
680
- console.log("Cortex Status Check");
681
- console.log("=".repeat(50));
682
- // Health check
683
- const startHealth = Date.now();
684
- let healthy = false;
685
- try {
686
- healthy = await client.healthCheck();
687
- const ms = Date.now() - startHealth;
688
- console.log(` API Health: ${healthy ? "OK" : "UNREACHABLE"} (${ms}ms)`);
689
- }
690
- catch {
691
- console.log(` API Health: UNREACHABLE`);
692
- }
693
- if (!healthy) {
694
- console.log("\nAPI is unreachable. Check baseUrl and network connectivity.");
695
- return;
696
- }
697
- // Knowledge
698
- try {
699
- const startKnowledge = Date.now();
700
- const knowledge = await client.knowledge(undefined, userId);
701
- const ms = Date.now() - startKnowledge;
702
- console.log(` Knowledge: OK (${ms}ms)`);
703
- console.log(` Memories: ${knowledge.total_memories.toLocaleString()}`);
704
- console.log(` Sessions: ${knowledge.total_sessions}`);
705
- console.log(` Maturity: ${knowledge.maturity}`);
706
- }
707
- catch (err) {
708
- console.log(` Knowledge: FAILED — ${String(err)}`);
709
- }
710
- // Stats
711
- try {
712
- const startStats = Date.now();
713
- const stats = await client.stats(undefined, userId);
714
- const ms = Date.now() - startStats;
715
- console.log(` Stats: OK (${ms}ms)`);
716
- console.log(` Pipeline: tier ${stats.pipeline_tier}`);
717
- }
718
- catch (err) {
719
- console.log(` Stats: FAILED — ${String(err)}`);
720
- }
721
- // Recall
722
- try {
723
- const startRecall = Date.now();
724
- await client.recall("test", 5000, { limit: 1, userId });
725
- const ms = Date.now() - startRecall;
726
- console.log(` Recall: OK (${ms}ms)`);
727
- }
728
- catch (err) {
729
- console.log(` Recall: FAILED — ${String(err)}`);
730
- }
731
- // Retrieve
732
- try {
733
- const startRetrieve = Date.now();
734
- await client.retrieve("test", 1, "fast", 5000, undefined, { userId });
735
- const ms = Date.now() - startRetrieve;
736
- console.log(` Retrieve: OK (${ms}ms)`);
737
- }
738
- catch (err) {
739
- console.log(` Retrieve: FAILED — ${String(err)}`);
740
- }
741
- console.log("");
742
- console.log(` Version: ${version}`);
743
- console.log(` User ID: ${userId ?? "unknown"}`);
744
- console.log(` Base URL: ${config.baseUrl}`);
745
- console.log(` Auto-Recall: ${config.autoRecall ? "on" : "off"}`);
746
- console.log(` Auto-Capture: ${config.autoCapture ? "on" : "off"}`);
747
- console.log(` File Sync: ${config.fileSync ? "on" : "off"}`);
748
- console.log(` Dedupe Window: ${config.dedupeWindowMinutes > 0 ? `${config.dedupeWindowMinutes}min` : "off"}`);
749
- // Session activity stats — read from persisted file so CLI process
750
- // can see stats from the running gateway instance
751
- const liveStats = loadPersistedStats() ?? sessionStats;
752
- const totalSkipped = liveStats.savesSkippedDedupe + liveStats.savesSkippedNovelty;
753
- const avgRecallMemories = liveStats.recallCount > 0
754
- ? (liveStats.recallMemoriesTotal / liveStats.recallCount).toFixed(1)
755
- : "0";
756
- console.log("");
757
- console.log("Session Activity");
758
- console.log("-".repeat(50));
759
- console.log(` Saves: ${liveStats.saves}`);
760
- if (totalSkipped > 0) {
761
- console.log(` Skipped: ${totalSkipped} (${liveStats.savesSkippedDedupe} dedupe, ${liveStats.savesSkippedNovelty} novelty)`);
762
- }
763
- console.log(` Searches: ${liveStats.searches}`);
764
- console.log(` Recalls: ${liveStats.recallCount}`);
765
- console.log(` Avg memories/recall: ${avgRecallMemories}`);
766
- if (liveStats.recallDuplicatesCollapsed > 0) {
767
- console.log(` Duplicates collapsed: ${liveStats.recallDuplicatesCollapsed}`);
768
- }
769
- });
770
- cortex
771
- .command("memories")
772
- .description("Show memory count and maturity")
773
- .action(async () => {
774
- await userIdReady;
775
- try {
776
- const knowledge = await client.knowledge(undefined, userId);
777
- console.log(`Memories: ${knowledge.total_memories.toLocaleString()}`);
778
- console.log(`Sessions: ${knowledge.total_sessions}`);
779
- console.log(`Maturity: ${knowledge.maturity}`);
780
- if (knowledge.entities.length > 0) {
781
- console.log(`\nTop Entities:`);
782
- knowledge.entities.slice(0, 10).forEach((e) => {
783
- console.log(` ${e.name} (${e.memory_count} memories, last seen ${e.last_seen})`);
784
- });
785
- }
786
- }
787
- catch (err) {
788
- console.error(`Failed: ${String(err)}`);
789
- process.exitCode = 1;
790
- }
791
- });
792
- cortex
793
- .command("search")
794
- .description("Search memories from the terminal")
795
- .argument("<query>", "Search query")
796
- .option("--limit <n>", "Max results", "10")
797
- .action(async (query, opts) => {
798
- await userIdReady;
799
- try {
800
- const response = await client.recall(query, config.toolTimeoutMs, {
801
- limit: parseInt(opts.limit),
802
- userId,
803
- queryType: "combined",
804
- });
805
- if (!response.memories?.length) {
806
- console.log(`No memories found for: "${query}"`);
807
- return;
808
- }
809
- console.log(`Found ${response.memories.length} memories:\n`);
810
- response.memories.forEach((m, i) => {
811
- console.log(`${i + 1}. [${m.confidence.toFixed(2)}] ${m.content}`);
812
- if (m.entities.length > 0) {
813
- console.log(` entities: ${m.entities.join(", ")}`);
814
- }
815
- console.log("");
816
- });
817
- }
818
- catch (err) {
819
- console.error(`Search failed: ${String(err)}`);
820
- process.exitCode = 1;
821
- }
822
- });
823
- cortex
824
- .command("config")
825
- .description("Show current Cortex plugin configuration")
826
- .action(async () => {
827
- await userIdReady;
828
- console.log(`Version: ${version}`);
829
- console.log(`Base URL: ${config.baseUrl}`);
830
- console.log(`User ID: ${userId ?? "unknown"}`);
831
- console.log(`Namespace: ${namespace}`);
832
- console.log(`Auto-Recall: ${config.autoRecall ? "on" : "off"}`);
833
- console.log(`Auto-Capture: ${config.autoCapture ? "on" : "off"}`);
834
- console.log(`File Sync: ${config.fileSync ? "on" : "off"}`);
835
- console.log(`Transcript Sync: ${config.transcriptSync ? "on" : "off"}`);
836
- console.log(`Recall Limit: ${config.recallLimit}`);
837
- console.log(`Recall Timeout: ${config.recallTimeoutMs}ms`);
838
- console.log(`Tool Timeout: ${config.toolTimeoutMs}ms`);
839
- console.log(`Audit Log: ${config.auditLog ? "on" : "off"}`);
840
- });
841
- cortex
842
- .command("pair")
843
- .description("Generate a TooToo pairing code to link your agent")
844
- .action(async () => {
845
- await userIdReady;
846
- if (!userId) {
847
- console.error("Cannot generate pairing code: user ID not available.");
848
- process.exitCode = 1;
849
- return;
850
- }
851
- try {
852
- const { user_code, expires_in } = await client.generatePairingCode(userId);
853
- const mins = Math.floor(expires_in / 60);
854
- console.log(`Agent ID: ${userId}`);
855
- console.log(`Pairing code: ${user_code}`);
856
- console.log(`Expires in: ${mins} minute${mins !== 1 ? "s" : ""}`);
857
- console.log("");
858
- console.log("To link your TooToo account:");
859
- console.log(" 1. Open app.tootoo.io/settings/agents");
860
- console.log(' 2. Click "Connect Agent"');
861
- console.log(" 3. Enter the code above");
862
- }
863
- catch (err) {
864
- console.error(`Failed to generate pairing code: ${String(err)}`);
865
- process.exitCode = 1;
866
- }
867
- });
868
- cortex
869
- .command("reset")
870
- .description("Permanently delete ALL memories for this agent (irreversible)")
871
- .option("--yes", "Skip confirmation prompt")
872
- .action(async (opts) => {
873
- await userIdReady;
874
- if (!userId) {
875
- console.error("Cannot reset: user ID not available.");
876
- process.exitCode = 1;
877
- return;
878
- }
879
- // Show what will be deleted
880
- let memoryCount = 0;
881
- let sessionCount = 0;
882
- try {
883
- const knowledge = await client.knowledge(undefined, userId);
884
- memoryCount = knowledge.total_memories;
885
- sessionCount = knowledge.total_sessions;
886
- }
887
- catch {
888
- // Continue even if we can't get counts
889
- }
890
- console.log("");
891
- console.log(" WARNING: This will permanently delete ALL data for this agent.");
892
- console.log("");
893
- console.log(` Agent ID: ${userId}`);
894
- if (memoryCount > 0 || sessionCount > 0) {
895
- console.log(` Memories: ${memoryCount.toLocaleString()}`);
896
- console.log(` Sessions: ${sessionCount}`);
897
- }
898
- console.log("");
899
- console.log(" This includes all memories, facts, suggestions, and graph data.");
900
- console.log(" Agent links (TooToo pairing) will be preserved.");
901
- console.log(" This action CANNOT be undone.");
902
- console.log("");
903
- if (!opts.yes) {
904
- const { createInterface } = await import("node:readline");
905
- const rl = createInterface({ input: process.stdin, output: process.stdout });
906
- const answer = await new Promise((resolve) => {
907
- rl.question(" Type 'reset' to confirm: ", resolve);
908
- });
909
- rl.close();
910
- if (answer.trim().toLowerCase() !== "reset") {
911
- console.log("\n Aborted. No data was deleted.");
912
- return;
913
- }
914
- }
915
- try {
916
- const result = await client.forgetUser(userId);
917
- const d = result.deleted;
918
- console.log("");
919
- console.log(" Memory reset complete.");
920
- console.log("");
921
- console.log(` Deleted:`);
922
- console.log(` Engraved memories: ${d.engraved_memories}`);
923
- console.log(` Resonated memories: ${d.resonated_memories}`);
924
- console.log(` Graph nodes: ${d.nodes}`);
925
- console.log(` Codex suggestions: ${d.codex_suggestions}`);
926
- console.log(` Suppressions: ${d.codex_suggestion_suppressions}`);
927
- }
928
- catch (err) {
929
- if (isAbortError(err) && await resetCompletedAfterAbort(client, userId)) {
930
- console.log("");
931
- console.log(" Memory reset complete.");
932
- console.log("");
933
- console.log(" The server finished the reset, but the request ended before deletion stats were returned.");
934
- return;
935
- }
936
- console.error(`\n Reset failed: ${String(err)}`);
937
- process.exitCode = 1;
938
- }
939
- });
940
- }, { commands: ["cortex"] });
397
+ registerCliCommands(api.registerCli.bind(api), {
398
+ client,
399
+ config,
400
+ version,
401
+ getUserId: () => userId,
402
+ userIdReady,
403
+ getNamespace: () => namespace,
404
+ sessionStats,
405
+ loadPersistedStats,
406
+ isAbortError,
407
+ resetCompletedAfterAbort,
408
+ });
941
409
  api.logger.debug?.("Cortex CLI registered: openclaw cortex {status,memories,search,config,pair,reset}");
942
410
  }
943
411
  // --- Services: retry queue, file sync ---