@memtensor/memos-local-openclaw-plugin 1.0.4-beta.6 → 1.0.4-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -22
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +27 -7
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +29 -0
- package/dist/client/connector.d.ts.map +1 -0
- package/dist/client/connector.js +218 -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 +170 -0
- package/dist/client/hub.js.map +1 -0
- package/dist/client/skill-sync.d.ts +36 -0
- package/dist/client/skill-sync.d.ts.map +1 -0
- package/dist/client/skill-sync.js +226 -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 +72 -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 +767 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/user-manager.d.ts +31 -0
- package/dist/hub/user-manager.d.ts.map +1 -0
- package/dist/hub/user-manager.js +129 -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 +8 -4
- 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 +209 -43
- 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 -2
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +20 -81
- 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/evolver.d.ts +0 -2
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +0 -3
- package/dist/skill/evolver.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/ensure-binding.d.ts.map +1 -1
- package/dist/storage/ensure-binding.js +3 -1
- package/dist/storage/ensure-binding.js.map +1 -1
- package/dist/storage/sqlite.d.ts +329 -1
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +909 -4
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts +5 -12
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +38 -135
- package/dist/telemetry.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 +5 -2
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +50 -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 +49 -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 +3965 -459
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +51 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1564 -23
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +769 -67
- package/openclaw.plugin.json +2 -1
- package/package.json +4 -3
- package/scripts/postinstall.cjs +283 -46
- package/skill/memos-memory-guide/SKILL.md +82 -20
- package/src/capture/index.ts +27 -7
- package/src/client/connector.ts +212 -0
- package/src/client/hub.ts +207 -0
- package/src/client/skill-sync.ts +216 -0
- package/src/config.ts +94 -3
- package/src/embedding/index.ts +21 -1
- package/src/hub/auth.ts +78 -0
- package/src/hub/server.ts +754 -0
- package/src/hub/user-manager.ts +143 -0
- package/src/index.ts +13 -5
- package/src/ingest/providers/index.ts +246 -46
- 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 +23 -95
- 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/evolver.ts +0 -5
- package/src/skill/generator.ts +6 -4
- package/src/skill/upgrader.ts +1 -1
- package/src/skill/validator.ts +1 -1
- package/src/storage/ensure-binding.ts +3 -1
- package/src/storage/sqlite.ts +1159 -4
- package/src/telemetry.ts +39 -152
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-search.ts +58 -8
- package/src/tools/network-memory-detail.ts +34 -0
- package/src/types.ts +44 -2
- package/src/viewer/html.ts +3965 -459
- package/src/viewer/server.ts +1452 -25
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) {
|
|
@@ -224,8 +228,18 @@ class ViewerServer {
|
|
|
224
228
|
}
|
|
225
229
|
if (p === "/api/memories" && req.method === "GET")
|
|
226
230
|
this.serveMemories(res, url);
|
|
231
|
+
else if (p === "/api/memories/share-local" && req.method === "POST")
|
|
232
|
+
this.handleMemoryLocalShare(req, res);
|
|
233
|
+
else if (p === "/api/memories/unshare-local" && req.method === "POST")
|
|
234
|
+
this.handleMemoryLocalUnshare(req, res);
|
|
235
|
+
else if (p.match(/^\/api\/memory\/[^/]+\/scope$/) && req.method === "PUT")
|
|
236
|
+
this.handleMemoryScope(req, res, p);
|
|
237
|
+
else if (p.match(/^\/api\/task\/[^/]+\/scope$/) && req.method === "PUT")
|
|
238
|
+
this.handleTaskScope(req, res, p);
|
|
239
|
+
else if (p.match(/^\/api\/skill\/[^/]+\/scope$/) && req.method === "PUT")
|
|
240
|
+
this.handleSkillScope(req, res, p);
|
|
227
241
|
else if (p === "/api/stats")
|
|
228
|
-
this.serveStats(res);
|
|
242
|
+
this.serveStats(res, url);
|
|
229
243
|
else if (p === "/api/metrics")
|
|
230
244
|
this.serveMetrics(res, url);
|
|
231
245
|
else if (p === "/api/tool-metrics")
|
|
@@ -270,6 +284,74 @@ class ViewerServer {
|
|
|
270
284
|
this.serveLogs(res, url);
|
|
271
285
|
else if (p === "/api/log-tools" && req.method === "GET")
|
|
272
286
|
this.serveLogTools(res);
|
|
287
|
+
else if (p === "/api/sharing/status" && req.method === "GET")
|
|
288
|
+
this.serveSharingStatus(res);
|
|
289
|
+
else if (p === "/api/sharing/pending-users" && req.method === "GET")
|
|
290
|
+
this.serveSharingPendingUsers(res);
|
|
291
|
+
else if (p === "/api/sharing/approve-user" && req.method === "POST")
|
|
292
|
+
this.handleSharingApproveUser(req, res);
|
|
293
|
+
else if (p === "/api/sharing/reject-user" && req.method === "POST")
|
|
294
|
+
this.handleSharingRejectUser(req, res);
|
|
295
|
+
else if (p === "/api/sharing/remove-user" && req.method === "POST")
|
|
296
|
+
this.handleSharingRemoveUser(req, res);
|
|
297
|
+
else if (p === "/api/sharing/change-role" && req.method === "POST")
|
|
298
|
+
this.handleSharingChangeRole(req, res);
|
|
299
|
+
else if (p === "/api/sharing/retry-join" && req.method === "POST")
|
|
300
|
+
this.handleRetryJoin(req, res);
|
|
301
|
+
else if (p === "/api/sharing/search/memories" && req.method === "POST")
|
|
302
|
+
this.handleSharingMemorySearch(req, res);
|
|
303
|
+
else if (p === "/api/sharing/memories/list" && req.method === "GET")
|
|
304
|
+
this.serveSharingMemoryList(res, url);
|
|
305
|
+
else if (p === "/api/sharing/tasks/list" && req.method === "GET")
|
|
306
|
+
this.serveSharingTaskList(res, url);
|
|
307
|
+
else if (p === "/api/sharing/skills/list" && req.method === "GET")
|
|
308
|
+
this.serveSharingSkillList(res, url);
|
|
309
|
+
else if (p === "/api/sharing/memory-detail" && req.method === "POST")
|
|
310
|
+
this.handleSharingMemoryDetail(req, res);
|
|
311
|
+
else if (p === "/api/sharing/search/skills" && req.method === "GET")
|
|
312
|
+
this.serveSharingSkillSearch(res, url);
|
|
313
|
+
else if (p === "/api/sharing/tasks/share" && req.method === "POST")
|
|
314
|
+
this.handleSharingTaskShare(req, res);
|
|
315
|
+
else if (p === "/api/sharing/tasks/unshare" && req.method === "POST")
|
|
316
|
+
this.handleSharingTaskUnshare(req, res);
|
|
317
|
+
else if (p === "/api/sharing/update-username" && req.method === "POST")
|
|
318
|
+
this.handleUpdateUsername(req, res);
|
|
319
|
+
else if (p === "/api/sharing/rename-user" && req.method === "POST")
|
|
320
|
+
this.handleAdminRenameUser(req, res);
|
|
321
|
+
else if (p === "/api/sharing/test-hub" && req.method === "POST")
|
|
322
|
+
this.handleTestHubConnection(req, res);
|
|
323
|
+
else if (p === "/api/sharing/memories/share" && req.method === "POST")
|
|
324
|
+
this.handleSharingMemoryShare(req, res);
|
|
325
|
+
else if (p === "/api/sharing/memories/unshare" && req.method === "POST")
|
|
326
|
+
this.handleSharingMemoryUnshare(req, res);
|
|
327
|
+
else if (p === "/api/sharing/skills/pull" && req.method === "POST")
|
|
328
|
+
this.handleSharingSkillPull(req, res);
|
|
329
|
+
else if (p === "/api/sharing/skills/share" && req.method === "POST")
|
|
330
|
+
this.handleSharingSkillShare(req, res);
|
|
331
|
+
else if (p === "/api/sharing/skills/unshare" && req.method === "POST")
|
|
332
|
+
this.handleSharingSkillUnshare(req, res);
|
|
333
|
+
else if (p === "/api/sharing/users" && req.method === "GET")
|
|
334
|
+
this.serveSharingUsers(res);
|
|
335
|
+
else if (p === "/api/sharing/notifications" && req.method === "GET")
|
|
336
|
+
this.serveSharingNotifications(res, url);
|
|
337
|
+
else if (p === "/api/sharing/notifications/read" && req.method === "POST")
|
|
338
|
+
this.handleSharingNotificationsRead(req, res);
|
|
339
|
+
else if (p === "/api/sharing/notifications/clear" && req.method === "POST")
|
|
340
|
+
this.handleSharingNotificationsClear(req, res);
|
|
341
|
+
else if (p === "/api/admin/shared-tasks" && req.method === "GET")
|
|
342
|
+
this.serveAdminSharedTasks(res);
|
|
343
|
+
else if (p.match(/^\/api\/admin\/shared-tasks\/[^/]+$/) && req.method === "DELETE")
|
|
344
|
+
this.handleAdminDeleteTask(res, p);
|
|
345
|
+
else if (p === "/api/admin/shared-skills" && req.method === "GET")
|
|
346
|
+
this.serveAdminSharedSkills(res);
|
|
347
|
+
else if (p.match(/^\/api\/admin\/shared-skills\/[^/]+$/) && req.method === "DELETE")
|
|
348
|
+
this.handleAdminDeleteSkill(res, p);
|
|
349
|
+
else if (p === "/api/admin/shared-memories" && req.method === "GET")
|
|
350
|
+
this.serveAdminSharedMemories(res);
|
|
351
|
+
else if (p.match(/^\/api\/admin\/shared-memories\/[^/]+$/) && req.method === "DELETE")
|
|
352
|
+
this.handleAdminDeleteMemory(res, p);
|
|
353
|
+
else if (p === "/api/local-ips" && req.method === "GET")
|
|
354
|
+
this.serveLocalIPs(res);
|
|
273
355
|
else if (p === "/api/config" && req.method === "GET")
|
|
274
356
|
this.serveConfig(res);
|
|
275
357
|
else if (p === "/api/config" && req.method === "PUT")
|
|
@@ -455,15 +537,36 @@ class ViewerServer {
|
|
|
455
537
|
const totalRow = db.prepare("SELECT COUNT(*) as count FROM chunks" + where).get(...params);
|
|
456
538
|
const rawMemories = db.prepare("SELECT * FROM chunks" + where + ` ORDER BY created_at ${sortBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
457
539
|
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
|
-
|
|
540
|
+
const chunkIds = rawMemories.map((m) => m.id);
|
|
541
|
+
const sharingMap = new Map();
|
|
542
|
+
const localShareMap = new Map();
|
|
543
|
+
if (chunkIds.length > 0) {
|
|
544
|
+
try {
|
|
545
|
+
const placeholders = chunkIds.map(() => "?").join(",");
|
|
546
|
+
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
547
|
+
for (const r of sharedRows)
|
|
548
|
+
sharingMap.set(r.source_chunk_id, r);
|
|
549
|
+
const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
550
|
+
for (const r of localRows)
|
|
551
|
+
localShareMap.set(r.chunk_id, r);
|
|
461
552
|
}
|
|
462
|
-
|
|
463
|
-
const sources = findMergeSources.all(m.id);
|
|
464
|
-
m.merge_sources = sources;
|
|
553
|
+
catch {
|
|
465
554
|
}
|
|
466
|
-
|
|
555
|
+
}
|
|
556
|
+
const memories = rawMemories.map((m) => {
|
|
557
|
+
const out = m.role === "user" && m.content ? { ...m, content: (0, capture_1.stripInboundMetadata)(m.content) } : { ...m };
|
|
558
|
+
if (out.merge_count > 0) {
|
|
559
|
+
const sources = findMergeSources.all(m.id);
|
|
560
|
+
out.merge_sources = sources;
|
|
561
|
+
}
|
|
562
|
+
const shared = sharingMap.get(m.id);
|
|
563
|
+
const localShared = localShareMap.get(m.id);
|
|
564
|
+
out.sharingVisibility = shared?.visibility ?? null;
|
|
565
|
+
out.sharingGroupId = shared?.group_id ?? null;
|
|
566
|
+
out.localSharing = out.owner === "public";
|
|
567
|
+
out.localSharingManaged = !!localShared;
|
|
568
|
+
out.localOriginalOwner = localShared?.original_owner ?? null;
|
|
569
|
+
return out;
|
|
467
570
|
});
|
|
468
571
|
this.store.recordViewerEvent("list");
|
|
469
572
|
this.jsonResponse(res, {
|
|
@@ -477,7 +580,21 @@ class ViewerServer {
|
|
|
477
580
|
this.jsonResponse(res, data);
|
|
478
581
|
}
|
|
479
582
|
serveToolMetrics(res, url) {
|
|
480
|
-
const
|
|
583
|
+
const fromParam = url.searchParams.get("from");
|
|
584
|
+
const toParam = url.searchParams.get("to");
|
|
585
|
+
if (fromParam) {
|
|
586
|
+
const fromMs = new Date(fromParam).getTime();
|
|
587
|
+
const toMs = toParam ? new Date(toParam).getTime() : Date.now();
|
|
588
|
+
if (isNaN(fromMs) || isNaN(toMs)) {
|
|
589
|
+
this.jsonResponse(res, { error: "Invalid date" }, 400);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const diffMin = Math.max(10, Math.min(43200, Math.round((toMs - fromMs) / 60000)));
|
|
593
|
+
const data = this.store.getToolMetrics(diffMin, fromMs, toMs);
|
|
594
|
+
this.jsonResponse(res, data);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const minutes = Math.min(43200, Math.max(10, Number(url.searchParams.get("minutes")) || 60));
|
|
481
598
|
const data = this.store.getToolMetrics(minutes);
|
|
482
599
|
this.jsonResponse(res, data);
|
|
483
600
|
}
|
|
@@ -489,7 +606,8 @@ class ViewerServer {
|
|
|
489
606
|
const { tasks, total } = this.store.listTasks({ status, limit, offset });
|
|
490
607
|
const db = this.store.db;
|
|
491
608
|
const items = tasks.map((t) => {
|
|
492
|
-
const meta = db.prepare("SELECT skill_status FROM tasks WHERE id = ?").get(t.id);
|
|
609
|
+
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id);
|
|
610
|
+
const sharedTask = db.prepare("SELECT visibility FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(t.id);
|
|
493
611
|
return {
|
|
494
612
|
id: t.id,
|
|
495
613
|
sessionKey: t.sessionKey,
|
|
@@ -500,6 +618,8 @@ class ViewerServer {
|
|
|
500
618
|
endedAt: t.endedAt,
|
|
501
619
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
502
620
|
skillStatus: meta?.skill_status ?? null,
|
|
621
|
+
owner: meta?.owner ?? "agent:main",
|
|
622
|
+
sharingVisibility: sharedTask?.visibility ?? null,
|
|
503
623
|
};
|
|
504
624
|
});
|
|
505
625
|
this.jsonResponse(res, { tasks: items, total, limit, offset });
|
|
@@ -528,21 +648,26 @@ class ViewerServer {
|
|
|
528
648
|
}));
|
|
529
649
|
const db = this.store.db;
|
|
530
650
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId);
|
|
651
|
+
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
652
|
this.jsonResponse(res, {
|
|
532
653
|
id: task.id,
|
|
533
654
|
sessionKey: task.sessionKey,
|
|
534
655
|
title: task.title,
|
|
535
656
|
summary: task.summary,
|
|
536
657
|
status: task.status,
|
|
658
|
+
owner: task.owner ?? "agent:main",
|
|
537
659
|
startedAt: task.startedAt,
|
|
538
660
|
endedAt: task.endedAt,
|
|
539
661
|
chunks: chunkItems,
|
|
540
662
|
skillStatus: meta?.skill_status ?? null,
|
|
541
663
|
skillReason: meta?.skill_reason ?? null,
|
|
542
664
|
skillLinks,
|
|
665
|
+
sharingVisibility: sharedTask?.visibility ?? null,
|
|
666
|
+
sharingGroupId: sharedTask?.group_id ?? null,
|
|
667
|
+
hubTaskId: sharedTask ? true : false,
|
|
543
668
|
});
|
|
544
669
|
}
|
|
545
|
-
serveStats(res) {
|
|
670
|
+
serveStats(res, url) {
|
|
546
671
|
const emptyStats = {
|
|
547
672
|
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
548
673
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
@@ -554,6 +679,7 @@ class ViewerServer {
|
|
|
554
679
|
this.jsonResponse(res, emptyStats);
|
|
555
680
|
return;
|
|
556
681
|
}
|
|
682
|
+
const ownerFilter = url?.searchParams.get("owner") ?? "";
|
|
557
683
|
try {
|
|
558
684
|
const db = this.store.db;
|
|
559
685
|
const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get();
|
|
@@ -572,7 +698,12 @@ class ViewerServer {
|
|
|
572
698
|
embCount = db.prepare("SELECT COUNT(*) as count FROM embeddings").get().count;
|
|
573
699
|
}
|
|
574
700
|
catch { /* table may not exist */ }
|
|
575
|
-
const
|
|
701
|
+
const sessionQuery = ownerFilter
|
|
702
|
+
? "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"
|
|
703
|
+
: "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";
|
|
704
|
+
const sessionList = (ownerFilter
|
|
705
|
+
? db.prepare(sessionQuery).all(ownerFilter)
|
|
706
|
+
: db.prepare(sessionQuery).all());
|
|
576
707
|
let skillCount = 0;
|
|
577
708
|
try {
|
|
578
709
|
skillCount = db.prepare("SELECT COUNT(*) as count FROM skills").get().count;
|
|
@@ -721,7 +852,12 @@ class ViewerServer {
|
|
|
721
852
|
if (visibility) {
|
|
722
853
|
skills = skills.filter(s => s.visibility === visibility);
|
|
723
854
|
}
|
|
724
|
-
|
|
855
|
+
const db = this.store.db;
|
|
856
|
+
const enriched = skills.map(s => {
|
|
857
|
+
const hub = db.prepare("SELECT visibility FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(s.id);
|
|
858
|
+
return { ...s, sharingVisibility: hub?.visibility ?? null };
|
|
859
|
+
});
|
|
860
|
+
this.jsonResponse(res, { skills: enriched });
|
|
725
861
|
}
|
|
726
862
|
serveSkillDetail(res, urlPath) {
|
|
727
863
|
const skillId = urlPath.replace("/api/skill/", "");
|
|
@@ -734,8 +870,10 @@ class ViewerServer {
|
|
|
734
870
|
const versions = this.store.getSkillVersions(skillId);
|
|
735
871
|
const relatedTasks = this.store.getTasksBySkill(skillId);
|
|
736
872
|
const files = node_fs_1.default.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
|
|
873
|
+
const db = this.store.db;
|
|
874
|
+
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
875
|
this.jsonResponse(res, {
|
|
738
|
-
skill,
|
|
876
|
+
skill: { ...skill, sharingVisibility: sharedSkill?.visibility ?? null, sharingGroupId: sharedSkill?.group_id ?? null },
|
|
739
877
|
versions: versions.map(v => ({
|
|
740
878
|
id: v.id,
|
|
741
879
|
version: v.version,
|
|
@@ -844,7 +982,7 @@ class ViewerServer {
|
|
|
844
982
|
handleSkillVisibility(req, res, urlPath) {
|
|
845
983
|
const segments = urlPath.split("/");
|
|
846
984
|
const skillId = segments[segments.length - 2];
|
|
847
|
-
this.readBody(req, (body) => {
|
|
985
|
+
this.readBody(req, async (body) => {
|
|
848
986
|
try {
|
|
849
987
|
const parsed = JSON.parse(body);
|
|
850
988
|
const visibility = parsed.visibility;
|
|
@@ -860,7 +998,47 @@ class ViewerServer {
|
|
|
860
998
|
return;
|
|
861
999
|
}
|
|
862
1000
|
this.store.setSkillVisibility(skillId, visibility);
|
|
863
|
-
|
|
1001
|
+
let hubSynced = false;
|
|
1002
|
+
const sharing = this.ctx?.config?.sharing;
|
|
1003
|
+
if (sharing?.enabled && this.ctx) {
|
|
1004
|
+
try {
|
|
1005
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1006
|
+
if (visibility === "public") {
|
|
1007
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
1008
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1011
|
+
});
|
|
1012
|
+
if (hubClient.userId) {
|
|
1013
|
+
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1014
|
+
this.store.upsertHubSkill({
|
|
1015
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1016
|
+
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
1017
|
+
name: skill.name, description: skill.description, version: skill.version,
|
|
1018
|
+
groupId: null, visibility: "public",
|
|
1019
|
+
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
1020
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
hubSynced = true;
|
|
1024
|
+
this.log.info(`Skill "${skill.name}" published to Hub`);
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1030
|
+
});
|
|
1031
|
+
if (hubClient.userId)
|
|
1032
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1033
|
+
hubSynced = true;
|
|
1034
|
+
this.log.info(`Skill "${skill.name}" unpublished from Hub`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
catch (hubErr) {
|
|
1038
|
+
this.log.warn(`Hub sync failed for skill visibility change: ${hubErr}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, hubSynced });
|
|
864
1042
|
}
|
|
865
1043
|
catch (err) {
|
|
866
1044
|
const errMsg = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
@@ -997,7 +1175,15 @@ class ViewerServer {
|
|
|
997
1175
|
const cleaned = chunk.role === "user" && chunk.content
|
|
998
1176
|
? { ...chunk, content: (0, capture_1.stripInboundMetadata)(chunk.content) }
|
|
999
1177
|
: chunk;
|
|
1000
|
-
this.
|
|
1178
|
+
const localShared = this.store.getLocalSharedMemory(chunkId);
|
|
1179
|
+
this.jsonResponse(res, {
|
|
1180
|
+
memory: {
|
|
1181
|
+
...cleaned,
|
|
1182
|
+
localSharing: cleaned.owner === "public",
|
|
1183
|
+
localSharingManaged: !!localShared,
|
|
1184
|
+
localOriginalOwner: localShared?.originalOwner ?? null,
|
|
1185
|
+
},
|
|
1186
|
+
});
|
|
1001
1187
|
}
|
|
1002
1188
|
handleUpdate(req, res, urlPath) {
|
|
1003
1189
|
const chunkId = urlPath.replace("/api/memory/", "");
|
|
@@ -1032,6 +1218,348 @@ class ViewerServer {
|
|
|
1032
1218
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
1033
1219
|
}
|
|
1034
1220
|
}
|
|
1221
|
+
handleMemoryLocalShare(req, res) {
|
|
1222
|
+
this.readBody(req, (body) => {
|
|
1223
|
+
try {
|
|
1224
|
+
const parsed = JSON.parse(body || "{}");
|
|
1225
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1226
|
+
if (!chunkId)
|
|
1227
|
+
return this.jsonResponse(res, { ok: false, error: "missing_chunk_id" }, 400);
|
|
1228
|
+
const result = this.store.markMemorySharedLocally(chunkId);
|
|
1229
|
+
if (!result.ok) {
|
|
1230
|
+
return this.jsonResponse(res, { ok: false, error: result.reason ?? "share_failed" }, result.reason === "not_found" ? 404 : 400);
|
|
1231
|
+
}
|
|
1232
|
+
this.jsonResponse(res, {
|
|
1233
|
+
ok: true,
|
|
1234
|
+
chunkId,
|
|
1235
|
+
owner: result.owner,
|
|
1236
|
+
localSharing: true,
|
|
1237
|
+
localSharingManaged: true,
|
|
1238
|
+
localOriginalOwner: result.originalOwner ?? null,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
catch (err) {
|
|
1242
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 400);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
handleMemoryLocalUnshare(req, res) {
|
|
1247
|
+
this.readBody(req, (body) => {
|
|
1248
|
+
try {
|
|
1249
|
+
const parsed = JSON.parse(body || "{}");
|
|
1250
|
+
const chunkId = String(parsed.chunkId || "");
|
|
1251
|
+
const privateOwner = typeof parsed.privateOwner === "string" ? parsed.privateOwner : undefined;
|
|
1252
|
+
if (!chunkId)
|
|
1253
|
+
return this.jsonResponse(res, { ok: false, error: "missing_chunk_id" }, 400);
|
|
1254
|
+
const result = this.store.unmarkMemorySharedLocally(chunkId, privateOwner);
|
|
1255
|
+
if (!result.ok) {
|
|
1256
|
+
return this.jsonResponse(res, { ok: false, error: result.reason ?? "unshare_failed" }, result.reason === "not_found" ? 404 : 400);
|
|
1257
|
+
}
|
|
1258
|
+
this.jsonResponse(res, {
|
|
1259
|
+
ok: true,
|
|
1260
|
+
chunkId,
|
|
1261
|
+
owner: result.owner,
|
|
1262
|
+
localSharing: false,
|
|
1263
|
+
localOriginalOwner: result.originalOwner ?? null,
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
catch (err) {
|
|
1267
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 400);
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
// ─── Unified scope API ───
|
|
1272
|
+
handleMemoryScope(req, res, urlPath) {
|
|
1273
|
+
const chunkId = urlPath.split("/")[3];
|
|
1274
|
+
this.readBody(req, async (body) => {
|
|
1275
|
+
try {
|
|
1276
|
+
const parsed = JSON.parse(body || "{}");
|
|
1277
|
+
const scope = parsed.scope;
|
|
1278
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1279
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1280
|
+
}
|
|
1281
|
+
const db = this.store.db;
|
|
1282
|
+
const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1283
|
+
if (!chunk)
|
|
1284
|
+
return this.jsonResponse(res, { ok: false, error: "not_found" }, 404);
|
|
1285
|
+
if (chunk.dedup_status && chunk.dedup_status !== "active") {
|
|
1286
|
+
return this.jsonResponse(res, { ok: false, error: "inactive_memory", message: "Merged/duplicate memories cannot be shared" }, 400);
|
|
1287
|
+
}
|
|
1288
|
+
const isLocalShared = chunk.owner === "public";
|
|
1289
|
+
const hubMemory = this.getHubMemoryForChunk(chunkId);
|
|
1290
|
+
const isTeamShared = !!hubMemory;
|
|
1291
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1292
|
+
if (scope === currentScope) {
|
|
1293
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1294
|
+
}
|
|
1295
|
+
let hubSynced = false;
|
|
1296
|
+
if (scope === "team") {
|
|
1297
|
+
if (!isLocalShared)
|
|
1298
|
+
this.store.markMemorySharedLocally(chunkId);
|
|
1299
|
+
if (!isTeamShared) {
|
|
1300
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1301
|
+
const refreshedChunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1302
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
1303
|
+
method: "POST",
|
|
1304
|
+
body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
|
|
1305
|
+
});
|
|
1306
|
+
if (hubClient.userId) {
|
|
1307
|
+
const existing = this.store.getHubMemoryBySource(hubClient.userId, chunkId);
|
|
1308
|
+
this.store.upsertHubMemory({
|
|
1309
|
+
id: response?.memoryId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1310
|
+
sourceChunkId: chunkId, sourceUserId: hubClient.userId,
|
|
1311
|
+
role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary ?? "",
|
|
1312
|
+
kind: refreshedChunk.kind, groupId: null, visibility: "public",
|
|
1313
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
hubSynced = true;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
else if (scope === "local") {
|
|
1320
|
+
if (isTeamShared) {
|
|
1321
|
+
try {
|
|
1322
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1323
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1324
|
+
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1325
|
+
});
|
|
1326
|
+
if (hubClient.userId)
|
|
1327
|
+
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1328
|
+
hubSynced = true;
|
|
1329
|
+
}
|
|
1330
|
+
catch (err) {
|
|
1331
|
+
this.log.warn(`Failed to unshare memory from team: ${err}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!isLocalShared)
|
|
1335
|
+
this.store.markMemorySharedLocally(chunkId);
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
if (isTeamShared) {
|
|
1339
|
+
try {
|
|
1340
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1341
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1342
|
+
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1343
|
+
});
|
|
1344
|
+
if (hubClient.userId)
|
|
1345
|
+
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1346
|
+
hubSynced = true;
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
this.log.warn(`Failed to unshare memory from team: ${err}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (isLocalShared)
|
|
1353
|
+
this.store.unmarkMemorySharedLocally(chunkId);
|
|
1354
|
+
}
|
|
1355
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1356
|
+
}
|
|
1357
|
+
catch (err) {
|
|
1358
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
handleTaskScope(req, res, urlPath) {
|
|
1363
|
+
const taskId = urlPath.split("/")[3];
|
|
1364
|
+
this.readBody(req, async (body) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const parsed = JSON.parse(body || "{}");
|
|
1367
|
+
const scope = parsed.scope;
|
|
1368
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1369
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1370
|
+
}
|
|
1371
|
+
const task = this.store.getTask(taskId);
|
|
1372
|
+
if (!task)
|
|
1373
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" }, 404);
|
|
1374
|
+
if (scope !== "private" && task.status !== "completed") {
|
|
1375
|
+
return this.jsonResponse(res, { ok: false, error: "only_completed_tasks_can_be_shared" }, 400);
|
|
1376
|
+
}
|
|
1377
|
+
const isLocalShared = task.owner === "public";
|
|
1378
|
+
const hubTask = this.getHubTaskForLocal(taskId);
|
|
1379
|
+
const isTeamShared = !!hubTask;
|
|
1380
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1381
|
+
if (scope === currentScope) {
|
|
1382
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1383
|
+
}
|
|
1384
|
+
let hubSynced = false;
|
|
1385
|
+
if (scope === "local" || scope === "team") {
|
|
1386
|
+
if (!isLocalShared) {
|
|
1387
|
+
const originalOwner = task.owner;
|
|
1388
|
+
const db = this.store.db;
|
|
1389
|
+
db.prepare("INSERT INTO local_shared_tasks (task_id, hub_task_id, original_owner, shared_at) VALUES (?, ?, ?, ?) ON CONFLICT(task_id) DO UPDATE SET original_owner = excluded.original_owner, shared_at = excluded.shared_at").run(taskId, "", originalOwner, Date.now());
|
|
1390
|
+
db.prepare("UPDATE tasks SET owner = 'public' WHERE id = ?").run(taskId);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (scope === "team") {
|
|
1394
|
+
if (!isTeamShared) {
|
|
1395
|
+
const chunks = this.store.getChunksByTask(taskId);
|
|
1396
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1397
|
+
const refreshedTask = this.store.getTask(taskId);
|
|
1398
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
1399
|
+
method: "POST",
|
|
1400
|
+
body: JSON.stringify({
|
|
1401
|
+
task: { id: refreshedTask.id, sourceTaskId: refreshedTask.id, title: refreshedTask.title, summary: refreshedTask.summary, groupId: null, visibility: "public", createdAt: refreshedTask.startedAt ?? Date.now(), updatedAt: refreshedTask.updatedAt ?? Date.now() },
|
|
1402
|
+
chunks: chunks.map((c) => ({ id: c.id, hubTaskId: refreshedTask.id, sourceTaskId: refreshedTask.id, sourceChunkId: c.id, role: c.role, content: c.content, summary: c.summary, kind: c.kind, groupId: null, visibility: "public", createdAt: c.createdAt ?? Date.now() })),
|
|
1403
|
+
}),
|
|
1404
|
+
});
|
|
1405
|
+
if (hubClient.userId) {
|
|
1406
|
+
const existing = this.store.getHubTaskBySource(hubClient.userId, taskId);
|
|
1407
|
+
this.store.upsertHubTask({
|
|
1408
|
+
id: response?.taskId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1409
|
+
sourceTaskId: taskId, sourceUserId: hubClient.userId, title: refreshedTask.title ?? "",
|
|
1410
|
+
summary: refreshedTask.summary ?? "", groupId: null, visibility: "public",
|
|
1411
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
hubSynced = true;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (scope === "local" && isTeamShared) {
|
|
1418
|
+
try {
|
|
1419
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1420
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1421
|
+
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1422
|
+
});
|
|
1423
|
+
if (hubClient.userId)
|
|
1424
|
+
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1425
|
+
hubSynced = true;
|
|
1426
|
+
}
|
|
1427
|
+
catch (err) {
|
|
1428
|
+
this.log.warn(`Failed to unshare task from team: ${err}`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
if (scope === "private") {
|
|
1432
|
+
if (isTeamShared) {
|
|
1433
|
+
try {
|
|
1434
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1435
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1436
|
+
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1437
|
+
});
|
|
1438
|
+
if (hubClient.userId)
|
|
1439
|
+
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1440
|
+
hubSynced = true;
|
|
1441
|
+
}
|
|
1442
|
+
catch (err) {
|
|
1443
|
+
this.log.warn(`Failed to unshare task from team: ${err}`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (isLocalShared) {
|
|
1447
|
+
const db = this.store.db;
|
|
1448
|
+
const shared = db.prepare("SELECT original_owner FROM local_shared_tasks WHERE task_id = ?").get(taskId);
|
|
1449
|
+
const restoreOwner = shared?.original_owner ?? task.owner;
|
|
1450
|
+
if (restoreOwner && restoreOwner !== "public") {
|
|
1451
|
+
db.prepare("UPDATE tasks SET owner = ? WHERE id = ?").run(restoreOwner, taskId);
|
|
1452
|
+
}
|
|
1453
|
+
db.prepare("DELETE FROM local_shared_tasks WHERE task_id = ?").run(taskId);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1457
|
+
}
|
|
1458
|
+
catch (err) {
|
|
1459
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
handleSkillScope(req, res, urlPath) {
|
|
1464
|
+
const skillId = urlPath.split("/")[3];
|
|
1465
|
+
this.readBody(req, async (body) => {
|
|
1466
|
+
try {
|
|
1467
|
+
const parsed = JSON.parse(body || "{}");
|
|
1468
|
+
const scope = parsed.scope;
|
|
1469
|
+
if (!["private", "local", "team"].includes(scope)) {
|
|
1470
|
+
return this.jsonResponse(res, { ok: false, error: "scope must be 'private', 'local', or 'team'" }, 400);
|
|
1471
|
+
}
|
|
1472
|
+
const skill = this.store.getSkill(skillId);
|
|
1473
|
+
if (!skill)
|
|
1474
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" }, 404);
|
|
1475
|
+
if (scope !== "private" && skill.status !== "active") {
|
|
1476
|
+
return this.jsonResponse(res, { ok: false, error: "only_active_skills_can_be_shared" }, 400);
|
|
1477
|
+
}
|
|
1478
|
+
const isLocalShared = skill.visibility === "public";
|
|
1479
|
+
const hubSkill = this.getHubSkillForLocal(skillId);
|
|
1480
|
+
const isTeamShared = !!hubSkill;
|
|
1481
|
+
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1482
|
+
if (scope === currentScope) {
|
|
1483
|
+
return this.jsonResponse(res, { ok: true, scope, changed: false });
|
|
1484
|
+
}
|
|
1485
|
+
let hubSynced = false;
|
|
1486
|
+
if (scope === "local" || scope === "team") {
|
|
1487
|
+
if (!isLocalShared)
|
|
1488
|
+
this.store.setSkillVisibility(skillId, "public");
|
|
1489
|
+
}
|
|
1490
|
+
if (scope === "team") {
|
|
1491
|
+
if (!isTeamShared) {
|
|
1492
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
1493
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1494
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
1495
|
+
method: "POST",
|
|
1496
|
+
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1497
|
+
});
|
|
1498
|
+
if (hubClient.userId) {
|
|
1499
|
+
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1500
|
+
this.store.upsertHubSkill({
|
|
1501
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
1502
|
+
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
1503
|
+
name: skill.name, description: skill.description, version: skill.version,
|
|
1504
|
+
groupId: null, visibility: "public",
|
|
1505
|
+
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
1506
|
+
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
hubSynced = true;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (scope === "local" && isTeamShared) {
|
|
1513
|
+
try {
|
|
1514
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1515
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1516
|
+
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1517
|
+
});
|
|
1518
|
+
if (hubClient.userId)
|
|
1519
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1520
|
+
hubSynced = true;
|
|
1521
|
+
}
|
|
1522
|
+
catch (err) {
|
|
1523
|
+
this.log.warn(`Failed to unpublish skill from team: ${err}`);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (scope === "private") {
|
|
1527
|
+
if (isTeamShared) {
|
|
1528
|
+
try {
|
|
1529
|
+
const hubClient = await this.resolveHubClientAware();
|
|
1530
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1531
|
+
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1532
|
+
});
|
|
1533
|
+
if (hubClient.userId)
|
|
1534
|
+
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1535
|
+
hubSynced = true;
|
|
1536
|
+
}
|
|
1537
|
+
catch (err) {
|
|
1538
|
+
this.log.warn(`Failed to unpublish skill from team: ${err}`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (isLocalShared)
|
|
1542
|
+
this.store.setSkillVisibility(skillId, "private");
|
|
1543
|
+
}
|
|
1544
|
+
this.jsonResponse(res, { ok: true, scope, changed: true, hubSynced });
|
|
1545
|
+
}
|
|
1546
|
+
catch (err) {
|
|
1547
|
+
this.jsonResponse(res, { ok: false, error: String(err) }, 500);
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
getHubMemoryForChunk(chunkId) {
|
|
1552
|
+
const db = this.store.db;
|
|
1553
|
+
return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
|
|
1554
|
+
}
|
|
1555
|
+
getHubTaskForLocal(taskId) {
|
|
1556
|
+
const db = this.store.db;
|
|
1557
|
+
return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
|
|
1558
|
+
}
|
|
1559
|
+
getHubSkillForLocal(skillId) {
|
|
1560
|
+
const db = this.store.db;
|
|
1561
|
+
return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
|
|
1562
|
+
}
|
|
1035
1563
|
handleDeleteSession(res, url) {
|
|
1036
1564
|
const key = url.searchParams.get("key");
|
|
1037
1565
|
if (!key) {
|
|
@@ -1069,8 +1597,894 @@ class ViewerServer {
|
|
|
1069
1597
|
// ─── Config API ───
|
|
1070
1598
|
getOpenClawConfigPath() {
|
|
1071
1599
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1072
|
-
|
|
1073
|
-
|
|
1600
|
+
return node_path_1.default.join(home, ".openclaw", "openclaw.json");
|
|
1601
|
+
}
|
|
1602
|
+
getPluginEntryConfig(raw) {
|
|
1603
|
+
const entries = raw?.plugins?.entries ?? {};
|
|
1604
|
+
return entries["memos-local-openclaw-plugin"]?.config
|
|
1605
|
+
?? entries["memos-lite-openclaw-plugin"]?.config
|
|
1606
|
+
?? entries["memos-lite"]?.config
|
|
1607
|
+
?? {};
|
|
1608
|
+
}
|
|
1609
|
+
getResolvedViewerConfig(raw) {
|
|
1610
|
+
const pluginCfg = this.getPluginEntryConfig(raw);
|
|
1611
|
+
const stateDir = this.ctx?.stateDir ?? this.getOpenClawHome();
|
|
1612
|
+
return (0, config_1.resolveConfig)(pluginCfg, stateDir);
|
|
1613
|
+
}
|
|
1614
|
+
hasUsableEmbeddingProvider(cfg) {
|
|
1615
|
+
const embedding = cfg.embedding;
|
|
1616
|
+
if (!embedding?.provider)
|
|
1617
|
+
return false;
|
|
1618
|
+
if (embedding.provider === "openclaw") {
|
|
1619
|
+
return !!(this.ctx?.openclawAPI) && embedding.capabilities?.hostEmbedding === true;
|
|
1620
|
+
}
|
|
1621
|
+
return true;
|
|
1622
|
+
}
|
|
1623
|
+
hasUsableSummarizerProvider(cfg) {
|
|
1624
|
+
const summarizer = cfg.summarizer;
|
|
1625
|
+
if (!summarizer?.provider)
|
|
1626
|
+
return false;
|
|
1627
|
+
if (summarizer.provider === "openclaw") {
|
|
1628
|
+
return !!(this.ctx?.openclawAPI) && summarizer.capabilities?.hostCompletion === true;
|
|
1629
|
+
}
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
async serveSharingStatus(res) {
|
|
1633
|
+
const sharing = this.ctx?.config?.sharing;
|
|
1634
|
+
const persisted = this.store.getClientHubConnection();
|
|
1635
|
+
const resolvedHubUrl = sharing?.client?.hubAddress ? (0, hub_1.normalizeHubUrl)(sharing.client.hubAddress) : persisted?.hubUrl ?? null;
|
|
1636
|
+
const hasClientConfig = Boolean((sharing?.client?.hubAddress && sharing?.client?.userToken) ||
|
|
1637
|
+
(persisted?.hubUrl && persisted?.userToken));
|
|
1638
|
+
const base = {
|
|
1639
|
+
enabled: Boolean(sharing?.enabled),
|
|
1640
|
+
role: sharing?.role ?? null,
|
|
1641
|
+
clientConfigured: hasClientConfig,
|
|
1642
|
+
hubUrl: resolvedHubUrl,
|
|
1643
|
+
connection: { connected: false, user: null, hubUrl: undefined, teamName: null, apiVersion: null },
|
|
1644
|
+
admin: { canManageUsers: false, rejectSupported: false },
|
|
1645
|
+
};
|
|
1646
|
+
if (!this.ctx || !sharing?.enabled) {
|
|
1647
|
+
this.jsonResponse(res, base);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
// Hub 模式下,本机就是管理者,直接赋予 admin 权限
|
|
1651
|
+
if (sharing.role === "hub") {
|
|
1652
|
+
base.admin.canManageUsers = true;
|
|
1653
|
+
base.admin.rejectSupported = true;
|
|
1654
|
+
base.connection.connected = true;
|
|
1655
|
+
base.connection.hubUrl = resolvedHubUrl ?? undefined;
|
|
1656
|
+
let adminUser = { username: "hub-admin", role: "admin" };
|
|
1657
|
+
try {
|
|
1658
|
+
const hub = this.resolveHubConnection();
|
|
1659
|
+
if (hub) {
|
|
1660
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
1661
|
+
if (me) {
|
|
1662
|
+
adminUser = {
|
|
1663
|
+
id: me.id,
|
|
1664
|
+
username: me.username ?? "hub-admin",
|
|
1665
|
+
role: me.role ?? "admin",
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
catch { /* fallback to default */ }
|
|
1671
|
+
base.connection.user = adminUser;
|
|
1672
|
+
// Fetch team info from own hub
|
|
1673
|
+
try {
|
|
1674
|
+
const selfUrl = resolvedHubUrl || `http://localhost:${sharing.hub?.port ?? 21816}`;
|
|
1675
|
+
const info = await fetch(`${selfUrl}/api/v1/hub/info`).then(r => r.ok ? r.json() : null).catch(() => null);
|
|
1676
|
+
base.connection.teamName = info?.teamName ?? sharing.hub?.teamName ?? null;
|
|
1677
|
+
base.connection.apiVersion = info?.apiVersion ?? null;
|
|
1678
|
+
}
|
|
1679
|
+
catch { /* ignore */ }
|
|
1680
|
+
this.jsonResponse(res, base);
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
const hasPendingConnection = Boolean(persisted?.hubUrl && persisted?.userId && !persisted?.userToken);
|
|
1684
|
+
if (!hasClientConfig && !hasPendingConnection) {
|
|
1685
|
+
this.jsonResponse(res, base);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
try {
|
|
1689
|
+
const status = await (0, connector_1.getHubStatus)(this.store, this.ctx.config);
|
|
1690
|
+
const output = { ...base, connection: { ...base.connection, ...status } };
|
|
1691
|
+
if (status.user?.status === "pending") {
|
|
1692
|
+
output.connection.pendingApproval = true;
|
|
1693
|
+
}
|
|
1694
|
+
if (status.user?.status === "rejected") {
|
|
1695
|
+
output.connection.rejected = true;
|
|
1696
|
+
}
|
|
1697
|
+
if (status.connected && status.hubUrl) {
|
|
1698
|
+
try {
|
|
1699
|
+
const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
1700
|
+
output.connection.teamName = info?.teamName ?? null;
|
|
1701
|
+
output.connection.apiVersion = info?.apiVersion ?? null;
|
|
1702
|
+
}
|
|
1703
|
+
catch { }
|
|
1704
|
+
}
|
|
1705
|
+
else if (status.hubUrl) {
|
|
1706
|
+
try {
|
|
1707
|
+
const info = await fetch(`${status.hubUrl}/api/v1/hub/info`).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
1708
|
+
output.connection.teamName = info?.teamName ?? null;
|
|
1709
|
+
}
|
|
1710
|
+
catch { }
|
|
1711
|
+
}
|
|
1712
|
+
output.admin.canManageUsers = status.connected && status.user?.role === "admin";
|
|
1713
|
+
output.admin.rejectSupported = output.admin.canManageUsers;
|
|
1714
|
+
this.jsonResponse(res, output);
|
|
1715
|
+
}
|
|
1716
|
+
catch (err) {
|
|
1717
|
+
this.jsonResponse(res, { ...base, error: String(err) });
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
async serveSharingPendingUsers(res) {
|
|
1721
|
+
if (!this.ctx)
|
|
1722
|
+
return this.jsonResponse(res, { users: [], error: "sharing_unavailable" });
|
|
1723
|
+
try {
|
|
1724
|
+
const hub = this.resolveHubConnection();
|
|
1725
|
+
if (!hub)
|
|
1726
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
1727
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/pending-users", { method: "GET" });
|
|
1728
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
1729
|
+
}
|
|
1730
|
+
catch (err) {
|
|
1731
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
handleSharingApproveUser(req, res) {
|
|
1735
|
+
this.readBody(req, async (body) => {
|
|
1736
|
+
if (!this.ctx)
|
|
1737
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1738
|
+
try {
|
|
1739
|
+
const parsed = JSON.parse(body || "{}");
|
|
1740
|
+
const hub = this.resolveHubConnection();
|
|
1741
|
+
if (!hub)
|
|
1742
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1743
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/approve-user", {
|
|
1744
|
+
method: "POST",
|
|
1745
|
+
body: JSON.stringify({ userId: parsed.userId, username: parsed.username }),
|
|
1746
|
+
});
|
|
1747
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1748
|
+
}
|
|
1749
|
+
catch (err) {
|
|
1750
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
handleSharingRejectUser(req, res) {
|
|
1755
|
+
this.readBody(req, async (body) => {
|
|
1756
|
+
if (!this.ctx)
|
|
1757
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1758
|
+
try {
|
|
1759
|
+
const parsed = JSON.parse(body || "{}");
|
|
1760
|
+
const hub = this.resolveHubConnection();
|
|
1761
|
+
if (!hub)
|
|
1762
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1763
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/reject-user", {
|
|
1764
|
+
method: "POST",
|
|
1765
|
+
body: JSON.stringify({ userId: parsed.userId }),
|
|
1766
|
+
});
|
|
1767
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1768
|
+
}
|
|
1769
|
+
catch (err) {
|
|
1770
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
handleSharingChangeRole(req, res) {
|
|
1775
|
+
this.readBody(req, async (body) => {
|
|
1776
|
+
if (!this.ctx)
|
|
1777
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1778
|
+
try {
|
|
1779
|
+
const parsed = JSON.parse(body || "{}");
|
|
1780
|
+
const hub = this.resolveHubConnection();
|
|
1781
|
+
if (!hub)
|
|
1782
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1783
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/change-role", {
|
|
1784
|
+
method: "POST",
|
|
1785
|
+
body: JSON.stringify({ userId: parsed.userId, role: parsed.role }),
|
|
1786
|
+
});
|
|
1787
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1788
|
+
}
|
|
1789
|
+
catch (err) {
|
|
1790
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
handleSharingRemoveUser(req, res) {
|
|
1795
|
+
this.readBody(req, async (body) => {
|
|
1796
|
+
if (!this.ctx)
|
|
1797
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1798
|
+
try {
|
|
1799
|
+
const parsed = JSON.parse(body || "{}");
|
|
1800
|
+
const hub = this.resolveHubConnection();
|
|
1801
|
+
if (!hub)
|
|
1802
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1803
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/remove-user", {
|
|
1804
|
+
method: "POST",
|
|
1805
|
+
body: JSON.stringify({ userId: parsed.userId, cleanResources: parsed.cleanResources === true }),
|
|
1806
|
+
});
|
|
1807
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1808
|
+
}
|
|
1809
|
+
catch (err) {
|
|
1810
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
handleAdminRenameUser(req, res) {
|
|
1815
|
+
this.readBody(req, async (body) => {
|
|
1816
|
+
if (!this.ctx)
|
|
1817
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1818
|
+
try {
|
|
1819
|
+
const parsed = JSON.parse(body || "{}");
|
|
1820
|
+
const hub = this.resolveHubConnection();
|
|
1821
|
+
if (!hub)
|
|
1822
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
1823
|
+
const result = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/rename-user", {
|
|
1824
|
+
method: "POST",
|
|
1825
|
+
body: JSON.stringify({ userId: parsed.userId, username: parsed.username }),
|
|
1826
|
+
});
|
|
1827
|
+
this.jsonResponse(res, { ok: true, result });
|
|
1828
|
+
}
|
|
1829
|
+
catch (err) {
|
|
1830
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
handleRetryJoin(req, res) {
|
|
1835
|
+
this.readBody(req, async (_body) => {
|
|
1836
|
+
if (!this.ctx)
|
|
1837
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
1838
|
+
const sharing = this.ctx.config.sharing;
|
|
1839
|
+
if (!sharing?.enabled || sharing.role !== "client") {
|
|
1840
|
+
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
|
|
1841
|
+
}
|
|
1842
|
+
const hubAddress = sharing.client?.hubAddress ?? "";
|
|
1843
|
+
const teamToken = sharing.client?.teamToken ?? "";
|
|
1844
|
+
if (!hubAddress || !teamToken) {
|
|
1845
|
+
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
|
|
1846
|
+
}
|
|
1847
|
+
try {
|
|
1848
|
+
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
1849
|
+
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
1850
|
+
const username = os.userInfo().username || "user";
|
|
1851
|
+
const hostname = os.hostname() || "unknown";
|
|
1852
|
+
const result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
|
|
1853
|
+
method: "POST",
|
|
1854
|
+
body: JSON.stringify({ teamToken, username, deviceName: hostname }),
|
|
1855
|
+
});
|
|
1856
|
+
this.store.setClientHubConnection({
|
|
1857
|
+
hubUrl,
|
|
1858
|
+
userId: String(result.userId || ""),
|
|
1859
|
+
username,
|
|
1860
|
+
userToken: result.userToken || "",
|
|
1861
|
+
role: "member",
|
|
1862
|
+
connectedAt: Date.now(),
|
|
1863
|
+
});
|
|
1864
|
+
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
1865
|
+
}
|
|
1866
|
+
catch (err) {
|
|
1867
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
async serveSharingMemoryList(res, url) {
|
|
1872
|
+
if (!this.ctx)
|
|
1873
|
+
return this.jsonResponse(res, { memories: [], error: "sharing_unavailable" });
|
|
1874
|
+
try {
|
|
1875
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1876
|
+
const data = await (0, hub_1.hubListMemories)(this.store, this.ctx, { limit });
|
|
1877
|
+
this.jsonResponse(res, { memories: Array.isArray(data?.memories) ? data.memories : [] });
|
|
1878
|
+
}
|
|
1879
|
+
catch (err) {
|
|
1880
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
async serveSharingTaskList(res, url) {
|
|
1884
|
+
if (!this.ctx)
|
|
1885
|
+
return this.jsonResponse(res, { tasks: [], error: "sharing_unavailable" });
|
|
1886
|
+
try {
|
|
1887
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1888
|
+
const data = await (0, hub_1.hubListTasks)(this.store, this.ctx, { limit });
|
|
1889
|
+
this.jsonResponse(res, { tasks: Array.isArray(data?.tasks) ? data.tasks : [] });
|
|
1890
|
+
}
|
|
1891
|
+
catch (err) {
|
|
1892
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
async serveSharingSkillList(res, url) {
|
|
1896
|
+
if (!this.ctx)
|
|
1897
|
+
return this.jsonResponse(res, { skills: [], error: "sharing_unavailable" });
|
|
1898
|
+
try {
|
|
1899
|
+
const limit = Number(url.searchParams.get("limit") || 40);
|
|
1900
|
+
const data = await (0, hub_1.hubListSkills)(this.store, this.ctx, { limit });
|
|
1901
|
+
this.jsonResponse(res, { skills: Array.isArray(data?.skills) ? data.skills : [] });
|
|
1902
|
+
}
|
|
1903
|
+
catch (err) {
|
|
1904
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
handleSharingMemorySearch(req, res) {
|
|
1908
|
+
this.readBody(req, async (body) => {
|
|
1909
|
+
if (!this.ctx)
|
|
1910
|
+
return this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } }, error: "sharing_unavailable" });
|
|
1911
|
+
const emptyHub = { hits: [], meta: { totalCandidates: 0, searchedGroups: [], includedPublic: false } };
|
|
1912
|
+
try {
|
|
1913
|
+
const parsed = JSON.parse(body || "{}");
|
|
1914
|
+
const query = String(parsed.query || "");
|
|
1915
|
+
const role = typeof parsed.role === "string" ? parsed.role : undefined;
|
|
1916
|
+
const maxResults = typeof parsed.maxResults === "number" ? parsed.maxResults : 10;
|
|
1917
|
+
const scope = parsed.scope === "group" || parsed.scope === "all" ? parsed.scope : "local";
|
|
1918
|
+
const local = this.searchLocalViewerMemories(query, { role, maxResults });
|
|
1919
|
+
if (scope === "local") {
|
|
1920
|
+
return this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub });
|
|
1921
|
+
}
|
|
1922
|
+
try {
|
|
1923
|
+
const hub = await (0, hub_1.hubSearchMemories)(this.store, this.ctx, { query, maxResults, scope });
|
|
1924
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub });
|
|
1925
|
+
}
|
|
1926
|
+
catch (err) {
|
|
1927
|
+
this.jsonResponse(res, { local: { hits: local.hits, meta: local.meta }, hub: emptyHub, error: String(err) });
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
catch (err) {
|
|
1931
|
+
this.jsonResponse(res, { local: { hits: [], meta: {} }, hub: emptyHub, error: String(err) });
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
handleSharingMemoryDetail(req, res) {
|
|
1936
|
+
this.readBody(req, async (body) => {
|
|
1937
|
+
if (!this.ctx)
|
|
1938
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
1939
|
+
try {
|
|
1940
|
+
const parsed = JSON.parse(body || "{}");
|
|
1941
|
+
const detail = await (0, hub_1.hubGetMemoryDetail)(this.store, this.ctx, { remoteHitId: String(parsed.remoteHitId || "") });
|
|
1942
|
+
this.jsonResponse(res, detail);
|
|
1943
|
+
}
|
|
1944
|
+
catch (err) {
|
|
1945
|
+
this.jsonResponse(res, { error: String(err) });
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
async serveSharingSkillSearch(res, url) {
|
|
1950
|
+
if (!this.ctx)
|
|
1951
|
+
return this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: "sharing_unavailable" });
|
|
1952
|
+
try {
|
|
1953
|
+
const query = String(url.searchParams.get("query") || "");
|
|
1954
|
+
const scope = url.searchParams.get("scope") === "group" || url.searchParams.get("scope") === "all" ? url.searchParams.get("scope") : "local";
|
|
1955
|
+
const recall = new engine_1.RecallEngine(this.store, this.embedder, this.ctx);
|
|
1956
|
+
const localHits = await recall.searchSkills(query, "mix", "agent:main");
|
|
1957
|
+
if (scope === "local") {
|
|
1958
|
+
return this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] } });
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
const hub = await (0, hub_1.hubSearchSkills)(this.store, this.ctx, { query, maxResults: Number(url.searchParams.get("maxResults") || 20) });
|
|
1962
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub });
|
|
1963
|
+
}
|
|
1964
|
+
catch (err) {
|
|
1965
|
+
this.jsonResponse(res, { local: { hits: localHits }, hub: { hits: [] }, error: String(err) });
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
catch (err) {
|
|
1969
|
+
this.jsonResponse(res, { local: { hits: [] }, hub: { hits: [] }, error: String(err) });
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
searchLocalViewerMemories(query, options) {
|
|
1973
|
+
const db = this.store.db;
|
|
1974
|
+
const role = options?.role;
|
|
1975
|
+
const maxResults = options?.maxResults ?? 10;
|
|
1976
|
+
const params = [];
|
|
1977
|
+
let rows = [];
|
|
1978
|
+
try {
|
|
1979
|
+
let sql = "SELECT c.* FROM chunks_fts f JOIN chunks c ON f.rowid = c.rowid WHERE chunks_fts MATCH ?";
|
|
1980
|
+
params.push(query);
|
|
1981
|
+
if (role) {
|
|
1982
|
+
sql += " AND c.role = ?";
|
|
1983
|
+
params.push(role);
|
|
1984
|
+
}
|
|
1985
|
+
sql += " ORDER BY rank LIMIT ?";
|
|
1986
|
+
params.push(maxResults);
|
|
1987
|
+
rows = db.prepare(sql).all(...params);
|
|
1988
|
+
}
|
|
1989
|
+
catch {
|
|
1990
|
+
const likeParams = [`%${query}%`, `%${query}%`];
|
|
1991
|
+
let sql = "SELECT * FROM chunks WHERE (content LIKE ? OR summary LIKE ?)";
|
|
1992
|
+
if (role) {
|
|
1993
|
+
sql += " AND role = ?";
|
|
1994
|
+
likeParams.push(role);
|
|
1995
|
+
}
|
|
1996
|
+
sql += " ORDER BY created_at DESC LIMIT ?";
|
|
1997
|
+
likeParams.push(maxResults);
|
|
1998
|
+
rows = db.prepare(sql).all(...likeParams);
|
|
1999
|
+
}
|
|
2000
|
+
const hits = rows.map((row, idx) => ({
|
|
2001
|
+
id: row.id,
|
|
2002
|
+
summary: row.summary || row.content?.slice(0, 120) || "",
|
|
2003
|
+
excerpt: row.content || "",
|
|
2004
|
+
score: Math.max(0.3, 1 - idx * 0.1),
|
|
2005
|
+
role: row.role,
|
|
2006
|
+
ref: { sessionKey: row.session_key, chunkId: row.id, turnId: row.turn_id, seq: row.seq },
|
|
2007
|
+
taskId: row.task_id ?? null,
|
|
2008
|
+
skillId: row.skill_id ?? null,
|
|
2009
|
+
}));
|
|
2010
|
+
return { hits, meta: { total: hits.length, usedMaxResults: maxResults } };
|
|
2011
|
+
}
|
|
2012
|
+
handleSharingTaskShare(req, res) {
|
|
2013
|
+
this.readBody(req, async (body) => {
|
|
2014
|
+
if (!this.ctx)
|
|
2015
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2016
|
+
try {
|
|
2017
|
+
const parsed = JSON.parse(body || "{}");
|
|
2018
|
+
const taskId = String(parsed.taskId || "");
|
|
2019
|
+
const visibility = "public";
|
|
2020
|
+
const groupId = undefined;
|
|
2021
|
+
const task = this.store.getTask(taskId);
|
|
2022
|
+
if (!task)
|
|
2023
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
2024
|
+
const chunks = this.store.getChunksByTask(taskId);
|
|
2025
|
+
if (chunks.length === 0)
|
|
2026
|
+
return this.jsonResponse(res, { ok: false, error: "no_chunks" });
|
|
2027
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2028
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/share", {
|
|
2029
|
+
method: "POST",
|
|
2030
|
+
body: JSON.stringify({
|
|
2031
|
+
task: {
|
|
2032
|
+
id: task.id,
|
|
2033
|
+
sourceTaskId: task.id,
|
|
2034
|
+
title: task.title,
|
|
2035
|
+
summary: task.summary,
|
|
2036
|
+
groupId: null,
|
|
2037
|
+
visibility,
|
|
2038
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
2039
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
2040
|
+
},
|
|
2041
|
+
chunks: chunks.map((chunk) => ({
|
|
2042
|
+
id: chunk.id,
|
|
2043
|
+
hubTaskId: task.id,
|
|
2044
|
+
sourceTaskId: task.id,
|
|
2045
|
+
sourceChunkId: chunk.id,
|
|
2046
|
+
role: chunk.role,
|
|
2047
|
+
content: chunk.content,
|
|
2048
|
+
summary: chunk.summary,
|
|
2049
|
+
kind: chunk.kind,
|
|
2050
|
+
createdAt: chunk.createdAt,
|
|
2051
|
+
})),
|
|
2052
|
+
}),
|
|
2053
|
+
});
|
|
2054
|
+
const hubUserId = hubClient.userId;
|
|
2055
|
+
if (hubUserId) {
|
|
2056
|
+
this.store.upsertHubTask({
|
|
2057
|
+
id: task.id,
|
|
2058
|
+
sourceTaskId: task.id,
|
|
2059
|
+
sourceUserId: hubUserId,
|
|
2060
|
+
title: task.title,
|
|
2061
|
+
summary: task.summary,
|
|
2062
|
+
groupId: null,
|
|
2063
|
+
visibility,
|
|
2064
|
+
createdAt: task.startedAt ?? Date.now(),
|
|
2065
|
+
updatedAt: task.updatedAt ?? Date.now(),
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
this.jsonResponse(res, { ok: true, taskId, visibility, response });
|
|
2069
|
+
}
|
|
2070
|
+
catch (err) {
|
|
2071
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
handleSharingTaskUnshare(req, res) {
|
|
2076
|
+
this.readBody(req, async (body) => {
|
|
2077
|
+
if (!this.ctx)
|
|
2078
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2079
|
+
try {
|
|
2080
|
+
const parsed = JSON.parse(body || "{}");
|
|
2081
|
+
const taskId = String(parsed.taskId || "");
|
|
2082
|
+
const task = this.store.getTask(taskId);
|
|
2083
|
+
if (!task)
|
|
2084
|
+
return this.jsonResponse(res, { ok: false, error: "task_not_found" });
|
|
2085
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2086
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
2087
|
+
method: "POST",
|
|
2088
|
+
body: JSON.stringify({ sourceTaskId: task.id }),
|
|
2089
|
+
});
|
|
2090
|
+
const hubUserId = hubClient.userId;
|
|
2091
|
+
if (hubUserId)
|
|
2092
|
+
this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
2093
|
+
this.jsonResponse(res, { ok: true, taskId });
|
|
2094
|
+
}
|
|
2095
|
+
catch (err) {
|
|
2096
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
handleSharingMemoryShare(req, res) {
|
|
2101
|
+
this.readBody(req, async (body) => {
|
|
2102
|
+
if (!this.ctx)
|
|
2103
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2104
|
+
try {
|
|
2105
|
+
const parsed = JSON.parse(body || "{}");
|
|
2106
|
+
const chunkId = String(parsed.chunkId || "");
|
|
2107
|
+
const visibility = "public";
|
|
2108
|
+
const groupId = undefined;
|
|
2109
|
+
const db = this.store.db;
|
|
2110
|
+
const chunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
2111
|
+
if (!chunk)
|
|
2112
|
+
return this.jsonResponse(res, { ok: false, error: "memory_not_found" });
|
|
2113
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2114
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
2115
|
+
method: "POST",
|
|
2116
|
+
body: JSON.stringify({
|
|
2117
|
+
memory: {
|
|
2118
|
+
sourceChunkId: chunk.id,
|
|
2119
|
+
role: chunk.role,
|
|
2120
|
+
content: chunk.content,
|
|
2121
|
+
summary: chunk.summary,
|
|
2122
|
+
kind: chunk.kind,
|
|
2123
|
+
groupId: null,
|
|
2124
|
+
visibility,
|
|
2125
|
+
},
|
|
2126
|
+
}),
|
|
2127
|
+
});
|
|
2128
|
+
const hubUserId = hubClient.userId;
|
|
2129
|
+
if (hubUserId) {
|
|
2130
|
+
const now = Date.now();
|
|
2131
|
+
const existing = this.store.getHubMemoryBySource(hubUserId, chunk.id);
|
|
2132
|
+
this.store.upsertHubMemory({
|
|
2133
|
+
id: response?.memoryId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
2134
|
+
sourceChunkId: chunk.id,
|
|
2135
|
+
sourceUserId: hubUserId,
|
|
2136
|
+
role: chunk.role,
|
|
2137
|
+
content: chunk.content,
|
|
2138
|
+
summary: chunk.summary ?? "",
|
|
2139
|
+
kind: chunk.kind,
|
|
2140
|
+
groupId: null,
|
|
2141
|
+
visibility,
|
|
2142
|
+
createdAt: existing?.createdAt ?? now,
|
|
2143
|
+
updatedAt: now,
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
this.jsonResponse(res, { ok: true, chunkId, visibility, response });
|
|
2147
|
+
}
|
|
2148
|
+
catch (err) {
|
|
2149
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
handleSharingMemoryUnshare(req, res) {
|
|
2154
|
+
this.readBody(req, async (body) => {
|
|
2155
|
+
if (!this.ctx)
|
|
2156
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2157
|
+
try {
|
|
2158
|
+
const parsed = JSON.parse(body || "{}");
|
|
2159
|
+
const chunkId = String(parsed.chunkId || "");
|
|
2160
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2161
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
2162
|
+
method: "POST",
|
|
2163
|
+
body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
2164
|
+
});
|
|
2165
|
+
const hubUserId = hubClient.userId;
|
|
2166
|
+
if (hubUserId)
|
|
2167
|
+
this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
2168
|
+
this.jsonResponse(res, { ok: true, chunkId });
|
|
2169
|
+
}
|
|
2170
|
+
catch (err) {
|
|
2171
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2172
|
+
}
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
handleSharingSkillPull(req, res) {
|
|
2176
|
+
this.readBody(req, async (body) => {
|
|
2177
|
+
if (!this.ctx)
|
|
2178
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2179
|
+
try {
|
|
2180
|
+
const parsed = JSON.parse(body || "{}");
|
|
2181
|
+
const skillId = String(parsed.skillId || "");
|
|
2182
|
+
const payload = await (0, skill_sync_1.fetchHubSkillBundle)(this.store, this.ctx, { skillId });
|
|
2183
|
+
const restored = (0, skill_sync_1.restoreSkillBundleFromHub)(this.store, this.ctx, payload);
|
|
2184
|
+
this.jsonResponse(res, { ok: true, pulled: true, hubSkillId: skillId, ...restored });
|
|
2185
|
+
}
|
|
2186
|
+
catch (err) {
|
|
2187
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2188
|
+
}
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
handleSharingSkillShare(req, res) {
|
|
2192
|
+
this.readBody(req, async (body) => {
|
|
2193
|
+
if (!this.ctx)
|
|
2194
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2195
|
+
try {
|
|
2196
|
+
const parsed = JSON.parse(body || "{}");
|
|
2197
|
+
const skillId = String(parsed.skillId || "");
|
|
2198
|
+
const visibility = "public";
|
|
2199
|
+
const groupId = null;
|
|
2200
|
+
const skill = this.store.getSkill(skillId);
|
|
2201
|
+
if (!skill)
|
|
2202
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
2203
|
+
const bundle = (0, skill_sync_1.buildSkillBundleForHub)(this.store, skillId);
|
|
2204
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2205
|
+
const response = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/publish", {
|
|
2206
|
+
method: "POST",
|
|
2207
|
+
body: JSON.stringify({
|
|
2208
|
+
visibility,
|
|
2209
|
+
groupId: null,
|
|
2210
|
+
metadata: bundle.metadata,
|
|
2211
|
+
bundle: bundle.bundle,
|
|
2212
|
+
}),
|
|
2213
|
+
});
|
|
2214
|
+
const hubUserId = hubClient.userId;
|
|
2215
|
+
if (hubUserId) {
|
|
2216
|
+
const existing = this.store.getHubSkillBySource(hubUserId, skillId);
|
|
2217
|
+
this.store.upsertHubSkill({
|
|
2218
|
+
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
2219
|
+
sourceSkillId: skillId,
|
|
2220
|
+
sourceUserId: hubUserId,
|
|
2221
|
+
name: skill.name,
|
|
2222
|
+
description: skill.description,
|
|
2223
|
+
version: skill.version,
|
|
2224
|
+
groupId: null,
|
|
2225
|
+
visibility,
|
|
2226
|
+
bundle: JSON.stringify(bundle.bundle),
|
|
2227
|
+
qualityScore: skill.qualityScore,
|
|
2228
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
2229
|
+
updatedAt: Date.now(),
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
this.jsonResponse(res, { ok: true, skillId, visibility, response });
|
|
2233
|
+
}
|
|
2234
|
+
catch (err) {
|
|
2235
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
handleSharingSkillUnshare(req, res) {
|
|
2240
|
+
this.readBody(req, async (body) => {
|
|
2241
|
+
if (!this.ctx)
|
|
2242
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2243
|
+
try {
|
|
2244
|
+
const parsed = JSON.parse(body || "{}");
|
|
2245
|
+
const skillId = String(parsed.skillId || "");
|
|
2246
|
+
const skill = this.store.getSkill(skillId);
|
|
2247
|
+
if (!skill)
|
|
2248
|
+
return this.jsonResponse(res, { ok: false, error: "skill_not_found" });
|
|
2249
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2250
|
+
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
2251
|
+
method: "POST",
|
|
2252
|
+
body: JSON.stringify({ sourceSkillId: skill.id }),
|
|
2253
|
+
});
|
|
2254
|
+
const hubUserId = hubClient.userId;
|
|
2255
|
+
if (hubUserId)
|
|
2256
|
+
this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
2257
|
+
this.jsonResponse(res, { ok: true, skillId });
|
|
2258
|
+
}
|
|
2259
|
+
catch (err) {
|
|
2260
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
resolveHubConnection() {
|
|
2265
|
+
if (!this.ctx)
|
|
2266
|
+
return null;
|
|
2267
|
+
// Hub 模式:连接自己,用 bootstrap admin token
|
|
2268
|
+
const sharing = this.ctx.config.sharing;
|
|
2269
|
+
if (sharing?.role === "hub") {
|
|
2270
|
+
const hubPort = sharing.hub?.port ?? 18800;
|
|
2271
|
+
const hubUrl = `http://127.0.0.1:${hubPort}`;
|
|
2272
|
+
try {
|
|
2273
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
2274
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
2275
|
+
const adminToken = authData?.bootstrapAdminToken;
|
|
2276
|
+
if (adminToken)
|
|
2277
|
+
return { hubUrl, userToken: adminToken };
|
|
2278
|
+
}
|
|
2279
|
+
catch {
|
|
2280
|
+
// hub-auth.json 不存在或读取失败,fall through
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
// Client 模式:用配置的 hubAddress + userToken
|
|
2284
|
+
const conn = this.store.getClientHubConnection();
|
|
2285
|
+
const hubUrl = conn?.hubUrl || this.ctx.config.sharing?.client?.hubAddress || "";
|
|
2286
|
+
const userToken = conn?.userToken || this.ctx.config.sharing?.client?.userToken || "";
|
|
2287
|
+
if (!hubUrl || !userToken)
|
|
2288
|
+
return null;
|
|
2289
|
+
return { hubUrl: (0, hub_1.normalizeHubUrl)(hubUrl), userToken };
|
|
2290
|
+
}
|
|
2291
|
+
/** resolveHubClient 的 viewer 版本:hub 模式下使用 bootstrap admin 身份 */
|
|
2292
|
+
async resolveHubClientAware() {
|
|
2293
|
+
if (!this.ctx)
|
|
2294
|
+
throw new Error("sharing_unavailable");
|
|
2295
|
+
const sharing = this.ctx.config.sharing;
|
|
2296
|
+
if (sharing?.role === "hub") {
|
|
2297
|
+
const hub = this.resolveHubConnection();
|
|
2298
|
+
if (hub) {
|
|
2299
|
+
const me = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/me", { method: "GET" });
|
|
2300
|
+
return {
|
|
2301
|
+
hubUrl: hub.hubUrl,
|
|
2302
|
+
userToken: hub.userToken,
|
|
2303
|
+
userId: String(me.id),
|
|
2304
|
+
username: String(me.username ?? "hub-admin"),
|
|
2305
|
+
role: String(me.role ?? "admin"),
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
return (0, hub_1.resolveHubClient)(this.store, this.ctx);
|
|
2310
|
+
}
|
|
2311
|
+
async serveSharingUsers(res) {
|
|
2312
|
+
const hub = this.resolveHubConnection();
|
|
2313
|
+
if (!hub)
|
|
2314
|
+
return this.jsonResponse(res, { users: [], error: "not_configured" });
|
|
2315
|
+
try {
|
|
2316
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/users", { method: "GET" });
|
|
2317
|
+
this.jsonResponse(res, { users: Array.isArray(data?.users) ? data.users : [] });
|
|
2318
|
+
}
|
|
2319
|
+
catch (err) {
|
|
2320
|
+
this.jsonResponse(res, { users: [], error: String(err) });
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// ─── Admin management endpoints (Hub-side data) ───
|
|
2324
|
+
async serveAdminSharedTasks(res) {
|
|
2325
|
+
const hub = this.resolveHubConnection();
|
|
2326
|
+
if (!hub)
|
|
2327
|
+
return this.jsonResponse(res, { tasks: [], error: "not_configured" });
|
|
2328
|
+
try {
|
|
2329
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-tasks", { method: "GET" });
|
|
2330
|
+
const tasks = Array.isArray(data?.tasks) ? data.tasks : [];
|
|
2331
|
+
for (const tk of tasks) {
|
|
2332
|
+
if (!tk.summary && tk.sourceTaskId) {
|
|
2333
|
+
const local = this.store.getTask(tk.sourceTaskId);
|
|
2334
|
+
if (local) {
|
|
2335
|
+
tk.summary = local.summary;
|
|
2336
|
+
tk.title = tk.title || local.title;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
this.jsonResponse(res, { tasks });
|
|
2341
|
+
}
|
|
2342
|
+
catch (err) {
|
|
2343
|
+
this.jsonResponse(res, { tasks: [], error: String(err) });
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
async handleAdminDeleteTask(res, p) {
|
|
2347
|
+
const hub = this.resolveHubConnection();
|
|
2348
|
+
if (!hub)
|
|
2349
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2350
|
+
const taskId = decodeURIComponent(p.replace("/api/admin/shared-tasks/", ""));
|
|
2351
|
+
try {
|
|
2352
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-tasks/${encodeURIComponent(taskId)}`, { method: "DELETE" });
|
|
2353
|
+
this.jsonResponse(res, { ok: true });
|
|
2354
|
+
}
|
|
2355
|
+
catch (err) {
|
|
2356
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async serveAdminSharedSkills(res) {
|
|
2360
|
+
const hub = this.resolveHubConnection();
|
|
2361
|
+
if (!hub)
|
|
2362
|
+
return this.jsonResponse(res, { skills: [], error: "not_configured" });
|
|
2363
|
+
try {
|
|
2364
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-skills", { method: "GET" });
|
|
2365
|
+
const skills = Array.isArray(data?.skills) ? data.skills : [];
|
|
2366
|
+
for (const sk of skills) {
|
|
2367
|
+
if (!sk.description && sk.sourceSkillId) {
|
|
2368
|
+
const local = this.store.getSkill(sk.sourceSkillId);
|
|
2369
|
+
if (local) {
|
|
2370
|
+
sk.description = sk.description || local.description;
|
|
2371
|
+
sk.name = sk.name || local.name;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
this.jsonResponse(res, { skills });
|
|
2376
|
+
}
|
|
2377
|
+
catch (err) {
|
|
2378
|
+
this.jsonResponse(res, { skills: [], error: String(err) });
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
async handleAdminDeleteSkill(res, p) {
|
|
2382
|
+
const hub = this.resolveHubConnection();
|
|
2383
|
+
if (!hub)
|
|
2384
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2385
|
+
const skillId = decodeURIComponent(p.replace("/api/admin/shared-skills/", ""));
|
|
2386
|
+
try {
|
|
2387
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-skills/${encodeURIComponent(skillId)}`, { method: "DELETE" });
|
|
2388
|
+
this.jsonResponse(res, { ok: true });
|
|
2389
|
+
}
|
|
2390
|
+
catch (err) {
|
|
2391
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
async serveAdminSharedMemories(res) {
|
|
2395
|
+
const hub = this.resolveHubConnection();
|
|
2396
|
+
if (!hub)
|
|
2397
|
+
return this.jsonResponse(res, { memories: [], error: "not_configured" });
|
|
2398
|
+
try {
|
|
2399
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/admin/shared-memories", { method: "GET" });
|
|
2400
|
+
const memories = Array.isArray(data?.memories) ? data.memories : [];
|
|
2401
|
+
for (const m of memories) {
|
|
2402
|
+
if (!m.content && m.sourceChunkId) {
|
|
2403
|
+
const local = this.store.getChunk(m.sourceChunkId);
|
|
2404
|
+
if (local) {
|
|
2405
|
+
m.content = local.content;
|
|
2406
|
+
if (!m.summary && local.summary)
|
|
2407
|
+
m.summary = local.summary;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
this.jsonResponse(res, { memories });
|
|
2412
|
+
}
|
|
2413
|
+
catch (err) {
|
|
2414
|
+
this.jsonResponse(res, { memories: [], error: String(err) });
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
async handleAdminDeleteMemory(res, p) {
|
|
2418
|
+
const hub = this.resolveHubConnection();
|
|
2419
|
+
if (!hub)
|
|
2420
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2421
|
+
const memoryId = decodeURIComponent(p.replace("/api/admin/shared-memories/", ""));
|
|
2422
|
+
try {
|
|
2423
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/admin/shared-memories/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
|
|
2424
|
+
this.jsonResponse(res, { ok: true });
|
|
2425
|
+
}
|
|
2426
|
+
catch (err) {
|
|
2427
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
async serveSharingNotifications(res, url) {
|
|
2431
|
+
const hub = this.resolveHubConnection();
|
|
2432
|
+
if (!hub)
|
|
2433
|
+
return this.jsonResponse(res, { notifications: [], unreadCount: 0 });
|
|
2434
|
+
try {
|
|
2435
|
+
const unread = url.searchParams.get("unread") === "1" ? "?unread=1" : "";
|
|
2436
|
+
const data = await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, `/api/v1/hub/notifications${unread}`);
|
|
2437
|
+
this.jsonResponse(res, data);
|
|
2438
|
+
}
|
|
2439
|
+
catch {
|
|
2440
|
+
this.jsonResponse(res, { notifications: [], unreadCount: 0 });
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
handleSharingNotificationsRead(req, res) {
|
|
2444
|
+
const hub = this.resolveHubConnection();
|
|
2445
|
+
if (!hub)
|
|
2446
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2447
|
+
this.readBody(req, async (raw) => {
|
|
2448
|
+
try {
|
|
2449
|
+
const body = JSON.parse(raw || "{}");
|
|
2450
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/notifications/read", { method: "POST", body: JSON.stringify(body) });
|
|
2451
|
+
this.jsonResponse(res, { ok: true });
|
|
2452
|
+
}
|
|
2453
|
+
catch (err) {
|
|
2454
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
handleSharingNotificationsClear(req, res) {
|
|
2459
|
+
const hub = this.resolveHubConnection();
|
|
2460
|
+
if (!hub)
|
|
2461
|
+
return this.jsonResponse(res, { ok: false, error: "not_configured" });
|
|
2462
|
+
this.readBody(req, async () => {
|
|
2463
|
+
try {
|
|
2464
|
+
await (0, hub_1.hubRequestJson)(hub.hubUrl, hub.userToken, "/api/v1/hub/notifications/clear", { method: "POST", body: "{}" });
|
|
2465
|
+
this.jsonResponse(res, { ok: true });
|
|
2466
|
+
}
|
|
2467
|
+
catch (err) {
|
|
2468
|
+
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
getLocalIPs() {
|
|
2473
|
+
const nets = node_os_1.default.networkInterfaces();
|
|
2474
|
+
const ips = [];
|
|
2475
|
+
for (const name of Object.keys(nets)) {
|
|
2476
|
+
for (const net of nets[name] ?? []) {
|
|
2477
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
2478
|
+
ips.push(net.address);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
return ips;
|
|
2483
|
+
}
|
|
2484
|
+
serveLocalIPs(res) {
|
|
2485
|
+
const ips = this.getLocalIPs();
|
|
2486
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2487
|
+
res.end(JSON.stringify({ ips }));
|
|
1074
2488
|
}
|
|
1075
2489
|
serveConfig(res) {
|
|
1076
2490
|
try {
|
|
@@ -1092,7 +2506,10 @@ class ViewerServer {
|
|
|
1092
2506
|
?? entries["memos-lite-openclaw-plugin"]
|
|
1093
2507
|
?? entries["memos-lite"]
|
|
1094
2508
|
?? {};
|
|
1095
|
-
if (pluginEntry.viewerPort
|
|
2509
|
+
if (pluginEntry.viewerPort != null) {
|
|
2510
|
+
result.viewerPort = pluginEntry.viewerPort;
|
|
2511
|
+
}
|
|
2512
|
+
else if (topEntry.viewerPort) {
|
|
1096
2513
|
result.viewerPort = topEntry.viewerPort;
|
|
1097
2514
|
}
|
|
1098
2515
|
this.jsonResponse(res, result);
|
|
@@ -1138,6 +2555,37 @@ class ViewerServer {
|
|
|
1138
2555
|
config.viewerPort = newCfg.viewerPort;
|
|
1139
2556
|
if (newCfg.telemetry !== undefined)
|
|
1140
2557
|
config.telemetry = newCfg.telemetry;
|
|
2558
|
+
if (newCfg.sharing !== undefined) {
|
|
2559
|
+
const existing = config.sharing || {};
|
|
2560
|
+
const merged = { ...existing, ...newCfg.sharing };
|
|
2561
|
+
if (newCfg.sharing.capabilities && existing.capabilities) {
|
|
2562
|
+
merged.capabilities = { ...existing.capabilities, ...newCfg.sharing.capabilities };
|
|
2563
|
+
}
|
|
2564
|
+
if (merged.role === "client" && merged.client) {
|
|
2565
|
+
const clientCfg = merged.client;
|
|
2566
|
+
const addr = String(clientCfg.hubAddress || "");
|
|
2567
|
+
if (addr) {
|
|
2568
|
+
const localIPs = this.getLocalIPs();
|
|
2569
|
+
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2570
|
+
try {
|
|
2571
|
+
const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`);
|
|
2572
|
+
if (localIPs.includes(u.hostname)) {
|
|
2573
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2574
|
+
res.end(JSON.stringify({ error: "cannot_join_self" }));
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
catch { }
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
if (merged.role === "hub") {
|
|
2582
|
+
merged.client = { hubAddress: "", userToken: "", teamToken: "" };
|
|
2583
|
+
}
|
|
2584
|
+
else if (merged.role === "client") {
|
|
2585
|
+
merged.hub = { port: 18800, teamName: "", teamToken: "" };
|
|
2586
|
+
}
|
|
2587
|
+
config.sharing = merged;
|
|
2588
|
+
}
|
|
1141
2589
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(cfgPath), { recursive: true });
|
|
1142
2590
|
node_fs_1.default.writeFileSync(cfgPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
1143
2591
|
this.log.info("Plugin config updated via Viewer");
|
|
@@ -1150,6 +2598,99 @@ class ViewerServer {
|
|
|
1150
2598
|
}
|
|
1151
2599
|
});
|
|
1152
2600
|
}
|
|
2601
|
+
handleUpdateUsername(req, res) {
|
|
2602
|
+
this.readBody(req, async (body) => {
|
|
2603
|
+
if (!this.ctx)
|
|
2604
|
+
return this.jsonResponse(res, { error: "sharing_unavailable" });
|
|
2605
|
+
try {
|
|
2606
|
+
const { username } = JSON.parse(body || "{}");
|
|
2607
|
+
if (!username || typeof username !== "string" || username.trim().length < 2 || username.trim().length > 32) {
|
|
2608
|
+
return this.jsonResponse(res, { error: "invalid_username" }, 400);
|
|
2609
|
+
}
|
|
2610
|
+
const trimmed = username.trim();
|
|
2611
|
+
const hubClient = await this.resolveHubClientAware();
|
|
2612
|
+
const result = await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/me/update-profile", {
|
|
2613
|
+
method: "POST",
|
|
2614
|
+
body: JSON.stringify({ username: trimmed }),
|
|
2615
|
+
});
|
|
2616
|
+
if (result.ok && result.userToken) {
|
|
2617
|
+
const sharing = this.ctx.config.sharing;
|
|
2618
|
+
if (sharing?.role === "hub") {
|
|
2619
|
+
try {
|
|
2620
|
+
const authPath = node_path_1.default.join(this.dataDir, "hub-auth.json");
|
|
2621
|
+
const authData = JSON.parse(node_fs_1.default.readFileSync(authPath, "utf8"));
|
|
2622
|
+
authData.bootstrapAdminToken = result.userToken;
|
|
2623
|
+
node_fs_1.default.writeFileSync(authPath, JSON.stringify(authData, null, 2), "utf-8");
|
|
2624
|
+
this.log.info("hub-auth.json updated with new admin token after username change");
|
|
2625
|
+
}
|
|
2626
|
+
catch (e) {
|
|
2627
|
+
this.log.warn(`Failed to update hub-auth.json: ${e}`);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
else {
|
|
2631
|
+
const persisted = this.store.getClientHubConnection();
|
|
2632
|
+
if (persisted) {
|
|
2633
|
+
this.store.setClientHubConnection({
|
|
2634
|
+
...persisted,
|
|
2635
|
+
username: result.username,
|
|
2636
|
+
userToken: result.userToken,
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
this.jsonResponse(res, result);
|
|
2642
|
+
}
|
|
2643
|
+
catch (err) {
|
|
2644
|
+
const msg = String(err?.message || err);
|
|
2645
|
+
if (msg.includes("409") || msg.includes("username_taken")) {
|
|
2646
|
+
return this.jsonResponse(res, { error: "username_taken" }, 409);
|
|
2647
|
+
}
|
|
2648
|
+
this.jsonResponse(res, { error: msg }, 500);
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
handleTestHubConnection(req, res) {
|
|
2653
|
+
this.readBody(req, async (body) => {
|
|
2654
|
+
try {
|
|
2655
|
+
const { hubUrl } = JSON.parse(body);
|
|
2656
|
+
if (!hubUrl) {
|
|
2657
|
+
this.jsonResponse(res, { ok: false, error: "hubUrl is required" });
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
try {
|
|
2661
|
+
const localIPs = this.getLocalIPs();
|
|
2662
|
+
localIPs.push("127.0.0.1", "localhost", "0.0.0.0");
|
|
2663
|
+
const parsed = new URL(hubUrl.startsWith("http") ? hubUrl : `http://${hubUrl}`);
|
|
2664
|
+
if (localIPs.includes(parsed.hostname)) {
|
|
2665
|
+
this.jsonResponse(res, { ok: false, error: "cannot_join_self" });
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
catch { }
|
|
2670
|
+
const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
|
|
2671
|
+
const ctrl = new AbortController();
|
|
2672
|
+
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
2673
|
+
try {
|
|
2674
|
+
const r = await fetch(url, { signal: ctrl.signal });
|
|
2675
|
+
clearTimeout(timeout);
|
|
2676
|
+
if (!r.ok) {
|
|
2677
|
+
this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
const info = await r.json();
|
|
2681
|
+
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
2682
|
+
}
|
|
2683
|
+
catch (e) {
|
|
2684
|
+
clearTimeout(timeout);
|
|
2685
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2686
|
+
this.jsonResponse(res, { ok: false, error: msg.includes("abort") ? "Connection timeout (8s)" : msg });
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
catch (e) {
|
|
2690
|
+
this.jsonResponse(res, { ok: false, error: String(e) });
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
1153
2694
|
handleTestModel(req, res) {
|
|
1154
2695
|
this.readBody(req, async (body) => {
|
|
1155
2696
|
try {
|
|
@@ -1531,7 +3072,7 @@ class ViewerServer {
|
|
|
1531
3072
|
// ─── Migration: scan OpenClaw built-in memory ───
|
|
1532
3073
|
getOpenClawHome() {
|
|
1533
3074
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1534
|
-
return
|
|
3075
|
+
return node_path_1.default.join(home, ".openclaw");
|
|
1535
3076
|
}
|
|
1536
3077
|
handleCleanupPolluted(res) {
|
|
1537
3078
|
try {
|
|
@@ -2461,8 +4002,8 @@ class ViewerServer {
|
|
|
2461
4002
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
2462
4003
|
req.on("end", () => cb(body));
|
|
2463
4004
|
}
|
|
2464
|
-
jsonResponse(res, data) {
|
|
2465
|
-
res.writeHead(
|
|
4005
|
+
jsonResponse(res, data, statusCode = 200) {
|
|
4006
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
2466
4007
|
res.end(JSON.stringify(data));
|
|
2467
4008
|
}
|
|
2468
4009
|
}
|