@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.
- package/dist/{adapters/cortex → cortex}/client.d.ts +41 -5
- package/dist/cortex/client.d.ts.map +1 -0
- package/dist/{adapters/cortex → cortex}/client.js +1 -0
- package/dist/cortex/client.js.map +1 -0
- package/dist/features/capture/filter.d.ts +2 -1
- package/dist/features/capture/filter.d.ts.map +1 -1
- package/dist/features/capture/filter.js +11 -0
- package/dist/features/capture/filter.js.map +1 -1
- package/dist/features/capture/handler.d.ts +4 -4
- package/dist/features/capture/handler.d.ts.map +1 -1
- package/dist/features/capture/handler.js +10 -2
- package/dist/features/capture/handler.js.map +1 -1
- package/dist/features/capture/index.d.ts +3 -0
- package/dist/features/capture/index.d.ts.map +1 -0
- package/dist/features/capture/index.js +3 -0
- package/dist/features/capture/index.js.map +1 -0
- package/dist/features/checkpoint/handler.d.ts +3 -3
- package/dist/features/checkpoint/handler.d.ts.map +1 -1
- package/dist/features/heartbeat/handler.d.ts +2 -2
- package/dist/features/heartbeat/handler.d.ts.map +1 -1
- package/dist/features/index.d.ts +7 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +7 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/recall/context-profile.d.ts +1 -1
- package/dist/features/recall/context-profile.d.ts.map +1 -1
- package/dist/features/recall/formatter.d.ts +1 -1
- package/dist/features/recall/formatter.d.ts.map +1 -1
- package/dist/features/recall/formatter.js +10 -5
- package/dist/features/recall/formatter.js.map +1 -1
- package/dist/features/recall/handler.d.ts +4 -4
- package/dist/features/recall/handler.d.ts.map +1 -1
- package/dist/features/recall/handler.js +3 -2
- package/dist/features/recall/handler.js.map +1 -1
- package/dist/features/recall/index.d.ts +4 -0
- package/dist/features/recall/index.d.ts.map +1 -0
- package/dist/features/recall/index.js +4 -0
- package/dist/features/recall/index.js.map +1 -0
- package/dist/features/sync/daily-logs-sync.d.ts +3 -3
- package/dist/features/sync/daily-logs-sync.d.ts.map +1 -1
- package/dist/features/sync/daily-logs-sync.js +1 -1
- package/dist/features/sync/daily-logs-sync.js.map +1 -1
- package/dist/features/sync/index.d.ts +5 -0
- package/dist/features/sync/index.d.ts.map +1 -0
- package/dist/features/sync/index.js +5 -0
- package/dist/features/sync/index.js.map +1 -0
- package/dist/features/sync/memory-md-sync.d.ts +3 -3
- package/dist/features/sync/memory-md-sync.d.ts.map +1 -1
- package/dist/features/sync/memory-md-sync.js +1 -1
- package/dist/features/sync/memory-md-sync.js.map +1 -1
- package/dist/features/sync/transcripts-sync.d.ts +3 -3
- package/dist/features/sync/transcripts-sync.d.ts.map +1 -1
- package/dist/features/sync/transcripts-sync.js +2 -2
- package/dist/features/sync/transcripts-sync.js.map +1 -1
- package/dist/features/sync/watcher.d.ts +3 -3
- package/dist/features/sync/watcher.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/api-key.d.ts.map +1 -0
- package/dist/internal/api-key.js.map +1 -0
- package/dist/internal/audit-logger.d.ts.map +1 -0
- package/dist/internal/audit-logger.js.map +1 -0
- package/dist/internal/{transcript/cleaner.d.ts → cleaner.d.ts} +1 -1
- package/dist/internal/cleaner.d.ts.map +1 -0
- package/dist/internal/cleaner.js.map +1 -0
- package/dist/internal/index.d.ts +11 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +11 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/internal/latency-metrics.d.ts.map +1 -0
- package/dist/internal/latency-metrics.js.map +1 -0
- package/dist/internal/retry-queue.d.ts.map +1 -0
- package/dist/internal/retry-queue.js.map +1 -0
- package/dist/internal/safe-path.d.ts.map +1 -0
- package/dist/internal/safe-path.js.map +1 -0
- package/dist/internal/session-state.d.ts.map +1 -0
- package/dist/internal/session-state.js.map +1 -0
- package/dist/internal/user-id.d.ts.map +1 -0
- package/dist/internal/user-id.js.map +1 -0
- package/dist/plugin/cli.d.ts +33 -0
- package/dist/plugin/cli.d.ts.map +1 -0
- package/dist/plugin/cli.js +278 -0
- package/dist/plugin/cli.js.map +1 -0
- package/dist/plugin/commands.d.ts +21 -0
- package/dist/plugin/commands.d.ts.map +1 -0
- package/dist/plugin/commands.js +94 -0
- package/dist/plugin/commands.js.map +1 -0
- package/dist/plugin/{config/schema.d.ts → config.d.ts} +1 -1
- package/dist/plugin/config.d.ts.map +1 -0
- package/dist/plugin/{config/schema.js → config.js} +1 -1
- package/dist/plugin/config.js.map +1 -0
- package/dist/plugin/index.d.ts +1 -87
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +54 -586
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/tools.d.ts +31 -0
- package/dist/plugin/tools.d.ts.map +1 -0
- package/dist/plugin/tools.js +235 -0
- package/dist/plugin/tools.js.map +1 -0
- package/dist/plugin/types.d.ts +83 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin/types.js +3 -0
- package/dist/plugin/types.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/adapters/cortex/client.d.ts.map +0 -1
- package/dist/adapters/cortex/client.js.map +0 -1
- package/dist/internal/audit/audit-logger.d.ts.map +0 -1
- package/dist/internal/audit/audit-logger.js.map +0 -1
- package/dist/internal/fs/safe-path.d.ts.map +0 -1
- package/dist/internal/fs/safe-path.js.map +0 -1
- package/dist/internal/identity/api-key.d.ts.map +0 -1
- package/dist/internal/identity/api-key.js.map +0 -1
- package/dist/internal/identity/user-id.d.ts.map +0 -1
- package/dist/internal/identity/user-id.js.map +0 -1
- package/dist/internal/metrics/latency-metrics.d.ts.map +0 -1
- package/dist/internal/metrics/latency-metrics.js.map +0 -1
- package/dist/internal/queue/retry-queue.d.ts.map +0 -1
- package/dist/internal/queue/retry-queue.js.map +0 -1
- package/dist/internal/session/session-state.d.ts.map +0 -1
- package/dist/internal/session/session-state.js.map +0 -1
- package/dist/internal/transcript/cleaner.d.ts.map +0 -1
- package/dist/internal/transcript/cleaner.js.map +0 -1
- package/dist/plugin/config/schema.d.ts.map +0 -1
- package/dist/plugin/config/schema.js.map +0 -1
- /package/dist/internal/{identity/api-key.d.ts → api-key.d.ts} +0 -0
- /package/dist/internal/{identity/api-key.js → api-key.js} +0 -0
- /package/dist/internal/{audit/audit-logger.d.ts → audit-logger.d.ts} +0 -0
- /package/dist/internal/{audit/audit-logger.js → audit-logger.js} +0 -0
- /package/dist/internal/{transcript/cleaner.js → cleaner.js} +0 -0
- /package/dist/internal/{metrics/latency-metrics.d.ts → latency-metrics.d.ts} +0 -0
- /package/dist/internal/{metrics/latency-metrics.js → latency-metrics.js} +0 -0
- /package/dist/internal/{queue/retry-queue.d.ts → retry-queue.d.ts} +0 -0
- /package/dist/internal/{queue/retry-queue.js → retry-queue.js} +0 -0
- /package/dist/internal/{fs/safe-path.d.ts → safe-path.d.ts} +0 -0
- /package/dist/internal/{fs/safe-path.js → safe-path.js} +0 -0
- /package/dist/internal/{session/session-state.d.ts → session-state.d.ts} +0 -0
- /package/dist/internal/{session/session-state.js → session-state.js} +0 -0
- /package/dist/internal/{identity/user-id.d.ts → user-id.d.ts} +0 -0
- /package/dist/internal/{identity/user-id.js → user-id.js} +0 -0
package/dist/plugin/index.js
CHANGED
|
@@ -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
|
|
7
|
-
import { CortexClient } from "../
|
|
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/
|
|
12
|
-
import { LatencyMetrics } from "../internal/
|
|
13
|
-
import { loadOrCreateUserId } from "../internal/
|
|
14
|
-
import { BAKED_API_KEY } from "../internal/
|
|
15
|
-
import {
|
|
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
|
|
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
|
-
//
|
|
325
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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 ---
|