@matrixorigin/thememoria 0.4.0 → 0.4.1

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.
@@ -0,0 +1,453 @@
1
+ /**
2
+ * MemoriaHttpTransport — direct HTTP client for the Memoria REST API.
3
+ *
4
+ * Replaces the MCP stdio bridge (MemoriaMcpSession) when backend === "api".
5
+ * No Rust binary needed. Uses native fetch().
6
+ */
7
+
8
+ import type { MemoriaPluginConfig } from "./config.js";
9
+
10
+ export class MemoriaHttpTransport {
11
+ private readonly apiUrl: string;
12
+ private readonly apiKey: string;
13
+ private readonly timeoutMs: number;
14
+
15
+ constructor(config: MemoriaPluginConfig, private readonly userId: string) {
16
+ if (!config.apiUrl) {
17
+ throw new Error("apiUrl is required for api backend mode");
18
+ }
19
+ if (!config.apiKey) {
20
+ throw new Error("apiKey is required for api backend mode");
21
+ }
22
+ this.apiUrl = config.apiUrl.replace(/\/+$/, "");
23
+ this.apiKey = config.apiKey;
24
+ this.timeoutMs = config.timeoutMs;
25
+ }
26
+
27
+ /** Matches MemoriaMcpSession.isAlive() — HTTP transport is always "alive". */
28
+ isAlive(): boolean {
29
+ return true;
30
+ }
31
+
32
+ /** No-op for HTTP transport (no child process to kill). */
33
+ close(): void {}
34
+
35
+ /**
36
+ * Unified entry point that mirrors MemoriaMcpSession.callTool().
37
+ * Maps MCP tool names to REST API calls and returns the same
38
+ * text-content structure the MCP session would return.
39
+ */
40
+ async callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
41
+ const result = await this.dispatch(name, args);
42
+ // Wrap in MCP-compatible content block so extractToolText() works unchanged
43
+ return { content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result) }] };
44
+ }
45
+
46
+ // ── REST dispatch ──────────────────────────────────────────────
47
+
48
+ private async dispatch(tool: string, args: Record<string, unknown>): Promise<unknown> {
49
+ switch (tool) {
50
+ case "memory_store":
51
+ return this.store(args);
52
+ case "memory_retrieve":
53
+ return this.retrieve(args);
54
+ case "memory_search":
55
+ return this.search(args);
56
+ case "memory_list":
57
+ return this.list(args);
58
+ case "memory_profile":
59
+ return this.profile();
60
+ case "memory_correct":
61
+ return this.correct(args);
62
+ case "memory_purge":
63
+ return this.purge(args);
64
+ case "memory_observe":
65
+ return this.observe(args);
66
+ case "memory_governance":
67
+ return this.governance(args);
68
+ case "memory_consolidate":
69
+ return this.consolidate(args);
70
+ case "memory_reflect":
71
+ return this.reflect(args);
72
+ case "memory_extract_entities":
73
+ return this.extractEntities(args);
74
+ case "memory_link_entities":
75
+ return this.linkEntities(args);
76
+ case "memory_rebuild_index":
77
+ return { message: "rebuild_index is managed by the cloud service and not available via API." };
78
+ case "memory_snapshot":
79
+ return this.createSnapshot(args);
80
+ case "memory_snapshots":
81
+ return this.listSnapshots(args);
82
+ case "memory_rollback":
83
+ return this.rollbackSnapshot(args);
84
+ case "memory_branch":
85
+ return this.branchCreate(args);
86
+ case "memory_branches":
87
+ return this.branchList();
88
+ case "memory_checkout":
89
+ return this.branchCheckout(args);
90
+ case "memory_branch_delete":
91
+ return this.branchDelete(args);
92
+ case "memory_merge":
93
+ return this.branchMerge(args);
94
+ case "memory_diff":
95
+ return this.branchDiff(args);
96
+ default:
97
+ throw new Error(`Unknown Memoria tool: ${tool}`);
98
+ }
99
+ }
100
+
101
+ // ── Memory CRUD ────────────────────────────────────────────────
102
+
103
+ private async store(args: Record<string, unknown>) {
104
+ const body: Record<string, unknown> = { content: args.content };
105
+ if (args.memory_type) body.memory_type = args.memory_type;
106
+ if (args.session_id) body.session_id = args.session_id;
107
+ if (args.trust_tier) body.trust_tier = args.trust_tier;
108
+ const data = await this.post("/v1/memories", body);
109
+ const rec = this.asRecord(data);
110
+ const id = rec?.memory_id ?? "";
111
+ const content = rec?.content ?? args.content ?? "";
112
+ return `Stored memory ${id}: ${content}`;
113
+ }
114
+
115
+ private async retrieve(args: Record<string, unknown>) {
116
+ const body: Record<string, unknown> = {
117
+ query: args.query,
118
+ top_k: args.top_k ?? 5,
119
+ };
120
+ if (args.session_id) body.session_id = args.session_id;
121
+ const data = await this.post("/v1/memories/retrieve", body);
122
+ return this.formatMemoryList(data);
123
+ }
124
+
125
+ private async search(args: Record<string, unknown>) {
126
+ const body: Record<string, unknown> = {
127
+ query: args.query,
128
+ top_k: args.top_k ?? 10,
129
+ };
130
+ const data = await this.post("/v1/memories/search", body);
131
+ return this.formatMemoryList(data);
132
+ }
133
+
134
+ private async list(args: Record<string, unknown>) {
135
+ const limit = typeof args.limit === "number" ? args.limit : 100;
136
+ const data = await this.get(`/v1/memories?limit=${limit}`);
137
+ const rec = this.asRecord(data);
138
+ const items = Array.isArray(rec?.items) ? rec!.items : Array.isArray(data) ? data : [];
139
+ return this.formatMemoryItems(items);
140
+ }
141
+
142
+ private async profile() {
143
+ const data = await this.get("/v1/profiles/me");
144
+ const rec = this.asRecord(data);
145
+ if (rec?.profile && typeof rec.profile === "string") {
146
+ return rec.profile;
147
+ }
148
+ return "No profile memories found.";
149
+ }
150
+
151
+ private async correct(args: Record<string, unknown>) {
152
+ // Correct by ID or by query
153
+ if (args.memory_id && typeof args.memory_id === "string") {
154
+ const body: Record<string, unknown> = {
155
+ new_content: args.new_content,
156
+ };
157
+ if (args.reason) body.reason = args.reason;
158
+ const data = await this.put(`/v1/memories/${args.memory_id}/correct`, body);
159
+ const rec = this.asRecord(data);
160
+ return `Corrected memory ${rec?.memory_id ?? args.memory_id}: ${rec?.content ?? args.new_content}`;
161
+ }
162
+ // Correct by query
163
+ const body: Record<string, unknown> = {
164
+ query: args.query,
165
+ new_content: args.new_content,
166
+ };
167
+ if (args.reason) body.reason = args.reason;
168
+ const data = await this.post("/v1/memories/correct", body);
169
+ const rec = this.asRecord(data);
170
+ return `Corrected memory ${rec?.memory_id ?? ""}: ${rec?.content ?? args.new_content}`;
171
+ }
172
+
173
+ private async purge(args: Record<string, unknown>) {
174
+ const body: Record<string, unknown> = {};
175
+ if (args.memory_id) body.memory_ids = [args.memory_id];
176
+ if (args.topic) body.topic = args.topic;
177
+ if (args.reason) body.reason = args.reason;
178
+ const data = await this.post("/v1/memories/purge", body);
179
+ const rec = this.asRecord(data);
180
+ const count = typeof rec?.purged === "number" ? rec.purged : 0;
181
+ return `Purged ${count} memory(ies).`;
182
+ }
183
+
184
+ private async observe(args: Record<string, unknown>) {
185
+ const body: Record<string, unknown> = {
186
+ messages: args.messages,
187
+ };
188
+ if (args.session_id) body.session_id = args.session_id;
189
+ const data = await this.post("/v1/observe", body);
190
+ return JSON.stringify(data);
191
+ }
192
+
193
+ // ── Governance / Graph ──────────────────────────────────────────
194
+
195
+ private async governance(args: Record<string, unknown>) {
196
+ return this.post("/v1/governance", { force: args.force ?? false });
197
+ }
198
+
199
+ private async consolidate(args: Record<string, unknown>) {
200
+ return this.post("/v1/consolidate", { force: args.force ?? false });
201
+ }
202
+
203
+ private async reflect(args: Record<string, unknown>) {
204
+ return this.post("/v1/reflect", {
205
+ force: args.force ?? false,
206
+ mode: args.mode ?? "auto",
207
+ });
208
+ }
209
+
210
+ private async extractEntities(args: Record<string, unknown>) {
211
+ return this.post("/v1/extract-entities", { mode: args.mode ?? "auto" });
212
+ }
213
+
214
+ private async linkEntities(args: Record<string, unknown>) {
215
+ let entities = args.entities;
216
+ if (typeof entities === "string") {
217
+ try { entities = JSON.parse(entities); } catch { /* keep as-is */ }
218
+ }
219
+ return this.post("/v1/extract-entities/link", { entities });
220
+ }
221
+
222
+ // ── Snapshots ─────────────────────────────────────────────────
223
+
224
+ private async createSnapshot(args: Record<string, unknown>) {
225
+ const body: Record<string, unknown> = { name: args.name };
226
+ if (args.description) body.description = args.description;
227
+ const data = await this.post("/v1/snapshots", body);
228
+ const rec = this.asRecord(data);
229
+ const name = rec?.name ?? args.name ?? "";
230
+ const ts = rec?.timestamp ?? rec?.created_at ?? "";
231
+ return `Snapshot '${name}' created at ${ts}`;
232
+ }
233
+
234
+ private async listSnapshots(args: Record<string, unknown>) {
235
+ const limit = typeof args.limit === "number" ? args.limit : 20;
236
+ const offset = typeof args.offset === "number" ? args.offset : 0;
237
+ const data = await this.get(`/v1/snapshots?limit=${limit}&offset=${offset}`);
238
+ // Format as MCP-compatible text so parseSnapshotList() in client.ts works
239
+ return this.formatSnapshotList(data);
240
+ }
241
+
242
+ private async rollbackSnapshot(args: Record<string, unknown>) {
243
+ return this.post(`/v1/snapshots/${encodeURIComponent(String(args.name))}/rollback`, {});
244
+ }
245
+
246
+ // ── Branches ──────────────────────────────────────────────────
247
+
248
+ private async branchCreate(args: Record<string, unknown>) {
249
+ const body: Record<string, unknown> = { name: args.name };
250
+ if (args.from_snapshot) body.from_snapshot = args.from_snapshot;
251
+ if (args.from_timestamp) body.from_timestamp = args.from_timestamp;
252
+ return this.post("/v1/branches", body);
253
+ }
254
+
255
+ private async branchList() {
256
+ const data = await this.get("/v1/branches");
257
+ // Format as MCP-compatible text so parseBranches() in client.ts works
258
+ return this.formatBranchList(data);
259
+ }
260
+
261
+ private async branchCheckout(args: Record<string, unknown>) {
262
+ return this.post(`/v1/branches/${encodeURIComponent(String(args.name))}/checkout`, {});
263
+ }
264
+
265
+ private async branchDelete(args: Record<string, unknown>) {
266
+ return this.delete(`/v1/branches/${encodeURIComponent(String(args.name))}`);
267
+ }
268
+
269
+ private async branchMerge(args: Record<string, unknown>) {
270
+ const body: Record<string, unknown> = {};
271
+ if (args.strategy) body.strategy = args.strategy;
272
+ return this.post(`/v1/branches/${encodeURIComponent(String(args.source))}/merge`, body);
273
+ }
274
+
275
+ private async branchDiff(args: Record<string, unknown>) {
276
+ const limit = typeof args.limit === "number" ? args.limit : 50;
277
+ return this.get(`/v1/branches/${encodeURIComponent(String(args.source))}/diff?limit=${limit}`);
278
+ }
279
+
280
+ // ── Health ──────────────────────────────────────────────────────
281
+
282
+ async healthCheck(): Promise<{ status: string; instance_id?: string; db?: boolean }> {
283
+ const data = await this.get("/health/instance");
284
+ const rec = this.asRecord(data);
285
+ return {
286
+ status: typeof rec?.status === "string" ? rec.status : "ok",
287
+ instance_id: typeof rec?.instance_id === "string" ? rec.instance_id : undefined,
288
+ db: typeof rec?.db === "boolean" ? rec.db : undefined,
289
+ };
290
+ }
291
+
292
+ // ── HTTP primitives ───────────────────────────────────────────
293
+
294
+ private headers(): Record<string, string> {
295
+ // In api mode, the API key (sk-...) already scopes to its owning user.
296
+ // Sending X-User-Id is unnecessary and can cause cross-user leakage
297
+ // in open-auth deployments. Let the API key determine identity.
298
+ return {
299
+ "Authorization": `Bearer ${this.apiKey}`,
300
+ "Content-Type": "application/json",
301
+ };
302
+ }
303
+
304
+ private async request(method: string, path: string, body?: unknown): Promise<unknown> {
305
+ const url = `${this.apiUrl}${path}`;
306
+ const controller = new AbortController();
307
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
308
+
309
+ try {
310
+ const response = await fetch(url, {
311
+ method,
312
+ headers: this.headers(),
313
+ body: body !== undefined ? JSON.stringify(body) : undefined,
314
+ signal: controller.signal,
315
+ });
316
+
317
+ const text = await response.text();
318
+
319
+ if (!response.ok) {
320
+ throw new Error(
321
+ `Memoria API ${method} ${path} returned ${response.status}: ${text.slice(0, 500)}`,
322
+ );
323
+ }
324
+
325
+ if (!text.trim()) {
326
+ return { ok: true };
327
+ }
328
+
329
+ try {
330
+ return JSON.parse(text);
331
+ } catch {
332
+ return text;
333
+ }
334
+ } catch (error) {
335
+ if (error instanceof DOMException && error.name === "AbortError") {
336
+ throw new Error(`Memoria API request timed out after ${this.timeoutMs}ms: ${method} ${path}`);
337
+ }
338
+ throw error;
339
+ } finally {
340
+ clearTimeout(timer);
341
+ }
342
+ }
343
+
344
+ private get(path: string) {
345
+ return this.request("GET", path);
346
+ }
347
+
348
+ private post(path: string, body: unknown) {
349
+ return this.request("POST", path, body);
350
+ }
351
+
352
+ private put(path: string, body: unknown) {
353
+ return this.request("PUT", path, body);
354
+ }
355
+
356
+ private delete(path: string) {
357
+ return this.request("DELETE", path);
358
+ }
359
+
360
+ // ── Formatting helpers ────────────────────────────────────────
361
+ // Produce text output matching the Rust MCP binary's format so that
362
+ // the existing parsers in client.ts (parseMemoryTextList, etc.) work.
363
+
364
+ private formatMemoryList(data: unknown): string {
365
+ // The retrieve/search endpoints return either an array or { results: [...] }
366
+ const rec = this.asRecord(data);
367
+ const items = Array.isArray(data)
368
+ ? data
369
+ : Array.isArray(rec?.results)
370
+ ? rec!.results
371
+ : Array.isArray(rec?.items)
372
+ ? rec!.items
373
+ : [];
374
+ return this.formatMemoryItems(items);
375
+ }
376
+
377
+ private formatMemoryItems(items: unknown[]): string {
378
+ if (items.length === 0) {
379
+ return "No relevant memories found.";
380
+ }
381
+ const lines: string[] = [];
382
+ for (const item of items) {
383
+ const rec = this.asRecord(item);
384
+ if (!rec) continue;
385
+ const id = rec.memory_id ?? "";
386
+ const type = rec.memory_type ?? "semantic";
387
+ const content = typeof rec.content === "string" ? rec.content : "";
388
+ lines.push(`[${id}] (${type}) ${content}`);
389
+ }
390
+ return lines.join("\n");
391
+ }
392
+
393
+ private formatSnapshotList(data: unknown): string {
394
+ // Produce text matching Rust MCP format: "Snapshots (N):\n name (timestamp)\n ..."
395
+ const rec = this.asRecord(data);
396
+ const items = rec?.result ? this.asRecord(rec.result) : rec;
397
+ // The API may return an array or an object with items/snapshots
398
+ let snapshots: unknown[] = [];
399
+ if (Array.isArray(data)) {
400
+ snapshots = data;
401
+ } else if (items && Array.isArray(items)) {
402
+ snapshots = items;
403
+ } else if (rec) {
404
+ // Try common response shapes
405
+ if (Array.isArray(rec.snapshots)) snapshots = rec.snapshots;
406
+ else if (Array.isArray(rec.items)) snapshots = rec.items;
407
+ else if (typeof rec.result === "string") return rec.result as string;
408
+ }
409
+ if (snapshots.length === 0) {
410
+ return "Snapshots (0):";
411
+ }
412
+ const lines = [`Snapshots (${snapshots.length}):`];
413
+ for (const snap of snapshots) {
414
+ const s = this.asRecord(snap);
415
+ if (!s) continue;
416
+ const name = s.name ?? s.snapshot_name ?? "";
417
+ const ts = s.timestamp ?? s.created_at ?? "";
418
+ lines.push(` ${name} (${ts})`);
419
+ }
420
+ return lines.join("\n");
421
+ }
422
+
423
+ private formatBranchList(data: unknown): string {
424
+ // Produce text matching Rust MCP format: "Branches:\n name\n name ← active"
425
+ const rec = this.asRecord(data);
426
+ let branches: unknown[] = [];
427
+ if (Array.isArray(data)) {
428
+ branches = data;
429
+ } else if (rec) {
430
+ if (Array.isArray(rec.branches)) branches = rec.branches;
431
+ else if (Array.isArray(rec.items)) branches = rec.items;
432
+ else if (typeof rec.result === "string") return rec.result as string;
433
+ }
434
+ if (branches.length === 0) {
435
+ return "Branches:\n main ← active";
436
+ }
437
+ const lines = ["Branches:"];
438
+ for (const branch of branches) {
439
+ const b = this.asRecord(branch);
440
+ if (!b) continue;
441
+ const name = typeof b.name === "string" ? b.name : "";
442
+ const active = b.active === true;
443
+ lines.push(` ${name}${active ? " ← active" : ""}`);
444
+ }
445
+ return lines.join("\n");
446
+ }
447
+
448
+ private asRecord(value: unknown): Record<string, unknown> | null {
449
+ return value && typeof value === "object" && !Array.isArray(value)
450
+ ? (value as Record<string, unknown>)
451
+ : null;
452
+ }
453
+ }
package/openclaw/index.ts CHANGED
@@ -432,7 +432,7 @@ function buildCapabilitiesPayload(config: MemoriaPluginConfig): Record<string, u
432
432
  userIdStrategy: config.userIdStrategy,
