@memtensor/memos-local-openclaw-plugin 1.0.4-beta.5 → 1.0.4-beta.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.
- package/README.md +23 -23
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +28 -2
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +1 -2
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +18 -19
- package/dist/client/connector.js.map +1 -1
- package/dist/client/hub.d.ts.map +1 -1
- package/dist/client/hub.js +22 -0
- package/dist/client/hub.js.map +1 -1
- package/dist/client/skill-sync.d.ts +7 -0
- package/dist/client/skill-sync.d.ts.map +1 -1
- package/dist/client/skill-sync.js +10 -0
- package/dist/client/skill-sync.js.map +1 -1
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +101 -81
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +2 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +5 -1
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/storage/sqlite.d.ts +54 -20
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +185 -101
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -1
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +3 -1
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +1619 -629
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +14 -8
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +545 -141
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +355 -41
- package/package.json +1 -1
- package/skill/memos-memory-guide/SKILL.md +64 -26
- package/src/capture/index.ts +29 -1
- package/src/client/connector.ts +15 -21
- package/src/client/hub.ts +18 -0
- package/src/client/skill-sync.ts +14 -0
- package/src/hub/server.ts +88 -74
- package/src/hub/user-manager.ts +7 -3
- package/src/index.ts +7 -2
- package/src/storage/sqlite.ts +192 -122
- package/src/tools/memory-search.ts +2 -1
- package/src/viewer/html.ts +1619 -629
- package/src/viewer/server.ts +506 -128
package/dist/viewer/server.js
CHANGED
|
@@ -228,6 +228,16 @@ class ViewerServer {
|
|
|
228
228
|
}
|
|
229
229
|
if (p === "/api/memories" && req.method === "GET")
|
|
230
230
|
this.serveMemories(res, url);
|
|
231
|
+
else if (p === "/api/memories/share-local" && req.method === "POST")
|
|
232
|
+
this.handleMemoryLocalShare(req, res);
|
|
233
|
+
else if (p === "/api/memories/unshare-local" && req.method === "POST")
|
|
234
|
+
this.handleMemoryLocalUnshare(req, res);
|
|
235
|
+
else if (p.match(/^\/api\/memory\/[^/]+\/scope$/) && req.method === "PUT")
|
|
236
|
+
this.handleMemoryScope(req, res, p);
|
|
237
|
+
else if (p.match(/^\/api\/task\/[^/]+\/scope$/) && req.method === "PUT")
|
|
238
|
+
this.handleTaskScope(req, res, p);
|
|
239
|
+
else if (p.match(/^\/api\/skill\/[^/]+\/scope$/) && req.method === "PUT")
|
|
240
|
+
this.handleSkillScope(req, res, p);
|
|
231
241
|
else if (p === "/api/stats")
|
|
232
242
|
this.serveStats(res, url);
|
|
233
243
|
else if (p === "/api/metrics")
|
|
@@ -282,6 +292,10 @@ class ViewerServer {
|
|
|
282
292
|
this.handleSharingApproveUser(req, res);
|
|
283
293
|
else if (p === "/api/sharing/reject-user" && req.method === "POST")
|
|
284
294
|
this.handleSharingRejectUser(req, res);
|
|
295
|
+
else if (p === "/api/sharing/remove-user" && req.method === "POST")
|
|
296
|
+
this.handleSharingRemoveUser(req, res);
|
|
297
|
+
else if (p === "/api/sharing/change-role" && req.method === "POST")
|
|
298
|
+
this.handleSharingChangeRole(req, res);
|
|
285
299
|
else if (p === "/api/sharing/retry-join" && req.method === "POST")
|
|
286
300
|
this.handleRetryJoin(req, res);
|
|
287
301
|
else if (p === "/api/sharing/search/memories" && req.method === "POST")
|
|
@@ -302,6 +316,8 @@ class ViewerServer {
|
|
|
302
316
|
this.handleSharingTaskUnshare(req, res);
|
|
303
317
|
else if (p === "/api/sharing/update-username" && req.method === "POST")
|
|
304
318
|
this.handleUpdateUsername(req, res);
|
|
319
|
+
else if (p === "/api/sharing/rename-user" && req.method === "POST")
|
|
320
|
+
this.handleAdminRenameUser(req, res);
|
|
305
321
|
else if (p === "/api/sharing/test-hub" && req.method === "POST")
|
|
306
322
|
this.handleTestHubConnection(req, res);
|
|
307
323
|
else if (p === "/api/sharing/memories/share" && req.method === "POST")
|
|
@@ -314,22 +330,14 @@ class ViewerServer {
|
|
|
314
330
|
this.handleSharingSkillShare(req, res);
|
|
315
331
|
else if (p === "/api/sharing/skills/unshare" && req.method === "POST")
|
|
316
332
|
this.handleSharingSkillUnshare(req, res);
|
|
317
|
-
else if (p === "/api/sharing/groups" && req.method === "GET")
|
|
318
|
-
this.serveSharingGroups(res);
|
|
319
|
-
else if (p === "/api/sharing/groups" && req.method === "POST")
|
|
320
|
-
this.handleSharingGroupCreate(req, res);
|
|
321
|
-
else if (p.match(/^\/api\/sharing\/groups\/[^/]+$/) && req.method === "PUT")
|
|
322
|
-
this.handleSharingGroupUpdate(req, res, p);
|
|
323
|
-
else if (p.match(/^\/api\/sharing\/groups\/[^/]+$/) && req.method === "DELETE")
|
|
324
|
-
this.handleSharingGroupDelete(res, p);
|
|
325
|
-
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "GET")
|
|
326
|
-
this.serveSharingGroupMembers(res, p);
|
|
327
|
-
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "POST")
|
|
328
|
-
this.handleSharingGroupAddMember(req, res, p);
|
|
329
|
-
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "DELETE")
|
|
330
|
-
this.handleSharingGroupRemoveMember(req, res, p);
|
|
331
333
|
else if (p === "/api/sharing/users" && req.method === "GET")
|
|
332
334
|
this.serveSharingUsers(res);
|
|
335
|
+
else if (p === "/api/sharing/notifications" && req.method === "GET")
|
|
336
|
+
this.serveSharingNotifications(res, url);
|
|
337
|
+
else if (p === "/api/sharing/notifications/read" && req.method === "POST")
|
|
338
|
+
this.handleSharingNotificationsRead(req, res);
|
|
339
|
+
else if (p === "/api/sharing/notifications/clear" && req.method === "POST")
|
|
340
|
+
this.handleSharingNotificationsClear(req, res);
|
|
333
341
|
else if (p === "/api/admin/shared-tasks" && req.method === "GET")
|
|
334
342
|
this.serveAdminSharedTasks(res);
|
|
335
343
|
else if (p.match(/^\/api\/admin\/shared-tasks\/[^/]+$/) && req.method === "DELETE")
|
|
@@ -531,12 +539,16 @@ class ViewerServer {
|
|
|
531
539
|
const findMergeSources = db.prepare("SELECT id, summary, role FROM chunks WHERE dedup_target = ? AND (dedup_status = 'merged' OR dedup_status = 'duplicate')");
|
|
532
540
|
const chunkIds = rawMemories.map((m) => m.id);
|
|
533
541
|
const sharingMap = new Map();
|
|
542
|
+
const localShareMap = new Map();
|
|
534
543
|
if (chunkIds.length > 0) {
|
|
535
544
|
try {
|
|
536
545
|
const placeholders = chunkIds.map(() => "?").join(",");
|
|
537
546
|
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
538
547
|
for (const r of sharedRows)
|
|
539
548
|
sharingMap.set(r.source_chunk_id, r);
|
|
549
|
+
const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
550
|
+
for (const r of localRows)
|
|
551
|
+
localShareMap.set(r.chunk_id, r);
|
|
540
552
|
}
|
|
541
553
|
catch {
|
|
542
554
|
}
|
|
@@ -548,8 +560,12 @@ class ViewerServer {
|
|
|
548
560
|
out.merge_sources = sources;
|
|
549
561
|
}
|
|
550
562
|
const shared = sharingMap.get(m.id);
|
|
563
|
+
const localShared = localShareMap.get(m.id);
|
|
551
564
|
out.sharingVisibility = shared?.visibility ?? null;
|
|
552
565
|
out.sharingGroupId = shared?.group_id ?? null;
|
|
566
|
+
out.localSharing = out.owner === "public";
|
|
567
|
+
out.localSharingManaged = !!localShared;
|
|
568
|
+
out.localOriginalOwner = localShared?.original_owner ?? null;
|
|
553
569
|
return out;
|
|
554
570
|
});
|
|
555
571
|
this.store.recordViewerEvent("list");
|
|
@@ -564,7 +580,21 @@ class ViewerServer {
|
|
|
564
580
|
this.jsonResponse(res, data);
|
|
565
581
|
}
|
|
566
582
|
serveToolMetrics(res, url) {
|
|
567
|
-
const
|
|
583
|
+
const fromParam = url.searchParams.get("from");
|
|
584
|
+
const toParam = url.searchParams.get("to");
|
|
585
|
+
if (fromParam) {
|
|
586
|
+
const fromMs = new Date(fromParam).getTime();
|
|
587
|
+
const toMs = toParam ? new Date(toParam).getTime() : Date.now();
|
|
588
|
+
if (isNaN(fromMs) || isNaN(toMs)) {
|
|
589
|
+
this.jsonResponse(res, { error: "Invalid date" }, 400);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const diffMin = Math.max(10, Math.min(43200, Math.round((toMs - fromMs) / 60000)));
|
|
593
|
+
const data = this.store.getToolMetrics(diffMin, fromMs, toMs);
|
|
594
|
+
this.jsonResponse(res, data);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const minutes = Math.min(43200, Math.max(10, Number(url.searchParams.get("minutes")) || 60));
|
|
568
598
|
const data = this.store.getToolMetrics(minutes);
|
|
569
599
|
this.jsonResponse(res, data);
|
|
570
600
|
}
|
|
@@ -576,7 +606,8 @@ class ViewerServer {
|
|
|
576
606
|
const { tasks, total } = this.store.listTasks({ status, limit, offset });
|
|
577
607
|
const db = this.store.db;
|
|
578
608
|
const items = tasks.map((t) => {
|
|
579
|
-
const meta = db.prepare("SELECT skill_status FROM tasks WHERE id = ?").get(t.id);
|
|
609
|
+
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id);
|
|
610
|
+
const sharedTask = db.prepare("SELECT visibility FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(t.id);
|
|
580
611
|
return {
|
|
581
612
|
id: t.id,
|
|
582
613
|
sessionKey: t.sessionKey,
|
|
@@ -587,6 +618,8 @@ class ViewerServer {
|
|
|
587
618
|
endedAt: t.endedAt,
|
|
588
619
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
589
620
|
skillStatus: meta?.skill_status ?? null,
|
|
621
|
+
owner: meta?.owner ?? "agent:main",
|
|
622
|
+
sharingVisibility: sharedTask?.visibility ?? null,
|
|
590
623
|
};
|
|
591
624
|
});
|
|
592
625
|
this.jsonResponse(res, { tasks: items, total, limit, offset });
|
|
@@ -622,6 +655,7 @@ class ViewerServer {
|
|
|
622
655
|
title: task.title,
|
|
623
656
|
summary: task.summary,
|
|
624
657
|
status: task.status,
|
|
658
|
+
owner: task.owner ?? "agent:main",
|
|
625
659
|
startedAt: task.startedAt,
|
|
626
660
|
endedAt: task.endedAt,
|
|
627
661
|
chunks: chunkItems,
|
|
@@ -630,6 +664,7 @@ class ViewerServer {
|
|
|
630
664
|
skillLinks,
|
|
631
665
|
sharingVisibility: sharedTask?.visibility ?? null,
|
|
632
666
|
sharingGroupId: sharedTask?.group_id ?? null,
|
|
667
|
+
hubTaskId: sharedTask ? true : false,
|
|
633
668
|
});
|
|
634
669
|
}
|
|
635
670
|
serveStats(res, url) {
|
|
@@ -817,7 +852,12 @@ class ViewerServer {
|
|
|
817
852
|
if (visibility) {
|
|
818
853
|
skills = skills.filter(s => s.visibility === visibility);
|
|
819
854
|
}
|
|
820
|
-
|
|
855
|
+
const db = this.store.db;
|
|
856
|
+
const enriched = skills.map(s => {
|
|
857
|
+
const hub = db.prepare("SELECT visibility FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(s.id);
|
|
858
|
+
return { ...s, sharingVisibility: hub?.visibility ?? null };
|
|
859
|
+
});
|
|
860
|
+
this.jsonResponse(res, { skills: enriched });
|
|
821
861
|
}
|
|
822
862
|
serveSkillDetail(res, urlPath) {
|
|
823
863
|
const skillId = urlPath.replace("/api/skill/", "");
|
|
@@ -1135,7 +1175,15 @@ class ViewerServer {
|
|
|
1135
1175
|
const cleaned = chunk.role === "user" && chunk.content
|
|
1136
1176
|
? { ...chunk, content: (0, capture_1.stripInboundMetadata)(chunk.content) }
|
|
1137
1177
|
: chunk;
|
|
1138
|
-
this.
|
|
1178
|
+
const localShared = this.store.getLocalSharedMemory(chunkId);
|
|
1179
|
+
this.jsonResponse(res, {
|
|
1180
|
+
memory: {
|
|
1181
|
+
...cleaned,
|
|
1182
|
+
localSharing: cleaned.owner === "public",
|
|
1183
|
+
localSharingManaged: !!localShared,
|
|
1184
|
+
localOriginalOwner: localShared?.originalOwner ?? null,
|
|
1185
|
+
},
|
|
1186
|
+
});
|
|
1139
1187
|
}
|
|
1140
1188
|
handleUpdate(req, res, urlPath) {
|
|
1141
1189
|
const chunkId = urlPath.replace("/api/memory/", "");
|
|
@@ -1170,6 +1218,348 @@ class ViewerServer {
|
|
|
1170
1218
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
1171
1219
|
}
|
|
1172
1220
|
}
|
|
1221
|
+
handleMemoryLocalShare(req, res) {
|
|
1222
|
+
this.readBody(req, (body) => {
|
|
1223
|
+
try {
|
|
1224
|
+
const parsed = JSON.parse(body || "{}");
|
|
1225
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1226
|
+
if (!chunkId)
|
|
1227
|
+
return this.jsonResponse(res, { ok: false, error: "missing_chunk_id" }, 400);
|
|
1228
|
+
const result = this.store.markMemorySharedLocally(chunkId);
|
|
1229
|
+
if (!result.ok) {
|
|
1230
|
+
return this.jsonResponse(res, { ok: false, error: result.reason ?? "share_failed" }, result.reason === "not_found" ? 404 : 400);
|
|
1231
|
+
}
|
|
1232
|
+
this.jsonResponse(res, {
|
|
1233
|
+
ok: true,
|
|
1234
|
+
chunkId,
|
|
1235
|
+
owner: result.owner,
|
|
1236
|
+
localSharing: true,
|
|
1237
|
+
localSharingManaged: true,
|
|
1238
|
+
localOriginalOwner: result.originalOwner ?? null,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
catch (err) {
|
|
1242
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 400);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
handleMemoryLocalUnshare(req, res) {
|
|
1247
|
+
this.readBody(req, (body) => {
|
|
1248
|
+
try {
|
|
1249
|
+
const parsed = JSON.parse(body || "{}");
|
|
1250
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1251
|
+
const privateOwner = typeof parsed.privateOwner === "string" ? parsed.privateOwner : undefined;
|
|
1252
|
+
if (!chunkId)
|
|
1253
|
+
return this.jsonResponse(res, { ok: false, error: "missing_chunk_id" }, 400);
|
|
1254
|
+
const result = this.store.unmarkMemorySharedLocally(chunkId, privateOwner);
|
|
1255
|
+
if (!result.ok) {
|
|
1256
|
+
return this.jsonResponse(res, { ok: false, error: result.reason ?? "unshare_failed" }, result.reason === "not_found" ? 404 : 400);
|
|
1257
|
+
}
|
|
1258
|
+
this.jsonResponse(res, {
|
|
1259
|
+
ok: true,
|
|
1260
|
+
chunkId,
|
|
1261
|
+
owner: result.owner,
|
|
1262
|
+
localSharing: false,
|
|
1263
|
+
localOriginalOwner: result.originalOwner ?? null,
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
catch (err) {
|
|
1267
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 400);
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
// ─── Unified scope API ───
|
|
1272
|
+
handleMemoryScope(req, res, urlPath) {
|
|
1273
|
+
const chunkId = urlPath.split("/")[3];
|
|
1274
|
+
this.readBody(req, async (body) => {
|
|
1275
|
+
try {
|
|
1276
|
+
const parsed = JSON.parse(body || "{}");
|
|
1277
|
+
const scope = parsed.scope;
|
|
1278
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1279
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1280
|
+
}
|
|
1281
|
+
const db = this.store.db;
|
|
1282
|
+
const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1283
|
+
if (!chunk)
|
|
1284
|
+
return this.jsonResponse(res, { ok: false, error: "not_found" }, 404);
|
|
1285
|
+
if (chunk.dedup_status && chunk.dedup_status !== "active") {
|
|
1286
|
+
return this.jsonResponse(res, { ok: false, error: "inactive_memory", message: "Merged/duplicate memories cannot be shared" }, 400);
|
|
1287
|
+
}
|
|
1288
|
+
const isLocalShared = chunk.owner === "public";
|
|
1289
|
+
const hubMemory = this.getHubMemoryForChunk(chunkId);
|
|
1290
|
+
const isTeamShared = !!hubMemory;
|
|
1291
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1292
|
+
if (scope === currentScope) {
|
|
1293
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1294
|
+
}
|
|
1295
|
+
let hubSynced = false;
|
|
1296
|
+
if (scope === "team") {
|
|
1297
|
+
if (!isLocalShared)
|
|
1298
|
+
this.store.markMemorySharedLocally(chunkId);
|
|
1299
|
+
if (!isTeamShared) {
|
|
1300
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1301
|
+
const refreshedChunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1302
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
1303
|
+
method: "POST",
|
|
1304
|
+
body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
|
|
1305
|
+
});
|
|
1306
|
+
if (hubClient.userId) {
|
|
1307
|
+
const existing = this.store.getHubMemoryBySource(hubClient.userId, chunkId);
|
|
1308
|
+
this.store.upsertHubMemory({
|
|
1309
|
+
id: response?.memoryId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1310
|
+
sourceChunkId: chunkId, sourceUserId: hubClient.userId,
|
|
1311
|
+
role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary ?? "",
|
|
1312
|
+
kind: refreshedChunk.kind, groupId: null, visibility: "public",
|
|
1313
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
hubSynced = true;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
else if (scope === "local") {
|
|
1320
|
+
if (isTeamShared) {
|
|
1321
|
+
try {
|
|
1322
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1323
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1324
|
+
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1325
|
+
});
|
|
1326
|
+
if (hubClient.userId)
|
|
1327
|
+
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1328
|
+
hubSynced = true;
|
|
1329
|
+
}
|
|
1330
|
+
catch (err) {
|
|
1331
|
+
this.log.warn(`Failed to unshare memory from team: ${err}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!isLocalShared)
|
|
1335
|
+
this.store.markMemorySharedLocally(chunkId);
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
if (isTeamShared) {
|
|
1339
|
+
try {
|
|
1340
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1341
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1342
|
+
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1343
|
+
});
|
|
1344
|
+
if (hubClient.userId)
|
|
1345
|
+
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1346
|
+
hubSynced = true;
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
this.log.warn(`Failed to unshare memory from team: ${err}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (isLocalShared)
|
|
1353
|
+
this.store.unmarkMemorySharedLocally(chunkId);
|
|
1354
|
+
}
|
|
1355
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1356
|
+
}
|
|
1357
|
+
catch (err) {
|
|
1358
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
handleTaskScope(req, res, urlPath) {
|
|
1363
|
+
const taskId = urlPath.split("/")[3];
|
|
1364
|
+
this.readBody(req, async (body) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const parsed = JSON.parse(body || "{}");
|
|
1367
|
+
const scope = parsed.scope;
|
|
1368
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1369
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1370
|
+
}
|
|
1371
|
+
const task = this.store.getTask(taskId);
|
|
1372
|
+
if (!task)
|
|
1373
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" }, 404);
|
|
1374
|
+
if (scope !== "private" && task.status !== "completed") {
|
|
1375
|
+
return this.jsonResponse(res, { ok: false, error: "only_completed_tasks_can_be_shared" }, 400);
|
|
1376
|
+
}
|
|
1377
|
+
const isLocalShared = task.owner === "public";
|
|
1378
|
+
const hubTask = this.getHubTaskForLocal(taskId);
|
|
1379
|
+
const isTeamShared = !!hubTask;
|
|
1380
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1381
|
+
if (scope === currentScope) {
|
|
1382
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1383
|
+
}
|
|
1384
|
+
let hubSynced = false;
|
|
1385
|
+
if (scope === "local" || scope === "team") {
|
|
1386
|
+
if (!isLocalShared) {
|
|
1387
|
+
const originalOwner = task.owner;
|
|
1388
|
+
const db = this.store.db;
|
|
1389
|
+
db.prepare("INSERT INTO local_shared_tasks (task_id, hub_task_id, original_owner, shared_at) VALUES (?, ?, ?, ?) ON CONFLICT(task_id) DO UPDATE SET original_owner = excluded.original_owner, shared_at = excluded.shared_at").run(taskId, "", originalOwner, Date.now());
|
|
1390
|
+
db.prepare("UPDATE tasks SET owner = 'public' WHERE id = ?").run(taskId);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (scope === "team") {
|
|
1394
|
+
if (!isTeamShared) {
|
|
1395
|
+
const chunks = this.store.getChunksByTask(taskId);
|
|
1396
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1397
|
+
const refreshedTask = this.store.getTask(taskId);
|
|
1398
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1399
|
+
method: "POST",
|
|
1400
|
+
body: JSON.stringify({
|
|
1401
|
+
task: { id: refreshedTask.id, sourceTaskId: refreshedTask.id, title: refreshedTask.title, summary: refreshedTask.summary, groupId: null, visibility: "public", createdAt: refreshedTask.startedAt ?? Date.now(), updatedAt: refreshedTask.updatedAt ?? Date.now() },
|
|
1402
|
+
chunks: chunks.map((c) => ({ id: c.id, hubTaskId: refreshedTask.id, sourceTaskId: refreshedTask.id, sourceChunkId: c.id, role: c.role, content: c.content, summary: c.summary, kind: c.kind, groupId: null, visibility: "public", createdAt: c.createdAt ?? Date.now() })),
|
|
1403
|
+
}),
|
|
1404
|
+
});
|
|
1405
|
+
if (hubClient.userId) {
|
|
1406
|
+
const existing = this.store.getHubTaskBySource(hubClient.userId, taskId);
|
|
1407
|
+
this.store.upsertHubTask({
|
|
1408
|
+
id: response?.taskId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1409
|
+
sourceTaskId: taskId, sourceUserId: hubClient.userId, title: refreshedTask.title ?? "",
|
|
1410
|
+
summary: refreshedTask.summary ?? "", groupId: null, visibility: "public",
|
|
1411
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
hubSynced = true;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (scope === "local" && isTeamShared) {
|
|
1418
|
+
try {
|
|
1419
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1420
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1421
|
+
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1422
|
+
});
|
|
1423
|
+
if (hubClient.userId)
|
|
1424
|
+
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1425
|
+
hubSynced = true;
|
|
1426
|
+
}
|
|
1427
|
+
catch (err) {
|
|
1428
|
+
this.log.warn(`Failed to unshare task from team: ${err}`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
if (scope === "private") {
|
|
1432
|
+
if (isTeamShared) {
|
|
1433
|
+
try {
|
|
1434
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1435
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1436
|
+
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1437
|
+
});
|
|
1438
|
+
if (hubClient.userId)
|
|
1439
|
+
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1440
|
+
hubSynced = true;
|
|
1441
|
+
}
|
|
1442
|
+
catch (err) {
|
|
1443
|
+
this.log.warn(`Failed to unshare task from team: ${err}`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (isLocalShared) {
|
|
1447
|
+
const db = this.store.db;
|
|
1448
|
+
const shared = db.prepare("SELECT original_owner FROM local_shared_tasks WHERE task_id = ?").get(taskId);
|
|
1449
|
+
const restoreOwner = shared?.original_owner ?? task.owner;
|
|
1450
|
+
if (restoreOwner && restoreOwner !== "public") {
|
|
1451
|
+
db.prepare("UPDATE tasks SET owner = ? WHERE id = ?").run(restoreOwner, taskId);
|
|
1452
|
+
}
|
|
1453
|
+
db.prepare("DELETE FROM local_shared_tasks WHERE task_id = ?").run(taskId);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1457
|
+
}
|
|
1458
|
+
catch (err) {
|
|
1459
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
handleSkillScope(req, res, urlPath) {
|
|
1464
|
+
const skillId = urlPath.split("/")[3];
|
|
1465
|
+
this.readBody(req, async (body) => {
|
|
1466
|
+
try {
|
|
1467
|
+
const parsed = JSON.parse(body || "{}");
|
|
1468
|
+
const scope = parsed.scope;
|
|
1469
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1470
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1471
|
+
}
|
|
1472
|
+
const skill = this.store.getSkill(skillId);
|
|
1473
|
+
if (!skill)
|
|
1474
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" }, 404);
|
|
1475
|
+
if (scope !== "private" && skill.status !== "active") {
|
|
1476
|
+
return this.jsonResponse(res, { ok: false, error: "only_active_skills_can_be_shared" }, 400);
|
|
1477
|
+
}
|
|
1478
|
+
const isLocalShared = skill.visibility === "public";
|
|
1479
|
+
const hubSkill = this.getHubSkillForLocal(skillId);
|
|
1480
|
+
const isTeamShared = !!hubSkill;
|
|
1481
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1482
|
+
if (scope === currentScope) {
|
|
1483
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1484
|
+
}
|
|
1485
|
+
let hubSynced = false;
|
|
1486
|
+
if (scope === "local" || scope === "team") {
|
|
1487
|
+
if (!isLocalShared)
|
|
1488
|
+
this.store.setSkillVisibility(skillId, "public");
|
|
1489
|
+
}
|
|
1490
|
+
if (scope === "team") {
|
|
1491
|
+
if (!isTeamShared) {
|
|
1492
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
1493
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1494
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
1495
|
+
method: "POST",
|
|
1496
|
+
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1497
|
+
});
|
|
1498
|
+
if (hubClient.userId) {
|
|
1499
|
+
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1500
|
+
this.store.upsertHubSkill({
|
|
1501
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1502
|
+
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
1503
|
+
name: skill.name, description: skill.description, version: skill.version,
|
|
1504
|
+
groupId: null, visibility: "public",
|
|
1505
|
+
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
1506
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
hubSynced = true;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (scope === "local" && isTeamShared) {
|
|
1513
|
+
try {
|
|
1514
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1515
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1516
|
+
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1517
|
+
});
|
|
1518
|
+
if (hubClient.userId)
|
|
1519
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1520
|
+
hubSynced = true;
|
|
1521
|
+
}
|
|
1522
|
+
catch (err) {
|
|
1523
|
+
this.log.warn(`Failed to unpublish skill from team: ${err}`);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (scope === "private") {
|
|
1527
|
+
if (isTeamShared) {
|
|
1528
|
+
try {
|
|
1529
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1530
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1531
|
+
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1532
|
+
});
|
|
1533
|
+
if (hubClient.userId)
|
|
1534
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1535
|
+
hubSynced = true;
|
|
1536
|
+
}
|
|
1537
|
+
catch (err) {
|
|
1538
|
+
this.log.warn(`Failed to unpublish skill from team: ${err}`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (isLocalShared)
|
|
1542
|
+
this.store.setSkillVisibility(skillId, "private");
|
|
1543
|
+
}
|
|
1544
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1545
|
+
}
|
|
1546
|
+
catch (err) {
|
|
1547
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
getHubMemoryForChunk(chunkId) {
|
|
1552
|
+
const db = this.store.db;
|
|
1553
|
+
return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
|
|
1554
|
+
}
|
|
1555
|
+
getHubTaskForLocal(taskId) {
|
|
1556
|
+
const db = this.store.db;
|
|
1557
|
+
return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
|
|
1558
|
+
}
|
|
1559
|
+
getHubSkillForLocal(skillId) {
|
|
1560
|
+
const db = this.store.db;
|
|
1561
|
+
return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
|
|
1562
|
+
}
|
|
1173
1563
|
handleDeleteSession(res, url) {
|
|
1174
1564
|
const key = url.searchParams.get("key");
|
|
1175
1565
|
if (!key) {
|
|
@@ -1263,8 +1653,7 @@ class ViewerServer {
|
|
|
1263
1653
|
base.admin.rejectSupported = true;
|
|
1264
1654
|
base.connection.connected = true;
|
|
1265
1655
|
base.connection.hubUrl = resolvedHubUrl ?? undefined;
|
|
1266
|
-
|
|
1267
|
-
let adminUser = { username: "hub-admin", role: "admin", groups: [] };
|
|
1656
|
+
let adminUser = { username: "hub-admin", role: "admin" };
|
|
1268
1657
|
try {
|
|
1269
1658
|
const hub = this.resolveHubConnection();
|
|
1270
1659
|
if (hub) {
|
|
@@ -1274,7 +1663,6 @@ class ViewerServer {
|
|
|
1274
1663
|
id: me.id,
|
|
1275
1664
|
username: me.username ?? "hub-admin",
|
|
1276
1665
|
role: me.role ?? "admin",
|
|
1277
|
-
groups: Array.isArray(me.groups) ? me.groups : [],
|
|
1278
1666
|
};
|
|
1279
1667
|
}
|
|
1280
1668
|
}
|
|
@@ -1383,6 +1771,66 @@ class ViewerServer {
|
|
|
1383
1771
|
}
|
|
1384
1772
|
});
|
|
1385
1773
|
}
|
|
1774
|
+
handleSharingChangeRole(req, res) {
|
|
1775
|
+
this.readBody(req, async (body) => {
|
|
1776
|
+
if (!this.ctx)
|
|
1777
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1778
|
+
try {
|
|
1779
|
+
const parsed = JSON.parse(body || "{}");
|
|
1780
|
+
const hub = this.resolveHubConnection();
|
|
1781
|
+
if (!hub)
|
|
1782
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1783
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/change-role", {
|
|
1784
|
+
method: "POST",
|
|
1785
|
+
body: JSON.stringify({ userId: parsed.userId, role: parsed.role }),
|
|
1786
|
+
});
|
|
1787
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1788
|
+
}
|
|
1789
|
+
catch (err) {
|
|
1790
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
handleSharingRemoveUser(req, res) {
|
|
1795
|
+
this.readBody(req, async (body) => {
|
|
1796
|
+
if (!this.ctx)
|
|
1797
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1798
|
+
try {
|
|
1799
|
+
const parsed = JSON.parse(body || "{}");
|
|
1800
|
+
const hub = this.resolveHubConnection();
|
|
1801
|
+
if (!hub)
|
|
1802
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1803
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/remove-user", {
|
|
1804
|
+
method: "POST",
|
|
1805
|
+
body: JSON.stringify({ userId: parsed.userId, cleanResources: parsed.cleanResources === true }),
|
|
1806
|
+
});
|
|
1807
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1808
|
+
}
|
|
1809
|
+
catch (err) {
|
|
1810
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
handleAdminRenameUser(req, res) {
|
|
1815
|
+
this.readBody(req, async (body) => {
|
|
1816
|
+
if (!this.ctx)
|
|
1817
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1818
|
+
try {
|
|
1819
|
+
const parsed = JSON.parse(body || "{}");
|
|
1820
|
+
const hub = this.resolveHubConnection();
|
|
1821
|
+
if (!hub)
|
|
1822
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1823
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/rename-user", {
|
|
1824
|
+
method: "POST",
|
|
1825
|
+
body: JSON.stringify({ userId: parsed.userId, username: parsed.username }),
|
|
1826
|
+
});
|
|
1827
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1828
|
+
}
|
|
1829
|
+
catch (err) {
|
|
1830
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1386
1834
|
handleRetryJoin(req, res) {
|
|
1387
1835
|
this.readBody(req, async (_body) => {
|
|
1388
1836
|
if (!this.ctx)
|
|
@@ -1860,123 +2308,6 @@ class ViewerServer {
|
|
|
1860
2308
|
}
|
|
1861
2309
|
return (0, hub_1.resolveHubClient)(this.store, this.ctx);
|
|
1862
2310
|
}
|
|
1863
|
-
extractGroupId(path) {
|
|
1864
|
-
const m = path.match(/\/api\/sharing\/groups\/([^/]+)/);
|
|
1865
|
-
return m ? decodeURIComponent(m[1]) : "";
|
|
1866
|
-
}
|
|
1867
|
-
async serveSharingGroups(res) {
|
|
1868
|
-
const hub = this.resolveHubConnection();
|
|
1869
|
-
if (!hub)
|
|
1870
|
-
return this.jsonResponse(res, { groups: [], error: "not_configured" });
|
|
1871
|
-
try {
|
|
1872
|
-
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/groups", { method: "GET" });
|
|
1873
|
-
this.jsonResponse(res, { groups: Array.isArray(data?.groups) ? data.groups : [] });
|
|
1874
|
-
}
|
|
1875
|
-
catch (err) {
|
|
1876
|
-
this.jsonResponse(res, { groups: [], error: String(err) });
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
handleSharingGroupCreate(req, res) {
|
|
1880
|
-
this.readBody(req, async (body) => {
|
|
1881
|
-
const hub = this.resolveHubConnection();
|
|
1882
|
-
if (!hub)
|
|
1883
|
-
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1884
|
-
try {
|
|
1885
|
-
const parsed = JSON.parse(body || "{}");
|
|
1886
|
-
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/groups", {
|
|
1887
|
-
method: "POST",
|
|
1888
|
-
body: JSON.stringify({ name: parsed.name, description: parsed.description }),
|
|
1889
|
-
});
|
|
1890
|
-
this.jsonResponse(res, { ok: true, ...data });
|
|
1891
|
-
}
|
|
1892
|
-
catch (err) {
|
|
1893
|
-
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
|
-
}
|
|
1897
|
-
handleSharingGroupUpdate(req, res, p) {
|
|
1898
|
-
this.readBody(req, async (body) => {
|
|
1899
|
-
const hub = this.resolveHubConnection();
|
|
1900
|
-
if (!hub)
|
|
1901
|
-
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1902
|
-
const groupId = this.extractGroupId(p);
|
|
1903
|
-
try {
|
|
1904
|
-
const parsed = JSON.parse(body || "{}");
|
|
1905
|
-
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, {
|
|
1906
|
-
method: "PUT",
|
|
1907
|
-
body: JSON.stringify({ name: parsed.name, description: parsed.description }),
|
|
1908
|
-
});
|
|
1909
|
-
this.jsonResponse(res, { ok: true });
|
|
1910
|
-
}
|
|
1911
|
-
catch (err) {
|
|
1912
|
-
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
}
|
|
1916
|
-
async handleSharingGroupDelete(res, p) {
|
|
1917
|
-
const hub = this.resolveHubConnection();
|
|
1918
|
-
if (!hub)
|
|
1919
|
-
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1920
|
-
const groupId = this.extractGroupId(p);
|
|
1921
|
-
try {
|
|
1922
|
-
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, { method: "DELETE" });
|
|
1923
|
-
this.jsonResponse(res, { ok: true });
|
|
1924
|
-
}
|
|
1925
|
-
catch (err) {
|
|
1926
|
-
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
async serveSharingGroupMembers(res, p) {
|
|
1930
|
-
const hub = this.resolveHubConnection();
|
|
1931
|
-
if (!hub)
|
|
1932
|
-
return this.jsonResponse(res, { members: [], error: "not_configured" });
|
|
1933
|
-
const groupId = this.extractGroupId(p);
|
|
1934
|
-
try {
|
|
1935
|
-
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, { method: "GET" });
|
|
1936
|
-
this.jsonResponse(res, { members: Array.isArray(data?.members) ? data.members : [] });
|
|
1937
|
-
}
|
|
1938
|
-
catch (err) {
|
|
1939
|
-
this.jsonResponse(res, { members: [], error: String(err) });
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
handleSharingGroupAddMember(req, res, p) {
|
|
1943
|
-
this.readBody(req, async (body) => {
|
|
1944
|
-
const hub = this.resolveHubConnection();
|
|
1945
|
-
if (!hub)
|
|
1946
|
-
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1947
|
-
const groupId = this.extractGroupId(p);
|
|
1948
|
-
try {
|
|
1949
|
-
const parsed = JSON.parse(body || "{}");
|
|
1950
|
-
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}/members`, {
|
|
1951
|
-
method: "POST",
|
|
1952
|
-
body: JSON.stringify({ userId: parsed.userId }),
|
|
1953
|
-
});
|
|
1954
|
-
this.jsonResponse(res, { ok: true });
|
|
1955
|
-
}
|
|
1956
|
-
catch (err) {
|
|
1957
|
-
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1958
|
-
}
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
handleSharingGroupRemoveMember(req, res, p) {
|
|
1962
|
-
this.readBody(req, async (body) => {
|
|
1963
|
-
const hub = this.resolveHubConnection();
|
|
1964
|
-
if (!hub)
|
|
1965
|
-
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1966
|
-
const groupId = this.extractGroupId(p);
|
|
1967
|
-
try {
|
|
1968
|
-
const parsed = JSON.parse(body || "{}");
|
|
1969
|
-
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}/members`, {
|
|
1970
|
-
method: "DELETE",
|
|
1971
|
-
body: JSON.stringify({ userId: parsed.userId }),
|
|
1972
|
-
});
|
|
1973
|
-
this.jsonResponse(res, { ok: true });
|
|
1974
|
-
}
|
|
1975
|
-
catch (err) {
|
|
1976
|
-
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1977
|
-
}
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
2311
|
async serveSharingUsers(res) {
|
|
1981
2312
|
const hub = this.resolveHubConnection();
|
|
1982
2313
|
if (!hub)
|
|
@@ -1996,7 +2327,17 @@ class ViewerServer {
|
|
|
1996
2327
|
return this.jsonResponse(res, { tasks: [], error: "not_configured" });
|
|
1997
2328
|
try {
|
|
1998
2329
|
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-tasks", { method: "GET" });
|
|
1999
|
-
|
|
2330
|
+
const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
|
|
2331
|
+
for (const tk of tasks) {
|
|
2332
|
+
if (!tk.summary && tk.sourceTaskId) {
|
|
2333
|
+
const local = this.store.getTask(tk.sourceTaskId);
|
|
2334
|
+
if (local) {
|
|
2335
|
+
tk.summary = local.summary;
|
|
2336
|
+
tk.title = tk.title || local.title;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
this.jsonResponse(res, { tasks });
|
|
2000
2341
|
}
|
|
2001
2342
|
catch (err) {
|
|
2002
2343
|
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
@@ -2021,7 +2362,17 @@ class ViewerServer {
|
|
|
2021
2362
|
return this.jsonResponse(res, { skills: [], error: "not_configured" });
|
|
2022
2363
|
try {
|
|
2023
2364
|
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-skills", { method: "GET" });
|
|
2024
|
-
|
|
2365
|
+
const skills = Array.isArray(data?.skills) ? data.skills : [];
|
|
2366
|
+
for (const sk of skills) {
|
|
2367
|
+
if (!sk.description && sk.sourceSkillId) {
|
|
2368
|
+
const local = this.store.getSkill(sk.sourceSkillId);
|
|
2369
|
+
if (local) {
|
|
2370
|
+
sk.description = sk.description || local.description;
|
|
2371
|
+
sk.name = sk.name || local.name;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
this.jsonResponse(res, { skills });
|
|
2025
2376
|
}
|
|
2026
2377
|
catch (err) {
|
|
2027
2378
|
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
@@ -2046,7 +2397,18 @@ class ViewerServer {
|
|
|
2046
2397
|
return this.jsonResponse(res, { memories: [], error: "not_configured" });
|
|
2047
2398
|
try {
|
|
2048
2399
|
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-memories", { method: "GET" });
|
|
2049
|
-
|
|
2400
|
+
const memories = Array.isArray(data?.memories) ? data.memories : [];
|
|
2401
|
+
for (const m of memories) {
|
|
2402
|
+
if (!m.content && m.sourceChunkId) {
|
|
2403
|
+
const local = this.store.getChunk(m.sourceChunkId);
|
|
2404
|
+
if (local) {
|
|
2405
|
+
m.content = local.content;
|
|
2406
|
+
if (!m.summary && local.summary)
|
|
2407
|
+
m.summary = local.summary;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
this.jsonResponse(res, { memories });
|
|
2050
2412
|
}
|
|
2051
2413
|
catch (err) {
|
|
2052
2414
|
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
@@ -2065,6 +2427,48 @@ class ViewerServer {
|
|
|
2065
2427
|
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2066
2428
|
}
|
|
2067
2429
|
}
|
|
2430
|
+
async serveSharingNotifications(res, url) {
|
|
2431
|
+
const hub = this.resolveHubConnection();
|
|
2432
|
+
if (!hub)
|
|
2433
|
+
return this.jsonResponse(res, { notifications: [], unreadCount: 0 });
|
|
2434
|
+
try {
|
|
2435
|
+
const unread = url.searchParams.get("unread") === "1" ? "?unread=1" : "";
|
|
2436
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/notifications${unread}`);
|
|
2437
|
+
this.jsonResponse(res, data);
|
|
2438
|
+
}
|
|
2439
|
+
catch {
|
|
2440
|
+
this.jsonResponse(res, { notifications: [], unreadCount: 0 });
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
handleSharingNotificationsRead(req, res) {
|
|
2444
|
+
const hub = this.resolveHubConnection();
|
|
2445
|
+
if (!hub)
|
|
2446
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2447
|
+
this.readBody(req, async (raw) => {
|
|
2448
|
+
try {
|
|
2449
|
+
const body = JSON.parse(raw || "{}");
|
|
2450
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/notifications/read", { method: "POST", body: JSON.stringify(body) });
|
|
2451
|
+
this.jsonResponse(res, { ok: true });
|
|
2452
|
+
}
|
|
2453
|
+
catch (err) {
|
|
2454
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
handleSharingNotificationsClear(req, res) {
|
|
2459
|
+
const hub = this.resolveHubConnection();
|
|
2460
|
+
if (!hub)
|
|
2461
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2462
|
+
this.readBody(req, async () => {
|
|
2463
|
+
try {
|
|
2464
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/notifications/clear", { method: "POST", body: "{}" });
|
|
2465
|
+
this.jsonResponse(res, { ok: true });
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2068
2472
|
getLocalIPs() {
|
|
2069
2473
|
const nets = node_os_1.default.networkInterfaces();
|
|
2070
2474
|
const ips = [];
|