@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.
Files changed (100) hide show
  1. package/.env.example +7 -0
  2. package/README.md +94 -27
  3. package/dist/capture/index.js +3 -1
  4. package/dist/capture/index.js.map +1 -1
  5. package/dist/client/connector.d.ts +5 -0
  6. package/dist/client/connector.d.ts.map +1 -1
  7. package/dist/client/connector.js +89 -8
  8. package/dist/client/connector.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +2 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/hub/server.d.ts +2 -0
  13. package/dist/hub/server.d.ts.map +1 -1
  14. package/dist/hub/server.js +240 -35
  15. package/dist/hub/server.js.map +1 -1
  16. package/dist/hub/user-manager.d.ts +9 -0
  17. package/dist/hub/user-manager.d.ts.map +1 -1
  18. package/dist/hub/user-manager.js +26 -2
  19. package/dist/hub/user-manager.js.map +1 -1
  20. package/dist/ingest/chunker.d.ts +2 -1
  21. package/dist/ingest/chunker.d.ts.map +1 -1
  22. package/dist/ingest/chunker.js +14 -10
  23. package/dist/ingest/chunker.js.map +1 -1
  24. package/dist/ingest/providers/index.js +2 -2
  25. package/dist/ingest/providers/index.js.map +1 -1
  26. package/dist/recall/engine.d.ts.map +1 -1
  27. package/dist/recall/engine.js +22 -4
  28. package/dist/recall/engine.js.map +1 -1
  29. package/dist/shared/llm-call.d.ts.map +1 -1
  30. package/dist/shared/llm-call.js +2 -1
  31. package/dist/shared/llm-call.js.map +1 -1
  32. package/dist/sharing/types.d.ts +1 -1
  33. package/dist/sharing/types.d.ts.map +1 -1
  34. package/dist/skill/evolver.d.ts +2 -0
  35. package/dist/skill/evolver.d.ts.map +1 -1
  36. package/dist/skill/evolver.js +56 -5
  37. package/dist/skill/evolver.js.map +1 -1
  38. package/dist/skill/generator.d.ts +2 -0
  39. package/dist/skill/generator.d.ts.map +1 -1
  40. package/dist/skill/generator.js +45 -3
  41. package/dist/skill/generator.js.map +1 -1
  42. package/dist/skill/installer.d.ts +26 -0
  43. package/dist/skill/installer.d.ts.map +1 -1
  44. package/dist/skill/installer.js +80 -4
  45. package/dist/skill/installer.js.map +1 -1
  46. package/dist/skill/upgrader.d.ts +2 -0
  47. package/dist/skill/upgrader.d.ts.map +1 -1
  48. package/dist/skill/upgrader.js +139 -1
  49. package/dist/skill/upgrader.js.map +1 -1
  50. package/dist/skill/validator.d.ts +3 -0
  51. package/dist/skill/validator.d.ts.map +1 -1
  52. package/dist/skill/validator.js +75 -0
  53. package/dist/skill/validator.js.map +1 -1
  54. package/dist/storage/sqlite.d.ts +57 -0
  55. package/dist/storage/sqlite.d.ts.map +1 -1
  56. package/dist/storage/sqlite.js +290 -35
  57. package/dist/storage/sqlite.js.map +1 -1
  58. package/dist/telemetry.d.ts +4 -1
  59. package/dist/telemetry.d.ts.map +1 -1
  60. package/dist/telemetry.js +39 -12
  61. package/dist/telemetry.js.map +1 -1
  62. package/dist/types.d.ts +10 -0
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js +4 -0
  65. package/dist/types.js.map +1 -1
  66. package/dist/viewer/html.d.ts.map +1 -1
  67. package/dist/viewer/html.js +564 -225
  68. package/dist/viewer/html.js.map +1 -1
  69. package/dist/viewer/server.d.ts +9 -0
  70. package/dist/viewer/server.d.ts.map +1 -1
  71. package/dist/viewer/server.js +357 -108
  72. package/dist/viewer/server.js.map +1 -1
  73. package/index.ts +412 -53
  74. package/openclaw.plugin.json +1 -1
  75. package/package.json +2 -1
  76. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  77. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  78. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  79. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  80. package/src/capture/index.ts +4 -1
  81. package/src/client/connector.ts +92 -8
  82. package/src/config.ts +2 -1
  83. package/src/hub/server.ts +235 -35
  84. package/src/hub/user-manager.ts +42 -6
  85. package/src/ingest/chunker.ts +19 -13
  86. package/src/ingest/providers/index.ts +2 -2
  87. package/src/recall/engine.ts +20 -4
  88. package/src/shared/llm-call.ts +2 -1
  89. package/src/sharing/types.ts +1 -1
  90. package/src/skill/evolver.ts +58 -6
  91. package/src/skill/generator.ts +44 -5
  92. package/src/skill/installer.ts +107 -4
  93. package/src/skill/upgrader.ts +139 -1
  94. package/src/skill/validator.ts +79 -0
  95. package/src/storage/sqlite.ts +318 -40
  96. package/src/telemetry.ts +39 -14
  97. package/src/types.ts +11 -0
  98. package/src/viewer/html.ts +564 -225
  99. package/src/viewer/server.ts +333 -105
  100. package/telemetry.credentials.json +5 -0
@@ -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
- this.server?.off("listening", onListening);
99
- reject(err);
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.once("error", onError);
119
+ this.server.on("error", onError);
106
120
  this.server.once("listening", onListening);
107
- this.server.listen(this.port, "0.0.0.0");
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:${this.port}`;
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
- return this.opts.config.sharing?.hub?.port ?? 18800;
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 existingUsers = this.opts.store.listHubUsers();
211
- const existingUser = existingUsers.find(u => u.username === username);
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
- return this.json(res, 200, { status: "active", userId: existingUser.id, userToken: token });
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
- try {
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("user_offline", "user", auth.username, auth.userId);
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 token = (0, auth_1.issueUserToken)({ userId: String(body.userId), username: String(body.username || ""), role: "member", status: "active" }, this.authSecret);
328
- const approved = this.userManager.approveUser(String(body.userId), token);
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(String(body.userId), "");
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 rejected = this.userManager.rejectUser(String(body.userId));
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 hits = this.opts.store.searchHubSkills(String(url.searchParams.get("query") || ""), {
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: Number(url.searchParams.get("maxResults") || 10),
617
- }).map(({ hit }) => ({
618
- skillId: hit.id,
619
- name: hit.name,
620
- description: hit.description,
621
- version: hit.version,
622
- visibility: hit.visibility,
623
- groupName: hit.group_name,
624
- ownerName: hit.owner_name || "unknown",
625
- qualityScore: hit.quality_score,
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
- this.opts.store.insertHubNotification({ id: (0, crypto_1.randomUUID)(), userId: memInfo.sourceUserId, type: "resource_removed", resource: "memory", title: memInfo.summary || memInfo.id });
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
  }