mr-memory 3.9.0 → 3.10.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/README.md +13 -0
- package/import-transport.js +457 -0
- package/import-transport.ts +511 -0
- package/index.js +52 -35
- package/index.ts +51 -36
- package/memoryrouter-api.js +51 -0
- package/memoryrouter-api.ts +59 -0
- package/package.json +3 -1
package/index.ts
CHANGED
|
@@ -65,7 +65,7 @@ function jsonToolResult(payload: unknown) {
|
|
|
65
65
|
return { content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }], details: payload };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// ── Reflection debt nudge (
|
|
68
|
+
// ── Reflection debt nudge (one line, appended to prepare injections whenever the server suggests it)
|
|
69
69
|
type ReflectionDebt = {
|
|
70
70
|
suggestion?: string | null;
|
|
71
71
|
unconsolidated?: {
|
|
@@ -75,15 +75,22 @@ type ReflectionDebt = {
|
|
|
75
75
|
threshold_tokens?: number;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
const NUDGE_INTERVAL_MS = 24 * 60 * 60 * 1000; // at most one nudge per 24h
|
|
79
|
-
|
|
80
78
|
function buildDebtNudgeLine(reflection: ReflectionDebt | undefined): string | null {
|
|
81
79
|
if (!reflection || !reflection.suggestion) return null;
|
|
82
80
|
const rawTokens = reflection.unconsolidated?.raw?.est_tokens ?? 0;
|
|
83
81
|
const tier2Tokens = reflection.unconsolidated?.tier2?.est_tokens ?? 0;
|
|
84
82
|
const totalTokens = rawTokens + tier2Tokens;
|
|
85
83
|
const kTokens = Math.max(1, Math.round(totalTokens / 1000));
|
|
86
|
-
return `[MemoryRouter] Consolidation
|
|
84
|
+
return `[MemoryRouter] Consolidation suggested: ${kTokens}k unconsolidated tokens. Use the memory_reflect tool to consolidate them now.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Historical import nudge (server-owned import.suggested block; additive,
|
|
88
|
+
// no rate limit, clears server-side on import completion or dismiss)
|
|
89
|
+
type ImportSuggestedState = { suggested?: boolean; platform_hint?: string | null; suggestion?: string };
|
|
90
|
+
|
|
91
|
+
function buildImportNudgeLine(importState: ImportSuggestedState | undefined): string | null {
|
|
92
|
+
if (!importState || !importState.suggested) return null;
|
|
93
|
+
return `[MemoryRouter] Historical import suggested: this machine has local conversation history not yet in your vault. Run: openclaw mr import --instructions`;
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
// ── memory_get helpers (copied from OpenClaw source — pure filesystem, no embeddings)
|
|
@@ -354,36 +361,6 @@ async function writeOpenClawConfigFile(config: JsonObject): Promise<void> {
|
|
|
354
361
|
`, "utf8");
|
|
355
362
|
}
|
|
356
363
|
|
|
357
|
-
// Nudge rate-limit state lives beside the OpenClaw config so it survives restarts
|
|
358
|
-
// without churning the main config file.
|
|
359
|
-
function nudgeStatePath(): string {
|
|
360
|
-
return join(dirname(openClawConfigPath()), "mr-memory-state.json");
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async function consumeNudgeBudget(): Promise<boolean> {
|
|
364
|
-
const now = Date.now();
|
|
365
|
-
let state: { lastNudgeAt?: number } = {};
|
|
366
|
-
try {
|
|
367
|
-
state = JSON.parse(await readFile(nudgeStatePath(), "utf8"));
|
|
368
|
-
} catch { /* missing or unreadable state: treat as never nudged */ }
|
|
369
|
-
if (typeof state.lastNudgeAt === "number" && now - state.lastNudgeAt < NUDGE_INTERVAL_MS) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
try {
|
|
373
|
-
await writeFile(nudgeStatePath(), `${JSON.stringify({ ...state, lastNudgeAt: now }, null, 2)}\n`, "utf8");
|
|
374
|
-
} catch { /* state write failure must never break prepare */ }
|
|
375
|
-
return true;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/** One-line, rate-limited debt nudge for prepare injections. Returns null when
|
|
379
|
-
* there is no suggestion or the 24h budget is spent. */
|
|
380
|
-
async function maybeDebtNudge(reflection: ReflectionDebt | undefined): Promise<string | null> {
|
|
381
|
-
const line = buildDebtNudgeLine(reflection);
|
|
382
|
-
if (!line) return null;
|
|
383
|
-
if (!(await consumeNudgeBudget())) return null;
|
|
384
|
-
return line;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
364
|
// ──────────────────────────────────────────────────────
|
|
388
365
|
// Full-replace: disable OpenClaw's native memory search so MemoryRouter
|
|
389
366
|
// owns the memory_search tool (no name conflict, no native embeddings 429).
|
|
@@ -820,6 +797,7 @@ If setting up MemoryRouter for another agent:
|
|
|
820
797
|
memory_tokens?: number;
|
|
821
798
|
metrics?: Record<string, unknown>;
|
|
822
799
|
reflection?: ReflectionDebt;
|
|
800
|
+
import?: ImportSuggestedState;
|
|
823
801
|
};
|
|
824
802
|
|
|
825
803
|
if (data.context) {
|
|
@@ -827,8 +805,10 @@ If setting up MemoryRouter for another agent:
|
|
|
827
805
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
828
806
|
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
829
807
|
let contextText = data.context;
|
|
830
|
-
const nudge =
|
|
808
|
+
const nudge = buildDebtNudgeLine(data.reflection);
|
|
831
809
|
if (nudge) contextText = `${contextText}\n\n${nudge}`;
|
|
810
|
+
const importNudge = buildImportNudgeLine(data.import);
|
|
811
|
+
if (importNudge) contextText = `${contextText}\n\n${importNudge}`;
|
|
832
812
|
const wrapped = wrapForInjection(contextText);
|
|
833
813
|
return { appendSystemContext: wrapped };
|
|
834
814
|
}
|
|
@@ -941,6 +921,7 @@ If setting up MemoryRouter for another agent:
|
|
|
941
921
|
memory_tokens?: number;
|
|
942
922
|
metrics?: Record<string, unknown>;
|
|
943
923
|
reflection?: ReflectionDebt;
|
|
924
|
+
import?: ImportSuggestedState;
|
|
944
925
|
};
|
|
945
926
|
|
|
946
927
|
if (data.context) {
|
|
@@ -948,8 +929,10 @@ If setting up MemoryRouter for another agent:
|
|
|
948
929
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
949
930
|
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
950
931
|
let contextText = data.context;
|
|
951
|
-
const nudge =
|
|
932
|
+
const nudge = buildDebtNudgeLine(data.reflection);
|
|
952
933
|
if (nudge) contextText = `${contextText}\n\n${nudge}`;
|
|
934
|
+
const importNudge = buildImportNudgeLine(data.import);
|
|
935
|
+
if (importNudge) contextText = `${contextText}\n\n${importNudge}`;
|
|
953
936
|
const wrapped = wrapForInjection(contextText);
|
|
954
937
|
return { appendSystemContext: wrapped };
|
|
955
938
|
}
|
|
@@ -1682,6 +1665,38 @@ If setting up MemoryRouter for another agent:
|
|
|
1682
1665
|
}
|
|
1683
1666
|
});
|
|
1684
1667
|
|
|
1668
|
+
mr.command("import")
|
|
1669
|
+
.description("Import historical OpenClaw conversations into your vault (agent-driven extraction + CLI transport)")
|
|
1670
|
+
.option("--instructions", "Fetch and print the server's extraction prompt for the host agent to execute")
|
|
1671
|
+
.option("--file <path>", "Upload a JSONL extract: preview, price quote, explicit approval, idempotent batches, resume")
|
|
1672
|
+
.option("--dismiss", "Permanently silence the import suggestion for this vault")
|
|
1673
|
+
.option("--yes", "Skip the interactive approval prompt (required for non-interactive --file runs)")
|
|
1674
|
+
.option("--key <key>", "Override memory key (for subagent vaults)")
|
|
1675
|
+
.action(async (opts: { instructions?: boolean; file?: string; dismiss?: boolean; yes?: boolean; key?: string }) => {
|
|
1676
|
+
const effectiveKey = opts.key || process.env.MEMORYROUTER_API_KEY || memoryKey;
|
|
1677
|
+
if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
|
|
1678
|
+
const modes = [Boolean(opts.instructions), opts.file !== undefined, Boolean(opts.dismiss)].filter(Boolean).length;
|
|
1679
|
+
if (modes > 1) { console.error("Choose one of --instructions, --file <path>, or --dismiss"); return; }
|
|
1680
|
+
try {
|
|
1681
|
+
const transport = await import("./import-transport.js");
|
|
1682
|
+
if (opts.dismiss) {
|
|
1683
|
+
await transport.runDismiss({ endpoint, key: effectiveKey });
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (opts.file) {
|
|
1687
|
+
await transport.runImportFile({ endpoint, key: effectiveKey, file: opts.file, yes: Boolean(opts.yes) });
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
await transport.runInstructions({ endpoint, key: effectiveKey });
|
|
1691
|
+
console.log("");
|
|
1692
|
+
console.log("Execute the prompt above with your agent, then upload the extract:");
|
|
1693
|
+
console.log(" openclaw mr import --file <path>");
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
console.error(`Import failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1696
|
+
process.exitCode = 1;
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1685
1700
|
mr.command("reflect")
|
|
1686
1701
|
.description("Show reflection debt status (consolidation runs via the agent's memory_reflect tool)")
|
|
1687
1702
|
.option("--key <key>", "Override memory key (for subagent vaults)")
|
package/memoryrouter-api.js
CHANGED
|
@@ -115,6 +115,57 @@ export async function deleteMemories(endpoint, key, embeddings) {
|
|
|
115
115
|
headers: { Authorization: `Bearer ${key}` },
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
|
+
// ── Historical import wrappers (agent-driven import) ────────────────────────
|
|
119
|
+
// The server owns the extraction prompt; the CLI owns transport. The imports
|
|
120
|
+
// API is fully idempotent: create/approve/batches are safe to re-run.
|
|
121
|
+
export async function importInstructions(endpoint, key, platform, outputPath) {
|
|
122
|
+
const query = `platform=${encodeURIComponent(platform)}` + (outputPath ? `&output_path=${encodeURIComponent(outputPath)}` : "");
|
|
123
|
+
return fetch(`${endpoint}/v1/memory/imports/instructions?${query}`, {
|
|
124
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export async function importCreate(endpoint, key, body) {
|
|
128
|
+
return fetch(`${endpoint}/v1/memory/imports`, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
131
|
+
body: JSON.stringify(body),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export async function importApprove(endpoint, key, importId, approvalDigest) {
|
|
135
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/approve`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
138
|
+
body: JSON.stringify({ approval_digest: approvalDigest }),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
export async function importBatch(endpoint, key, importId, batchIndex, jsonl, idempotencyKey) {
|
|
142
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/batches/${batchIndex}`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${key}`,
|
|
146
|
+
"Content-Type": "application/x-ndjson",
|
|
147
|
+
"Idempotency-Key": idempotencyKey,
|
|
148
|
+
},
|
|
149
|
+
body: jsonl,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export async function importStatus(endpoint, key, importId) {
|
|
153
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}`, {
|
|
154
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export async function importReceipt(endpoint, key, importId) {
|
|
158
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/receipt`, {
|
|
159
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export async function importDismiss(endpoint, key, platform) {
|
|
163
|
+
return fetch(`${endpoint}/v1/memory/imports/dismiss-suggestion`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
166
|
+
body: JSON.stringify({ platform }),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
118
169
|
export async function warmupMemory(endpoint, key, embeddings) {
|
|
119
170
|
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
120
171
|
method: "POST",
|
package/memoryrouter-api.ts
CHANGED
|
@@ -132,6 +132,65 @@ export async function deleteMemories(endpoint: string, key: string, embeddings?:
|
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// ── Historical import wrappers (agent-driven import) ────────────────────────
|
|
136
|
+
// The server owns the extraction prompt; the CLI owns transport. The imports
|
|
137
|
+
// API is fully idempotent: create/approve/batches are safe to re-run.
|
|
138
|
+
|
|
139
|
+
export async function importInstructions(endpoint: string, key: string, platform: string, outputPath?: string): Promise<Response> {
|
|
140
|
+
const query = `platform=${encodeURIComponent(platform)}` + (outputPath ? `&output_path=${encodeURIComponent(outputPath)}` : "");
|
|
141
|
+
return fetch(`${endpoint}/v1/memory/imports/instructions?${query}`, {
|
|
142
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function importCreate(endpoint: string, key: string, body: Record<string, unknown>): Promise<Response> {
|
|
147
|
+
return fetch(`${endpoint}/v1/memory/imports`, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function importApprove(endpoint: string, key: string, importId: string, approvalDigest: string): Promise<Response> {
|
|
155
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/approve`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
158
|
+
body: JSON.stringify({ approval_digest: approvalDigest }),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function importBatch(endpoint: string, key: string, importId: string, batchIndex: number, jsonl: string, idempotencyKey: string): Promise<Response> {
|
|
163
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/batches/${batchIndex}`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${key}`,
|
|
167
|
+
"Content-Type": "application/x-ndjson",
|
|
168
|
+
"Idempotency-Key": idempotencyKey,
|
|
169
|
+
},
|
|
170
|
+
body: jsonl,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function importStatus(endpoint: string, key: string, importId: string): Promise<Response> {
|
|
175
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}`, {
|
|
176
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function importReceipt(endpoint: string, key: string, importId: string): Promise<Response> {
|
|
181
|
+
return fetch(`${endpoint}/v1/memory/imports/${encodeURIComponent(importId)}/receipt`, {
|
|
182
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function importDismiss(endpoint: string, key: string, platform: string): Promise<Response> {
|
|
187
|
+
return fetch(`${endpoint}/v1/memory/imports/dismiss-suggestion`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: { ...JSON_HEADERS, Authorization: `Bearer ${key}` },
|
|
190
|
+
body: JSON.stringify({ platform }),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
135
194
|
export async function warmupMemory(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
136
195
|
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
137
196
|
method: "POST",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-memory",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Persistent memory for OpenClaw, powered by MemoryRouter. Relay-based AI agent memory across sessions, compaction, and model changes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"upload.ts",
|
|
12
12
|
"memoryrouter-api.js",
|
|
13
13
|
"memoryrouter-api.ts",
|
|
14
|
+
"import-transport.js",
|
|
15
|
+
"import-transport.ts",
|
|
14
16
|
"upload-api.js",
|
|
15
17
|
"upload-api.ts",
|
|
16
18
|
"openclaw.plugin.json"
|