433
433
  autoRecall: config.autoRecall,
434
434
  autoObserve: config.autoObserve,
435
- llmConfigured: Boolean(config.llmApiKey || config.backend === "http"),
435
+ llmConfigured: Boolean(config.llmApiKey || config.backend === "api"),
436
436
  tools: supportedToolNames(),
437
437
  embeddedOnly: [...EMBEDDED_ONLY_TOOL_NAMES],
438
438
  cliCommands: [...CLI_COMMAND_NAMES],
@@ -551,7 +551,7 @@ function shouldShowOnboardingHint(rawPluginConfig: unknown): boolean {
551
551
  const hasCloudConfig = hasNonEmptyString(raw.apiUrl) || hasNonEmptyString(raw.apiKey);
552
552
  const hasLocalConfig = hasNonEmptyString(raw.dbUrl);
553
553
 
554
- return !(backend === "http" || hasCloudConfig || hasLocalConfig);
554
+ return !(backend === "api" || hasCloudConfig || hasLocalConfig);
555
555
  }
556
556
 
557
557
  const ONBOARDING_HINT_ONCE_KEY = "__memory_memoria_onboarding_hint_logged__";
@@ -566,9 +566,9 @@ function shouldLogOnboardingHintOnce(): boolean {
566
566
  }
567
567
 
