@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.
Files changed (152) hide show
  1. package/README.md +22 -39
  2. package/dist/capture/index.d.ts.map +1 -1
  3. package/dist/capture/index.js +6 -0
  4. package/dist/capture/index.js.map +1 -1
  5. package/dist/config.d.ts +1 -2
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +3 -72
  8. package/dist/config.js.map +1 -1
  9. package/dist/embedding/index.d.ts +2 -4
  10. package/dist/embedding/index.d.ts.map +1 -1
  11. package/dist/embedding/index.js +1 -17
  12. package/dist/embedding/index.js.map +1 -1
  13. package/dist/index.d.ts +0 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/ingest/providers/index.d.ts +2 -10
  18. package/dist/ingest/providers/index.d.ts.map +1 -1
  19. package/dist/ingest/providers/index.js +43 -209
  20. package/dist/ingest/providers/index.js.map +1 -1
  21. package/dist/ingest/providers/openai.d.ts +0 -1
  22. package/dist/ingest/providers/openai.d.ts.map +1 -1
  23. package/dist/ingest/providers/openai.js +0 -1
  24. package/dist/ingest/providers/openai.js.map +1 -1
  25. package/dist/ingest/task-processor.js +1 -1
  26. package/dist/ingest/task-processor.js.map +1 -1
  27. package/dist/recall/engine.js +1 -1
  28. package/dist/recall/engine.js.map +1 -1
  29. package/dist/shared/llm-call.d.ts +2 -4
  30. package/dist/shared/llm-call.d.ts.map +1 -1
  31. package/dist/shared/llm-call.js +81 -20
  32. package/dist/shared/llm-call.js.map +1 -1
  33. package/dist/skill/evaluator.d.ts.map +1 -1
  34. package/dist/skill/evaluator.js +2 -2
  35. package/dist/skill/evaluator.js.map +1 -1
  36. package/dist/skill/evolver.d.ts +2 -0
  37. package/dist/skill/evolver.d.ts.map +1 -1
  38. package/dist/skill/evolver.js +3 -0
  39. package/dist/skill/evolver.js.map +1 -1
  40. package/dist/skill/generator.d.ts.map +1 -1
  41. package/dist/skill/generator.js +4 -4
  42. package/dist/skill/generator.js.map +1 -1
  43. package/dist/skill/upgrader.js +1 -1
  44. package/dist/skill/upgrader.js.map +1 -1
  45. package/dist/skill/validator.js +1 -1
  46. package/dist/skill/validator.js.map +1 -1
  47. package/dist/storage/ensure-binding.d.ts.map +1 -1
  48. package/dist/storage/ensure-binding.js +1 -3
  49. package/dist/storage/ensure-binding.js.map +1 -1
  50. package/dist/storage/sqlite.d.ts +0 -294
  51. package/dist/storage/sqlite.d.ts.map +1 -1
  52. package/dist/storage/sqlite.js +0 -821
  53. package/dist/storage/sqlite.js.map +1 -1
  54. package/dist/telemetry.d.ts +12 -5
  55. package/dist/telemetry.d.ts.map +1 -1
  56. package/dist/telemetry.js +135 -38
  57. package/dist/telemetry.js.map +1 -1
  58. package/dist/tools/index.d.ts +0 -1
  59. package/dist/tools/index.d.ts.map +1 -1
  60. package/dist/tools/index.js +1 -3
  61. package/dist/tools/index.js.map +1 -1
  62. package/dist/tools/memory-search.d.ts +2 -3
  63. package/dist/tools/memory-search.d.ts.map +1 -1
  64. package/dist/tools/memory-search.js +7 -48
  65. package/dist/tools/memory-search.js.map +1 -1
  66. package/dist/types.d.ts +2 -49
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js.map +1 -1
  69. package/dist/viewer/html.d.ts.map +1 -1
  70. package/dist/viewer/html.js +471 -2974
  71. package/dist/viewer/html.js.map +1 -1
  72. package/dist/viewer/server.d.ts +0 -45
  73. package/dist/viewer/server.d.ts.map +1 -1
  74. package/dist/viewer/server.js +18 -1155
  75. package/dist/viewer/server.js.map +1 -1
  76. package/index.ts +42 -430
  77. package/openclaw.plugin.json +1 -2
  78. package/package.json +3 -4
  79. package/scripts/postinstall.cjs +46 -283
  80. package/skill/memos-memory-guide/SKILL.md +2 -26
  81. package/src/capture/index.ts +8 -0
  82. package/src/config.ts +3 -94
  83. package/src/embedding/index.ts +1 -21
  84. package/src/index.ts +4 -7
  85. package/src/ingest/providers/index.ts +46 -246
  86. package/src/ingest/providers/openai.ts +1 -1
  87. package/src/ingest/task-processor.ts +1 -1
  88. package/src/recall/engine.ts +1 -1
  89. package/src/shared/llm-call.ts +95 -23
  90. package/src/skill/evaluator.ts +2 -3
  91. package/src/skill/evolver.ts +5 -0
  92. package/src/skill/generator.ts +4 -6
  93. package/src/skill/upgrader.ts +1 -1
  94. package/src/skill/validator.ts +1 -1
  95. package/src/storage/ensure-binding.ts +1 -3
  96. package/src/storage/sqlite.ts +0 -1085
  97. package/src/telemetry.ts +152 -39
  98. package/src/tools/index.ts +0 -1
  99. package/src/tools/memory-search.ts +8 -57
  100. package/src/types.ts +2 -44
  101. package/src/viewer/html.ts +471 -2974
  102. package/src/viewer/server.ts +21 -1070
  103. package/dist/client/connector.d.ts +0 -30
  104. package/dist/client/connector.d.ts.map +0 -1
  105. package/dist/client/connector.js +0 -219
  106. package/dist/client/connector.js.map +0 -1
  107. package/dist/client/hub.d.ts +0 -61
  108. package/dist/client/hub.d.ts.map +0 -1
  109. package/dist/client/hub.js +0 -148
  110. package/dist/client/hub.js.map +0 -1
  111. package/dist/client/skill-sync.d.ts +0 -29
  112. package/dist/client/skill-sync.d.ts.map +0 -1
  113. package/dist/client/skill-sync.js +0 -216
  114. package/dist/client/skill-sync.js.map +0 -1
  115. package/dist/hub/auth.d.ts +0 -19
  116. package/dist/hub/auth.d.ts.map +0 -1
  117. package/dist/hub/auth.js +0 -70
  118. package/dist/hub/auth.js.map +0 -1
  119. package/dist/hub/server.d.ts +0 -41
  120. package/dist/hub/server.d.ts.map +0 -1
  121. package/dist/hub/server.js +0 -747
  122. package/dist/hub/server.js.map +0 -1
  123. package/dist/hub/user-manager.d.ts +0 -29
  124. package/dist/hub/user-manager.d.ts.map +0 -1
  125. package/dist/hub/user-manager.js +0 -125
  126. package/dist/hub/user-manager.js.map +0 -1
  127. package/dist/openclaw-api.d.ts +0 -53
  128. package/dist/openclaw-api.d.ts.map +0 -1
  129. package/dist/openclaw-api.js +0 -189
  130. package/dist/openclaw-api.js.map +0 -1
  131. package/dist/sharing/types.contract.d.ts +0 -2
  132. package/dist/sharing/types.contract.d.ts.map +0 -1
  133. package/dist/sharing/types.contract.js +0 -3
  134. package/dist/sharing/types.contract.js.map +0 -1
  135. package/dist/sharing/types.d.ts +0 -80
  136. package/dist/sharing/types.d.ts.map +0 -1
  137. package/dist/sharing/types.js +0 -3
  138. package/dist/sharing/types.js.map +0 -1
  139. package/dist/tools/network-memory-detail.d.ts +0 -4
  140. package/dist/tools/network-memory-detail.d.ts.map +0 -1
  141. package/dist/tools/network-memory-detail.js +0 -34
  142. package/dist/tools/network-memory-detail.js.map +0 -1
  143. package/src/client/connector.ts +0 -218
  144. package/src/client/hub.ts +0 -189
  145. package/src/client/skill-sync.ts +0 -202
  146. package/src/hub/auth.ts +0 -78
  147. package/src/hub/server.ts +0 -740
  148. package/src/hub/user-manager.ts +0 -139
  149. package/src/openclaw-api.ts +0 -287
  150. package/src/sharing/types.contract.ts +0 -40
  151. package/src/sharing/types.ts +0 -102
  152. package/src/tools/network-memory-detail.ts +0 -34
