@memtensor/memos-local-openclaw-plugin 1.0.4-beta.9 → 1.0.5
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 +94 -27
- package/dist/capture/index.js +3 -1
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +5 -0
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +89 -8
- package/dist/client/connector.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +240 -35
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +9 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +26 -2
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/ingest/chunker.d.ts +2 -1
- package/dist/ingest/chunker.d.ts.map +1 -1
- package/dist/ingest/chunker.js +14 -10
- package/dist/ingest/chunker.js.map +1 -1
- package/dist/ingest/providers/index.js +2 -2
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +22 -4
- package/dist/recall/engine.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/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 +56 -5
- package/dist/skill/evolver.js.map +1 -1
- package/dist/skill/generator.d.ts +2 -0
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +45 -3
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/installer.d.ts +26 -0
- package/dist/skill/installer.d.ts.map +1 -1
- package/dist/skill/installer.js +80 -4
- package/dist/skill/installer.js.map +1 -1
- package/dist/skill/upgrader.d.ts +2 -0
- package/dist/skill/upgrader.d.ts.map +1 -1
- package/dist/skill/upgrader.js +139 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.d.ts +3 -0
- package/dist/skill/validator.d.ts.map +1 -1
- package/dist/skill/validator.js +75 -0
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +57 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +290 -35
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts +4 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +39 -12
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +564 -225
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +9 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +357 -108
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +412 -53
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/src/capture/index.ts +4 -1
- package/src/client/connector.ts +92 -8
- package/src/config.ts +2 -1
- package/src/hub/server.ts +235 -35
- package/src/hub/user-manager.ts +42 -6
- package/src/ingest/chunker.ts +19 -13
- package/src/ingest/providers/index.ts +2 -2
- package/src/recall/engine.ts +20 -4
- package/src/shared/llm-call.ts +2 -1
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +58 -6
- package/src/skill/generator.ts +44 -5
- package/src/skill/installer.ts +107 -4
- package/src/skill/upgrader.ts +139 -1
- package/src/skill/validator.ts +79 -0
- package/src/storage/sqlite.ts +318 -40
- package/src/telemetry.ts +39 -14
- package/src/types.ts +11 -0
- package/src/viewer/html.ts +564 -225
- package/src/viewer/server.ts +333 -105
- package/telemetry.credentials.json +5 -0
package/dist/hub/server.js
CHANGED
|
@@ -93,18 +93,32 @@ class HubServer {
|
|
|
93
93
|
res.end(JSON.stringify({ error: message }));
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
|
+
const MAX_PORT_RETRIES = 3;
|
|
97
|
+
let hubPort = this.port;
|
|
96
98
|
await new Promise((resolve, reject) => {
|
|
99
|
+
let retries = 0;
|
|
97
100
|
const onError = (err) => {
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
if (err.code === "EADDRINUSE" && retries < MAX_PORT_RETRIES) {
|
|
102
|
+
retries++;
|
|
103
|
+
hubPort = this.port + retries;
|
|
104
|
+
this.opts.log.warn(`Hub port ${hubPort - 1} in use, trying ${hubPort}`);
|
|
105
|
+
this.server.listen(hubPort, "0.0.0.0");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.server?.off("listening", onListening);
|
|
109
|
+
reject(err);
|
|
110
|
+
}
|
|
100
111
|
};
|
|
101
112
|
const onListening = () => {
|
|
102
113
|
this.server?.off("error", onError);
|
|
114
|
+
if (hubPort !== this.port) {
|
|
115
|
+
this.opts.log.info(`Hub started on fallback port ${hubPort} (configured: ${this.port})`);
|
|
116
|
+
}
|
|
103
117
|
resolve();
|
|
104
118
|
};
|
|
105
|
-
this.server.
|
|
119
|
+
this.server.on("error", onError);
|
|
106
120
|
this.server.once("listening", onListening);
|
|
107
|
-
this.server.listen(
|
|
121
|
+
this.server.listen(hubPort, "0.0.0.0");
|
|
108
122
|
});
|
|
109
123
|
const bootstrap = this.userManager.ensureBootstrapAdmin(this.authSecret, "admin", this.authState.bootstrapAdminUserId, this.authState.bootstrapAdminToken);
|
|
110
124
|
if (bootstrap.token) {
|
|
@@ -115,7 +129,7 @@ class HubServer {
|
|
|
115
129
|
}
|
|
116
130
|
this.initOnlineTracking();
|
|
117
131
|
this.offlineCheckTimer = setInterval(() => this.checkOfflineUsers(), HubServer.OFFLINE_CHECK_INTERVAL_MS);
|
|
118
|
-
return `http://127.0.0.1:${
|
|
132
|
+
return `http://127.0.0.1:${hubPort}`;
|
|
119
133
|
}
|
|
120
134
|
async stop() {
|
|
121
135
|
if (this.offlineCheckTimer) {
|
|
@@ -124,12 +138,32 @@ class HubServer {
|
|
|
124
138
|
}
|
|
125
139
|
if (!this.server)
|
|
126
140
|
return;
|
|
141
|
+
try {
|
|
142
|
+
const activeUsers = this.opts.store.listHubUsers("active");
|
|
143
|
+
const ownerId = this.authState.bootstrapAdminUserId || "";
|
|
144
|
+
for (const u of activeUsers) {
|
|
145
|
+
if (u.id === ownerId)
|
|
146
|
+
continue;
|
|
147
|
+
try {
|
|
148
|
+
this.opts.store.insertHubNotification({
|
|
149
|
+
id: (0, crypto_1.randomUUID)(), userId: u.id, type: "hub_shutdown",
|
|
150
|
+
resource: "system", title: `Team server "${this.teamName}" has been shut down by the admin.`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch { /* best-effort */ }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch { /* best-effort */ }
|
|
127
157
|
const server = this.server;
|
|
128
158
|
this.server = undefined;
|
|
129
159
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
130
160
|
}
|
|
131
161
|
get port() {
|
|
132
|
-
|
|
162
|
+
const configured = this.opts.config.sharing?.hub?.port;
|
|
163
|
+
const derived = this.opts.defaultHubPort;
|
|
164
|
+
if (derived && (!configured || configured === 18800))
|
|
165
|
+
return derived;
|
|
166
|
+
return configured ?? 18800;
|
|
133
167
|
}
|
|
134
168
|
get teamName() {
|
|
135
169
|
return this.opts.config.sharing?.hub?.teamName ?? "";
|
|
@@ -173,6 +207,20 @@ class HubServer {
|
|
|
173
207
|
this.opts.log.warn(`hub: embedding shared chunks failed: ${err}`);
|
|
174
208
|
});
|
|
175
209
|
}
|
|
210
|
+
embedSkillAsync(skillId, name, description, sourceUserId, sourceSkillId) {
|
|
211
|
+
const embedder = this.opts.embedder;
|
|
212
|
+
if (!embedder)
|
|
213
|
+
return;
|
|
214
|
+
const text = `${name}: ${description}`;
|
|
215
|
+
embedder.embed([text]).then((vectors) => {
|
|
216
|
+
if (vectors[0]) {
|
|
217
|
+
this.opts.store.upsertHubSkillEmbedding(skillId, Array.from(vectors[0]), sourceUserId, sourceSkillId);
|
|
218
|
+
this.opts.log.info(`hub: embedded shared skill ${skillId}`);
|
|
219
|
+
}
|
|
220
|
+
}).catch((err) => {
|
|
221
|
+
this.opts.log.warn(`hub: embedding shared skill failed: ${err}`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
176
224
|
embedMemoryAsync(memoryId, summary, content) {
|
|
177
225
|
const embedder = this.opts.embedder;
|
|
178
226
|
if (!embedder)
|
|
@@ -207,8 +255,14 @@ class HubServer {
|
|
|
207
255
|
|| req.headers["x-client-ip"]?.trim()
|
|
208
256
|
|| req.headers["x-forwarded-for"]?.split(",")[0]?.trim()
|
|
209
257
|
|| req.socket.remoteAddress || "";
|
|
210
|
-
const
|
|
211
|
-
|
|
258
|
+
const identityKey = typeof body.identityKey === "string" ? body.identityKey.trim() : "";
|
|
259
|
+
let existingUser = identityKey
|
|
260
|
+
? this.userManager.findByIdentityKey(identityKey)
|
|
261
|
+
: null;
|
|
262
|
+
if (!existingUser) {
|
|
263
|
+
const existingUsers = this.opts.store.listHubUsers();
|
|
264
|
+
existingUser = existingUsers.find(u => u.username === username && u.status !== "left" && u.status !== "removed") ?? null;
|
|
265
|
+
}
|
|
212
266
|
if (existingUser) {
|
|
213
267
|
try {
|
|
214
268
|
this.opts.store.updateHubUserActivity(existingUser.id, joinIp);
|
|
@@ -217,25 +271,45 @@ class HubServer {
|
|
|
217
271
|
if (existingUser.status === "active") {
|
|
218
272
|
const token = (0, auth_1.issueUserToken)({ userId: existingUser.id, username: existingUser.username, role: existingUser.role, status: "active" }, this.authSecret);
|
|
219
273
|
this.userManager.approveUser(existingUser.id, token);
|
|
220
|
-
|
|
274
|
+
if (identityKey && !existingUser.identityKey) {
|
|
275
|
+
this.opts.store.upsertHubUser({ ...existingUser, identityKey });
|
|
276
|
+
}
|
|
277
|
+
return this.json(res, 200, { status: "active", userId: existingUser.id, userToken: token, identityKey: existingUser.identityKey || identityKey });
|
|
221
278
|
}
|
|
222
279
|
if (existingUser.status === "pending") {
|
|
223
280
|
this.notifyAdmins("user_join_request", "user", username, "", { dedup: true });
|
|
224
|
-
return this.json(res, 200, { status: "pending", userId: existingUser.id });
|
|
281
|
+
return this.json(res, 200, { status: "pending", userId: existingUser.id, identityKey: existingUser.identityKey || identityKey });
|
|
225
282
|
}
|
|
226
283
|
if (existingUser.status === "rejected") {
|
|
227
284
|
if (body.reapply === true) {
|
|
228
285
|
this.userManager.resetToPending(existingUser.id);
|
|
229
286
|
this.notifyAdmins("user_join_request", "user", username, "");
|
|
230
287
|
this.opts.log.info(`Hub: rejected user "${username}" (${existingUser.id}) re-applied, reset to pending`);
|
|
231
|
-
return this.json(res, 200, { status: "pending", userId: existingUser.id });
|
|
288
|
+
return this.json(res, 200, { status: "pending", userId: existingUser.id, identityKey: existingUser.identityKey || identityKey });
|
|
232
289
|
}
|
|
233
290
|
return this.json(res, 200, { status: "rejected", userId: existingUser.id });
|
|
234
291
|
}
|
|
292
|
+
if (existingUser.status === "removed") {
|
|
293
|
+
this.userManager.rejoinUser(existingUser.id);
|
|
294
|
+
this.notifyAdmins("user_join_request", "user", username, "", { dedup: true });
|
|
295
|
+
this.opts.log.info(`Hub: removed user "${username}" (${existingUser.id}) re-applied via rejoin, reset to pending`);
|
|
296
|
+
return this.json(res, 200, { status: "pending", userId: existingUser.id, identityKey: existingUser.identityKey || identityKey });
|
|
297
|
+
}
|
|
298
|
+
if (existingUser.status === "left") {
|
|
299
|
+
this.userManager.rejoinUser(existingUser.id);
|
|
300
|
+
this.notifyAdmins("user_join_request", "user", username, "", { dedup: true });
|
|
301
|
+
this.opts.log.info(`Hub: left user "${username}" (${existingUser.id}) re-applied via rejoin, reset to pending`);
|
|
302
|
+
return this.json(res, 200, { status: "pending", userId: existingUser.id, identityKey: existingUser.identityKey || identityKey });
|
|
303
|
+
}
|
|
304
|
+
if (existingUser.status === "blocked") {
|
|
305
|
+
return this.json(res, 200, { status: "blocked", userId: existingUser.id });
|
|
306
|
+
}
|
|
235
307
|
}
|
|
308
|
+
const generatedIdentityKey = identityKey || (0, crypto_1.randomUUID)();
|
|
236
309
|
const user = this.userManager.createPendingUser({
|
|
237
310
|
username,
|
|
238
311
|
deviceName: typeof body.deviceName === "string" ? body.deviceName : undefined,
|
|
312
|
+
identityKey: generatedIdentityKey,
|
|
239
313
|
});
|
|
240
314
|
try {
|
|
241
315
|
this.opts.store.updateHubUserActivity(user.id, joinIp);
|
|
@@ -243,7 +317,7 @@ class HubServer {
|
|
|
243
317
|
catch { /* best-effort */ }
|
|
244
318
|
this.opts.log.info(`Hub: user "${username}" (${user.id}) registered as pending, awaiting admin approval`);
|
|
245
319
|
this.notifyAdmins("user_join_request", "user", username, "");
|
|
246
|
-
return this.json(res, 200, { status: "pending", userId: user.id });
|
|
320
|
+
return this.json(res, 200, { status: "pending", userId: user.id, identityKey: generatedIdentityKey });
|
|
247
321
|
}
|
|
248
322
|
if (req.method === "POST" && routePath === "/api/v1/hub/registration-status") {
|
|
249
323
|
const body = await this.readJson(req);
|
|
@@ -262,12 +336,39 @@ class HubServer {
|
|
|
262
336
|
if (user.status === "rejected") {
|
|
263
337
|
return this.json(res, 200, { status: "rejected" });
|
|
264
338
|
}
|
|
339
|
+
if (user.status === "blocked") {
|
|
340
|
+
return this.json(res, 200, { status: "blocked" });
|
|
341
|
+
}
|
|
342
|
+
if (user.status === "left") {
|
|
343
|
+
return this.json(res, 200, { status: "left" });
|
|
344
|
+
}
|
|
345
|
+
if (user.status === "removed") {
|
|
346
|
+
return this.json(res, 200, { status: "removed" });
|
|
347
|
+
}
|
|
265
348
|
if (user.status === "active") {
|
|
266
349
|
const token = (0, auth_1.issueUserToken)({ userId: user.id, username: user.username, role: user.role, status: user.status }, this.authSecret);
|
|
350
|
+
this.userManager.approveUser(user.id, token);
|
|
267
351
|
return this.json(res, 200, { status: "active", userToken: token });
|
|
268
352
|
}
|
|
269
353
|
return this.json(res, 200, { status: user.status });
|
|
270
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)
|
|
362
|
+
return this.json(res, 400, { error: "missing_user_id" });
|
|
363
|
+
const user = this.opts.store.getHubUser(userId);
|
|
364
|
+
if (!user)
|
|
365
|
+
return this.json(res, 200, { ok: true });
|
|
366
|
+
if (user.status === "pending") {
|
|
367
|
+
this.userManager.markUserLeft(userId);
|
|
368
|
+
this.opts.log.info(`Hub: user "${user.username}" (${userId}) withdrew pending application`);
|
|
369
|
+
}
|
|
370
|
+
return this.json(res, 200, { ok: true });
|
|
371
|
+
}
|
|
271
372
|
// All endpoints below require authentication + rate limiting
|
|
272
373
|
const auth = this.authenticate(req);
|
|
273
374
|
if (!auth)
|
|
@@ -280,13 +381,10 @@ class HubServer {
|
|
|
280
381
|
return this.json(res, 200, { ok: true });
|
|
281
382
|
}
|
|
282
383
|
if (req.method === "POST" && routePath === "/api/v1/hub/leave") {
|
|
283
|
-
|
|
284
|
-
this.opts.store.updateHubUserActivity(auth.userId, "", 0);
|
|
285
|
-
}
|
|
286
|
-
catch { /* best-effort */ }
|
|
384
|
+
this.userManager.markUserLeft(auth.userId);
|
|
287
385
|
this.knownOnlineUsers.delete(auth.userId);
|
|
288
|
-
this.notifyAdmins("
|
|
289
|
-
this.opts.log.info(`Hub: user "${auth.username}" (${auth.userId}) left voluntarily`);
|
|
386
|
+
this.notifyAdmins("user_left", "user", auth.username, auth.userId);
|
|
387
|
+
this.opts.log.info(`Hub: user "${auth.username}" (${auth.userId}) left voluntarily, status set to "left"`);
|
|
290
388
|
return this.json(res, 200, { ok: true });
|
|
291
389
|
}
|
|
292
390
|
if (req.method === "GET" && routePath === "/api/v1/hub/me") {
|
|
@@ -312,6 +410,10 @@ class HubServer {
|
|
|
312
410
|
const ttlMs = updated.role === "admin" ? 3650 * 24 * 60 * 60 * 1000 : undefined;
|
|
313
411
|
const newToken = (0, auth_1.issueUserToken)({ userId: updated.id, username: newUsername, role: updated.role, status: updated.status }, this.authSecret, ttlMs);
|
|
314
412
|
this.userManager.approveUser(updated.id, newToken);
|
|
413
|
+
if (updated.id === this.authState.bootstrapAdminUserId) {
|
|
414
|
+
this.authState.bootstrapAdminToken = newToken;
|
|
415
|
+
this.saveAuthState();
|
|
416
|
+
}
|
|
315
417
|
this.opts.log.info(`Hub: user "${auth.userId}" renamed to "${newUsername}"`);
|
|
316
418
|
return this.json(res, 200, { ok: true, username: newUsername, userToken: newToken });
|
|
317
419
|
}
|
|
@@ -324,12 +426,21 @@ class HubServer {
|
|
|
324
426
|
if (auth.role !== "admin")
|
|
325
427
|
return this.json(res, 403, { error: "forbidden" });
|
|
326
428
|
const body = await this.readJson(req);
|
|
327
|
-
const
|
|
328
|
-
const
|
|
429
|
+
const userId = String(body.userId);
|
|
430
|
+
const username = String(body.username || "");
|
|
431
|
+
const token = (0, auth_1.issueUserToken)({ userId, username, role: "member", status: "active" }, this.authSecret);
|
|
432
|
+
const approved = this.userManager.approveUser(userId, token);
|
|
329
433
|
if (!approved)
|
|
330
434
|
return this.json(res, 404, { error: "not_found" });
|
|
331
435
|
try {
|
|
332
|
-
this.opts.store.updateHubUserActivity(
|
|
436
|
+
this.opts.store.updateHubUserActivity(userId, "");
|
|
437
|
+
}
|
|
438
|
+
catch { /* best-effort */ }
|
|
439
|
+
try {
|
|
440
|
+
this.opts.store.insertHubNotification({
|
|
441
|
+
id: (0, crypto_1.randomUUID)(), userId, type: "membership_approved",
|
|
442
|
+
resource: "user", title: `Your request to join team "${this.teamName}" has been approved. Welcome!`,
|
|
443
|
+
});
|
|
333
444
|
}
|
|
334
445
|
catch { /* best-effort */ }
|
|
335
446
|
return this.json(res, 200, { status: "active", token });
|
|
@@ -338,9 +449,17 @@ class HubServer {
|
|
|
338
449
|
if (auth.role !== "admin")
|
|
339
450
|
return this.json(res, 403, { error: "forbidden" });
|
|
340
451
|
const body = await this.readJson(req);
|
|
341
|
-
const
|
|
452
|
+
const userId = String(body.userId);
|
|
453
|
+
const rejected = this.userManager.rejectUser(userId);
|
|
342
454
|
if (!rejected)
|
|
343
455
|
return this.json(res, 404, { error: "not_found" });
|
|
456
|
+
try {
|
|
457
|
+
this.opts.store.insertHubNotification({
|
|
458
|
+
id: (0, crypto_1.randomUUID)(), userId, type: "membership_rejected",
|
|
459
|
+
resource: "user", title: `Your request to join team "${this.teamName}" has been declined.`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
catch { /* best-effort */ }
|
|
344
463
|
return this.json(res, 200, { status: "rejected" });
|
|
345
464
|
}
|
|
346
465
|
if (req.method === "GET" && routePath === "/api/v1/hub/admin/users") {
|
|
@@ -379,6 +498,14 @@ class HubServer {
|
|
|
379
498
|
const updatedUser = { ...user, role: newRole };
|
|
380
499
|
this.opts.store.upsertHubUser(updatedUser);
|
|
381
500
|
this.opts.log.info(`Hub: admin "${auth.userId}" changed role of "${userId}" to "${newRole}"`);
|
|
501
|
+
try {
|
|
502
|
+
const notifType = newRole === "admin" ? "role_promoted" : "role_demoted";
|
|
503
|
+
this.opts.store.insertHubNotification({
|
|
504
|
+
id: (0, crypto_1.randomUUID)(), userId, type: notifType,
|
|
505
|
+
resource: "user", title: `Your role in team "${this.teamName}" has been changed to ${newRole}.`,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
catch { /* best-effort */ }
|
|
382
509
|
return this.json(res, 200, { ok: true, role: newRole });
|
|
383
510
|
}
|
|
384
511
|
if (req.method === "POST" && routePath === "/api/v1/hub/admin/rename-user") {
|
|
@@ -402,6 +529,10 @@ class HubServer {
|
|
|
402
529
|
const updated = this.opts.store.getHubUser(userId);
|
|
403
530
|
const finalUser = { ...updated, username: newUsername };
|
|
404
531
|
this.opts.store.upsertHubUser(finalUser);
|
|
532
|
+
if (userId === this.authState.bootstrapAdminUserId) {
|
|
533
|
+
this.authState.bootstrapAdminToken = newToken;
|
|
534
|
+
this.saveAuthState();
|
|
535
|
+
}
|
|
405
536
|
this.opts.log.info(`Hub: admin "${auth.userId}" renamed user "${userId}" to "${newUsername}"`);
|
|
406
537
|
return this.json(res, 200, { ok: true, username: newUsername });
|
|
407
538
|
}
|
|
@@ -416,10 +547,18 @@ class HubServer {
|
|
|
416
547
|
return this.json(res, 400, { error: "cannot_remove_self" });
|
|
417
548
|
if (userId === this.authState.bootstrapAdminUserId)
|
|
418
549
|
return this.json(res, 403, { error: "cannot_remove_owner", message: "The hub owner cannot be removed" });
|
|
550
|
+
try {
|
|
551
|
+
this.opts.store.insertHubNotification({
|
|
552
|
+
id: (0, crypto_1.randomUUID)(), userId, type: "membership_removed",
|
|
553
|
+
resource: "user", title: `You have been removed from team "${this.teamName}" by the admin.`,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
catch { /* best-effort */ }
|
|
419
557
|
const cleanResources = body?.cleanResources === true;
|
|
420
558
|
const deleted = this.opts.store.deleteHubUser(userId, cleanResources);
|
|
421
559
|
if (!deleted)
|
|
422
560
|
return this.json(res, 404, { error: "not_found" });
|
|
561
|
+
this.knownOnlineUsers.delete(userId);
|
|
423
562
|
this.opts.log.info(`Hub: admin "${auth.userId}" removed user "${userId}" (cleanResources=${cleanResources})`);
|
|
424
563
|
return this.json(res, 200, { ok: true });
|
|
425
564
|
}
|
|
@@ -611,19 +750,73 @@ class HubServer {
|
|
|
611
750
|
return this.json(res, 200, { hits, meta: { totalCandidates: hits.length, searchedGroups: [], includedPublic: true } });
|
|
612
751
|
}
|
|
613
752
|
if (req.method === "GET" && routePath === "/api/v1/hub/skills") {
|
|
614
|
-
const
|
|
753
|
+
const skillQuery = String(url.searchParams.get("query") || "");
|
|
754
|
+
const skillMaxResults = Number(url.searchParams.get("maxResults") || 10);
|
|
755
|
+
const ftsSkillHits = this.opts.store.searchHubSkills(skillQuery, {
|
|
615
756
|
userId: auth.userId,
|
|
616
|
-
maxResults:
|
|
617
|
-
})
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
757
|
+
maxResults: skillMaxResults * 2,
|
|
758
|
+
});
|
|
759
|
+
let mergedSkillIds;
|
|
760
|
+
if (this.opts.embedder && skillQuery) {
|
|
761
|
+
try {
|
|
762
|
+
const [queryVec] = await this.opts.embedder.embed([skillQuery]);
|
|
763
|
+
if (queryVec) {
|
|
764
|
+
const skillEmbs = this.opts.store.getVisibleHubSkillEmbeddings();
|
|
765
|
+
const cosineSim = (vec) => {
|
|
766
|
+
let dot = 0, nA = 0, nB = 0;
|
|
767
|
+
for (let i = 0; i < queryVec.length && i < vec.length; i++) {
|
|
768
|
+
dot += queryVec[i] * vec[i];
|
|
769
|
+
nA += queryVec[i] * queryVec[i];
|
|
770
|
+
nB += vec[i] * vec[i];
|
|
771
|
+
}
|
|
772
|
+
return nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
|
|
773
|
+
};
|
|
774
|
+
const vecScored = skillEmbs
|
|
775
|
+
.map(e => ({ id: e.skillId, score: cosineSim(e.vector) }))
|
|
776
|
+
.filter(e => e.score > 0.3)
|
|
777
|
+
.sort((a, b) => b.score - a.score)
|
|
778
|
+
.slice(0, skillMaxResults * 2);
|
|
779
|
+
const K = 60;
|
|
780
|
+
const rrfScores = new Map();
|
|
781
|
+
ftsSkillHits.forEach(({ hit }, idx) => {
|
|
782
|
+
rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
|
|
783
|
+
});
|
|
784
|
+
vecScored.forEach(({ id }, idx) => {
|
|
785
|
+
rrfScores.set(id, (rrfScores.get(id) ?? 0) + 1 / (K + idx + 1));
|
|
786
|
+
});
|
|
787
|
+
mergedSkillIds = [...rrfScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, skillMaxResults).map(([id]) => id);
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
|
|
799
|
+
}
|
|
800
|
+
const ftsSkillMap = new Map(ftsSkillHits.map(({ hit }) => [hit.id, hit]));
|
|
801
|
+
const hits = mergedSkillIds.map(id => {
|
|
802
|
+
const hit = ftsSkillMap.get(id);
|
|
803
|
+
if (hit) {
|
|
804
|
+
return {
|
|
805
|
+
skillId: hit.id, name: hit.name, description: hit.description,
|
|
806
|
+
version: hit.version, visibility: hit.visibility, groupName: hit.group_name,
|
|
807
|
+
ownerName: hit.owner_name || "unknown", ownerStatus: hit.owner_status || "",
|
|
808
|
+
qualityScore: hit.quality_score,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const skill = this.opts.store.getHubSkillById(id);
|
|
812
|
+
if (!skill)
|
|
813
|
+
return null;
|
|
814
|
+
return {
|
|
815
|
+
skillId: skill.id, name: skill.name, description: skill.description,
|
|
816
|
+
version: skill.version, visibility: skill.visibility, groupName: "",
|
|
817
|
+
ownerName: "unknown", ownerStatus: "", qualityScore: skill.qualityScore,
|
|
818
|
+
};
|
|
819
|
+
}).filter(Boolean);
|
|
627
820
|
return this.json(res, 200, { hits });
|
|
628
821
|
}
|
|
629
822
|
if (req.method === "POST" && routePath === "/api/v1/hub/skills/publish") {
|
|
@@ -649,6 +842,7 @@ class HubServer {
|
|
|
649
842
|
createdAt: existing?.createdAt ?? Date.now(),
|
|
650
843
|
updatedAt: Date.now(),
|
|
651
844
|
});
|
|
845
|
+
this.embedSkillAsync(skillId, String(metadata.name || sourceSkillId), String(metadata.description || ""), auth.userId, sourceSkillId);
|
|
652
846
|
if (!existing) {
|
|
653
847
|
this.notifyAdmins("resource_shared", "skill", String(metadata.name || sourceSkillId), auth.userId);
|
|
654
848
|
}
|
|
@@ -771,7 +965,18 @@ class HubServer {
|
|
|
771
965
|
if (!deleted)
|
|
772
966
|
return this.json(res, 404, { error: "not_found" });
|
|
773
967
|
if (memInfo) {
|
|
774
|
-
|
|
968
|
+
const payload = JSON.stringify({
|
|
969
|
+
memoryId,
|
|
970
|
+
sourceChunkId: memInfo.sourceChunkId,
|
|
971
|
+
});
|
|
972
|
+
this.opts.store.insertHubNotification({
|
|
973
|
+
id: (0, crypto_1.randomUUID)(),
|
|
974
|
+
userId: memInfo.sourceUserId,
|
|
975
|
+
type: "resource_removed",
|
|
976
|
+
resource: "memory",
|
|
977
|
+
title: memInfo.summary || memInfo.id,
|
|
978
|
+
message: payload,
|
|
979
|
+
});
|
|
775
980
|
}
|
|
776
981
|
return this.json(res, 200, { ok: true });
|
|
777
982
|
}
|