@memtensor/memos-local-openclaw-plugin 1.0.6-beta.9 → 1.0.7

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 (224) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +3 -5
  3. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  4. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  5. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  6. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  7. package/scripts/postinstall.cjs +44 -44
  8. package/skill/memos-memory-guide/SKILL.md +20 -0
  9. package/src/update-check.ts +2 -7
  10. package/src/viewer/html.ts +4 -4
  11. package/src/viewer/server.ts +12 -2
  12. package/telemetry.credentials.json +5 -0
  13. package/dist/capture/index.d.ts +0 -26
  14. package/dist/capture/index.d.ts.map +0 -1
  15. package/dist/capture/index.js +0 -283
  16. package/dist/capture/index.js.map +0 -1
  17. package/dist/client/connector.d.ts +0 -34
  18. package/dist/client/connector.d.ts.map +0 -1
  19. package/dist/client/connector.js +0 -381
  20. package/dist/client/connector.js.map +0 -1
  21. package/dist/client/hub.d.ts +0 -61
  22. package/dist/client/hub.d.ts.map +0 -1
  23. package/dist/client/hub.js +0 -174
  24. package/dist/client/hub.js.map +0 -1
  25. package/dist/client/skill-sync.d.ts +0 -36
  26. package/dist/client/skill-sync.d.ts.map +0 -1
  27. package/dist/client/skill-sync.js +0 -226
  28. package/dist/client/skill-sync.js.map +0 -1
  29. package/dist/config.d.ts +0 -5
  30. package/dist/config.d.ts.map +0 -1
  31. package/dist/config.js +0 -171
  32. package/dist/config.js.map +0 -1
  33. package/dist/embedding/index.d.ts +0 -14
  34. package/dist/embedding/index.d.ts.map +0 -1
  35. package/dist/embedding/index.js +0 -105
  36. package/dist/embedding/index.js.map +0 -1
  37. package/dist/embedding/local.d.ts +0 -3
  38. package/dist/embedding/local.d.ts.map +0 -1
  39. package/dist/embedding/local.js +0 -66
  40. package/dist/embedding/local.js.map +0 -1
  41. package/dist/embedding/providers/cohere.d.ts +0 -4
  42. package/dist/embedding/providers/cohere.d.ts.map +0 -1
  43. package/dist/embedding/providers/cohere.js +0 -57
  44. package/dist/embedding/providers/cohere.js.map +0 -1
  45. package/dist/embedding/providers/gemini.d.ts +0 -3
  46. package/dist/embedding/providers/gemini.d.ts.map +0 -1
  47. package/dist/embedding/providers/gemini.js +0 -31
  48. package/dist/embedding/providers/gemini.js.map +0 -1
  49. package/dist/embedding/providers/mistral.d.ts +0 -3
  50. package/dist/embedding/providers/mistral.d.ts.map +0 -1
  51. package/dist/embedding/providers/mistral.js +0 -25
  52. package/dist/embedding/providers/mistral.js.map +0 -1
  53. package/dist/embedding/providers/openai.d.ts +0 -3
  54. package/dist/embedding/providers/openai.d.ts.map +0 -1
  55. package/dist/embedding/providers/openai.js +0 -35
  56. package/dist/embedding/providers/openai.js.map +0 -1
  57. package/dist/embedding/providers/voyage.d.ts +0 -3
  58. package/dist/embedding/providers/voyage.d.ts.map +0 -1
  59. package/dist/embedding/providers/voyage.js +0 -25
  60. package/dist/embedding/providers/voyage.js.map +0 -1
  61. package/dist/hub/auth.d.ts +0 -19
  62. package/dist/hub/auth.d.ts.map +0 -1
  63. package/dist/hub/auth.js +0 -70
  64. package/dist/hub/auth.js.map +0 -1
  65. package/dist/hub/server.d.ts +0 -52
  66. package/dist/hub/server.d.ts.map +0 -1
  67. package/dist/hub/server.js +0 -1197
  68. package/dist/hub/server.js.map +0 -1
  69. package/dist/hub/user-manager.d.ts +0 -40
  70. package/dist/hub/user-manager.d.ts.map +0 -1
  71. package/dist/hub/user-manager.js +0 -153
  72. package/dist/hub/user-manager.js.map +0 -1
  73. package/dist/index.d.ts +0 -46
  74. package/dist/index.d.ts.map +0 -1
  75. package/dist/index.js +0 -82
  76. package/dist/index.js.map +0 -1
  77. package/dist/ingest/chunker.d.ts +0 -15
  78. package/dist/ingest/chunker.d.ts.map +0 -1
  79. package/dist/ingest/chunker.js +0 -192
  80. package/dist/ingest/chunker.js.map +0 -1
  81. package/dist/ingest/dedup.d.ts +0 -19
  82. package/dist/ingest/dedup.d.ts.map +0 -1
  83. package/dist/ingest/dedup.js +0 -50
  84. package/dist/ingest/dedup.js.map +0 -1
  85. package/dist/ingest/providers/anthropic.d.ts +0 -21
  86. package/dist/ingest/providers/anthropic.d.ts.map +0 -1
  87. package/dist/ingest/providers/anthropic.js +0 -314
  88. package/dist/ingest/providers/anthropic.js.map +0 -1
  89. package/dist/ingest/providers/bedrock.d.ts +0 -21
  90. package/dist/ingest/providers/bedrock.d.ts.map +0 -1
  91. package/dist/ingest/providers/bedrock.js +0 -313
  92. package/dist/ingest/providers/bedrock.js.map +0 -1
  93. package/dist/ingest/providers/gemini.d.ts +0 -21
  94. package/dist/ingest/providers/gemini.d.ts.map +0 -1
  95. package/dist/ingest/providers/gemini.js +0 -298
  96. package/dist/ingest/providers/gemini.js.map +0 -1
  97. package/dist/ingest/providers/index.d.ts +0 -68
  98. package/dist/ingest/providers/index.d.ts.map +0 -1
  99. package/dist/ingest/providers/index.js +0 -611
  100. package/dist/ingest/providers/index.js.map +0 -1
  101. package/dist/ingest/providers/openai.d.ts +0 -30
  102. package/dist/ingest/providers/openai.d.ts.map +0 -1
  103. package/dist/ingest/providers/openai.js +0 -387
  104. package/dist/ingest/providers/openai.js.map +0 -1
  105. package/dist/ingest/task-processor.d.ts +0 -91
  106. package/dist/ingest/task-processor.d.ts.map +0 -1
  107. package/dist/ingest/task-processor.js +0 -478
  108. package/dist/ingest/task-processor.js.map +0 -1
  109. package/dist/ingest/worker.d.ts +0 -23
  110. package/dist/ingest/worker.d.ts.map +0 -1
  111. package/dist/ingest/worker.js +0 -255
  112. package/dist/ingest/worker.js.map +0 -1
  113. package/dist/openclaw-api.d.ts +0 -53
  114. package/dist/openclaw-api.d.ts.map +0 -1
  115. package/dist/openclaw-api.js +0 -189
  116. package/dist/openclaw-api.js.map +0 -1
  117. package/dist/recall/engine.d.ts +0 -28
  118. package/dist/recall/engine.d.ts.map +0 -1
  119. package/dist/recall/engine.js +0 -343
  120. package/dist/recall/engine.js.map +0 -1
  121. package/dist/recall/mmr.d.ts +0 -17
  122. package/dist/recall/mmr.d.ts.map +0 -1
  123. package/dist/recall/mmr.js +0 -53
  124. package/dist/recall/mmr.js.map +0 -1
  125. package/dist/recall/recency.d.ts +0 -20
  126. package/dist/recall/recency.d.ts.map +0 -1
  127. package/dist/recall/recency.js +0 -26
  128. package/dist/recall/recency.js.map +0 -1
  129. package/dist/recall/rrf.d.ts +0 -16
  130. package/dist/recall/rrf.d.ts.map +0 -1
  131. package/dist/recall/rrf.js +0 -15
  132. package/dist/recall/rrf.js.map +0 -1
  133. package/dist/shared/llm-call.d.ts +0 -30
  134. package/dist/shared/llm-call.d.ts.map +0 -1
  135. package/dist/shared/llm-call.js +0 -253
  136. package/dist/shared/llm-call.js.map +0 -1
  137. package/dist/sharing/types.contract.d.ts +0 -2
  138. package/dist/sharing/types.contract.d.ts.map +0 -1
  139. package/dist/sharing/types.contract.js +0 -3
  140. package/dist/sharing/types.contract.js.map +0 -1
  141. package/dist/sharing/types.d.ts +0 -80
  142. package/dist/sharing/types.d.ts.map +0 -1
  143. package/dist/sharing/types.js +0 -3
  144. package/dist/sharing/types.js.map +0 -1
  145. package/dist/skill/bundled-memory-guide.d.ts +0 -2
  146. package/dist/skill/bundled-memory-guide.d.ts.map +0 -1
  147. package/dist/skill/bundled-memory-guide.js +0 -45
  148. package/dist/skill/bundled-memory-guide.js.map +0 -1
  149. package/dist/skill/evaluator.d.ts +0 -28
  150. package/dist/skill/evaluator.d.ts.map +0 -1
  151. package/dist/skill/evaluator.js +0 -169
  152. package/dist/skill/evaluator.js.map +0 -1
  153. package/dist/skill/evolver.d.ts +0 -48
  154. package/dist/skill/evolver.d.ts.map +0 -1
  155. package/dist/skill/evolver.js +0 -406
  156. package/dist/skill/evolver.js.map +0 -1
  157. package/dist/skill/generator.d.ts +0 -26
  158. package/dist/skill/generator.d.ts.map +0 -1
  159. package/dist/skill/generator.js +0 -521
  160. package/dist/skill/generator.js.map +0 -1
  161. package/dist/skill/installer.d.ts +0 -42
  162. package/dist/skill/installer.d.ts.map +0 -1
  163. package/dist/skill/installer.js +0 -165
  164. package/dist/skill/installer.js.map +0 -1
  165. package/dist/skill/upgrader.d.ts +0 -19
  166. package/dist/skill/upgrader.d.ts.map +0 -1
  167. package/dist/skill/upgrader.js +0 -366
  168. package/dist/skill/upgrader.js.map +0 -1
  169. package/dist/skill/validator.d.ts +0 -30
  170. package/dist/skill/validator.d.ts.map +0 -1
  171. package/dist/skill/validator.js +0 -272
  172. package/dist/skill/validator.js.map +0 -1
  173. package/dist/storage/ensure-binding.d.ts +0 -12
  174. package/dist/storage/ensure-binding.d.ts.map +0 -1
  175. package/dist/storage/ensure-binding.js +0 -53
  176. package/dist/storage/ensure-binding.js.map +0 -1
  177. package/dist/storage/sqlite.d.ts +0 -649
  178. package/dist/storage/sqlite.d.ts.map +0 -1
  179. package/dist/storage/sqlite.js +0 -2657
  180. package/dist/storage/sqlite.js.map +0 -1
  181. package/dist/storage/vector.d.ts +0 -12
  182. package/dist/storage/vector.d.ts.map +0 -1
  183. package/dist/storage/vector.js +0 -34
  184. package/dist/storage/vector.js.map +0 -1
  185. package/dist/telemetry.d.ts +0 -47
  186. package/dist/telemetry.d.ts.map +0 -1
  187. package/dist/telemetry.js +0 -312
  188. package/dist/telemetry.js.map +0 -1
  189. package/dist/tools/index.d.ts +0 -5
  190. package/dist/tools/index.d.ts.map +0 -1
  191. package/dist/tools/index.js +0 -12
  192. package/dist/tools/index.js.map +0 -1
  193. package/dist/tools/memory-get.d.ts +0 -4
  194. package/dist/tools/memory-get.d.ts.map +0 -1
  195. package/dist/tools/memory-get.js +0 -64
  196. package/dist/tools/memory-get.js.map +0 -1
  197. package/dist/tools/memory-search.d.ts +0 -7
  198. package/dist/tools/memory-search.d.ts.map +0 -1
  199. package/dist/tools/memory-search.js +0 -84
  200. package/dist/tools/memory-search.js.map +0 -1
  201. package/dist/tools/memory-timeline.d.ts +0 -4
  202. package/dist/tools/memory-timeline.d.ts.map +0 -1
  203. package/dist/tools/memory-timeline.js +0 -73
  204. package/dist/tools/memory-timeline.js.map +0 -1
  205. package/dist/tools/network-memory-detail.d.ts +0 -4
  206. package/dist/tools/network-memory-detail.d.ts.map +0 -1
  207. package/dist/tools/network-memory-detail.js +0 -34
  208. package/dist/tools/network-memory-detail.js.map +0 -1
  209. package/dist/types.d.ts +0 -330
  210. package/dist/types.d.ts.map +0 -1
  211. package/dist/types.js +0 -38
  212. package/dist/types.js.map +0 -1
  213. package/dist/update-check.d.ts +0 -21
  214. package/dist/update-check.d.ts.map +0 -1
  215. package/dist/update-check.js +0 -110
  216. package/dist/update-check.js.map +0 -1
  217. package/dist/viewer/html.d.ts +0 -2
  218. package/dist/viewer/html.d.ts.map +0 -1
  219. package/dist/viewer/html.js +0 -9168
  220. package/dist/viewer/html.js.map +0 -1
  221. package/dist/viewer/server.d.ts +0 -205
  222. package/dist/viewer/server.d.ts.map +0 -1
  223. package/dist/viewer/server.js +0 -4876
  224. package/dist/viewer/server.js.map +0 -1
