@memtensor/memos-local-openclaw-plugin 1.0.4-beta.0 → 1.0.4-beta.10
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/.env.example +7 -0
- package/README.md +24 -24
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +34 -2
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +5 -2
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +173 -14
- package/dist/client/connector.js.map +1 -1
- package/dist/client/hub.d.ts.map +1 -1
- package/dist/client/hub.js +22 -0
- package/dist/client/hub.js.map +1 -1
- package/dist/client/skill-sync.d.ts +7 -0
- package/dist/client/skill-sync.d.ts.map +1 -1
- package/dist/client/skill-sync.js +10 -0
- package/dist/client/skill-sync.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -11
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts +7 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +301 -106
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +3 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +18 -1
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +37 -6
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +91 -1
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +1 -0
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +82 -8
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.d.ts +1 -1
- package/dist/sharing/types.d.ts.map +1 -1
- package/dist/skill/evolver.d.ts +2 -0
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +3 -0
- package/dist/skill/evolver.js.map +1 -1
- package/dist/storage/ensure-binding.d.ts +12 -0
- package/dist/storage/ensure-binding.d.ts.map +1 -0
- package/dist/storage/ensure-binding.js +53 -0
- package/dist/storage/ensure-binding.js.map +1 -0
- package/dist/storage/sqlite.d.ts +74 -20
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +301 -207
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts +12 -5
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +156 -40
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -1
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +3 -1
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +2991 -1041
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +32 -8
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1122 -261
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +384 -43
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.cjs +1 -1
- package/skill/memos-memory-guide/SKILL.md +64 -26
- package/src/capture/index.ts +37 -1
- package/src/client/connector.ts +173 -16
- package/src/client/hub.ts +18 -0
- package/src/client/skill-sync.ts +14 -0
- package/src/config.ts +9 -11
- package/src/hub/server.ts +285 -98
- package/src/hub/user-manager.ts +20 -3
- package/src/index.ts +10 -2
- package/src/ingest/providers/index.ts +41 -7
- package/src/recall/engine.ts +84 -1
- package/src/shared/llm-call.ts +97 -9
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +5 -0
- package/src/storage/ensure-binding.ts +52 -0
- package/src/storage/sqlite.ts +310 -233
- package/src/telemetry.ts +172 -41
- package/src/tools/memory-search.ts +2 -1
- package/src/types.ts +1 -2
- package/src/viewer/html.ts +2991 -1041
- package/src/viewer/server.ts +984 -190
package/index.ts
CHANGED
|
@@ -9,8 +9,10 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
9
9
|
import { Type } from "@sinclair/typebox";
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
12
13
|
import { buildContext } from "./src/config";
|
|
13
14
|
import type { HostModelsConfig } from "./src/openclaw-api";
|
|
15
|
+
import { ensureSqliteBinding } from "./src/storage/ensure-binding";
|
|
14
16
|
import { SqliteStore } from "./src/storage/sqlite";
|
|
15
17
|
import { Embedder } from "./src/embedding";
|
|
16
18
|
import { IngestWorker } from "./src/ingest/worker";
|
|
@@ -21,7 +23,7 @@ import { ViewerServer } from "./src/viewer/server";
|
|
|
21
23
|
import { HubServer } from "./src/hub/server";
|
|
22
24
|
import { hubGetMemoryDetail, hubRequestJson, hubSearchMemories, hubSearchSkills, resolveHubClient } from "./src/client/hub";
|
|
23
25
|
import { getHubStatus, connectToHub } from "./src/client/connector";
|
|
24
|
-
import { fetchHubSkillBundle, publishSkillBundleToHub, restoreSkillBundleFromHub } from "./src/client/skill-sync";
|
|
26
|
+
import { fetchHubSkillBundle, publishSkillBundleToHub, restoreSkillBundleFromHub, unpublishSkillBundleFromHub } from "./src/client/skill-sync";
|
|
25
27
|
import { SkillEvolver } from "./src/skill/evolver";
|
|
26
28
|
import { SkillInstaller } from "./src/skill/installer";
|
|
27
29
|
import { Summarizer } from "./src/ingest/providers";
|
|
@@ -81,13 +83,20 @@ const memosLocalPlugin = {
|
|
|
81
83
|
|
|
82
84
|
register(api: OpenClawPluginApi) {
|
|
83
85
|
// ─── Ensure better-sqlite3 native module is available ───
|
|
84
|
-
const pluginDir = path.dirname(
|
|
86
|
+
const pluginDir = path.dirname(fileURLToPath(import.meta.url));
|
|
87
|
+
|
|
88
|
+
function normalizeFsPath(p: string): string {
|
|
89
|
+
return path.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
let sqliteReady = false;
|
|
86
93
|
|
|
87
94
|
function trySqliteLoad(): boolean {
|
|
88
95
|
try {
|
|
89
96
|
const resolved = require.resolve("better-sqlite3", { paths: [pluginDir] });
|
|
90
|
-
|
|
97
|
+
const resolvedNorm = normalizeFsPath(resolved);
|
|
98
|
+
const pluginNorm = normalizeFsPath(pluginDir);
|
|
99
|
+
if (!resolvedNorm.startsWith(pluginNorm + "/") && resolvedNorm !== pluginNorm) {
|
|
91
100
|
api.logger.warn(`memos-local: better-sqlite3 resolved outside plugin dir: ${resolved}`);
|
|
92
101
|
return false;
|
|
93
102
|
}
|
|
@@ -196,6 +205,8 @@ const memosLocalPlugin = {
|
|
|
196
205
|
error: (msg: string) => api.logger.warn(`[error] ${msg}`),
|
|
197
206
|
}, hostModels);
|
|
198
207
|
|
|
208
|
+
ensureSqliteBinding(ctx.log);
|
|
209
|
+
|
|
199
210
|
const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log);
|
|
200
211
|
const embedder = new Embedder(ctx.config.embedding, ctx.log, ctx.openclawAPI);
|
|
201
212
|
const worker = new IngestWorker(store, embedder, ctx);
|
|
@@ -205,6 +216,7 @@ const memosLocalPlugin = {
|
|
|
205
216
|
const workspaceDir = api.resolvePath("~/.openclaw/workspace");
|
|
206
217
|
const skillCtx = { ...ctx, workspaceDir };
|
|
207
218
|
const skillEvolver = new SkillEvolver(store, engine, skillCtx);
|
|
219
|
+
skillEvolver.onSkillEvolved = (name, type) => telemetry.trackSkillEvolved(name, type);
|
|
208
220
|
const skillInstaller = new SkillInstaller(store, skillCtx);
|
|
209
221
|
|
|
210
222
|
let pluginVersion = "0.0.0";
|
|
@@ -269,6 +281,18 @@ const memosLocalPlugin = {
|
|
|
269
281
|
// Falls back to "main" when no hook has fired yet (single-agent setups).
|
|
270
282
|
let currentAgentId = "main";
|
|
271
283
|
|
|
284
|
+
// ─── Check allowPromptInjection policy ───
|
|
285
|
+
// When allowPromptInjection=false, the prompt mutation fields (such as prependContext) in the hook return value
|
|
286
|
+
// will be stripped by the framework. Skip auto-recall to avoid unnecessary LLM/embedding calls.
|
|
287
|
+
const pluginEntry = (api.config as any)?.plugins?.entries?.[api.id];
|
|
288
|
+
const allowPromptInjection = pluginEntry?.hooks?.allowPromptInjection !== false;
|
|
289
|
+
if (!allowPromptInjection) {
|
|
290
|
+
api.logger.info("memos-local: allowPromptInjection=false, auto-recall disabled");
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
api.logger.info("memos-local: allowPromptInjection=true, auto-recall enabled");
|
|
294
|
+
}
|
|
295
|
+
|
|
272
296
|
const trackTool = (toolName: string, fn: (...args: any[]) => Promise<any>) =>
|
|
273
297
|
async (...args: any[]) => {
|
|
274
298
|
const t0 = performance.now();
|
|
@@ -280,6 +304,7 @@ const memosLocalPlugin = {
|
|
|
280
304
|
return result;
|
|
281
305
|
} catch (e) {
|
|
282
306
|
ok = false;
|
|
307
|
+
telemetry.trackError(toolName, (e as Error)?.name ?? "unknown");
|
|
283
308
|
throw e;
|
|
284
309
|
} finally {
|
|
285
310
|
const dur = performance.now() - t0;
|
|
@@ -301,6 +326,89 @@ const memosLocalPlugin = {
|
|
|
301
326
|
}
|
|
302
327
|
};
|
|
303
328
|
|
|
329
|
+
const getCurrentOwner = () => `agent:${currentAgentId}`;
|
|
330
|
+
const resolveMemorySearchScope = (scope?: string): "local" | "group" | "all" =>
|
|
331
|
+
scope === "group" || scope === "all" ? scope : "local";
|
|
332
|
+
const resolveMemoryShareTarget = (target?: string): "agents" | "hub" | "both" =>
|
|
333
|
+
target === "hub" || target === "both" ? target : "agents";
|
|
334
|
+
const resolveMemoryUnshareTarget = (target?: string): "agents" | "hub" | "all" =>
|
|
335
|
+
target === "agents" || target === "hub" ? target : "all";
|
|
336
|
+
const resolveSkillPublishTarget = (target?: string, scope?: string): "agents" | "hub" => {
|
|
337
|
+
if (target === "hub") return "hub";
|
|
338
|
+
if (target === "agents") return "agents";
|
|
339
|
+
return scope === "public" || scope === "group" ? "hub" : "agents";
|
|
340
|
+
};
|
|
341
|
+
const resolveSkillHubVisibility = (visibility?: string, scope?: string): "public" | "group" =>
|
|
342
|
+
visibility === "group" || scope === "group" ? "group" : "public";
|
|
343
|
+
const resolveSkillUnpublishTarget = (target?: string): "agents" | "hub" | "all" =>
|
|
344
|
+
target === "hub" || target === "all" ? target : "agents";
|
|
345
|
+
|
|
346
|
+
const shareMemoryToHub = async (
|
|
347
|
+
chunkId: string,
|
|
348
|
+
input?: { visibility?: "public" | "group"; groupId?: string; hubAddress?: string; userToken?: string },
|
|
349
|
+
): Promise<{ memoryId: string; visibility: "public" | "group"; groupId: string | null }> => {
|
|
350
|
+
const chunk = store.getChunk(chunkId);
|
|
351
|
+
if (!chunk) {
|
|
352
|
+
throw new Error(`Memory not found: ${chunkId}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const visibility = input?.visibility === "group" ? "group" : "public";
|
|
356
|
+
const groupId = visibility === "group" ? (input?.groupId ?? null) : null;
|
|
357
|
+
const hubClient = await resolveHubClient(store, ctx, { hubAddress: input?.hubAddress, userToken: input?.userToken });
|
|
358
|
+
const response = await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
359
|
+
method: "POST",
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
memory: {
|
|
362
|
+
sourceChunkId: chunk.id,
|
|
363
|
+
role: chunk.role,
|
|
364
|
+
content: chunk.content,
|
|
365
|
+
summary: chunk.summary,
|
|
366
|
+
kind: chunk.kind,
|
|
367
|
+
groupId,
|
|
368
|
+
visibility,
|
|
369
|
+
},
|
|
370
|
+
}),
|
|
371
|
+
}) as { memoryId?: string; visibility?: "public" | "group" };
|
|
372
|
+
|
|
373
|
+
const now = Date.now();
|
|
374
|
+
const existing = store.getHubMemoryBySource(hubClient.userId, chunk.id);
|
|
375
|
+
store.upsertHubMemory({
|
|
376
|
+
id: response?.memoryId ?? existing?.id ?? `${chunk.id}-hub`,
|
|
377
|
+
sourceChunkId: chunk.id,
|
|
378
|
+
sourceUserId: hubClient.userId,
|
|
379
|
+
role: chunk.role,
|
|
380
|
+
content: chunk.content,
|
|
381
|
+
summary: chunk.summary ?? "",
|
|
382
|
+
kind: chunk.kind,
|
|
383
|
+
groupId,
|
|
384
|
+
visibility,
|
|
385
|
+
createdAt: existing?.createdAt ?? now,
|
|
386
|
+
updatedAt: now,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
memoryId: response?.memoryId ?? existing?.id ?? `${chunk.id}-hub`,
|
|
391
|
+
visibility,
|
|
392
|
+
groupId,
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const unshareMemoryFromHub = async (
|
|
397
|
+
chunkId: string,
|
|
398
|
+
input?: { hubAddress?: string; userToken?: string },
|
|
399
|
+
): Promise<void> => {
|
|
400
|
+
const chunk = store.getChunk(chunkId);
|
|
401
|
+
if (!chunk) {
|
|
402
|
+
throw new Error(`Memory not found: ${chunkId}`);
|
|
403
|
+
}
|
|
404
|
+
const hubClient = await resolveHubClient(store, ctx, { hubAddress: input?.hubAddress, userToken: input?.userToken });
|
|
405
|
+
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
406
|
+
method: "POST",
|
|
407
|
+
body: JSON.stringify({ sourceChunkId: chunk.id }),
|
|
408
|
+
});
|
|
409
|
+
store.deleteHubMemoryBySource(hubClient.userId, chunk.id);
|
|
410
|
+
};
|
|
411
|
+
|
|
304
412
|
// ─── Tool: memory_search ───
|
|
305
413
|
|
|
306
414
|
api.registerTool(
|
|
@@ -309,24 +417,43 @@ const memosLocalPlugin = {
|
|
|
309
417
|
label: "Memory Search",
|
|
310
418
|
description:
|
|
311
419
|
"Search long-term conversation memory for past conversations, user preferences, decisions, and experiences. " +
|
|
312
|
-
"
|
|
313
|
-
"
|
|
314
|
-
"Pass only a short natural-language query (2-5 key words).",
|
|
420
|
+
"Use scope='local' for this agent plus local shared memories, or scope='group'/'all' to include Hub-shared memories. " +
|
|
421
|
+
"Supports optional maxResults, minScore, and role filtering when you need tighter control.",
|
|
315
422
|
parameters: Type.Object({
|
|
316
423
|
query: Type.String({ description: "Short natural language search query (2-5 key words)" }),
|
|
424
|
+
scope: Type.Optional(Type.String({ description: "Search scope: 'local' (default), 'group', or 'all'. Use group/all to include Hub-shared memories." })),
|
|
425
|
+
maxResults: Type.Optional(Type.Number({ description: "Maximum results to return. Default 10, max 20." })),
|
|
426
|
+
minScore: Type.Optional(Type.Number({ description: "Minimum score threshold for local recall. Default 0.45, floor 0.35." })),
|
|
427
|
+
role: Type.Optional(Type.String({ description: "Optional local role filter: 'user', 'assistant', 'tool', or 'system'." })),
|
|
428
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for group/all search." })),
|
|
429
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for group/all search." })),
|
|
317
430
|
}),
|
|
318
431
|
execute: trackTool("memory_search", async (_toolCallId: any, params: any) => {
|
|
319
|
-
const {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
432
|
+
const {
|
|
433
|
+
query,
|
|
434
|
+
scope: rawScope,
|
|
435
|
+
maxResults,
|
|
436
|
+
minScore: rawMinScore,
|
|
437
|
+
role: rawRole,
|
|
438
|
+
hubAddress,
|
|
439
|
+
userToken,
|
|
440
|
+
} = params as {
|
|
441
|
+
query: string;
|
|
442
|
+
scope?: string;
|
|
443
|
+
maxResults?: number;
|
|
444
|
+
minScore?: number;
|
|
445
|
+
role?: string;
|
|
446
|
+
hubAddress?: string;
|
|
447
|
+
userToken?: string;
|
|
448
|
+
};
|
|
449
|
+
const role = rawRole === "user" || rawRole === "assistant" || rawRole === "tool" || rawRole === "system" ? rawRole : undefined;
|
|
450
|
+
const minScore = typeof rawMinScore === "number" ? Math.max(0.35, Math.min(1, rawMinScore)) : undefined;
|
|
451
|
+
const searchScope = resolveMemorySearchScope(rawScope);
|
|
452
|
+
const searchLimit = typeof maxResults === "number" ? Math.max(1, Math.min(20, Math.round(maxResults))) : 10;
|
|
326
453
|
|
|
327
454
|
const agentId = currentAgentId;
|
|
328
|
-
const ownerFilter = [
|
|
329
|
-
const effectiveMaxResults =
|
|
455
|
+
const ownerFilter = [getCurrentOwner(), "public"];
|
|
456
|
+
const effectiveMaxResults = searchLimit;
|
|
330
457
|
ctx.log.debug(`memory_search query="${query}" maxResults=${effectiveMaxResults} minScore=${minScore ?? 0.45} role=${role ?? "all"} owner=agent:${agentId}`);
|
|
331
458
|
const result = await engine.search({ query, maxResults: effectiveMaxResults, minScore, role, ownerFilter });
|
|
332
459
|
ctx.log.debug(`memory_search raw candidates: ${result.hits.length}`);
|
|
@@ -339,7 +466,7 @@ const memosLocalPlugin = {
|
|
|
339
466
|
original_excerpt: (h.original_excerpt ?? "").slice(0, 200),
|
|
340
467
|
}));
|
|
341
468
|
|
|
342
|
-
if (result.hits.length === 0) {
|
|
469
|
+
if (result.hits.length === 0 && searchScope === "local") {
|
|
343
470
|
return {
|
|
344
471
|
content: [{ type: "text", text: result.meta.note ?? "No relevant memories found." }],
|
|
345
472
|
details: { candidates: [], meta: result.meta },
|
|
@@ -363,11 +490,13 @@ const memosLocalPlugin = {
|
|
|
363
490
|
const indexSet = new Set(filterResult.relevant);
|
|
364
491
|
filteredHits = result.hits.filter((_, i) => indexSet.has(i + 1));
|
|
365
492
|
ctx.log.debug(`memory_search LLM filter: ${result.hits.length} → ${filteredHits.length} hits, sufficient=${sufficient}`);
|
|
366
|
-
} else {
|
|
493
|
+
} else if (searchScope === "local") {
|
|
367
494
|
return {
|
|
368
495
|
content: [{ type: "text", text: "No relevant memories found for this query." }],
|
|
369
496
|
details: { candidates: rawCandidates, filtered: [], meta: result.meta },
|
|
370
497
|
};
|
|
498
|
+
} else {
|
|
499
|
+
filteredHits = [];
|
|
371
500
|
}
|
|
372
501
|
}
|
|
373
502
|
|
|
@@ -843,7 +972,9 @@ ${detail.content}`,
|
|
|
843
972
|
{
|
|
844
973
|
name: "network_team_info",
|
|
845
974
|
label: "Network Team Info",
|
|
846
|
-
description:
|
|
975
|
+
description:
|
|
976
|
+
"Show current Hub connection status, signed-in user, role, and group memberships. " +
|
|
977
|
+
"Use this as a preflight check before any Hub share/unshare or Hub pull operation.",
|
|
847
978
|
parameters: Type.Object({}),
|
|
848
979
|
execute: trackTool("network_team_info", async () => {
|
|
849
980
|
const status = await getHubStatus(store, ctx.config);
|
|
@@ -988,6 +1119,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
988
1119
|
parameters: Type.Object({}),
|
|
989
1120
|
execute: trackTool("memory_viewer", async () => {
|
|
990
1121
|
ctx.log.debug(`memory_viewer called`);
|
|
1122
|
+
telemetry.trackViewerOpened();
|
|
991
1123
|
const url = `http://127.0.0.1:${viewerPort}`;
|
|
992
1124
|
return {
|
|
993
1125
|
content: [
|
|
@@ -1018,12 +1150,13 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1018
1150
|
api.registerTool(
|
|
1019
1151
|
{
|
|
1020
1152
|
name: "memory_write_public",
|
|
1021
|
-
label: "Write
|
|
1153
|
+
label: "Write Local Shared Memory",
|
|
1022
1154
|
description:
|
|
1023
|
-
"Write a piece of information to
|
|
1024
|
-
"Use this
|
|
1155
|
+
"Write a piece of information to local shared memory for all agents in this OpenClaw workspace. " +
|
|
1156
|
+
"Use this when you are creating a new shared note from scratch. This does not publish to Hub. " +
|
|
1157
|
+
"If you already have a memory chunk and want to expose it, use memory_share instead.",
|
|
1025
1158
|
parameters: Type.Object({
|
|
1026
|
-
content: Type.String({ description: "The content to write to
|
|
1159
|
+
content: Type.String({ description: "The content to write to local shared memory" }),
|
|
1027
1160
|
summary: Type.Optional(Type.String({ description: "Optional short summary of the content" })),
|
|
1028
1161
|
}),
|
|
1029
1162
|
execute: trackTool("memory_write_public", async (_toolCallId: any, params: any) => {
|
|
@@ -1068,7 +1201,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1068
1201
|
}
|
|
1069
1202
|
|
|
1070
1203
|
return {
|
|
1071
|
-
content: [{ type: "text", text: `
|
|
1204
|
+
content: [{ type: "text", text: `Memory shared to local agents successfully (id: ${chunkId}).` }],
|
|
1072
1205
|
details: { chunkId, owner: "public" },
|
|
1073
1206
|
};
|
|
1074
1207
|
}),
|
|
@@ -1076,6 +1209,164 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1076
1209
|
{ name: "memory_write_public" },
|
|
1077
1210
|
);
|
|
1078
1211
|
|
|
1212
|
+
api.registerTool(
|
|
1213
|
+
{
|
|
1214
|
+
name: "memory_share",
|
|
1215
|
+
label: "Share Memory",
|
|
1216
|
+
description:
|
|
1217
|
+
"Share an existing memory either with local OpenClaw agents, to the Hub team, or to both targets. " +
|
|
1218
|
+
"Use this only for an existing chunkId. Use target='agents' for local multi-agent sharing, target='hub' for team sharing, or target='both' for both. " +
|
|
1219
|
+
"If you need to create a brand new shared memory instead of exposing an existing one, use memory_write_public.",
|
|
1220
|
+
parameters: Type.Object({
|
|
1221
|
+
chunkId: Type.String({ description: "Existing local memory chunk ID to share" }),
|
|
1222
|
+
target: Type.Optional(Type.String({ description: "Share target: 'agents' (default), 'hub', or 'both'" })),
|
|
1223
|
+
visibility: Type.Optional(Type.String({ description: "Hub visibility when target includes hub: 'public' (default) or 'group'" })),
|
|
1224
|
+
groupId: Type.Optional(Type.String({ description: "Optional Hub group ID when visibility='group'" })),
|
|
1225
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override" })),
|
|
1226
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override" })),
|
|
1227
|
+
}),
|
|
1228
|
+
execute: trackTool("memory_share", async (_toolCallId: any, params: any) => {
|
|
1229
|
+
const {
|
|
1230
|
+
chunkId,
|
|
1231
|
+
target: rawTarget,
|
|
1232
|
+
visibility: rawVisibility,
|
|
1233
|
+
groupId,
|
|
1234
|
+
hubAddress,
|
|
1235
|
+
userToken,
|
|
1236
|
+
} = params as {
|
|
1237
|
+
chunkId: string;
|
|
1238
|
+
target?: string;
|
|
1239
|
+
visibility?: string;
|
|
1240
|
+
groupId?: string;
|
|
1241
|
+
hubAddress?: string;
|
|
1242
|
+
userToken?: string;
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
const chunk = store.getChunk(chunkId);
|
|
1246
|
+
if (!chunk) {
|
|
1247
|
+
return { content: [{ type: "text", text: `Memory not found: ${chunkId}` }], details: { error: "not_found", chunkId } };
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const target = resolveMemoryShareTarget(rawTarget);
|
|
1251
|
+
const visibility = rawVisibility === "group" ? "group" : "public";
|
|
1252
|
+
const details: Record<string, unknown> = { chunkId, target };
|
|
1253
|
+
const messages: string[] = [];
|
|
1254
|
+
|
|
1255
|
+
if (target === "agents" || target === "both") {
|
|
1256
|
+
const local = store.markMemorySharedLocally(chunkId);
|
|
1257
|
+
if (!local.ok) {
|
|
1258
|
+
return { content: [{ type: "text", text: `Failed to share memory ${chunkId} to local agents.` }], details: { error: local.reason ?? "local_share_failed", chunkId, target } };
|
|
1259
|
+
}
|
|
1260
|
+
details.local = {
|
|
1261
|
+
shared: true,
|
|
1262
|
+
owner: local.owner,
|
|
1263
|
+
originalOwner: local.originalOwner ?? null,
|
|
1264
|
+
};
|
|
1265
|
+
messages.push("shared to local agents");
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (target === "hub" || target === "both") {
|
|
1269
|
+
const hub = await shareMemoryToHub(chunkId, { visibility, groupId, hubAddress, userToken });
|
|
1270
|
+
details.hub = {
|
|
1271
|
+
shared: true,
|
|
1272
|
+
memoryId: hub.memoryId,
|
|
1273
|
+
visibility: hub.visibility,
|
|
1274
|
+
groupId: hub.groupId,
|
|
1275
|
+
};
|
|
1276
|
+
messages.push(`shared to Hub (${hub.visibility})`);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
return {
|
|
1280
|
+
content: [{ type: "text", text: `Memory "${chunk.summary || chunk.id}" ${messages.join(" and ")}.` }],
|
|
1281
|
+
details,
|
|
1282
|
+
};
|
|
1283
|
+
}),
|
|
1284
|
+
},
|
|
1285
|
+
{ name: "memory_share" },
|
|
1286
|
+
);
|
|
1287
|
+
|
|
1288
|
+
api.registerTool(
|
|
1289
|
+
{
|
|
1290
|
+
name: "memory_unshare",
|
|
1291
|
+
label: "Unshare Memory",
|
|
1292
|
+
description:
|
|
1293
|
+
"Remove sharing from an existing memory. Use target='agents' to stop local multi-agent sharing, target='hub' to remove it from Hub, or target='all' (default) to remove both. " +
|
|
1294
|
+
"privateOwner is only needed for older public memories that were never tracked with an original owner.",
|
|
1295
|
+
parameters: Type.Object({
|
|
1296
|
+
chunkId: Type.String({ description: "Existing local memory chunk ID to unshare" }),
|
|
1297
|
+
target: Type.Optional(Type.String({ description: "Unshare target: 'agents', 'hub', or 'all' (default)" })),
|
|
1298
|
+
privateOwner: Type.Optional(Type.String({ description: "Optional owner to restore when converting a public memory back to private and no original owner was tracked" })),
|
|
1299
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override" })),
|
|
1300
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override" })),
|
|
1301
|
+
}),
|
|
1302
|
+
execute: trackTool("memory_unshare", async (_toolCallId: any, params: any) => {
|
|
1303
|
+
const {
|
|
1304
|
+
chunkId,
|
|
1305
|
+
target: rawTarget,
|
|
1306
|
+
privateOwner,
|
|
1307
|
+
hubAddress,
|
|
1308
|
+
userToken,
|
|
1309
|
+
} = params as {
|
|
1310
|
+
chunkId: string;
|
|
1311
|
+
target?: string;
|
|
1312
|
+
privateOwner?: string;
|
|
1313
|
+
hubAddress?: string;
|
|
1314
|
+
userToken?: string;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
const chunk = store.getChunk(chunkId);
|
|
1318
|
+
if (!chunk) {
|
|
1319
|
+
return { content: [{ type: "text", text: `Memory not found: ${chunkId}` }], details: { error: "not_found", chunkId } };
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const target = resolveMemoryUnshareTarget(rawTarget);
|
|
1323
|
+
const details: Record<string, unknown> = { chunkId, target };
|
|
1324
|
+
const messages: string[] = [];
|
|
1325
|
+
|
|
1326
|
+
if (target === "agents" || target === "all") {
|
|
1327
|
+
const local = store.unmarkMemorySharedLocally(chunkId, privateOwner);
|
|
1328
|
+
if (!local.ok) {
|
|
1329
|
+
return {
|
|
1330
|
+
content: [{
|
|
1331
|
+
type: "text",
|
|
1332
|
+
text: local.reason === "original_owner_missing"
|
|
1333
|
+
? `Cannot restore memory "${chunk.summary || chunk.id}" to a private owner automatically. Pass privateOwner to unshare it locally.`
|
|
1334
|
+
: `Failed to stop local sharing for memory ${chunkId}.`,
|
|
1335
|
+
}],
|
|
1336
|
+
details: { error: local.reason ?? "local_unshare_failed", chunkId, target },
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
details.local = {
|
|
1340
|
+
shared: false,
|
|
1341
|
+
owner: local.owner,
|
|
1342
|
+
};
|
|
1343
|
+
messages.push("removed from local agent sharing");
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
if (target === "hub" || target === "all") {
|
|
1347
|
+
try {
|
|
1348
|
+
await unshareMemoryFromHub(chunkId, { hubAddress, userToken });
|
|
1349
|
+
details.hub = { shared: false };
|
|
1350
|
+
messages.push("removed from Hub");
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1353
|
+
if (target === "all" && msg.includes("hub client connection is not configured")) {
|
|
1354
|
+
details.hub = { shared: false, skipped: true, reason: "hub_not_configured" };
|
|
1355
|
+
} else {
|
|
1356
|
+
throw err;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
return {
|
|
1362
|
+
content: [{ type: "text", text: `Memory "${chunk.summary || chunk.id}" ${messages.join(" and ")}.` }],
|
|
1363
|
+
details,
|
|
1364
|
+
};
|
|
1365
|
+
}),
|
|
1366
|
+
},
|
|
1367
|
+
{ name: "memory_unshare" },
|
|
1368
|
+
);
|
|
1369
|
+
|
|
1079
1370
|
// ─── Tool: skill_search ───
|
|
1080
1371
|
|
|
1081
1372
|
api.registerTool(
|
|
@@ -1083,16 +1374,16 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1083
1374
|
name: "skill_search",
|
|
1084
1375
|
label: "Skill Search",
|
|
1085
1376
|
description:
|
|
1086
|
-
"Search available skills by natural language.
|
|
1087
|
-
"Use when you need a capability or guide and don't have a matching skill at hand.",
|
|
1377
|
+
"Search available skills by natural language. Use scope='mix' (default) for this agent plus local shared skills, 'self' for this agent only, 'public' for local shared skills only, or 'group'/'all' to include Hub skills as well. " +
|
|
1378
|
+
"Use this when you need a capability or guide and don't have a matching skill at hand.",
|
|
1088
1379
|
parameters: Type.Object({
|
|
1089
1380
|
query: Type.String({ description: "Natural language description of the needed skill" }),
|
|
1090
|
-
scope: Type.Optional(Type.String({ description: "Search scope: 'mix'
|
|
1381
|
+
scope: Type.Optional(Type.String({ description: "Search scope: 'mix' (default), 'self', 'public', 'group', or 'all'." })),
|
|
1091
1382
|
}),
|
|
1092
1383
|
execute: trackTool("skill_search", async (_toolCallId: any, params: any, context?: any) => {
|
|
1093
1384
|
const { query: skillQuery, scope: rawScope } = params as { query: string; scope?: string };
|
|
1094
1385
|
const scope = (rawScope === "self" || rawScope === "public") ? rawScope : "mix";
|
|
1095
|
-
const currentOwner =
|
|
1386
|
+
const currentOwner = getCurrentOwner();
|
|
1096
1387
|
|
|
1097
1388
|
if (rawScope === "group" || rawScope === "all") {
|
|
1098
1389
|
const [localHits, hub] = await Promise.all([
|
|
@@ -1108,7 +1399,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1108
1399
|
}
|
|
1109
1400
|
|
|
1110
1401
|
const localText = localHits.length > 0
|
|
1111
|
-
? localHits.map((h, i) => `${i + 1}. [${h.name}] ${h.description.slice(0, 150)}${h.visibility === "public" ? " (
|
|
1402
|
+
? localHits.map((h, i) => `${i + 1}. [${h.name}] ${h.description.slice(0, 150)}${h.visibility === "public" ? " (shared to local agents)" : ""}`).join("\n")
|
|
1112
1403
|
: "(none)";
|
|
1113
1404
|
const hubText = hub.hits.length > 0
|
|
1114
1405
|
? hub.hits.map((h, i) => `${i + 1}. [${h.name}] ${h.description.slice(0, 150)} (${h.visibility}${h.groupName ? `:${h.groupName}` : ""}, owner=${h.ownerName})`).join("\n")
|
|
@@ -1130,7 +1421,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1130
1421
|
}
|
|
1131
1422
|
|
|
1132
1423
|
const text = hits.map((h, i) =>
|
|
1133
|
-
`${i + 1}. [${h.name}] ${h.description}${h.visibility === "public" ? " (
|
|
1424
|
+
`${i + 1}. [${h.name}] ${h.description}${h.visibility === "public" ? " (shared to local agents)" : ""}`,
|
|
1134
1425
|
).join("\n");
|
|
1135
1426
|
|
|
1136
1427
|
return {
|
|
@@ -1148,31 +1439,54 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1148
1439
|
{
|
|
1149
1440
|
name: "skill_publish",
|
|
1150
1441
|
label: "Publish Skill",
|
|
1151
|
-
description:
|
|
1442
|
+
description:
|
|
1443
|
+
"Share a skill with local agents or publish it to the Hub. " +
|
|
1444
|
+
"Use target='agents' for local sharing, or target='hub' with visibility='public'/'group' for Hub publishing. " +
|
|
1445
|
+
"The old scope parameter is still accepted for backward compatibility.",
|
|
1152
1446
|
parameters: Type.Object({
|
|
1153
1447
|
skillId: Type.String({ description: "The skill ID to publish" }),
|
|
1154
|
-
|
|
1448
|
+
target: Type.Optional(Type.String({ description: "Publish target: 'agents' (default) or 'hub'." })),
|
|
1449
|
+
visibility: Type.Optional(Type.String({ description: "Hub visibility when target='hub': 'public' (default) or 'group'." })),
|
|
1450
|
+
scope: Type.Optional(Type.String({ description: "Deprecated alias: omit for local agents, or use 'public' / 'group' to publish to Hub." })),
|
|
1155
1451
|
groupId: Type.Optional(Type.String({ description: "Optional group ID when scope='group'" })),
|
|
1156
1452
|
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1157
1453
|
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
1158
1454
|
}),
|
|
1159
1455
|
execute: trackTool("skill_publish", async (_toolCallId: any, params: any) => {
|
|
1160
|
-
const {
|
|
1456
|
+
const {
|
|
1457
|
+
skillId: pubSkillId,
|
|
1458
|
+
target: rawTarget,
|
|
1459
|
+
visibility: rawVisibility,
|
|
1460
|
+
scope,
|
|
1461
|
+
groupId,
|
|
1462
|
+
hubAddress,
|
|
1463
|
+
userToken,
|
|
1464
|
+
} = params as {
|
|
1465
|
+
skillId: string;
|
|
1466
|
+
target?: string;
|
|
1467
|
+
visibility?: string;
|
|
1468
|
+
scope?: string;
|
|
1469
|
+
groupId?: string;
|
|
1470
|
+
hubAddress?: string;
|
|
1471
|
+
userToken?: string;
|
|
1472
|
+
};
|
|
1161
1473
|
const skill = store.getSkill(pubSkillId);
|
|
1162
1474
|
if (!skill) {
|
|
1163
1475
|
return { content: [{ type: "text", text: `Skill not found: ${pubSkillId}` }] };
|
|
1164
1476
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1477
|
+
const target = resolveSkillPublishTarget(rawTarget, scope);
|
|
1478
|
+
const visibility = resolveSkillHubVisibility(rawVisibility, scope);
|
|
1479
|
+
if (target === "hub") {
|
|
1480
|
+
const published = await publishSkillBundleToHub(store, ctx, { skillId: pubSkillId, visibility, groupId, hubAddress, userToken });
|
|
1167
1481
|
return {
|
|
1168
|
-
content: [{ type: "text", text: `Skill "${skill.name}"
|
|
1169
|
-
details: { skillId: pubSkillId, name: skill.name, publishedToHub: true, hubSkillId: published.skillId, visibility: published.visibility },
|
|
1482
|
+
content: [{ type: "text", text: `Skill "${skill.name}" shared to Hub (${published.visibility}).` }],
|
|
1483
|
+
details: { skillId: pubSkillId, name: skill.name, target, publishedToHub: true, hubSkillId: published.skillId, visibility: published.visibility },
|
|
1170
1484
|
};
|
|
1171
1485
|
}
|
|
1172
1486
|
store.setSkillVisibility(pubSkillId, "public");
|
|
1173
1487
|
return {
|
|
1174
|
-
content: [{ type: "text", text: `Skill "${skill.name}" is now
|
|
1175
|
-
details: { skillId: pubSkillId, name: skill.name, visibility: "public", publishedToHub: false },
|
|
1488
|
+
content: [{ type: "text", text: `Skill "${skill.name}" is now shared with local agents.` }],
|
|
1489
|
+
details: { skillId: pubSkillId, name: skill.name, target, visibility: "public", publishedToHub: false },
|
|
1176
1490
|
};
|
|
1177
1491
|
}),
|
|
1178
1492
|
},
|
|
@@ -1185,20 +1499,46 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1185
1499
|
{
|
|
1186
1500
|
name: "skill_unpublish",
|
|
1187
1501
|
label: "Unpublish Skill",
|
|
1188
|
-
description:
|
|
1502
|
+
description:
|
|
1503
|
+
"Stop sharing a skill with local agents, remove it from the Hub, or do both. " +
|
|
1504
|
+
"Use target='agents' (default), 'hub', or 'all'.",
|
|
1189
1505
|
parameters: Type.Object({
|
|
1190
1506
|
skillId: Type.String({ description: "The skill ID to unpublish" }),
|
|
1507
|
+
target: Type.Optional(Type.String({ description: "Unpublish target: 'agents' (default), 'hub', or 'all'." })),
|
|
1508
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1509
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
1191
1510
|
}),
|
|
1192
1511
|
execute: trackTool("skill_unpublish", async (_toolCallId: any, params: any) => {
|
|
1193
|
-
const { skillId: unpubSkillId } = params as { skillId: string };
|
|
1512
|
+
const { skillId: unpubSkillId, target, hubAddress, userToken } = params as { skillId: string; target?: string; hubAddress?: string; userToken?: string };
|
|
1194
1513
|
const skill = store.getSkill(unpubSkillId);
|
|
1195
1514
|
if (!skill) {
|
|
1196
1515
|
return { content: [{ type: "text", text: `Skill not found: ${unpubSkillId}` }] };
|
|
1197
1516
|
}
|
|
1198
|
-
|
|
1517
|
+
const resolvedTarget = resolveSkillUnpublishTarget(target);
|
|
1518
|
+
const messages: string[] = [];
|
|
1519
|
+
const details: Record<string, unknown> = { skillId: unpubSkillId, name: skill.name, target: resolvedTarget };
|
|
1520
|
+
if (resolvedTarget === "hub" || resolvedTarget === "all") {
|
|
1521
|
+
try {
|
|
1522
|
+
await unpublishSkillBundleFromHub(store, ctx, { skillId: unpubSkillId, hubAddress, userToken });
|
|
1523
|
+
details.hub = { unpublished: true };
|
|
1524
|
+
messages.push("removed from Hub sharing");
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1527
|
+
if (resolvedTarget === "all" && msg.includes("hub client connection is not configured")) {
|
|
1528
|
+
details.hub = { unpublished: false, skipped: true, reason: "hub_not_configured" };
|
|
1529
|
+
} else {
|
|
1530
|
+
throw err;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (resolvedTarget === "agents" || resolvedTarget === "all") {
|
|
1535
|
+
store.setSkillVisibility(unpubSkillId, "private");
|
|
1536
|
+
details.local = { visibility: "private" };
|
|
1537
|
+
messages.push("limited to this agent");
|
|
1538
|
+
}
|
|
1199
1539
|
return {
|
|
1200
|
-
content: [{ type: "text", text: `Skill "${skill.name}"
|
|
1201
|
-
details
|
|
1540
|
+
content: [{ type: "text", text: `Skill "${skill.name}" ${messages.join(" and ")}.` }],
|
|
1541
|
+
details,
|
|
1202
1542
|
};
|
|
1203
1543
|
}),
|
|
1204
1544
|
},
|
|
@@ -1231,6 +1571,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1231
1571
|
// ─── Auto-recall: inject relevant memories before agent starts ───
|
|
1232
1572
|
|
|
1233
1573
|
api.on("before_agent_start", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
|
|
1574
|
+
if (!allowPromptInjection) return {};
|
|
1234
1575
|
if (!event.prompt || event.prompt.length < 3) return;
|
|
1235
1576
|
|
|
1236
1577
|
const recallAgentId = hookCtx?.agentId ?? "main";
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "MemOS Local Memory",
|
|
4
4
|
"description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency). Provides memory_search, memory_get, task_summary, memory_timeline, memory_viewer for layered retrieval.",
|
|
5
5
|
"kind": "memory",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.12",
|
|
7
7
|
"skills": [
|
|
8
8
|
"skill/memos-memory-guide"
|
|
9
9
|
],
|