@memtensor/memos-local-openclaw-plugin 1.0.4-beta.4 → 1.0.4-beta.6
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 +22 -39
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +6 -0
- package/dist/capture/index.js.map +1 -1
- package/dist/config.d.ts +1 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -72
- package/dist/config.js.map +1 -1
- package/dist/embedding/index.d.ts +2 -4
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +1 -17
- package/dist/embedding/index.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +2 -10
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +43 -209
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +0 -1
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +0 -1
- 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/recall/engine.js +1 -1
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +2 -4
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +81 -20
- package/dist/shared/llm-call.js.map +1 -1
- 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/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/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/ensure-binding.d.ts.map +1 -1
- package/dist/storage/ensure-binding.js +1 -3
- package/dist/storage/ensure-binding.js.map +1 -1
- package/dist/storage/sqlite.d.ts +0 -294
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +0 -821
- 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 +135 -38
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory-search.d.ts +2 -3
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +7 -48
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/types.d.ts +2 -49
- 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 +471 -2974
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +0 -45
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +18 -1155
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +42 -430
- package/openclaw.plugin.json +1 -2
- package/package.json +3 -4
- package/scripts/postinstall.cjs +46 -283
- package/skill/memos-memory-guide/SKILL.md +2 -26
- package/src/capture/index.ts +8 -0
- package/src/config.ts +3 -94
- package/src/embedding/index.ts +1 -21
- package/src/index.ts +4 -7
- package/src/ingest/providers/index.ts +46 -246
- package/src/ingest/providers/openai.ts +1 -1
- package/src/ingest/task-processor.ts +1 -1
- package/src/recall/engine.ts +1 -1
- package/src/shared/llm-call.ts +95 -23
- package/src/skill/evaluator.ts +2 -3
- package/src/skill/evolver.ts +5 -0
- package/src/skill/generator.ts +4 -6
- package/src/skill/upgrader.ts +1 -1
- package/src/skill/validator.ts +1 -1
- package/src/storage/ensure-binding.ts +1 -3
- package/src/storage/sqlite.ts +0 -1085
- package/src/telemetry.ts +152 -39
- package/src/tools/index.ts +0 -1
- package/src/tools/memory-search.ts +8 -57
- package/src/types.ts +2 -44
- package/src/viewer/html.ts +471 -2974
- package/src/viewer/server.ts +21 -1070
- package/dist/client/connector.d.ts +0 -30
- package/dist/client/connector.d.ts.map +0 -1
- package/dist/client/connector.js +0 -219
- package/dist/client/connector.js.map +0 -1
- package/dist/client/hub.d.ts +0 -61
- package/dist/client/hub.d.ts.map +0 -1
- package/dist/client/hub.js +0 -148
- package/dist/client/hub.js.map +0 -1
- package/dist/client/skill-sync.d.ts +0 -29
- package/dist/client/skill-sync.d.ts.map +0 -1
- package/dist/client/skill-sync.js +0 -216
- package/dist/client/skill-sync.js.map +0 -1
- package/dist/hub/auth.d.ts +0 -19
- package/dist/hub/auth.d.ts.map +0 -1
- package/dist/hub/auth.js +0 -70
- package/dist/hub/auth.js.map +0 -1
- package/dist/hub/server.d.ts +0 -41
- package/dist/hub/server.d.ts.map +0 -1
- package/dist/hub/server.js +0 -747
- package/dist/hub/server.js.map +0 -1
- package/dist/hub/user-manager.d.ts +0 -29
- package/dist/hub/user-manager.d.ts.map +0 -1
- package/dist/hub/user-manager.js +0 -125
- package/dist/hub/user-manager.js.map +0 -1
- package/dist/openclaw-api.d.ts +0 -53
- package/dist/openclaw-api.d.ts.map +0 -1
- package/dist/openclaw-api.js +0 -189
- package/dist/openclaw-api.js.map +0 -1
- package/dist/sharing/types.contract.d.ts +0 -2
- package/dist/sharing/types.contract.d.ts.map +0 -1
- package/dist/sharing/types.contract.js +0 -3
- package/dist/sharing/types.contract.js.map +0 -1
- package/dist/sharing/types.d.ts +0 -80
- package/dist/sharing/types.d.ts.map +0 -1
- package/dist/sharing/types.js +0 -3
- package/dist/sharing/types.js.map +0 -1
- package/dist/tools/network-memory-detail.d.ts +0 -4
- package/dist/tools/network-memory-detail.d.ts.map +0 -1
- package/dist/tools/network-memory-detail.js +0 -34
- package/dist/tools/network-memory-detail.js.map +0 -1
- package/src/client/connector.ts +0 -218
- package/src/client/hub.ts +0 -189
- package/src/client/skill-sync.ts +0 -202
- package/src/hub/auth.ts +0 -78
- package/src/hub/server.ts +0 -740
- package/src/hub/user-manager.ts +0 -139
- package/src/openclaw-api.ts +0 -287
- package/src/sharing/types.contract.ts +0 -40
- package/src/sharing/types.ts +0 -102
- package/src/tools/network-memory-detail.ts +0 -34
package/index.ts
CHANGED
|
@@ -9,8 +9,8 @@ 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
|
-
import type { HostModelsConfig } from "./src/openclaw-api";
|
|
14
14
|
import { ensureSqliteBinding } from "./src/storage/ensure-binding";
|
|
15
15
|
import { SqliteStore } from "./src/storage/sqlite";
|
|
16
16
|
import { Embedder } from "./src/embedding";
|
|
@@ -19,10 +19,6 @@ import { RecallEngine } from "./src/recall/engine";
|
|
|
19
19
|
import { captureMessages, stripInboundMetadata } from "./src/capture";
|
|
20
20
|
import { DEFAULTS } from "./src/types";
|
|
21
21
|
import { ViewerServer } from "./src/viewer/server";
|
|
22
|
-
import { HubServer } from "./src/hub/server";
|
|
23
|
-
import { hubGetMemoryDetail, hubRequestJson, hubSearchMemories, hubSearchSkills, resolveHubClient } from "./src/client/hub";
|
|
24
|
-
import { getHubStatus, connectToHub } from "./src/client/connector";
|
|
25
|
-
import { fetchHubSkillBundle, publishSkillBundleToHub, restoreSkillBundleFromHub } from "./src/client/skill-sync";
|
|
26
22
|
import { SkillEvolver } from "./src/skill/evolver";
|
|
27
23
|
import { SkillInstaller } from "./src/skill/installer";
|
|
28
24
|
import { Summarizer } from "./src/ingest/providers";
|
|
@@ -82,13 +78,20 @@ const memosLocalPlugin = {
|
|
|
82
78
|
|
|
83
79
|
register(api: OpenClawPluginApi) {
|
|
84
80
|
// ─── Ensure better-sqlite3 native module is available ───
|
|
85
|
-
const pluginDir = path.dirname(
|
|
81
|
+
const pluginDir = path.dirname(fileURLToPath(import.meta.url));
|
|
82
|
+
|
|
83
|
+
function normalizeFsPath(p: string): string {
|
|
84
|
+
return path.resolve(p).replace(/\\/g, "/").toLowerCase();
|
|
85
|
+
}
|
|
86
|
+
|
|
86
87
|
let sqliteReady = false;
|
|
87
88
|
|
|
88
89
|
function trySqliteLoad(): boolean {
|
|
89
90
|
try {
|
|
90
91
|
const resolved = require.resolve("better-sqlite3", { paths: [pluginDir] });
|
|
91
|
-
|
|
92
|
+
const resolvedNorm = normalizeFsPath(resolved);
|
|
93
|
+
const pluginNorm = normalizeFsPath(pluginDir);
|
|
94
|
+
if (!resolvedNorm.startsWith(pluginNorm + "/") && resolvedNorm !== pluginNorm) {
|
|
92
95
|
api.logger.warn(`memos-local: better-sqlite3 resolved outside plugin dir: ${resolved}`);
|
|
93
96
|
return false;
|
|
94
97
|
}
|
|
@@ -170,37 +173,19 @@ const memosLocalPlugin = {
|
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
|
|
176
|
+
const pluginCfg = (api.pluginConfig ?? {}) as Record<string, unknown>;
|
|
174
177
|
const stateDir = api.resolvePath("~/.openclaw");
|
|
175
|
-
|
|
176
|
-
// Fallback: read config from file if not provided by OpenClaw
|
|
177
|
-
const configPath = path.join(stateDir, "state", "memos-local", "config.json");
|
|
178
|
-
if (Object.keys(pluginCfg).length === 0 && fs.existsSync(configPath)) {
|
|
179
|
-
try {
|
|
180
|
-
const fileConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
181
|
-
pluginCfg = fileConfig;
|
|
182
|
-
api.logger.info(`memos-local: loaded config from ${configPath}`);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
api.logger.warn(`memos-local: failed to load config from ${configPath}: ${e}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Extract host model providers so OpenClawAPIClient can proxy completion/embedding
|
|
189
|
-
const hostModels: HostModelsConfig | undefined = api.config?.models?.providers
|
|
190
|
-
? { providers: api.config.models.providers as Record<string, import("./src/openclaw-api").HostModelProvider> }
|
|
191
|
-
: undefined;
|
|
192
|
-
|
|
193
178
|
const ctx = buildContext(stateDir, process.cwd(), pluginCfg as any, {
|
|
194
179
|
debug: (msg: string) => api.logger.info(`[debug] ${msg}`),
|
|
195
180
|
info: (msg: string) => api.logger.info(msg),
|
|
196
181
|
warn: (msg: string) => api.logger.warn(msg),
|
|
197
182
|
error: (msg: string) => api.logger.warn(`[error] ${msg}`),
|
|
198
|
-
}
|
|
183
|
+
});
|
|
199
184
|
|
|
200
185
|
ensureSqliteBinding(ctx.log);
|
|
201
186
|
|
|
202
187
|
const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log);
|
|
203
|
-
const embedder = new Embedder(ctx.config.embedding, ctx.log
|
|
188
|
+
const embedder = new Embedder(ctx.config.embedding, ctx.log);
|
|
204
189
|
const worker = new IngestWorker(store, embedder, ctx);
|
|
205
190
|
const engine = new RecallEngine(store, embedder, ctx);
|
|
206
191
|
const evidenceTag = ctx.config.capture?.evidenceWrapperTag ?? DEFAULTS.evidenceWrapperTag;
|
|
@@ -208,6 +193,7 @@ const memosLocalPlugin = {
|
|
|
208
193
|
const workspaceDir = api.resolvePath("~/.openclaw/workspace");
|
|
209
194
|
const skillCtx = { ...ctx, workspaceDir };
|
|
210
195
|
const skillEvolver = new SkillEvolver(store, engine, skillCtx);
|
|
196
|
+
skillEvolver.onSkillEvolved = (name, type) => telemetry.trackSkillEvolved(name, type);
|
|
211
197
|
const skillInstaller = new SkillInstaller(store, skillCtx);
|
|
212
198
|
|
|
213
199
|
let pluginVersion = "0.0.0";
|
|
@@ -264,7 +250,7 @@ const memosLocalPlugin = {
|
|
|
264
250
|
});
|
|
265
251
|
});
|
|
266
252
|
|
|
267
|
-
const summarizer = new Summarizer(ctx.config.summarizer, ctx.log
|
|
253
|
+
const summarizer = new Summarizer(ctx.config.summarizer, ctx.log);
|
|
268
254
|
|
|
269
255
|
api.logger.info(`memos-local: initialized (db: ${ctx.config.storage!.dbPath})`);
|
|
270
256
|
|
|
@@ -272,6 +258,18 @@ const memosLocalPlugin = {
|
|
|
272
258
|
// Falls back to "main" when no hook has fired yet (single-agent setups).
|
|
273
259
|
let currentAgentId = "main";
|
|
274
260
|
|
|
261
|
+
// ─── Check allowPromptInjection policy ───
|
|
262
|
+
// When allowPromptInjection=false, the prompt mutation fields (such as prependContext) in the hook return value
|
|
263
|
+
// will be stripped by the framework. Skip auto-recall to avoid unnecessary LLM/embedding calls.
|
|
264
|
+
const pluginEntry = (api.config as any)?.plugins?.entries?.[api.id];
|
|
265
|
+
const allowPromptInjection = pluginEntry?.hooks?.allowPromptInjection !== false;
|
|
266
|
+
if (!allowPromptInjection) {
|
|
267
|
+
api.logger.info("memos-local: allowPromptInjection=false, auto-recall disabled");
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
api.logger.info("memos-local: allowPromptInjection=true, auto-recall enabled");
|
|
271
|
+
}
|
|
272
|
+
|
|
275
273
|
const trackTool = (toolName: string, fn: (...args: any[]) => Promise<any>) =>
|
|
276
274
|
async (...args: any[]) => {
|
|
277
275
|
const t0 = performance.now();
|
|
@@ -283,6 +281,7 @@ const memosLocalPlugin = {
|
|
|
283
281
|
return result;
|
|
284
282
|
} catch (e) {
|
|
285
283
|
ok = false;
|
|
284
|
+
telemetry.trackError(toolName, (e as Error)?.name ?? "unknown");
|
|
286
285
|
throw e;
|
|
287
286
|
} finally {
|
|
288
287
|
const dur = performance.now() - t0;
|
|
@@ -322,10 +321,6 @@ const memosLocalPlugin = {
|
|
|
322
321
|
const { query } = params as { query: string };
|
|
323
322
|
const role = undefined;
|
|
324
323
|
const minScore = undefined;
|
|
325
|
-
const searchScope = "local";
|
|
326
|
-
const searchLimit = 10;
|
|
327
|
-
const hubAddress: string | undefined = undefined;
|
|
328
|
-
const userToken: string | undefined = undefined;
|
|
329
324
|
|
|
330
325
|
const agentId = currentAgentId;
|
|
331
326
|
const ownerFilter = [`agent:${agentId}`, "public"];
|
|
@@ -349,6 +344,7 @@ const memosLocalPlugin = {
|
|
|
349
344
|
};
|
|
350
345
|
}
|
|
351
346
|
|
|
347
|
+
// LLM relevance + sufficiency filtering
|
|
352
348
|
let filteredHits = result.hits;
|
|
353
349
|
let sufficient = false;
|
|
354
350
|
|
|
@@ -374,51 +370,6 @@ const memosLocalPlugin = {
|
|
|
374
370
|
}
|
|
375
371
|
}
|
|
376
372
|
|
|
377
|
-
const beforeDedup = filteredHits.length;
|
|
378
|
-
filteredHits = deduplicateHits(filteredHits);
|
|
379
|
-
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredHits.length}`);
|
|
380
|
-
|
|
381
|
-
const localDetailsHits = filteredHits.map((h) => {
|
|
382
|
-
let effectiveTaskId = h.taskId;
|
|
383
|
-
if (effectiveTaskId) {
|
|
384
|
-
const t = store.getTask(effectiveTaskId);
|
|
385
|
-
if (t && t.status === "skipped") effectiveTaskId = null;
|
|
386
|
-
}
|
|
387
|
-
return {
|
|
388
|
-
ref: h.ref,
|
|
389
|
-
chunkId: h.ref.chunkId,
|
|
390
|
-
taskId: effectiveTaskId,
|
|
391
|
-
skillId: h.skillId,
|
|
392
|
-
role: h.source.role,
|
|
393
|
-
score: h.score,
|
|
394
|
-
summary: h.summary,
|
|
395
|
-
};
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
if (searchScope !== "local") {
|
|
399
|
-
const hub = await hubSearchMemories(store, ctx, { query, maxResults: searchLimit, scope: searchScope as any, hubAddress, userToken }).catch(() => ({ hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: searchScope === "all" } }));
|
|
400
|
-
const localText = filteredHits.length > 0
|
|
401
|
-
? filteredHits.map((h, i) => {
|
|
402
|
-
const excerpt = h.original_excerpt.length > 220 ? h.original_excerpt.slice(0, 217) + "..." : h.original_excerpt;
|
|
403
|
-
return `${i + 1}. [${h.source.role}] ${excerpt}`;
|
|
404
|
-
}).join("\n")
|
|
405
|
-
: "(none)";
|
|
406
|
-
const hubText = hub.hits.length > 0
|
|
407
|
-
? hub.hits.map((h, i) => `${i + 1}. [${h.ownerName}] ${h.summary}${h.groupName ? ` (${h.groupName})` : ""}`).join("\n")
|
|
408
|
-
: "(none)";
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
content: [{
|
|
412
|
-
type: "text",
|
|
413
|
-
text: `Local results:\n${localText}\n\nHub results:\n${hubText}`,
|
|
414
|
-
}],
|
|
415
|
-
details: {
|
|
416
|
-
local: { hits: localDetailsHits, meta: result.meta },
|
|
417
|
-
hub,
|
|
418
|
-
},
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
373
|
if (filteredHits.length === 0) {
|
|
423
374
|
return {
|
|
424
375
|
content: [{ type: "text", text: "No relevant memories found for this query." }],
|
|
@@ -426,6 +377,10 @@ const memosLocalPlugin = {
|
|
|
426
377
|
};
|
|
427
378
|
}
|
|
428
379
|
|
|
380
|
+
const beforeDedup = filteredHits.length;
|
|
381
|
+
filteredHits = deduplicateHits(filteredHits);
|
|
382
|
+
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredHits.length}`);
|
|
383
|
+
|
|
429
384
|
const lines = filteredHits.map((h, i) => {
|
|
430
385
|
const excerpt = h.original_excerpt;
|
|
431
386
|
const parts = [`${i + 1}. [${h.source.role}]`];
|
|
@@ -518,7 +473,7 @@ const memosLocalPlugin = {
|
|
|
518
473
|
if (!anchorChunk) {
|
|
519
474
|
return {
|
|
520
475
|
content: [{ type: "text", text: `Chunk not found: ${chunkId}` }],
|
|
521
|
-
details: { error: "not_found"
|
|
476
|
+
details: { error: "not_found" },
|
|
522
477
|
};
|
|
523
478
|
}
|
|
524
479
|
|
|
@@ -567,7 +522,7 @@ const memosLocalPlugin = {
|
|
|
567
522
|
Type.Number({ description: `Max chars (default ${DEFAULTS.getMaxCharsDefault}, max ${DEFAULTS.getMaxCharsMax})` }),
|
|
568
523
|
),
|
|
569
524
|
}),
|
|
570
|
-
execute: trackTool("memory_get", async (_toolCallId: any, params: any
|
|
525
|
+
execute: trackTool("memory_get", async (_toolCallId: any, params: any) => {
|
|
571
526
|
const { chunkId, maxChars } = params as { chunkId: string; maxChars?: number };
|
|
572
527
|
const limit = Math.min(maxChars ?? DEFAULTS.getMaxCharsDefault, DEFAULTS.getMaxCharsMax);
|
|
573
528
|
|
|
@@ -674,207 +629,6 @@ const memosLocalPlugin = {
|
|
|
674
629
|
{ name: "task_summary" },
|
|
675
630
|
);
|
|
676
631
|
|
|
677
|
-
// ─── Tool: task_share ───
|
|
678
|
-
|
|
679
|
-
api.registerTool(
|
|
680
|
-
{
|
|
681
|
-
name: "task_share",
|
|
682
|
-
label: "Task Share",
|
|
683
|
-
description:
|
|
684
|
-
"Share one existing local task and its chunks to the configured hub. " +
|
|
685
|
-
"Minimal MVP path for validating team task sharing.",
|
|
686
|
-
parameters: Type.Object({
|
|
687
|
-
taskId: Type.String({ description: "Local task ID to share" }),
|
|
688
|
-
visibility: Type.Optional(Type.String({ description: "Share visibility: 'public' (default) or 'group'" })),
|
|
689
|
-
groupId: Type.Optional(Type.String({ description: "Optional group ID when visibility='group'" })),
|
|
690
|
-
}),
|
|
691
|
-
execute: trackTool("task_share", async (_toolCallId: any, params: any) => {
|
|
692
|
-
const { taskId, visibility: rawVisibility, groupId } = params as {
|
|
693
|
-
taskId: string;
|
|
694
|
-
visibility?: string;
|
|
695
|
-
groupId?: string;
|
|
696
|
-
};
|
|
697
|
-
|
|
698
|
-
const task = store.getTask(taskId);
|
|
699
|
-
if (!task) {
|
|
700
|
-
return {
|
|
701
|
-
content: [{ type: "text", text: `Task not found: ${taskId}` }],
|
|
702
|
-
details: { error: "not_found", taskId },
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const chunks = store.getChunksByTask(taskId);
|
|
707
|
-
if (chunks.length === 0) {
|
|
708
|
-
return {
|
|
709
|
-
content: [{ type: "text", text: `Task ${taskId} has no chunks to share.` }],
|
|
710
|
-
details: { error: "no_chunks", taskId },
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const visibility = rawVisibility === "group" ? "group" : "public";
|
|
715
|
-
const hubClient = await resolveHubClient(store, ctx);
|
|
716
|
-
const { v4: uuidv4 } = require("uuid");
|
|
717
|
-
const hubTaskId = uuidv4();
|
|
718
|
-
|
|
719
|
-
const response = await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
720
|
-
method: "POST",
|
|
721
|
-
body: JSON.stringify({
|
|
722
|
-
task: {
|
|
723
|
-
id: hubTaskId,
|
|
724
|
-
sourceTaskId: task.id,
|
|
725
|
-
sourceUserId: hubClient.userId,
|
|
726
|
-
title: task.title,
|
|
727
|
-
summary: task.summary,
|
|
728
|
-
groupId: visibility === "group" ? (groupId ?? null) : null,
|
|
729
|
-
visibility,
|
|
730
|
-
createdAt: task.startedAt,
|
|
731
|
-
updatedAt: task.updatedAt,
|
|
732
|
-
},
|
|
733
|
-
chunks: chunks.map((chunk) => ({
|
|
734
|
-
id: uuidv4(),
|
|
735
|
-
hubTaskId,
|
|
736
|
-
sourceTaskId: task.id,
|
|
737
|
-
sourceChunkId: chunk.id,
|
|
738
|
-
sourceUserId: hubClient.userId,
|
|
739
|
-
role: chunk.role,
|
|
740
|
-
content: chunk.content,
|
|
741
|
-
summary: chunk.summary,
|
|
742
|
-
kind: chunk.kind,
|
|
743
|
-
createdAt: chunk.createdAt,
|
|
744
|
-
})),
|
|
745
|
-
}),
|
|
746
|
-
}) as any;
|
|
747
|
-
|
|
748
|
-
store.markTaskShared(task.id, hubTaskId, chunks.length, visibility, groupId);
|
|
749
|
-
|
|
750
|
-
return {
|
|
751
|
-
content: [{ type: "text", text: `Shared task "${task.title}" with ${chunks.length} chunks to the hub.` }],
|
|
752
|
-
details: {
|
|
753
|
-
shared: true,
|
|
754
|
-
taskId: task.id,
|
|
755
|
-
visibility,
|
|
756
|
-
chunkCount: chunks.length,
|
|
757
|
-
hubUrl: hubClient.hubUrl,
|
|
758
|
-
response,
|
|
759
|
-
},
|
|
760
|
-
};
|
|
761
|
-
}),
|
|
762
|
-
},
|
|
763
|
-
{ name: "task_share" },
|
|
764
|
-
);
|
|
765
|
-
|
|
766
|
-
// ─── Tool: task_unshare ───
|
|
767
|
-
|
|
768
|
-
api.registerTool(
|
|
769
|
-
{
|
|
770
|
-
name: "task_unshare",
|
|
771
|
-
label: "Task Unshare",
|
|
772
|
-
description: "Remove one previously shared task from the configured hub.",
|
|
773
|
-
parameters: Type.Object({
|
|
774
|
-
taskId: Type.String({ description: "Local task ID to unshare" }),
|
|
775
|
-
}),
|
|
776
|
-
execute: trackTool("task_unshare", async (_toolCallId: any, params: any) => {
|
|
777
|
-
const { taskId } = params as { taskId: string };
|
|
778
|
-
|
|
779
|
-
const task = store.getTask(taskId);
|
|
780
|
-
if (!task) {
|
|
781
|
-
return {
|
|
782
|
-
content: [{ type: "text", text: `Task not found: ${taskId}` }],
|
|
783
|
-
details: { error: "not_found", taskId },
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const hubClient = await resolveHubClient(store, ctx);
|
|
788
|
-
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
789
|
-
method: "POST",
|
|
790
|
-
body: JSON.stringify({
|
|
791
|
-
sourceUserId: hubClient.userId,
|
|
792
|
-
sourceTaskId: task.id,
|
|
793
|
-
}),
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
store.unmarkTaskShared(task.id);
|
|
797
|
-
|
|
798
|
-
return {
|
|
799
|
-
content: [{ type: "text", text: `Unshared task "${task.title}" from the hub.` }],
|
|
800
|
-
details: {
|
|
801
|
-
unshared: true,
|
|
802
|
-
taskId: task.id,
|
|
803
|
-
hubUrl: hubClient.hubUrl,
|
|
804
|
-
},
|
|
805
|
-
};
|
|
806
|
-
}),
|
|
807
|
-
},
|
|
808
|
-
{ name: "task_unshare" },
|
|
809
|
-
);
|
|
810
|
-
|
|
811
|
-
api.registerTool(
|
|
812
|
-
{
|
|
813
|
-
name: "network_memory_detail",
|
|
814
|
-
label: "Network Memory Detail",
|
|
815
|
-
description: "Fetch the full detail for a Hub search hit returned by memory_search(scope=group|all).",
|
|
816
|
-
parameters: Type.Object({
|
|
817
|
-
remoteHitId: Type.String({ description: "The remoteHitId returned by a Hub search hit" }),
|
|
818
|
-
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
819
|
-
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
820
|
-
}),
|
|
821
|
-
execute: trackTool("network_memory_detail", async (_toolCallId: any, params: any) => {
|
|
822
|
-
const { remoteHitId, hubAddress, userToken } = params as {
|
|
823
|
-
remoteHitId: string;
|
|
824
|
-
hubAddress?: string;
|
|
825
|
-
userToken?: string;
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
const detail = await hubGetMemoryDetail(store, ctx, { remoteHitId, hubAddress, userToken });
|
|
829
|
-
return {
|
|
830
|
-
content: [{
|
|
831
|
-
type: "text",
|
|
832
|
-
text: `## Shared Memory Detail
|
|
833
|
-
|
|
834
|
-
${detail.summary}
|
|
835
|
-
|
|
836
|
-
${detail.content}`,
|
|
837
|
-
}],
|
|
838
|
-
details: detail,
|
|
839
|
-
};
|
|
840
|
-
}),
|
|
841
|
-
},
|
|
842
|
-
{ name: "network_memory_detail" },
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
api.registerTool(
|
|
846
|
-
{
|
|
847
|
-
name: "network_team_info",
|
|
848
|
-
label: "Network Team Info",
|
|
849
|
-
description: "Show current Hub connection status, signed-in user, role, and group memberships.",
|
|
850
|
-
parameters: Type.Object({}),
|
|
851
|
-
execute: trackTool("network_team_info", async () => {
|
|
852
|
-
const status = await getHubStatus(store, ctx.config);
|
|
853
|
-
if (!status.connected || !status.user) {
|
|
854
|
-
return {
|
|
855
|
-
content: [{ type: "text", text: "Hub is not connected." }],
|
|
856
|
-
details: status,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
const groupNames = status.user.groups.map((group) => group.name);
|
|
861
|
-
return {
|
|
862
|
-
content: [{
|
|
863
|
-
type: "text",
|
|
864
|
-
text: `## Team Connection
|
|
865
|
-
|
|
866
|
-
User: ${status.user.username}
|
|
867
|
-
Role: ${status.user.role}
|
|
868
|
-
Hub: ${status.hubUrl ?? "(unknown)"}
|
|
869
|
-
Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
870
|
-
}],
|
|
871
|
-
details: status,
|
|
872
|
-
};
|
|
873
|
-
}),
|
|
874
|
-
},
|
|
875
|
-
{ name: "network_team_info" },
|
|
876
|
-
);
|
|
877
|
-
|
|
878
632
|
// ─── Tool: skill_get ───
|
|
879
633
|
|
|
880
634
|
api.registerTool(
|
|
@@ -991,6 +745,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
991
745
|
parameters: Type.Object({}),
|
|
992
746
|
execute: trackTool("memory_viewer", async () => {
|
|
993
747
|
ctx.log.debug(`memory_viewer called`);
|
|
748
|
+
telemetry.trackViewerOpened();
|
|
994
749
|
const url = `http://127.0.0.1:${viewerPort}`;
|
|
995
750
|
return {
|
|
996
751
|
content: [
|
|
@@ -1086,43 +841,17 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1086
841
|
name: "skill_search",
|
|
1087
842
|
label: "Skill Search",
|
|
1088
843
|
description:
|
|
1089
|
-
"Search available skills by natural language. Searches
|
|
844
|
+
"Search available skills by natural language. Searches your own skills, public skills, or both. " +
|
|
1090
845
|
"Use when you need a capability or guide and don't have a matching skill at hand.",
|
|
1091
846
|
parameters: Type.Object({
|
|
1092
847
|
query: Type.String({ description: "Natural language description of the needed skill" }),
|
|
1093
|
-
scope: Type.Optional(Type.String({ description: "Search scope: 'mix'
|
|
848
|
+
scope: Type.Optional(Type.String({ description: "Search scope: 'mix' (default, self + public), 'self' (own only), 'public' (public only)" })),
|
|
1094
849
|
}),
|
|
1095
|
-
execute: trackTool("skill_search", async (_toolCallId: any, params: any
|
|
850
|
+
execute: trackTool("skill_search", async (_toolCallId: any, params: any) => {
|
|
1096
851
|
const { query: skillQuery, scope: rawScope } = params as { query: string; scope?: string };
|
|
1097
852
|
const scope = (rawScope === "self" || rawScope === "public") ? rawScope : "mix";
|
|
1098
853
|
const currentOwner = `agent:${currentAgentId}`;
|
|
1099
854
|
|
|
1100
|
-
if (rawScope === "group" || rawScope === "all") {
|
|
1101
|
-
const [localHits, hub] = await Promise.all([
|
|
1102
|
-
engine.searchSkills(skillQuery, "mix" as any, currentOwner),
|
|
1103
|
-
hubSearchSkills(store, ctx, { query: skillQuery, maxResults: 10 }).catch(() => ({ hits: [] })),
|
|
1104
|
-
]);
|
|
1105
|
-
|
|
1106
|
-
if (localHits.length === 0 && hub.hits.length === 0) {
|
|
1107
|
-
return {
|
|
1108
|
-
content: [{ type: "text", text: `No relevant skills found for: "${skillQuery}" (scope: ${rawScope})` }],
|
|
1109
|
-
details: { query: skillQuery, scope: rawScope, local: { hits: [] }, hub },
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
const localText = localHits.length > 0
|
|
1114
|
-
? localHits.map((h, i) => `${i + 1}. [${h.name}] ${h.description.slice(0, 150)}${h.visibility === "public" ? " (public)" : ""}`).join("\n")
|
|
1115
|
-
: "(none)";
|
|
1116
|
-
const hubText = hub.hits.length > 0
|
|
1117
|
-
? 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")
|
|
1118
|
-
: "(none)";
|
|
1119
|
-
|
|
1120
|
-
return {
|
|
1121
|
-
content: [{ type: "text", text: `Local skills:\n${localText}\n\nHub skills:\n${hubText}` }],
|
|
1122
|
-
details: { query: skillQuery, scope: rawScope, local: { hits: localHits }, hub },
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
855
|
const hits = await engine.searchSkills(skillQuery, scope as any, currentOwner);
|
|
1127
856
|
|
|
1128
857
|
if (hits.length === 0) {
|
|
@@ -1154,28 +883,17 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1154
883
|
description: "Make a skill public so other agents can discover and install it via skill_search.",
|
|
1155
884
|
parameters: Type.Object({
|
|
1156
885
|
skillId: Type.String({ description: "The skill ID to publish" }),
|
|
1157
|
-
scope: Type.Optional(Type.String({ description: "Publish scope: omit for local public, or use 'public' / 'group' to publish to Hub" })),
|
|
1158
|
-
groupId: Type.Optional(Type.String({ description: "Optional group ID when scope='group'" })),
|
|
1159
|
-
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1160
|
-
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
1161
886
|
}),
|
|
1162
887
|
execute: trackTool("skill_publish", async (_toolCallId: any, params: any) => {
|
|
1163
|
-
const { skillId: pubSkillId
|
|
888
|
+
const { skillId: pubSkillId } = params as { skillId: string };
|
|
1164
889
|
const skill = store.getSkill(pubSkillId);
|
|
1165
890
|
if (!skill) {
|
|
1166
891
|
return { content: [{ type: "text", text: `Skill not found: ${pubSkillId}` }] };
|
|
1167
892
|
}
|
|
1168
|
-
if (scope === "public" || scope === "group") {
|
|
1169
|
-
const published = await publishSkillBundleToHub(store, ctx, { skillId: pubSkillId, visibility: scope, groupId, hubAddress, userToken });
|
|
1170
|
-
return {
|
|
1171
|
-
content: [{ type: "text", text: `Skill "${skill.name}" published to hub (${published.visibility}).` }],
|
|
1172
|
-
details: { skillId: pubSkillId, name: skill.name, publishedToHub: true, hubSkillId: published.skillId, visibility: published.visibility },
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
893
|
store.setSkillVisibility(pubSkillId, "public");
|
|
1176
894
|
return {
|
|
1177
895
|
content: [{ type: "text", text: `Skill "${skill.name}" is now public.` }],
|
|
1178
|
-
details: { skillId: pubSkillId, name: skill.name, visibility: "public"
|
|
896
|
+
details: { skillId: pubSkillId, name: skill.name, visibility: "public" },
|
|
1179
897
|
};
|
|
1180
898
|
}),
|
|
1181
899
|
},
|
|
@@ -1208,32 +926,10 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1208
926
|
{ name: "skill_unpublish" },
|
|
1209
927
|
);
|
|
1210
928
|
|
|
1211
|
-
api.registerTool(
|
|
1212
|
-
{
|
|
1213
|
-
name: "network_skill_pull",
|
|
1214
|
-
label: "Network Skill Pull",
|
|
1215
|
-
description: "Download a published Hub skill bundle and restore it into local managed skills.",
|
|
1216
|
-
parameters: Type.Object({
|
|
1217
|
-
skillId: Type.String({ description: "The Hub skill ID to pull" }),
|
|
1218
|
-
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for tests or manual routing" })),
|
|
1219
|
-
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for tests" })),
|
|
1220
|
-
}),
|
|
1221
|
-
execute: trackTool("network_skill_pull", async (_toolCallId: any, params: any) => {
|
|
1222
|
-
const { skillId, hubAddress, userToken } = params as { skillId: string; hubAddress?: string; userToken?: string };
|
|
1223
|
-
const payload = await fetchHubSkillBundle(store, ctx, { skillId, hubAddress, userToken });
|
|
1224
|
-
const restored = restoreSkillBundleFromHub(store, ctx, payload);
|
|
1225
|
-
return {
|
|
1226
|
-
content: [{ type: "text", text: `Pulled Hub skill "${restored.localName}" into local storage.` }],
|
|
1227
|
-
details: { pulled: true, hubSkillId: skillId, localSkillId: restored.localSkillId, localName: restored.localName, dirPath: restored.dirPath },
|
|
1228
|
-
};
|
|
1229
|
-
}),
|
|
1230
|
-
},
|
|
1231
|
-
{ name: "network_skill_pull" },
|
|
1232
|
-
);
|
|
1233
|
-
|
|
1234
929
|
// ─── Auto-recall: inject relevant memories before agent starts ───
|
|
1235
930
|
|
|
1236
931
|
api.on("before_agent_start", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
|
|
932
|
+
if (!allowPromptInjection) return {};
|
|
1237
933
|
if (!event.prompt || event.prompt.length < 3) return;
|
|
1238
934
|
|
|
1239
935
|
const recallAgentId = hookCtx?.agentId ?? "main";
|
|
@@ -1535,74 +1231,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1535
1231
|
worker.enqueue(captured);
|
|
1536
1232
|
telemetry.trackMemoryIngested(captured.length);
|
|
1537
1233
|
}
|
|
1538
|
-
|
|
1539
|
-
// Incremental push: sync new chunks for already-shared tasks
|
|
1540
|
-
syncSharedTasksIncremental().catch((err) => {
|
|
1541
|
-
ctx.log.warn(`incremental sync failed: ${err}`);
|
|
1542
|
-
});
|
|
1543
1234
|
} catch (err) {
|
|
1544
1235
|
api.logger.warn(`memos-local: capture failed: ${String(err)}`);
|
|
1545
1236
|
}
|
|
1546
1237
|
});
|
|
1547
1238
|
|
|
1548
|
-
async function syncSharedTasksIncremental(): Promise<void> {
|
|
1549
|
-
if (!ctx.config.sharing?.enabled || ctx.config.sharing.role !== "client") return;
|
|
1550
|
-
const shared = store.listLocalSharedTasks();
|
|
1551
|
-
if (shared.length === 0) return;
|
|
1552
|
-
|
|
1553
|
-
let hubClient: { hubUrl: string; userToken: string; userId: string } | undefined;
|
|
1554
|
-
try {
|
|
1555
|
-
hubClient = await resolveHubClient(store, ctx);
|
|
1556
|
-
} catch {
|
|
1557
|
-
return;
|
|
1558
|
-
}
|
|
1559
|
-
const { v4: uuidv4 } = require("uuid");
|
|
1560
|
-
|
|
1561
|
-
for (const entry of shared) {
|
|
1562
|
-
const task = store.getTask(entry.taskId);
|
|
1563
|
-
if (!task) continue;
|
|
1564
|
-
const chunks = store.getChunksByTask(entry.taskId);
|
|
1565
|
-
if (chunks.length <= entry.syncedChunks) continue;
|
|
1566
|
-
|
|
1567
|
-
const newChunks = chunks.slice(entry.syncedChunks);
|
|
1568
|
-
ctx.log.info(`incremental sync: task=${entry.taskId} pushing ${newChunks.length} new chunk(s)`);
|
|
1569
|
-
|
|
1570
|
-
try {
|
|
1571
|
-
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1572
|
-
method: "POST",
|
|
1573
|
-
body: JSON.stringify({
|
|
1574
|
-
task: {
|
|
1575
|
-
id: entry.hubTaskId,
|
|
1576
|
-
sourceTaskId: entry.taskId,
|
|
1577
|
-
sourceUserId: hubClient.userId,
|
|
1578
|
-
title: task.title,
|
|
1579
|
-
summary: task.summary,
|
|
1580
|
-
groupId: entry.visibility === "group" ? entry.groupId ?? null : null,
|
|
1581
|
-
visibility: entry.visibility,
|
|
1582
|
-
createdAt: task.startedAt ?? task.updatedAt ?? Date.now(),
|
|
1583
|
-
updatedAt: task.updatedAt ?? Date.now(),
|
|
1584
|
-
},
|
|
1585
|
-
chunks: newChunks.map((chunk) => ({
|
|
1586
|
-
id: uuidv4(),
|
|
1587
|
-
hubTaskId: entry.hubTaskId,
|
|
1588
|
-
sourceTaskId: entry.taskId,
|
|
1589
|
-
sourceChunkId: chunk.id,
|
|
1590
|
-
sourceUserId: hubClient.userId,
|
|
1591
|
-
role: chunk.role,
|
|
1592
|
-
content: chunk.content,
|
|
1593
|
-
summary: chunk.summary,
|
|
1594
|
-
kind: chunk.kind,
|
|
1595
|
-
createdAt: chunk.createdAt,
|
|
1596
|
-
})),
|
|
1597
|
-
}),
|
|
1598
|
-
});
|
|
1599
|
-
store.markTaskShared(entry.taskId, entry.hubTaskId, chunks.length, entry.visibility, entry.groupId);
|
|
1600
|
-
} catch (err) {
|
|
1601
|
-
ctx.log.warn(`incremental sync failed for task=${entry.taskId}: ${err}`);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
1239
|
// ─── Memory Viewer (web UI) ───
|
|
1607
1240
|
|
|
1608
1241
|
const viewer = new ViewerServer({
|
|
@@ -1614,30 +1247,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1614
1247
|
ctx,
|
|
1615
1248
|
});
|
|
1616
1249
|
|
|
1617
|
-
const hubServer = ctx.config.sharing?.enabled && ctx.config.sharing.role === "hub"
|
|
1618
|
-
? new HubServer({ store, log: ctx.log, config: ctx.config, dataDir: stateDir, embedder })
|
|
1619
|
-
: null;
|
|
1620
|
-
|
|
1621
1250
|
// ─── Service lifecycle ───
|
|
1622
1251
|
|
|
1623
1252
|
api.registerService({
|
|
1624
1253
|
id: "memos-local-openclaw-plugin",
|
|
1625
1254
|
start: async () => {
|
|
1626
|
-
if (hubServer) {
|
|
1627
|
-
const hubUrl = await hubServer.start();
|
|
1628
|
-
api.logger.info(`memos-local: hub started at ${hubUrl}`);
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// Auto-connect to Hub in client mode (handles both existing token and auto-join via teamToken)
|
|
1632
|
-
if (ctx.config.sharing?.enabled && ctx.config.sharing.role === "client") {
|
|
1633
|
-
try {
|
|
1634
|
-
const session = await connectToHub(store, ctx.config, ctx.log);
|
|
1635
|
-
api.logger.info(`memos-local: connected to Hub as "${session.username}" (${session.userId})`);
|
|
1636
|
-
} catch (err) {
|
|
1637
|
-
api.logger.warn(`memos-local: Hub connection failed: ${err}`);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
1255
|
try {
|
|
1642
1256
|
const viewerUrl = await viewer.start();
|
|
1643
1257
|
api.logger.info(`memos-local: started (embedding: ${embedder.provider})`);
|
|
@@ -1663,9 +1277,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1663
1277
|
);
|
|
1664
1278
|
},
|
|
1665
1279
|
stop: async () => {
|
|
1666
|
-
await worker.flush();
|
|
1667
1280
|
await telemetry.shutdown();
|
|
1668
|
-
await hubServer?.stop();
|
|
1669
1281
|
viewer.stop();
|
|
1670
1282
|
store.close();
|
|
1671
1283
|
api.logger.info("memos-local: stopped");
|
package/openclaw.plugin.json
CHANGED