@@ -1,1197 +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
- static OFFLINE_THRESHOLD_MS = 2 * 60 * 1000;
55
- static OFFLINE_CHECK_INTERVAL_MS = 30 * 1000;
56
- offlineCheckTimer;
57
- knownOnlineUsers = new Set();
58
- constructor(opts) {
59
- this.opts = opts;
60
- this.userManager = new user_manager_1.HubUserManager(opts.store, opts.log);
61
- this.authStatePath = path.join(opts.dataDir, "hub-auth.json");
62
- this.authState = this.loadAuthState();
63
- }
64
- checkRateLimit(userId, endpoint) {
65
- const key = `${userId}:${endpoint}`;
66
- const now = Date.now();
67
- const limit = endpoint === "search" ? HubServer.RATE_LIMIT_SEARCH : HubServer.RATE_LIMIT_DEFAULT;
68
- const bucket = this.rateBuckets.get(key);
69
- if (!bucket || now - bucket.windowStart > HubServer.RATE_WINDOW_MS) {
70
- this.rateBuckets.set(key, { count: 1, windowStart: now });
71
- return true;
72
- }
73
- bucket.count++;
74
- return bucket.count <= limit;
75
- }
76
- async start() {
77
- if (!this.teamToken) {
78
- throw new Error("team token is required to start hub mode");
79
- }
80
- if (this.server?.listening) {
81
- return `http://127.0.0.1:${this.port}`;
82
- }
83
- this.server = http.createServer(async (req, res) => {
84
- try {
85
- await this.handle(req, res);
86
- }
87
- catch (err) {
88
- const code = err?.statusCode ?? 500;
89
- const message = code === 413 ? "request_body_too_large" : "internal_error";
90
- this.opts.log.warn(`hub server error: ${String(err)}`);
91
- res.statusCode = code;
92
- res.setHeader("content-type", "application/json");
93
- res.end(JSON.stringify({ error: message }));
94
- }
95
- });
96
- const MAX_PORT_RETRIES = 3;
97
- let hubPort = this.port;
98
- await new Promise((resolve, reject) => {
99
- let retries = 0;
100
- const onError = (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
- }
111
- };
112
- const onListening = () => {
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
- }
117
- resolve();
118
- };
119
- this.server.on("error", onError);
120
- this.server.once("listening", onListening);
121
- this.server.listen(hubPort, "0.0.0.0");
122
- });
123
- const bootstrap = this.userManager.ensureBootstrapAdmin(this.authSecret, "admin", this.authState.bootstrapAdminUserId, this.authState.bootstrapAdminToken);
124
- if (bootstrap.token) {
125
- this.authState.bootstrapAdminUserId = bootstrap.user.id;
126
- this.authState.bootstrapAdminToken = bootstrap.token;
127
- this.saveAuthState();
128
- this.opts.log.info(`memos-local: bootstrap admin token persisted to ${this.authStatePath}`);
129
- }
130
- this.initOnlineTracking();
131
- this.offlineCheckTimer = setInterval(() => this.checkOfflineUsers(), HubServer.OFFLINE_CHECK_INTERVAL_MS);
132
- this.backfillMemoryEmbeddings();
133
- return `http://127.0.0.1:${hubPort}`;
134
- }
135
- async stop() {
136
- if (this.offlineCheckTimer) {
137
- clearInterval(this.offlineCheckTimer);
138
- this.offlineCheckTimer = undefined;
139
- }
140
- if (!this.server)
141
- return;
142
- try {
143
- const activeUsers = this.opts.store.listHubUsers("active");
144
- const ownerId = this.authState.bootstrapAdminUserId || "";
145
- for (const u of activeUsers) {
146
- if (u.id === ownerId)
147
- continue;
148
- try {
149
- this.opts.store.insertHubNotification({
150
- id: (0, crypto_1.randomUUID)(), userId: u.id, type: "hub_shutdown",
151
- resource: "system", title: `Team server "${this.teamName}" has been shut down by the admin.`,
152
- });
153
- }
154
- catch { /* best-effort */ }
155
- }
156
- }
157
- catch { /* best-effort */ }
158
- const server = this.server;
159
- this.server = undefined;
160
- await new Promise((resolve) => server.close(() => resolve()));
161
- }
162
- get port() {
163
- const configured = this.opts.config.sharing?.hub?.port;
164
- const derived = this.opts.defaultHubPort;
165
- if (derived && (!configured || configured === 18800))
166
- return derived;
167
- return configured ?? 18800;
168
- }
169
- get teamName() {
170
- return this.opts.config.sharing?.hub?.teamName ?? "";
171
- }
172
- get teamToken() {
173
- return this.opts.config.sharing?.hub?.teamToken ?? "";
174
- }
175
- get authSecret() {
176
- return this.authState.authSecret;
177
- }
178
- get hubInstanceId() {
179
- return this.authState.hubInstanceId ?? "";
180
- }
181
- loadAuthState() {
182
- try {
183
- const raw = fs.readFileSync(this.authStatePath, "utf8");
184
- const parsed = JSON.parse(raw);
185
- if (parsed.authSecret) {
186
- if (!parsed.hubInstanceId) {
187
- parsed.hubInstanceId = (0, crypto_1.randomUUID)();
188
- fs.writeFileSync(this.authStatePath, JSON.stringify(parsed, null, 2), "utf8");
189
- }
190
- return parsed;
191
- }
192
- }
193
- catch { }
194
- const initial = { authSecret: (0, crypto_1.randomBytes)(32).toString("hex"), hubInstanceId: (0, crypto_1.randomUUID)() };
195
- fs.mkdirSync(path.dirname(this.authStatePath), { recursive: true });
196
- fs.writeFileSync(this.authStatePath, JSON.stringify(initial, null, 2), "utf8");
197
- return initial;
198
- }
199
- saveAuthState() {
200
- fs.mkdirSync(path.dirname(this.authStatePath), { recursive: true });
201
- fs.writeFileSync(this.authStatePath, JSON.stringify(this.authState, null, 2), "utf8");
202
- }
203
- embedChunksAsync(chunkIds, chunks) {
204
- const embedder = this.opts.embedder;
205
- if (!embedder)
206
- return;
207
- const texts = chunks.map(c => c.summary || (c.content ? c.content.slice(0, 500) : ""));
208
- embedder.embed(texts).then((vectors) => {
209
- for (let i = 0; i < vectors.length; i++) {
210
- if (vectors[i]) {
211
- this.opts.store.upsertHubEmbedding(chunkIds[i], new Float32Array(vectors[i]));
212
- }
213
- }
214
- this.opts.log.info(`hub: embedded ${vectors.filter(Boolean).length}/${chunkIds.length} shared chunks`);
215
- }).catch((err) => {
216
- this.opts.log.warn(`hub: embedding shared chunks failed: ${err}`);
217
- });
218
- }
219
- embedSkillAsync(skillId, name, description, sourceUserId, sourceSkillId) {
220
- const embedder = this.opts.embedder;
221
- if (!embedder)
222
- return;
223
- const text = `${name}: ${description}`;
224
- embedder.embed([text]).then((vectors) => {
225
- if (vectors[0]) {
226
- this.opts.store.upsertHubSkillEmbedding(skillId, Array.from(vectors[0]), sourceUserId, sourceSkillId);
227
- this.opts.log.info(`hub: embedded shared skill ${skillId}`);
228
- }
229
- }).catch((err) => {
230
- this.opts.log.warn(`hub: embedding shared skill failed: ${err}`);
231
- });
232
- }
233
- backfillMemoryEmbeddings() {
234
- if (!this.opts.embedder)
235
- return;
236
- try {
237
- const all = this.opts.store.listHubMemories({ limit: 500 });
238
- const missing = all.filter(m => {
239
- try {
240
- return !this.opts.store.getHubMemoryEmbedding(m.id);
241
- }
242
- catch {
243
- return true;
244
- }
245
- });
246
- if (missing.length === 0)
247
- return;
248
- this.opts.log.info(`hub: backfilling embeddings for ${missing.length} hub memories`);
249
- const texts = missing.map(m => (m.summary || m.content || "").slice(0, 500));
250
- this.opts.embedder.embed(texts).then((vectors) => {
251
- let count = 0;
252
- for (let i = 0; i < vectors.length; i++) {
253
- if (vectors[i]) {
254
- this.opts.store.upsertHubMemoryEmbedding(missing[i].id, new Float32Array(vectors[i]));
255
- count++;
256
- }
257
- }
258
- this.opts.log.info(`hub: backfilled ${count}/${missing.length} memory embeddings`);
259
- }).catch((err) => {
260
- this.opts.log.warn(`hub: backfill memory embeddings failed: ${err}`);
261
- });
262
- }
263
- catch (err) {
264
- this.opts.log.warn(`hub: backfill memory embeddings error: ${err}`);
265
- }
266
- }
267
- embedMemoryAsync(memoryId, summary, content) {
268
- const embedder = this.opts.embedder;
269
- if (!embedder)
270
- return;
271
- const text = (summary || content || "").slice(0, 500);
272
- if (!text)
273
- return;
274
- embedder.embed([text]).then((vectors) => {
275
- if (vectors[0]) {
276
- this.opts.store.upsertHubMemoryEmbedding(memoryId, new Float32Array(vectors[0]));
277
- this.opts.log.info(`hub: embedded shared memory ${memoryId}`);
278
- }
279
- }).catch((err) => {
280
- this.opts.log.warn(`hub: embedding shared memory failed: ${err}`);
281
- });
282
- }
283
- async handle(req, res) {
284
- const url = new URL(req.url || "/", `http://127.0.0.1:${this.port}`);
285
- const routePath = url.pathname;
286
- if (req.method === "GET" && routePath === "/api/v1/hub/info") {
287
- return this.json(res, 200, {
288
- teamName: this.teamName,
289
- version: "0.0.0",
290
- apiVersion: "v1",
291
- hubInstanceId: this.hubInstanceId,
292
- });
293
- }
294
- if (req.method === "POST" && routePath === "/api/v1/hub/join") {
295
- const body = await this.readJson(req);
296
- if (!body || body.teamToken !== this.teamToken) {
297
- return this.json(res, 403, { error: "invalid_team_token" });
298
- }
299
- const username = String(body.username || `user-${(0, crypto_1.randomUUID)().slice(0, 8)}`);
300
- const joinIp = (typeof body.clientIp === "string" && body.clientIp)
301
- || req.headers["x-client-ip"]?.trim()
302
- || req.headers["x-forwarded-for"]?.split(",")[0]?.trim()
303
- || req.socket.remoteAddress || "";
304
- const identityKey = typeof body.identityKey === "string" ? body.identityKey.trim() : "";
305
- const dryRun = body.dryRun === true;
306
- const identityMatch = identityKey
307
- ? this.userManager.findByIdentityKey(identityKey)
308
- : null;
309
- if (identityMatch) {
310
- if (!dryRun) {
311
- try {
312
- this.opts.store.updateHubUserActivity(identityMatch.id, joinIp);
313
- }
314
- catch { /* best-effort */ }
315
- }
316
- if (identityMatch.status === "active") {
317
- if (dryRun)
318
- return this.json(res, 200, { status: "active", dryRun: true });
319
- const token = (0, auth_1.issueUserToken)({ userId: identityMatch.id, username: identityMatch.username, role: identityMatch.role, status: "active" }, this.authSecret);
320
- this.userManager.approveUser(identityMatch.id, token);
321
- return this.json(res, 200, { status: "active", userId: identityMatch.id, userToken: token, identityKey: identityMatch.identityKey || identityKey });
322
- }
323
- if (identityMatch.status === "pending") {
324
- if (dryRun)
325
- return this.json(res, 200, { status: "pending", dryRun: true });
326
- this.notifyAdmins("user_join_request", "user", identityMatch.username, "", { dedup: true });
327
- return this.json(res, 200, { status: "pending", userId: identityMatch.id, identityKey: identityMatch.identityKey || identityKey });
328
- }
329
- if (identityMatch.status === "rejected") {
330
- if (dryRun)
331
- return this.json(res, 200, { status: "rejected", dryRun: true });
332
- if (body.reapply === true) {
333
- this.userManager.resetToPending(identityMatch.id);
334
- this.notifyAdmins("user_join_request", "user", identityMatch.username, "");
335
- this.opts.log.info(`Hub: rejected user "${identityMatch.username}" (${identityMatch.id}) re-applied, reset to pending`);
336
- return this.json(res, 200, { status: "pending", userId: identityMatch.id, identityKey: identityMatch.identityKey || identityKey });
337
- }
338
- return this.json(res, 200, { status: "rejected", userId: identityMatch.id });
339
- }
340
- if (identityMatch.status === "removed" || identityMatch.status === "left") {
341
- if (dryRun)
342
- return this.json(res, 200, { status: "can_rejoin", dryRun: true });
343
- this.userManager.rejoinUser(identityMatch.id);
344
- this.notifyAdmins("user_join_request", "user", identityMatch.username, "", { dedup: true });
345
- this.opts.log.info(`Hub: ${identityMatch.status} user "${identityMatch.username}" (${identityMatch.id}) re-applied via rejoin, reset to pending`);
346
- return this.json(res, 200, { status: "pending", userId: identityMatch.id, identityKey: identityMatch.identityKey || identityKey });
347
- }
348
- if (identityMatch.status === "blocked") {
349
- return this.json(res, 200, { status: "blocked", userId: identityMatch.id });
350
- }
351
- }
352
- const existingUsers = this.opts.store.listHubUsers();
353
- const nameConflict = existingUsers.find(u => u.username === username);
354
- if (nameConflict) {
355
- this.opts.log.info(`Hub: join rejected — username "${username}" already taken by user ${nameConflict.id} (status=${nameConflict.status})`);
356
- return this.json(res, 409, { error: "username_taken", message: `Username "${username}" is already in use. Please choose a different nickname.` });
357
- }
358
- if (dryRun) {
359
- return this.json(res, 200, { status: "ok", dryRun: true });
360
- }
361
- const generatedIdentityKey = identityKey || (0, crypto_1.randomUUID)();
362
- const user = this.userManager.createPendingUser({
363
- username,
364
- deviceName: typeof body.deviceName === "string" ? body.deviceName : undefined,
365
- identityKey: generatedIdentityKey,
366
- });
367
- try {
368
- this.opts.store.updateHubUserActivity(user.id, joinIp);
369
- }
370
- catch { /* best-effort */ }
371
- this.opts.log.info(`Hub: user "${username}" (${user.id}) registered as pending, awaiting admin approval`);
372
- this.notifyAdmins("user_join_request", "user", username, "");
373
- return this.json(res, 200, { status: "pending", userId: user.id, identityKey: generatedIdentityKey });
374
- }
375
- if (req.method === "POST" && routePath === "/api/v1/hub/registration-status") {
376
- const body = await this.readJson(req);
377
- if (!body || body.teamToken !== this.teamToken) {
378
- return this.json(res, 403, { error: "invalid_team_token" });
379
- }
380
- const userId = String(body.userId || "");
381
- if (!userId)
382
- return this.json(res, 400, { error: "missing_user_id" });
383
- const user = this.opts.store.getHubUser(userId);
384
- if (!user)
385
- return this.json(res, 404, { error: "not_found" });
386
- if (user.status === "pending") {
387
- return this.json(res, 200, { status: "pending" });
388
- }
389
- if (user.status === "rejected") {
390
- return this.json(res, 200, { status: "rejected" });
391
- }
392
- if (user.status === "blocked") {
393
- return this.json(res, 200, { status: "blocked" });
394
- }
395
- if (user.status === "left") {
396
- return this.json(res, 200, { status: "left" });
397
- }
398
- if (user.status === "removed") {
399
- return this.json(res, 200, { status: "removed" });
400
- }
401
- if (user.status === "active") {
402
- const token = (0, auth_1.issueUserToken)({ userId: user.id, username: user.username, role: user.role, status: user.status }, this.authSecret);
403
- this.userManager.approveUser(user.id, token);
404
- return this.json(res, 200, { status: "active", userToken: token });
405
- }
406
- return this.json(res, 200, { status: user.status });
407
- }
408
- if (req.method === "POST" && routePath === "/api/v1/hub/withdraw-pending") {
409
- const body = await this.readJson(req);
410
- if (!body || body.teamToken !== this.teamToken) {
411
- return this.json(res, 403, { error: "invalid_team_token" });
412
- }
413
- const userId = String(body.userId || "");
414
- if (!userId)
415
- return this.json(res, 400, { error: "missing_user_id" });
416
- const user = this.opts.store.getHubUser(userId);
417
- if (!user)
418
- return this.json(res, 200, { ok: true });
419
- if (user.status === "pending") {
420
- this.userManager.markUserLeft(userId);
421
- this.opts.log.info(`Hub: user "${user.username}" (${userId}) withdrew pending application`);
422
- }
423
- return this.json(res, 200, { ok: true });
424
- }
425
- // All endpoints below require authentication + rate limiting
426
- const auth = this.authenticate(req);
427
- if (!auth)
428
- return this.json(res, 401, { error: "unauthorized" });
429
- const endpointKey = routePath.replace(/^\/api\/v1\/hub\//, "").replace(/\/[^/]+\/bundle$/, "/bundle");
430
- if (!this.checkRateLimit(auth.userId, endpointKey)) {
431
- return this.json(res, 429, { error: "rate_limit_exceeded", retryAfterMs: HubServer.RATE_WINDOW_MS });
432
- }
433
- if (req.method === "POST" && routePath === "/api/v1/hub/heartbeat") {
434
- return this.json(res, 200, { ok: true });
435
- }
436
- if (req.method === "POST" && routePath === "/api/v1/hub/leave") {
437
- this.opts.store.deleteHubMemoriesByUser(auth.userId);
438
- this.opts.store.deleteHubTasksByUser(auth.userId);
439
- this.opts.store.deleteHubSkillsByUser(auth.userId);
440
- this.userManager.markUserLeft(auth.userId);
441
- this.knownOnlineUsers.delete(auth.userId);
442
- this.notifyAdmins("user_left", "user", auth.username, auth.userId);
443
- this.opts.log.info(`Hub: user "${auth.username}" (${auth.userId}) left voluntarily, resources cleaned, status set to "left"`);
444
- return this.json(res, 200, { ok: true });
445
- }
446
- if (req.method === "GET" && routePath === "/api/v1/hub/me") {
447
- const user = this.opts.store.getHubUser(auth.userId);
448
- if (!user)
449
- return this.json(res, 401, { error: "unauthorized" });
450
- return this.json(res, 200, user);
451
- }
452
- if (req.method === "POST" && routePath === "/api/v1/hub/me/update-profile") {
453
- const body = await this.readJson(req);
454
- if (!body)
455
- return this.json(res, 400, { error: "invalid_body" });
456
- const newUsername = String(body.username || "").trim();
457
- if (!newUsername || newUsername.length < 2 || newUsername.length > 32) {
458
- return this.json(res, 400, { error: "invalid_username", message: "Username must be 2-32 characters" });
459
- }
460
- if (this.userManager.isUsernameTaken(newUsername, auth.userId)) {
461
- return this.json(res, 409, { error: "username_taken", message: "Username already in use" });
462
- }
463
- const updated = this.userManager.updateUsername(auth.userId, newUsername);
464
- if (!updated)
465
- return this.json(res, 404, { error: "not_found" });
466
- const ttlMs = updated.role === "admin" ? 3650 * 24 * 60 * 60 * 1000 : undefined;
467
- const newToken = (0, auth_1.issueUserToken)({ userId: updated.id, username: newUsername, role: updated.role, status: updated.status }, this.authSecret, ttlMs);
468
- this.userManager.approveUser(updated.id, newToken);
469
- if (updated.id === this.authState.bootstrapAdminUserId) {
470
- this.authState.bootstrapAdminToken = newToken;
471
- this.saveAuthState();
472
- }
473
- this.opts.log.info(`Hub: user "${auth.userId}" renamed to "${newUsername}"`);
474
- return this.json(res, 200, { ok: true, username: newUsername, userToken: newToken });
475
- }
476
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/pending-users") {
477
- if (auth.role !== "admin")
478
- return this.json(res, 403, { error: "forbidden" });
479
- return this.json(res, 200, { users: this.userManager.listPendingUsers() });
480
- }
481
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/approve-user") {
482
- if (auth.role !== "admin")
483
- return this.json(res, 403, { error: "forbidden" });
484
- const body = await this.readJson(req);
485
- const userId = String(body.userId);
486
- const username = String(body.username || "");
487
- const token = (0, auth_1.issueUserToken)({ userId, username, role: "member", status: "active" }, this.authSecret);
488
- const approved = this.userManager.approveUser(userId, token);
489
- if (!approved)
490
- return this.json(res, 404, { error: "not_found" });
491
- try {
492
- this.opts.store.updateHubUserActivity(userId, "");
493
- }
494
- catch { /* best-effort */ }
495
- try {
496
- this.opts.store.insertHubNotification({
497
- id: (0, crypto_1.randomUUID)(), userId, type: "membership_approved",
498
- resource: "user", title: `Your request to join team "${this.teamName}" has been approved. Welcome!`,
499
- });
500
- }
501
- catch { /* best-effort */ }
502
- return this.json(res, 200, { status: "active", token });
503
- }
504
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/reject-user") {
505
- if (auth.role !== "admin")
506
- return this.json(res, 403, { error: "forbidden" });
507
- const body = await this.readJson(req);
508
- const userId = String(body.userId);
509
- const rejected = this.userManager.rejectUser(userId);
510
- if (!rejected)
511
- return this.json(res, 404, { error: "not_found" });
512
- try {
513
- this.opts.store.insertHubNotification({
514
- id: (0, crypto_1.randomUUID)(), userId, type: "membership_rejected",
515
- resource: "user", title: `Your request to join team "${this.teamName}" has been declined.`,
516
- });
517
- }
518
- catch { /* best-effort */ }
519
- return this.json(res, 200, { status: "rejected" });
520
- }
521
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/users") {
522
- if (auth.role !== "admin")
523
- return this.json(res, 403, { error: "forbidden" });
524
- const users = this.opts.store.listHubUsers().filter(u => u.status === "active");
525
- const contribs = this.opts.store.getHubUserContributions();
526
- const ownerId = this.authState.bootstrapAdminUserId || "";
527
- const now = Date.now();
528
- return this.json(res, 200, { users: users.map(u => {
529
- const c = contribs[u.id] || { memoryCount: 0, taskCount: 0, skillCount: 0 };
530
- const isOnline = u.id === ownerId || (!!u.lastActiveAt && now - u.lastActiveAt < HubServer.OFFLINE_THRESHOLD_MS);
531
- return {
532
- id: u.id, username: u.username, role: u.role, status: u.status,
533
- deviceName: u.deviceName, createdAt: u.createdAt, approvedAt: u.approvedAt,
534
- lastIp: u.lastIp || "", lastActiveAt: u.lastActiveAt,
535
- isOwner: u.id === ownerId, isOnline,
536
- memoryCount: c.memoryCount, taskCount: c.taskCount, skillCount: c.skillCount,
537
- };
538
- }) });
539
- }
540
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/change-role") {
541
- if (auth.role !== "admin")
542
- return this.json(res, 403, { error: "forbidden" });
543
- const body = await this.readJson(req);
544
- const userId = String(body?.userId || "");
545
- const newRole = String(body?.role || "");
546
- if (!userId || (newRole !== "admin" && newRole !== "member"))
547
- return this.json(res, 400, { error: "invalid_params" });
548
- if (newRole === "member" && userId === this.authState.bootstrapAdminUserId) {
549
- return this.json(res, 403, { error: "cannot_demote_owner", message: "The hub owner cannot be demoted" });
550
- }
551
- const user = this.opts.store.getHubUser(userId);
552
- if (!user || user.status !== "active")
553
- return this.json(res, 404, { error: "not_found" });
554
- const updatedUser = { ...user, role: newRole };
555
- this.opts.store.upsertHubUser(updatedUser);
556
- this.opts.log.info(`Hub: admin "${auth.userId}" changed role of "${userId}" to "${newRole}"`);
557
- try {
558
- const notifType = newRole === "admin" ? "role_promoted" : "role_demoted";
559
- this.opts.store.insertHubNotification({
560
- id: (0, crypto_1.randomUUID)(), userId, type: notifType,
561
- resource: "user", title: `Your role in team "${this.teamName}" has been changed to ${newRole}.`,
562
- });
563
- }
564
- catch { /* best-effort */ }
565
- return this.json(res, 200, { ok: true, role: newRole });
566
- }
567
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/rename-user") {
568
- if (auth.role !== "admin")
569
- return this.json(res, 403, { error: "forbidden" });
570
- const body = await this.readJson(req);
571
- const userId = String(body?.userId || "");
572
- const newUsername = String(body?.username || "").trim();
573
- if (!userId || !newUsername || newUsername.length < 2 || newUsername.length > 32) {
574
- return this.json(res, 400, { error: "invalid_params", message: "userId and username (2-32 chars) required" });
575
- }
576
- if (this.userManager.isUsernameTaken(newUsername, userId)) {
577
- return this.json(res, 409, { error: "username_taken", message: "Username already in use" });
578
- }
579
- const user = this.opts.store.getHubUser(userId);
580
- if (!user || user.status !== "active")
581
- return this.json(res, 404, { error: "not_found" });
582
- const ttlMs = user.role === "admin" ? 3650 * 24 * 60 * 60 * 1000 : undefined;
583
- const newToken = (0, auth_1.issueUserToken)({ userId: user.id, username: newUsername, role: user.role, status: user.status }, this.authSecret, ttlMs);
584
- this.userManager.approveUser(user.id, newToken);
585
- const updated = this.opts.store.getHubUser(userId);
586
- const finalUser = { ...updated, username: newUsername };
587
- this.opts.store.upsertHubUser(finalUser);
588
- if (userId === this.authState.bootstrapAdminUserId) {
589
- this.authState.bootstrapAdminToken = newToken;
590
- this.saveAuthState();
591
- }
592
- try {
593
- this.opts.store.insertHubNotification({
594
- id: (0, crypto_1.randomUUID)(), userId, type: "username_renamed",
595
- resource: "user", title: `Your nickname has been changed from "${user.username}" to "${newUsername}" by the admin.`,
596
- });
597
- }
598
- catch { /* best-effort */ }
599
- this.opts.log.info(`Hub: admin "${auth.userId}" renamed user "${userId}" to "${newUsername}"`);
600
- return this.json(res, 200, { ok: true, username: newUsername });
601
- }
602
- if (req.method === "POST" && routePath === "/api/v1/hub/admin/remove-user") {
603
- if (auth.role !== "admin")
604
- return this.json(res, 403, { error: "forbidden" });
605
- const body = await this.readJson(req);
606
- const userId = String(body?.userId || "");
607
- if (!userId)
608
- return this.json(res, 400, { error: "missing_user_id" });
609
- if (userId === auth.userId)
610
- return this.json(res, 400, { error: "cannot_remove_self" });
611
- if (userId === this.authState.bootstrapAdminUserId)
612
- return this.json(res, 403, { error: "cannot_remove_owner", message: "The hub owner cannot be removed" });
613
- try {
614
- this.opts.store.insertHubNotification({
615
- id: (0, crypto_1.randomUUID)(), userId, type: "membership_removed",
616
- resource: "user", title: `You have been removed from team "${this.teamName}" by the admin.`,
617
- });
618
- }
619
- catch { /* best-effort */ }
620
- const cleanResources = body?.cleanResources === true;
621
- const deleted = this.opts.store.deleteHubUser(userId, cleanResources);
622
- if (!deleted)
623
- return this.json(res, 404, { error: "not_found" });
624
- this.knownOnlineUsers.delete(userId);
625
- this.opts.log.info(`Hub: admin "${auth.userId}" removed user "${userId}" (cleanResources=${cleanResources})`);
626
- return this.json(res, 200, { ok: true });
627
- }
628
- if (req.method === "POST" && routePath === "/api/v1/hub/tasks/share") {
629
- const body = await this.readJson(req);
630
- if (!body?.task)
631
- return this.json(res, 400, { error: "invalid_payload" });
632
- const task = { ...body.task, sourceUserId: auth.userId };
633
- const existingTask = task.sourceTaskId ? this.opts.store.getHubTaskBySource(auth.userId, task.sourceTaskId) : null;
634
- this.opts.store.upsertHubTask(task);
635
- const chunks = Array.isArray(body.chunks) ? body.chunks : [];
636
- const chunkIds = [];
637
- for (const chunk of chunks) {
638
- this.opts.store.upsertHubChunk({ ...chunk, sourceUserId: auth.userId });
639
- chunkIds.push(chunk.id);
640
- }
641
- if (this.opts.embedder && chunkIds.length > 0) {
642
- this.embedChunksAsync(chunkIds, chunks);
643
- }
644
- if (!existingTask) {
645
- this.notifyAdmins("resource_shared", "task", String(task.title || task.sourceTaskId || ""), auth.userId);
646
- }
647
- return this.json(res, 200, { ok: true, chunks: chunkIds.length });
648
- }
649
- if (req.method === "POST" && routePath === "/api/v1/hub/tasks/unshare") {
650
- const body = await this.readJson(req);
651
- const srcTaskId = String(body.sourceTaskId);
652
- const existing = this.opts.store.getHubTaskBySource(auth.userId, srcTaskId);
653
- this.opts.store.deleteHubTaskBySource(auth.userId, srcTaskId);
654
- if (existing) {
655
- this.notifyAdmins("resource_unshared", "task", existing.title || srcTaskId, auth.userId);
656
- }
657
- return this.json(res, 200, { ok: true });
658
- }
659
- if (req.method === "POST" && routePath === "/api/v1/hub/memories/share") {
660
- const body = await this.readJson(req);
661
- if (!body?.memory)
662
- return this.json(res, 400, { error: "invalid_payload" });
663
- const m = body.memory;
664
- const sourceChunkId = String(m.sourceChunkId || "");
665
- if (!sourceChunkId)
666
- return this.json(res, 400, { error: "missing_source_chunk_id" });
667
- const existing = this.opts.store.getHubMemoryBySource(auth.userId, sourceChunkId);
668
- const memoryId = existing?.id ?? (0, crypto_1.randomUUID)();
669
- const visibility = "public";
670
- const resolvedGroupId = null;
671
- const now = Date.now();
672
- this.opts.store.upsertHubMemory({
673
- id: memoryId,
674
- sourceChunkId,
675
- sourceUserId: auth.userId,
676
- role: String(m.role || "assistant"),
677
- content: String(m.content || ""),
678
- summary: String(m.summary || ""),
679
- kind: String(m.kind || "paragraph"),
680
- groupId: resolvedGroupId,
681
- visibility,
682
- createdAt: existing?.createdAt ?? now,
683
- updatedAt: now,
684
- });
685
- this.embedMemoryAsync(memoryId, String(m.summary || ""), String(m.content || ""));
686
- if (!existing) {
687
- this.notifyAdmins("resource_shared", "memory", String(m.summary || m.content?.slice(0, 60) || memoryId), auth.userId);
688
- }
689
- return this.json(res, 200, { ok: true, memoryId, visibility });
690
- }
691
- if (req.method === "POST" && routePath === "/api/v1/hub/memories/unshare") {
692
- const body = await this.readJson(req);
693
- const sourceChunkId = String(body?.sourceChunkId || "");
694
- if (!sourceChunkId)
695
- return this.json(res, 400, { error: "missing_source_chunk_id" });
696
- const existing = this.opts.store.getHubMemoryBySource(auth.userId, sourceChunkId);
697
- this.opts.store.deleteHubMemoryBySource(auth.userId, sourceChunkId);
698
- if (existing) {
699
- this.notifyAdmins("resource_unshared", "memory", existing.summary || existing.content?.slice(0, 60) || sourceChunkId, auth.userId);
700
- }
701
- return this.json(res, 200, { ok: true });
702
- }
703
- if (req.method === "GET" && routePath === "/api/v1/hub/memories") {
704
- const limit = Number(url.searchParams.get("limit") || 40);
705
- const memories = this.opts.store.listVisibleHubMemories(auth.userId, limit);
706
- return this.json(res, 200, { memories });
707
- }
708
- if (req.method === "GET" && routePath === "/api/v1/hub/tasks") {
709
- const limit = Number(url.searchParams.get("limit") || 40);
710
- const tasks = this.opts.store.listVisibleHubTasks(auth.userId, limit);
711
- return this.json(res, 200, { tasks });
712
- }
713
- if (req.method === "GET" && routePath === "/api/v1/hub/skills/list") {
714
- const limit = Number(url.searchParams.get("limit") || 40);
715
- const skills = this.opts.store.listVisibleHubSkills(auth.userId, limit);
716
- return this.json(res, 200, { skills });
717
- }
718
- if (req.method === "POST" && routePath === "/api/v1/hub/search") {
719
- const body = await this.readJson(req);
720
- const query = String(body.query || "");
721
- const maxResults = Number(body.maxResults || 10);
722
- const ftsHits = this.opts.store.searchHubChunks(query, { userId: auth.userId, maxResults: maxResults * 2 });
723
- const memFtsHits = this.opts.store.searchHubMemories(query, { userId: auth.userId, maxResults: maxResults * 2 });
724
- // Track which IDs are memories vs chunks
725
- const memoryIdSet = new Set(memFtsHits.map(({ hit }) => hit.id));
726
- // Two-stage retrieval: FTS candidates first, then embed + cosine rerank
727
- let mergedIds;
728
- if (this.opts.embedder) {
729
- try {
730
- const [queryVec] = await this.opts.embedder.embed([query]);
731
- if (queryVec) {
732
- const allEmb = this.opts.store.getVisibleHubEmbeddings(auth.userId);
733
- const scored = [];
734
- const cosineSim = (a, b) => {
735
- let dot = 0, nA = 0, nB = 0;
736
- const len = Math.min(a.length, b.length);
737
- for (let i = 0; i < len; i++) {
738
- dot += a[i] * b[i];
739
- nA += a[i] * a[i];
740
- nB += b[i] * b[i];
741
- }
742
- return nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
743
- };
744
- for (const e of allEmb)
745
- scored.push({ id: e.chunkId, score: cosineSim(e.vector, queryVec) });
746
- const memEmb = this.opts.store.getVisibleHubMemoryEmbeddings(auth.userId);
747
- for (const e of memEmb) {
748
- scored.push({ id: e.memoryId, score: cosineSim(e.vector, queryVec) });
749
- memoryIdSet.add(e.memoryId);
750
- }
751
- scored.sort((a, b) => b.score - a.score);
752
- const topScored = scored.slice(0, maxResults * 2);
753
- const K = 60;
754
- const rrfScores = new Map();
755
- ftsHits.forEach(({ hit }, idx) => {
756
- rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
757
- });
758
- memFtsHits.forEach(({ hit }, idx) => {
759
- rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
760
- });
761
- topScored.forEach(({ id }, idx) => {
762
- rrfScores.set(id, (rrfScores.get(id) ?? 0) + 1 / (K + idx + 1));
763
- });
764
- mergedIds = [...rrfScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, maxResults).map(([id]) => id);
765
- }
766
- else {
767
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
768
- }
769
- }
770
- catch {
771
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
772
- }
773
- }
774
- else {
775
- mergedIds = [...ftsHits.map(({ hit }) => hit.id), ...memFtsHits.map(({ hit }) => hit.id)].slice(0, maxResults);
776
- }
777
- const ftsMap = new Map(ftsHits.map(({ hit }) => [hit.id, hit]));
778
- const memFtsMap = new Map(memFtsHits.map(({ hit }) => [hit.id, hit]));
779
- const hits = mergedIds.map((id, rank) => {
780
- const isMemory = memoryIdSet.has(id);
781
- if (isMemory) {
782
- let mhit = memFtsMap.get(id);
783
- if (!mhit) {
784
- const visibleHit = this.opts.store.getVisibleHubSearchHitByMemoryId(id, auth.userId);
785
- if (!visibleHit)
786
- return null;
787
- mhit = visibleHit;
788
- }
789
- const remoteHitId = (0, crypto_1.randomUUID)();
790
- this.remoteHitMap.set(remoteHitId, { chunkId: id, type: "memory", expiresAt: Date.now() + 10 * 60 * 1000, requesterUserId: auth.userId });
791
- return {
792
- remoteHitId, summary: mhit.summary, excerpt: mhit.content.slice(0, 240), hubRank: rank + 1,
793
- taskTitle: null, ownerName: mhit.owner_name || "unknown", groupName: mhit.group_name,
794
- visibility: mhit.visibility, source: { ts: mhit.created_at, role: mhit.role },
795
- };
796
- }
797
- let hit = ftsMap.get(id);
798
- if (!hit) {
799
- const visibleHit = this.opts.store.getVisibleHubSearchHitByChunkId(id, auth.userId);
800
- if (!visibleHit)
801
- return null;
802
- hit = visibleHit;
803
- }
804
- const remoteHitId = (0, crypto_1.randomUUID)();
805
- this.remoteHitMap.set(remoteHitId, { chunkId: id, type: "chunk", expiresAt: Date.now() + 10 * 60 * 1000, requesterUserId: auth.userId });
806
- return {
807
- remoteHitId, summary: hit.summary, excerpt: hit.content.slice(0, 240), hubRank: rank + 1,
808
- taskTitle: hit.task_title, ownerName: hit.owner_name || "unknown", groupName: hit.group_name,
809
- visibility: hit.visibility, source: { ts: hit.created_at, role: hit.role },
810
- };
811
- }).filter(Boolean);
812
- return this.json(res, 200, { hits, meta: { totalCandidates: hits.length, searchedGroups: [], includedPublic: true } });
813
- }
814
- if (req.method === "GET" && routePath === "/api/v1/hub/skills") {
815
- const skillQuery = String(url.searchParams.get("query") || "");
816
- const skillMaxResults = Number(url.searchParams.get("maxResults") || 10);
817
- const ftsSkillHits = this.opts.store.searchHubSkills(skillQuery, {
818
- userId: auth.userId,
819
- maxResults: skillMaxResults * 2,
820
- });
821
- let mergedSkillIds;
822
- if (this.opts.embedder && skillQuery) {
823
- try {
824
- const [queryVec] = await this.opts.embedder.embed([skillQuery]);
825
- if (queryVec) {
826
- const skillEmbs = this.opts.store.getVisibleHubSkillEmbeddings();
827
- const cosineSim = (vec) => {
828
- let dot = 0, nA = 0, nB = 0;
829
- for (let i = 0; i < queryVec.length && i < vec.length; i++) {
830
- dot += queryVec[i] * vec[i];
831
- nA += queryVec[i] * queryVec[i];
832
- nB += vec[i] * vec[i];
833
- }
834
- return nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
835
- };
836
- const vecScored = skillEmbs
837
- .map(e => ({ id: e.skillId, score: cosineSim(e.vector) }))
838
- .filter(e => e.score > 0.3)
839
- .sort((a, b) => b.score - a.score)
840
- .slice(0, skillMaxResults * 2);
841
- const K = 60;
842
- const rrfScores = new Map();
843
- ftsSkillHits.forEach(({ hit }, idx) => {
844
- rrfScores.set(hit.id, (rrfScores.get(hit.id) ?? 0) + 1 / (K + idx + 1));
845
- });
846
- vecScored.forEach(({ id }, idx) => {
847
- rrfScores.set(id, (rrfScores.get(id) ?? 0) + 1 / (K + idx + 1));
848
- });
849
- mergedSkillIds = [...rrfScores.entries()].sort((a, b) => b[1] - a[1]).slice(0, skillMaxResults).map(([id]) => id);
850
- }
851
- else {
852
- mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
853
- }
854
- }
855
- catch {
856
- mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
857
- }
858
- }
859
- else {
860
- mergedSkillIds = ftsSkillHits.slice(0, skillMaxResults).map(({ hit }) => hit.id);
861
- }
862
- const ftsSkillMap = new Map(ftsSkillHits.map(({ hit }) => [hit.id, hit]));
863
- const hits = mergedSkillIds.map(id => {
864
- const hit = ftsSkillMap.get(id);
865
- if (hit) {
866
- return {
867
- skillId: hit.id, name: hit.name, description: hit.description,
868
- version: hit.version, visibility: hit.visibility, groupName: hit.group_name,
869
- ownerName: hit.owner_name || "unknown", ownerStatus: hit.owner_status || "",
870
- qualityScore: hit.quality_score,
871
- };
872
- }
873
- const skill = this.opts.store.getHubSkillById(id);
874
- if (!skill)
875
- return null;
876
- return {
877
- skillId: skill.id, name: skill.name, description: skill.description,
878
- version: skill.version, visibility: skill.visibility, groupName: "",
879
- ownerName: "unknown", ownerStatus: "", qualityScore: skill.qualityScore,
880
- };
881
- }).filter(Boolean);
882
- return this.json(res, 200, { hits });
883
- }
884
- if (req.method === "POST" && routePath === "/api/v1/hub/skills/publish") {
885
- const body = await this.readJson(req);
886
- const metadata = body?.metadata ?? {};
887
- const sourceSkillId = String(metadata.id || "");
888
- if (!sourceSkillId)
889
- return this.json(res, 400, { error: "missing_skill_id" });
890
- const existing = this.opts.store.getHubSkillBySource(auth.userId, sourceSkillId);
891
- const skillId = existing?.id ?? (0, crypto_1.randomUUID)();
892
- const visibility = "public";
893
- this.opts.store.upsertHubSkill({
894
- id: skillId,
895
- sourceSkillId,
896
- sourceUserId: auth.userId,
897
- name: String(metadata.name || sourceSkillId),
898
- description: String(metadata.description || ""),
899
- version: Number(metadata.version || 1),
900
- groupId: null,
901
- visibility,
902
- bundle: JSON.stringify(body?.bundle ?? {}),
903
- qualityScore: metadata.qualityScore == null ? null : Number(metadata.qualityScore),
904
- createdAt: existing?.createdAt ?? Date.now(),
905
- updatedAt: Date.now(),
906
- });
907
- this.embedSkillAsync(skillId, String(metadata.name || sourceSkillId), String(metadata.description || ""), auth.userId, sourceSkillId);
908
- if (!existing) {
909
- this.notifyAdmins("resource_shared", "skill", String(metadata.name || sourceSkillId), auth.userId);
910
- }
911
- return this.json(res, 200, { ok: true, skillId, visibility });
912
- }
913
- const skillBundleMatch = req.method === "GET" ? routePath.match(/^\/api\/v1\/hub\/skills\/([^/]+)\/bundle$/) : null;
914
- if (skillBundleMatch) {
915
- const skill = this.opts.store.getHubSkillById(decodeURIComponent(skillBundleMatch[1]));
916
- if (!skill)
917
- return this.json(res, 404, { error: "not_found" });
918
- return this.json(res, 200, {
919
- skillId: skill.id,
920
- metadata: {
921
- id: skill.sourceSkillId,
922
- name: skill.name,
923
- description: skill.description,
924
- version: skill.version,
925
- qualityScore: skill.qualityScore,
926
- },
927
- bundle: JSON.parse(skill.bundle),
928
- });
929
- }
930
- if (req.method === "POST" && routePath === "/api/v1/hub/skills/unpublish") {
931
- const body = await this.readJson(req);
932
- const srcSkillId = String(body?.sourceSkillId || "");
933
- const existing = this.opts.store.getHubSkillBySource(auth.userId, srcSkillId);
934
- this.opts.store.deleteHubSkillBySource(auth.userId, srcSkillId);
935
- if (existing) {
936
- this.notifyAdmins("resource_unshared", "skill", existing.name || srcSkillId, auth.userId);
937
- }
938
- return this.json(res, 200, { ok: true });
939
- }
940
- // ── Admin: shared tasks & skills management ──
941
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-tasks") {
942
- if (auth.role !== "admin")
943
- return this.json(res, 403, { error: "forbidden" });
944
- const tasks = this.opts.store.listAllHubTasks();
945
- return this.json(res, 200, { tasks });
946
- }
947
- const hubTaskDetailMatch = req.method === "GET" ? routePath.match(/^\/api\/v1\/hub\/shared-tasks\/([^/]+)\/detail$/) : null;
948
- if (hubTaskDetailMatch) {
949
- const taskId = decodeURIComponent(hubTaskDetailMatch[1]);
950
- const task = this.opts.store.getHubTaskById(taskId);
951
- if (!task)
952
- return this.json(res, 404, { error: "not_found" });
953
- const chunks = this.opts.store.listHubChunksByTaskId(taskId);
954
- return this.json(res, 200, {
955
- id: task.id, title: task.title, summary: task.summary,
956
- startedAt: task.createdAt, endedAt: task.updatedAt,
957
- chunks: chunks.map(c => ({ role: c.role, content: c.content, summary: c.summary, kind: c.kind, createdAt: c.createdAt })),
958
- });
959
- }
960
- const hubSkillDetailMatch = req.method === "GET" ? routePath.match(/^\/api\/v1\/hub\/shared-skills\/([^/]+)\/detail$/) : null;
961
- if (hubSkillDetailMatch) {
962
- const skillId = decodeURIComponent(hubSkillDetailMatch[1]);
963
- const skill = this.opts.store.getHubSkillById(skillId);
964
- if (!skill)
965
- return this.json(res, 404, { error: "not_found" });
966
- let files = [];
967
- try {
968
- const bundle = JSON.parse(skill.bundle || "{}");
969
- if (Array.isArray(bundle.files)) {
970
- files = bundle.files.map((f) => ({ path: f.path ?? f.name ?? "unknown", type: f.type ?? "file", size: f.size ?? (f.content ? f.content.length : 0) }));
971
- }
972
- }
973
- catch { /* ignore parse error */ }
974
- return this.json(res, 200, {
975
- skill: { id: skill.id, name: skill.name, description: skill.description, version: skill.version, qualityScore: skill.qualityScore, status: "published" },
976
- files,
977
- versions: [],
978
- });
979
- }
980
- const adminTaskDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-tasks\/([^/]+)$/) : null;
981
- if (adminTaskDeleteMatch) {
982
- if (auth.role !== "admin")
983
- return this.json(res, 403, { error: "forbidden" });
984
- const taskId = decodeURIComponent(adminTaskDeleteMatch[1]);
985
- const taskInfo = this.opts.store.getHubTaskById(taskId);
986
- const deleted = this.opts.store.deleteHubTaskById(taskId);
987
- if (!deleted)
988
- return this.json(res, 404, { error: "not_found" });
989
- if (taskInfo) {
990
- this.opts.store.insertHubNotification({ id: (0, crypto_1.randomUUID)(), userId: taskInfo.sourceUserId, type: "resource_removed", resource: "task", title: taskInfo.title });
991
- }
992
- return this.json(res, 200, { ok: true });
993
- }
994
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-skills") {
995
- if (auth.role !== "admin")
996
- return this.json(res, 403, { error: "forbidden" });
997
- const skills = this.opts.store.listAllHubSkills();
998
- return this.json(res, 200, { skills });
999
- }
1000
- const adminSkillDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-skills\/([^/]+)$/) : null;
1001
- if (adminSkillDeleteMatch) {
1002
- if (auth.role !== "admin")
1003
- return this.json(res, 403, { error: "forbidden" });
1004
- const skillId = decodeURIComponent(adminSkillDeleteMatch[1]);
1005
- const skillInfo = this.opts.store.getHubSkillById(skillId);
1006
- const deleted = this.opts.store.deleteHubSkillById(skillId);
1007
- if (!deleted)
1008
- return this.json(res, 404, { error: "not_found" });
1009
- if (skillInfo) {
1010
- this.opts.store.insertHubNotification({ id: (0, crypto_1.randomUUID)(), userId: skillInfo.sourceUserId, type: "resource_removed", resource: "skill", title: skillInfo.name });
1011
- }
1012
- return this.json(res, 200, { ok: true });
1013
- }
1014
- if (req.method === "GET" && routePath === "/api/v1/hub/admin/shared-memories") {
1015
- if (auth.role !== "admin")
1016
- return this.json(res, 403, { error: "forbidden" });
1017
- const memories = this.opts.store.listAllHubMemories();
1018
- return this.json(res, 200, { memories });
1019
- }
1020
- const adminMemoryDeleteMatch = req.method === "DELETE" ? routePath.match(/^\/api\/v1\/hub\/admin\/shared-memories\/([^/]+)$/) : null;
1021
- if (adminMemoryDeleteMatch) {
1022
- if (auth.role !== "admin")
1023
- return this.json(res, 403, { error: "forbidden" });
1024
- const memoryId = decodeURIComponent(adminMemoryDeleteMatch[1]);
1025
- const memInfo = this.opts.store.getHubMemoryById(memoryId);
1026
- const deleted = this.opts.store.deleteHubMemoryById(memoryId);
1027
- if (!deleted)
1028
- return this.json(res, 404, { error: "not_found" });
1029
- if (memInfo) {
1030
- const payload = JSON.stringify({
1031
- memoryId,
1032
- sourceChunkId: memInfo.sourceChunkId,
1033
- });
1034
- this.opts.store.insertHubNotification({
1035
- id: (0, crypto_1.randomUUID)(),
1036
- userId: memInfo.sourceUserId,
1037
- type: "resource_removed",
1038
- resource: "memory",
1039
- title: memInfo.summary || memInfo.id,
1040
- message: payload,
1041
- });
1042
- }
1043
- return this.json(res, 200, { ok: true });
1044
- }
1045
- if (req.method === "POST" && routePath === "/api/v1/hub/memory-detail") {
1046
- const body = await this.readJson(req);
1047
- const hit = this.remoteHitMap.get(String(body.remoteHitId));
1048
- if (!hit || hit.expiresAt < Date.now())
1049
- return this.json(res, 404, { error: "not_found" });
1050
- if (hit.requesterUserId !== auth.userId)
1051
- return this.json(res, 403, { error: "forbidden" });
1052
- if (hit.type === "memory") {
1053
- const mem = this.opts.store.getHubMemoryById(hit.chunkId);
1054
- if (!mem)
1055
- return this.json(res, 404, { error: "not_found" });
1056
- return this.json(res, 200, { content: mem.content, summary: mem.summary, source: { ts: mem.createdAt, role: mem.role } });
1057
- }
1058
- const chunk = this.opts.store.getHubChunkById(hit.chunkId);
1059
- if (!chunk)
1060
- return this.json(res, 404, { error: "not_found" });
1061
- return this.json(res, 200, {
1062
- content: chunk.content,
1063
- summary: chunk.summary,
1064
- source: { ts: chunk.createdAt, role: chunk.role },
1065
- });
1066
- }
1067
- if (req.method === "GET" && routePath === "/api/v1/hub/notifications") {
1068
- const unread = (new URL(req.url, `http://${req.headers.host}`)).searchParams.get("unread") === "1";
1069
- const list = this.opts.store.listHubNotifications(auth.userId, { unreadOnly: unread, limit: 50 });
1070
- const unreadCount = this.opts.store.countUnreadHubNotifications(auth.userId);
1071
- return this.json(res, 200, { notifications: list, unreadCount });
1072
- }
1073
- if (req.method === "POST" && routePath === "/api/v1/hub/notifications/read") {
1074
- const body = await this.readJson(req);
1075
- const ids = Array.isArray(body.ids) ? body.ids : undefined;
1076
- this.opts.store.markHubNotificationsRead(auth.userId, ids);
1077
- return this.json(res, 200, { ok: true });
1078
- }
1079
- if (req.method === "POST" && routePath === "/api/v1/hub/notifications/clear") {
1080
- this.opts.store.clearHubNotifications(auth.userId);
1081
- return this.json(res, 200, { ok: true });
1082
- }
1083
- return this.json(res, 404, { error: "not_found" });
1084
- }
1085
- notifyAdmins(type, resource, title, fromUserId, opts) {
1086
- try {
1087
- const admins = this.opts.store.listHubUsers("active").filter(u => u.role === "admin" && u.id !== fromUserId);
1088
- for (const admin of admins) {
1089
- if (opts?.dedup && this.opts.store.hasRecentHubNotification(admin.id, type, resource, opts.deduoWindowMs ?? 300_000)) {
1090
- continue;
1091
- }
1092
- this.opts.store.insertHubNotification({ id: (0, crypto_1.randomUUID)(), userId: admin.id, type, resource, title });
1093
- }
1094
- }
1095
- catch { /* best-effort */ }
1096
- }
1097
- initOnlineTracking() {
1098
- try {
1099
- const ownerId = this.authState.bootstrapAdminUserId || "";
1100
- const users = this.opts.store.listHubUsers("active");
1101
- const now = Date.now();
1102
- for (const u of users) {
1103
- if (u.id === ownerId)
1104
- continue;
1105
- if (u.lastActiveAt && now - u.lastActiveAt < HubServer.OFFLINE_THRESHOLD_MS) {
1106
- this.knownOnlineUsers.add(u.id);
1107
- }
1108
- }
1109
- }
1110
- catch { /* best-effort */ }
1111
- }
1112
- checkOfflineUsers() {
1113
- try {
1114
- const ownerId = this.authState.bootstrapAdminUserId || "";
1115
- const users = this.opts.store.listHubUsers("active");
1116
- const now = Date.now();
1117
- const currentlyOnline = new Set();
1118
- for (const u of users) {
1119
- if (u.id === ownerId)
1120
- continue;
1121
- if (u.lastActiveAt && now - u.lastActiveAt < HubServer.OFFLINE_THRESHOLD_MS) {
1122
- currentlyOnline.add(u.id);
1123
- }
1124
- }
1125
- for (const uid of this.knownOnlineUsers) {
1126
- if (!currentlyOnline.has(uid)) {
1127
- const user = users.find(u => u.id === uid);
1128
- if (user) {
1129
- this.notifyAdmins("user_offline", "user", user.username, uid);
1130
- this.opts.log.info(`Hub: user "${user.username}" (${uid}) went offline`);
1131
- }
1132
- }
1133
- }
1134
- for (const uid of currentlyOnline) {
1135
- if (!this.knownOnlineUsers.has(uid)) {
1136
- const user = users.find(u => u.id === uid);
1137
- if (user) {
1138
- this.notifyAdmins("user_online", "user", user.username, uid);
1139
- }
1140
- }
1141
- }
1142
- this.knownOnlineUsers = currentlyOnline;
1143
- }
1144
- catch { /* best-effort */ }
1145
- }
1146
- authenticate(req) {
1147
- const header = req.headers.authorization;
1148
- if (!header || !header.startsWith("Bearer "))
1149
- return null;
1150
- const token = header.slice("Bearer ".length);
1151
- const payload = (0, auth_1.verifyUserToken)(token, this.authSecret);
1152
- if (!payload)
1153
- return null;
1154
- const user = this.opts.store.getHubUser(payload.userId);
1155
- if (!user || user.status !== "active")
1156
- return null;
1157
- const hash = (0, crypto_1.createHash)("sha256").update(token).digest("hex");
1158
- if (user.tokenHash !== hash)
1159
- return null;
1160
- const clientIp = req.headers["x-client-ip"]?.trim()
1161
- || req.headers["x-forwarded-for"]?.split(",")[0]?.trim()
1162
- || req.socket.remoteAddress || "";
1163
- try {
1164
- this.opts.store.updateHubUserActivity(user.id, clientIp);
1165
- }
1166
- catch { /* best-effort */ }
1167
- return {
1168
- userId: user.id,
1169
- username: user.username,
1170
- role: user.role,
1171
- status: user.status,
1172
- };
1173
- }
1174
- static MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
1175
- async readJson(req) {
1176
- const chunks = [];
1177
- let totalBytes = 0;
1178
- for await (const chunk of req) {
1179
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1180
- totalBytes += buf.length;
1181
- if (totalBytes > HubServer.MAX_BODY_BYTES) {
1182
- req.destroy();
1183
- throw Object.assign(new Error("request body too large"), { statusCode: 413 });
1184
- }
1185
- chunks.push(buf);
1186
- }
1187
- const raw = Buffer.concat(chunks).toString("utf8");
1188
- return raw ? JSON.parse(raw) : {};
1189
- }
1190
- json(res, statusCode, body) {
1191
- res.statusCode = statusCode;
1192
- res.setHeader("content-type", "application/json");
1193
- res.end(JSON.stringify(body));
1194
- }
1195
- }
1196
- exports.HubServer = HubServer;
1197
- //# sourceMappingURL=server.js.map