@memtensor/memos-local-openclaw-plugin 1.0.4-beta.15 → 1.0.4-beta.17
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 +94 -27
- package/dist/hub/server.d.ts +1 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +74 -7
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.js +2 -2
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +2 -1
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +214 -67
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +6 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +152 -42
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +7 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/hub/server.ts +70 -8
- package/src/ingest/providers/index.ts +2 -2
- package/src/shared/llm-call.ts +2 -1
- package/src/viewer/html.ts +214 -67
- package/src/viewer/server.ts +145 -41
package/index.ts
CHANGED
|
@@ -180,7 +180,7 @@ const memosLocalPlugin = {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
let pluginCfg = (api.pluginConfig ?? {}) as Record<string, unknown>;
|
|
183
|
-
const stateDir = api.resolvePath("~/.openclaw");
|
|
183
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || api.resolvePath("~/.openclaw");
|
|
184
184
|
|
|
185
185
|
// Fallback: read config from file if not provided by OpenClaw
|
|
186
186
|
const configPath = path.join(stateDir, "state", "memos-local", "config.json");
|
|
@@ -1314,7 +1314,8 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1314
1314
|
|
|
1315
1315
|
// ─── Tool: memory_viewer ───
|
|
1316
1316
|
|
|
1317
|
-
const
|
|
1317
|
+
const gatewayPort = (api.config as any)?.gateway?.port ?? 18789;
|
|
1318
|
+
const viewerPort = (pluginCfg as any).viewerPort ?? (gatewayPort + 10);
|
|
1318
1319
|
|
|
1319
1320
|
api.registerTool(
|
|
1320
1321
|
{
|
|
@@ -2297,6 +2298,8 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2297
2298
|
|
|
2298
2299
|
// ─── Memory Viewer (web UI) ───
|
|
2299
2300
|
|
|
2301
|
+
const derivedHubPort = gatewayPort + 11;
|
|
2302
|
+
|
|
2300
2303
|
const viewer = new ViewerServer({
|
|
2301
2304
|
store,
|
|
2302
2305
|
embedder,
|
|
@@ -2304,10 +2307,10 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2304
2307
|
log: ctx.log,
|
|
2305
2308
|
dataDir: stateDir,
|
|
2306
2309
|
ctx,
|
|
2310
|
+
defaultHubPort: derivedHubPort,
|
|
2307
2311
|
});
|
|
2308
|
-
|
|
2309
2312
|
const hubServer = ctx.config.sharing?.enabled && ctx.config.sharing.role === "hub"
|
|
2310
|
-
? new HubServer({ store, log: ctx.log, config: ctx.config, dataDir: stateDir, embedder })
|
|
2313
|
+
? new HubServer({ store, log: ctx.log, config: ctx.config, dataDir: stateDir, embedder, defaultHubPort: derivedHubPort })
|
|
2311
2314
|
: null;
|
|
2312
2315
|
|
|
2313
2316
|
// ─── Service lifecycle ───
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "memos-local-openclaw-plugin",
|
|
3
3
|
"name": "MemOS Local Memory",
|
|
4
|
-
"description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency). Provides memory_search, memory_get, task_summary,
|
|
4
|
+
"description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency), task summarization, skill evolution, and team sharing (Hub-Client). Provides memory_search, memory_get, task_summary, skill_search, task_share, network_skill_pull, network_team_info, memory_viewer for layered retrieval and team collaboration.",
|
|
5
5
|
"kind": "memory",
|
|
6
6
|
"version": "0.1.12",
|
|
7
7
|
"skills": [
|
package/package.json
CHANGED
package/src/hub/server.ts
CHANGED
|
@@ -14,6 +14,7 @@ type HubServerOptions = {
|
|
|
14
14
|
config: MemosLocalConfig;
|
|
15
15
|
dataDir: string;
|
|
16
16
|
embedder?: Embedder;
|
|
17
|
+
defaultHubPort?: number;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
type HubAuthState = {
|
|
@@ -79,18 +80,31 @@ export class HubServer {
|
|
|
79
80
|
}
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
const MAX_PORT_RETRIES = 3;
|
|
84
|
+
let hubPort = this.port;
|
|
82
85
|
await new Promise<void>((resolve, reject) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
let retries = 0;
|
|
87
|
+
const onError = (err: NodeJS.ErrnoException) => {
|
|
88
|
+
if (err.code === "EADDRINUSE" && retries < MAX_PORT_RETRIES) {
|
|
89
|
+
retries++;
|
|
90
|
+
hubPort = this.port + retries;
|
|
91
|
+
this.opts.log.warn(`Hub port ${hubPort - 1} in use, trying ${hubPort}`);
|
|
92
|
+
this.server!.listen(hubPort, "0.0.0.0");
|
|
93
|
+
} else {
|
|
94
|
+
this.server?.off("listening", onListening);
|
|
95
|
+
reject(err);
|
|
96
|
+
}
|
|
86
97
|
};
|
|
87
98
|
const onListening = () => {
|
|
88
99
|
this.server?.off("error", onError);
|
|
100
|
+
if (hubPort !== this.port) {
|
|
101
|
+
this.opts.log.info(`Hub started on fallback port ${hubPort} (configured: ${this.port})`);
|
|
102
|
+
}
|
|
89
103
|
resolve();
|
|
90
104
|
};
|
|
91
|
-
this.server!.
|
|
105
|
+
this.server!.on("error", onError);
|
|
92
106
|
this.server!.once("listening", onListening);
|
|
93
|
-
this.server!.listen(
|
|
107
|
+
this.server!.listen(hubPort, "0.0.0.0");
|
|
94
108
|
});
|
|
95
109
|
|
|
96
110
|
const bootstrap = this.userManager.ensureBootstrapAdmin(
|
|
@@ -109,19 +123,37 @@ export class HubServer {
|
|
|
109
123
|
this.initOnlineTracking();
|
|
110
124
|
this.offlineCheckTimer = setInterval(() => this.checkOfflineUsers(), HubServer.OFFLINE_CHECK_INTERVAL_MS);
|
|
111
125
|
|
|
112
|
-
return `http://127.0.0.1:${
|
|
126
|
+
return `http://127.0.0.1:${hubPort}`;
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
async stop(): Promise<void> {
|
|
116
130
|
if (this.offlineCheckTimer) { clearInterval(this.offlineCheckTimer); this.offlineCheckTimer = undefined; }
|
|
117
131
|
if (!this.server) return;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const activeUsers = this.opts.store.listHubUsers("active");
|
|
135
|
+
const ownerId = this.authState.bootstrapAdminUserId || "";
|
|
136
|
+
for (const u of activeUsers) {
|
|
137
|
+
if (u.id === ownerId) continue;
|
|
138
|
+
try {
|
|
139
|
+
this.opts.store.insertHubNotification({
|
|
140
|
+
id: randomUUID(), userId: u.id, type: "hub_shutdown",
|
|
141
|
+
resource: "system", title: `Team server "${this.teamName}" has been shut down by the admin.`,
|
|
142
|
+
});
|
|
143
|
+
} catch { /* best-effort */ }
|
|
144
|
+
}
|
|
145
|
+
} catch { /* best-effort */ }
|
|
146
|
+
|
|
118
147
|
const server = this.server;
|
|
119
148
|
this.server = undefined;
|
|
120
149
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
121
150
|
}
|
|
122
151
|
|
|
123
152
|
private get port(): number {
|
|
124
|
-
|
|
153
|
+
const configured = this.opts.config.sharing?.hub?.port;
|
|
154
|
+
const derived = this.opts.defaultHubPort;
|
|
155
|
+
if (derived && (!configured || configured === 18800)) return derived;
|
|
156
|
+
return configured ?? 18800;
|
|
125
157
|
}
|
|
126
158
|
|
|
127
159
|
private get teamName(): string {
|
|
@@ -320,6 +352,22 @@ export class HubServer {
|
|
|
320
352
|
return this.json(res, 200, { status: user.status });
|
|
321
353
|
}
|
|
322
354
|
|
|
355
|
+
if (req.method === "POST" && routePath === "/api/v1/hub/withdraw-pending") {
|
|
356
|
+
const body = await this.readJson(req);
|
|
357
|
+
if (!body || body.teamToken !== this.teamToken) {
|
|
358
|
+
return this.json(res, 403, { error: "invalid_team_token" });
|
|
359
|
+
}
|
|
360
|
+
const userId = String(body.userId || "");
|
|
361
|
+
if (!userId) return this.json(res, 400, { error: "missing_user_id" });
|
|
362
|
+
const user = this.opts.store.getHubUser(userId);
|
|
363
|
+
if (!user) return this.json(res, 200, { ok: true });
|
|
364
|
+
if (user.status === "pending") {
|
|
365
|
+
this.userManager.markUserLeft(userId);
|
|
366
|
+
this.opts.log.info(`Hub: user "${user.username}" (${userId}) withdrew pending application`);
|
|
367
|
+
}
|
|
368
|
+
return this.json(res, 200, { ok: true });
|
|
369
|
+
}
|
|
370
|
+
|
|
323
371
|
// All endpoints below require authentication + rate limiting
|
|
324
372
|
const auth = this.authenticate(req);
|
|
325
373
|
if (!auth) return this.json(res, 401, { error: "unauthorized" });
|
|
@@ -336,7 +384,7 @@ export class HubServer {
|
|
|
336
384
|
if (req.method === "POST" && routePath === "/api/v1/hub/leave") {
|
|
337
385
|
this.userManager.markUserLeft(auth.userId);
|
|
338
386
|
this.knownOnlineUsers.delete(auth.userId);
|
|
339
|
-
this.notifyAdmins("
|
|
387
|
+
this.notifyAdmins("user_left", "user", auth.username, auth.userId);
|
|
340
388
|
this.opts.log.info(`Hub: user "${auth.username}" (${auth.userId}) left voluntarily, status set to "left"`);
|
|
341
389
|
return this.json(res, 200, { ok: true });
|
|
342
390
|
}
|
|
@@ -441,6 +489,13 @@ export class HubServer {
|
|
|
441
489
|
const updatedUser = { ...user, role: newRole as "admin" | "member" };
|
|
442
490
|
this.opts.store.upsertHubUser(updatedUser);
|
|
443
491
|
this.opts.log.info(`Hub: admin "${auth.userId}" changed role of "${userId}" to "${newRole}"`);
|
|
492
|
+
try {
|
|
493
|
+
const notifType = newRole === "admin" ? "role_promoted" : "role_demoted";
|
|
494
|
+
this.opts.store.insertHubNotification({
|
|
495
|
+
id: randomUUID(), userId, type: notifType,
|
|
496
|
+
resource: "user", title: `Your role in team "${this.teamName}" has been changed to ${newRole}.`,
|
|
497
|
+
});
|
|
498
|
+
} catch { /* best-effort */ }
|
|
444
499
|
return this.json(res, 200, { ok: true, role: newRole });
|
|
445
500
|
}
|
|
446
501
|
|
|
@@ -478,9 +533,16 @@ export class HubServer {
|
|
|
478
533
|
if (!userId) return this.json(res, 400, { error: "missing_user_id" });
|
|
479
534
|
if (userId === auth.userId) return this.json(res, 400, { error: "cannot_remove_self" });
|
|
480
535
|
if (userId === this.authState.bootstrapAdminUserId) return this.json(res, 403, { error: "cannot_remove_owner", message: "The hub owner cannot be removed" });
|
|
536
|
+
try {
|
|
537
|
+
this.opts.store.insertHubNotification({
|
|
538
|
+
id: randomUUID(), userId, type: "membership_removed",
|
|
539
|
+
resource: "user", title: `You have been removed from team "${this.teamName}" by the admin.`,
|
|
540
|
+
});
|
|
541
|
+
} catch { /* best-effort */ }
|
|
481
542
|
const cleanResources = body?.cleanResources === true;
|
|
482
543
|
const deleted = this.opts.store.deleteHubUser(userId, cleanResources);
|
|
483
544
|
if (!deleted) return this.json(res, 404, { error: "not_found" });
|
|
545
|
+
this.knownOnlineUsers.delete(userId);
|
|
484
546
|
this.opts.log.info(`Hub: admin "${auth.userId}" removed user "${userId}" (cleanResources=${cleanResources})`);
|
|
485
547
|
return this.json(res, 200, { ok: true });
|
|
486
548
|
}
|
|
@@ -49,8 +49,8 @@ function normalizeEndpointForProvider(
|
|
|
49
49
|
function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | undefined {
|
|
50
50
|
try {
|
|
51
51
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
52
|
-
const
|
|
53
|
-
|
|
52
|
+
const cfgPath = process.env.OPENCLAW_CONFIG_PATH
|
|
53
|
+
|| path.join(process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"), "openclaw.json");
|
|
54
54
|
if (!fs.existsSync(cfgPath)) return undefined;
|
|
55
55
|
|
|
56
56
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
package/src/shared/llm-call.ts
CHANGED
|
@@ -37,7 +37,8 @@ function defaultEndpointForProvider(provider: SummaryProvider, baseUrl: string):
|
|
|
37
37
|
export function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | undefined {
|
|
38
38
|
try {
|
|
39
39
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
40
|
-
const cfgPath =
|
|
40
|
+
const cfgPath = process.env.OPENCLAW_CONFIG_PATH
|
|
41
|
+
|| path.join(process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"), "openclaw.json");
|
|
41
42
|
if (!fs.existsSync(cfgPath)) return undefined;
|
|
42
43
|
|
|
43
44
|
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|