@memtensor/memos-local-openclaw-plugin 1.0.3 → 1.0.4-beta.1
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 +30 -0
- package/dist/client/connector.d.ts.map +1 -0
- package/dist/client/connector.js +219 -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 +747 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/user-manager.d.ts +29 -0
- package/dist/hub/user-manager.d.ts.map +1 -0
- package/dist/hub/user-manager.js +125 -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 +828 -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 +2595 -345
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +45 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1153 -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 +218 -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 +740 -0
- package/src/hub/user-manager.ts +139 -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 +1093 -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 +2595 -345
- package/src/viewer/server.ts +1068 -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,76 @@ 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/retry-join" && req.method === "POST")
|
|
286
|
+
this.handleRetryJoin(req, res);
|
|
287
|
+
else if (p === "/api/sharing/search/memories" && req.method === "POST")
|
|
288
|
+
this.handleSharingMemorySearch(req, res);
|
|
289
|
+
else if (p === "/api/sharing/memories/list" && req.method === "GET")
|
|
290
|
+
this.serveSharingMemoryList(res, url);
|
|
291
|
+
else if (p === "/api/sharing/tasks/list" && req.method === "GET")
|
|
292
|
+
this.serveSharingTaskList(res, url);
|
|
293
|
+
else if (p === "/api/sharing/skills/list" && req.method === "GET")
|
|
294
|
+
this.serveSharingSkillList(res, url);
|
|
295
|
+
else if (p === "/api/sharing/memory-detail" && req.method === "POST")
|
|
296
|
+
this.handleSharingMemoryDetail(req, res);
|
|
297
|
+
else if (p === "/api/sharing/search/skills" && req.method === "GET")
|
|
298
|
+
this.serveSharingSkillSearch(res, url);
|
|
299
|
+
else if (p === "/api/sharing/tasks/share" && req.method === "POST")
|
|
300
|
+
this.handleSharingTaskShare(req, res);
|
|
301
|
+
else if (p === "/api/sharing/tasks/unshare" && req.method === "POST")
|
|
302
|
+
this.handleSharingTaskUnshare(req, res);
|
|
303
|
+
else if (p === "/api/sharing/update-username" && req.method === "POST")
|
|
304
|
+
this.handleUpdateUsername(req, res);
|
|
305
|
+
else if (p === "/api/sharing/test-hub" && req.method === "POST")
|
|
306
|
+
this.handleTestHubConnection(req, res);
|
|
307
|
+
else if (p === "/api/sharing/memories/share" && req.method === "POST")
|
|
308
|
+
this.handleSharingMemoryShare(req, res);
|
|
309
|
+
else if (p === "/api/sharing/memories/unshare" && req.method === "POST")
|
|
310
|
+
this.handleSharingMemoryUnshare(req, res);
|
|
311
|
+
else if (p === "/api/sharing/skills/pull" && req.method === "POST")
|
|
312
|
+
this.handleSharingSkillPull(req, res);
|
|
313
|
+
else if (p === "/api/sharing/skills/share" && req.method === "POST")
|
|
314
|
+
this.handleSharingSkillShare(req, res);
|
|
315
|
+
else if (p === "/api/sharing/skills/unshare" && req.method === "POST")
|
|
316
|
+
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
|
+
else if (p === "/api/sharing/users" && req.method === "GET")
|
|
332
|
+
this.serveSharingUsers(res);
|
|
333
|
+
else if (p === "/api/admin/shared-tasks" && req.method === "GET")
|
|
334
|
+
this.serveAdminSharedTasks(res);
|
|
335
|
+
else if (p.match(/^\/api\/admin\/shared-tasks\/[^/]+$/) && req.method === "DELETE")
|
|
336
|
+
this.handleAdminDeleteTask(res, p);
|
|
337
|
+
else if (p === "/api/admin/shared-skills" && req.method === "GET")
|
|
338
|
+
this.serveAdminSharedSkills(res);
|
|
339
|
+
else if (p.match(/^\/api\/admin\/shared-skills\/[^/]+$/) && req.method === "DELETE")
|
|
340
|
+
this.handleAdminDeleteSkill(res, p);
|
|
341
|
+
else if (p === "/api/admin/shared-memories" && req.method === "GET")
|
|
342
|
+
this.serveAdminSharedMemories(res);
|
|
343
|
+
else if (p.match(/^\/api\/admin\/shared-memories\/[^/]+$/) && req.method === "DELETE")
|
|
344
|
+
this.handleAdminDeleteMemory(res, p);
|
|
345
|
+
else if (p === "/api/local-ips" && req.method === "GET")
|
|
346
|
+
this.serveLocalIPs(res);
|
|
273
347
|
else if (p === "/api/config" && req.method === "GET")
|
|
274
348
|
this.serveConfig(res);
|
|
275
349
|
else if (p === "/api/config" && req.method === "PUT")
|
|
@@ -455,15 +529,28 @@ class ViewerServer {
|
|
|
455
529
|
const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks" + where).get(...params);
|
|
456
530
|
const rawMemories = db.prepare("SELECT * FROM chunks" + where + ` ORDER BY created_at ${sortBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
457
531
|
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
|
-
|
|
532
|
+
const chunkIds = rawMemories.map((m) => m.id);
|
|
533
|
+
const sharingMap = new Map();
|
|
534
|
+
if (chunkIds.length > 0) {
|
|
535
|
+
try {
|
|
536
|
+
const placeholders = chunkIds.map(() => "?").join(",");
|
|
537
|
+
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
538
|
+
for (const r of sharedRows)
|
|
539
|
+
sharingMap.set(r.source_chunk_id, r);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
461
542
|
}
|
|
462
|
-
|
|
543
|
+
}
|
|
544
|
+
const memories = rawMemories.map((m) => {
|
|
545
|
+
const out = m.role === "user" && m.content ? { ...m, content: (0, capture_1.stripInboundMetadata)(m.content) } : { ...m };
|
|
546
|
+
if (out.merge_count > 0) {
|
|
463
547
|
const sources = findMergeSources.all(m.id);
|
|
464
|
-
|
|
548
|
+
out.merge_sources = sources;
|
|
465
549
|
}
|
|
466
|
-
|
|
550
|
+
const shared = sharingMap.get(m.id);
|
|
551
|
+
out.sharingVisibility = shared?.visibility ?? null;
|
|
552
|
+
out.sharingGroupId = shared?.group_id ?? null;
|
|
553
|
+
return out;
|
|
467
554
|
});
|
|
468
555
|
this.store.recordViewerEvent("list");
|
|
469
556
|
this.jsonResponse(res, {
|
|
@@ -528,6 +615,7 @@ class ViewerServer {
|
|
|
528
615
|
}));
|
|
529
616
|
const db = this.store.db;
|
|
530
617
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId);
|
|
618
|
+
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
619
|
this.jsonResponse(res, {
|
|
532
620
|
id: task.id,
|
|
533
621
|
sessionKey: task.sessionKey,
|
|
@@ -540,9 +628,11 @@ class ViewerServer {
|
|
|
540
628
|
skillStatus: meta?.skill_status ?? null,
|
|
541
629
|
skillReason: meta?.skill_reason ?? null,
|
|
542
630
|
skillLinks,
|
|
631
|
+
sharingVisibility: sharedTask?.visibility ?? null,
|
|
632
|
+
sharingGroupId: sharedTask?.group_id ?? null,
|
|
543
633
|
});
|
|
544
634
|
}
|
|
545
|
-
serveStats(res) {
|
|
635
|
+
serveStats(res, url) {
|
|
546
636
|
const emptyStats = {
|
|
547
637
|
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
548
638
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
@@ -554,6 +644,7 @@ class ViewerServer {
|
|
|
554
644
|
this.jsonResponse(res, emptyStats);
|
|
555
645
|
return;
|
|
556
646
|
}
|
|
647
|
+
const ownerFilter = url?.searchParams.get("owner") ?? "";
|
|
557
648
|
try {
|
|
558
649
|
const db = this.store.db;
|
|
559
650
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get();
|
|
@@ -572,7 +663,12 @@ class ViewerServer {
|
|
|
572
663
|
embCount = db.prepare("SELECT COUNT(*) as count FROM embeddings").get().count;
|
|
573
664
|
}
|
|
574
665
|
catch { /* table may not exist */ }
|
|
575
|
-
const
|
|
666
|
+
const sessionQuery = ownerFilter
|
|
667
|
+
? "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"
|
|
668
|
+
: "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";
|
|
669
|
+
const sessionList = (ownerFilter
|
|
670
|
+
? db.prepare(sessionQuery).all(ownerFilter)
|
|
671
|
+
: db.prepare(sessionQuery).all());
|
|
576
672
|
let skillCount = 0;
|
|
577
673
|
try {
|
|
578
674
|
skillCount = db.prepare("SELECT COUNT(*) as count FROM skills").get().count;
|
|
@@ -734,8 +830,10 @@ class ViewerServer {
|
|
|
734
830
|
const versions = this.store.getSkillVersions(skillId);
|
|
735
831
|
const relatedTasks = this.store.getTasksBySkill(skillId);
|
|
736
832
|
const files = node_fs_1.default.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
|
|
833
|
+
const db = this.store.db;
|
|
834
|
+
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
835
|
this.jsonResponse(res, {
|
|
738
|
-
skill,
|
|
836
|
+
skill: { ...skill, sharingVisibility: sharedSkill?.visibility ?? null, sharingGroupId: sharedSkill?.group_id ?? null },
|
|
739
837
|
versions: versions.map(v => ({
|
|
740
838
|
id: v.id,
|
|
741
839
|
version: v.version,
|
|
@@ -844,7 +942,7 @@ class ViewerServer {
|
|
|
844
942
|
handleSkillVisibility(req, res, urlPath) {
|
|
845
943
|
const segments = urlPath.split("/");
|
|
846
944
|
const skillId = segments[segments.length - 2];
|
|
847
|
-
this.readBody(req, (body) => {
|
|
945
|
+
this.readBody(req, async (body) => {
|
|
848
946
|
try {
|
|
849
947
|
const parsed = JSON.parse(body);
|
|
850
948
|
const visibility = parsed.visibility;
|
|
@@ -860,7 +958,47 @@ class ViewerServer {
|
|
|
860
958
|
return;
|
|
861
959
|
}
|
|
862
960
|
this.store.setSkillVisibility(skillId, visibility);
|
|
863
|
-
|
|
961
|
+
let hubSynced = false;
|
|
962
|
+
const sharing = this.ctx?.config?.sharing;
|
|
963
|
+
if (sharing?.enabled && this.ctx) {
|
|
964
|
+
try {
|
|
965
|
+
const hubClient = await this.resolveHubClientAware();
|
|
966
|
+
if (visibility === "public") {
|
|
967
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
968
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
969
|
+
method: "POST",
|
|
970
|
+
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
971
|
+
});
|
|
972
|
+
if (hubClient.userId) {
|
|
973
|
+
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
974
|
+
this.store.upsertHubSkill({
|
|
975
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
976
|
+
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
977
|
+
name: skill.name, description: skill.description, version: skill.version,
|
|
978
|
+
groupId: null, visibility: "public",
|
|
979
|
+
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
980
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
hubSynced = true;
|
|
984
|
+
this.log.info(`Skill "${skill.name}" published to Hub`);
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
988
|
+
method: "POST",
|
|
989
|
+
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
990
|
+
});
|
|
991
|
+
if (hubClient.userId)
|
|
992
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
993
|
+
hubSynced = true;
|
|
994
|
+
this.log.info(`Skill "${skill.name}" unpublished from Hub`);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
catch (hubErr) {
|
|
998
|
+
this.log.warn(`Hub sync failed for skill visibility change: ${hubErr}`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, hubSynced });
|
|
864
1002
|
}
|
|
865
1003
|
catch (err) {
|
|
866
1004
|
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
@@ -1071,6 +1209,879 @@ class ViewerServer {
|
|
|
1071
1209
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1072
1210
|
return node_path_1.default.join(home, ".openclaw", "openclaw.json");
|
|
1073
1211
|
}
|
|
1212
|
+
getPluginEntryConfig(raw) {
|
|
1213
|
+
const entries = raw?.plugins?.entries ?? {};
|
|
1214
|
+
return entries["memos-local-openclaw-plugin"]?.config
|
|
1215
|
+
?? entries["memos-lite-openclaw-plugin"]?.config
|
|
1216
|
+
?? entries["memos-lite"]?.config
|
|
1217
|
+
?? {};
|
|
1218
|
+
}
|
|
1219
|
+
getResolvedViewerConfig(raw) {
|
|
1220
|
+
const pluginCfg = this.getPluginEntryConfig(raw);
|
|
1221
|
+
const stateDir = this.ctx?.stateDir ?? this.getOpenClawHome();
|
|
1222
|
+
return (0, config_1.resolveConfig)(pluginCfg, stateDir);
|
|
1223
|
+
}
|
|
1224
|
+
hasUsableEmbeddingProvider(cfg) {
|
|
1225
|
+
const embedding = cfg.embedding;
|
|
1226
|
+
if (!embedding?.provider)
|
|
1227
|
+
return false;
|
|
1228
|
+
if (embedding.provider === "openclaw") {
|
|
1229
|
+
return !!(this.ctx?.openclawAPI) && embedding.capabilities?.hostEmbedding === true;
|
|
1230
|
+
}
|
|
1231
|
+
return true;
|
|
1232
|
+
}
|
|
1233
|
+
hasUsableSummarizerProvider(cfg) {
|
|
1234
|
+
const summarizer = cfg.summarizer;
|
|
1235
|
+
if (!summarizer?.provider)
|
|
1236
|
+
return false;
|
|
1237
|
+
if (summarizer.provider === "openclaw") {
|
|
1238
|
+
return !!(this.ctx?.openclawAPI) && summarizer.capabilities?.hostCompletion === true;
|
|
1239
|
+
}
|
|
1240
|
+
return true;
|
|
1241
|
+
}
|
|
1242
|
+
async serveSharingStatus(res) {
|
|
1243
|
+
const sharing = this.ctx?.config?.sharing;
|
|
1244
|
+
const persisted = this.store.getClientHubConnection();
|
|
1245
|
+
const resolvedHubUrl = sharing?.client?.hubAddress ? (0, hub_1.normalizeHubUrl)(sharing.client.hubAddress) : persisted?.hubUrl ?? null;
|
|
1246
|
+
const hasClientConfig = Boolean((sharing?.client?.hubAddress && sharing?.client?.userToken) ||
|
|
1247
|
+
(persisted?.hubUrl && persisted?.userToken));
|
|
1248
|
+
const base = {
|
|
1249
|
+
enabled: Boolean(sharing?.enabled),
|
|
1250
|
+
role: sharing?.role ?? null,
|
|
1251
|
+
clientConfigured: hasClientConfig,
|
|
1252
|
+
hubUrl: resolvedHubUrl,
|
|
1253
|
+
connection: { connected: false, user: null, hubUrl: undefined, teamName: null, apiVersion: null },
|
|
1254
|
+
admin: { canManageUsers: false, rejectSupported: false },
|
|
1255
|
+
};
|
|
1256
|
+
if (!this.ctx || !sharing?.enabled) {
|
|
1257
|
+
this.jsonResponse(res, base);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
// Hub 模式下,本机就是管理者,直接赋予 admin 权限
|
|
1261
|
+
if (sharing.role === "hub") {
|
|
1262
|
+
base.admin.canManageUsers = true;
|
|
1263
|
+
base.admin.rejectSupported = true;
|
|
1264
|
+
base.connection.connected = true;
|
|
1265
|
+
base.connection.hubUrl = resolvedHubUrl ?? undefined;
|
|
1266
|
+
// 通过 hub API 获取 admin 用户的真实信息(含分组)
|
|
1267
|
+
let adminUser = { username: "hub-admin", role: "admin", groups: [] };
|
|
1268
|
+
try {
|
|
1269
|
+
const hub = this.resolveHubConnection();
|
|
1270
|
+
if (hub) {
|
|
1271
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
1272
|
+
if (me) {
|
|
1273
|
+
adminUser = {
|
|
1274
|
+
id: me.id,
|
|
1275
|
+
username: me.username ?? "hub-admin",
|
|
1276
|
+
role: me.role ?? "admin",
|
|
1277
|
+
groups: Array.isArray(me.groups) ? me.groups : [],
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
catch { /* fallback to default */ }
|
|
1283
|
+
base.connection.user = adminUser;
|
|
1284
|
+
// Fetch team info from own hub
|
|
1285
|
+
try {
|
|
1286
|
+
const selfUrl = resolvedHubUrl || `http://localhost:${sharing.hub?.port ?? 21816}`;
|
|
1287
|
+
const info = await fetch(`${selfUrl}/api/v1/hub/info`).then(r => r.ok ? r.json() : null).catch(() => null);
|
|
1288
|
+
base.connection.teamName = info?.teamName ?? sharing.hub?.teamName ?? null;
|
|
1289
|
+
base.connection.apiVersion = info?.apiVersion ?? null;
|
|
1290
|
+
}
|
|
1291
|
+
catch { /* ignore */ }
|
|
1292
|
+
this.jsonResponse(res, base);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const hasPendingConnection = Boolean(persisted?.hubUrl && persisted?.userId && !persisted?.userToken);
|
|
1296
|
+
if (!hasClientConfig && !hasPendingConnection) {
|
|
1297
|
+
this.jsonResponse(res, base);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
const status = await (0, connector_1.getHubStatus)(this.store, this.ctx.config);
|
|
1302
|
+
const output = { ...base, connection: { ...base.connection, ...status } };
|
|
1303
|
+
if (status.user?.status === "pending") {
|
|
1304
|
+
output.connection.pendingApproval = true;
|
|
1305
|
+
}
|
|
1306
|
+
if (status.user?.status === "rejected") {
|
|
1307
|
+
output.connection.rejected = true;
|
|
1308
|
+
}
|
|
1309
|
+
if (status.connected && status.hubUrl) {
|
|
1310
|
+
try {
|
|
1311
|
+
const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
1312
|
+
output.connection.teamName = info?.teamName ?? null;
|
|
1313
|
+
output.connection.apiVersion = info?.apiVersion ?? null;
|
|
1314
|
+
}
|
|
1315
|
+
catch { }
|
|
1316
|
+
}
|
|
1317
|
+
else if (status.hubUrl) {
|
|
1318
|
+
try {
|
|
1319
|
+
const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
1320
|
+
output.connection.teamName = info?.teamName ?? null;
|
|
1321
|
+
}
|
|
1322
|
+
catch { }
|
|
1323
|
+
}
|
|
1324
|
+
output.admin.canManageUsers = status.connected && status.user?.role === "admin";
|
|
1325
|
+
output.admin.rejectSupported = output.admin.canManageUsers;
|
|
1326
|
+
this.jsonResponse(res, output);
|
|
1327
|
+
}
|
|
1328
|
+
catch (err) {
|
|
1329
|
+
this.jsonResponse(res, { ...base, error: String(err) });
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
async serveSharingPendingUsers(res) {
|
|
1333
|
+
if (!this.ctx)
|
|
1334
|
+
return this.jsonResponse(res, { users: [], error: "sharing_unavailable" });
|
|
1335
|
+
try {
|
|
1336
|
+
const hub = this.resolveHubConnection();
|
|
1337
|
+
if (!hub)
|
|
1338
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
1339
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/pending-users", { method: "GET" });
|
|
1340
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
1341
|
+
}
|
|
1342
|
+
catch (err) {
|
|
1343
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
handleSharingApproveUser(req, res) {
|
|
1347
|
+
this.readBody(req, async (body) => {
|
|
1348
|
+
if (!this.ctx)
|
|
1349
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1350
|
+
try {
|
|
1351
|
+
const parsed = JSON.parse(body || "{}");
|
|
1352
|
+
const hub = this.resolveHubConnection();
|
|
1353
|
+
if (!hub)
|
|
1354
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1355
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/approve-user", {
|
|
1356
|
+
method: "POST",
|
|
1357
|
+
body: JSON.stringify({ userId: parsed.userId, username: parsed.username }),
|
|
1358
|
+
});
|
|
1359
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1360
|
+
}
|
|
1361
|
+
catch (err) {
|
|
1362
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
handleSharingRejectUser(req, res) {
|
|
1367
|
+
this.readBody(req, async (body) => {
|
|
1368
|
+
if (!this.ctx)
|
|
1369
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1370
|
+
try {
|
|
1371
|
+
const parsed = JSON.parse(body || "{}");
|
|
1372
|
+
const hub = this.resolveHubConnection();
|
|
1373
|
+
if (!hub)
|
|
1374
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1375
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/reject-user", {
|
|
1376
|
+
method: "POST",
|
|
1377
|
+
body: JSON.stringify({ userId: parsed.userId }),
|
|
1378
|
+
});
|
|
1379
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1380
|
+
}
|
|
1381
|
+
catch (err) {
|
|
1382
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
handleRetryJoin(req, res) {
|
|
1387
|
+
this.readBody(req, async (_body) => {
|
|
1388
|
+
if (!this.ctx)
|
|
1389
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1390
|
+
const sharing = this.ctx.config.sharing;
|
|
1391
|
+
if (!sharing?.enabled || sharing.role !== "client") {
|
|
1392
|
+
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
|
|
1393
|
+
}
|
|
1394
|
+
const hubAddress = sharing.client?.hubAddress ?? "";
|
|
1395
|
+
const teamToken = sharing.client?.teamToken ?? "";
|
|
1396
|
+
if (!hubAddress || !teamToken) {
|
|
1397
|
+
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
1401
|
+
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
1402
|
+
const username = os.userInfo().username || "user";
|
|
1403
|
+
const hostname = os.hostname() || "unknown";
|
|
1404
|
+
const result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
|
|
1405
|
+
method: "POST",
|
|
1406
|
+
body: JSON.stringify({ teamToken, username, deviceName: hostname }),
|
|
1407
|
+
});
|
|
1408
|
+
this.store.setClientHubConnection({
|
|
1409
|
+
hubUrl,
|
|
1410
|
+
userId: String(result.userId || ""),
|
|
1411
|
+
username,
|
|
1412
|
+
userToken: result.userToken || "",
|
|
1413
|
+
role: "member",
|
|
1414
|
+
connectedAt: Date.now(),
|
|
1415
|
+
});
|
|
1416
|
+
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
1417
|
+
}
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
async serveSharingMemoryList(res, url) {
|
|
1424
|
+
if (!this.ctx)
|
|
1425
|
+
return this.jsonResponse(res, { memories: [], error: "sharing_unavailable" });
|
|
1426
|
+
try {
|
|
1427
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1428
|
+
const data = await (0, hub_1.hubListMemories)(this.store, this.ctx, { limit });
|
|
1429
|
+
this.jsonResponse(res, { memories: Array.isArray(data?.memories) ? data.memories : [] });
|
|
1430
|
+
}
|
|
1431
|
+
catch (err) {
|
|
1432
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
async serveSharingTaskList(res, url) {
|
|
1436
|
+
if (!this.ctx)
|
|
1437
|
+
return this.jsonResponse(res, { tasks: [], error: "sharing_unavailable" });
|
|
1438
|
+
try {
|
|
1439
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1440
|
+
const data = await (0, hub_1.hubListTasks)(this.store, this.ctx, { limit });
|
|
1441
|
+
this.jsonResponse(res, { tasks: Array.isArray(data?.tasks) ? data.tasks : [] });
|
|
1442
|
+
}
|
|
1443
|
+
catch (err) {
|
|
1444
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
async serveSharingSkillList(res, url) {
|
|
1448
|
+
if (!this.ctx)
|
|
1449
|
+
return this.jsonResponse(res, { skills: [], error: "sharing_unavailable" });
|
|
1450
|
+
try {
|
|
1451
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1452
|
+
const data = await (0, hub_1.hubListSkills)(this.store, this.ctx, { limit });
|
|
1453
|
+
this.jsonResponse(res, { skills: Array.isArray(data?.skills) ? data.skills : [] });
|
|
1454
|
+
}
|
|
1455
|
+
catch (err) {
|
|
1456
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
handleSharingMemorySearch(req, res) {
|
|
1460
|
+
this.readBody(req, async (body) => {
|
|
1461
|
+
if (!this.ctx)
|
|
1462
|
+
return this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } }, error: "sharing_unavailable" });
|
|
1463
|
+
const emptyHub = { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } };
|
|
1464
|
+
try {
|
|
1465
|
+
const parsed = JSON.parse(body || "{}");
|
|
1466
|
+
const query = String(parsed.query || "");
|
|
1467
|
+
const role = typeof parsed.role === "string" ? parsed.role : undefined;
|
|
1468
|
+
const maxResults = typeof parsed.maxResults === "number" ? parsed.maxResults : 10;
|
|
1469
|
+
const scope = parsed.scope === "group" || parsed.scope === "all" ? parsed.scope : "local";
|
|
1470
|
+
const local = this.searchLocalViewerMemories(query, { role, maxResults });
|
|
1471
|
+
if (scope === "local") {
|
|
1472
|
+
return this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub });
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
const hub = await (0, hub_1.hubSearchMemories)(this.store, this.ctx, { query, maxResults, scope });
|
|
1476
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub });
|
|
1477
|
+
}
|
|
1478
|
+
catch (err) {
|
|
1479
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub, error: String(err) });
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
catch (err) {
|
|
1483
|
+
this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: emptyHub, error: String(err) });
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
handleSharingMemoryDetail(req, res) {
|
|
1488
|
+
this.readBody(req, async (body) => {
|
|
1489
|
+
if (!this.ctx)
|
|
1490
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
1491
|
+
try {
|
|
1492
|
+
const parsed = JSON.parse(body || "{}");
|
|
1493
|
+
const detail = await (0, hub_1.hubGetMemoryDetail)(this.store, this.ctx, { remoteHitId: String(parsed.remoteHitId || "") });
|
|
1494
|
+
this.jsonResponse(res, detail);
|
|
1495
|
+
}
|
|
1496
|
+
catch (err) {
|
|
1497
|
+
this.jsonResponse(res, { error: String(err) });
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
async serveSharingSkillSearch(res, url) {
|
|
1502
|
+
if (!this.ctx)
|
|
1503
|
+
return this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: "sharing_unavailable" });
|
|
1504
|
+
try {
|
|
1505
|
+
const query = String(url.searchParams.get("query") || "");
|
|
1506
|
+
const scope = url.searchParams.get("scope") === "group" || url.searchParams.get("scope") === "all" ? url.searchParams.get("scope") : "local";
|
|
1507
|
+
const recall = new engine_1.RecallEngine(this.store, this.embedder, this.ctx);
|
|
1508
|
+
const localHits = await recall.searchSkills(query, "mix", "agent:main");
|
|
1509
|
+
if (scope === "local") {
|
|
1510
|
+
return this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] } });
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
const hub = await (0, hub_1.hubSearchSkills)(this.store, this.ctx, { query, maxResults: Number(url.searchParams.get("maxResults") || 20) });
|
|
1514
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub });
|
|
1515
|
+
}
|
|
1516
|
+
catch (err) {
|
|
1517
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] }, error: String(err) });
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
catch (err) {
|
|
1521
|
+
this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: String(err) });
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
searchLocalViewerMemories(query, options) {
|
|
1525
|
+
const db = this.store.db;
|
|
1526
|
+
const role = options?.role;
|
|
1527
|
+
const maxResults = options?.maxResults ?? 10;
|
|
1528
|
+
const params = [];
|
|
1529
|
+
let rows = [];
|
|
1530
|
+
try {
|
|
1531
|
+
let sql = "SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ?";
|
|
1532
|
+
params.push(query);
|
|
1533
|
+
if (role) {
|
|
1534
|
+
sql += " AND c.role = ?";
|
|
1535
|
+
params.push(role);
|
|
1536
|
+
}
|
|
1537
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
1538
|
+
params.push(maxResults);
|
|
1539
|
+
rows = db.prepare(sql).all(...params);
|
|
1540
|
+
}
|
|
1541
|
+
catch {
|
|
1542
|
+
const likeParams = [`%${query}%`, `%${query}%`];
|
|
1543
|
+
let sql = "SELECT * FROM chunks WHERE (content LIKE ? OR summary LIKE ?)";
|
|
1544
|
+
if (role) {
|
|
1545
|
+
sql += " AND role = ?";
|
|
1546
|
+
likeParams.push(role);
|
|
1547
|
+
}
|
|
1548
|
+
sql += " ORDER BY created_at DESC LIMIT ?";
|
|
1549
|
+
likeParams.push(maxResults);
|
|
1550
|
+
rows = db.prepare(sql).all(...likeParams);
|
|
1551
|
+
}
|
|
1552
|
+
const hits = rows.map((row, idx) => ({
|
|
1553
|
+
id: row.id,
|
|
1554
|
+
summary: row.summary || row.content?.slice(0, 120) || "",
|
|
1555
|
+
excerpt: row.content || "",
|
|
1556
|
+
score: Math.max(0.3, 1 - idx * 0.1),
|
|
1557
|
+
role: row.role,
|
|
1558
|
+
ref: { sessionKey: row.session_key, chunkId: row.id, turnId: row.turn_id, seq: row.seq },
|
|
1559
|
+
taskId: row.task_id ?? null,
|
|
1560
|
+
skillId: row.skill_id ?? null,
|
|
1561
|
+
}));
|
|
1562
|
+
return { hits, meta: { total: hits.length, usedMaxResults: maxResults } };
|
|
1563
|
+
}
|
|
1564
|
+
handleSharingTaskShare(req, res) {
|
|
1565
|
+
this.readBody(req, async (body) => {
|
|
1566
|
+
if (!this.ctx)
|
|
1567
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1568
|
+
try {
|
|
1569
|
+
const parsed = JSON.parse(body || "{}");
|
|
1570
|
+
const taskId = String(parsed.taskId || "");
|
|
1571
|
+
const visibility = "public";
|
|
1572
|
+
const groupId = undefined;
|
|
1573
|
+
const task = this.store.getTask(taskId);
|
|
1574
|
+
if (!task)
|
|
1575
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
1576
|
+
const chunks = this.store.getChunksByTask(taskId);
|
|
1577
|
+
if (chunks.length === 0)
|
|
1578
|
+
return this.jsonResponse(res, { ok: false, error: "no_chunks" });
|
|
1579
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1580
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1581
|
+
method: "POST",
|
|
1582
|
+
body: JSON.stringify({
|
|
1583
|
+
task: {
|
|
1584
|
+
id: task.id,
|
|
1585
|
+
sourceTaskId: task.id,
|
|
1586
|
+
title: task.title,
|
|
1587
|
+
summary: task.summary,
|
|
1588
|
+
groupId: null,
|
|
1589
|
+
visibility,
|
|
1590
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
1591
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
1592
|
+
},
|
|
1593
|
+
chunks: chunks.map((chunk) => ({
|
|
1594
|
+
id: chunk.id,
|
|
1595
|
+
hubTaskId: task.id,
|
|
1596
|
+
sourceTaskId: task.id,
|
|
1597
|
+
sourceChunkId: chunk.id,
|
|
1598
|
+
role: chunk.role,
|
|
1599
|
+
content: chunk.content,
|
|
1600
|
+
summary: chunk.summary,
|
|
1601
|
+
kind: chunk.kind,
|
|
1602
|
+
createdAt: chunk.createdAt,
|
|
1603
|
+
})),
|
|
1604
|
+
}),
|
|
1605
|
+
});
|
|
1606
|
+
const hubUserId = hubClient.userId;
|
|
1607
|
+
if (hubUserId) {
|
|
1608
|
+
this.store.upsertHubTask({
|
|
1609
|
+
id: task.id,
|
|
1610
|
+
sourceTaskId: task.id,
|
|
1611
|
+
sourceUserId: hubUserId,
|
|
1612
|
+
title: task.title,
|
|
1613
|
+
summary: task.summary,
|
|
1614
|
+
groupId: null,
|
|
1615
|
+
visibility,
|
|
1616
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
1617
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
this.jsonResponse(res, { ok: true, taskId, visibility, response });
|
|
1621
|
+
}
|
|
1622
|
+
catch (err) {
|
|
1623
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
handleSharingTaskUnshare(req, res) {
|
|
1628
|
+
this.readBody(req, async (body) => {
|
|
1629
|
+
if (!this.ctx)
|
|
1630
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1631
|
+
try {
|
|
1632
|
+
const parsed = JSON.parse(body || "{}");
|
|
1633
|
+
const taskId = String(parsed.taskId || "");
|
|
1634
|
+
const task = this.store.getTask(taskId);
|
|
1635
|
+
if (!task)
|
|
1636
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
1637
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1638
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1639
|
+
method: "POST",
|
|
1640
|
+
body: JSON.stringify({ sourceTaskId: task.id }),
|
|
1641
|
+
});
|
|
1642
|
+
const hubUserId = hubClient.userId;
|
|
1643
|
+
if (hubUserId)
|
|
1644
|
+
this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
1645
|
+
this.jsonResponse(res, { ok: true, taskId });
|
|
1646
|
+
}
|
|
1647
|
+
catch (err) {
|
|
1648
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
handleSharingMemoryShare(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 visibility = "public";
|
|
1660
|
+
const groupId = undefined;
|
|
1661
|
+
const db = this.store.db;
|
|
1662
|
+
const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1663
|
+
if (!chunk)
|
|
1664
|
+
return this.jsonResponse(res, { ok: false, error: "memory_not_found" });
|
|
1665
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1666
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
1667
|
+
method: "POST",
|
|
1668
|
+
body: JSON.stringify({
|
|
1669
|
+
memory: {
|
|
1670
|
+
sourceChunkId: chunk.id,
|
|
1671
|
+
role: chunk.role,
|
|
1672
|
+
content: chunk.content,
|
|
1673
|
+
summary: chunk.summary,
|
|
1674
|
+
kind: chunk.kind,
|
|
1675
|
+
groupId: null,
|
|
1676
|
+
visibility,
|
|
1677
|
+
},
|
|
1678
|
+
}),
|
|
1679
|
+
});
|
|
1680
|
+
const hubUserId = hubClient.userId;
|
|
1681
|
+
if (hubUserId) {
|
|
1682
|
+
const now = Date.now();
|
|
1683
|
+
const existing = this.store.getHubMemoryBySource(hubUserId, chunk.id);
|
|
1684
|
+
this.store.upsertHubMemory({
|
|
1685
|
+
id: response?.memoryId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1686
|
+
sourceChunkId: chunk.id,
|
|
1687
|
+
sourceUserId: hubUserId,
|
|
1688
|
+
role: chunk.role,
|
|
1689
|
+
content: chunk.content,
|
|
1690
|
+
summary: chunk.summary ?? "",
|
|
1691
|
+
kind: chunk.kind,
|
|
1692
|
+
groupId: null,
|
|
1693
|
+
visibility,
|
|
1694
|
+
createdAt: existing?.createdAt ?? now,
|
|
1695
|
+
updatedAt: now,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
this.jsonResponse(res, { ok: true, chunkId, visibility, response });
|
|
1699
|
+
}
|
|
1700
|
+
catch (err) {
|
|
1701
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
handleSharingMemoryUnshare(req, res) {
|
|
1706
|
+
this.readBody(req, async (body) => {
|
|
1707
|
+
if (!this.ctx)
|
|
1708
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1709
|
+
try {
|
|
1710
|
+
const parsed = JSON.parse(body || "{}");
|
|
1711
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1712
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1713
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1714
|
+
method: "POST",
|
|
1715
|
+
body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1716
|
+
});
|
|
1717
|
+
const hubUserId = hubClient.userId;
|
|
1718
|
+
if (hubUserId)
|
|
1719
|
+
this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
1720
|
+
this.jsonResponse(res, { ok: true, chunkId });
|
|
1721
|
+
}
|
|
1722
|
+
catch (err) {
|
|
1723
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
handleSharingSkillPull(req, res) {
|
|
1728
|
+
this.readBody(req, async (body) => {
|
|
1729
|
+
if (!this.ctx)
|
|
1730
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1731
|
+
try {
|
|
1732
|
+
const parsed = JSON.parse(body || "{}");
|
|
1733
|
+
const skillId = String(parsed.skillId || "");
|
|
1734
|
+
const payload = await (0, skill_sync_1.fetchHubSkillBundle)(this.store, this.ctx, { skillId });
|
|
1735
|
+
const restored = (0, skill_sync_1.restoreSkillBundleFromHub)(this.store, this.ctx, payload);
|
|
1736
|
+
this.jsonResponse(res, { ok: true, pulled: true, hubSkillId: skillId, ...restored });
|
|
1737
|
+
}
|
|
1738
|
+
catch (err) {
|
|
1739
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
handleSharingSkillShare(req, res) {
|
|
1744
|
+
this.readBody(req, async (body) => {
|
|
1745
|
+
if (!this.ctx)
|
|
1746
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1747
|
+
try {
|
|
1748
|
+
const parsed = JSON.parse(body || "{}");
|
|
1749
|
+
const skillId = String(parsed.skillId || "");
|
|
1750
|
+
const visibility = "public";
|
|
1751
|
+
const groupId = null;
|
|
1752
|
+
const skill = this.store.getSkill(skillId);
|
|
1753
|
+
if (!skill)
|
|
1754
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
1755
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
1756
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1757
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
1758
|
+
method: "POST",
|
|
1759
|
+
body: JSON.stringify({
|
|
1760
|
+
visibility,
|
|
1761
|
+
groupId: null,
|
|
1762
|
+
metadata: bundle.metadata,
|
|
1763
|
+
bundle: bundle.bundle,
|
|
1764
|
+
}),
|
|
1765
|
+
});
|
|
1766
|
+
const hubUserId = hubClient.userId;
|
|
1767
|
+
if (hubUserId) {
|
|
1768
|
+
const existing = this.store.getHubSkillBySource(hubUserId, skillId);
|
|
1769
|
+
this.store.upsertHubSkill({
|
|
1770
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1771
|
+
sourceSkillId: skillId,
|
|
1772
|
+
sourceUserId: hubUserId,
|
|
1773
|
+
name: skill.name,
|
|
1774
|
+
description: skill.description,
|
|
1775
|
+
version: skill.version,
|
|
1776
|
+
groupId: null,
|
|
1777
|
+
visibility,
|
|
1778
|
+
bundle: JSON.stringify(bundle.bundle),
|
|
1779
|
+
qualityScore: skill.qualityScore,
|
|
1780
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
1781
|
+
updatedAt: Date.now(),
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, response });
|
|
1785
|
+
}
|
|
1786
|
+
catch (err) {
|
|
1787
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
handleSharingSkillUnshare(req, res) {
|
|
1792
|
+
this.readBody(req, async (body) => {
|
|
1793
|
+
if (!this.ctx)
|
|
1794
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1795
|
+
try {
|
|
1796
|
+
const parsed = JSON.parse(body || "{}");
|
|
1797
|
+
const skillId = String(parsed.skillId || "");
|
|
1798
|
+
const skill = this.store.getSkill(skillId);
|
|
1799
|
+
if (!skill)
|
|
1800
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
1801
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1802
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1803
|
+
method: "POST",
|
|
1804
|
+
body: JSON.stringify({ sourceSkillId: skill.id }),
|
|
1805
|
+
});
|
|
1806
|
+
const hubUserId = hubClient.userId;
|
|
1807
|
+
if (hubUserId)
|
|
1808
|
+
this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
1809
|
+
this.jsonResponse(res, { ok: true, skillId });
|
|
1810
|
+
}
|
|
1811
|
+
catch (err) {
|
|
1812
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
resolveHubConnection() {
|
|
1817
|
+
if (!this.ctx)
|
|
1818
|
+
return null;
|
|
1819
|
+
// Hub 模式:连接自己,用 bootstrap admin token
|
|
1820
|
+
const sharing = this.ctx.config.sharing;
|
|
1821
|
+
if (sharing?.role === "hub") {
|
|
1822
|
+
const hubPort = sharing.hub?.port ?? 18800;
|
|
1823
|
+
const hubUrl = `http://127.0.0.1:${hubPort}`;
|
|
1824
|
+
try {
|
|
1825
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
1826
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
1827
|
+
const adminToken = authData?.bootstrapAdminToken;
|
|
1828
|
+
if (adminToken)
|
|
1829
|
+
return { hubUrl, userToken: adminToken };
|
|
1830
|
+
}
|
|
1831
|
+
catch {
|
|
1832
|
+
// hub-auth.json 不存在或读取失败,fall through
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
// Client 模式:用配置的 hubAddress + userToken
|
|
1836
|
+
const conn = this.store.getClientHubConnection();
|
|
1837
|
+
const hubUrl = conn?.hubUrl || this.ctx.config.sharing?.client?.hubAddress || "";
|
|
1838
|
+
const userToken = conn?.userToken || this.ctx.config.sharing?.client?.userToken || "";
|
|
1839
|
+
if (!hubUrl || !userToken)
|
|
1840
|
+
return null;
|
|
1841
|
+
return { hubUrl: (0, hub_1.normalizeHubUrl)(hubUrl), userToken };
|
|
1842
|
+
}
|
|
1843
|
+
/** resolveHubClient 的 viewer 版本:hub 模式下使用 bootstrap admin 身份 */
|
|
1844
|
+
async resolveHubClientAware() {
|
|
1845
|
+
if (!this.ctx)
|
|
1846
|
+
throw new Error("sharing_unavailable");
|
|
1847
|
+
const sharing = this.ctx.config.sharing;
|
|
1848
|
+
if (sharing?.role === "hub") {
|
|
1849
|
+
const hub = this.resolveHubConnection();
|
|
1850
|
+
if (hub) {
|
|
1851
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
1852
|
+
return {
|
|
1853
|
+
hubUrl: hub.hubUrl,
|
|
1854
|
+
userToken: hub.userToken,
|
|
1855
|
+
userId: String(me.id),
|
|
1856
|
+
username: String(me.username ?? "hub-admin"),
|
|
1857
|
+
role: String(me.role ?? "admin"),
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return (0, hub_1.resolveHubClient)(this.store, this.ctx);
|
|
1862
|
+
}
|
|
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
|
+
async serveSharingUsers(res) {
|
|
1981
|
+
const hub = this.resolveHubConnection();
|
|
1982
|
+
if (!hub)
|
|
1983
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
1984
|
+
try {
|
|
1985
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/users", { method: "GET" });
|
|
1986
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
1987
|
+
}
|
|
1988
|
+
catch (err) {
|
|
1989
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
// ─── Admin management endpoints (Hub-side data) ───
|
|
1993
|
+
async serveAdminSharedTasks(res) {
|
|
1994
|
+
const hub = this.resolveHubConnection();
|
|
1995
|
+
if (!hub)
|
|
1996
|
+
return this.jsonResponse(res, { tasks: [], error: "not_configured" });
|
|
1997
|
+
try {
|
|
1998
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-tasks", { method: "GET" });
|
|
1999
|
+
this.jsonResponse(res, { tasks: Array.isArray(data?.tasks) ? data.tasks : [] });
|
|
2000
|
+
}
|
|
2001
|
+
catch (err) {
|
|
2002
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
async handleAdminDeleteTask(res, p) {
|
|
2006
|
+
const hub = this.resolveHubConnection();
|
|
2007
|
+
if (!hub)
|
|
2008
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2009
|
+
const taskId = decodeURIComponent(p.replace("/api/admin/shared-tasks/", ""));
|
|
2010
|
+
try {
|
|
2011
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-tasks/${encodeURIComponent(taskId)}`, { method: "DELETE" });
|
|
2012
|
+
this.jsonResponse(res, { ok: true });
|
|
2013
|
+
}
|
|
2014
|
+
catch (err) {
|
|
2015
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
async serveAdminSharedSkills(res) {
|
|
2019
|
+
const hub = this.resolveHubConnection();
|
|
2020
|
+
if (!hub)
|
|
2021
|
+
return this.jsonResponse(res, { skills: [], error: "not_configured" });
|
|
2022
|
+
try {
|
|
2023
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-skills", { method: "GET" });
|
|
2024
|
+
this.jsonResponse(res, { skills: Array.isArray(data?.skills) ? data.skills : [] });
|
|
2025
|
+
}
|
|
2026
|
+
catch (err) {
|
|
2027
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
async handleAdminDeleteSkill(res, p) {
|
|
2031
|
+
const hub = this.resolveHubConnection();
|
|
2032
|
+
if (!hub)
|
|
2033
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2034
|
+
const skillId = decodeURIComponent(p.replace("/api/admin/shared-skills/", ""));
|
|
2035
|
+
try {
|
|
2036
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-skills/${encodeURIComponent(skillId)}`, { method: "DELETE" });
|
|
2037
|
+
this.jsonResponse(res, { ok: true });
|
|
2038
|
+
}
|
|
2039
|
+
catch (err) {
|
|
2040
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
async serveAdminSharedMemories(res) {
|
|
2044
|
+
const hub = this.resolveHubConnection();
|
|
2045
|
+
if (!hub)
|
|
2046
|
+
return this.jsonResponse(res, { memories: [], error: "not_configured" });
|
|
2047
|
+
try {
|
|
2048
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-memories", { method: "GET" });
|
|
2049
|
+
this.jsonResponse(res, { memories: Array.isArray(data?.memories) ? data.memories : [] });
|
|
2050
|
+
}
|
|
2051
|
+
catch (err) {
|
|
2052
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
async handleAdminDeleteMemory(res, p) {
|
|
2056
|
+
const hub = this.resolveHubConnection();
|
|
2057
|
+
if (!hub)
|
|
2058
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2059
|
+
const memoryId = decodeURIComponent(p.replace("/api/admin/shared-memories/", ""));
|
|
2060
|
+
try {
|
|
2061
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-memories/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
|
|
2062
|
+
this.jsonResponse(res, { ok: true });
|
|
2063
|
+
}
|
|
2064
|
+
catch (err) {
|
|
2065
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
getLocalIPs() {
|
|
2069
|
+
const nets = node_os_1.default.networkInterfaces();
|
|
2070
|
+
const ips = [];
|
|
2071
|
+
for (const name of Object.keys(nets)) {
|
|
2072
|
+
for (const net of nets[name] ?? []) {
|
|
2073
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
2074
|
+
ips.push(net.address);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return ips;
|
|
2079
|
+
}
|
|
2080
|
+
serveLocalIPs(res) {
|
|
2081
|
+
const ips = this.getLocalIPs();
|
|
2082
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2083
|
+
res.end(JSON.stringify({ ips }));
|
|
2084
|
+
}
|
|
1074
2085
|
serveConfig(res) {
|
|
1075
2086
|
try {
|
|
1076
2087
|
const cfgPath = this.getOpenClawConfigPath();
|
|
@@ -1091,7 +2102,10 @@ class ViewerServer {
|
|
|
1091
2102
|
?? entries["memos-lite-openclaw-plugin"]
|
|
1092
2103
|
?? entries["memos-lite"]
|
|
1093
2104
|
?? {};
|
|
1094
|
-
if (pluginEntry.viewerPort
|
|
2105
|
+
if (pluginEntry.viewerPort != null) {
|
|
2106
|
+
result.viewerPort = pluginEntry.viewerPort;
|
|
2107
|
+
}
|
|
2108
|
+
else if (topEntry.viewerPort) {
|
|
1095
2109
|
result.viewerPort = topEntry.viewerPort;
|
|
1096
2110
|
}
|
|
1097
2111
|
this.jsonResponse(res, result);
|
|
@@ -1137,6 +2151,37 @@ class ViewerServer {
|
|
|
1137
2151
|
config.viewerPort = newCfg.viewerPort;
|
|
1138
2152
|
if (newCfg.telemetry !== undefined)
|
|
1139
2153
|
config.telemetry = newCfg.telemetry;
|
|
2154
|
+
if (newCfg.sharing !== undefined) {
|
|
2155
|
+
const existing = config.sharing || {};
|
|
2156
|
+
const merged = { ...existing, ...newCfg.sharing };
|
|
2157
|
+
if (newCfg.sharing.capabilities && existing.capabilities) {
|
|
2158
|
+
merged.capabilities = { ...existing.capabilities, ...newCfg.sharing.capabilities };
|
|
2159
|
+
}
|
|
2160
|
+
if (merged.role === "client" && merged.client) {
|
|
2161
|
+
const clientCfg = merged.client;
|
|
2162
|
+
const addr = String(clientCfg.hubAddress || "");
|
|
2163
|
+
if (addr) {
|
|
2164
|
+
const localIPs = this.getLocalIPs();
|
|
2165
|
+
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2166
|
+
try {
|
|
2167
|
+
const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`);
|
|
2168
|
+
if (localIPs.includes(u.hostname)) {
|
|
2169
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2170
|
+
res.end(JSON.stringify({ error: "cannot_join_self" }));
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
catch { }
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (merged.role === "hub") {
|
|
2178
|
+
merged.client = { hubAddress: "", userToken: "", teamToken: "" };
|
|
2179
|
+
}
|
|
2180
|
+
else if (merged.role === "client") {
|
|
2181
|
+
merged.hub = { port: 18800, teamName: "", teamToken: "" };
|
|
2182
|
+
}
|
|
2183
|
+
config.sharing = merged;
|
|
2184
|
+
}
|
|
1140
2185
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(cfgPath), { recursive: true });
|
|
1141
2186
|
node_fs_1.default.writeFileSync(cfgPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
1142
2187
|
this.log.info("Plugin config updated via Viewer");
|
|
@@ -1149,6 +2194,99 @@ class ViewerServer {
|
|
|
1149
2194
|
}
|
|
1150
2195
|
});
|
|
1151
2196
|
}
|
|
2197
|
+
handleUpdateUsername(req, res) {
|
|
2198
|
+
this.readBody(req, async (body) => {
|
|
2199
|
+
if (!this.ctx)
|
|
2200
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
2201
|
+
try {
|
|
2202
|
+
const { username } = JSON.parse(body || "{}");
|
|
2203
|
+
if (!username || typeof username !== "string" || username.trim().length < 2 || username.trim().length > 32) {
|
|
2204
|
+
return this.jsonResponse(res, { error: "invalid_username" }, 400);
|
|
2205
|
+
}
|
|
2206
|
+
const trimmed = username.trim();
|
|
2207
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2208
|
+
const result = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/me/update-profile", {
|
|
2209
|
+
method: "POST",
|
|
2210
|
+
body: JSON.stringify({ username: trimmed }),
|
|
2211
|
+
});
|
|
2212
|
+
if (result.ok && result.userToken) {
|
|
2213
|
+
const sharing = this.ctx.config.sharing;
|
|
2214
|
+
if (sharing?.role === "hub") {
|
|
2215
|
+
try {
|
|
2216
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
2217
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
2218
|
+
authData.bootstrapAdminToken = result.userToken;
|
|
2219
|
+
node_fs_1.default.writeFileSync(authPath, JSON.stringify(authData, null, 2), "utf-8");
|
|
2220
|
+
this.log.info("hub-auth.json updated with new admin token after username change");
|
|
2221
|
+
}
|
|
2222
|
+
catch (e) {
|
|
2223
|
+
this.log.warn(`Failed to update hub-auth.json: ${e}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
else {
|
|
2227
|
+
const persisted = this.store.getClientHubConnection();
|
|
2228
|
+
if (persisted) {
|
|
2229
|
+
this.store.setClientHubConnection({
|
|
2230
|
+
...persisted,
|
|
2231
|
+
username: result.username,
|
|
2232
|
+
userToken: result.userToken,
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
this.jsonResponse(res, result);
|
|
2238
|
+
}
|
|
2239
|
+
catch (err) {
|
|
2240
|
+
const msg = String(err?.message || err);
|
|
2241
|
+
if (msg.includes("409") || msg.includes("username_taken")) {
|
|
2242
|
+
return this.jsonResponse(res, { error: "username_taken" }, 409);
|
|
2243
|
+
}
|
|
2244
|
+
this.jsonResponse(res, { error: msg }, 500);
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
handleTestHubConnection(req, res) {
|
|
2249
|
+
this.readBody(req, async (body) => {
|
|
2250
|
+
try {
|
|
2251
|
+
const { hubUrl } = JSON.parse(body);
|
|
2252
|
+
if (!hubUrl) {
|
|
2253
|
+
this.jsonResponse(res, { ok: false, error: "hubUrl is required" });
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
try {
|
|
2257
|
+
const localIPs = this.getLocalIPs();
|
|
2258
|
+
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2259
|
+
const parsed = new URL(hubUrl.startsWith("http") ? hubUrl : `http://${hubUrl}`);
|
|
2260
|
+
if (localIPs.includes(parsed.hostname)) {
|
|
2261
|
+
this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
catch { }
|
|
2266
|
+
const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
|
|
2267
|
+
const ctrl = new AbortController();
|
|
2268
|
+
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
2269
|
+
try {
|
|
2270
|
+
const r = await fetch(url, { signal: ctrl.signal });
|
|
2271
|
+
clearTimeout(timeout);
|
|
2272
|
+
if (!r.ok) {
|
|
2273
|
+
this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const info = await r.json();
|
|
2277
|
+
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
2278
|
+
}
|
|
2279
|
+
catch (e) {
|
|
2280
|
+
clearTimeout(timeout);
|
|
2281
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2282
|
+
this.jsonResponse(res, { ok: false, error: msg.includes("abort") ? "Connection timeout (8s)" : msg });
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
catch (e) {
|
|
2286
|
+
this.jsonResponse(res, { ok: false, error: String(e) });
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
1152
2290
|
handleTestModel(req, res) {
|
|
1153
2291
|
this.readBody(req, async (body) => {
|
|
1154
2292
|
try {
|
|
@@ -2460,8 +3598,8 @@ class ViewerServer {
|
|
|
2460
3598
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
2461
3599
|
req.on("end", () => cb(body));
|
|
2462
3600
|
}
|
|
2463
|
-
jsonResponse(res, data) {
|
|
2464
|
-
res.writeHead(
|
|
3601
|
+
jsonResponse(res, data, statusCode = 200) {
|
|
3602
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
2465
3603
|
res.end(JSON.stringify(data));
|
|
2466
3604
|
}
|
|
2467
3605
|
}
|