@memtensor/memos-local-openclaw-plugin 1.0.3 → 1.0.4-beta.0
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 +38 -21
- package/dist/client/connector.d.ts +26 -0
- package/dist/client/connector.d.ts.map +1 -0
- package/dist/client/connector.js +127 -0
- package/dist/client/connector.js.map +1 -0
- package/dist/client/hub.d.ts +61 -0
- package/dist/client/hub.d.ts.map +1 -0
- package/dist/client/hub.js +148 -0
- package/dist/client/hub.js.map +1 -0
- package/dist/client/skill-sync.d.ts +29 -0
- package/dist/client/skill-sync.d.ts.map +1 -0
- package/dist/client/skill-sync.js +216 -0
- package/dist/client/skill-sync.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -3
- package/dist/config.js.map +1 -1
- package/dist/embedding/index.d.ts +4 -2
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +17 -1
- package/dist/embedding/index.js.map +1 -1
- package/dist/hub/auth.d.ts +19 -0
- package/dist/hub/auth.d.ts.map +1 -0
- package/dist/hub/auth.js +70 -0
- package/dist/hub/auth.js.map +1 -0
- package/dist/hub/server.d.ts +41 -0
- package/dist/hub/server.d.ts.map +1 -0
- package/dist/hub/server.js +742 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/user-manager.d.ts +28 -0
- package/dist/hub/user-manager.d.ts.map +1 -0
- package/dist/hub/user-manager.js +112 -0
- package/dist/hub/user-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +10 -2
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +203 -6
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +1 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +1 -0
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.js +1 -1
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/openclaw-api.d.ts +53 -0
- package/dist/openclaw-api.d.ts.map +1 -0
- package/dist/openclaw-api.js +189 -0
- package/dist/openclaw-api.js.map +1 -0
- package/dist/recall/engine.js +1 -1
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +4 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +15 -0
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.contract.d.ts +2 -0
- package/dist/sharing/types.contract.d.ts.map +1 -0
- package/dist/sharing/types.contract.js +3 -0
- package/dist/sharing/types.contract.js.map +1 -0
- package/dist/sharing/types.d.ts +80 -0
- package/dist/sharing/types.d.ts.map +1 -0
- package/dist/sharing/types.js +3 -0
- package/dist/sharing/types.js.map +1 -0
- package/dist/skill/evaluator.d.ts.map +1 -1
- package/dist/skill/evaluator.js +2 -2
- package/dist/skill/evaluator.js.map +1 -1
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +4 -4
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/upgrader.js +1 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.js +1 -1
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +294 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +902 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -2
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +48 -7
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/tools/network-memory-detail.d.ts +4 -0
- package/dist/tools/network-memory-detail.d.ts.map +1 -0
- package/dist/tools/network-memory-detail.js +34 -0
- package/dist/tools/network-memory-detail.js.map +1 -0
- package/dist/types.d.ts +47 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +2323 -252
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +43 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1064 -15
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +428 -16
- package/openclaw.plugin.json +2 -1
- package/package.json +3 -3
- package/scripts/postinstall.cjs +282 -45
- package/skill/memos-memory-guide/SKILL.md +26 -2
- package/src/client/connector.ts +124 -0
- package/src/client/hub.ts +189 -0
- package/src/client/skill-sync.ts +202 -0
- package/src/config.ts +92 -3
- package/src/embedding/index.ts +21 -1
- package/src/hub/auth.ts +78 -0
- package/src/hub/server.ts +734 -0
- package/src/hub/user-manager.ts +126 -0
- package/src/index.ts +7 -4
- package/src/ingest/providers/index.ts +240 -6
- package/src/ingest/providers/openai.ts +1 -1
- package/src/ingest/task-processor.ts +1 -1
- package/src/openclaw-api.ts +287 -0
- package/src/recall/engine.ts +1 -1
- package/src/shared/llm-call.ts +19 -1
- package/src/sharing/types.contract.ts +40 -0
- package/src/sharing/types.ts +102 -0
- package/src/skill/evaluator.ts +3 -2
- package/src/skill/generator.ts +6 -4
- package/src/skill/upgrader.ts +1 -1
- package/src/skill/validator.ts +1 -1
- package/src/storage/sqlite.ts +1167 -7
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-search.ts +57 -8
- package/src/tools/network-memory-detail.ts +34 -0
- package/src/types.ts +42 -2
- package/src/viewer/html.ts +2323 -252
- package/src/viewer/server.ts +985 -18
- package/dist/ingest/extra-paths.d.ts +0 -13
- package/dist/ingest/extra-paths.d.ts.map +0 -1
- package/dist/ingest/extra-paths.js +0 -173
- package/dist/ingest/extra-paths.js.map +0 -1
package/dist/viewer/server.js
CHANGED
|
@@ -51,6 +51,10 @@ const vector_1 = require("../storage/vector");
|
|
|
51
51
|
const task_processor_1 = require("../ingest/task-processor");
|
|
52
52
|
const engine_1 = require("../recall/engine");
|
|
53
53
|
const evolver_1 = require("../skill/evolver");
|
|
54
|
+
const config_1 = require("../config");
|
|
55
|
+
const connector_1 = require("../client/connector");
|
|
56
|
+
const hub_1 = require("../client/hub");
|
|
57
|
+
const skill_sync_1 = require("../client/skill-sync");
|
|
54
58
|
const html_1 = require("./html");
|
|
55
59
|
const uuid_1 = require("uuid");
|
|
56
60
|
function normalizeTimestamp(ts) {
|
|
@@ -225,7 +229,7 @@ class ViewerServer {
|
|
|
225
229
|
if (p === "/api/memories" && req.method === "GET")
|
|
226
230
|
this.serveMemories(res, url);
|
|
227
231
|
else if (p === "/api/stats")
|
|
228
|
-
this.serveStats(res);
|
|
232
|
+
this.serveStats(res, url);
|
|
229
233
|
else if (p === "/api/metrics")
|
|
230
234
|
this.serveMetrics(res, url);
|
|
231
235
|
else if (p === "/api/tool-metrics")
|
|
@@ -270,6 +274,74 @@ class ViewerServer {
|
|
|
270
274
|
this.serveLogs(res, url);
|
|
271
275
|
else if (p === "/api/log-tools" && req.method === "GET")
|
|
272
276
|
this.serveLogTools(res);
|
|
277
|
+
else if (p === "/api/sharing/status" && req.method === "GET")
|
|
278
|
+
this.serveSharingStatus(res);
|
|
279
|
+
else if (p === "/api/sharing/pending-users" && req.method === "GET")
|
|
280
|
+
this.serveSharingPendingUsers(res);
|
|
281
|
+
else if (p === "/api/sharing/approve-user" && req.method === "POST")
|
|
282
|
+
this.handleSharingApproveUser(req, res);
|
|
283
|
+
else if (p === "/api/sharing/reject-user" && req.method === "POST")
|
|
284
|
+
this.handleSharingRejectUser(req, res);
|
|
285
|
+
else if (p === "/api/sharing/search/memories" && req.method === "POST")
|
|
286
|
+
this.handleSharingMemorySearch(req, res);
|
|
287
|
+
else if (p === "/api/sharing/memories/list" && req.method === "GET")
|
|
288
|
+
this.serveSharingMemoryList(res, url);
|
|
289
|
+
else if (p === "/api/sharing/tasks/list" && req.method === "GET")
|
|
290
|
+
this.serveSharingTaskList(res, url);
|
|
291
|
+
else if (p === "/api/sharing/skills/list" && req.method === "GET")
|
|
292
|
+
this.serveSharingSkillList(res, url);
|
|
293
|
+
else if (p === "/api/sharing/memory-detail" && req.method === "POST")
|
|
294
|
+
this.handleSharingMemoryDetail(req, res);
|
|
295
|
+
else if (p === "/api/sharing/search/skills" && req.method === "GET")
|
|
296
|
+
this.serveSharingSkillSearch(res, url);
|
|
297
|
+
else if (p === "/api/sharing/tasks/share" && req.method === "POST")
|
|
298
|
+
this.handleSharingTaskShare(req, res);
|
|
299
|
+
else if (p === "/api/sharing/tasks/unshare" && req.method === "POST")
|
|
300
|
+
this.handleSharingTaskUnshare(req, res);
|
|
301
|
+
else if (p === "/api/sharing/update-username" && req.method === "POST")
|
|
302
|
+
this.handleUpdateUsername(req, res);
|
|
303
|
+
else if (p === "/api/sharing/test-hub" && req.method === "POST")
|
|
304
|
+
this.handleTestHubConnection(req, res);
|
|
305
|
+
else if (p === "/api/sharing/memories/share" && req.method === "POST")
|
|
306
|
+
this.handleSharingMemoryShare(req, res);
|
|
307
|
+
else if (p === "/api/sharing/memories/unshare" && req.method === "POST")
|
|
308
|
+
this.handleSharingMemoryUnshare(req, res);
|
|
309
|
+
else if (p === "/api/sharing/skills/pull" && req.method === "POST")
|
|
310
|
+
this.handleSharingSkillPull(req, res);
|
|
311
|
+
else if (p === "/api/sharing/skills/share" && req.method === "POST")
|
|
312
|
+
this.handleSharingSkillShare(req, res);
|
|
313
|
+
else if (p === "/api/sharing/skills/unshare" && req.method === "POST")
|
|
314
|
+
this.handleSharingSkillUnshare(req, res);
|
|
315
|
+
else if (p === "/api/sharing/groups" && req.method === "GET")
|
|
316
|
+
this.serveSharingGroups(res);
|
|
317
|
+
else if (p === "/api/sharing/groups" && req.method === "POST")
|
|
318
|
+
this.handleSharingGroupCreate(req, res);
|
|
319
|
+
else if (p.match(/^\/api\/sharing\/groups\/[^/]+$/) && req.method === "PUT")
|
|
320
|
+
this.handleSharingGroupUpdate(req, res, p);
|
|
321
|
+
else if (p.match(/^\/api\/sharing\/groups\/[^/]+$/) && req.method === "DELETE")
|
|
322
|
+
this.handleSharingGroupDelete(res, p);
|
|
323
|
+
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "GET")
|
|
324
|
+
this.serveSharingGroupMembers(res, p);
|
|
325
|
+
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "POST")
|
|
326
|
+
this.handleSharingGroupAddMember(req, res, p);
|
|
327
|
+
else if (p.match(/^\/api\/sharing\/groups\/[^/]+\/members$/) && req.method === "DELETE")
|
|
328
|
+
this.handleSharingGroupRemoveMember(req, res, p);
|
|
329
|
+
else if (p === "/api/sharing/users" && req.method === "GET")
|
|
330
|
+
this.serveSharingUsers(res);
|
|
331
|
+
else if (p === "/api/admin/shared-tasks" && req.method === "GET")
|
|
332
|
+
this.serveAdminSharedTasks(res);
|
|
333
|
+
else if (p.match(/^\/api\/admin\/shared-tasks\/[^/]+$/) && req.method === "DELETE")
|
|
334
|
+
this.handleAdminDeleteTask(res, p);
|
|
335
|
+
else if (p === "/api/admin/shared-skills" && req.method === "GET")
|
|
336
|
+
this.serveAdminSharedSkills(res);
|
|
337
|
+
else if (p.match(/^\/api\/admin\/shared-skills\/[^/]+$/) && req.method === "DELETE")
|
|
338
|
+
this.handleAdminDeleteSkill(res, p);
|
|
339
|
+
else if (p === "/api/admin/shared-memories" && req.method === "GET")
|
|
340
|
+
this.serveAdminSharedMemories(res);
|
|
341
|
+
else if (p.match(/^\/api\/admin\/shared-memories\/[^/]+$/) && req.method === "DELETE")
|
|
342
|
+
this.handleAdminDeleteMemory(res, p);
|
|
343
|
+
else if (p === "/api/local-ips" && req.method === "GET")
|
|
344
|
+
this.serveLocalIPs(res);
|
|
273
345
|
else if (p === "/api/config" && req.method === "GET")
|
|
274
346
|
this.serveConfig(res);
|
|
275
347
|
else if (p === "/api/config" && req.method === "PUT")
|
|
@@ -455,15 +527,28 @@ class ViewerServer {
|
|
|
455
527
|
const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks" + where).get(...params);
|
|
456
528
|
const rawMemories = db.prepare("SELECT * FROM chunks" + where + ` ORDER BY created_at ${sortBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
457
529
|
const findMergeSources = db.prepare("SELECT id, summary, role FROM chunks WHERE dedup_target = ? AND (dedup_status = 'merged' OR dedup_status = 'duplicate')");
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
530
|
+
const chunkIds = rawMemories.map((m) => m.id);
|
|
531
|
+
const sharingMap = new Map();
|
|
532
|
+
if (chunkIds.length > 0) {
|
|
533
|
+
try {
|
|
534
|
+
const placeholders = chunkIds.map(() => "?").join(",");
|
|
535
|
+
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
536
|
+
for (const r of sharedRows)
|
|
537
|
+
sharingMap.set(r.source_chunk_id, r);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
461
540
|
}
|
|
462
|
-
|
|
541
|
+
}
|
|
542
|
+
const memories = rawMemories.map((m) => {
|
|
543
|
+
const out = m.role === "user" && m.content ? { ...m, content: (0, capture_1.stripInboundMetadata)(m.content) } : { ...m };
|
|
544
|
+
if (out.merge_count > 0) {
|
|
463
545
|
const sources = findMergeSources.all(m.id);
|
|
464
|
-
|
|
546
|
+
out.merge_sources = sources;
|
|
465
547
|
}
|
|
466
|
-
|
|
548
|
+
const shared = sharingMap.get(m.id);
|
|
549
|
+
out.sharingVisibility = shared?.visibility ?? null;
|
|
550
|
+
out.sharingGroupId = shared?.group_id ?? null;
|
|
551
|
+
return out;
|
|
467
552
|
});
|
|
468
553
|
this.store.recordViewerEvent("list");
|
|
469
554
|
this.jsonResponse(res, {
|
|
@@ -528,6 +613,7 @@ class ViewerServer {
|
|
|
528
613
|
}));
|
|
529
614
|
const db = this.store.db;
|
|
530
615
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId);
|
|
616
|
+
const sharedTask = db.prepare("SELECT visibility, group_id FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(taskId);
|
|
531
617
|
this.jsonResponse(res, {
|
|
532
618
|
id: task.id,
|
|
533
619
|
sessionKey: task.sessionKey,
|
|
@@ -540,9 +626,11 @@ class ViewerServer {
|
|
|
540
626
|
skillStatus: meta?.skill_status ?? null,
|
|
541
627
|
skillReason: meta?.skill_reason ?? null,
|
|
542
628
|
skillLinks,
|
|
629
|
+
sharingVisibility: sharedTask?.visibility ?? null,
|
|
630
|
+
sharingGroupId: sharedTask?.group_id ?? null,
|
|
543
631
|
});
|
|
544
632
|
}
|
|
545
|
-
serveStats(res) {
|
|
633
|
+
serveStats(res, url) {
|
|
546
634
|
const emptyStats = {
|
|
547
635
|
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
548
636
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
@@ -554,6 +642,7 @@ class ViewerServer {
|
|
|
554
642
|
this.jsonResponse(res, emptyStats);
|
|
555
643
|
return;
|
|
556
644
|
}
|
|
645
|
+
const ownerFilter = url?.searchParams.get("owner") ?? "";
|
|
557
646
|
try {
|
|
558
647
|
const db = this.store.db;
|
|
559
648
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get();
|
|
@@ -572,7 +661,12 @@ class ViewerServer {
|
|
|
572
661
|
embCount = db.prepare("SELECT COUNT(*) as count FROM embeddings").get().count;
|
|
573
662
|
}
|
|
574
663
|
catch { /* table may not exist */ }
|
|
575
|
-
const
|
|
664
|
+
const sessionQuery = ownerFilter
|
|
665
|
+
? "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks WHERE owner = ? GROUP BY session_key ORDER BY latest DESC"
|
|
666
|
+
: "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks GROUP BY session_key ORDER BY latest DESC";
|
|
667
|
+
const sessionList = (ownerFilter
|
|
668
|
+
? db.prepare(sessionQuery).all(ownerFilter)
|
|
669
|
+
: db.prepare(sessionQuery).all());
|
|
576
670
|
let skillCount = 0;
|
|
577
671
|
try {
|
|
578
672
|
skillCount = db.prepare("SELECT COUNT(*) as count FROM skills").get().count;
|
|
@@ -734,8 +828,10 @@ class ViewerServer {
|
|
|
734
828
|
const versions = this.store.getSkillVersions(skillId);
|
|
735
829
|
const relatedTasks = this.store.getTasksBySkill(skillId);
|
|
736
830
|
const files = node_fs_1.default.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
|
|
831
|
+
const db = this.store.db;
|
|
832
|
+
const sharedSkill = db.prepare("SELECT visibility, group_id FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(skillId);
|
|
737
833
|
this.jsonResponse(res, {
|
|
738
|
-
skill,
|
|
834
|
+
skill: { ...skill, sharingVisibility: sharedSkill?.visibility ?? null, sharingGroupId: sharedSkill?.group_id ?? null },
|
|
739
835
|
versions: versions.map(v => ({
|
|
740
836
|
id: v.id,
|
|
741
837
|
version: v.version,
|
|
@@ -844,7 +940,7 @@ class ViewerServer {
|
|
|
844
940
|
handleSkillVisibility(req, res, urlPath) {
|
|
845
941
|
const segments = urlPath.split("/");
|
|
846
942
|
const skillId = segments[segments.length - 2];
|
|
847
|
-
this.readBody(req, (body) => {
|
|
943
|
+
this.readBody(req, async (body) => {
|
|
848
944
|
try {
|
|
849
945
|
const parsed = JSON.parse(body);
|
|
850
946
|
const visibility = parsed.visibility;
|
|
@@ -860,7 +956,47 @@ class ViewerServer {
|
|
|
860
956
|
return;
|
|
861
957
|
}
|
|
862
958
|
this.store.setSkillVisibility(skillId, visibility);
|
|
863
|
-
|
|
959
|
+
let hubSynced = false;
|
|
960
|
+
const sharing = this.ctx?.config?.sharing;
|
|
961
|
+
if (sharing?.enabled && this.ctx) {
|
|
962
|
+
try {
|
|
963
|
+
const hubClient = await this.resolveHubClientAware();
|
|
964
|
+
if (visibility === "public") {
|
|
965
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
966
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
967
|
+
method: "POST",
|
|
968
|
+
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
969
|
+
});
|
|
970
|
+
if (hubClient.userId) {
|
|
971
|
+
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
972
|
+
this.store.upsertHubSkill({
|
|
973
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
974
|
+
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
975
|
+
name: skill.name, description: skill.description, version: skill.version,
|
|
976
|
+
groupId: null, visibility: "public",
|
|
977
|
+
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
978
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
hubSynced = true;
|
|
982
|
+
this.log.info(`Skill "${skill.name}" published to Hub`);
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
986
|
+
method: "POST",
|
|
987
|
+
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
988
|
+
});
|
|
989
|
+
if (hubClient.userId)
|
|
990
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
991
|
+
hubSynced = true;
|
|
992
|
+
this.log.info(`Skill "${skill.name}" unpublished from Hub`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
catch (hubErr) {
|
|
996
|
+
this.log.warn(`Hub sync failed for skill visibility change: ${hubErr}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, hubSynced });
|
|
864
1000
|
}
|
|
865
1001
|
catch (err) {
|
|
866
1002
|
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
@@ -1071,6 +1207,824 @@ class ViewerServer {
|
|
|
1071
1207
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1072
1208
|
return node_path_1.default.join(home, ".openclaw", "openclaw.json");
|
|
1073
1209
|
}
|
|
1210
|
+
getPluginEntryConfig(raw) {
|
|
1211
|
+
const entries = raw?.plugins?.entries ?? {};
|
|
1212
|
+
return entries["memos-local-openclaw-plugin"]?.config
|
|
1213
|
+
?? entries["memos-lite-openclaw-plugin"]?.config
|
|
1214
|
+
?? entries["memos-lite"]?.config
|
|
1215
|
+
?? {};
|
|
1216
|
+
}
|
|
1217
|
+
getResolvedViewerConfig(raw) {
|
|
1218
|
+
const pluginCfg = this.getPluginEntryConfig(raw);
|
|
1219
|
+
const stateDir = this.ctx?.stateDir ?? this.getOpenClawHome();
|
|
1220
|
+
return (0, config_1.resolveConfig)(pluginCfg, stateDir);
|
|
1221
|
+
}
|
|
1222
|
+
hasUsableEmbeddingProvider(cfg) {
|
|
1223
|
+
const embedding = cfg.embedding;
|
|
1224
|
+
if (!embedding?.provider)
|
|
1225
|
+
return false;
|
|
1226
|
+
if (embedding.provider === "openclaw") {
|
|
1227
|
+
return !!(this.ctx?.openclawAPI) && embedding.capabilities?.hostEmbedding === true;
|
|
1228
|
+
}
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
hasUsableSummarizerProvider(cfg) {
|
|
1232
|
+
const summarizer = cfg.summarizer;
|
|
1233
|
+
if (!summarizer?.provider)
|
|
1234
|
+
return false;
|
|
1235
|
+
if (summarizer.provider === "openclaw") {
|
|
1236
|
+
return !!(this.ctx?.openclawAPI) && summarizer.capabilities?.hostCompletion === true;
|
|
1237
|
+
}
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
async serveSharingStatus(res) {
|
|
1241
|
+
const sharing = this.ctx?.config?.sharing;
|
|
1242
|
+
const persisted = this.store.getClientHubConnection();
|
|
1243
|
+
const resolvedHubUrl = sharing?.client?.hubAddress ? (0, hub_1.normalizeHubUrl)(sharing.client.hubAddress) : persisted?.hubUrl ?? null;
|
|
1244
|
+
const hasClientConfig = Boolean((sharing?.client?.hubAddress && sharing?.client?.userToken) ||
|
|
1245
|
+
(persisted?.hubUrl && persisted?.userToken));
|
|
1246
|
+
const base = {
|
|
1247
|
+
enabled: Boolean(sharing?.enabled),
|
|
1248
|
+
role: sharing?.role ?? null,
|
|
1249
|
+
clientConfigured: hasClientConfig,
|
|
1250
|
+
hubUrl: resolvedHubUrl,
|
|
1251
|
+
connection: { connected: false, user: null, hubUrl: undefined, teamName: null, apiVersion: null },
|
|
1252
|
+
admin: { canManageUsers: false, rejectSupported: false },
|
|
1253
|
+
};
|
|
1254
|
+
if (!this.ctx || !sharing?.enabled) {
|
|
1255
|
+
this.jsonResponse(res, base);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
// Hub 模式下,本机就是管理者,直接赋予 admin 权限
|
|
1259
|
+
if (sharing.role === "hub") {
|
|
1260
|
+
base.admin.canManageUsers = true;
|
|
1261
|
+
base.admin.rejectSupported = true;
|
|
1262
|
+
base.connection.connected = true;
|
|
1263
|
+
base.connection.hubUrl = resolvedHubUrl ?? undefined;
|
|
1264
|
+
// 通过 hub API 获取 admin 用户的真实信息(含分组)
|
|
1265
|
+
let adminUser = { username: "hub-admin", role: "admin", groups: [] };
|
|
1266
|
+
try {
|
|
1267
|
+
const hub = this.resolveHubConnection();
|
|
1268
|
+
if (hub) {
|
|
1269
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
1270
|
+
if (me) {
|
|
1271
|
+
adminUser = {
|
|
1272
|
+
id: me.id,
|
|
1273
|
+
username: me.username ?? "hub-admin",
|
|
1274
|
+
role: me.role ?? "admin",
|
|
1275
|
+
groups: Array.isArray(me.groups) ? me.groups : [],
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
catch { /* fallback to default */ }
|
|
1281
|
+
base.connection.user = adminUser;
|
|
1282
|
+
// Fetch team info from own hub
|
|
1283
|
+
try {
|
|
1284
|
+
const selfUrl = resolvedHubUrl || `http://localhost:${sharing.hub?.port ?? 21816}`;
|
|
1285
|
+
const info = await fetch(`${selfUrl}/api/v1/hub/info`).then(r => r.ok ? r.json() : null).catch(() => null);
|
|
1286
|
+
base.connection.teamName = info?.teamName ?? sharing.hub?.teamName ?? null;
|
|
1287
|
+
base.connection.apiVersion = info?.apiVersion ?? null;
|
|
1288
|
+
}
|
|
1289
|
+
catch { /* ignore */ }
|
|
1290
|
+
this.jsonResponse(res, base);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (!hasClientConfig) {
|
|
1294
|
+
this.jsonResponse(res, base);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
try {
|
|
1298
|
+
const status = await (0, connector_1.getHubStatus)(this.store, this.ctx.config);
|
|
1299
|
+
const output = { ...base, connection: { ...base.connection, ...status } };
|
|
1300
|
+
if (status.connected && status.hubUrl) {
|
|
1301
|
+
try {
|
|
1302
|
+
const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
1303
|
+
output.connection.teamName = info?.teamName ?? null;
|
|
1304
|
+
output.connection.apiVersion = info?.apiVersion ?? null;
|
|
1305
|
+
}
|
|
1306
|
+
catch { }
|
|
1307
|
+
}
|
|
1308
|
+
output.admin.canManageUsers = status.connected && status.user?.role === "admin";
|
|
1309
|
+
output.admin.rejectSupported = output.admin.canManageUsers;
|
|
1310
|
+
this.jsonResponse(res, output);
|
|
1311
|
+
}
|
|
1312
|
+
catch (err) {
|
|
1313
|
+
this.jsonResponse(res, { ...base, error: String(err) });
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async serveSharingPendingUsers(res) {
|
|
1317
|
+
if (!this.ctx)
|
|
1318
|
+
return this.jsonResponse(res, { users: [], error: "sharing_unavailable" });
|
|
1319
|
+
try {
|
|
1320
|
+
const hub = this.resolveHubConnection();
|
|
1321
|
+
if (!hub)
|
|
1322
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
1323
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/pending-users", { method: "GET" });
|
|
1324
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
1325
|
+
}
|
|
1326
|
+
catch (err) {
|
|
1327
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
handleSharingApproveUser(req, res) {
|
|
1331
|
+
this.readBody(req, async (body) => {
|
|
1332
|
+
if (!this.ctx)
|
|
1333
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1334
|
+
try {
|
|
1335
|
+
const parsed = JSON.parse(body || "{}");
|
|
1336
|
+
const hub = this.resolveHubConnection();
|
|
1337
|
+
if (!hub)
|
|
1338
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1339
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/approve-user", {
|
|
1340
|
+
method: "POST",
|
|
1341
|
+
body: JSON.stringify({ userId: parsed.userId, username: parsed.username }),
|
|
1342
|
+
});
|
|
1343
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
handleSharingRejectUser(req, res) {
|
|
1351
|
+
this.readBody(req, async (body) => {
|
|
1352
|
+
if (!this.ctx)
|
|
1353
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1354
|
+
try {
|
|
1355
|
+
const parsed = JSON.parse(body || "{}");
|
|
1356
|
+
const hub = this.resolveHubConnection();
|
|
1357
|
+
if (!hub)
|
|
1358
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1359
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/reject-user", {
|
|
1360
|
+
method: "POST",
|
|
1361
|
+
body: JSON.stringify({ userId: parsed.userId }),
|
|
1362
|
+
});
|
|
1363
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1364
|
+
}
|
|
1365
|
+
catch (err) {
|
|
1366
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
async serveSharingMemoryList(res, url) {
|
|
1371
|
+
if (!this.ctx)
|
|
1372
|
+
return this.jsonResponse(res, { memories: [], error: "sharing_unavailable" });
|
|
1373
|
+
try {
|
|
1374
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1375
|
+
const data = await (0, hub_1.hubListMemories)(this.store, this.ctx, { limit });
|
|
1376
|
+
this.jsonResponse(res, { memories: Array.isArray(data?.memories) ? data.memories : [] });
|
|
1377
|
+
}
|
|
1378
|
+
catch (err) {
|
|
1379
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
async serveSharingTaskList(res, url) {
|
|
1383
|
+
if (!this.ctx)
|
|
1384
|
+
return this.jsonResponse(res, { tasks: [], error: "sharing_unavailable" });
|
|
1385
|
+
try {
|
|
1386
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1387
|
+
const data = await (0, hub_1.hubListTasks)(this.store, this.ctx, { limit });
|
|
1388
|
+
this.jsonResponse(res, { tasks: Array.isArray(data?.tasks) ? data.tasks : [] });
|
|
1389
|
+
}
|
|
1390
|
+
catch (err) {
|
|
1391
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
async serveSharingSkillList(res, url) {
|
|
1395
|
+
if (!this.ctx)
|
|
1396
|
+
return this.jsonResponse(res, { skills: [], error: "sharing_unavailable" });
|
|
1397
|
+
try {
|
|
1398
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1399
|
+
const data = await (0, hub_1.hubListSkills)(this.store, this.ctx, { limit });
|
|
1400
|
+
this.jsonResponse(res, { skills: Array.isArray(data?.skills) ? data.skills : [] });
|
|
1401
|
+
}
|
|
1402
|
+
catch (err) {
|
|
1403
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
handleSharingMemorySearch(req, res) {
|
|
1407
|
+
this.readBody(req, async (body) => {
|
|
1408
|
+
if (!this.ctx)
|
|
1409
|
+
return this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } }, error: "sharing_unavailable" });
|
|
1410
|
+
const emptyHub = { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } };
|
|
1411
|
+
try {
|
|
1412
|
+
const parsed = JSON.parse(body || "{}");
|
|
1413
|
+
const query = String(parsed.query || "");
|
|
1414
|
+
const role = typeof parsed.role === "string" ? parsed.role : undefined;
|
|
1415
|
+
const maxResults = typeof parsed.maxResults === "number" ? parsed.maxResults : 10;
|
|
1416
|
+
const scope = parsed.scope === "group" || parsed.scope === "all" ? parsed.scope : "local";
|
|
1417
|
+
const local = this.searchLocalViewerMemories(query, { role, maxResults });
|
|
1418
|
+
if (scope === "local") {
|
|
1419
|
+
return this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub });
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
const hub = await (0, hub_1.hubSearchMemories)(this.store, this.ctx, { query, maxResults, scope });
|
|
1423
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub });
|
|
1424
|
+
}
|
|
1425
|
+
catch (err) {
|
|
1426
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub, error: String(err) });
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
catch (err) {
|
|
1430
|
+
this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: emptyHub, error: String(err) });
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
handleSharingMemoryDetail(req, res) {
|
|
1435
|
+
this.readBody(req, async (body) => {
|
|
1436
|
+
if (!this.ctx)
|
|
1437
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
1438
|
+
try {
|
|
1439
|
+
const parsed = JSON.parse(body || "{}");
|
|
1440
|
+
const detail = await (0, hub_1.hubGetMemoryDetail)(this.store, this.ctx, { remoteHitId: String(parsed.remoteHitId || "") });
|
|
1441
|
+
this.jsonResponse(res, detail);
|
|
1442
|
+
}
|
|
1443
|
+
catch (err) {
|
|
1444
|
+
this.jsonResponse(res, { error: String(err) });
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
async serveSharingSkillSearch(res, url) {
|
|
1449
|
+
if (!this.ctx)
|
|
1450
|
+
return this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: "sharing_unavailable" });
|
|
1451
|
+
try {
|
|
1452
|
+
const query = String(url.searchParams.get("query") || "");
|
|
1453
|
+
const scope = url.searchParams.get("scope") === "group" || url.searchParams.get("scope") === "all" ? url.searchParams.get("scope") : "local";
|
|
1454
|
+
const recall = new engine_1.RecallEngine(this.store, this.embedder, this.ctx);
|
|
1455
|
+
const localHits = await recall.searchSkills(query, "mix", "agent:main");
|
|
1456
|
+
if (scope === "local") {
|
|
1457
|
+
return this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] } });
|
|
1458
|
+
}
|
|
1459
|
+
try {
|
|
1460
|
+
const hub = await (0, hub_1.hubSearchSkills)(this.store, this.ctx, { query, maxResults: Number(url.searchParams.get("maxResults") || 20) });
|
|
1461
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub });
|
|
1462
|
+
}
|
|
1463
|
+
catch (err) {
|
|
1464
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] }, error: String(err) });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
catch (err) {
|
|
1468
|
+
this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: String(err) });
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
searchLocalViewerMemories(query, options) {
|
|
1472
|
+
const db = this.store.db;
|
|
1473
|
+
const role = options?.role;
|
|
1474
|
+
const maxResults = options?.maxResults ?? 10;
|
|
1475
|
+
const params = [];
|
|
1476
|
+
let rows = [];
|
|
1477
|
+
try {
|
|
1478
|
+
let sql = "SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ?";
|
|
1479
|
+
params.push(query);
|
|
1480
|
+
if (role) {
|
|
1481
|
+
sql += " AND c.role = ?";
|
|
1482
|
+
params.push(role);
|
|
1483
|
+
}
|
|
1484
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
1485
|
+
params.push(maxResults);
|
|
1486
|
+
rows = db.prepare(sql).all(...params);
|
|
1487
|
+
}
|
|
1488
|
+
catch {
|
|
1489
|
+
const likeParams = [`%${query}%`, `%${query}%`];
|
|
1490
|
+
let sql = "SELECT * FROM chunks WHERE (content LIKE ? OR summary LIKE ?)";
|
|
1491
|
+
if (role) {
|
|
1492
|
+
sql += " AND role = ?";
|
|
1493
|
+
likeParams.push(role);
|
|
1494
|
+
}
|
|
1495
|
+
sql += " ORDER BY created_at DESC LIMIT ?";
|
|
1496
|
+
likeParams.push(maxResults);
|
|
1497
|
+
rows = db.prepare(sql).all(...likeParams);
|
|
1498
|
+
}
|
|
1499
|
+
const hits = rows.map((row, idx) => ({
|
|
1500
|
+
id: row.id,
|
|
1501
|
+
summary: row.summary || row.content?.slice(0, 120) || "",
|
|
1502
|
+
excerpt: row.content || "",
|
|
1503
|
+
score: Math.max(0.3, 1 - idx * 0.1),
|
|
1504
|
+
role: row.role,
|
|
1505
|
+
ref: { sessionKey: row.session_key, chunkId: row.id, turnId: row.turn_id, seq: row.seq },
|
|
1506
|
+
taskId: row.task_id ?? null,
|
|
1507
|
+
skillId: row.skill_id ?? null,
|
|
1508
|
+
}));
|
|
1509
|
+
return { hits, meta: { total: hits.length, usedMaxResults: maxResults } };
|
|
1510
|
+
}
|
|
1511
|
+
handleSharingTaskShare(req, res) {
|
|
1512
|
+
this.readBody(req, async (body) => {
|
|
1513
|
+
if (!this.ctx)
|
|
1514
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1515
|
+
try {
|
|
1516
|
+
const parsed = JSON.parse(body || "{}");
|
|
1517
|
+
const taskId = String(parsed.taskId || "");
|
|
1518
|
+
const visibility = parsed.visibility === "group" ? "group" : "public";
|
|
1519
|
+
const groupId = typeof parsed.groupId === "string" ? parsed.groupId : undefined;
|
|
1520
|
+
const task = this.store.getTask(taskId);
|
|
1521
|
+
if (!task)
|
|
1522
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
1523
|
+
const chunks = this.store.getChunksByTask(taskId);
|
|
1524
|
+
if (chunks.length === 0)
|
|
1525
|
+
return this.jsonResponse(res, { ok: false, error: "no_chunks" });
|
|
1526
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1527
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1528
|
+
method: "POST",
|
|
1529
|
+
body: JSON.stringify({
|
|
1530
|
+
task: {
|
|
1531
|
+
id: task.id,
|
|
1532
|
+
sourceTaskId: task.id,
|
|
1533
|
+
title: task.title,
|
|
1534
|
+
summary: task.summary,
|
|
1535
|
+
groupId: visibility === "group" ? groupId ?? null : null,
|
|
1536
|
+
visibility,
|
|
1537
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
1538
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
1539
|
+
},
|
|
1540
|
+
chunks: chunks.map((chunk) => ({
|
|
1541
|
+
id: chunk.id,
|
|
1542
|
+
hubTaskId: task.id,
|
|
1543
|
+
sourceTaskId: task.id,
|
|
1544
|
+
sourceChunkId: chunk.id,
|
|
1545
|
+
role: chunk.role,
|
|
1546
|
+
content: chunk.content,
|
|
1547
|
+
summary: chunk.summary,
|
|
1548
|
+
kind: chunk.kind,
|
|
1549
|
+
createdAt: chunk.createdAt,
|
|
1550
|
+
})),
|
|
1551
|
+
}),
|
|
1552
|
+
});
|
|
1553
|
+
const hubUserId = hubClient.userId;
|
|
1554
|
+
if (hubUserId) {
|
|
1555
|
+
this.store.upsertHubTask({
|
|
1556
|
+
id: task.id,
|
|
1557
|
+
sourceTaskId: task.id,
|
|
1558
|
+
sourceUserId: hubUserId,
|
|
1559
|
+
title: task.title,
|
|
1560
|
+
summary: task.summary,
|
|
1561
|
+
groupId: visibility === "group" ? groupId ?? null : null,
|
|
1562
|
+
visibility,
|
|
1563
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
1564
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
this.jsonResponse(res, { ok: true, taskId, visibility, response });
|
|
1568
|
+
}
|
|
1569
|
+
catch (err) {
|
|
1570
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
handleSharingTaskUnshare(req, res) {
|
|
1575
|
+
this.readBody(req, async (body) => {
|
|
1576
|
+
if (!this.ctx)
|
|
1577
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1578
|
+
try {
|
|
1579
|
+
const parsed = JSON.parse(body || "{}");
|
|
1580
|
+
const taskId = String(parsed.taskId || "");
|
|
1581
|
+
const task = this.store.getTask(taskId);
|
|
1582
|
+
if (!task)
|
|
1583
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
1584
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1585
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1586
|
+
method: "POST",
|
|
1587
|
+
body: JSON.stringify({ sourceTaskId: task.id }),
|
|
1588
|
+
});
|
|
1589
|
+
const hubUserId = hubClient.userId;
|
|
1590
|
+
if (hubUserId)
|
|
1591
|
+
this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
1592
|
+
this.jsonResponse(res, { ok: true, taskId });
|
|
1593
|
+
}
|
|
1594
|
+
catch (err) {
|
|
1595
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
handleSharingMemoryShare(req, res) {
|
|
1600
|
+
this.readBody(req, async (body) => {
|
|
1601
|
+
if (!this.ctx)
|
|
1602
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1603
|
+
try {
|
|
1604
|
+
const parsed = JSON.parse(body || "{}");
|
|
1605
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1606
|
+
const visibility = parsed.visibility === "group" ? "group" : "public";
|
|
1607
|
+
const groupId = typeof parsed.groupId === "string" ? parsed.groupId : undefined;
|
|
1608
|
+
const db = this.store.db;
|
|
1609
|
+
const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1610
|
+
if (!chunk)
|
|
1611
|
+
return this.jsonResponse(res, { ok: false, error: "memory_not_found" });
|
|
1612
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1613
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
1614
|
+
method: "POST",
|
|
1615
|
+
body: JSON.stringify({
|
|
1616
|
+
memory: {
|
|
1617
|
+
sourceChunkId: chunk.id,
|
|
1618
|
+
role: chunk.role,
|
|
1619
|
+
content: chunk.content,
|
|
1620
|
+
summary: chunk.summary,
|
|
1621
|
+
kind: chunk.kind,
|
|
1622
|
+
groupId: visibility === "group" ? groupId ?? null : null,
|
|
1623
|
+
visibility,
|
|
1624
|
+
},
|
|
1625
|
+
}),
|
|
1626
|
+
});
|
|
1627
|
+
const hubUserId = hubClient.userId;
|
|
1628
|
+
if (hubUserId) {
|
|
1629
|
+
const now = Date.now();
|
|
1630
|
+
const existing = this.store.getHubMemoryBySource(hubUserId, chunk.id);
|
|
1631
|
+
this.store.upsertHubMemory({
|
|
1632
|
+
id: response?.memoryId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1633
|
+
sourceChunkId: chunk.id,
|
|
1634
|
+
sourceUserId: hubUserId,
|
|
1635
|
+
role: chunk.role,
|
|
1636
|
+
content: chunk.content,
|
|
1637
|
+
summary: chunk.summary ?? "",
|
|
1638
|
+
kind: chunk.kind,
|
|
1639
|
+
groupId: visibility === "group" ? groupId ?? null : null,
|
|
1640
|
+
visibility,
|
|
1641
|
+
createdAt: existing?.createdAt ?? now,
|
|
1642
|
+
updatedAt: now,
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
this.jsonResponse(res, { ok: true, chunkId, visibility, response });
|
|
1646
|
+
}
|
|
1647
|
+
catch (err) {
|
|
1648
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
handleSharingMemoryUnshare(req, res) {
|
|
1653
|
+
this.readBody(req, async (body) => {
|
|
1654
|
+
if (!this.ctx)
|
|
1655
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1656
|
+
try {
|
|
1657
|
+
const parsed = JSON.parse(body || "{}");
|
|
1658
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1659
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1660
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1661
|
+
method: "POST",
|
|
1662
|
+
body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1663
|
+
});
|
|
1664
|
+
const hubUserId = hubClient.userId;
|
|
1665
|
+
if (hubUserId)
|
|
1666
|
+
this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
1667
|
+
this.jsonResponse(res, { ok: true, chunkId });
|
|
1668
|
+
}
|
|
1669
|
+
catch (err) {
|
|
1670
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
handleSharingSkillPull(req, res) {
|
|
1675
|
+
this.readBody(req, async (body) => {
|
|
1676
|
+
if (!this.ctx)
|
|
1677
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1678
|
+
try {
|
|
1679
|
+
const parsed = JSON.parse(body || "{}");
|
|
1680
|
+
const skillId = String(parsed.skillId || "");
|
|
1681
|
+
const payload = await (0, skill_sync_1.fetchHubSkillBundle)(this.store, this.ctx, { skillId });
|
|
1682
|
+
const restored = (0, skill_sync_1.restoreSkillBundleFromHub)(this.store, this.ctx, payload);
|
|
1683
|
+
this.jsonResponse(res, { ok: true, pulled: true, hubSkillId: skillId, ...restored });
|
|
1684
|
+
}
|
|
1685
|
+
catch (err) {
|
|
1686
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
handleSharingSkillShare(req, res) {
|
|
1691
|
+
this.readBody(req, async (body) => {
|
|
1692
|
+
if (!this.ctx)
|
|
1693
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1694
|
+
try {
|
|
1695
|
+
const parsed = JSON.parse(body || "{}");
|
|
1696
|
+
const skillId = String(parsed.skillId || "");
|
|
1697
|
+
const visibility = parsed.visibility === "group" ? "group" : "public";
|
|
1698
|
+
const groupId = parsed.groupId ? String(parsed.groupId) : null;
|
|
1699
|
+
const skill = this.store.getSkill(skillId);
|
|
1700
|
+
if (!skill)
|
|
1701
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
1702
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
1703
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1704
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
1705
|
+
method: "POST",
|
|
1706
|
+
body: JSON.stringify({
|
|
1707
|
+
visibility,
|
|
1708
|
+
groupId: visibility === "group" ? groupId : null,
|
|
1709
|
+
metadata: bundle.metadata,
|
|
1710
|
+
bundle: bundle.bundle,
|
|
1711
|
+
}),
|
|
1712
|
+
});
|
|
1713
|
+
const hubUserId = hubClient.userId;
|
|
1714
|
+
if (hubUserId) {
|
|
1715
|
+
const existing = this.store.getHubSkillBySource(hubUserId, skillId);
|
|
1716
|
+
this.store.upsertHubSkill({
|
|
1717
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1718
|
+
sourceSkillId: skillId,
|
|
1719
|
+
sourceUserId: hubUserId,
|
|
1720
|
+
name: skill.name,
|
|
1721
|
+
description: skill.description,
|
|
1722
|
+
version: skill.version,
|
|
1723
|
+
groupId: visibility === "group" ? groupId : null,
|
|
1724
|
+
visibility,
|
|
1725
|
+
bundle: JSON.stringify(bundle.bundle),
|
|
1726
|
+
qualityScore: skill.qualityScore,
|
|
1727
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
1728
|
+
updatedAt: Date.now(),
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, response });
|
|
1732
|
+
}
|
|
1733
|
+
catch (err) {
|
|
1734
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
handleSharingSkillUnshare(req, res) {
|
|
1739
|
+
this.readBody(req, async (body) => {
|
|
1740
|
+
if (!this.ctx)
|
|
1741
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1742
|
+
try {
|
|
1743
|
+
const parsed = JSON.parse(body || "{}");
|
|
1744
|
+
const skillId = String(parsed.skillId || "");
|
|
1745
|
+
const skill = this.store.getSkill(skillId);
|
|
1746
|
+
if (!skill)
|
|
1747
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
1748
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1749
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1750
|
+
method: "POST",
|
|
1751
|
+
body: JSON.stringify({ sourceSkillId: skill.id }),
|
|
1752
|
+
});
|
|
1753
|
+
const hubUserId = hubClient.userId;
|
|
1754
|
+
if (hubUserId)
|
|
1755
|
+
this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
1756
|
+
this.jsonResponse(res, { ok: true, skillId });
|
|
1757
|
+
}
|
|
1758
|
+
catch (err) {
|
|
1759
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1760
|
+
}
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
resolveHubConnection() {
|
|
1764
|
+
if (!this.ctx)
|
|
1765
|
+
return null;
|
|
1766
|
+
// Hub 模式:连接自己,用 bootstrap admin token
|
|
1767
|
+
const sharing = this.ctx.config.sharing;
|
|
1768
|
+
if (sharing?.role === "hub") {
|
|
1769
|
+
const hubPort = sharing.hub?.port ?? 18800;
|
|
1770
|
+
const hubUrl = `http://127.0.0.1:${hubPort}`;
|
|
1771
|
+
try {
|
|
1772
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
1773
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
1774
|
+
const adminToken = authData?.bootstrapAdminToken;
|
|
1775
|
+
if (adminToken)
|
|
1776
|
+
return { hubUrl, userToken: adminToken };
|
|
1777
|
+
}
|
|
1778
|
+
catch {
|
|
1779
|
+
// hub-auth.json 不存在或读取失败,fall through
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
// Client 模式:用配置的 hubAddress + userToken
|
|
1783
|
+
const conn = this.store.getClientHubConnection();
|
|
1784
|
+
const hubUrl = conn?.hubUrl || this.ctx.config.sharing?.client?.hubAddress || "";
|
|
1785
|
+
const userToken = conn?.userToken || this.ctx.config.sharing?.client?.userToken || "";
|
|
1786
|
+
if (!hubUrl || !userToken)
|
|
1787
|
+
return null;
|
|
1788
|
+
return { hubUrl: (0, hub_1.normalizeHubUrl)(hubUrl), userToken };
|
|
1789
|
+
}
|
|
1790
|
+
/** resolveHubClient 的 viewer 版本:hub 模式下使用 bootstrap admin 身份 */
|
|
1791
|
+
async resolveHubClientAware() {
|
|
1792
|
+
if (!this.ctx)
|
|
1793
|
+
throw new Error("sharing_unavailable");
|
|
1794
|
+
const sharing = this.ctx.config.sharing;
|
|
1795
|
+
if (sharing?.role === "hub") {
|
|
1796
|
+
const hub = this.resolveHubConnection();
|
|
1797
|
+
if (hub) {
|
|
1798
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
1799
|
+
return {
|
|
1800
|
+
hubUrl: hub.hubUrl,
|
|
1801
|
+
userToken: hub.userToken,
|
|
1802
|
+
userId: String(me.id),
|
|
1803
|
+
username: String(me.username ?? "hub-admin"),
|
|
1804
|
+
role: String(me.role ?? "admin"),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return (0, hub_1.resolveHubClient)(this.store, this.ctx);
|
|
1809
|
+
}
|
|
1810
|
+
extractGroupId(path) {
|
|
1811
|
+
const m = path.match(/\/api\/sharing\/groups\/([^/]+)/);
|
|
1812
|
+
return m ? decodeURIComponent(m[1]) : "";
|
|
1813
|
+
}
|
|
1814
|
+
async serveSharingGroups(res) {
|
|
1815
|
+
const hub = this.resolveHubConnection();
|
|
1816
|
+
if (!hub)
|
|
1817
|
+
return this.jsonResponse(res, { groups: [], error: "not_configured" });
|
|
1818
|
+
try {
|
|
1819
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/groups", { method: "GET" });
|
|
1820
|
+
this.jsonResponse(res, { groups: Array.isArray(data?.groups) ? data.groups : [] });
|
|
1821
|
+
}
|
|
1822
|
+
catch (err) {
|
|
1823
|
+
this.jsonResponse(res, { groups: [], error: String(err) });
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
handleSharingGroupCreate(req, res) {
|
|
1827
|
+
this.readBody(req, async (body) => {
|
|
1828
|
+
const hub = this.resolveHubConnection();
|
|
1829
|
+
if (!hub)
|
|
1830
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1831
|
+
try {
|
|
1832
|
+
const parsed = JSON.parse(body || "{}");
|
|
1833
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/groups", {
|
|
1834
|
+
method: "POST",
|
|
1835
|
+
body: JSON.stringify({ name: parsed.name, description: parsed.description }),
|
|
1836
|
+
});
|
|
1837
|
+
this.jsonResponse(res, { ok: true, ...data });
|
|
1838
|
+
}
|
|
1839
|
+
catch (err) {
|
|
1840
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
handleSharingGroupUpdate(req, res, p) {
|
|
1845
|
+
this.readBody(req, async (body) => {
|
|
1846
|
+
const hub = this.resolveHubConnection();
|
|
1847
|
+
if (!hub)
|
|
1848
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1849
|
+
const groupId = this.extractGroupId(p);
|
|
1850
|
+
try {
|
|
1851
|
+
const parsed = JSON.parse(body || "{}");
|
|
1852
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, {
|
|
1853
|
+
method: "PUT",
|
|
1854
|
+
body: JSON.stringify({ name: parsed.name, description: parsed.description }),
|
|
1855
|
+
});
|
|
1856
|
+
this.jsonResponse(res, { ok: true });
|
|
1857
|
+
}
|
|
1858
|
+
catch (err) {
|
|
1859
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
async handleSharingGroupDelete(res, p) {
|
|
1864
|
+
const hub = this.resolveHubConnection();
|
|
1865
|
+
if (!hub)
|
|
1866
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1867
|
+
const groupId = this.extractGroupId(p);
|
|
1868
|
+
try {
|
|
1869
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, { method: "DELETE" });
|
|
1870
|
+
this.jsonResponse(res, { ok: true });
|
|
1871
|
+
}
|
|
1872
|
+
catch (err) {
|
|
1873
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
async serveSharingGroupMembers(res, p) {
|
|
1877
|
+
const hub = this.resolveHubConnection();
|
|
1878
|
+
if (!hub)
|
|
1879
|
+
return this.jsonResponse(res, { members: [], error: "not_configured" });
|
|
1880
|
+
const groupId = this.extractGroupId(p);
|
|
1881
|
+
try {
|
|
1882
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}`, { method: "GET" });
|
|
1883
|
+
this.jsonResponse(res, { members: Array.isArray(data?.members) ? data.members : [] });
|
|
1884
|
+
}
|
|
1885
|
+
catch (err) {
|
|
1886
|
+
this.jsonResponse(res, { members: [], error: String(err) });
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
handleSharingGroupAddMember(req, res, p) {
|
|
1890
|
+
this.readBody(req, async (body) => {
|
|
1891
|
+
const hub = this.resolveHubConnection();
|
|
1892
|
+
if (!hub)
|
|
1893
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1894
|
+
const groupId = this.extractGroupId(p);
|
|
1895
|
+
try {
|
|
1896
|
+
const parsed = JSON.parse(body || "{}");
|
|
1897
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}/members`, {
|
|
1898
|
+
method: "POST",
|
|
1899
|
+
body: JSON.stringify({ userId: parsed.userId }),
|
|
1900
|
+
});
|
|
1901
|
+
this.jsonResponse(res, { ok: true });
|
|
1902
|
+
}
|
|
1903
|
+
catch (err) {
|
|
1904
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
handleSharingGroupRemoveMember(req, res, p) {
|
|
1909
|
+
this.readBody(req, async (body) => {
|
|
1910
|
+
const hub = this.resolveHubConnection();
|
|
1911
|
+
if (!hub)
|
|
1912
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1913
|
+
const groupId = this.extractGroupId(p);
|
|
1914
|
+
try {
|
|
1915
|
+
const parsed = JSON.parse(body || "{}");
|
|
1916
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/groups/${encodeURIComponent(groupId)}/members`, {
|
|
1917
|
+
method: "DELETE",
|
|
1918
|
+
body: JSON.stringify({ userId: parsed.userId }),
|
|
1919
|
+
});
|
|
1920
|
+
this.jsonResponse(res, { ok: true });
|
|
1921
|
+
}
|
|
1922
|
+
catch (err) {
|
|
1923
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
async serveSharingUsers(res) {
|
|
1928
|
+
const hub = this.resolveHubConnection();
|
|
1929
|
+
if (!hub)
|
|
1930
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
1931
|
+
try {
|
|
1932
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/users", { method: "GET" });
|
|
1933
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
1934
|
+
}
|
|
1935
|
+
catch (err) {
|
|
1936
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
// ─── Admin management endpoints (Hub-side data) ───
|
|
1940
|
+
async serveAdminSharedTasks(res) {
|
|
1941
|
+
const hub = this.resolveHubConnection();
|
|
1942
|
+
if (!hub)
|
|
1943
|
+
return this.jsonResponse(res, { tasks: [], error: "not_configured" });
|
|
1944
|
+
try {
|
|
1945
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-tasks", { method: "GET" });
|
|
1946
|
+
this.jsonResponse(res, { tasks: Array.isArray(data?.tasks) ? data.tasks : [] });
|
|
1947
|
+
}
|
|
1948
|
+
catch (err) {
|
|
1949
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
async handleAdminDeleteTask(res, p) {
|
|
1953
|
+
const hub = this.resolveHubConnection();
|
|
1954
|
+
if (!hub)
|
|
1955
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1956
|
+
const taskId = decodeURIComponent(p.replace("/api/admin/shared-tasks/", ""));
|
|
1957
|
+
try {
|
|
1958
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-tasks/${encodeURIComponent(taskId)}`, { method: "DELETE" });
|
|
1959
|
+
this.jsonResponse(res, { ok: true });
|
|
1960
|
+
}
|
|
1961
|
+
catch (err) {
|
|
1962
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
async serveAdminSharedSkills(res) {
|
|
1966
|
+
const hub = this.resolveHubConnection();
|
|
1967
|
+
if (!hub)
|
|
1968
|
+
return this.jsonResponse(res, { skills: [], error: "not_configured" });
|
|
1969
|
+
try {
|
|
1970
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-skills", { method: "GET" });
|
|
1971
|
+
this.jsonResponse(res, { skills: Array.isArray(data?.skills) ? data.skills : [] });
|
|
1972
|
+
}
|
|
1973
|
+
catch (err) {
|
|
1974
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
async handleAdminDeleteSkill(res, p) {
|
|
1978
|
+
const hub = this.resolveHubConnection();
|
|
1979
|
+
if (!hub)
|
|
1980
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1981
|
+
const skillId = decodeURIComponent(p.replace("/api/admin/shared-skills/", ""));
|
|
1982
|
+
try {
|
|
1983
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-skills/${encodeURIComponent(skillId)}`, { method: "DELETE" });
|
|
1984
|
+
this.jsonResponse(res, { ok: true });
|
|
1985
|
+
}
|
|
1986
|
+
catch (err) {
|
|
1987
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
async serveAdminSharedMemories(res) {
|
|
1991
|
+
const hub = this.resolveHubConnection();
|
|
1992
|
+
if (!hub)
|
|
1993
|
+
return this.jsonResponse(res, { memories: [], error: "not_configured" });
|
|
1994
|
+
try {
|
|
1995
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-memories", { method: "GET" });
|
|
1996
|
+
this.jsonResponse(res, { memories: Array.isArray(data?.memories) ? data.memories : [] });
|
|
1997
|
+
}
|
|
1998
|
+
catch (err) {
|
|
1999
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
async handleAdminDeleteMemory(res, p) {
|
|
2003
|
+
const hub = this.resolveHubConnection();
|
|
2004
|
+
if (!hub)
|
|
2005
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2006
|
+
const memoryId = decodeURIComponent(p.replace("/api/admin/shared-memories/", ""));
|
|
2007
|
+
try {
|
|
2008
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-memories/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
|
|
2009
|
+
this.jsonResponse(res, { ok: true });
|
|
2010
|
+
}
|
|
2011
|
+
catch (err) {
|
|
2012
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
serveLocalIPs(res) {
|
|
2016
|
+
const nets = node_os_1.default.networkInterfaces();
|
|
2017
|
+
const ips = [];
|
|
2018
|
+
for (const name of Object.keys(nets)) {
|
|
2019
|
+
for (const net of nets[name] ?? []) {
|
|
2020
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
2021
|
+
ips.push(net.address);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2026
|
+
res.end(JSON.stringify({ ips }));
|
|
2027
|
+
}
|
|
1074
2028
|
serveConfig(res) {
|
|
1075
2029
|
try {
|
|
1076
2030
|
const cfgPath = this.getOpenClawConfigPath();
|
|
@@ -1091,7 +2045,10 @@ class ViewerServer {
|
|
|
1091
2045
|
?? entries["memos-lite-openclaw-plugin"]
|
|
1092
2046
|
?? entries["memos-lite"]
|
|
1093
2047
|
?? {};
|
|
1094
|
-
if (pluginEntry.viewerPort
|
|
2048
|
+
if (pluginEntry.viewerPort != null) {
|
|
2049
|
+
result.viewerPort = pluginEntry.viewerPort;
|
|
2050
|
+
}
|
|
2051
|
+
else if (topEntry.viewerPort) {
|
|
1095
2052
|
result.viewerPort = topEntry.viewerPort;
|
|
1096
2053
|
}
|
|
1097
2054
|
this.jsonResponse(res, result);
|
|
@@ -1137,6 +2094,15 @@ class ViewerServer {
|
|
|
1137
2094
|
config.viewerPort = newCfg.viewerPort;
|
|
1138
2095
|
if (newCfg.telemetry !== undefined)
|
|
1139
2096
|
config.telemetry = newCfg.telemetry;
|
|
2097
|
+
if (newCfg.sharing !== undefined) {
|
|
2098
|
+
const existing = config.sharing || {};
|
|
2099
|
+
const merged = { ...existing, ...newCfg.sharing };
|
|
2100
|
+
// Deep-merge capabilities so new keys don't wipe existing ones
|
|
2101
|
+
if (newCfg.sharing.capabilities && existing.capabilities) {
|
|
2102
|
+
merged.capabilities = { ...existing.capabilities, ...newCfg.sharing.capabilities };
|
|
2103
|
+
}
|
|
2104
|
+
config.sharing = merged;
|
|
2105
|
+
}
|
|
1140
2106
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(cfgPath), { recursive: true });
|
|
1141
2107
|
node_fs_1.default.writeFileSync(cfgPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
1142
2108
|
this.log.info("Plugin config updated via Viewer");
|
|
@@ -1149,6 +2115,89 @@ class ViewerServer {
|
|
|
1149
2115
|
}
|
|
1150
2116
|
});
|
|
1151
2117
|
}
|
|
2118
|
+
handleUpdateUsername(req, res) {
|
|
2119
|
+
this.readBody(req, async (body) => {
|
|
2120
|
+
if (!this.ctx)
|
|
2121
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
2122
|
+
try {
|
|
2123
|
+
const { username } = JSON.parse(body || "{}");
|
|
2124
|
+
if (!username || typeof username !== "string" || username.trim().length < 2 || username.trim().length > 32) {
|
|
2125
|
+
return this.jsonResponse(res, { error: "invalid_username" }, 400);
|
|
2126
|
+
}
|
|
2127
|
+
const trimmed = username.trim();
|
|
2128
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2129
|
+
const result = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/me/update-profile", {
|
|
2130
|
+
method: "POST",
|
|
2131
|
+
body: JSON.stringify({ username: trimmed }),
|
|
2132
|
+
});
|
|
2133
|
+
if (result.ok && result.userToken) {
|
|
2134
|
+
const sharing = this.ctx.config.sharing;
|
|
2135
|
+
if (sharing?.role === "hub") {
|
|
2136
|
+
try {
|
|
2137
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
2138
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
2139
|
+
authData.bootstrapAdminToken = result.userToken;
|
|
2140
|
+
node_fs_1.default.writeFileSync(authPath, JSON.stringify(authData, null, 2), "utf-8");
|
|
2141
|
+
this.log.info("hub-auth.json updated with new admin token after username change");
|
|
2142
|
+
}
|
|
2143
|
+
catch (e) {
|
|
2144
|
+
this.log.warn(`Failed to update hub-auth.json: ${e}`);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
else {
|
|
2148
|
+
const persisted = this.store.getClientHubConnection();
|
|
2149
|
+
if (persisted) {
|
|
2150
|
+
this.store.setClientHubConnection({
|
|
2151
|
+
...persisted,
|
|
2152
|
+
username: result.username,
|
|
2153
|
+
userToken: result.userToken,
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
this.jsonResponse(res, result);
|
|
2159
|
+
}
|
|
2160
|
+
catch (err) {
|
|
2161
|
+
const msg = String(err?.message || err);
|
|
2162
|
+
if (msg.includes("409") || msg.includes("username_taken")) {
|
|
2163
|
+
return this.jsonResponse(res, { error: "username_taken" }, 409);
|
|
2164
|
+
}
|
|
2165
|
+
this.jsonResponse(res, { error: msg }, 500);
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
handleTestHubConnection(req, res) {
|
|
2170
|
+
this.readBody(req, async (body) => {
|
|
2171
|
+
try {
|
|
2172
|
+
const { hubUrl } = JSON.parse(body);
|
|
2173
|
+
if (!hubUrl) {
|
|
2174
|
+
this.jsonResponse(res, { ok: false, error: "hubUrl is required" });
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
|
|
2178
|
+
const ctrl = new AbortController();
|
|
2179
|
+
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
2180
|
+
try {
|
|
2181
|
+
const r = await fetch(url, { signal: ctrl.signal });
|
|
2182
|
+
clearTimeout(timeout);
|
|
2183
|
+
if (!r.ok) {
|
|
2184
|
+
this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
const info = await r.json();
|
|
2188
|
+
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
2189
|
+
}
|
|
2190
|
+
catch (e) {
|
|
2191
|
+
clearTimeout(timeout);
|
|
2192
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2193
|
+
this.jsonResponse(res, { ok: false, error: msg.includes("abort") ? "Connection timeout (8s)" : msg });
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
catch (e) {
|
|
2197
|
+
this.jsonResponse(res, { ok: false, error: String(e) });
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
1152
2201
|
handleTestModel(req, res) {
|
|
1153
2202
|
this.readBody(req, async (body) => {
|
|
1154
2203
|
try {
|
|
@@ -2460,8 +3509,8 @@ class ViewerServer {
|
|
|
2460
3509
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
2461
3510
|
req.on("end", () => cb(body));
|
|
2462
3511
|
}
|
|
2463
|
-
jsonResponse(res, data) {
|
|
2464
|
-
res.writeHead(
|
|
3512
|
+
jsonResponse(res, data, statusCode = 200) {
|
|
3513
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
2465
3514
|
res.end(JSON.stringify(data));
|
|
2466
3515
|
}
|
|
2467
3516
|
}
|