568
568
  const plugin = {
569
- id: "memory-memoria",
569
+ id: "thememoria",
570
570
  name: "Memory (Memoria)",
571
- description: "Memoria-backed long-term memory plugin for OpenClaw powered by the Rust memoria CLI and API.",
571
+ description: "Memoria-backed long-term memory plugin for OpenClaw. Supports direct HTTP API mode (no binary) and embedded mode (local Rust CLI).",
572
572
  kind: "memory" as const,
573
573
  configSchema: memoriaPluginConfigSchema,
574
574
 
@@ -581,7 +581,7 @@ const plugin = {
581
581
 
582
582
  if (isFirstRegister) {
583
583
  api.logger.info(
584
- `memory-memoria: registered (${needsSetup ? "pending setup" : config.backend})`,
584
+ `thememoria: registered (${needsSetup ? "pending setup" : config.backend})`,
585
585
  );
586
586
 
587
587
  const isEnableCommand =
@@ -589,7 +589,7 @@ const plugin = {
589
589
  process.argv.some((arg) => arg === "plugins");
590
590
  if (needsSetup && isEnableCommand) {
591
591
  api.logger.info(
592
- "🧠 Memoria next step (Cloud, recommended): openclaw memoria setup --mode cloud --api-url <MEMORIA_API_URL> --api-key <MEMORIA_API_KEY> --install-memoria",
592
+ "🧠 Memoria next step (Cloud, recommended): openclaw memoria setup --mode cloud --api-url <MEMORIA_API_URL> --api-key <MEMORIA_API_KEY>",
593
593
  );
594
594
  api.logger.info(
595
595
  "🧩 Local quick start: openclaw memoria setup --mode local --install-memoria --embedding-api-key <EMBEDDING_API_KEY>",
@@ -1891,7 +1891,7 @@ const plugin = {
1891
1891
  };
1892
1892
  };
1893
1893
 
1894
- const applyConnectOptions = (normalized: NormalizedConnectOptions) => {
1894
+ const applyConnectOptions = async (normalized: NormalizedConnectOptions) => {
1895
1895
  const resolvedConfigFile = resolveOpenClawConfigFile();
1896
1896
  let memoriaBinForConfig = normalized.memoriaBin;
1897
1897
  const installDirFallback =
@@ -1901,7 +1901,7 @@ const plugin = {
1901
1901
  : path.join(process.env.HOME ?? "", ".local", "bin"));
1902
1902
  let effectiveMemoriaExecutable = memoriaBinForConfig ?? config.memoriaExecutable;
1903
1903
 
1904
- if (normalized.installMemoria && !isExecutableAvailable(effectiveMemoriaExecutable)) {
1904
+ if (normalized.mode === "local" && normalized.installMemoria && !isExecutableAvailable(effectiveMemoriaExecutable)) {
1905
1905
  runMemoriaInstaller({
1906
1906
  memoriaVersion: normalized.memoriaVersion,
1907
1907
  memoriaInstallDir: installDirFallback,
@@ -1941,12 +1941,39 @@ const plugin = {
1941
1941
  }
1942
1942
 
1943
1943
  if (normalized.healthCheck) {
1944
- assertMemoriaExecutableAvailable(effectiveMemoriaExecutable, normalized.mode);
1945
- const healthArgs = ["memoria", "health"];
1946
- if (normalized.userId) {
1947
- healthArgs.push("--user-id", normalized.userId);
1944
+ if (normalized.mode === "cloud") {
1945
+ // API mode: direct HTTP health check, no binary needed
1946
+ const healthUrl = `${normalized.apiUrl}/health/instance`;
1947
+ const controller = new AbortController();
1948
+ const timer = setTimeout(() => controller.abort(), 10_000);
1949
+ try {
1950
+ const resp = await fetch(healthUrl, {
1951
+ headers: {
1952
+ "Authorization": `Bearer ${normalized.apiKey}`,
1953
+ "Content-Type": "application/json",
1954
+ },
1955
+ signal: controller.signal,
1956
+ });
1957
+ if (!resp.ok) {
1958
+ throw new Error(`Health check returned ${resp.status}: ${await resp.text()}`);
1959
+ }
1960
+ const data = await resp.json();
1961
+ printJson({ healthCheck: "ok", ...data });
1962
+ } catch (error) {
1963
+ throw new Error(
1964
+ `Health check failed against ${healthUrl}: ${error instanceof Error ? error.message : String(error)}`,
1965
+ );
1966
+ } finally {
1967
+ clearTimeout(timer);
1968
+ }
1969
+ } else {
1970
+ assertMemoriaExecutableAvailable(effectiveMemoriaExecutable, normalized.mode);
1971
+ const healthArgs = ["memoria", "health"];
1972
+ if (normalized.userId) {
1973
+ healthArgs.push("--user-id", normalized.userId);
1974
+ }
1975
+ runLocalCommand(openclawBin, healthArgs, { env: openclawEnv });
1948
1976
  }
1949
- runLocalCommand(openclawBin, healthArgs, { env: openclawEnv });
1950
1977
  }
1951
1978
 
1952
1979
  printJson({
@@ -2124,7 +2151,7 @@ const plugin = {
2124
2151
  .option("--skip-health-check", "Skip `openclaw memoria health`", false)
2125
2152
  .action(withCliClient(async (opts) => {
2126
2153
  const normalized = normalizeConnectOptions(opts as RawConnectCliOptions, "cloud");
2127
- applyConnectOptions(normalized);
2154
+ await applyConnectOptions(normalized);
2128
2155
  }));
2129
2156
 
2130
2157
  memoria
@@ -2145,7 +2172,7 @@ const plugin = {
2145
2172
  .option("--skip-health-check", "Skip `openclaw memoria health`", false)
2146
2173
  .action(withCliClient(async (opts) => {
2147
2174
  const normalized = normalizeConnectOptions(opts as RawConnectCliOptions, "cloud");
2148
- applyConnectOptions(normalized);
2175
+ await applyConnectOptions(normalized);
2149
2176
  }));
2150
2177
 
2151
2178
  ltm
@@ -2231,7 +2258,7 @@ const plugin = {
2231
2258
  const handleAutoRecall = async (
2232
2259
  prompt: string,
2233
2260
  ctx: PluginIdentityContext,
2234
- ): Promise<{ prependContext?: string } | void> => {
2261
+ ): Promise<{ appendSystemContext?: string } | void> => {
2235
2262
  const trimmed = prompt.trim();
2236
2263
  if (trimmed.length < config.recallMinPromptLength) {
2237
2264
  return;
@@ -2251,12 +2278,12 @@ const plugin = {
2251
2278
  if (memories.length === 0) {
2252
2279
  return;
2253
2280
  }
2254
- api.logger.info(`memory-memoria: recalled ${memories.length} memories`);
2281
+ api.logger.info(`thememoria: recalled ${memories.length} memories`);
2255
2282
  return {
2256
- prependContext: formatRelevantMemoriesContext(memories),
2283
+ appendSystemContext: formatRelevantMemoriesContext(memories),
2257
2284
  };
2258
2285
  } catch (error) {
2259
- api.logger.warn(`memory-memoria: auto-recall failed: ${String(error)}`);
2286
+ api.logger.warn(`thememoria: auto-recall failed: ${String(error)}`);
2260
2287
  }
2261
2288
  };
2262
2289
 
@@ -2264,10 +2291,6 @@ const plugin = {
2264
2291
  api.on("before_prompt_build", async (event, ctx) => {
2265
2292
  return await handleAutoRecall(event.prompt, ctx);
2266
2293
  });
2267
-
2268
- api.on("before_agent_start", async (event, ctx) => {
2269
- return await handleAutoRecall(event.prompt, ctx);
2270
- });
2271
2294
  }
2272
2295
 
2273
2296
  if (config.autoObserve) {
@@ -2293,10 +2316,10 @@ const plugin = {
2293
2316
  sessionId: ctx.sessionId,
2294
2317
  });
2295
2318
  if (created.length > 0) {
2296
- api.logger.info(`memory-memoria: observed ${created.length} new memories`);
2319
+ api.logger.info(`thememoria: observed ${created.length} new memories`);
2297
2320
  }
2298
2321
  } catch (error) {
2299
- api.logger.warn(`memory-memoria: auto-observe failed: ${String(error)}`);
2322
+ api.logger.warn(`thememoria: auto-observe failed: ${String(error)}`);
2300
2323
  }
2301
2324
  });
2302
2325
 
@@ -2324,34 +2347,34 @@ const plugin = {
2324
2347
  });
2325
2348
  if (created.length > 0) {
2326
2349
  api.logger.info(
2327
- `memory-memoria: observed ${created.length} new memories before reset`,
2350
+ `thememoria: observed ${created.length} new memories before reset`,
2328
2351
  );
2329
2352
  }
2330
2353
  } catch (error) {
2331
- api.logger.warn(`memory-memoria: before_reset observe failed: ${String(error)}`);
2354
+ api.logger.warn(`thememoria: before_reset observe failed: ${String(error)}`);
2332
2355
  }
2333
2356
  });
2334
2357
  }
2335
2358
 
2336
2359
  api.on("after_compaction", async () => {
2337
2360
  api.logger.info(
2338
- "memory-memoria: compaction finished; next prompt will use live Memoria recall",
2361
+ "thememoria: compaction finished; next prompt will use live Memoria recall",
2339
2362
  );
2340
2363
  });
2341
2364
 
2342
2365
  api.registerService({
2343
- id: "memory-memoria",
2366
+ id: "thememoria",
2344
2367
  async start() {
2345
2368
  try {
2346
2369
  const result = await client.health(config.defaultUserId);
2347
- api.logger.info(`memory-memoria: connected (${String(result.status ?? "ok")})`);
2370
+ api.logger.info(`thememoria: connected (${String(result.status ?? "ok")})`);
2348
2371
  } catch (error) {
2349
- api.logger.warn(`memory-memoria: health check failed: ${String(error)}`);
2372
+ api.logger.warn(`thememoria: health check failed: ${String(error)}`);
2350
2373
  }
2351
2374
  },
2352
2375
  stop() {
2353
2376
  client.close();
2354
- api.logger.info("memory-memoria: stopped");
2377
+ api.logger.info("thememoria: stopped");
2355
2378
  },
2356
2379
  });
2357
2380
  },