@@ -1,747 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.HubServer = void 0;
37
- const fs = __importStar(require("fs"));
38
- const http = __importStar(require("http"));
39
- const path = __importStar(require("path"));
40
- const crypto_1 = require("crypto");
41
- const auth_1 = require("./auth");
42
- const user_manager_1 = require("./user-manager");
43
- class HubServer {
44
- opts;
45
- server;
46
- remoteHitMap = new Map();
47
- userManager;
48
- authStatePath;
49
- authState;
50
- static RATE_WINDOW_MS = 60_000;
51
- static RATE_LIMIT_DEFAULT = 60;
52
- static RATE_LIMIT_SEARCH = 30;
53
- rateBuckets = new Map();
54
- constructor(opts) {
55
- this.opts = opts;
56
- this.userManager = new user_manager_1.HubUserManager(opts.store, opts.log);
57
- this.authStatePath = path.join(opts.dataDir, "hub-auth.json");
58
- this.authState = this.loadAuthState();
59
- }
60
- checkRateLimit(userId, endpoint) {
61
- const key = `${userId}:${endpoint}`;
62
- const now = Date.now();
63
- const limit = endpoint === "search" ? HubServer.RATE_LIMIT_SEARCH : HubServer.RATE_LIMIT_DEFAULT;
64
- const bucket = this.rateBuckets.get(key);
65
- if (!bucket || now - bucket.windowStart > HubServer.RATE_WINDOW_MS) {
66
- this.rateBuckets.set(key, { count: 1, windowStart: now });
67
- return true;
68
- }
69
- bucket.count++;
70
- return bucket.count <= limit;
71
- }
72
- async start() {
73
- if (!this.teamToken) {
74
- throw new Error("team token is required to start hub mode");
75
- }
76
- if (this.server?.listening) {
77
- return `http://127.0.0.1:${this.port}`;
78
- }
79
- this.server = http.createServer(async (req, res) => {
80
- try {
81
- await this.handle(req, res);
82
- }
83
- catch (err) {
84
- const code = err?.statusCode ?? 500;
85
- const message = code === 413 ? "request_body_too_large" : "internal_error";
86
- this.opts.log.warn(`hub server error: ${String(err)}`);
87
- res.statusCode = code;
88
- res.setHeader("content-type", "application/json");
89
- res.end(JSON.stringify({ error: message }));
90
- }
91
- });
92
- await new Promise((resolve, reject) => {
93
- const onError = (err) => {
94
- this.server?.off("listening", onListening);
95
- reject(err);
96
- };
97
- const onListening = () => {
98
- this.server?.off("error", onError);
99
- resolve();
100
- };
101
- this.server.once("error", onError);
102
- this.server.once("listening", onListening);
103
- this.server.listen(this.port, "0.0.0.0");
104
- });
105
- const bootstrap = this.userManager.ensureBootstrapAdmin(this.authSecret, "admin", this.authState.bootstrapAdminUserId, this.authState.bootstrapAdminToken);
106
- if (bootstrap.token) {
107
- this.authState.bootstrapAdminUserId = bootstrap.user.id;
108
- this.authState.bootstrapAdminToken = bootstrap.token;
109
- this.saveAuthState();
110
- this.opts.log.info(`memos-local: bootstrap admin token persisted to ${this.authStatePath}`);
111
- }
112
- return `http://127.0.0.1:${this.port}`;
113
- }
114
- async stop() {
115
- if (!this.server)
116
- return;
117
- const server = this.server;
118
- this.server = undefined;
119
- await new Promise((resolve) => server.close(() => resolve()));
120
- }
121
- get port() {
122
- return this.opts.config.sharing?.hub?.port ?? 18800;
123
- }
124
- get teamName() {
125
- return this.opts.config.sharing?.hub?.teamName ?? "";
126
- }
127
- get teamToken() {
128
- return this.opts.config.sharing?.hub?.teamToken ?? "";
129
- }
130
- get authSecret() {
131
- return this.authState.authSecret;
132
- }
133
- loadAuthState() {
134
- try {
135
- const raw = fs.readFileSync(this.authStatePath, "utf8");
136
- const parsed = JSON.parse(raw);
137
- if (parsed.authSecret)
138
- return parsed;
139
- }
140
- catch { }
141
- const initial = { authSecret: (0, crypto_1.randomBytes)(32).toString("hex") };
142
- fs.mkdirSync(path.dirname(this.authStatePath), { recursive: true });
143
- fs.writeFileSync(this.authStatePath, JSON.stringify(initial, null, 2), "utf8");
144
- return initial;
145
- }
146
- saveAuthState() {
147
- fs.mkdirSync(path.dirname(this.authStatePath), { recursive: true });
148
- fs.writeFileSync(this.authStatePath, JSON.stringify(this.authState, null, 2), "utf8");
149
- }
150
- embedChunksAsync(chunkIds, chunks) {
151
- const embedder = this.opts.embedder;
152
- if (!embedder)
153
- return;
154
- const texts = chunks.map(c => c.summary || (c.content ? c.content.slice(0, 500) : ""));
155
- embedder.embed(texts).then((vectors) => {
156
- for (let i = 0; i < vectors.length; i++) {
157
- if (vectors[i]) {
158
- this.opts.store.upsertHubEmbedding(chunkIds[i], new Float32Array(vectors[i]));
159
- }
160
- }
161
- this.opts.log.info(`hub: embedded ${vectors.filter(Boolean).length}/${chunkIds.length} shared chunks`);
162
- }).catch((err) => {
163
- this.opts.log.warn(`hub: embedding shared chunks failed: ${err}`);
164
- });
165
- }
166
- embedMemoryAsync(memoryId, summary, content) {
167
- const embedder = this.opts.embedder;
168
- if (!embedder)
169
- return;
170
- const text = summary || content.slice(0, 500);
171
- embedder.embed([text]).then((vectors) => {
172
- if (vectors[0]) {
173
- this.opts.store.upsertHubMemoryEmbedding(memoryId, new Float32Array(vectors[0]));
174
- this.opts.log.info(`hub: embedded shared memory ${memoryId}`);
175
- }
176
- }).catch((err) => {
177
- this.opts.log.warn(`hub: embedding shared memory failed: ${err}`);
178
- });
179
- }
180
- async handle(req, res) {
181
- const url = new URL(req.url || "/", `http://127.0.0.1:${this.port}`);
182
- const routePath = url.pathname;
183
- if (req.method === "GET" && routePath === "/api/v1/hub/info") {
184
- return this.json(res, 200, {
185
- teamName: this.teamName,
186
- version: "0.0.0",
187
- apiVersion: "v1",
188
- });
189
- }
190
- if (req.method === "POST" && routePath === "/api/v1/hub/join") {
191
- const body = await this.readJson(req);
192
- if (!body || body.teamToken !== this.teamToken) {
193
- return this.json(res, 403, { error: "invalid_team_token" });
194
- }
195
- const username = String(body.username || `user-${(0, crypto_1.randomUUID)().slice(0, 8)}`);
196
- const existingUsers = this.opts.store.listHubUsers();
197
- const existingUser = existingUsers.find(u => u.username === username);
198
- if (existingUser) {
199
- if (existingUser.status === "active") {
200
- const token = (0, auth_1.issueUserToken)({ userId: existingUser.id, username: existingUser.username, role: existingUser.role, status: "active" }, this.authSecret);
201
- this.userManager.approveUser(existingUser.id, token);
202
- return this.json(res, 200, { status: "active", userId: existingUser.id, userToken: token });
203
- }
204
- if (existingUser.status === "pending") {
205
- return this.json(res, 200, { status: "pending", userId: existingUser.id });
206
- }
207
- if (existingUser.status === "rejected") {
208
- this.userManager.resetToPending(existingUser.id);
209
- this.opts.log.info(`Hub: rejected user "${username}" (${existingUser.id}) re-applied, reset to pending`);
210
- return this.json(res, 200, { status: "pending", userId: existingUser.id });
211
- }
212
- }
213
- const user = this.userManager.createPendingUser({
214
- username,
215
- deviceName: typeof body.deviceName === "string" ? body.deviceName : undefined,
216
- });
217
- this.opts.log.info(`Hub: user "${username}" (${user.id}) registered as pending, awaiting admin approval`);
218
- return this.json(res, 200, { status: "pending", userId: user.id });
219
- }
220
- if (req.method === "POST" && routePath === "/api/v1/hub/registration-status") {
221
- const body = await this.readJson(req);
222
- if (!body || body.teamToken !== this.teamToken) {
223
- return this.json(res, 403, { error: "invalid_team_token" });
224
- }
225
- const userId = String(body.userId || "");
226
- if (!userId)
227
- return this.json(res, 400, { error: "missing_user_id" });
228
- const user = this.opts.store.getHubUser(userId);
229
- if (!user)
230
- return this.json(res, 404, { error: "not_found" });
231
- if (user.status === "pending") {
232
- return this.json(res, 200, { status: "pending" });
233
- }
234
- if (user.status === "rejected") {
235
- return this.json(res, 200, { status: "rejected" });
236
- }
237
- if (user.status === "active") {
238
- const token = (0, auth_1.issueUserToken)({ userId: user.id, username: user.username, role: user.role, status: user.status }, this.authSecret);
239
- return this.json(res, 200, { status: "active", userToken: token });
240
- }
241
- return this.json(res, 200, { status: user.status });
242
- }
243
- // All endpoints below require authentication + rate limiting
244
- const auth = this.authenticate(req);
245
- if (!auth)
246
- return this.json(res, 401, { error: "unauthorized" });
247
- const endpointKey = routePath.replace(/^\/api\/v1\/hub\//, "").replace(/\/[^/]+\/bundle$/, "/bundle");
248
- if (!this.checkRateLimit(auth.userId, endpointKey)) {
249
- return this.json(res, 429, { error: "rate_limit_exceeded", retryAfterMs: HubServer.RATE_WINDOW_MS });
250
- }
251
- if (req.method === "GET" && routePath === "/api/v1/hub/me") {
252
- const user = this.opts.store.getHubUser(auth.userId);
253
- if (!user)
254
- return this.json(res, 401, { error: "unauthorized" });
255
- return this.json(res, 200, user);
256
- }
257
- if (req.method === "POST" && routePath === "/api/v1/hub/me/update-profile") {
258
- const body = await this.readJson(req);
259
- if (!body)
260
- return this.json(res, 400, { error: "invalid_body" });
261
- const newUsername = String(body.username || "").trim();
262
- if (!newUsername || newUsername.length < 2 || newUsername.length > 32) {
263
- return this.json(res, 400, { error: "invalid_username", message: "Username must be 2-32 characters" });
264
- }
265
- if (this.userManager.isUsernameTaken(newUsername, auth.userId)) {
266
- return this.json(res, 409, { error: "username_taken", message: "Username already in use" });
267
- }
268
- const updated = this.userManager.updateUsername(auth.userId, newUsername);
269
- if (!updated)
270
- return this.json(res, 404, { error: "not_found" });
271
- const newToken = (0, auth_1.issueUserToken)({ userId: updated.id, username: newUsername, role: updated.role, status: updated.status }, this.authSecret);
272
- this.userManager.approveUser(updated.id, newToken);
273
- this.opts.log.info(`Hub: user "${auth.userId}" renamed to "${newUsername}"`);
274
- return this.json(res, 200, { ok: true, username: newUsername, userToken: newToken });
275
- }
276
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/pending-users") {
277
- if (auth.role !== "admin")
278
- return this.json(res, 403, { error: "forbidden" });
279
- return this.json(res, 200, { users: this.userManager.listPendingUsers() });
280
- }
281
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/approve-user") {
282
- if (auth.role !== "admin")
283
- return this.json(res, 403, { error: "forbidden" });
284
- const body = await this.readJson(req);
285
- const token = (0, auth_1.issueUserToken)({ userId: String(body.userId), username: String(body.username || ""), role: "member", status: "active" }, this.authSecret);
286
- const approved = this.userManager.approveUser(String(body.userId), token);
287
- if (!approved)
288
- return this.json(res, 404, { error: "not_found" });
289
- return this.json(res, 200, { status: "active", token });
290
- }
291
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/reject-user") {
292
- if (auth.role !== "admin")
293
- return this.json(res, 403, { error: "forbidden" });
294
- const body = await this.readJson(req);
295
- const rejected = this.userManager.rejectUser(String(body.userId));
296
- if (!rejected)
297
- return this.json(res, 404, { error: "not_found" });
298
- return this.json(res, 200, { status: "rejected" });
299
- }
300
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/users") {
301
- if (auth.role !== "admin")
302
- return this.json(res, 403, { error: "forbidden" });
303
- const users = this.opts.store.listHubUsers().filter(u => u.status === "active");
304
- return this.json(res, 200, { users: users.map(u => ({ id: u.id, username: u.username, role: u.role, status: u.status })) });
305
- }
306
- // ── Group management ──
307
- if (req.method === "GET" && routePath === "/api/v1/hub/groups") {
308
- if (auth.role !== "admin")
309
- return this.json(res, 403, { error: "forbidden" });
310
- const groups = this.opts.store.listHubGroups();
311
- return this.json(res, 200, { groups });
312
- }
313
- if (req.method === "POST" && routePath === "/api/v1/hub/groups") {
314
- if (auth.role !== "admin")
315
- return this.json(res, 403, { error: "forbidden" });
316
- const body = await this.readJson(req);
317
- const name = String(body.name || "").trim();
318
- if (!name)
319
- return this.json(res, 400, { error: "name_required" });
320
- const groupId = (0, crypto_1.randomUUID)();
321
- this.opts.store.upsertHubGroup({
322
- id: groupId,
323
- name,
324
- description: String(body.description || ""),
325
- createdAt: Date.now(),
326
- });
327
- return this.json(res, 201, { id: groupId, name });
328
- }
329
- const groupDetailMatch = routePath.match(/^\/api\/v1\/hub\/groups\/([^/]+)$/);
330
- if (groupDetailMatch) {
331
- const groupId = decodeURIComponent(groupDetailMatch[1]);
332
- if (req.method === "GET") {
333
- if (auth.role !== "admin")
334
- return this.json(res, 403, { error: "forbidden" });
335
- const group = this.opts.store.getHubGroupById(groupId);
336
- if (!group)
337
- return this.json(res, 404, { error: "not_found" });
338
- const members = this.opts.store.listHubGroupMembers(groupId);
339
- return this.json(res, 200, { ...group, members });
340
- }
341
- if (req.method === "PUT") {
342
- if (auth.role !== "admin")
343
- return this.json(res, 403, { error: "forbidden" });
344
- const existing = this.opts.store.getHubGroupById(groupId);
345
- if (!existing)
346
- return this.json(res, 404, { error: "not_found" });
347
- const body = await this.readJson(req);
348
- this.opts.store.upsertHubGroup({
349
- id: groupId,
350
- name: String(body.name || existing.name).trim(),
351
- description: String(body.description ?? existing.description),
352
- createdAt: existing.createdAt,
353
- });
354
- return this.json(res, 200, { ok: true });
355
- }
356
- if (req.method === "DELETE") {
357
- if (auth.role !== "admin")
358
- return this.json(res, 403, { error: "forbidden" });
359
- const deleted = this.opts.store.deleteHubGroup(groupId);
360
- if (!deleted)
361
- return this.json(res, 404, { error: "not_found" });
362
- return this.json(res, 200, { ok: true });
363
- }
364
- }
365
- const groupMembersMatch = routePath.match(/^\/api\/v1\/hub\/groups\/([^/]+)\/members$/);
366
- if (groupMembersMatch) {
367
- const groupId = decodeURIComponent(groupMembersMatch[1]);
368
- if (req.method === "POST") {
369
- if (auth.role !== "admin")
370
- return this.json(res, 403, { error: "forbidden" });
371
- const group = this.opts.store.getHubGroupById(groupId);
372
- if (!group)
373
- return this.json(res, 404, { error: "group_not_found" });
374
- const body = await this.readJson(req);
375
- const userId = String(body.userId || "");
376
- if (!userId)
377
- return this.json(res, 400, { error: "userId_required" });
378
- const user = this.opts.store.getHubUser(userId);
379
- if (!user)
380
- return this.json(res, 404, { error: "user_not_found" });
381
- this.opts.store.addHubGroupMember(groupId, userId);
382
- return this.json(res, 200, { ok: true });
383
- }
384
- if (req.method === "DELETE") {
385
- if (auth.role !== "admin")
386
- return this.json(res, 403, { error: "forbidden" });
387
- const body = await this.readJson(req);
388
- const userId = String(body.userId || "");
389
- if (!userId)
390
- return this.json(res, 400, { error: "userId_required" });
391
- this.opts.store.removeHubGroupMember(groupId, userId);
392
- return this.json(res, 200, { ok: true });
393
- }
394
- }
395
- if (req.method === "POST" && routePath === "/api/v1/hub/tasks/share") {
396
- const body = await this.readJson(req);
397
- if (!body?.task)
398
- return this.json(res, 400, { error: "invalid_payload" });
399
- const task = { ...body.task, sourceUserId: auth.userId };
400
- this.opts.store.upsertHubTask(task);
401
- const chunks = Array.isArray(body.chunks) ? body.chunks : [];
402
- const chunkIds = [];
403
- for (const chunk of chunks) {
404
- this.opts.store.upsertHubChunk({ ...chunk, sourceUserId: auth.userId });
405
- chunkIds.push(chunk.id);
406
- }
407
- // Async embedding: don't block the response
408
- if (this.opts.embedder && chunkIds.length > 0) {
409
- this.embedChunksAsync(chunkIds, chunks);
410
- }
411
- return this.json(res, 200, { ok: true, chunks: chunkIds.length });
412
- }
413
- if (req.method === "POST" && routePath === "/api/v1/hub/tasks/unshare") {
414
- const body = await this.readJson(req);
415
- this.opts.store.deleteHubTaskBySource(auth.userId, String(body.sourceTaskId));
416
- return this.json(res, 200, { ok: true });
417
- }
418
- if (req.method === "POST" && routePath === "/api/v1/hub/memories/share") {
419
- const body = await this.readJson(req);
420
- if (!body?.memory)
421
- return this.json(res, 400, { error: "invalid_payload" });
422
- const m = body.memory;
423
- const sourceChunkId = String(m.sourceChunkId || "");
424
- if (!sourceChunkId)
425
- return this.json(res, 400, { error: "missing_source_chunk_id" });
426
- const existing = this.opts.store.getHubMemoryBySource(auth.userId, sourceChunkId);
427
- const memoryId = existing?.id ?? (0, crypto_1.randomUUID)();
428
- const visibility = "public";
429
- const resolvedGroupId = null;
430
- const now = Date.now();
431
- this.opts.store.upsertHubMemory({
432
- id: memoryId,
433
- sourceChunkId,
434
- sourceUserId: auth.userId,
435
- role: String(m.role || "assistant"),
436
- content: String(m.content || ""),
437
- summary: String(m.summary || ""),
438
- kind: String(m.kind || "paragraph"),
439
- groupId: resolvedGroupId,
440
- visibility,
441
- createdAt: existing?.createdAt ?? now,
442
- updatedAt: now,
443
- });
444
- if (this.opts.embedder) {
445
- this.embedMemoryAsync(memoryId, String(m.summary || ""), String(m.content || ""));
446
- }
447
- return this.json(res, 200, { ok: true, memoryId, visibility });
448
- }
449
- if (req.method === "POST" && routePath === "/api/v1/hub/memories/unshare") {
450
- const body = await this.readJson(req);
451
- const sourceChunkId = String(body?.sourceChunkId || "");
452
- if (!sourceChunkId)
453
- return this.json(res, 400, { error: "missing_source_chunk_id" });
454
- this.opts.store.deleteHubMemoryBySource(auth.userId, sourceChunkId);
455
- return this.json(res, 200, { ok: true });
456
- }
457
- if (req.method === "GET" && routePath === "/api/v1/hub/memories") {
458
- const limit = Number(url.searchParams.get("limit") || 40);
459
- const memories = this.opts.store.listVisibleHubMemories(auth.userId, limit);
460
- return this.json(res, 200, { memories });
461
- }
462
- if (req.method === "GET" && routePath === "/api/v1/hub/tasks") {
463
- const limit = Number(url.searchParams.get("limit") || 40);
464
- const tasks = this.opts.store.listVisibleHubTasks(auth.userId, limit);
465
- return this.json(res, 200, { tasks });
466
- }
467
- if (req.method === "GET" && routePath === "/api/v1/hub/skills/list") {
468
- const limit = Number(url.searchParams.get("limit") || 40);
469
- const skills = this.opts.store.listVisibleHubSkills(auth.userId, limit);
470
- return this.json(res, 200, { skills });
471
- }
472
- if (req.method === "POST" && routePath === "/api/v1/hub/search") {
473
- const body = await this.readJson(req);
474
- const query = String(body.query || "");
475
- const maxResults = Number(body.maxResults || 10);
476
- const ftsHits = this.opts.store.searchHubChunks(query, { userId: auth.userId, maxResults: maxResults * 2 });
477
- const memFtsHits = this.opts.store.searchHubMemories(query, { userId: auth.userId, maxResults: maxResults * 2 });
478
- // Track which IDs are memories vs chunks
479
- const memoryIdSet = new Set(memFtsHits.map(({ hit }) => hit.id));
480
- // Attempt vector search and RRF merge if embedder is available
481
- let mergedIds;
482
- if (this.opts.embedder) {
483
- try {
484
- const [queryVec] = await this.opts.embedder.embed([query]);
485
- if (queryVec) {
486
- const allEmb = this.opts.store.getVisibleHubEmbeddings(auth.userId);
487
- const memEmb = this.opts.store.getVisibleHubMemoryEmbeddings(auth.userId);
488
- const scored = [];
489
- const cosineSim = (vec) => {
490
- let dot = 0, nA = 0, nB = 0;
491
- for (let i = 0; i < queryVec.length && i < vec.length; i++) {
492
- dot += queryVec[i] * vec[i];
493
- nA += queryVec[i] * queryVec[i];
494
- nB += vec[i] * vec[i];
495
- }
496
- return nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
497
- };
498
- for (const e of allEmb)
499
- scored.push({ id: e.chunkId, score: cosineSim(e.vector) });
500
- for (const e of memEmb) {
501
- scored.push({ id: e.memoryId, score: cosineSim(e.vector) });
502
- memoryIdSet.add(e.memoryId);
503
- }
504
- scored.sort((a, b) => b.score - a.score);
505
- const topScored = scored.slice(0, maxResults * 2);
506
- const K = 60;
507
- const rrfScores = new Map();
508
- ftsHits.forEach(({ hit }, idx) => {
509
- rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
510
- });
511
- memFtsHits.forEach(({ hit }, idx) => {
512
- rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
513
- });
514
- topScored.forEach(({ id }, idx) => {
515
- rrfScores.set(id, (rrfScores.get(id) ?? 0) + 1 / (K + idx + 1));
516
- });
517
- mergedIds = [...rrfScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, maxResults).map(([id]) => id);
518
- }
519
- else {
520
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
521
- }
522
- }
523
- catch {
524
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
525
- }
526
- }
527
- else {
528
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
529
- }
530
- const ftsMap = new Map(ftsHits.map(({ hit }) => [hit.id, hit]));
531
- const memFtsMap = new Map(memFtsHits.map(({ hit }) => [hit.id, hit]));
532
- const hits = mergedIds.map((id, rank) => {
533
- const isMemory = memoryIdSet.has(id);
534
- if (isMemory) {
535
- let mhit = memFtsMap.get(id);
536
- if (!mhit) {
537
- const visibleHit = this.opts.store.getVisibleHubSearchHitByMemoryId(id, auth.userId);
538
- if (!visibleHit)
539
- return null;
540
- mhit = visibleHit;
541
- }
542
- const remoteHitId = (0, crypto_1.randomUUID)();
543
- this.remoteHitMap.set(remoteHitId, { chunkId: id, type: "memory", expiresAt: Date.now() + 10 * 60 * 1000, requesterUserId: auth.userId });
544
- return {
545
- remoteHitId, summary: mhit.summary, excerpt: mhit.content.slice(0, 240), hubRank: rank + 1,
546
- taskTitle: null, ownerName: mhit.owner_name || "unknown", groupName: mhit.group_name,
547
- visibility: mhit.visibility, source: { ts: mhit.created_at, role: mhit.role },
548
- };
549
- }
550
- let hit = ftsMap.get(id);
551
- if (!hit) {
552
- const visibleHit = this.opts.store.getVisibleHubSearchHitByChunkId(id, auth.userId);
553
- if (!visibleHit)
554
- return null;
555
- hit = visibleHit;
556
- }
557
- const remoteHitId = (0, crypto_1.randomUUID)();
558
- this.remoteHitMap.set(remoteHitId, { chunkId: id, type: "chunk", expiresAt: Date.now() + 10 * 60 * 1000, requesterUserId: auth.userId });
559
- return {
560
- remoteHitId, summary: hit.summary, excerpt: hit.content.slice(0, 240), hubRank: rank + 1,
561
- taskTitle: hit.task_title, ownerName: hit.owner_name || "unknown", groupName: hit.group_name,
562
- visibility: hit.visibility, source: { ts: hit.created_at, role: hit.role },
563
- };
564
- }).filter(Boolean);
565
- return this.json(res, 200, { hits, meta: { totalCandidates: hits.length, searchedGroups: [], includedPublic: true } });
566
- }
567
- if (req.method === "GET" && routePath === "/api/v1/hub/skills") {
568
- const hits = this.opts.store.searchHubSkills(String(url.searchParams.get("query") || ""), {
569
- userId: auth.userId,
570
- maxResults: Number(url.searchParams.get("maxResults") || 10),
571
- }).map(({ hit }) => ({
572
- skillId: hit.id,
573
- name: hit.name,
574
- description: hit.description,
575
- version: hit.version,
576
- visibility: hit.visibility,
577
- groupName: hit.group_name,
578
- ownerName: hit.owner_name || "unknown",
579
- qualityScore: hit.quality_score,
580
- }));
581
- return this.json(res, 200, { hits });
582
- }
583
- if (req.method === "POST" && routePath === "/api/v1/hub/skills/publish") {
584
- const body = await this.readJson(req);
585
- const metadata = body?.metadata ?? {};
586
- const sourceSkillId = String(metadata.id || "");
587
- if (!sourceSkillId)
588
- return this.json(res, 400, { error: "missing_skill_id" });
589
- const existing = this.opts.store.getHubSkillBySource(auth.userId, sourceSkillId);
590
- const skillId = existing?.id ?? (0, crypto_1.randomUUID)();
591
- const visibility = "public";
592
- this.opts.store.upsertHubSkill({
593
- id: skillId,
594
- sourceSkillId,
595
- sourceUserId: auth.userId,
596
- name: String(metadata.name || sourceSkillId),
597
- description: String(metadata.description || ""),
598
- version: Number(metadata.version || 1),
599
- groupId: null,
600
- visibility,
601
- bundle: JSON.stringify(body?.bundle ?? {}),
602
- qualityScore: metadata.qualityScore == null ? null : Number(metadata.qualityScore),
603
- createdAt: existing?.createdAt ?? Date.now(),
604
- updatedAt: Date.now(),
605
- });
606
- return this.json(res, 200, { ok: true, skillId, visibility });
607
- }
608
- const skillBundleMatch = req.method === "GET" ? routePath.match(/^\/api\/v1\/hub\/skills\/([^/]+)\/bundle$/) : null;
609
- if (skillBundleMatch) {
610
- const skill = this.opts.store.getHubSkillById(decodeURIComponent(skillBundleMatch[1]));
611
- if (!skill)
612
- return this.json(res, 404, { error: "not_found" });
613
- return this.json(res, 200, {
614
- skillId: skill.id,
615
- metadata: {
616
- id: skill.sourceSkillId,
617
- name: skill.name,
618
- description: skill.description,
619
- version: skill.version,
620
- qualityScore: skill.qualityScore,
621
- },
622
- bundle: JSON.parse(skill.bundle),
623
- });
624
- }
625
- if (req.method === "POST" && routePath === "/api/v1/hub/skills/unpublish") {
626
- const body = await this.readJson(req);
627
- this.opts.store.deleteHubSkillBySource(auth.userId, String(body?.sourceSkillId || ""));
628
- return this.json(res, 200, { ok: true });
629
- }
630
- // ── Admin: shared tasks & skills management ──
631
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-tasks") {
632
- if (auth.role !== "admin")
633
- return this.json(res, 403, { error: "forbidden" });
634
- const tasks = this.opts.store.listAllHubTasks();
635
- return this.json(res, 200, { tasks });
636
- }
637
- const adminTaskDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-tasks\/([^/]+)$/) : null;
638
- if (adminTaskDeleteMatch) {
639
- if (auth.role !== "admin")
640
- return this.json(res, 403, { error: "forbidden" });
641
- const taskId = decodeURIComponent(adminTaskDeleteMatch[1]);
642
- const deleted = this.opts.store.deleteHubTaskById(taskId);
643
- if (!deleted)
644
- return this.json(res, 404, { error: "not_found" });
645
- return this.json(res, 200, { ok: true });
646
- }
647
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-skills") {
648
- if (auth.role !== "admin")
649
- return this.json(res, 403, { error: "forbidden" });
650
- const skills = this.opts.store.listAllHubSkills();
651
- return this.json(res, 200, { skills });
652
- }
653
- const adminSkillDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-skills\/([^/]+)$/) : null;
654
- if (adminSkillDeleteMatch) {
655
- if (auth.role !== "admin")
656
- return this.json(res, 403, { error: "forbidden" });
657
- const skillId = decodeURIComponent(adminSkillDeleteMatch[1]);
658
- const deleted = this.opts.store.deleteHubSkillById(skillId);
659
- if (!deleted)
660
- return this.json(res, 404, { error: "not_found" });
661
- return this.json(res, 200, { ok: true });
662
- }
663
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-memories") {
664
- if (auth.role !== "admin")
665
- return this.json(res, 403, { error: "forbidden" });
666
- const memories = this.opts.store.listAllHubMemories();
667
- return this.json(res, 200, { memories });
668
- }
669
- const adminMemoryDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-memories\/([^/]+)$/) : null;
670
- if (adminMemoryDeleteMatch) {
671
- if (auth.role !== "admin")
672
- return this.json(res, 403, { error: "forbidden" });
673
- const memoryId = decodeURIComponent(adminMemoryDeleteMatch[1]);
674
- const deleted = this.opts.store.deleteHubMemoryById(memoryId);
675
- if (!deleted)
676
- return this.json(res, 404, { error: "not_found" });
677
- return this.json(res, 200, { ok: true });
678
- }
679
- if (req.method === "POST" && routePath === "/api/v1/hub/memory-detail") {
680
- const body = await this.readJson(req);
681
- const hit = this.remoteHitMap.get(String(body.remoteHitId));
682
- if (!hit || hit.expiresAt < Date.now())
683
- return this.json(res, 404, { error: "not_found" });
684
- if (hit.requesterUserId !== auth.userId)
685
- return this.json(res, 403, { error: "forbidden" });
686
- if (hit.type === "memory") {
687
- const mem = this.opts.store.getHubMemoryById(hit.chunkId);
688
- if (!mem)
689
- return this.json(res, 404, { error: "not_found" });
690
- return this.json(res, 200, { content: mem.content, summary: mem.summary, source: { ts: mem.createdAt, role: mem.role } });
691
- }
692
- const chunk = this.opts.store.getHubChunkById(hit.chunkId);
693
- if (!chunk)
694
- return this.json(res, 404, { error: "not_found" });
695
- return this.json(res, 200, {
696
- content: chunk.content,
697
- summary: chunk.summary,
698
- source: { ts: chunk.createdAt, role: chunk.role },
699
- });
700
- }
701
- return this.json(res, 404, { error: "not_found" });
702
- }
703
- authenticate(req) {
704
- const header = req.headers.authorization;
705
- if (!header || !header.startsWith("Bearer "))
706
- return null;
707
- const token = header.slice("Bearer ".length);
708
- const payload = (0, auth_1.verifyUserToken)(token, this.authSecret);
709
- if (!payload)
710
- return null;
711
- const user = this.opts.store.getHubUser(payload.userId);
712
- if (!user || user.status !== "active")
713
- return null;
714
- const hash = (0, crypto_1.createHash)("sha256").update(token).digest("hex");
715
- if (user.tokenHash !== hash)
716
- return null;
717
- return {
718
- userId: user.id,
719
- username: user.username,
720
- role: user.role,
721
- status: user.status,
722
- };
723
- }
724
- static MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
725
- async readJson(req) {
726
- const chunks = [];
727
- let totalBytes = 0;
728
- for await (const chunk of req) {
729
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
730
- totalBytes += buf.length;
731
- if (totalBytes > HubServer.MAX_BODY_BYTES) {
732
- req.destroy();
733
- throw Object.assign(new Error("request body too large"), { statusCode: 413 });
734
- }
735
- chunks.push(buf);
736
- }
737
- const raw = Buffer.concat(chunks).toString("utf8");
738
- return raw ? JSON.parse(raw) : {};
739
- }
740
- json(res, statusCode, body) {
741
- res.statusCode = statusCode;
742
- res.setHeader("content-type", "application/json");
743
- res.end(JSON.stringify(body));
744
- }
745
- }
746
- exports.HubServer = HubServer;
747
- //# sourceMappingURL=server.js.map