@ubundi/openclaw-cortex 2.0.0 → 2.0.2
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/cortex/client.d.ts.map +1 -0
- package/dist/cortex/client.js.map +1 -0
- package/dist/features/capture/filter.d.ts +1 -1
- package/dist/features/capture/filter.d.ts.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/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/handler.d.ts +4 -4
- package/dist/features/recall/handler.d.ts.map +1 -1
- package/dist/features/recall/handler.js +1 -1
- 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 +126 -578
- 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 +230 -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/{adapters/cortex → cortex}/client.d.ts +0 -0
- /package/dist/{adapters/cortex → cortex}/client.js +0 -0
- /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
|
@@ -1,23 +1,52 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { readFileSync, writeFileSync, statSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
3
5
|
import packageJson from "../../package.json" with { type: "json" };
|
|
4
|
-
import { CortexConfigSchema, configSchema } from "./config
|
|
5
|
-
import { CortexClient } from "../
|
|
6
|
+
import { CortexConfigSchema, configSchema } from "./config.js";
|
|
7
|
+
import { CortexClient } from "../cortex/client.js";
|
|
6
8
|
import { createRecallHandler } from "../features/recall/handler.js";
|
|
7
9
|
import { createCaptureHandler } from "../features/capture/handler.js";
|
|
8
10
|
import { FileSyncWatcher } from "../features/sync/watcher.js";
|
|
9
|
-
import { RetryQueue } from "../internal/
|
|
10
|
-
import { LatencyMetrics } from "../internal/
|
|
11
|
-
import { loadOrCreateUserId } from "../internal/
|
|
12
|
-
import { BAKED_API_KEY } from "../internal/
|
|
13
|
-
import {
|
|
14
|
-
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";
|
|
15
16
|
import { RecentSaves } from "../internal/dedupe.js";
|
|
16
17
|
import { injectAgentInstructions } from "../internal/agent-instructions.js";
|
|
17
|
-
import { createCheckpointHandler } from "../features/checkpoint/handler.js";
|
|
18
18
|
import { createHeartbeatHandler } from "../features/heartbeat/handler.js";
|
|
19
|
-
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";
|
|
20
23
|
const version = packageJson.version;
|
|
24
|
+
const STATS_FILE = join(homedir(), ".openclaw", "cortex-session-stats.json");
|
|
25
|
+
function persistStats(stats) {
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(STATS_FILE, JSON.stringify({ ...stats, updatedAt: Date.now() }) + "\n", { encoding: "utf-8", mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Best-effort — stats display is non-critical
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function loadPersistedStats() {
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(readFileSync(STATS_FILE, "utf-8"));
|
|
36
|
+
return {
|
|
37
|
+
saves: raw.saves ?? 0,
|
|
38
|
+
savesSkippedDedupe: raw.savesSkippedDedupe ?? 0,
|
|
39
|
+
savesSkippedNovelty: raw.savesSkippedNovelty ?? 0,
|
|
40
|
+
searches: raw.searches ?? 0,
|
|
41
|
+
recallCount: raw.recallCount ?? 0,
|
|
42
|
+
recallMemoriesTotal: raw.recallMemoriesTotal ?? 0,
|
|
43
|
+
recallDuplicatesCollapsed: raw.recallDuplicatesCollapsed ?? 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
21
50
|
/**
|
|
22
51
|
* Derives a workspace-scoped namespace from the workspace directory path.
|
|
23
52
|
* Uses the directory basename plus a short hash of the full path to avoid collisions
|
|
@@ -101,6 +130,46 @@ function registerHookCompat(api, hookName, handler, metadata) {
|
|
|
101
130
|
api.logger.warn(`Cortex: cannot register hook "${hookName}" — no registerHook or on method available`);
|
|
102
131
|
}
|
|
103
132
|
}
|
|
133
|
+
/** Tool names that must survive the tools.profile allowlist filter. */
|
|
134
|
+
const CORTEX_TOOL_NAMES = ["cortex_search_memory", "cortex_save_memory"];
|
|
135
|
+
/**
|
|
136
|
+
* Ensures `tools.alsoAllow` in the OpenClaw config includes our tool names.
|
|
137
|
+
* Without this, profiles like "coding" silently filter out plugin tools —
|
|
138
|
+
* auto-recall/capture hooks still work, but the agent can't explicitly
|
|
139
|
+
* search or save memories.
|
|
140
|
+
*
|
|
141
|
+
* Runs once on first registration; idempotent thereafter.
|
|
142
|
+
*/
|
|
143
|
+
function ensureToolsAllowlist(logger) {
|
|
144
|
+
try {
|
|
145
|
+
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
146
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
147
|
+
const config = JSON.parse(raw);
|
|
148
|
+
// Only needed when a tools profile is active (which creates a fixed allowlist)
|
|
149
|
+
if (!config.tools?.profile)
|
|
150
|
+
return;
|
|
151
|
+
const existing = Array.isArray(config.tools.alsoAllow)
|
|
152
|
+
? config.tools.alsoAllow
|
|
153
|
+
: [];
|
|
154
|
+
const missing = CORTEX_TOOL_NAMES.filter((name) => !existing.includes(name));
|
|
155
|
+
if (missing.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
config.tools.alsoAllow = [...existing, ...missing];
|
|
158
|
+
// Preserve original file permissions (e.g. 0o600) to avoid security audit warnings
|
|
159
|
+
let mode;
|
|
160
|
+
try {
|
|
161
|
+
mode = statSync(configPath).mode & 0o777;
|
|
162
|
+
}
|
|
163
|
+
catch { /* ignore */ }
|
|
164
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: mode ?? 0o600 });
|
|
165
|
+
logger.info(`Cortex: enabled memory tools for "${config.tools.profile}" profile`);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Config unreadable — tools may be filtered by the profile allowlist.
|
|
169
|
+
// Hooks (auto-recall, auto-capture) still work regardless.
|
|
170
|
+
logger.warn(`Cortex: could not verify tool access — if the agent cannot use cortex_search_memory or cortex_save_memory, add them to tools.alsoAllow in openclaw.json`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
104
173
|
const plugin = {
|
|
105
174
|
id: "openclaw-cortex",
|
|
106
175
|
name: "Cortex Memory",
|
|
@@ -124,6 +193,8 @@ const plugin = {
|
|
|
124
193
|
api.logger.error("Cortex plugin misconfigured: empty API key. Rebuild with BUILD_API_KEY=... npm run build, or install the published package.");
|
|
125
194
|
return;
|
|
126
195
|
}
|
|
196
|
+
// Ensure our tools survive the profile allowlist filter (one-time config patch)
|
|
197
|
+
ensureToolsAllowlist(api.logger);
|
|
127
198
|
const config = parsed.data;
|
|
128
199
|
const client = new CortexClient(config.baseUrl, BAKED_API_KEY);
|
|
129
200
|
const retryQueue = new RetryQueue(api.logger);
|
|
@@ -184,6 +255,7 @@ const plugin = {
|
|
|
184
255
|
sessionStats.recallCount++;
|
|
185
256
|
sessionStats.recallMemoriesTotal += stats.memoriesReturned;
|
|
186
257
|
sessionStats.recallDuplicatesCollapsed += stats.collapsedCount;
|
|
258
|
+
persistStats(sessionStats);
|
|
187
259
|
});
|
|
188
260
|
// Auto-Recall: inject relevant memories before every agent turn
|
|
189
261
|
registerHookCompat(api, "before_agent_start", async (event, ctx) => {
|
|
@@ -250,316 +322,46 @@ const plugin = {
|
|
|
250
322
|
recallMemoriesTotal: 0,
|
|
251
323
|
recallDuplicatesCollapsed: 0,
|
|
252
324
|
};
|
|
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.
|
|
253
328
|
// --- Agent Tools ---
|
|
254
329
|
const recentSaves = config.dedupeWindowMinutes > 0
|
|
255
330
|
? new RecentSaves(config.dedupeWindowMinutes)
|
|
256
331
|
: null;
|
|
257
332
|
if (api.registerTool) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
mode: {
|
|
274
|
-
type: "string",
|
|
275
|
-
enum: ["all", "decisions", "preferences", "facts", "recent"],
|
|
276
|
-
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.",
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
required: ["query"],
|
|
280
|
-
},
|
|
281
|
-
async execute(_id, params) {
|
|
282
|
-
const query = String(params.query ?? "");
|
|
283
|
-
const limit = Math.min(Math.max(Number(params.limit) || 10, 1), 50);
|
|
284
|
-
const mode = typeof params.mode === "string" ? params.mode : "all";
|
|
285
|
-
await userIdReady;
|
|
286
|
-
// Augment query based on mode to improve retrieval precision
|
|
287
|
-
let effectiveQuery = query;
|
|
288
|
-
let queryType = "combined";
|
|
289
|
-
switch (mode) {
|
|
290
|
-
case "decisions":
|
|
291
|
-
effectiveQuery = `[type:decision] ${query}`;
|
|
292
|
-
queryType = "factual";
|
|
293
|
-
break;
|
|
294
|
-
case "preferences":
|
|
295
|
-
effectiveQuery = `[type:preference] ${query}`;
|
|
296
|
-
queryType = "factual";
|
|
297
|
-
break;
|
|
298
|
-
case "facts":
|
|
299
|
-
effectiveQuery = `[type:fact] ${query}`;
|
|
300
|
-
queryType = "factual";
|
|
301
|
-
break;
|
|
302
|
-
case "recent":
|
|
303
|
-
queryType = "combined";
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
api.logger.debug?.(`Cortex search: "${effectiveQuery.slice(0, 80)}" (limit=${limit}, mode=${mode})`);
|
|
307
|
-
sessionStats.searches++;
|
|
308
|
-
void auditLoggerProxy.log({
|
|
309
|
-
feature: "tool-search-memory",
|
|
310
|
-
method: "POST",
|
|
311
|
-
endpoint: "/v1/recall",
|
|
312
|
-
payload: effectiveQuery,
|
|
313
|
-
userId,
|
|
314
|
-
});
|
|
315
|
-
try {
|
|
316
|
-
const doRecall = async (attempt = 0) => {
|
|
317
|
-
try {
|
|
318
|
-
return await client.recall(effectiveQuery, config.toolTimeoutMs, {
|
|
319
|
-
limit,
|
|
320
|
-
userId: userId,
|
|
321
|
-
queryType,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
catch (err) {
|
|
325
|
-
if (attempt < 1 && /50[23]/.test(String(err))) {
|
|
326
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
327
|
-
return doRecall(attempt + 1);
|
|
328
|
-
}
|
|
329
|
-
throw err;
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
const response = await doRecall();
|
|
333
|
-
if (!response.memories?.length) {
|
|
334
|
-
return { content: [{ type: "text", text: "No memories found matching that query." }] };
|
|
335
|
-
}
|
|
336
|
-
api.logger.debug?.(`Cortex search returned ${response.memories.length} memories`);
|
|
337
|
-
const formatted = formatMemories(response.memories, config.recallTopK);
|
|
338
|
-
return { content: [{ type: "text", text: formatted }] };
|
|
339
|
-
}
|
|
340
|
-
catch (err) {
|
|
341
|
-
api.logger.warn(`Cortex search failed: ${String(err)}`);
|
|
342
|
-
return { content: [{ type: "text", text: `Memory search failed: ${String(err)}` }] };
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
api.registerTool({
|
|
347
|
-
name: "cortex_save_memory",
|
|
348
|
-
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.",
|
|
349
|
-
parameters: {
|
|
350
|
-
type: "object",
|
|
351
|
-
properties: {
|
|
352
|
-
text: {
|
|
353
|
-
type: "string",
|
|
354
|
-
description: "The information to save to memory (a fact, preference, or context)",
|
|
355
|
-
},
|
|
356
|
-
type: {
|
|
357
|
-
type: "string",
|
|
358
|
-
enum: ["preference", "decision", "fact", "transient"],
|
|
359
|
-
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.",
|
|
360
|
-
},
|
|
361
|
-
importance: {
|
|
362
|
-
type: "string",
|
|
363
|
-
enum: ["high", "normal", "low"],
|
|
364
|
-
description: "How important this memory is for future recall. 'high' for critical preferences or decisions, 'normal' for general facts, 'low' for minor context.",
|
|
365
|
-
},
|
|
366
|
-
checkNovelty: {
|
|
367
|
-
type: "boolean",
|
|
368
|
-
description: "When true, checks if a similar memory already exists before saving. Skips the save if a near-duplicate is found. Defaults to false.",
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
required: ["text"],
|
|
372
|
-
},
|
|
373
|
-
async execute(_id, params) {
|
|
374
|
-
const text = String(params.text ?? "");
|
|
375
|
-
if (!text || text.length < 5) {
|
|
376
|
-
return { content: [{ type: "text", text: "Text too short to save as a memory." }] };
|
|
377
|
-
}
|
|
378
|
-
const memoryType = typeof params.type === "string" ? params.type : undefined;
|
|
379
|
-
const importance = typeof params.importance === "string" ? params.importance : undefined;
|
|
380
|
-
const checkNovelty = params.checkNovelty === true;
|
|
381
|
-
await userIdReady;
|
|
382
|
-
if (!userId) {
|
|
383
|
-
api.logger.warn("Cortex save: missing user_id");
|
|
384
|
-
return { content: [{ type: "text", text: "Failed to save memory: Cortex ingest requires user_id." }] };
|
|
385
|
-
}
|
|
386
|
-
// Prepend metadata tags so they're stored with the memory text
|
|
387
|
-
const metaTags = [];
|
|
388
|
-
if (memoryType)
|
|
389
|
-
metaTags.push(`[type:${memoryType}]`);
|
|
390
|
-
if (importance)
|
|
391
|
-
metaTags.push(`[importance:${importance}]`);
|
|
392
|
-
const enrichedText = metaTags.length > 0 ? `${metaTags.join(" ")} ${text}` : text;
|
|
393
|
-
// Item 5: Client-side dedupe — skip if near-duplicate was saved recently
|
|
394
|
-
if (recentSaves?.isDuplicate(text)) {
|
|
395
|
-
api.logger.debug?.(`Cortex save skipped (duplicate within window): "${text.slice(0, 60)}"`);
|
|
396
|
-
sessionStats.savesSkippedDedupe++;
|
|
397
|
-
return {
|
|
398
|
-
content: [{ type: "text", text: "This memory is very similar to one saved recently. Skipped to avoid duplication." }],
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
// Item 7: Novelty check — query existing memories to see if this is already stored
|
|
402
|
-
if (checkNovelty) {
|
|
403
|
-
try {
|
|
404
|
-
const existing = await client.retrieve(text, 1, "fast", config.toolTimeoutMs, "factual", { userId });
|
|
405
|
-
const topScore = existing.results?.[0]?.score ?? 0;
|
|
406
|
-
if (topScore >= config.noveltyThreshold) {
|
|
407
|
-
api.logger.debug?.(`Cortex save skipped (not novel, score=${topScore.toFixed(2)}): "${text.slice(0, 60)}"`);
|
|
408
|
-
sessionStats.savesSkippedNovelty++;
|
|
409
|
-
recentSaves?.record(text);
|
|
410
|
-
return {
|
|
411
|
-
content: [{
|
|
412
|
-
type: "text",
|
|
413
|
-
text: `This memory already exists (similarity ${(topScore * 100).toFixed(0)}%). Skipped to avoid duplication.`,
|
|
414
|
-
}],
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
catch (err) {
|
|
419
|
-
// Novelty check is best-effort — proceed with save on failure
|
|
420
|
-
api.logger.debug?.(`Cortex novelty check failed, proceeding with save: ${String(err)}`);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
api.logger.debug?.(`Cortex save: "${enrichedText.slice(0, 80)}"`);
|
|
424
|
-
void auditLoggerProxy.log({
|
|
425
|
-
feature: "tool-save-memory",
|
|
426
|
-
method: "POST",
|
|
427
|
-
endpoint: "/v1/remember",
|
|
428
|
-
payload: enrichedText,
|
|
429
|
-
sessionId,
|
|
430
|
-
userId,
|
|
431
|
-
});
|
|
432
|
-
try {
|
|
433
|
-
const now = new Date();
|
|
434
|
-
const referenceDate = now.toISOString().slice(0, 10);
|
|
435
|
-
await client.remember(enrichedText, sessionId, config.toolTimeoutMs, referenceDate, userId, "openclaw", "OpenClaw");
|
|
436
|
-
if (knowledgeState) {
|
|
437
|
-
knowledgeState.hasMemories = true;
|
|
438
|
-
}
|
|
439
|
-
recentSaves?.record(text);
|
|
440
|
-
sessionStats.saves++;
|
|
441
|
-
api.logger.debug?.("Cortex remember accepted");
|
|
442
|
-
return {
|
|
443
|
-
content: [{
|
|
444
|
-
type: "text",
|
|
445
|
-
text: "Memory submitted for processing. It should be available shortly.",
|
|
446
|
-
}],
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
catch (err) {
|
|
450
|
-
api.logger.warn(`Cortex save failed, falling back to async ingest: ${String(err)}`);
|
|
451
|
-
try {
|
|
452
|
-
const referenceDate = new Date().toISOString();
|
|
453
|
-
const job = await client.submitIngest(enrichedText, sessionId, referenceDate, userId, "openclaw", "OpenClaw");
|
|
454
|
-
if (knowledgeState) {
|
|
455
|
-
knowledgeState.hasMemories = true;
|
|
456
|
-
}
|
|
457
|
-
recentSaves?.record(text);
|
|
458
|
-
sessionStats.saves++;
|
|
459
|
-
return {
|
|
460
|
-
content: [{
|
|
461
|
-
type: "text",
|
|
462
|
-
text: `Memory save queued (job ${job.job_id}, status=${job.status}). It should be available shortly.`,
|
|
463
|
-
}],
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
catch (fallbackErr) {
|
|
467
|
-
api.logger.warn(`Cortex save fallback failed: ${String(fallbackErr)}`);
|
|
468
|
-
return { content: [{ type: "text", text: `Failed to save memory: ${String(err)}` }] };
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
},
|
|
472
|
-
});
|
|
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));
|
|
473
348
|
api.logger.debug?.("Cortex tools registered: cortex_search_memory, cortex_save_memory");
|
|
474
349
|
}
|
|
475
350
|
// --- Auto-Reply Commands ---
|
|
476
351
|
if (api.registerCommand) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
auditLoggerInner = new AuditLogger(workspaceDirResolved, api.logger);
|
|
492
|
-
api.logger.info(`Cortex audit log enabled via command: ${workspaceDirResolved}/.cortex/audit/`);
|
|
493
|
-
return {
|
|
494
|
-
text: [
|
|
495
|
-
`**Audit log enabled.**`,
|
|
496
|
-
``,
|
|
497
|
-
`All data sent to and received from Cortex will be recorded locally.`,
|
|
498
|
-
`Log path: \`${workspaceDirResolved}/.cortex/audit/\``,
|
|
499
|
-
``,
|
|
500
|
-
`Turn off with \`/audit off\`. Log files are preserved when disabled.`,
|
|
501
|
-
].join("\n"),
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
if (arg === "off") {
|
|
505
|
-
if (!auditLoggerInner) {
|
|
506
|
-
return { text: "Audit log is already off. No data is being recorded." };
|
|
507
|
-
}
|
|
508
|
-
auditLoggerInner = undefined;
|
|
509
|
-
api.logger.info("Cortex audit log disabled via command");
|
|
510
|
-
return {
|
|
511
|
-
text: [
|
|
512
|
-
`**Audit log disabled.**`,
|
|
513
|
-
``,
|
|
514
|
-
`Cortex API calls are no longer being recorded.`,
|
|
515
|
-
`Existing log files are preserved and can be reviewed at:`,
|
|
516
|
-
`\`${workspaceDirResolved}/.cortex/audit/\``,
|
|
517
|
-
].join("\n"),
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
// No args — show status
|
|
521
|
-
const status = auditLoggerInner ? "on" : "off";
|
|
522
|
-
const lines = [
|
|
523
|
-
`**Cortex Audit Log**`,
|
|
524
|
-
``,
|
|
525
|
-
`The audit log records all data sent to and received from the Cortex API, stored locally for inspection.`,
|
|
526
|
-
``,
|
|
527
|
-
`- Status: **${status}**`,
|
|
528
|
-
`- Config default: ${config.auditLog ? "on" : "off"}`,
|
|
529
|
-
];
|
|
530
|
-
if (workspaceDirResolved) {
|
|
531
|
-
lines.push(`- Log path: \`${workspaceDirResolved}/.cortex/audit/\``);
|
|
532
|
-
}
|
|
533
|
-
lines.push("", "Toggle: `/audit on` · `/audit off`");
|
|
534
|
-
return { text: lines.join("\n") };
|
|
535
|
-
},
|
|
536
|
-
});
|
|
537
|
-
api.registerCommand({
|
|
538
|
-
name: "checkpoint",
|
|
539
|
-
description: "Save a session checkpoint to Cortex before resetting",
|
|
540
|
-
acceptsArgs: true,
|
|
541
|
-
handler: checkpointHandler,
|
|
542
|
-
});
|
|
543
|
-
api.registerCommand({
|
|
544
|
-
name: "sleep",
|
|
545
|
-
description: "Mark the current session as cleanly ended (clears recovery warning state)",
|
|
546
|
-
acceptsArgs: false,
|
|
547
|
-
handler: async () => {
|
|
548
|
-
try {
|
|
549
|
-
await sessionState.clear();
|
|
550
|
-
return {
|
|
551
|
-
text: [
|
|
552
|
-
`**Session ended cleanly.**`,
|
|
553
|
-
``,
|
|
554
|
-
`Cortex will not show a recovery warning when you start your next session.`,
|
|
555
|
-
`Use \`/checkpoint\` before \`/sleep\` if you want to save a summary of what you were working on.`,
|
|
556
|
-
].join("\n"),
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
catch (err) {
|
|
560
|
-
return { text: `Failed to mark session clean: ${String(err)}` };
|
|
561
|
-
}
|
|
562
|
-
},
|
|
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; },
|
|
563
365
|
});
|
|
564
366
|
api.logger.debug?.("Cortex commands registered: /audit, /checkpoint, /sleep");
|
|
565
367
|
}
|
|
@@ -592,272 +394,18 @@ const plugin = {
|
|
|
592
394
|
}
|
|
593
395
|
// --- CLI Commands (terminal-level) ---
|
|
594
396
|
if (api.registerCli) {
|
|
595
|
-
api.registerCli(
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
try {
|
|
608
|
-
healthy = await client.healthCheck();
|
|
609
|
-
const ms = Date.now() - startHealth;
|
|
610
|
-
console.log(` API Health: ${healthy ? "OK" : "UNREACHABLE"} (${ms}ms)`);
|
|
611
|
-
}
|
|
612
|
-
catch {
|
|
613
|
-
console.log(` API Health: UNREACHABLE`);
|
|
614
|
-
}
|
|
615
|
-
if (!healthy) {
|
|
616
|
-
console.log("\nAPI is unreachable. Check baseUrl and network connectivity.");
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
// Knowledge
|
|
620
|
-
try {
|
|
621
|
-
const startKnowledge = Date.now();
|
|
622
|
-
const knowledge = await client.knowledge(undefined, userId);
|
|
623
|
-
const ms = Date.now() - startKnowledge;
|
|
624
|
-
console.log(` Knowledge: OK (${ms}ms)`);
|
|
625
|
-
console.log(` Memories: ${knowledge.total_memories.toLocaleString()}`);
|
|
626
|
-
console.log(` Sessions: ${knowledge.total_sessions}`);
|
|
627
|
-
console.log(` Maturity: ${knowledge.maturity}`);
|
|
628
|
-
}
|
|
629
|
-
catch (err) {
|
|
630
|
-
console.log(` Knowledge: FAILED — ${String(err)}`);
|
|
631
|
-
}
|
|
632
|
-
// Stats
|
|
633
|
-
try {
|
|
634
|
-
const startStats = Date.now();
|
|
635
|
-
const stats = await client.stats(undefined, userId);
|
|
636
|
-
const ms = Date.now() - startStats;
|
|
637
|
-
console.log(` Stats: OK (${ms}ms)`);
|
|
638
|
-
console.log(` Pipeline: tier ${stats.pipeline_tier}`);
|
|
639
|
-
}
|
|
640
|
-
catch (err) {
|
|
641
|
-
console.log(` Stats: FAILED — ${String(err)}`);
|
|
642
|
-
}
|
|
643
|
-
// Recall
|
|
644
|
-
try {
|
|
645
|
-
const startRecall = Date.now();
|
|
646
|
-
await client.recall("test", 5000, { limit: 1, userId });
|
|
647
|
-
const ms = Date.now() - startRecall;
|
|
648
|
-
console.log(` Recall: OK (${ms}ms)`);
|
|
649
|
-
}
|
|
650
|
-
catch (err) {
|
|
651
|
-
console.log(` Recall: FAILED — ${String(err)}`);
|
|
652
|
-
}
|
|
653
|
-
// Retrieve
|
|
654
|
-
try {
|
|
655
|
-
const startRetrieve = Date.now();
|
|
656
|
-
await client.retrieve("test", 1, "fast", 5000, undefined, { userId });
|
|
657
|
-
const ms = Date.now() - startRetrieve;
|
|
658
|
-
console.log(` Retrieve: OK (${ms}ms)`);
|
|
659
|
-
}
|
|
660
|
-
catch (err) {
|
|
661
|
-
console.log(` Retrieve: FAILED — ${String(err)}`);
|
|
662
|
-
}
|
|
663
|
-
console.log("");
|
|
664
|
-
console.log(` Version: ${version}`);
|
|
665
|
-
console.log(` User ID: ${userId ?? "unknown"}`);
|
|
666
|
-
console.log(` Base URL: ${config.baseUrl}`);
|
|
667
|
-
console.log(` Auto-Recall: ${config.autoRecall ? "on" : "off"}`);
|
|
668
|
-
console.log(` Auto-Capture: ${config.autoCapture ? "on" : "off"}`);
|
|
669
|
-
console.log(` File Sync: ${config.fileSync ? "on" : "off"}`);
|
|
670
|
-
console.log(` Dedupe Window: ${config.dedupeWindowMinutes > 0 ? `${config.dedupeWindowMinutes}min` : "off"}`);
|
|
671
|
-
// Session activity stats
|
|
672
|
-
const totalSkipped = sessionStats.savesSkippedDedupe + sessionStats.savesSkippedNovelty;
|
|
673
|
-
const avgRecallMemories = sessionStats.recallCount > 0
|
|
674
|
-
? (sessionStats.recallMemoriesTotal / sessionStats.recallCount).toFixed(1)
|
|
675
|
-
: "0";
|
|
676
|
-
console.log("");
|
|
677
|
-
console.log("Session Activity");
|
|
678
|
-
console.log("-".repeat(50));
|
|
679
|
-
console.log(` Saves: ${sessionStats.saves}`);
|
|
680
|
-
if (totalSkipped > 0) {
|
|
681
|
-
console.log(` Skipped: ${totalSkipped} (${sessionStats.savesSkippedDedupe} dedupe, ${sessionStats.savesSkippedNovelty} novelty)`);
|
|
682
|
-
}
|
|
683
|
-
console.log(` Searches: ${sessionStats.searches}`);
|
|
684
|
-
console.log(` Recalls: ${sessionStats.recallCount}`);
|
|
685
|
-
console.log(` Avg memories/recall: ${avgRecallMemories}`);
|
|
686
|
-
if (sessionStats.recallDuplicatesCollapsed > 0) {
|
|
687
|
-
console.log(` Duplicates collapsed: ${sessionStats.recallDuplicatesCollapsed}`);
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
cortex
|
|
691
|
-
.command("memories")
|
|
692
|
-
.description("Show memory count and maturity")
|
|
693
|
-
.action(async () => {
|
|
694
|
-
await userIdReady;
|
|
695
|
-
try {
|
|
696
|
-
const knowledge = await client.knowledge(undefined, userId);
|
|
697
|
-
console.log(`Memories: ${knowledge.total_memories.toLocaleString()}`);
|
|
698
|
-
console.log(`Sessions: ${knowledge.total_sessions}`);
|
|
699
|
-
console.log(`Maturity: ${knowledge.maturity}`);
|
|
700
|
-
if (knowledge.entities.length > 0) {
|
|
701
|
-
console.log(`\nTop Entities:`);
|
|
702
|
-
knowledge.entities.slice(0, 10).forEach((e) => {
|
|
703
|
-
console.log(` ${e.name} (${e.memory_count} memories, last seen ${e.last_seen})`);
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
catch (err) {
|
|
708
|
-
console.error(`Failed: ${String(err)}`);
|
|
709
|
-
process.exitCode = 1;
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
cortex
|
|
713
|
-
.command("search")
|
|
714
|
-
.description("Search memories from the terminal")
|
|
715
|
-
.argument("<query>", "Search query")
|
|
716
|
-
.option("--limit <n>", "Max results", "10")
|
|
717
|
-
.action(async (query, opts) => {
|
|
718
|
-
await userIdReady;
|
|
719
|
-
try {
|
|
720
|
-
const response = await client.recall(query, config.toolTimeoutMs, {
|
|
721
|
-
limit: parseInt(opts.limit),
|
|
722
|
-
userId,
|
|
723
|
-
queryType: "combined",
|
|
724
|
-
});
|
|
725
|
-
if (!response.memories?.length) {
|
|
726
|
-
console.log(`No memories found for: "${query}"`);
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
console.log(`Found ${response.memories.length} memories:\n`);
|
|
730
|
-
response.memories.forEach((m, i) => {
|
|
731
|
-
console.log(`${i + 1}. [${m.confidence.toFixed(2)}] ${m.content}`);
|
|
732
|
-
if (m.entities.length > 0) {
|
|
733
|
-
console.log(` entities: ${m.entities.join(", ")}`);
|
|
734
|
-
}
|
|
735
|
-
console.log("");
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
catch (err) {
|
|
739
|
-
console.error(`Search failed: ${String(err)}`);
|
|
740
|
-
process.exitCode = 1;
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
cortex
|
|
744
|
-
.command("config")
|
|
745
|
-
.description("Show current Cortex plugin configuration")
|
|
746
|
-
.action(async () => {
|
|
747
|
-
await userIdReady;
|
|
748
|
-
console.log(`Version: ${version}`);
|
|
749
|
-
console.log(`Base URL: ${config.baseUrl}`);
|
|
750
|
-
console.log(`User ID: ${userId ?? "unknown"}`);
|
|
751
|
-
console.log(`Namespace: ${namespace}`);
|
|
752
|
-
console.log(`Auto-Recall: ${config.autoRecall ? "on" : "off"}`);
|
|
753
|
-
console.log(`Auto-Capture: ${config.autoCapture ? "on" : "off"}`);
|
|
754
|
-
console.log(`File Sync: ${config.fileSync ? "on" : "off"}`);
|
|
755
|
-
console.log(`Transcript Sync: ${config.transcriptSync ? "on" : "off"}`);
|
|
756
|
-
console.log(`Recall Limit: ${config.recallLimit}`);
|
|
757
|
-
console.log(`Recall Timeout: ${config.recallTimeoutMs}ms`);
|
|
758
|
-
console.log(`Tool Timeout: ${config.toolTimeoutMs}ms`);
|
|
759
|
-
console.log(`Audit Log: ${config.auditLog ? "on" : "off"}`);
|
|
760
|
-
});
|
|
761
|
-
cortex
|
|
762
|
-
.command("pair")
|
|
763
|
-
.description("Generate a TooToo pairing code to link your agent")
|
|
764
|
-
.action(async () => {
|
|
765
|
-
await userIdReady;
|
|
766
|
-
if (!userId) {
|
|
767
|
-
console.error("Cannot generate pairing code: user ID not available.");
|
|
768
|
-
process.exitCode = 1;
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
try {
|
|
772
|
-
const { user_code, expires_in } = await client.generatePairingCode(userId);
|
|
773
|
-
const mins = Math.floor(expires_in / 60);
|
|
774
|
-
console.log(`Agent ID: ${userId}`);
|
|
775
|
-
console.log(`Pairing code: ${user_code}`);
|
|
776
|
-
console.log(`Expires in: ${mins} minute${mins !== 1 ? "s" : ""}`);
|
|
777
|
-
console.log("");
|
|
778
|
-
console.log("To link your TooToo account:");
|
|
779
|
-
console.log(" 1. Open app.tootoo.io/settings/agents");
|
|
780
|
-
console.log(' 2. Click "Connect Agent"');
|
|
781
|
-
console.log(" 3. Enter the code above");
|
|
782
|
-
}
|
|
783
|
-
catch (err) {
|
|
784
|
-
console.error(`Failed to generate pairing code: ${String(err)}`);
|
|
785
|
-
process.exitCode = 1;
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
cortex
|
|
789
|
-
.command("reset")
|
|
790
|
-
.description("Permanently delete ALL memories for this agent (irreversible)")
|
|
791
|
-
.option("--yes", "Skip confirmation prompt")
|
|
792
|
-
.action(async (opts) => {
|
|
793
|
-
await userIdReady;
|
|
794
|
-
if (!userId) {
|
|
795
|
-
console.error("Cannot reset: user ID not available.");
|
|
796
|
-
process.exitCode = 1;
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
// Show what will be deleted
|
|
800
|
-
let memoryCount = 0;
|
|
801
|
-
let sessionCount = 0;
|
|
802
|
-
try {
|
|
803
|
-
const knowledge = await client.knowledge(undefined, userId);
|
|
804
|
-
memoryCount = knowledge.total_memories;
|
|
805
|
-
sessionCount = knowledge.total_sessions;
|
|
806
|
-
}
|
|
807
|
-
catch {
|
|
808
|
-
// Continue even if we can't get counts
|
|
809
|
-
}
|
|
810
|
-
console.log("");
|
|
811
|
-
console.log(" WARNING: This will permanently delete ALL data for this agent.");
|
|
812
|
-
console.log("");
|
|
813
|
-
console.log(` Agent ID: ${userId}`);
|
|
814
|
-
if (memoryCount > 0 || sessionCount > 0) {
|
|
815
|
-
console.log(` Memories: ${memoryCount.toLocaleString()}`);
|
|
816
|
-
console.log(` Sessions: ${sessionCount}`);
|
|
817
|
-
}
|
|
818
|
-
console.log("");
|
|
819
|
-
console.log(" This includes all memories, facts, suggestions, and graph data.");
|
|
820
|
-
console.log(" Agent links (TooToo pairing) will be preserved.");
|
|
821
|
-
console.log(" This action CANNOT be undone.");
|
|
822
|
-
console.log("");
|
|
823
|
-
if (!opts.yes) {
|
|
824
|
-
const { createInterface } = await import("node:readline");
|
|
825
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
826
|
-
const answer = await new Promise((resolve) => {
|
|
827
|
-
rl.question(" Type 'reset' to confirm: ", resolve);
|
|
828
|
-
});
|
|
829
|
-
rl.close();
|
|
830
|
-
if (answer.trim().toLowerCase() !== "reset") {
|
|
831
|
-
console.log("\n Aborted. No data was deleted.");
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
try {
|
|
836
|
-
const result = await client.forgetUser(userId);
|
|
837
|
-
const d = result.deleted;
|
|
838
|
-
console.log("");
|
|
839
|
-
console.log(" Memory reset complete.");
|
|
840
|
-
console.log("");
|
|
841
|
-
console.log(` Deleted:`);
|
|
842
|
-
console.log(` Engraved memories: ${d.engraved_memories}`);
|
|
843
|
-
console.log(` Resonated memories: ${d.resonated_memories}`);
|
|
844
|
-
console.log(` Graph nodes: ${d.nodes}`);
|
|
845
|
-
console.log(` Codex suggestions: ${d.codex_suggestions}`);
|
|
846
|
-
console.log(` Suppressions: ${d.codex_suggestion_suppressions}`);
|
|
847
|
-
}
|
|
848
|
-
catch (err) {
|
|
849
|
-
if (isAbortError(err) && await resetCompletedAfterAbort(client, userId)) {
|
|
850
|
-
console.log("");
|
|
851
|
-
console.log(" Memory reset complete.");
|
|
852
|
-
console.log("");
|
|
853
|
-
console.log(" The server finished the reset, but the request ended before deletion stats were returned.");
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
console.error(`\n Reset failed: ${String(err)}`);
|
|
857
|
-
process.exitCode = 1;
|
|
858
|
-
}
|
|
859
|
-
});
|
|
860
|
-
}, { 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
|
+
});
|
|
861
409
|
api.logger.debug?.("Cortex CLI registered: openclaw cortex {status,memories,search,config,pair,reset}");
|
|
862
410
|
}
|
|
863
411
|
// --- Services: retry queue, file sync ---
|