@totalreclaw/totalreclaw 3.3.12-rc.2 → 3.3.12-rc.21
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/CHANGELOG.md +26 -0
- package/SKILL.md +34 -249
- package/api-client.ts +17 -9
- package/batch-gate.ts +42 -0
- package/billing-cache.ts +25 -20
- package/claims-helper.ts +7 -1
- package/config.ts +54 -12
- package/consolidation.ts +6 -13
- package/contradiction-sync.ts +19 -14
- package/credential-provider.ts +184 -0
- package/crypto.ts +27 -160
- package/dist/api-client.js +17 -9
- package/dist/batch-gate.js +40 -0
- package/dist/billing-cache.js +19 -21
- package/dist/claims-helper.js +7 -1
- package/dist/config.js +54 -12
- package/dist/consolidation.js +6 -15
- package/dist/contradiction-sync.js +15 -15
- package/dist/credential-provider.js +145 -0
- package/dist/crypto.js +17 -137
- package/dist/download-ux.js +11 -7
- package/dist/embedder-loader.js +266 -0
- package/dist/embedding.js +36 -3
- package/dist/entry.js +123 -0
- package/dist/extractor.js +134 -0
- package/dist/fs-helpers.js +116 -241
- package/dist/import-adapters/chatgpt-adapter.js +14 -0
- package/dist/import-adapters/claude-adapter.js +14 -0
- package/dist/import-adapters/gemini-adapter.js +43 -159
- package/dist/import-adapters/mcp-memory-adapter.js +14 -0
- package/dist/import-state-manager.js +100 -0
- package/dist/index.js +1130 -2520
- package/dist/llm-client.js +69 -1
- package/dist/memory-runtime.js +459 -0
- package/dist/native-memory.js +123 -0
- package/dist/onboarding-cli.js +3 -2
- package/dist/pair-cli.js +1 -1
- package/dist/pair-crypto.js +16 -358
- package/dist/pair-http.js +147 -4
- package/dist/relay.js +140 -0
- package/dist/reranker.js +13 -8
- package/dist/semantic-dedup.js +5 -7
- package/dist/skill-register.js +97 -0
- package/dist/subgraph-search.js +3 -1
- package/dist/subgraph-store.js +315 -282
- package/dist/tool-gating.js +39 -26
- package/dist/tools.js +367 -0
- package/dist/tr-cli-export-helper.js +103 -0
- package/dist/tr-cli.js +220 -127
- package/dist/trajectory-poller.js +155 -9
- package/dist/vault-crypto.js +551 -0
- package/download-ux.ts +12 -6
- package/embedder-loader.ts +293 -1
- package/embedding.ts +43 -3
- package/entry.ts +132 -0
- package/extractor.ts +167 -0
- package/fs-helpers.ts +166 -292
- package/import-adapters/chatgpt-adapter.ts +18 -0
- package/import-adapters/claude-adapter.ts +18 -0
- package/import-adapters/gemini-adapter.ts +56 -183
- package/import-adapters/mcp-memory-adapter.ts +18 -0
- package/import-adapters/types.ts +1 -1
- package/import-state-manager.ts +139 -0
- package/index.ts +1432 -3002
- package/llm-client.ts +74 -1
- package/memory-runtime.ts +723 -0
- package/native-memory.ts +196 -0
- package/onboarding-cli.ts +3 -2
- package/openclaw.plugin.json +5 -17
- package/package.json +7 -4
- package/pair-cli.ts +1 -1
- package/pair-crypto.ts +41 -483
- package/pair-http.ts +194 -5
- package/postinstall.mjs +138 -0
- package/relay.ts +172 -0
- package/reranker.ts +13 -8
- package/semantic-dedup.ts +5 -6
- package/skill-register.ts +146 -0
- package/skill.json +1 -1
- package/subgraph-search.ts +3 -1
- package/subgraph-store.ts +334 -299
- package/tool-gating.ts +39 -26
- package/tools.ts +499 -0
- package/tr-cli-export-helper.ts +138 -0
- package/tr-cli.ts +263 -133
- package/trajectory-poller.ts +162 -10
- package/vault-crypto.ts +705 -0
package/tr-cli.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* tr — TotalReclaw
|
|
3
|
+
* tr — TotalReclaw CLI (explicit-write + curation surface)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Scope (Phase 3.3 — OpenClaw native integration): recall is now NATIVE.
|
|
6
|
+
* The agent reads memories via OpenClaw's bundled `memory_search` /
|
|
7
|
+
* `memory_get` tools (backed by the MemoryPluginCapability + TrMemorySearchManager
|
|
8
|
+
* adapter registered in `index.ts`). This CLI no longer ships a recall path.
|
|
9
|
+
*
|
|
10
|
+
* What's still CLI-only (no native agent-facing surface):
|
|
11
|
+
* - explicit write (`tr remember`) — the conventional memory contract has no
|
|
12
|
+
* agent-facing write tool; auto-extraction stores facts via hooks.
|
|
13
|
+
* - curation / lifecycle (`tr forget`, `tr export`).
|
|
14
|
+
* - onboarding + pairing (`tr status`, `tr pair`).
|
|
10
15
|
*
|
|
11
16
|
* Phrase-safety: this CLI reads credentials.json (mnemonic at rest) but NEVER
|
|
12
17
|
* prints the mnemonic to stdout, stderr, or any log. Phrase only enters via QR-pair
|
|
@@ -15,8 +20,13 @@
|
|
|
15
20
|
* Commands:
|
|
16
21
|
* tr status [--json] — print onboarding + credentials state
|
|
17
22
|
* tr pair [--json] — start a relay pairing session, print URL+PIN+QR
|
|
18
|
-
* tr remember [--json] <text> — store a memory in the encrypted vault
|
|
19
|
-
* tr
|
|
23
|
+
* tr remember [--json] <text> — store a memory in the encrypted vault (on-chain)
|
|
24
|
+
* tr forget [--json] <factId> — tombstone a memory on-chain (find the id via memory_search)
|
|
25
|
+
* tr export [--json] [--format json|markdown] — dump all memories from the subgraph
|
|
26
|
+
*
|
|
27
|
+
* 3.3.12-rc.4 — switched remember/forget/export from `/v1/store` and
|
|
28
|
+
* `/v1/search` (those endpoints were removed during the on-chain pivot —
|
|
29
|
+
* relay returns 404) to the on-chain UserOp + subgraph paths.
|
|
20
30
|
*
|
|
21
31
|
* --json flag: all agent-facing CLI calls MUST use --json for clean machine-parseable output.
|
|
22
32
|
* Plain text mode is for direct user CLI use only.
|
|
@@ -25,22 +35,28 @@
|
|
|
25
35
|
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js status --json`
|
|
26
36
|
*/
|
|
27
37
|
|
|
28
|
-
import path from 'node:path';
|
|
29
|
-
import os from 'node:os';
|
|
30
38
|
import { randomUUID } from 'node:crypto';
|
|
31
39
|
|
|
32
|
-
import { CONFIG } from './config.js';
|
|
40
|
+
import { CONFIG, setRecoveryPhraseOverride } from './config.js';
|
|
33
41
|
import { loadCredentialsJson } from './fs-helpers.js';
|
|
34
42
|
import { printStatus } from './onboarding-cli.js';
|
|
35
43
|
import {
|
|
36
44
|
deriveKeys,
|
|
37
45
|
computeAuthKeyHash,
|
|
38
46
|
encrypt,
|
|
39
|
-
decrypt,
|
|
40
47
|
generateBlindIndices,
|
|
41
48
|
generateContentFingerprint,
|
|
42
49
|
} from './crypto.js';
|
|
43
50
|
import { createApiClient } from './api-client.js';
|
|
51
|
+
import {
|
|
52
|
+
encodeFactProtobuf,
|
|
53
|
+
submitFactBatchOnChain,
|
|
54
|
+
deriveSmartAccountAddress,
|
|
55
|
+
getSubgraphConfig,
|
|
56
|
+
PROTOBUF_VERSION_V4,
|
|
57
|
+
type FactPayload,
|
|
58
|
+
} from './subgraph-store.js';
|
|
59
|
+
import { exportAllFacts } from './tr-cli-export-helper.js';
|
|
44
60
|
|
|
45
61
|
// ---------------------------------------------------------------------------
|
|
46
62
|
// Helpers
|
|
@@ -52,7 +68,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
52
68
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
53
69
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
54
70
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
55
|
-
const PLUGIN_VERSION = '3.3.12-rc.
|
|
71
|
+
const PLUGIN_VERSION = '3.3.12-rc.21';
|
|
56
72
|
|
|
57
73
|
function die(msg: string, code = 1): never {
|
|
58
74
|
process.stderr.write(`tr: ${msg}\n`);
|
|
@@ -70,13 +86,25 @@ function popFlag(args: string[], flag: string): [boolean, string[]] {
|
|
|
70
86
|
return [true, [...args.slice(0, idx), ...args.slice(idx + 1)]];
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
/** Parse --
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
/** Parse --format VALUE from args, returning [value, cleanedArgs]. */
|
|
90
|
+
function popOptionFlag(
|
|
91
|
+
args: string[],
|
|
92
|
+
flag: string,
|
|
93
|
+
defaultValue: string,
|
|
94
|
+
): [string, string[]] {
|
|
95
|
+
const idx = args.indexOf(flag);
|
|
96
|
+
if (idx === -1 || idx + 1 >= args.length) return [defaultValue, args];
|
|
97
|
+
return [args[idx + 1], [...args.slice(0, idx), ...args.slice(idx + 2)]];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convert XChaCha20-Poly1305 base64 ciphertext to hex (the on-chain blob
|
|
102
|
+
* format). Mirrors `encryptToHex` in index.ts so we don't pull in the whole
|
|
103
|
+
* 7000-line module. Subgraph-stored facts use hex, not base64.
|
|
104
|
+
*/
|
|
105
|
+
function toHexBlob(plaintext: string, encryptionKey: Buffer): string {
|
|
106
|
+
const b64 = encrypt(plaintext, encryptionKey);
|
|
107
|
+
return Buffer.from(b64, 'base64').toString('hex');
|
|
80
108
|
}
|
|
81
109
|
|
|
82
110
|
// ---------------------------------------------------------------------------
|
|
@@ -89,6 +117,8 @@ interface CliContext {
|
|
|
89
117
|
dedupKey: Buffer;
|
|
90
118
|
apiClient: ReturnType<typeof createApiClient>;
|
|
91
119
|
userId: string;
|
|
120
|
+
/** Smart Account address derived from the mnemonic (subgraph owner key). */
|
|
121
|
+
walletAddress: string;
|
|
92
122
|
}
|
|
93
123
|
|
|
94
124
|
async function buildContext(): Promise<CliContext> {
|
|
@@ -106,6 +136,12 @@ async function buildContext(): Promise<CliContext> {
|
|
|
106
136
|
die('No recovery phrase in credentials.json. Run: tr pair --json');
|
|
107
137
|
}
|
|
108
138
|
|
|
139
|
+
// Make the mnemonic visible to subgraph-store helpers (getSubgraphConfig
|
|
140
|
+
// reads CONFIG.recoveryPhrase, which falls back to the override). We do
|
|
141
|
+
// NOT log the mnemonic anywhere — it just lives in process memory for the
|
|
142
|
+
// lifetime of this CLI invocation.
|
|
143
|
+
setRecoveryPhraseOverride(mnemonic);
|
|
144
|
+
|
|
109
145
|
// Parse existing salt/userId from credentials.json
|
|
110
146
|
let existingSalt: Buffer | undefined;
|
|
111
147
|
let existingUserId: string | undefined;
|
|
@@ -129,7 +165,8 @@ async function buildContext(): Promise<CliContext> {
|
|
|
129
165
|
if (existingUserId) {
|
|
130
166
|
userId = existingUserId;
|
|
131
167
|
} else {
|
|
132
|
-
// Register to get userId (idempotent on relay)
|
|
168
|
+
// Register to get userId (idempotent on relay) — auth key hash is the
|
|
169
|
+
// billing identity even in subgraph mode.
|
|
133
170
|
const authHash = computeAuthKeyHash(keys.authKey);
|
|
134
171
|
const saltHex = keys.salt.toString('hex');
|
|
135
172
|
try {
|
|
@@ -145,12 +182,24 @@ async function buildContext(): Promise<CliContext> {
|
|
|
145
182
|
}
|
|
146
183
|
}
|
|
147
184
|
|
|
185
|
+
// Derive the Smart Account address. This is the on-chain "owner" for
|
|
186
|
+
// every fact + the X-Wallet-Address header on every UserOp / subgraph
|
|
187
|
+
// call. Cheap eth_call to the SimpleAccountFactory; CREATE2 deterministic.
|
|
188
|
+
let walletAddress: string;
|
|
189
|
+
try {
|
|
190
|
+
walletAddress = await deriveSmartAccountAddress(mnemonic, CONFIG.chainId);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
193
|
+
die(`Failed to derive Smart Account address: ${msg}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
148
196
|
return {
|
|
149
197
|
authKeyHex,
|
|
150
198
|
encryptionKey: keys.encryptionKey,
|
|
151
199
|
dedupKey: keys.dedupKey,
|
|
152
200
|
apiClient,
|
|
153
201
|
userId,
|
|
202
|
+
walletAddress,
|
|
154
203
|
};
|
|
155
204
|
}
|
|
156
205
|
|
|
@@ -159,62 +208,27 @@ async function buildContext(): Promise<CliContext> {
|
|
|
159
208
|
// ---------------------------------------------------------------------------
|
|
160
209
|
|
|
161
210
|
async function cmdStatus(jsonMode: boolean): Promise<void> {
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let loadedAgeSec: number | undefined;
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const fs = await import('node:fs');
|
|
171
|
-
const candidatePaths = [
|
|
172
|
-
path.join(os.homedir(), '.openclaw', 'extensions', 'totalreclaw', '.loaded.json'),
|
|
173
|
-
path.join(os.homedir(), '.openclaw', 'npm', 'node_modules', '@totalreclaw', 'totalreclaw', 'dist', '.loaded.json'),
|
|
174
|
-
];
|
|
175
|
-
const resolvedPath = candidatePaths.find((p) => fs.existsSync(p));
|
|
176
|
-
if (resolvedPath) {
|
|
177
|
-
const raw = fs.readFileSync(resolvedPath, 'utf-8');
|
|
178
|
-
const manifest = JSON.parse(raw) as {
|
|
179
|
-
version?: string;
|
|
180
|
-
bootCount?: number;
|
|
181
|
-
loadedAt?: number;
|
|
182
|
-
hybridMode?: boolean;
|
|
183
|
-
tools?: string[];
|
|
184
|
-
};
|
|
185
|
-
pluginVersion = manifest.version ?? PLUGIN_VERSION;
|
|
186
|
-
bootCount = manifest.bootCount;
|
|
187
|
-
hybridMode = manifest.hybridMode !== false; // default true
|
|
188
|
-
toolCount = manifest.tools?.length;
|
|
189
|
-
const ageMs = Date.now() - (manifest.loadedAt ?? 0);
|
|
190
|
-
loadedAgeSec = Math.round(ageMs / 1000);
|
|
191
|
-
}
|
|
192
|
-
} catch {
|
|
193
|
-
// Best-effort
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check onboarding state
|
|
211
|
+
// Phase 3.4 retired the `.loaded.json` manifest — the writer was removed
|
|
212
|
+
// in 3.1 and the reader had nothing current to read. `tr status` now
|
|
213
|
+
// reports the static plugin version (from the CLI binary's own
|
|
214
|
+
// PLUGIN_VERSION constant) plus onboarding state. For per-boot history,
|
|
215
|
+
// consult the gateway log.
|
|
197
216
|
const creds = loadCredentialsJson(CREDENTIALS_PATH);
|
|
198
217
|
const onboarded = !!creds;
|
|
199
218
|
|
|
200
219
|
if (jsonMode) {
|
|
201
220
|
// JSON-first output for agent parsing
|
|
202
221
|
const out: Record<string, unknown> = {
|
|
203
|
-
version:
|
|
222
|
+
version: PLUGIN_VERSION,
|
|
204
223
|
onboarded,
|
|
205
224
|
next_step: onboarded ? 'none' : 'pair',
|
|
206
|
-
tool_count: toolCount ?? 17,
|
|
207
|
-
hybrid_mode: hybridMode,
|
|
208
225
|
};
|
|
209
|
-
if (bootCount !== undefined) out.boot_count = bootCount;
|
|
210
|
-
if (loadedAgeSec !== undefined) out.loaded_age_sec = loadedAgeSec;
|
|
211
226
|
log(JSON.stringify(out));
|
|
212
227
|
} else {
|
|
213
228
|
// Human-readable plain text for direct user CLI use
|
|
214
229
|
printStatus(CREDENTIALS_PATH, STATE_PATH, process.stdout);
|
|
215
230
|
process.stdout.write(
|
|
216
|
-
`\n plugin:
|
|
217
|
-
` hybrid-mode: ${hybridMode ? 'yes (primary — use tr <cmd> --json)' : 'no'}\n` +
|
|
231
|
+
`\n plugin: loaded (version=${PLUGIN_VERSION})\n` +
|
|
218
232
|
` hooks: before_agent_start, agent_end, message_received, before_reset\n`,
|
|
219
233
|
);
|
|
220
234
|
}
|
|
@@ -271,11 +285,10 @@ async function cmdRemember(rawArgs: string[]): Promise<void> {
|
|
|
271
285
|
|
|
272
286
|
const ctx = await buildContext();
|
|
273
287
|
|
|
274
|
-
// Build a
|
|
288
|
+
// Build a Memory Taxonomy v1 claim blob (matches storeExtractedFacts shape).
|
|
275
289
|
const now = new Date().toISOString();
|
|
276
|
-
const factId = randomUUID()
|
|
290
|
+
const factId = randomUUID();
|
|
277
291
|
|
|
278
|
-
// Encrypt the memory text
|
|
279
292
|
const blob = JSON.stringify({
|
|
280
293
|
text,
|
|
281
294
|
type: 'claim',
|
|
@@ -291,28 +304,53 @@ async function cmdRemember(rawArgs: string[]): Promise<void> {
|
|
|
291
304
|
timestamp: now,
|
|
292
305
|
version: 'v1',
|
|
293
306
|
});
|
|
294
|
-
const encrypted_blob = encrypt(blob, ctx.encryptionKey);
|
|
295
|
-
const blind_indices = generateBlindIndices(text);
|
|
296
|
-
const content_fp = generateContentFingerprint(text, ctx.dedupKey);
|
|
297
307
|
|
|
298
|
-
const
|
|
308
|
+
const encryptedBlob = toHexBlob(blob, ctx.encryptionKey);
|
|
309
|
+
const blindIndices = generateBlindIndices(text);
|
|
310
|
+
const contentFp = generateContentFingerprint(text, ctx.dedupKey);
|
|
311
|
+
|
|
312
|
+
// On-chain submission: encode protobuf, build SubgraphStoreConfig (auth +
|
|
313
|
+
// wallet), submit a single-fact UserOp through the relay bundler. The
|
|
314
|
+
// subgraph indexes the resulting Log(bytes) event so it is recall-able
|
|
315
|
+
// within ~5-15 s of the receipt.
|
|
316
|
+
const fact: FactPayload = {
|
|
299
317
|
id: factId,
|
|
300
318
|
timestamp: now,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
319
|
+
owner: ctx.walletAddress,
|
|
320
|
+
encryptedBlob,
|
|
321
|
+
blindIndices,
|
|
322
|
+
decayScore: 8,
|
|
304
323
|
source: 'cli:tr-remember',
|
|
305
|
-
|
|
324
|
+
contentFp,
|
|
325
|
+
agentId: 'tr-cli',
|
|
326
|
+
version: PROTOBUF_VERSION_V4,
|
|
306
327
|
};
|
|
307
328
|
|
|
308
329
|
try {
|
|
309
|
-
|
|
330
|
+
const protobuf = encodeFactProtobuf(fact);
|
|
331
|
+
const config = {
|
|
332
|
+
...getSubgraphConfig(),
|
|
333
|
+
authKeyHex: ctx.authKeyHex,
|
|
334
|
+
walletAddress: ctx.walletAddress,
|
|
335
|
+
};
|
|
336
|
+
const result = await submitFactBatchOnChain([protobuf], config);
|
|
337
|
+
|
|
338
|
+
if (!result.success) {
|
|
339
|
+
die(
|
|
340
|
+
`remember failed: on-chain UserOp did not succeed (userOpHash=${
|
|
341
|
+
result.userOpHash || 'none'
|
|
342
|
+
})`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
310
346
|
if (jsonMode) {
|
|
311
|
-
// JSON-first output for agent parsing
|
|
312
|
-
// claim_count
|
|
313
|
-
|
|
347
|
+
// JSON-first output for agent parsing.
|
|
348
|
+
// claim_count = 1 here (single fact stored). Computing the full vault
|
|
349
|
+
// count would require an extra subgraph query on every remember and
|
|
350
|
+
// isn't worth the latency.
|
|
351
|
+
log(JSON.stringify({ ok: true, id: factId, claim_count: 1 }));
|
|
314
352
|
} else {
|
|
315
|
-
log(`ok — stored memory (id=${factId})`);
|
|
353
|
+
log(`ok — stored memory (id=${factId}, tx=${result.txHash || 'pending'})`);
|
|
316
354
|
}
|
|
317
355
|
} catch (err) {
|
|
318
356
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -321,67 +359,115 @@ async function cmdRemember(rawArgs: string[]): Promise<void> {
|
|
|
321
359
|
}
|
|
322
360
|
|
|
323
361
|
// ---------------------------------------------------------------------------
|
|
324
|
-
// Command:
|
|
362
|
+
// Command: forget
|
|
325
363
|
// ---------------------------------------------------------------------------
|
|
326
364
|
|
|
327
|
-
async function
|
|
328
|
-
const [jsonMode,
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
365
|
+
async function cmdForget(rawArgs: string[]): Promise<void> {
|
|
366
|
+
const [jsonMode, args] = popFlag(rawArgs, '--json');
|
|
367
|
+
const factId = (args[0] ?? '').trim();
|
|
368
|
+
if (!factId) {
|
|
369
|
+
die('Usage: tr forget [--json] <factId>');
|
|
370
|
+
}
|
|
371
|
+
// UUID-v4-ish shape check — same validation the old totalreclaw_forget
|
|
372
|
+
// tool applied. Prevents fabricated / natural-language IDs from reaching
|
|
373
|
+
// the UserOp path and silently no-op'ing on-chain.
|
|
374
|
+
if (!/^[0-9a-f-]{8,}$/i.test(factId)) {
|
|
375
|
+
die(
|
|
376
|
+
`forget failed: "${factId.slice(0, 60)}" doesn't look like a memory ID. ` +
|
|
377
|
+
`Ask the agent to look it up via memory_search (or tr export) and pass a result's id.`,
|
|
378
|
+
);
|
|
333
379
|
}
|
|
334
380
|
|
|
335
381
|
const ctx = await buildContext();
|
|
336
382
|
|
|
337
|
-
//
|
|
338
|
-
|
|
383
|
+
// Tombstone shape (pin/unpin & native forget use the same one — see
|
|
384
|
+
// index.ts:4253-4267 + pin.ts:611-621). Deliberately NO version field
|
|
385
|
+
// → uses legacy v3 default so the subgraph's contradiction handler
|
|
386
|
+
// matches and flips isActive=false.
|
|
387
|
+
const tombstone: FactPayload = {
|
|
388
|
+
id: factId,
|
|
389
|
+
timestamp: new Date().toISOString(),
|
|
390
|
+
owner: ctx.walletAddress,
|
|
391
|
+
encryptedBlob: '00',
|
|
392
|
+
blindIndices: [],
|
|
393
|
+
decayScore: 0,
|
|
394
|
+
source: 'tombstone',
|
|
395
|
+
contentFp: '',
|
|
396
|
+
agentId: 'tr-cli',
|
|
397
|
+
// No `version` → legacy v3 (matches pin/unpin & native forget).
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const protobuf = encodeFactProtobuf(tombstone);
|
|
402
|
+
const config = {
|
|
403
|
+
...getSubgraphConfig(),
|
|
404
|
+
authKeyHex: ctx.authKeyHex,
|
|
405
|
+
walletAddress: ctx.walletAddress,
|
|
406
|
+
};
|
|
407
|
+
const result = await submitFactBatchOnChain([protobuf], config);
|
|
408
|
+
|
|
409
|
+
if (!result.success) {
|
|
410
|
+
die(
|
|
411
|
+
`forget failed: on-chain tombstone did not succeed (userOpHash=${
|
|
412
|
+
result.userOpHash || 'none'
|
|
413
|
+
})`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
339
416
|
|
|
340
|
-
if (trapdoors.length === 0) {
|
|
341
417
|
if (jsonMode) {
|
|
342
|
-
log(JSON.stringify({
|
|
418
|
+
log(JSON.stringify({ ok: true, id: factId, tx_hash: result.txHash }));
|
|
343
419
|
} else {
|
|
344
|
-
log(
|
|
420
|
+
log(`ok — tombstoned ${factId} (tx=${result.txHash || 'pending'})`);
|
|
345
421
|
}
|
|
346
|
-
|
|
422
|
+
} catch (err) {
|
|
423
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
424
|
+
die(`forget failed: ${msg}`);
|
|
347
425
|
}
|
|
426
|
+
}
|
|
348
427
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const results: Array<{ text: string; score: number }> = [];
|
|
353
|
-
|
|
354
|
-
for (const c of candidates) {
|
|
355
|
-
try {
|
|
356
|
-
const raw = decrypt(c.encrypted_blob, ctx.encryptionKey);
|
|
357
|
-
const parsed = JSON.parse(raw) as { text?: string };
|
|
358
|
-
if (parsed.text) {
|
|
359
|
-
results.push({
|
|
360
|
-
text: parsed.text,
|
|
361
|
-
score: c.decay_score,
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
} catch {
|
|
365
|
-
// Skip undecryptable
|
|
366
|
-
}
|
|
367
|
-
}
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
// Command: export
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
368
431
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
432
|
+
async function cmdExport(rawArgs: string[]): Promise<void> {
|
|
433
|
+
const [jsonMode, argsAfterJson] = popFlag(rawArgs, '--json');
|
|
434
|
+
const [format, _argsAfterFormat] = popOptionFlag(argsAfterJson, '--format', 'json');
|
|
372
435
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
436
|
+
if (format !== 'json' && format !== 'markdown') {
|
|
437
|
+
die('Usage: tr export [--json] [--format json|markdown]');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const ctx = await buildContext();
|
|
441
|
+
|
|
442
|
+
// Delegate the subgraph paginate + decrypt loop to a helper module —
|
|
443
|
+
// tr-cli.ts already includes `fs.readFileSync` (status command), and
|
|
444
|
+
// adding outbound HTTP here would trip the OpenClaw scanner's
|
|
445
|
+
// potential-exfiltration rule. See tr-cli-export-helper.ts.
|
|
446
|
+
const allFacts = await exportAllFacts(
|
|
447
|
+
ctx.walletAddress,
|
|
448
|
+
ctx.authKeyHex,
|
|
449
|
+
ctx.encryptionKey,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
if (format === 'markdown') {
|
|
453
|
+
if (allFacts.length === 0) {
|
|
454
|
+
log('*No memories stored.*');
|
|
376
455
|
} else {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
456
|
+
const lines = allFacts.map((f, i) => {
|
|
457
|
+
const meta = f.metadata;
|
|
458
|
+
const type = (meta.type as string) ?? 'fact';
|
|
459
|
+
return `${i + 1}. **[${type}]** ${f.text} \n _ID: ${f.id} | Created: ${f.created_at}_`;
|
|
460
|
+
});
|
|
461
|
+
log(`# Exported Memories (${allFacts.length})\n\n${lines.join('\n')}`);
|
|
381
462
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// json format (default — both --json mode and --format=json end up here)
|
|
467
|
+
if (jsonMode) {
|
|
468
|
+
log(JSON.stringify({ count: allFacts.length, facts: allFacts }));
|
|
469
|
+
} else {
|
|
470
|
+
log(JSON.stringify(allFacts, null, 2));
|
|
385
471
|
}
|
|
386
472
|
}
|
|
387
473
|
|
|
@@ -409,27 +495,71 @@ async function main(): Promise<void> {
|
|
|
409
495
|
break;
|
|
410
496
|
|
|
411
497
|
case 'recall':
|
|
412
|
-
|
|
498
|
+
// Retired in Phase 3.3 — recall is now native via the bundled
|
|
499
|
+
// memory_search / memory_get tools (MemoryPluginCapability). Surface
|
|
500
|
+
// a clear pointer instead of falling through to "unknown command"
|
|
501
|
+
// so agents / users running stale prompts get actionable guidance.
|
|
502
|
+
die(
|
|
503
|
+
'tr recall was retired — recall is now native. ' +
|
|
504
|
+
'The agent reads memories via the memory_search tool automatically; ' +
|
|
505
|
+
'use `tr export` to dump every memory outside the agent.',
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
case 'forget':
|
|
509
|
+
await cmdForget(args.slice(1));
|
|
413
510
|
break;
|
|
414
511
|
|
|
512
|
+
case 'export':
|
|
513
|
+
await cmdExport(args.slice(1));
|
|
514
|
+
break;
|
|
515
|
+
|
|
516
|
+
case 'import':
|
|
517
|
+
case 'upgrade':
|
|
518
|
+
// Import + upgrade run inside the gateway process (they need the
|
|
519
|
+
// plugin runtime — extraction pipeline, smart-import WASM, module-
|
|
520
|
+
// level auth state). The standalone `tr` binary is a separate Node
|
|
521
|
+
// script that does NOT load index.ts (would pull in the entire
|
|
522
|
+
// plugin runtime). Point users at the registerCli surface instead
|
|
523
|
+
// of silently no-op'ing or falling through to "unknown command".
|
|
524
|
+
die(
|
|
525
|
+
`${cmd} is not available on the standalone \`tr\` binary. ` +
|
|
526
|
+
`Run it on the gateway host via:\n` +
|
|
527
|
+
` openclaw totalreclaw ${cmd === 'import' ? 'import from <source>' : 'upgrade'}${cmd === 'import' ? ' [--file <path>] [--dry-run] [--json]' : ' [--json]'}\n` +
|
|
528
|
+
(cmd === 'import'
|
|
529
|
+
? ` openclaw totalreclaw import status [--id <importId>] [--json]\n` +
|
|
530
|
+
` openclaw totalreclaw import abort <importId> [--json]\n` +
|
|
531
|
+
`Sources: mem0 | mcp-memory | chatgpt | claude | gemini`
|
|
532
|
+
: `Returns a Stripe checkout URL for Pro upgrade.`),
|
|
533
|
+
);
|
|
534
|
+
|
|
415
535
|
case undefined:
|
|
416
536
|
case '--help':
|
|
417
537
|
case '-h':
|
|
418
538
|
process.stdout.write(
|
|
419
|
-
`TotalReclaw
|
|
539
|
+
`TotalReclaw CLI v${PLUGIN_VERSION} (recall is native — memory_search tool)\n\n` +
|
|
420
540
|
'Usage:\n' +
|
|
421
|
-
' tr status [--json]
|
|
422
|
-
' tr pair [--json]
|
|
423
|
-
' tr remember [--json] <text>
|
|
424
|
-
' tr
|
|
541
|
+
' tr status [--json] — onboarding + plugin load state\n' +
|
|
542
|
+
' tr pair [--json] — start a relay pairing session\n' +
|
|
543
|
+
' tr remember [--json] <text> — store a memory (on-chain UserOp)\n' +
|
|
544
|
+
' tr forget [--json] <factId> — tombstone a memory on-chain\n' +
|
|
545
|
+
' tr export [--json] [--format json|markdown] — dump every memory in the vault\n\n' +
|
|
546
|
+
'Recall: NOT a CLI command. The agent recalls via the bundled memory_search tool.\n' +
|
|
547
|
+
' To dump memories outside the agent, use `tr export`.\n\n' +
|
|
548
|
+
'Import + Upgrade: NOT on the standalone `tr` binary. They run inside the gateway\n' +
|
|
549
|
+
' process via the `openclaw totalreclaw` subcommand chain:\n' +
|
|
550
|
+
' openclaw totalreclaw import from <source> [--file <path>] [--dry-run] [--json]\n' +
|
|
551
|
+
' openclaw totalreclaw import status [--id <importId>] [--json]\n' +
|
|
552
|
+
' openclaw totalreclaw import abort <importId> [--json]\n' +
|
|
553
|
+
' openclaw totalreclaw upgrade [--json]\n' +
|
|
554
|
+
' Sources: mem0 | mcp-memory | chatgpt | claude | gemini\n\n' +
|
|
425
555
|
'Flags:\n' +
|
|
426
|
-
' --json Output machine-parseable JSON (required for agent shell calls)\n' +
|
|
427
|
-
' --limit N Limit recall results (default: 5)\n\n' +
|
|
556
|
+
' --json Output machine-parseable JSON (required for agent shell calls)\n\n' +
|
|
428
557
|
'JSON output shapes:\n' +
|
|
429
558
|
' status: {"version":"...","onboarded":bool,"next_step":"pair|none","tool_count":N,"hybrid_mode":bool}\n' +
|
|
430
559
|
' pair: {"url":"...","pin":"123456","expires_at":"..."}\n' +
|
|
431
560
|
' remember: {"ok":true,"id":"...","claim_count":N}\n' +
|
|
432
|
-
'
|
|
561
|
+
' forget: {"ok":true,"id":"...","tx_hash":"0x..."}\n' +
|
|
562
|
+
' export: {"count":N,"facts":[{"id":"...","text":"...","metadata":{...},"created_at":"..."}]}\n\n' +
|
|
433
563
|
'Environment:\n' +
|
|
434
564
|
' TOTALRECLAW_SERVER_URL — relay URL (default: api.totalreclaw.xyz; staging: api-staging.totalreclaw.xyz)\n' +
|
|
435
565
|
' TOTALRECLAW_CREDENTIALS_PATH — override credentials.json path\n',
|