@memtensor/memos-local-openclaw-plugin 1.0.3 → 1.0.4-beta.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.
- package/README.md +38 -21
- package/dist/client/connector.d.ts +30 -0
- package/dist/client/connector.d.ts.map +1 -0
- package/dist/client/connector.js +219 -0
- package/dist/client/connector.js.map +1 -0
- package/dist/client/hub.d.ts +61 -0
- package/dist/client/hub.d.ts.map +1 -0
- package/dist/client/hub.js +148 -0
- package/dist/client/hub.js.map +1 -0
- package/dist/client/skill-sync.d.ts +29 -0
- package/dist/client/skill-sync.d.ts.map +1 -0
- package/dist/client/skill-sync.js +216 -0
- package/dist/client/skill-sync.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -3
- package/dist/config.js.map +1 -1
- package/dist/embedding/index.d.ts +4 -2
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +17 -1
- package/dist/embedding/index.js.map +1 -1
- package/dist/hub/auth.d.ts +19 -0
- package/dist/hub/auth.d.ts.map +1 -0
- package/dist/hub/auth.js +70 -0
- package/dist/hub/auth.js.map +1 -0
- package/dist/hub/server.d.ts +41 -0
- package/dist/hub/server.d.ts.map +1 -0
- package/dist/hub/server.js +747 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/user-manager.d.ts +29 -0
- package/dist/hub/user-manager.d.ts.map +1 -0
- package/dist/hub/user-manager.js +125 -0
- package/dist/hub/user-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +10 -2
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +203 -6
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +1 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +1 -0
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.js +1 -1
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/openclaw-api.d.ts +53 -0
- package/dist/openclaw-api.d.ts.map +1 -0
- package/dist/openclaw-api.js +189 -0
- package/dist/openclaw-api.js.map +1 -0
- package/dist/recall/engine.js +1 -1
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +4 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +15 -0
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.contract.d.ts +2 -0
- package/dist/sharing/types.contract.d.ts.map +1 -0
- package/dist/sharing/types.contract.js +3 -0
- package/dist/sharing/types.contract.js.map +1 -0
- package/dist/sharing/types.d.ts +80 -0
- package/dist/sharing/types.d.ts.map +1 -0
- package/dist/sharing/types.js +3 -0
- package/dist/sharing/types.js.map +1 -0
- package/dist/skill/evaluator.d.ts.map +1 -1
- package/dist/skill/evaluator.js +2 -2
- package/dist/skill/evaluator.js.map +1 -1
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +4 -4
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/upgrader.js +1 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.js +1 -1
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +294 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +828 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -2
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +48 -7
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/tools/network-memory-detail.d.ts +4 -0
- package/dist/tools/network-memory-detail.d.ts.map +1 -0
- package/dist/tools/network-memory-detail.js +34 -0
- package/dist/tools/network-memory-detail.js.map +1 -0
- package/dist/types.d.ts +47 -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 +2595 -345
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +45 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1153 -15
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +428 -16
- package/openclaw.plugin.json +2 -1
- package/package.json +3 -3
- package/scripts/postinstall.cjs +282 -45
- package/skill/memos-memory-guide/SKILL.md +26 -2
- package/src/client/connector.ts +218 -0
- package/src/client/hub.ts +189 -0
- package/src/client/skill-sync.ts +202 -0
- package/src/config.ts +92 -3
- package/src/embedding/index.ts +21 -1
- package/src/hub/auth.ts +78 -0
- package/src/hub/server.ts +740 -0
- package/src/hub/user-manager.ts +139 -0
- package/src/index.ts +7 -4
- package/src/ingest/providers/index.ts +240 -6
- package/src/ingest/providers/openai.ts +1 -1
- package/src/ingest/task-processor.ts +1 -1
- package/src/openclaw-api.ts +287 -0
- package/src/recall/engine.ts +1 -1
- package/src/shared/llm-call.ts +19 -1
- package/src/sharing/types.contract.ts +40 -0
- package/src/sharing/types.ts +102 -0
- package/src/skill/evaluator.ts +3 -2
- package/src/skill/generator.ts +6 -4
- package/src/skill/upgrader.ts +1 -1
- package/src/skill/validator.ts +1 -1
- package/src/storage/sqlite.ts +1093 -7
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-search.ts +57 -8
- package/src/tools/network-memory-detail.ts +34 -0
- package/src/types.ts +42 -2
- package/src/viewer/html.ts +2595 -345
- package/src/viewer/server.ts +1068 -18
- package/dist/ingest/extra-paths.d.ts +0 -13
- package/dist/ingest/extra-paths.d.ts.map +0 -1
- package/dist/ingest/extra-paths.js +0 -173
- package/dist/ingest/extra-paths.js.map +0 -1
package/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
12
|
import { buildContext } from "./src/config";
|
|
13
|
+
import type { HostModelsConfig } from "./src/openclaw-api";
|
|
13
14
|
import { SqliteStore } from "./src/storage/sqlite";
|
|
14
15
|
import { Embedder } from "./src/embedding";
|
|
15
16
|
import { IngestWorker } from "./src/ingest/worker";
|
|
@@ -17,6 +18,10 @@ import { RecallEngine } from "./src/recall/engine";
|
|
|
17
18
|
import { captureMessages, stripInboundMetadata } from "./src/capture";
|
|
18
19
|
import { DEFAULTS } from "./src/types";
|
|
19
20
|
import { ViewerServer } from "./src/viewer/server";
|
|
21
|
+
import { HubServer } from "./src/hub/server";
|
|
22
|
+
import { hubGetMemoryDetail, hubRequestJson, hubSearchMemories, hubSearchSkills, resolveHubClient } from "./src/client/hub";
|
|
23
|
+
import { getHubStatus, connectToHub } from "./src/client/connector";
|
|
24
|
+
import { fetchHubSkillBundle, publishSkillBundleToHub, restoreSkillBundleFromHub } from "./src/client/skill-sync";
|
|
20
25
|
import { SkillEvolver } from "./src/skill/evolver";
|
|
21
26
|
import { SkillInstaller } from "./src/skill/installer";
|
|
22
27
|
import { Summarizer } from "./src/ingest/providers";
|
|
@@ -164,17 +169,35 @@ const memosLocalPlugin = {
|
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
let pluginCfg = (api.pluginConfig ?? {}) as Record<string, unknown>;
|
|
168
173
|
const stateDir = api.resolvePath("~/.openclaw");
|
|
174
|
+
|
|
175
|
+
// Fallback: read config from file if not provided by OpenClaw
|
|
176
|
+
const configPath = path.join(stateDir, "state", "memos-local", "config.json");
|
|
177
|
+
if (Object.keys(pluginCfg).length === 0 && fs.existsSync(configPath)) {
|
|
178
|
+
try {
|
|
179
|
+
const fileConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
180
|
+
pluginCfg = fileConfig;
|
|
181
|
+
api.logger.info(`memos-local: loaded config from ${configPath}`);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
api.logger.warn(`memos-local: failed to load config from ${configPath}: ${e}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Extract host model providers so OpenClawAPIClient can proxy completion/embedding
|
|
188
|
+
const hostModels: HostModelsConfig | undefined = api.config?.models?.providers
|
|
189
|
+
? { providers: api.config.models.providers as Record<string, import("./src/openclaw-api").HostModelProvider> }
|
|
190
|
+
: undefined;
|
|
191
|
+
|
|
169
192
|
const ctx = buildContext(stateDir, process.cwd(), pluginCfg as any, {
|
|
170
193
|
debug: (msg: string) => api.logger.info(`[debug] ${msg}`),
|
|
171
194
|
info: (msg: string) => api.logger.info(msg),
|
|
172
195
|
warn: (msg: string) => api.logger.warn(msg),
|
|
173
196
|
error: (msg: string) => api.logger.warn(`[error] ${msg}`),
|
|
174
|
-
});
|
|
197
|
+
}, hostModels);
|
|
175
198
|
|
|
176
199
|
const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log);
|
|
177
|
-
const embedder = new Embedder(ctx.config.embedding, ctx.log);
|
|
200
|
+
const embedder = new Embedder(ctx.config.embedding, ctx.log, ctx.openclawAPI);
|
|
178
201
|
const worker = new IngestWorker(store, embedder, ctx);
|
|
179
202
|
const engine = new RecallEngine(store, embedder, ctx);
|
|
180
203
|
const evidenceTag = ctx.config.capture?.evidenceWrapperTag ?? DEFAULTS.evidenceWrapperTag;
|
|
@@ -238,7 +261,7 @@ const memosLocalPlugin = {
|
|
|
238
261
|
});
|
|
239
262
|
});
|
|
240
263
|
|
|
241
|
-
const summarizer = new Summarizer(ctx.config.summarizer, ctx.log);
|
|
264
|
+
const summarizer = new Summarizer(ctx.config.summarizer, ctx.log, ctx.openclawAPI);
|
|
242
265
|
|
|
243
266
|
api.logger.info(`memos-local: initialized (db: ${ctx.config.storage!.dbPath})`);
|
|
244
267
|
|
|
@@ -296,6 +319,10 @@ const memosLocalPlugin = {
|
|
|
296
319
|
const { query } = params as { query: string };
|
|
297
320
|
const role = undefined;
|
|
298
321
|
const minScore = undefined;
|
|
322
|
+
const searchScope = "local";
|
|
323
|
+
const searchLimit = 10;
|
|
324
|
+
const hubAddress: string | undefined = undefined;
|
|
325
|
+
const userToken: string | undefined = undefined;
|
|
299
326
|
|
|
300
327
|
const agentId = currentAgentId;
|
|
301
328
|
const ownerFilter = [`agent:${agentId}`, "public"];
|
|
@@ -319,7 +346,6 @@ const memosLocalPlugin = {
|
|
|
319
346
|
};
|
|
320
347
|
}
|
|
321
348
|
|
|
322
|
-
// LLM relevance + sufficiency filtering
|
|
323
349
|
let filteredHits = result.hits;
|
|
324
350
|
let sufficient = false;
|
|
325
351
|
|
|
@@ -345,6 +371,51 @@ const memosLocalPlugin = {
|
|
|
345
371
|
}
|
|
346
372
|
}
|
|
347
373
|
|
|
374
|
+
const beforeDedup = filteredHits.length;
|
|
375
|
+
filteredHits = deduplicateHits(filteredHits);
|
|
376
|
+
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredHits.length}`);
|
|
377
|
+
|
|
378
|
+
const localDetailsHits = filteredHits.map((h) => {
|
|
379
|
+
let effectiveTaskId = h.taskId;
|
|
380
|
+
if (effectiveTaskId) {
|
|
381
|
+
const t = store.getTask(effectiveTaskId);
|
|
382
|
+
if (t && t.status === "skipped") effectiveTaskId = null;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
ref: h.ref,
|
|
386
|
+
chunkId: h.ref.chunkId,
|
|
387
|
+
taskId: effectiveTaskId,
|
|
388
|
+
skillId: h.skillId,
|
|
389
|
+
role: h.source.role,
|
|
390
|
+
score: h.score,
|
|
391
|
+
summary: h.summary,
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (searchScope !== "local") {
|
|
396
|
+
const hub = await hubSearchMemories(store, ctx, { query, maxResults: searchLimit, scope: searchScope as any, hubAddress, userToken }).catch(() => ({ hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: searchScope === "all" } }));
|
|
397
|
+
const localText = filteredHits.length > 0
|
|
398
|
+
? filteredHits.map((h, i) => {
|
|
399
|
+
const excerpt = h.original_excerpt.length > 220 ? h.original_excerpt.slice(0, 217) + "..." : h.original_excerpt;
|
|
400
|
+
return `${i + 1}. [${h.source.role}] ${excerpt}`;
|
|
401
|
+
}).join("\n")
|
|
402
|
+
: "(none)";
|
|
403
|
+
const hubText = hub.hits.length > 0
|
|
404
|
+
? hub.hits.map((h, i) => `${i + 1}. [${h.ownerName}] ${h.summary}${h.groupName ? ` (${h.groupName})` : ""}`).join("\n")
|
|
405
|
+
: "(none)";
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
content: [{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: `Local results:\n${localText}\n\nHub results:\n${hubText}`,
|
|
411
|
+
}],
|
|
412
|
+
details: {
|
|
413
|
+
local: { hits: localDetailsHits, meta: result.meta },
|
|
414
|
+
hub,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
348
419
|
if (filteredHits.length === 0) {
|
|
349
420
|
return {
|
|
350
421
|
content: [{ type: "text", text: "No relevant memories found for this query." }],
|
|
@@ -352,10 +423,6 @@ const memosLocalPlugin = {
|
|
|
352
423
|
};
|
|
353
424
|
}
|
|
354
425
|
|
|
355
|
-
const beforeDedup = filteredHits.length;
|
|
356
|
-
filteredHits = deduplicateHits(filteredHits);
|
|
357
|
-
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredHits.length}`);
|
|
358
|
-
|
|
359
426
|
const lines = filteredHits.map((h, i) => {
|
|
360
427
|
const excerpt = h.original_excerpt;
|
|
361
428
|
const parts = [`${i + 1}. [${h.source.role}]`];
|
|
@@ -448,7 +515,7 @@ const memosLocalPlugin = {
|
|
|
448
515
|
if (!anchorChunk) {
|
|
449
516
|
return {
|
|
450
517
|
content: [{ type: "text", text: `Chunk not found: ${chunkId}` }],
|
|
451
|
-
details: { error: "not_found" },
|
|
518
|
+
details: { error: "not_found", entries: [] },
|
|
452
519
|
};
|
|
453
520
|
}
|
|
454
521
|
|
|
@@ -497,7 +564,7 @@ const memosLocalPlugin = {
|
|
|
497
564
|
Type.Number({ description: `Max chars (default ${DEFAULTS.getMaxCharsDefault}, max ${DEFAULTS.getMaxCharsMax})` }),
|
|
498
565
|
),
|
|
499
566
|
}),
|
|
500
|
-
execute: trackTool("memory_get", async (_toolCallId: any, params: any) => {
|
|
567
|
+
execute: trackTool("memory_get", async (_toolCallId: any, params: any, context?: any) => {
|
|
501
568
|
const { chunkId, maxChars } = params as { chunkId: string; maxChars?: number };
|
|
502
569
|
const limit = Math.min(maxChars ?? DEFAULTS.getMaxCharsDefault, DEFAULTS.getMaxCharsMax);
|
|
503
570
|
|
|
@@ -604,6 +671,207 @@ const memosLocalPlugin = {
|
|
|
604
671
|
{ name: "task_summary" },
|
|
605
672
|
);
|
|
606
673
|
|
|
674
|
+
// ─── Tool: task_share ───
|
|
675
|
+
|
|
676
|
+
api.registerTool(
|
|
677
|
+
{
|
|
678
|
+
name: "task_share",
|
|
679
|
+
label: "Task Share",
|
|
680
|
+
description:
|
|
681
|
+
"Share one existing local task and its chunks to the configured hub. " +
|
|
682
|
+
"Minimal MVP path for validating team task sharing.",
|
|
683
|
+
parameters: Type.Object({
|
|
684
|
+
taskId: Type.String({ description: "Local task ID to share" }),
|
|
685
|
+
visibility: Type.Optional(Type.String({ description: "Share visibility: 'public' (default) or 'group'" })),
|
|
686
|
+
groupId: Type.Optional(Type.String({ description: "Optional group ID when visibility='group'" })),
|
|
687
|
+
}),
|
|
688
|
+
execute: trackTool("task_share", async (_toolCallId: any, params: any) => {
|
|
689
|
+
const { taskId, visibility: rawVisibility, groupId } = params as {
|
|
690
|
+
taskId: string;
|
|
691
|
+
visibility?: string;
|
|
692
|
+
groupId?: string;
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const task = store.getTask(taskId);
|
|
696
|
+
if (!task) {
|
|
697
|
+
return {
|
|
698
|
+
content: [{ type: "text", text: `Task not found: ${taskId}` }],
|
|
699
|
+
details: { error: "not_found", taskId },
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const chunks = store.getChunksByTask(taskId);
|
|
704
|
+
if (chunks.length === 0) {
|
|
705
|
+
return {
|
|
706
|
+
content: [{ type: "text", text: `Task ${taskId} has no chunks to share.` }],
|
|
707
|
+
details: { error: "no_chunks", taskId },
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const visibility = rawVisibility === "group" ? "group" : "public";
|
|
712
|
+
const hubClient = await resolveHubClient(store, ctx);
|
|
713
|
+
const { v4: uuidv4 } = require("uuid");
|
|
714
|
+
const hubTaskId = uuidv4();
|
|
715
|
+
|
|
716
|
+
const response = await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
717
|
+
method: "POST",
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
task: {
|
|
720
|
+
id: hubTaskId,
|
|
721
|
+
sourceTaskId: task.id,
|
|
722
|
+
sourceUserId: hubClient.userId,
|
|
723
|
+
title: task.title,
|
|
724
|
+
summary: task.summary,
|
|
725
|
+
groupId: visibility === "group" ? (groupId ?? null) : null,
|
|
726
|
+
visibility,
|
|
727
|
+
createdAt: task.startedAt,
|
|
728
|
+
updatedAt: task.updatedAt,
|
|
729
|
+
},
|
|
730
|
+
chunks: chunks.map((chunk) => ({
|
|
731
|
+
id: uuidv4(),
|
|
732
|
+
hubTaskId,
|
|
733
|
+
sourceTaskId: task.id,
|
|
734
|
+
sourceChunkId: chunk.id,
|
|
735
|
+
sourceUserId: hubClient.userId,
|
|
736
|
+
role: chunk.role,
|
|
737
|
+
content: chunk.content,
|
|
738
|
+
summary: chunk.summary,
|
|
739
|
+
kind: chunk.kind,
|
|
740
|
+
createdAt: chunk.createdAt,
|
|
741
|
+
})),
|
|
742
|
+
}),
|
|
743
|
+
}) as any;
|
|
744
|
+
|
|
745
|
+
store.markTaskShared(task.id, hubTaskId, chunks.length, visibility, groupId);
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: "text", text: `Shared task "${task.title}" with ${chunks.length} chunks to the hub.` }],
|
|
749
|
+
details: {
|
|
750
|
+
shared: true,
|
|
751
|
+
taskId: task.id,
|
|
752
|
+
visibility,
|
|
753
|
+
chunkCount: chunks.length,
|
|
754
|
+
hubUrl: hubClient.hubUrl,
|
|
755
|
+
response,
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
}),
|
|
759
|
+
},
|
|
760
|
+
{ name: "task_share" },
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// ─── Tool: task_unshare ───
|
|
764
|
+
|
|
765
|
+
api.registerTool(
|
|
766
|
+
{
|
|
767
|
+
name: "task_unshare",
|
|
768
|
+
label: "Task Unshare",
|
|
769
|
+
description: "Remove one previously shared task from the configured hub.",
|
|
770
|
+
parameters: Type.Object({
|
|
771
|
+
taskId: Type.String({ description: "Local task ID to unshare" }),
|
|
772
|
+
}),
|
|
773
|
+
execute: trackTool("task_unshare", async (_toolCallId: any, params: any) => {
|
|
774
|
+
const { taskId } = params as { taskId: string };
|
|
775
|
+
|
|
776
|
+
const task = store.getTask(taskId);
|
|
777
|
+
if (!task) {
|
|
778
|
+
return {
|
|
779
|
+
content: [{ type: "text", text: `Task not found: ${taskId}` }],
|
|
780
|
+
details: { error: "not_found", taskId },
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const hubClient = await resolveHubClient(store, ctx);
|
|
785
|
+
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
786
|
+
method: "POST",
|
|
787
|
+
body: JSON.stringify({
|
|
788
|
+
sourceUserId: hubClient.userId,
|
|
789
|
+
sourceTaskId: task.id,
|
|
790
|
+
}),
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
store.unmarkTaskShared(task.id);
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
content: [{ type: "text", text: `Unshared task "${task.title}" from the hub.` }],
|
|
797
|
+
details: {
|
|
798
|
+
unshared: true,
|
|
799
|
+
taskId: task.id,
|
|
800
|
+
hubUrl: hubClient.hubUrl,
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
}),
|
|
804
|
+
},
|
|
805
|
+
{ name: "task_unshare" },
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
api.registerTool(
|
|
809
|
+
{
|
|
810
|
+
name: "network_memory_detail",
|
|
811
|
+
label: "Network Memory Detail",
|
|
812
|
+
description: "Fetch the full detail for a Hub search hit returned by memory_search(scope=group|all).",
|
|
813
|
+
parameters: Type.Object({
|
|
814
|
+
remoteHitId: Type.String({ description: "The remoteHitId returned by a Hub search hit" }),
|
|
815
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
816
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
817
|
+
}),
|
|
818
|
+
execute: trackTool("network_memory_detail", async (_toolCallId: any, params: any) => {
|
|
819
|
+
const { remoteHitId, hubAddress, userToken } = params as {
|
|
820
|
+
remoteHitId: string;
|
|
821
|
+
hubAddress?: string;
|
|
822
|
+
userToken?: string;
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const detail = await hubGetMemoryDetail(store, ctx, { remoteHitId, hubAddress, userToken });
|
|
826
|
+
return {
|
|
827
|
+
content: [{
|
|
828
|
+
type: "text",
|
|
829
|
+
text: `## Shared Memory Detail
|
|
830
|
+
|
|
831
|
+
${detail.summary}
|
|
832
|
+
|
|
833
|
+
${detail.content}`,
|
|
834
|
+
}],
|
|
835
|
+
details: detail,
|
|
836
|
+
};
|
|
837
|
+
}),
|
|
838
|
+
},
|
|
839
|
+
{ name: "network_memory_detail" },
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
api.registerTool(
|
|
843
|
+
{
|
|
844
|
+
name: "network_team_info",
|
|
845
|
+
label: "Network Team Info",
|
|
846
|
+
description: "Show current Hub connection status, signed-in user, role, and group memberships.",
|
|
847
|
+
parameters: Type.Object({}),
|
|
848
|
+
execute: trackTool("network_team_info", async () => {
|
|
849
|
+
const status = await getHubStatus(store, ctx.config);
|
|
850
|
+
if (!status.connected || !status.user) {
|
|
851
|
+
return {
|
|
852
|
+
content: [{ type: "text", text: "Hub is not connected." }],
|
|
853
|
+
details: status,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const groupNames = status.user.groups.map((group) => group.name);
|
|
858
|
+
return {
|
|
859
|
+
content: [{
|
|
860
|
+
type: "text",
|
|
861
|
+
text: `## Team Connection
|
|
862
|
+
|
|
863
|
+
User: ${status.user.username}
|
|
864
|
+
Role: ${status.user.role}
|
|
865
|
+
Hub: ${status.hubUrl ?? "(unknown)"}
|
|
866
|
+
Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
867
|
+
}],
|
|
868
|
+
details: status,
|
|
869
|
+
};
|
|
870
|
+
}),
|
|
871
|
+
},
|
|
872
|
+
{ name: "network_team_info" },
|
|
873
|
+
);
|
|
874
|
+
|
|
607
875
|
// ─── Tool: skill_get ───
|
|
608
876
|
|
|
609
877
|
api.registerTool(
|
|
@@ -815,17 +1083,43 @@ const memosLocalPlugin = {
|
|
|
815
1083
|
name: "skill_search",
|
|
816
1084
|
label: "Skill Search",
|
|
817
1085
|
description:
|
|
818
|
-
"Search available skills by natural language. Searches
|
|
1086
|
+
"Search available skills by natural language. Searches local skills by default, or local + Hub skills when scope=group/all. " +
|
|
819
1087
|
"Use when you need a capability or guide and don't have a matching skill at hand.",
|
|
820
1088
|
parameters: Type.Object({
|
|
821
1089
|
query: Type.String({ description: "Natural language description of the needed skill" }),
|
|
822
|
-
scope: Type.Optional(Type.String({ description: "Search scope: 'mix'
|
|
1090
|
+
scope: Type.Optional(Type.String({ description: "Search scope: 'mix'/'self'/'public' for local search, or 'group'/'all' for local + Hub search" })),
|
|
823
1091
|
}),
|
|
824
|
-
execute: trackTool("skill_search", async (_toolCallId: any, params: any) => {
|
|
1092
|
+
execute: trackTool("skill_search", async (_toolCallId: any, params: any, context?: any) => {
|
|
825
1093
|
const { query: skillQuery, scope: rawScope } = params as { query: string; scope?: string };
|
|
826
1094
|
const scope = (rawScope === "self" || rawScope === "public") ? rawScope : "mix";
|
|
827
1095
|
const currentOwner = `agent:${currentAgentId}`;
|
|
828
1096
|
|
|
1097
|
+
if (rawScope === "group" || rawScope === "all") {
|
|
1098
|
+
const [localHits, hub] = await Promise.all([
|
|
1099
|
+
engine.searchSkills(skillQuery, "mix" as any, currentOwner),
|
|
1100
|
+
hubSearchSkills(store, ctx, { query: skillQuery, maxResults: 10 }).catch(() => ({ hits: [] })),
|
|
1101
|
+
]);
|
|
1102
|
+
|
|
1103
|
+
if (localHits.length === 0 && hub.hits.length === 0) {
|
|
1104
|
+
return {
|
|
1105
|
+
content: [{ type: "text", text: `No relevant skills found for: "${skillQuery}" (scope: ${rawScope})` }],
|
|
1106
|
+
details: { query: skillQuery, scope: rawScope, local: { hits: [] }, hub },
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const localText = localHits.length > 0
|
|
1111
|
+
? localHits.map((h, i) => `${i + 1}. [${h.name}] ${h.description.slice(0, 150)}${h.visibility === "public" ? " (public)" : ""}`).join("\n")
|
|
1112
|
+
: "(none)";
|
|
1113
|
+
const hubText = hub.hits.length > 0
|
|
1114
|
+
? 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")
|
|
1115
|
+
: "(none)";
|
|
1116
|
+
|
|
1117
|
+
return {
|
|
1118
|
+
content: [{ type: "text", text: `Local skills:\n${localText}\n\nHub skills:\n${hubText}` }],
|
|
1119
|
+
details: { query: skillQuery, scope: rawScope, local: { hits: localHits }, hub },
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
829
1123
|
const hits = await engine.searchSkills(skillQuery, scope as any, currentOwner);
|
|
830
1124
|
|
|
831
1125
|
if (hits.length === 0) {
|
|
@@ -857,17 +1151,28 @@ const memosLocalPlugin = {
|
|
|
857
1151
|
description: "Make a skill public so other agents can discover and install it via skill_search.",
|
|
858
1152
|
parameters: Type.Object({
|
|
859
1153
|
skillId: Type.String({ description: "The skill ID to publish" }),
|
|
1154
|
+
scope: Type.Optional(Type.String({ description: "Publish scope: omit for local public, or use 'public' / 'group' to publish to Hub" })),
|
|
1155
|
+
groupId: Type.Optional(Type.String({ description: "Optional group ID when scope='group'" })),
|
|
1156
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1157
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
860
1158
|
}),
|
|
861
1159
|
execute: trackTool("skill_publish", async (_toolCallId: any, params: any) => {
|
|
862
|
-
const { skillId: pubSkillId } = params as { skillId: string };
|
|
1160
|
+
const { skillId: pubSkillId, scope, groupId, hubAddress, userToken } = params as { skillId: string; scope?: string; groupId?: string; hubAddress?: string; userToken?: string };
|
|
863
1161
|
const skill = store.getSkill(pubSkillId);
|
|
864
1162
|
if (!skill) {
|
|
865
1163
|
return { content: [{ type: "text", text: `Skill not found: ${pubSkillId}` }] };
|
|
866
1164
|
}
|
|
1165
|
+
if (scope === "public" || scope === "group") {
|
|
1166
|
+
const published = await publishSkillBundleToHub(store, ctx, { skillId: pubSkillId, visibility: scope, groupId, hubAddress, userToken });
|
|
1167
|
+
return {
|
|
1168
|
+
content: [{ type: "text", text: `Skill "${skill.name}" published to hub (${published.visibility}).` }],
|
|
1169
|
+
details: { skillId: pubSkillId, name: skill.name, publishedToHub: true, hubSkillId: published.skillId, visibility: published.visibility },
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
867
1172
|
store.setSkillVisibility(pubSkillId, "public");
|
|
868
1173
|
return {
|
|
869
1174
|
content: [{ type: "text", text: `Skill "${skill.name}" is now public.` }],
|
|
870
|
-
details: { skillId: pubSkillId, name: skill.name, visibility: "public" },
|
|
1175
|
+
details: { skillId: pubSkillId, name: skill.name, visibility: "public", publishedToHub: false },
|
|
871
1176
|
};
|
|
872
1177
|
}),
|
|
873
1178
|
},
|
|
@@ -900,6 +1205,29 @@ const memosLocalPlugin = {
|
|
|
900
1205
|
{ name: "skill_unpublish" },
|
|
901
1206
|
);
|
|
902
1207
|
|
|
1208
|
+
api.registerTool(
|
|
1209
|
+
{
|
|
1210
|
+
name: "network_skill_pull",
|
|
1211
|
+
label: "Network Skill Pull",
|
|
1212
|
+
description: "Download a published Hub skill bundle and restore it into local managed skills.",
|
|
1213
|
+
parameters: Type.Object({
|
|
1214
|
+
skillId: Type.String({ description: "The Hub skill ID to pull" }),
|
|
1215
|
+
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1216
|
+
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
1217
|
+
}),
|
|
1218
|
+
execute: trackTool("network_skill_pull", async (_toolCallId: any, params: any) => {
|
|
1219
|
+
const { skillId, hubAddress, userToken } = params as { skillId: string; hubAddress?: string; userToken?: string };
|
|
1220
|
+
const payload = await fetchHubSkillBundle(store, ctx, { skillId, hubAddress, userToken });
|
|
1221
|
+
const restored = restoreSkillBundleFromHub(store, ctx, payload);
|
|
1222
|
+
return {
|
|
1223
|
+
content: [{ type: "text", text: `Pulled Hub skill "${restored.localName}" into local storage.` }],
|
|
1224
|
+
details: { pulled: true, hubSkillId: skillId, localSkillId: restored.localSkillId, localName: restored.localName, dirPath: restored.dirPath },
|
|
1225
|
+
};
|
|
1226
|
+
}),
|
|
1227
|
+
},
|
|
1228
|
+
{ name: "network_skill_pull" },
|
|
1229
|
+
);
|
|
1230
|
+
|
|
903
1231
|
// ─── Auto-recall: inject relevant memories before agent starts ───
|
|
904
1232
|
|
|
905
1233
|
api.on("before_agent_start", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
|
|
@@ -1204,11 +1532,74 @@ const memosLocalPlugin = {
|
|
|
1204
1532
|
worker.enqueue(captured);
|
|
1205
1533
|
telemetry.trackMemoryIngested(captured.length);
|
|
1206
1534
|
}
|
|
1535
|
+
|
|
1536
|
+
// Incremental push: sync new chunks for already-shared tasks
|
|
1537
|
+
syncSharedTasksIncremental().catch((err) => {
|
|
1538
|
+
ctx.log.warn(`incremental sync failed: ${err}`);
|
|
1539
|
+
});
|
|
1207
1540
|
} catch (err) {
|
|
1208
1541
|
api.logger.warn(`memos-local: capture failed: ${String(err)}`);
|
|
1209
1542
|
}
|
|
1210
1543
|
});
|
|
1211
1544
|
|
|
1545
|
+
async function syncSharedTasksIncremental(): Promise<void> {
|
|
1546
|
+
if (!ctx.config.sharing?.enabled || ctx.config.sharing.role !== "client") return;
|
|
1547
|
+
const shared = store.listLocalSharedTasks();
|
|
1548
|
+
if (shared.length === 0) return;
|
|
1549
|
+
|
|
1550
|
+
let hubClient: { hubUrl: string; userToken: string; userId: string } | undefined;
|
|
1551
|
+
try {
|
|
1552
|
+
hubClient = await resolveHubClient(store, ctx);
|
|
1553
|
+
} catch {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const { v4: uuidv4 } = require("uuid");
|
|
1557
|
+
|
|
1558
|
+
for (const entry of shared) {
|
|
1559
|
+
const task = store.getTask(entry.taskId);
|
|
1560
|
+
if (!task) continue;
|
|
1561
|
+
const chunks = store.getChunksByTask(entry.taskId);
|
|
1562
|
+
if (chunks.length <= entry.syncedChunks) continue;
|
|
1563
|
+
|
|
1564
|
+
const newChunks = chunks.slice(entry.syncedChunks);
|
|
1565
|
+
ctx.log.info(`incremental sync: task=${entry.taskId} pushing ${newChunks.length} new chunk(s)`);
|
|
1566
|
+
|
|
1567
|
+
try {
|
|
1568
|
+
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1569
|
+
method: "POST",
|
|
1570
|
+
body: JSON.stringify({
|
|
1571
|
+
task: {
|
|
1572
|
+
id: entry.hubTaskId,
|
|
1573
|
+
sourceTaskId: entry.taskId,
|
|
1574
|
+
sourceUserId: hubClient.userId,
|
|
1575
|
+
title: task.title,
|
|
1576
|
+
summary: task.summary,
|
|
1577
|
+
groupId: entry.visibility === "group" ? entry.groupId ?? null : null,
|
|
1578
|
+
visibility: entry.visibility,
|
|
1579
|
+
createdAt: task.startedAt ?? task.updatedAt ?? Date.now(),
|
|
1580
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
1581
|
+
},
|
|
1582
|
+
chunks: newChunks.map((chunk) => ({
|
|
1583
|
+
id: uuidv4(),
|
|
1584
|
+
hubTaskId: entry.hubTaskId,
|
|
1585
|
+
sourceTaskId: entry.taskId,
|
|
1586
|
+
sourceChunkId: chunk.id,
|
|
1587
|
+
sourceUserId: hubClient.userId,
|
|
1588
|
+
role: chunk.role,
|
|
1589
|
+
content: chunk.content,
|
|
1590
|
+
summary: chunk.summary,
|
|
1591
|
+
kind: chunk.kind,
|
|
1592
|
+
createdAt: chunk.createdAt,
|
|
1593
|
+
})),
|
|
1594
|
+
}),
|
|
1595
|
+
});
|
|
1596
|
+
store.markTaskShared(entry.taskId, entry.hubTaskId, chunks.length, entry.visibility, entry.groupId);
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
ctx.log.warn(`incremental sync failed for task=${entry.taskId}: ${err}`);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1212
1603
|
// ─── Memory Viewer (web UI) ───
|
|
1213
1604
|
|
|
1214
1605
|
const viewer = new ViewerServer({
|
|
@@ -1220,11 +1611,30 @@ const memosLocalPlugin = {
|
|
|
1220
1611
|
ctx,
|
|
1221
1612
|
});
|
|
1222
1613
|
|
|
1614
|
+
const hubServer = ctx.config.sharing?.enabled && ctx.config.sharing.role === "hub"
|
|
1615
|
+
? new HubServer({ store, log: ctx.log, config: ctx.config, dataDir: stateDir, embedder })
|
|
1616
|
+
: null;
|
|
1617
|
+
|
|
1223
1618
|
// ─── Service lifecycle ───
|
|
1224
1619
|
|
|
1225
1620
|
api.registerService({
|
|
1226
1621
|
id: "memos-local-openclaw-plugin",
|
|
1227
1622
|
start: async () => {
|
|
1623
|
+
if (hubServer) {
|
|
1624
|
+
const hubUrl = await hubServer.start();
|
|
1625
|
+
api.logger.info(`memos-local: hub started at ${hubUrl}`);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Auto-connect to Hub in client mode (handles both existing token and auto-join via teamToken)
|
|
1629
|
+
if (ctx.config.sharing?.enabled && ctx.config.sharing.role === "client") {
|
|
1630
|
+
try {
|
|
1631
|
+
const session = await connectToHub(store, ctx.config, ctx.log);
|
|
1632
|
+
api.logger.info(`memos-local: connected to Hub as "${session.username}" (${session.userId})`);
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
api.logger.warn(`memos-local: Hub connection failed: ${err}`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1228
1638
|
try {
|
|
1229
1639
|
const viewerUrl = await viewer.start();
|
|
1230
1640
|
api.logger.info(`memos-local: started (embedding: ${embedder.provider})`);
|
|
@@ -1250,7 +1660,9 @@ const memosLocalPlugin = {
|
|
|
1250
1660
|
);
|
|
1251
1661
|
},
|
|
1252
1662
|
stop: async () => {
|
|
1663
|
+
await worker.flush();
|
|
1253
1664
|
await telemetry.shutdown();
|
|
1665
|
+
await hubServer?.stop();
|
|
1254
1666
|
viewer.stop();
|
|
1255
1667
|
store.close();
|
|
1256
1668
|
api.logger.info("memos-local: stopped");
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memtensor/memos-local-openclaw-plugin",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MemOS Local memory plugin for OpenClaw
|
|
3
|
+
"version": "1.0.4-beta.1",
|
|
4
|
+
"description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -64,4 +64,4 @@
|
|
|
64
64
|
"typescript": "^5.7.0",
|
|
65
65
|
"vitest": "^2.1.0"
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|