@memtensor/memos-local-openclaw-plugin 1.0.5 → 1.0.6-beta.10
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/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +24 -0
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +33 -5
- package/dist/client/connector.js.map +1 -1
- package/dist/client/hub.d.ts.map +1 -1
- package/dist/client/hub.js +4 -0
- package/dist/client/hub.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +116 -54
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +4 -0
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +32 -86
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +29 -13
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +33 -32
- package/dist/recall/engine.js.map +1 -1
- package/dist/storage/sqlite.d.ts +43 -7
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +179 -58
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/memory-get.d.ts.map +1 -1
- package/dist/tools/memory-get.js +4 -1
- package/dist/tools/memory-get.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +2 -7
- package/dist/update-check.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +115 -27
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +25 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +503 -206
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +273 -282
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/native-binding.cjs +32 -0
- package/scripts/postinstall.cjs +24 -11
- package/src/capture/index.ts +36 -0
- package/src/client/connector.ts +32 -5
- package/src/client/hub.ts +4 -0
- package/src/hub/server.ts +110 -50
- package/src/ingest/providers/index.ts +37 -92
- package/src/ingest/providers/openai.ts +31 -13
- package/src/recall/engine.ts +32 -30
- package/src/storage/sqlite.ts +196 -63
- package/src/tools/memory-get.ts +4 -1
- package/src/types.ts +2 -0
- package/src/update-check.ts +2 -7
- package/src/viewer/html.ts +115 -27
- package/src/viewer/server.ts +483 -172
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/telemetry.credentials.json +0 -5
package/dist/viewer/server.js
CHANGED
|
@@ -37,6 +37,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.ViewerServer = void 0;
|
|
40
|
+
exports.computeMigrationSuccess = computeMigrationSuccess;
|
|
41
|
+
exports.createInitialMigrationState = createInitialMigrationState;
|
|
42
|
+
exports.applyMigrationItemToState = applyMigrationItemToState;
|
|
40
43
|
const node_http_1 = __importDefault(require("node:http"));
|
|
41
44
|
const node_os_1 = __importDefault(require("node:os"));
|
|
42
45
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
@@ -57,10 +60,53 @@ const hub_1 = require("../client/hub");
|
|
|
57
60
|
const skill_sync_1 = require("../client/skill-sync");
|
|
58
61
|
const html_1 = require("./html");
|
|
59
62
|
const uuid_1 = require("uuid");
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
function createInitialStepFailures() {
|
|
64
|
+
return { summarization: 0, dedup: 0, embedding: 0 };
|
|
65
|
+
}
|
|
66
|
+
function computeMigrationSuccess(state) {
|
|
67
|
+
const sf = state.stepFailures;
|
|
68
|
+
return state.errors === 0 && sf.summarization === 0 && sf.dedup === 0 && sf.embedding === 0;
|
|
69
|
+
}
|
|
70
|
+
function createInitialMigrationState() {
|
|
71
|
+
const stepFailures = createInitialStepFailures();
|
|
72
|
+
return {
|
|
73
|
+
phase: "",
|
|
74
|
+
stored: 0,
|
|
75
|
+
skipped: 0,
|
|
76
|
+
merged: 0,
|
|
77
|
+
errors: 0,
|
|
78
|
+
processed: 0,
|
|
79
|
+
total: 0,
|
|
80
|
+
lastItem: null,
|
|
81
|
+
done: false,
|
|
82
|
+
stopped: false,
|
|
83
|
+
stepFailures,
|
|
84
|
+
success: computeMigrationSuccess({ errors: 0, stepFailures }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function applyMigrationItemToState(state, d) {
|
|
88
|
+
if (d.status === "stored")
|
|
89
|
+
state.stored++;
|
|
90
|
+
else if (d.status === "skipped" || d.status === "duplicate")
|
|
91
|
+
state.skipped++;
|
|
92
|
+
else if (d.status === "merged")
|
|
93
|
+
state.merged++;
|
|
94
|
+
else if (d.status === "error")
|
|
95
|
+
state.errors++;
|
|
96
|
+
if (Array.isArray(d.stepFailures)) {
|
|
97
|
+
for (const step of d.stepFailures) {
|
|
98
|
+
if (step === "summarization")
|
|
99
|
+
state.stepFailures.summarization++;
|
|
100
|
+
else if (step === "dedup")
|
|
101
|
+
state.stepFailures.dedup++;
|
|
102
|
+
else if (step === "embedding")
|
|
103
|
+
state.stepFailures.embedding++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
state.processed = d.index ?? state.processed + 1;
|
|
107
|
+
state.total = d.total ?? state.total;
|
|
108
|
+
state.lastItem = d;
|
|
109
|
+
state.success = computeMigrationSuccess(state);
|
|
64
110
|
}
|
|
65
111
|
class ViewerServer {
|
|
66
112
|
server = null;
|
|
@@ -87,7 +133,7 @@ class ViewerServer {
|
|
|
87
133
|
resetToken;
|
|
88
134
|
migrationRunning = false;
|
|
89
135
|
migrationAbort = false;
|
|
90
|
-
migrationState =
|
|
136
|
+
migrationState = createInitialMigrationState();
|
|
91
137
|
migrationSSEClients = [];
|
|
92
138
|
ppRunning = false;
|
|
93
139
|
ppAbort = false;
|
|
@@ -591,14 +637,15 @@ class ViewerServer {
|
|
|
591
637
|
if (chunkIds.length > 0) {
|
|
592
638
|
try {
|
|
593
639
|
const placeholders = chunkIds.map(() => "?").join(",");
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
640
|
+
if (this.sharingRole === "hub") {
|
|
641
|
+
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
642
|
+
for (const r of sharedRows)
|
|
643
|
+
sharingMap.set(r.source_chunk_id, r);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
const teamMetaRows = db.prepare(`SELECT chunk_id, visibility, group_id FROM team_shared_chunks WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
647
|
+
for (const r of teamMetaRows)
|
|
600
648
|
sharingMap.set(r.chunk_id, { visibility: r.visibility, group_id: r.group_id });
|
|
601
|
-
}
|
|
602
649
|
}
|
|
603
650
|
const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
|
|
604
651
|
for (const r of localRows)
|
|
@@ -662,7 +709,7 @@ class ViewerServer {
|
|
|
662
709
|
const db = this.store.db;
|
|
663
710
|
const items = tasks.map((t) => {
|
|
664
711
|
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id);
|
|
665
|
-
const
|
|
712
|
+
const hubTask = this.getHubTaskForLocal(t.id);
|
|
666
713
|
return {
|
|
667
714
|
id: t.id,
|
|
668
715
|
sessionKey: t.sessionKey,
|
|
@@ -674,7 +721,7 @@ class ViewerServer {
|
|
|
674
721
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
675
722
|
skillStatus: meta?.skill_status ?? null,
|
|
676
723
|
owner: meta?.owner ?? "agent:main",
|
|
677
|
-
sharingVisibility:
|
|
724
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
678
725
|
};
|
|
679
726
|
});
|
|
680
727
|
this.jsonResponse(res, { tasks: items, total, limit, offset });
|
|
@@ -703,7 +750,7 @@ class ViewerServer {
|
|
|
703
750
|
}));
|
|
704
751
|
const db = this.store.db;
|
|
705
752
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId);
|
|
706
|
-
const
|
|
753
|
+
const hubTask = this.getHubTaskForLocal(taskId);
|
|
707
754
|
this.jsonResponse(res, {
|
|
708
755
|
id: task.id,
|
|
709
756
|
sessionKey: task.sessionKey,
|
|
@@ -717,9 +764,9 @@ class ViewerServer {
|
|
|
717
764
|
skillStatus: meta?.skill_status ?? null,
|
|
718
765
|
skillReason: meta?.skill_reason ?? null,
|
|
719
766
|
skillLinks,
|
|
720
|
-
sharingVisibility:
|
|
721
|
-
sharingGroupId:
|
|
722
|
-
hubTaskId:
|
|
767
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
768
|
+
sharingGroupId: hubTask?.group_id ?? null,
|
|
769
|
+
hubTaskId: hubTask ? true : false,
|
|
723
770
|
});
|
|
724
771
|
}
|
|
725
772
|
serveStats(res, url) {
|
|
@@ -924,10 +971,9 @@ class ViewerServer {
|
|
|
924
971
|
if (visibility) {
|
|
925
972
|
skills = skills.filter(s => s.visibility === visibility);
|
|
926
973
|
}
|
|
927
|
-
const db = this.store.db;
|
|
928
974
|
const enriched = skills.map(s => {
|
|
929
|
-
const
|
|
930
|
-
return { ...s, sharingVisibility:
|
|
975
|
+
const hubSkill = this.getHubSkillForLocal(s.id);
|
|
976
|
+
return { ...s, sharingVisibility: hubSkill?.visibility ?? null };
|
|
931
977
|
});
|
|
932
978
|
this.jsonResponse(res, { skills: enriched });
|
|
933
979
|
}
|
|
@@ -942,10 +988,9 @@ class ViewerServer {
|
|
|
942
988
|
const versions = this.store.getSkillVersions(skillId);
|
|
943
989
|
const relatedTasks = this.store.getTasksBySkill(skillId);
|
|
944
990
|
const files = node_fs_1.default.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
|
|
945
|
-
const
|
|
946
|
-
const sharedSkill = db.prepare("SELECT visibility, group_id FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(skillId);
|
|
991
|
+
const hubSkill = this.getHubSkillForLocal(skillId);
|
|
947
992
|
this.jsonResponse(res, {
|
|
948
|
-
skill: { ...skill, sharingVisibility:
|
|
993
|
+
skill: { ...skill, sharingVisibility: hubSkill?.visibility ?? null, sharingGroupId: hubSkill?.group_id ?? null },
|
|
949
994
|
versions: versions.map(v => ({
|
|
950
995
|
id: v.id,
|
|
951
996
|
version: v.version,
|
|
@@ -1081,7 +1126,7 @@ class ViewerServer {
|
|
|
1081
1126
|
method: "POST",
|
|
1082
1127
|
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1083
1128
|
});
|
|
1084
|
-
if (hubClient.userId) {
|
|
1129
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
1085
1130
|
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1086
1131
|
this.store.upsertHubSkill({
|
|
1087
1132
|
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
@@ -1092,6 +1137,15 @@ class ViewerServer {
|
|
|
1092
1137
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1093
1138
|
});
|
|
1094
1139
|
}
|
|
1140
|
+
else {
|
|
1141
|
+
const conn = this.store.getClientHubConnection();
|
|
1142
|
+
this.store.upsertTeamSharedSkill(skillId, {
|
|
1143
|
+
hubSkillId: String(response?.skillId ?? ""),
|
|
1144
|
+
visibility: "public",
|
|
1145
|
+
groupId: null,
|
|
1146
|
+
hubInstanceId: conn?.hubInstanceId ?? "",
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1095
1149
|
hubSynced = true;
|
|
1096
1150
|
this.log.info(`Skill "${skill.name}" published to Hub`);
|
|
1097
1151
|
}
|
|
@@ -1100,8 +1154,10 @@ class ViewerServer {
|
|
|
1100
1154
|
method: "POST",
|
|
1101
1155
|
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1102
1156
|
});
|
|
1103
|
-
if (hubClient.userId)
|
|
1157
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1104
1158
|
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1159
|
+
else
|
|
1160
|
+
this.store.deleteTeamSharedSkill(skillId);
|
|
1105
1161
|
hubSynced = true;
|
|
1106
1162
|
this.log.info(`Skill "${skill.name}" unpublished from Hub`);
|
|
1107
1163
|
}
|
|
@@ -1399,7 +1455,8 @@ class ViewerServer {
|
|
|
1399
1455
|
});
|
|
1400
1456
|
}
|
|
1401
1457
|
else if (hubClient.userId) {
|
|
1402
|
-
this.store.
|
|
1458
|
+
const conn = this.store.getClientHubConnection();
|
|
1459
|
+
this.store.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
1403
1460
|
}
|
|
1404
1461
|
hubSynced = true;
|
|
1405
1462
|
}
|
|
@@ -1415,7 +1472,7 @@ class ViewerServer {
|
|
|
1415
1472
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1416
1473
|
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1417
1474
|
});
|
|
1418
|
-
if (hubClient.userId)
|
|
1475
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1419
1476
|
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1420
1477
|
this.store.deleteTeamSharedChunk(chunkId);
|
|
1421
1478
|
hubSynced = true;
|
|
@@ -1434,7 +1491,7 @@ class ViewerServer {
|
|
|
1434
1491
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1435
1492
|
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1436
1493
|
});
|
|
1437
|
-
if (hubClient.userId)
|
|
1494
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1438
1495
|
this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1439
1496
|
this.store.deleteTeamSharedChunk(chunkId);
|
|
1440
1497
|
hubSynced = true;
|
|
@@ -1488,21 +1545,24 @@ class ViewerServer {
|
|
|
1488
1545
|
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() })),
|
|
1489
1546
|
}),
|
|
1490
1547
|
});
|
|
1491
|
-
|
|
1548
|
+
const hubTaskId = String(response?.taskId ?? "");
|
|
1549
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
1492
1550
|
const existing = this.store.getHubTaskBySource(hubClient.userId, taskId);
|
|
1493
1551
|
this.store.upsertHubTask({
|
|
1494
|
-
id:
|
|
1552
|
+
id: hubTaskId || existing?.id || node_crypto_1.default.randomUUID(),
|
|
1495
1553
|
sourceTaskId: taskId, sourceUserId: hubClient.userId, title: refreshedTask.title ?? "",
|
|
1496
1554
|
summary: refreshedTask.summary ?? "", groupId: null, visibility: "public",
|
|
1497
1555
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1498
1556
|
});
|
|
1499
1557
|
}
|
|
1558
|
+
const conn = this.store.getClientHubConnection();
|
|
1559
|
+
this.store.markTaskShared(taskId, hubTaskId, chunks.length, "public", null, conn?.hubInstanceId ?? "");
|
|
1500
1560
|
hubSynced = true;
|
|
1501
1561
|
}
|
|
1502
1562
|
if (!isLocalShared) {
|
|
1503
1563
|
const originalOwner = task.owner;
|
|
1504
1564
|
const db = this.store.db;
|
|
1505
|
-
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());
|
|
1565
|
+
db.prepare("INSERT INTO local_shared_tasks (task_id, hub_task_id, original_owner, hub_instance_id, shared_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(task_id) DO UPDATE SET original_owner = excluded.original_owner, hub_instance_id = excluded.hub_instance_id, shared_at = excluded.shared_at").run(taskId, "", originalOwner, "", Date.now());
|
|
1506
1566
|
db.prepare("UPDATE tasks SET owner = 'public' WHERE id = ?").run(taskId);
|
|
1507
1567
|
}
|
|
1508
1568
|
}
|
|
@@ -1520,8 +1580,10 @@ class ViewerServer {
|
|
|
1520
1580
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1521
1581
|
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1522
1582
|
});
|
|
1523
|
-
if (hubClient.userId)
|
|
1583
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1524
1584
|
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1585
|
+
else
|
|
1586
|
+
this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1525
1587
|
hubSynced = true;
|
|
1526
1588
|
}
|
|
1527
1589
|
catch (err) {
|
|
@@ -1535,8 +1597,10 @@ class ViewerServer {
|
|
|
1535
1597
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1536
1598
|
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1537
1599
|
});
|
|
1538
|
-
if (hubClient.userId)
|
|
1600
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1539
1601
|
this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1602
|
+
else if (!isLocalShared)
|
|
1603
|
+
this.store.unmarkTaskShared(taskId);
|
|
1540
1604
|
hubSynced = true;
|
|
1541
1605
|
}
|
|
1542
1606
|
catch (err) {
|
|
@@ -1591,10 +1655,11 @@ class ViewerServer {
|
|
|
1591
1655
|
method: "POST",
|
|
1592
1656
|
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1593
1657
|
});
|
|
1594
|
-
|
|
1658
|
+
const hubSkillId = String(response?.skillId ?? "");
|
|
1659
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
1595
1660
|
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1596
1661
|
this.store.upsertHubSkill({
|
|
1597
|
-
id:
|
|
1662
|
+
id: hubSkillId || existing?.id || node_crypto_1.default.randomUUID(),
|
|
1598
1663
|
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
1599
1664
|
name: skill.name, description: skill.description, version: skill.version,
|
|
1600
1665
|
groupId: null, visibility: "public",
|
|
@@ -1602,6 +1667,10 @@ class ViewerServer {
|
|
|
1602
1667
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1603
1668
|
});
|
|
1604
1669
|
}
|
|
1670
|
+
else {
|
|
1671
|
+
const conn = this.store.getClientHubConnection();
|
|
1672
|
+
this.store.upsertTeamSharedSkill(skillId, { hubSkillId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
1673
|
+
}
|
|
1605
1674
|
hubSynced = true;
|
|
1606
1675
|
}
|
|
1607
1676
|
if (!isLocalShared)
|
|
@@ -1617,8 +1686,10 @@ class ViewerServer {
|
|
|
1617
1686
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1618
1687
|
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1619
1688
|
});
|
|
1620
|
-
if (hubClient.userId)
|
|
1689
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1621
1690
|
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1691
|
+
else
|
|
1692
|
+
this.store.deleteTeamSharedSkill(skillId);
|
|
1622
1693
|
hubSynced = true;
|
|
1623
1694
|
}
|
|
1624
1695
|
catch (err) {
|
|
@@ -1632,8 +1703,10 @@ class ViewerServer {
|
|
|
1632
1703
|
await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1633
1704
|
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1634
1705
|
});
|
|
1635
|
-
if (hubClient.userId)
|
|
1706
|
+
if (this.sharingRole === "hub" && hubClient.userId)
|
|
1636
1707
|
this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1708
|
+
else
|
|
1709
|
+
this.store.deleteTeamSharedSkill(skillId);
|
|
1637
1710
|
hubSynced = true;
|
|
1638
1711
|
}
|
|
1639
1712
|
catch (err) {
|
|
@@ -1650,28 +1723,52 @@ class ViewerServer {
|
|
|
1650
1723
|
}
|
|
1651
1724
|
});
|
|
1652
1725
|
}
|
|
1726
|
+
get sharingRole() {
|
|
1727
|
+
return this.ctx?.config?.sharing?.role;
|
|
1728
|
+
}
|
|
1729
|
+
isCurrentClientHubInstance(hubInstanceId) {
|
|
1730
|
+
if (this.sharingRole !== "client")
|
|
1731
|
+
return true;
|
|
1732
|
+
const scopedHubInstanceId = String(hubInstanceId ?? "");
|
|
1733
|
+
if (!scopedHubInstanceId)
|
|
1734
|
+
return true;
|
|
1735
|
+
const currentHubInstanceId = this.store.getClientHubConnection()?.hubInstanceId ?? "";
|
|
1736
|
+
if (!currentHubInstanceId)
|
|
1737
|
+
return true;
|
|
1738
|
+
return scopedHubInstanceId === currentHubInstanceId;
|
|
1739
|
+
}
|
|
1653
1740
|
getHubMemoryForChunk(chunkId) {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1741
|
+
if (this.sharingRole === "hub") {
|
|
1742
|
+
const db = this.store.db;
|
|
1743
|
+
return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
|
|
1744
|
+
}
|
|
1658
1745
|
const ts = this.store.getTeamSharedChunk(chunkId);
|
|
1659
|
-
if (ts) {
|
|
1660
|
-
return {
|
|
1661
|
-
source_chunk_id: chunkId,
|
|
1662
|
-
visibility: ts.visibility,
|
|
1663
|
-
group_id: ts.groupId,
|
|
1664
|
-
};
|
|
1746
|
+
if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
|
|
1747
|
+
return { source_chunk_id: chunkId, visibility: ts.visibility, group_id: ts.groupId };
|
|
1665
1748
|
}
|
|
1666
1749
|
return undefined;
|
|
1667
1750
|
}
|
|
1668
1751
|
getHubTaskForLocal(taskId) {
|
|
1669
|
-
|
|
1670
|
-
|
|
1752
|
+
if (this.sharingRole === "hub") {
|
|
1753
|
+
const db = this.store.db;
|
|
1754
|
+
return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
|
|
1755
|
+
}
|
|
1756
|
+
const shared = this.store.getLocalSharedTask(taskId);
|
|
1757
|
+
if (shared && shared.hubTaskId && this.isCurrentClientHubInstance(shared.hubInstanceId)) {
|
|
1758
|
+
return { source_task_id: taskId, visibility: shared.visibility, group_id: shared.groupId };
|
|
1759
|
+
}
|
|
1760
|
+
return undefined;
|
|
1671
1761
|
}
|
|
1672
1762
|
getHubSkillForLocal(skillId) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1763
|
+
if (this.sharingRole === "hub") {
|
|
1764
|
+
const db = this.store.db;
|
|
1765
|
+
return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
|
|
1766
|
+
}
|
|
1767
|
+
const ts = this.store.getTeamSharedSkill(skillId);
|
|
1768
|
+
if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
|
|
1769
|
+
return { source_skill_id: skillId, visibility: ts.visibility, group_id: ts.groupId };
|
|
1770
|
+
}
|
|
1771
|
+
return undefined;
|
|
1675
1772
|
}
|
|
1676
1773
|
handleDeleteSession(res, url) {
|
|
1677
1774
|
const key = url.searchParams.get("key");
|
|
@@ -1973,18 +2070,24 @@ class ViewerServer {
|
|
|
1973
2070
|
handleRetryJoin(req, res) {
|
|
1974
2071
|
this.readBody(req, async (_body) => {
|
|
1975
2072
|
if (!this.ctx)
|
|
1976
|
-
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable" });
|
|
2073
|
+
return this.jsonResponse(res, { ok: false, error: "sharing_unavailable", errorCode: "sharing_unavailable" });
|
|
1977
2074
|
const sharing = this.ctx.config.sharing;
|
|
1978
2075
|
if (!sharing?.enabled || sharing.role !== "client") {
|
|
1979
|
-
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode" });
|
|
2076
|
+
return this.jsonResponse(res, { ok: false, error: "not_in_client_mode", errorCode: "not_in_client_mode" });
|
|
1980
2077
|
}
|
|
1981
2078
|
const hubAddress = sharing.client?.hubAddress ?? "";
|
|
1982
2079
|
const teamToken = sharing.client?.teamToken ?? "";
|
|
1983
2080
|
if (!hubAddress || !teamToken) {
|
|
1984
|
-
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token" });
|
|
2081
|
+
return this.jsonResponse(res, { ok: false, error: "missing_hub_address_or_team_token", errorCode: "missing_config" });
|
|
2082
|
+
}
|
|
2083
|
+
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
2084
|
+
try {
|
|
2085
|
+
await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
2086
|
+
}
|
|
2087
|
+
catch {
|
|
2088
|
+
return this.jsonResponse(res, { ok: false, error: "hub_unreachable", errorCode: "hub_unreachable" });
|
|
1985
2089
|
}
|
|
1986
2090
|
try {
|
|
1987
|
-
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
1988
2091
|
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
1989
2092
|
const nickname = sharing.client?.nickname;
|
|
1990
2093
|
const username = nickname || os.userInfo().username || "user";
|
|
@@ -1996,6 +2099,12 @@ class ViewerServer {
|
|
|
1996
2099
|
body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true, identityKey: existingIdentityKey }),
|
|
1997
2100
|
});
|
|
1998
2101
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
2102
|
+
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
2103
|
+
try {
|
|
2104
|
+
const info = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
2105
|
+
hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
|
|
2106
|
+
}
|
|
2107
|
+
catch { /* best-effort */ }
|
|
1999
2108
|
this.store.setClientHubConnection({
|
|
2000
2109
|
hubUrl,
|
|
2001
2110
|
userId: String(result.userId || ""),
|
|
@@ -2005,11 +2114,22 @@ class ViewerServer {
|
|
|
2005
2114
|
connectedAt: Date.now(),
|
|
2006
2115
|
identityKey: returnedIdentityKey,
|
|
2007
2116
|
lastKnownStatus: result.status || "",
|
|
2117
|
+
hubInstanceId,
|
|
2008
2118
|
});
|
|
2119
|
+
if (result.status === "blocked") {
|
|
2120
|
+
return this.jsonResponse(res, { ok: false, error: "blocked", errorCode: "blocked" });
|
|
2121
|
+
}
|
|
2009
2122
|
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
2010
2123
|
}
|
|
2011
2124
|
catch (err) {
|
|
2012
|
-
|
|
2125
|
+
const errStr = String(err);
|
|
2126
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
2127
|
+
return this.jsonResponse(res, { ok: false, error: "username_taken", errorCode: "username_taken" });
|
|
2128
|
+
}
|
|
2129
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
2130
|
+
return this.jsonResponse(res, { ok: false, error: "invalid_team_token", errorCode: "invalid_team_token" });
|
|
2131
|
+
}
|
|
2132
|
+
this.jsonResponse(res, { ok: false, error: errStr, errorCode: "unknown" });
|
|
2013
2133
|
}
|
|
2014
2134
|
});
|
|
2015
2135
|
}
|
|
@@ -2227,9 +2347,10 @@ class ViewerServer {
|
|
|
2227
2347
|
}),
|
|
2228
2348
|
});
|
|
2229
2349
|
const hubUserId = hubClient.userId;
|
|
2230
|
-
|
|
2350
|
+
const hubTaskId = String(response?.taskId ?? task.id);
|
|
2351
|
+
if (this.sharingRole === "hub" && hubUserId) {
|
|
2231
2352
|
this.store.upsertHubTask({
|
|
2232
|
-
id:
|
|
2353
|
+
id: hubTaskId,
|
|
2233
2354
|
sourceTaskId: task.id,
|
|
2234
2355
|
sourceUserId: hubUserId,
|
|
2235
2356
|
title: task.title,
|
|
@@ -2240,6 +2361,10 @@ class ViewerServer {
|
|
|
2240
2361
|
updatedAt: task.updatedAt ?? Date.now(),
|
|
2241
2362
|
});
|
|
2242
2363
|
}
|
|
2364
|
+
else {
|
|
2365
|
+
const conn = this.store.getClientHubConnection();
|
|
2366
|
+
this.store.markTaskShared(task.id, hubTaskId, chunks.length, visibility, groupId, conn?.hubInstanceId ?? "");
|
|
2367
|
+
}
|
|
2243
2368
|
this.jsonResponse(res, { ok: true, taskId, visibility, response });
|
|
2244
2369
|
}
|
|
2245
2370
|
catch (err) {
|
|
@@ -2263,8 +2388,12 @@ class ViewerServer {
|
|
|
2263
2388
|
body: JSON.stringify({ sourceTaskId: task.id }),
|
|
2264
2389
|
});
|
|
2265
2390
|
const hubUserId = hubClient.userId;
|
|
2266
|
-
if (hubUserId)
|
|
2391
|
+
if (this.sharingRole === "hub" && hubUserId)
|
|
2267
2392
|
this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
2393
|
+
else if (task.owner === "public")
|
|
2394
|
+
this.store.downgradeTeamSharedTaskToLocal(task.id);
|
|
2395
|
+
else
|
|
2396
|
+
this.store.unmarkTaskShared(task.id);
|
|
2268
2397
|
this.jsonResponse(res, { ok: true, taskId });
|
|
2269
2398
|
}
|
|
2270
2399
|
catch (err) {
|
|
@@ -2319,7 +2448,8 @@ class ViewerServer {
|
|
|
2319
2448
|
});
|
|
2320
2449
|
}
|
|
2321
2450
|
else if (hubClient.userId) {
|
|
2322
|
-
this.store.
|
|
2451
|
+
const conn = this.store.getClientHubConnection();
|
|
2452
|
+
this.store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
2323
2453
|
}
|
|
2324
2454
|
this.jsonResponse(res, { ok: true, chunkId, visibility, response });
|
|
2325
2455
|
}
|
|
@@ -2341,9 +2471,10 @@ class ViewerServer {
|
|
|
2341
2471
|
body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
2342
2472
|
});
|
|
2343
2473
|
const hubUserId = hubClient.userId;
|
|
2344
|
-
if (hubUserId)
|
|
2474
|
+
if (this.sharingRole === "hub" && hubUserId)
|
|
2345
2475
|
this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
2346
|
-
|
|
2476
|
+
else
|
|
2477
|
+
this.store.deleteTeamSharedChunk(chunkId);
|
|
2347
2478
|
this.jsonResponse(res, { ok: true, chunkId });
|
|
2348
2479
|
}
|
|
2349
2480
|
catch (err) {
|
|
@@ -2391,7 +2522,7 @@ class ViewerServer {
|
|
|
2391
2522
|
}),
|
|
2392
2523
|
});
|
|
2393
2524
|
const hubUserId = hubClient.userId;
|
|
2394
|
-
if (hubUserId) {
|
|
2525
|
+
if (this.sharingRole === "hub" && hubUserId) {
|
|
2395
2526
|
const existing = this.store.getHubSkillBySource(hubUserId, skillId);
|
|
2396
2527
|
this.store.upsertHubSkill({
|
|
2397
2528
|
id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
|
|
@@ -2408,6 +2539,15 @@ class ViewerServer {
|
|
|
2408
2539
|
updatedAt: Date.now(),
|
|
2409
2540
|
});
|
|
2410
2541
|
}
|
|
2542
|
+
else {
|
|
2543
|
+
const conn = this.store.getClientHubConnection();
|
|
2544
|
+
this.store.upsertTeamSharedSkill(skillId, {
|
|
2545
|
+
hubSkillId: String(response?.skillId ?? ""),
|
|
2546
|
+
visibility,
|
|
2547
|
+
groupId,
|
|
2548
|
+
hubInstanceId: conn?.hubInstanceId ?? "",
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2411
2551
|
this.jsonResponse(res, { ok: true, skillId, visibility, response });
|
|
2412
2552
|
}
|
|
2413
2553
|
catch (err) {
|
|
@@ -2431,8 +2571,10 @@ class ViewerServer {
|
|
|
2431
2571
|
body: JSON.stringify({ sourceSkillId: skill.id }),
|
|
2432
2572
|
});
|
|
2433
2573
|
const hubUserId = hubClient.userId;
|
|
2434
|
-
if (hubUserId)
|
|
2574
|
+
if (this.sharingRole === "hub" && hubUserId)
|
|
2435
2575
|
this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
2576
|
+
else
|
|
2577
|
+
this.store.deleteTeamSharedSkill(skill.id);
|
|
2436
2578
|
this.jsonResponse(res, { ok: true, skillId });
|
|
2437
2579
|
}
|
|
2438
2580
|
catch (err) {
|
|
@@ -2939,18 +3081,20 @@ class ViewerServer {
|
|
|
2939
3081
|
const isClient = newEnabled && newRole === "client";
|
|
2940
3082
|
if (wasClient && !isClient) {
|
|
2941
3083
|
await this.withdrawOrLeaveHub();
|
|
3084
|
+
this.store.clearAllTeamSharingState();
|
|
2942
3085
|
this.store.clearClientHubConnection();
|
|
2943
|
-
this.log.info("Client hub connection cleared (sharing disabled or role changed)");
|
|
3086
|
+
this.log.info("Client hub connection and team sharing state cleared (sharing disabled or role changed)");
|
|
2944
3087
|
}
|
|
2945
3088
|
if (wasClient && isClient) {
|
|
2946
3089
|
const newClientAddr = String(merged.client?.hubAddress || "");
|
|
2947
3090
|
if (newClientAddr && oldClientHubAddress && (0, hub_1.normalizeHubUrl)(newClientAddr) !== (0, hub_1.normalizeHubUrl)(oldClientHubAddress)) {
|
|
2948
3091
|
this.notifyHubLeave();
|
|
3092
|
+
this.store.clearAllTeamSharingState();
|
|
2949
3093
|
const oldConn = this.store.getClientHubConnection();
|
|
2950
3094
|
if (oldConn) {
|
|
2951
|
-
this.store.setClientHubConnection({ ...oldConn, hubUrl: (0, hub_1.normalizeHubUrl)(newClientAddr), userToken: "", lastKnownStatus: "hub_changed" });
|
|
3095
|
+
this.store.setClientHubConnection({ ...oldConn, hubUrl: (0, hub_1.normalizeHubUrl)(newClientAddr), userToken: "", hubInstanceId: "", lastKnownStatus: "hub_changed" });
|
|
2952
3096
|
}
|
|
2953
|
-
this.log.info("Client hub connection
|
|
3097
|
+
this.log.info("Client hub connection and team sharing state cleared (switched to different Hub)");
|
|
2954
3098
|
}
|
|
2955
3099
|
}
|
|
2956
3100
|
if (merged.role === "hub") {
|
|
@@ -2970,24 +3114,24 @@ class ViewerServer {
|
|
|
2970
3114
|
const nowClient = Boolean(finalSharing?.enabled) && finalSharing?.role === "client";
|
|
2971
3115
|
const previouslyClient = oldSharingEnabled && oldSharingRole === "client";
|
|
2972
3116
|
let joinStatus;
|
|
3117
|
+
let joinError;
|
|
2973
3118
|
if (nowClient && !previouslyClient) {
|
|
2974
3119
|
try {
|
|
2975
3120
|
joinStatus = await this.autoJoinOnSave(finalSharing);
|
|
2976
3121
|
}
|
|
2977
3122
|
catch (e) {
|
|
2978
|
-
|
|
3123
|
+
const msg = String(e instanceof Error ? e.message : e);
|
|
3124
|
+
this.log.warn(`Auto-join on save failed: ${msg}`);
|
|
3125
|
+
if (msg === "hub_unreachable" || msg === "username_taken" || msg === "invalid_team_token") {
|
|
3126
|
+
joinError = msg;
|
|
3127
|
+
}
|
|
2979
3128
|
}
|
|
2980
3129
|
}
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
}
|
|
2987
|
-
catch (sig) {
|
|
2988
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
2989
|
-
}
|
|
2990
|
-
}, 500);
|
|
3130
|
+
if (joinError) {
|
|
3131
|
+
this.jsonResponse(res, { ok: true, joinError, restart: false });
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
this.jsonResponseAndRestart(res, { ok: true, joinStatus, restart: true }, "config-save");
|
|
2991
3135
|
}
|
|
2992
3136
|
catch (e) {
|
|
2993
3137
|
this.log.warn(`handleSaveConfig error: ${e}`);
|
|
@@ -3003,17 +3147,42 @@ class ViewerServer {
|
|
|
3003
3147
|
if (!hubAddress || !teamToken)
|
|
3004
3148
|
return undefined;
|
|
3005
3149
|
const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
|
|
3150
|
+
try {
|
|
3151
|
+
await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
3152
|
+
}
|
|
3153
|
+
catch {
|
|
3154
|
+
throw new Error("hub_unreachable");
|
|
3155
|
+
}
|
|
3006
3156
|
const os = await Promise.resolve().then(() => __importStar(require("os")));
|
|
3007
3157
|
const nickname = String(clientCfg?.nickname || "");
|
|
3008
3158
|
const username = nickname || os.userInfo().username || "user";
|
|
3009
3159
|
const hostname = os.hostname() || "unknown";
|
|
3010
3160
|
const persisted = this.store.getClientHubConnection();
|
|
3011
3161
|
const existingIdentityKey = persisted?.identityKey || "";
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3162
|
+
let result;
|
|
3163
|
+
try {
|
|
3164
|
+
result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
|
|
3165
|
+
method: "POST",
|
|
3166
|
+
body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
catch (err) {
|
|
3170
|
+
const errStr = String(err);
|
|
3171
|
+
if (errStr.includes("(409)") || errStr.includes("username_taken")) {
|
|
3172
|
+
throw new Error("username_taken");
|
|
3173
|
+
}
|
|
3174
|
+
if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
|
|
3175
|
+
throw new Error("invalid_team_token");
|
|
3176
|
+
}
|
|
3177
|
+
throw err;
|
|
3178
|
+
}
|
|
3016
3179
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
3180
|
+
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
3181
|
+
try {
|
|
3182
|
+
const info = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
|
|
3183
|
+
hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
|
|
3184
|
+
}
|
|
3185
|
+
catch { /* best-effort */ }
|
|
3017
3186
|
this.store.setClientHubConnection({
|
|
3018
3187
|
hubUrl,
|
|
3019
3188
|
userId: String(result.userId || ""),
|
|
@@ -3023,6 +3192,7 @@ class ViewerServer {
|
|
|
3023
3192
|
connectedAt: Date.now(),
|
|
3024
3193
|
identityKey: returnedIdentityKey,
|
|
3025
3194
|
lastKnownStatus: result.status || "",
|
|
3195
|
+
hubInstanceId,
|
|
3026
3196
|
});
|
|
3027
3197
|
this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
|
|
3028
3198
|
if (result.userToken) {
|
|
@@ -3034,6 +3204,7 @@ class ViewerServer {
|
|
|
3034
3204
|
this.readBody(_req, async () => {
|
|
3035
3205
|
try {
|
|
3036
3206
|
await this.withdrawOrLeaveHub();
|
|
3207
|
+
this.store.clearAllTeamSharingState();
|
|
3037
3208
|
this.store.clearClientHubConnection();
|
|
3038
3209
|
const configPath = this.getOpenClawConfigPath();
|
|
3039
3210
|
if (configPath && node_fs_1.default.existsSync(configPath)) {
|
|
@@ -3050,16 +3221,7 @@ class ViewerServer {
|
|
|
3050
3221
|
this.log.info("handleLeaveTeam: config updated, sharing disabled");
|
|
3051
3222
|
}
|
|
3052
3223
|
}
|
|
3053
|
-
this.
|
|
3054
|
-
setTimeout(() => {
|
|
3055
|
-
this.log.info("handleLeaveTeam: triggering gateway restart via SIGUSR1...");
|
|
3056
|
-
try {
|
|
3057
|
-
process.kill(process.pid, "SIGUSR1");
|
|
3058
|
-
}
|
|
3059
|
-
catch (sig) {
|
|
3060
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
3061
|
-
}
|
|
3062
|
-
}, 500);
|
|
3224
|
+
this.jsonResponseAndRestart(res, { ok: true, restart: true }, "handleLeaveTeam");
|
|
3063
3225
|
}
|
|
3064
3226
|
catch (e) {
|
|
3065
3227
|
this.log.warn(`handleLeaveTeam error: ${e}`);
|
|
@@ -3233,17 +3395,45 @@ class ViewerServer {
|
|
|
3233
3395
|
}
|
|
3234
3396
|
}
|
|
3235
3397
|
catch { }
|
|
3236
|
-
const
|
|
3398
|
+
const baseUrl = hubUrl.replace(/\/+$/, "");
|
|
3399
|
+
const infoUrl = baseUrl + "/api/v1/hub/info";
|
|
3237
3400
|
const ctrl = new AbortController();
|
|
3238
3401
|
const timeout = setTimeout(() => ctrl.abort(), 8000);
|
|
3239
3402
|
try {
|
|
3240
|
-
const r = await fetch(
|
|
3403
|
+
const r = await fetch(infoUrl, { signal: ctrl.signal });
|
|
3241
3404
|
clearTimeout(timeout);
|
|
3242
3405
|
if (!r.ok) {
|
|
3243
3406
|
this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
|
|
3244
3407
|
return;
|
|
3245
3408
|
}
|
|
3246
3409
|
const info = await r.json();
|
|
3410
|
+
const { teamToken, nickname } = JSON.parse(body);
|
|
3411
|
+
if (teamToken) {
|
|
3412
|
+
const username = (typeof nickname === "string" && nickname.trim()) || node_os_1.default.userInfo().username || "user";
|
|
3413
|
+
const persisted = this.store.getClientHubConnection();
|
|
3414
|
+
const identityKey = persisted?.identityKey || "";
|
|
3415
|
+
try {
|
|
3416
|
+
const joinR = await fetch(baseUrl + "/api/v1/hub/join", {
|
|
3417
|
+
method: "POST",
|
|
3418
|
+
headers: { "content-type": "application/json" },
|
|
3419
|
+
body: JSON.stringify({ teamToken, username, identityKey, deviceName: node_os_1.default.hostname(), dryRun: true }),
|
|
3420
|
+
});
|
|
3421
|
+
const joinData = await joinR.json();
|
|
3422
|
+
if (!joinR.ok && joinData.error === "username_taken") {
|
|
3423
|
+
this.jsonResponse(res, { ok: false, error: "username_taken", teamName: info.teamName || "" });
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
if (!joinR.ok && joinData.error === "invalid_team_token") {
|
|
3427
|
+
this.jsonResponse(res, { ok: false, error: "invalid_team_token", teamName: info.teamName || "" });
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
if (joinR.ok && joinData.status === "blocked") {
|
|
3431
|
+
this.jsonResponse(res, { ok: false, error: "blocked", teamName: info.teamName || "" });
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
catch { /* join check is best-effort; connection itself is OK */ }
|
|
3436
|
+
}
|
|
3247
3437
|
this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
|
|
3248
3438
|
}
|
|
3249
3439
|
catch (e) {
|
|
@@ -3330,26 +3520,35 @@ class ViewerServer {
|
|
|
3330
3520
|
return null;
|
|
3331
3521
|
}
|
|
3332
3522
|
async handleUpdateCheck(res) {
|
|
3523
|
+
const sendNoStore = (data, statusCode = 200) => {
|
|
3524
|
+
res.writeHead(statusCode, {
|
|
3525
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
3526
|
+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
3527
|
+
"Pragma": "no-cache",
|
|
3528
|
+
"Expires": "0",
|
|
3529
|
+
});
|
|
3530
|
+
res.end(JSON.stringify(data));
|
|
3531
|
+
};
|
|
3333
3532
|
try {
|
|
3334
3533
|
const pkgPath = this.findPluginPackageJson();
|
|
3335
3534
|
if (!pkgPath) {
|
|
3336
|
-
|
|
3535
|
+
sendNoStore({ updateAvailable: false, error: "package.json not found" });
|
|
3337
3536
|
return;
|
|
3338
3537
|
}
|
|
3339
3538
|
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf-8"));
|
|
3340
3539
|
const current = pkg.version;
|
|
3341
3540
|
const name = pkg.name;
|
|
3342
3541
|
if (!current || !name) {
|
|
3343
|
-
|
|
3542
|
+
sendNoStore({ updateAvailable: false, current });
|
|
3344
3543
|
return;
|
|
3345
3544
|
}
|
|
3346
3545
|
const { computeUpdateCheck } = await Promise.resolve().then(() => __importStar(require("../update-check")));
|
|
3347
3546
|
const result = await computeUpdateCheck(name, current, fetch, 6_000);
|
|
3348
3547
|
if (!result) {
|
|
3349
|
-
|
|
3548
|
+
sendNoStore({ updateAvailable: false, current, packageName: name });
|
|
3350
3549
|
return;
|
|
3351
3550
|
}
|
|
3352
|
-
|
|
3551
|
+
sendNoStore({
|
|
3353
3552
|
updateAvailable: result.updateAvailable,
|
|
3354
3553
|
current: result.current,
|
|
3355
3554
|
latest: result.latest,
|
|
@@ -3361,7 +3560,7 @@ class ViewerServer {
|
|
|
3361
3560
|
}
|
|
3362
3561
|
catch (e) {
|
|
3363
3562
|
this.log.warn(`handleUpdateCheck error: ${e}`);
|
|
3364
|
-
|
|
3563
|
+
sendNoStore({ updateAvailable: false, error: String(e) });
|
|
3365
3564
|
}
|
|
3366
3565
|
}
|
|
3367
3566
|
handleUpdateInstall(req, res) {
|
|
@@ -3369,13 +3568,14 @@ class ViewerServer {
|
|
|
3369
3568
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
3370
3569
|
req.on("end", () => {
|
|
3371
3570
|
try {
|
|
3372
|
-
const { packageSpec: rawSpec } = JSON.parse(body);
|
|
3571
|
+
const { packageSpec: rawSpec, targetVersion: rawTargetVersion } = JSON.parse(body);
|
|
3373
3572
|
if (!rawSpec || typeof rawSpec !== "string") {
|
|
3374
3573
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
3375
3574
|
res.end(JSON.stringify({ ok: false, error: "Missing packageSpec" }));
|
|
3376
3575
|
return;
|
|
3377
3576
|
}
|
|
3378
3577
|
const packageSpec = rawSpec.trim().replace(/^(?:npx\s+)?openclaw\s+plugins\s+install\s+/i, "");
|
|
3578
|
+
const targetVersion = typeof rawTargetVersion === "string" ? rawTargetVersion.trim() : "";
|
|
3379
3579
|
const allowed = /^@[\w-]+\/[\w.-]+(@[\w.-]+)?$/;
|
|
3380
3580
|
this.log.info(`update-install: received packageSpec="${packageSpec}" (len=${packageSpec.length})`);
|
|
3381
3581
|
if (!allowed.test(packageSpec)) {
|
|
@@ -3396,18 +3596,50 @@ class ViewerServer {
|
|
|
3396
3596
|
const shortName = pluginName?.replace(/^@[\w-]+\//, "") ?? "memos-local-openclaw-plugin";
|
|
3397
3597
|
const extDir = node_path_1.default.join(node_os_1.default.homedir(), ".openclaw", "extensions", shortName);
|
|
3398
3598
|
const tmpDir = node_path_1.default.join(node_os_1.default.tmpdir(), `openclaw-update-${Date.now()}`);
|
|
3599
|
+
const backupDir = node_path_1.default.join(node_path_1.default.dirname(extDir), `${shortName}.backup-${Date.now()}`);
|
|
3600
|
+
let backupReady = false;
|
|
3601
|
+
const cleanupTmpDir = () => {
|
|
3602
|
+
try {
|
|
3603
|
+
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3604
|
+
}
|
|
3605
|
+
catch { }
|
|
3606
|
+
};
|
|
3607
|
+
const rollbackInstall = () => {
|
|
3608
|
+
try {
|
|
3609
|
+
node_fs_1.default.rmSync(extDir, { recursive: true, force: true });
|
|
3610
|
+
}
|
|
3611
|
+
catch { }
|
|
3612
|
+
if (!backupReady)
|
|
3613
|
+
return;
|
|
3614
|
+
try {
|
|
3615
|
+
node_fs_1.default.renameSync(backupDir, extDir);
|
|
3616
|
+
backupReady = false;
|
|
3617
|
+
this.log.info(`update-install: restored previous version from ${backupDir}`);
|
|
3618
|
+
}
|
|
3619
|
+
catch (restoreErr) {
|
|
3620
|
+
this.log.warn(`update-install: failed to restore previous version: ${restoreErr?.message ?? restoreErr}`);
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
const discardBackup = () => {
|
|
3624
|
+
if (!backupReady)
|
|
3625
|
+
return;
|
|
3626
|
+
try {
|
|
3627
|
+
node_fs_1.default.rmSync(backupDir, { recursive: true, force: true });
|
|
3628
|
+
backupReady = false;
|
|
3629
|
+
}
|
|
3630
|
+
catch (cleanupErr) {
|
|
3631
|
+
this.log.warn(`update-install: failed to remove backup dir ${backupDir}: ${cleanupErr?.message ?? cleanupErr}`);
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3399
3634
|
// Download via npm pack, extract, and replace extension dir.
|
|
3400
3635
|
// Does NOT touch openclaw.json → no config watcher SIGUSR1.
|
|
3401
3636
|
this.log.info(`update-install: downloading ${packageSpec} via npm pack...`);
|
|
3402
3637
|
node_fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
|
3403
|
-
(0, node_child_process_1.exec)(`npm pack ${packageSpec} --pack-destination ${tmpDir}`, { timeout: 60_000 }, (packErr, packOut) => {
|
|
3638
|
+
(0, node_child_process_1.exec)(`npm pack ${packageSpec} --pack-destination ${tmpDir} --prefer-online`, { timeout: 60_000 }, (packErr, packOut) => {
|
|
3404
3639
|
if (packErr) {
|
|
3405
3640
|
this.log.warn(`update-install: npm pack failed: ${packErr.message}`);
|
|
3406
3641
|
this.jsonResponse(res, { ok: false, error: `Download failed: ${packErr.message}` });
|
|
3407
|
-
|
|
3408
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3409
|
-
}
|
|
3410
|
-
catch { }
|
|
3642
|
+
cleanupTmpDir();
|
|
3411
3643
|
return;
|
|
3412
3644
|
}
|
|
3413
3645
|
const tgzFile = packOut.trim().split("\n").pop();
|
|
@@ -3419,83 +3651,85 @@ class ViewerServer {
|
|
|
3419
3651
|
if (tarErr) {
|
|
3420
3652
|
this.log.warn(`update-install: tar extract failed: ${tarErr.message}`);
|
|
3421
3653
|
this.jsonResponse(res, { ok: false, error: `Extract failed: ${tarErr.message}` });
|
|
3422
|
-
|
|
3423
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3424
|
-
}
|
|
3425
|
-
catch { }
|
|
3654
|
+
cleanupTmpDir();
|
|
3426
3655
|
return;
|
|
3427
3656
|
}
|
|
3428
3657
|
// npm pack extracts to a "package" subdirectory
|
|
3429
3658
|
const srcDir = node_path_1.default.join(extractDir, "package");
|
|
3430
3659
|
if (!node_fs_1.default.existsSync(srcDir)) {
|
|
3431
3660
|
this.jsonResponse(res, { ok: false, error: "Extracted package has no 'package' dir" });
|
|
3432
|
-
|
|
3433
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3434
|
-
}
|
|
3435
|
-
catch { }
|
|
3661
|
+
cleanupTmpDir();
|
|
3436
3662
|
return;
|
|
3437
3663
|
}
|
|
3438
3664
|
// Replace extension directory
|
|
3439
3665
|
this.log.info(`update-install: replacing ${extDir}...`);
|
|
3440
3666
|
try {
|
|
3441
|
-
node_fs_1.default.
|
|
3667
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(extDir), { recursive: true });
|
|
3668
|
+
try {
|
|
3669
|
+
node_fs_1.default.rmSync(backupDir, { recursive: true, force: true });
|
|
3670
|
+
}
|
|
3671
|
+
catch { }
|
|
3672
|
+
if (node_fs_1.default.existsSync(extDir)) {
|
|
3673
|
+
node_fs_1.default.renameSync(extDir, backupDir);
|
|
3674
|
+
backupReady = true;
|
|
3675
|
+
}
|
|
3676
|
+
node_fs_1.default.renameSync(srcDir, extDir);
|
|
3677
|
+
}
|
|
3678
|
+
catch (replaceErr) {
|
|
3679
|
+
this.log.warn(`update-install: replace failed: ${replaceErr?.message ?? replaceErr}`);
|
|
3680
|
+
cleanupTmpDir();
|
|
3681
|
+
rollbackInstall();
|
|
3682
|
+
this.jsonResponse(res, { ok: false, error: `Replace failed: ${replaceErr?.message ?? replaceErr}` });
|
|
3683
|
+
return;
|
|
3442
3684
|
}
|
|
3443
|
-
catch { }
|
|
3444
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(extDir), { recursive: true });
|
|
3445
|
-
node_fs_1.default.renameSync(srcDir, extDir);
|
|
3446
3685
|
// Install dependencies
|
|
3447
3686
|
this.log.info(`update-install: installing dependencies...`);
|
|
3448
|
-
|
|
3687
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3688
|
+
(0, node_child_process_1.execFile)(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
|
|
3449
3689
|
if (npmErr) {
|
|
3450
|
-
try {
|
|
3451
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3452
|
-
}
|
|
3453
|
-
catch { }
|
|
3454
3690
|
this.log.warn(`update-install: npm install failed: ${npmErr.message}`);
|
|
3691
|
+
cleanupTmpDir();
|
|
3692
|
+
rollbackInstall();
|
|
3455
3693
|
this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
|
|
3456
3694
|
return;
|
|
3457
3695
|
}
|
|
3458
|
-
|
|
3459
|
-
(0, node_child_process_1.exec)(`cd ${extDir} && npm rebuild better-sqlite3`, { timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
|
|
3696
|
+
(0, node_child_process_1.execFile)(npmCmd, ["rebuild", "better-sqlite3"], { cwd: extDir, timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
|
|
3460
3697
|
if (rebuildErr) {
|
|
3461
3698
|
this.log.warn(`update-install: better-sqlite3 rebuild failed: ${rebuildErr.message}`);
|
|
3462
3699
|
const stderr = String(rebuildStderr || "").trim();
|
|
3463
3700
|
if (stderr)
|
|
3464
3701
|
this.log.warn(`update-install: rebuild stderr: ${stderr.slice(0, 500)}`);
|
|
3465
|
-
// Continue so postinstall.cjs can run (it will try rebuild again and show user guidance)
|
|
3466
3702
|
}
|
|
3467
|
-
// Run postinstall.cjs: legacy cleanup, skill install, version marker, and optional sqlite re-check
|
|
3468
3703
|
this.log.info(`update-install: running postinstall...`);
|
|
3469
|
-
(0, node_child_process_1.
|
|
3470
|
-
|
|
3471
|
-
node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
3472
|
-
}
|
|
3473
|
-
catch { }
|
|
3704
|
+
(0, node_child_process_1.execFile)(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
3705
|
+
cleanupTmpDir();
|
|
3474
3706
|
if (postErr) {
|
|
3475
3707
|
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
3476
3708
|
const postStderrStr = String(postStderr || "").trim();
|
|
3477
3709
|
if (postStderrStr)
|
|
3478
3710
|
this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
|
|
3479
|
-
|
|
3711
|
+
rollbackInstall();
|
|
3712
|
+
this.jsonResponse(res, { ok: false, error: `Postinstall failed: ${postStderrStr || postErr.message}` });
|
|
3713
|
+
return;
|
|
3480
3714
|
}
|
|
3481
|
-
// Read new version
|
|
3482
3715
|
let newVersion = "unknown";
|
|
3483
3716
|
try {
|
|
3484
3717
|
const newPkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(extDir, "package.json"), "utf-8"));
|
|
3485
3718
|
newVersion = newPkg.version ?? newVersion;
|
|
3486
3719
|
}
|
|
3487
3720
|
catch { }
|
|
3721
|
+
if (targetVersion && newVersion !== targetVersion) {
|
|
3722
|
+
this.log.warn(`update-install: version mismatch! expected=${targetVersion}, got=${newVersion} — rolling back`);
|
|
3723
|
+
rollbackInstall();
|
|
3724
|
+
this.jsonResponse(res, {
|
|
3725
|
+
ok: false,
|
|
3726
|
+
error: `Version mismatch: expected ${targetVersion} but downloaded ${newVersion}. npm cache may be stale — please try again.`,
|
|
3727
|
+
});
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
discardBackup();
|
|
3488
3731
|
this.log.info(`update-install: success! Updated to ${newVersion}`);
|
|
3489
|
-
this.
|
|
3490
|
-
setTimeout(() => {
|
|
3491
|
-
this.log.info(`update-install: triggering gateway restart via SIGUSR1...`);
|
|
3492
|
-
try {
|
|
3493
|
-
process.kill(process.pid, "SIGUSR1");
|
|
3494
|
-
}
|
|
3495
|
-
catch (sig) {
|
|
3496
|
-
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
3497
|
-
}
|
|
3498
|
-
}, 500);
|
|
3732
|
+
this.jsonResponseAndRestart(res, { ok: true, version: newVersion }, "update-install");
|
|
3499
3733
|
});
|
|
3500
3734
|
});
|
|
3501
3735
|
});
|
|
@@ -3833,7 +4067,7 @@ class ViewerServer {
|
|
|
3833
4067
|
else if (this.migrationState.done) {
|
|
3834
4068
|
const evtName = this.migrationState.stopped ? "stopped" : "done";
|
|
3835
4069
|
res.write(`event: state\ndata: ${JSON.stringify(this.migrationState)}\n\n`);
|
|
3836
|
-
res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok:
|
|
4070
|
+
res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: this.migrationState.success, ...this.migrationState })}\n\n`);
|
|
3837
4071
|
res.end();
|
|
3838
4072
|
}
|
|
3839
4073
|
else {
|
|
@@ -3872,22 +4106,11 @@ class ViewerServer {
|
|
|
3872
4106
|
res.on("close", () => {
|
|
3873
4107
|
this.migrationSSEClients = this.migrationSSEClients.filter(c => c !== res);
|
|
3874
4108
|
});
|
|
3875
|
-
this.
|
|
3876
|
-
this.migrationState = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
|
|
4109
|
+
this.migrationState = createInitialMigrationState();
|
|
3877
4110
|
const send = (event, data) => {
|
|
3878
4111
|
if (event === "item") {
|
|
3879
4112
|
const d = data;
|
|
3880
|
-
|
|
3881
|
-
this.migrationState.stored++;
|
|
3882
|
-
else if (d.status === "skipped" || d.status === "duplicate")
|
|
3883
|
-
this.migrationState.skipped++;
|
|
3884
|
-
else if (d.status === "merged")
|
|
3885
|
-
this.migrationState.merged++;
|
|
3886
|
-
else if (d.status === "error")
|
|
3887
|
-
this.migrationState.errors++;
|
|
3888
|
-
this.migrationState.processed = d.index ?? this.migrationState.processed + 1;
|
|
3889
|
-
this.migrationState.total = d.total ?? this.migrationState.total;
|
|
3890
|
-
this.migrationState.lastItem = d;
|
|
4113
|
+
applyMigrationItemToState(this.migrationState, d);
|
|
3891
4114
|
}
|
|
3892
4115
|
else if (event === "phase") {
|
|
3893
4116
|
this.migrationState.phase = data.phase;
|
|
@@ -3901,12 +4124,14 @@ class ViewerServer {
|
|
|
3901
4124
|
this.runMigration(send, opts.sources, concurrency).finally(() => {
|
|
3902
4125
|
this.migrationRunning = false;
|
|
3903
4126
|
this.migrationState.done = true;
|
|
4127
|
+
this.migrationState.success = computeMigrationSuccess(this.migrationState);
|
|
4128
|
+
const donePayload = { ok: this.migrationState.success, ...this.migrationState };
|
|
3904
4129
|
if (this.migrationAbort) {
|
|
3905
4130
|
this.migrationState.stopped = true;
|
|
3906
|
-
this.broadcastSSE("stopped",
|
|
4131
|
+
this.broadcastSSE("stopped", donePayload);
|
|
3907
4132
|
}
|
|
3908
4133
|
else {
|
|
3909
|
-
this.broadcastSSE("done",
|
|
4134
|
+
this.broadcastSSE("done", donePayload);
|
|
3910
4135
|
}
|
|
3911
4136
|
this.migrationAbort = false;
|
|
3912
4137
|
const clientsToClose = [...this.migrationSSEClients];
|
|
@@ -3992,12 +4217,25 @@ class ViewerServer {
|
|
|
3992
4217
|
continue;
|
|
3993
4218
|
}
|
|
3994
4219
|
try {
|
|
3995
|
-
const
|
|
4220
|
+
const stepFailures = [];
|
|
4221
|
+
let summary = "";
|
|
4222
|
+
try {
|
|
4223
|
+
summary = await summarizer.summarize(row.text);
|
|
4224
|
+
}
|
|
4225
|
+
catch (err) {
|
|
4226
|
+
stepFailures.push("summarization");
|
|
4227
|
+
this.log.warn(`Migration summarization failed: ${err}`);
|
|
4228
|
+
}
|
|
4229
|
+
if (!summary) {
|
|
4230
|
+
stepFailures.push("summarization");
|
|
4231
|
+
summary = row.text.slice(0, 200);
|
|
4232
|
+
}
|
|
3996
4233
|
let embedding = null;
|
|
3997
4234
|
try {
|
|
3998
4235
|
[embedding] = await this.embedder.embed([summary]);
|
|
3999
4236
|
}
|
|
4000
4237
|
catch (err) {
|
|
4238
|
+
stepFailures.push("embedding");
|
|
4001
4239
|
this.log.warn(`Migration embed failed: ${err}`);
|
|
4002
4240
|
}
|
|
4003
4241
|
let dedupStatus = "active";
|
|
@@ -4013,30 +4251,36 @@ class ViewerServer {
|
|
|
4013
4251
|
return { index: idx + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
|
|
4014
4252
|
}).filter(c => c.summary);
|
|
4015
4253
|
if (candidates.length > 0) {
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4254
|
+
try {
|
|
4255
|
+
const dedupResult = await summarizer.judgeDedup(summary, candidates);
|
|
4256
|
+
if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
|
|
4257
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4258
|
+
if (targetId) {
|
|
4259
|
+
dedupStatus = "duplicate";
|
|
4260
|
+
dedupTarget = targetId;
|
|
4261
|
+
dedupReason = dedupResult.reason;
|
|
4262
|
+
}
|
|
4023
4263
|
}
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4264
|
+
else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
|
|
4265
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4266
|
+
if (targetId) {
|
|
4267
|
+
this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, row.text);
|
|
4268
|
+
try {
|
|
4269
|
+
const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
|
|
4270
|
+
if (newEmb)
|
|
4271
|
+
this.store.upsertEmbedding(targetId, newEmb);
|
|
4272
|
+
}
|
|
4273
|
+
catch { /* best-effort */ }
|
|
4274
|
+
dedupStatus = "merged";
|
|
4275
|
+
dedupTarget = targetId;
|
|
4276
|
+
dedupReason = dedupResult.reason;
|
|
4033
4277
|
}
|
|
4034
|
-
catch { /* best-effort */ }
|
|
4035
|
-
dedupStatus = "merged";
|
|
4036
|
-
dedupTarget = targetId;
|
|
4037
|
-
dedupReason = dedupResult.reason;
|
|
4038
4278
|
}
|
|
4039
4279
|
}
|
|
4280
|
+
catch (err) {
|
|
4281
|
+
stepFailures.push("dedup");
|
|
4282
|
+
this.log.warn(`Migration dedup judgment failed: ${err}`);
|
|
4283
|
+
}
|
|
4040
4284
|
}
|
|
4041
4285
|
}
|
|
4042
4286
|
}
|
|
@@ -4060,8 +4304,8 @@ class ViewerServer {
|
|
|
4060
4304
|
mergeCount: 0,
|
|
4061
4305
|
lastHitAt: null,
|
|
4062
4306
|
mergeHistory: "[]",
|
|
4063
|
-
createdAt:
|
|
4064
|
-
updatedAt:
|
|
4307
|
+
createdAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
4308
|
+
updatedAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
|
|
4065
4309
|
};
|
|
4066
4310
|
this.store.insertChunk(chunk);
|
|
4067
4311
|
if (embedding && dedupStatus === "active") {
|
|
@@ -4075,7 +4319,14 @@ class ViewerServer {
|
|
|
4075
4319
|
preview: row.text.slice(0, 120),
|
|
4076
4320
|
summary: summary.slice(0, 80),
|
|
4077
4321
|
source: file,
|
|
4322
|
+
stepFailures,
|
|
4078
4323
|
});
|
|
4324
|
+
if (stepFailures.length > 0) {
|
|
4325
|
+
this.log.warn(`[MIGRATION] sqlite item imported with step failures: ${stepFailures.join(",")}`);
|
|
4326
|
+
}
|
|
4327
|
+
else {
|
|
4328
|
+
this.log.info("[MIGRATION] sqlite item imported successfully (all steps)");
|
|
4329
|
+
}
|
|
4079
4330
|
}
|
|
4080
4331
|
catch (err) {
|
|
4081
4332
|
totalErrors++;
|
|
@@ -4216,12 +4467,25 @@ class ViewerServer {
|
|
|
4216
4467
|
continue;
|
|
4217
4468
|
}
|
|
4218
4469
|
try {
|
|
4219
|
-
const
|
|
4470
|
+
const stepFailures = [];
|
|
4471
|
+
let summary = "";
|
|
4472
|
+
try {
|
|
4473
|
+
summary = await summarizer.summarize(content);
|
|
4474
|
+
}
|
|
4475
|
+
catch (err) {
|
|
4476
|
+
stepFailures.push("summarization");
|
|
4477
|
+
this.log.warn(`Migration summarization failed: ${err}`);
|
|
4478
|
+
}
|
|
4479
|
+
if (!summary) {
|
|
4480
|
+
stepFailures.push("summarization");
|
|
4481
|
+
summary = content.slice(0, 200);
|
|
4482
|
+
}
|
|
4220
4483
|
let embedding = null;
|
|
4221
4484
|
try {
|
|
4222
4485
|
[embedding] = await this.embedder.embed([summary]);
|
|
4223
4486
|
}
|
|
4224
4487
|
catch (err) {
|
|
4488
|
+
stepFailures.push("embedding");
|
|
4225
4489
|
this.log.warn(`Migration embed failed: ${err}`);
|
|
4226
4490
|
}
|
|
4227
4491
|
let dedupStatus = "active";
|
|
@@ -4237,30 +4501,36 @@ class ViewerServer {
|
|
|
4237
4501
|
return { index: i + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
|
|
4238
4502
|
}).filter(c => c.summary);
|
|
4239
4503
|
if (candidates.length > 0) {
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4504
|
+
try {
|
|
4505
|
+
const dedupResult = await summarizer.judgeDedup(summary, candidates);
|
|
4506
|
+
if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
|
|
4507
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4508
|
+
if (targetId) {
|
|
4509
|
+
dedupStatus = "duplicate";
|
|
4510
|
+
dedupTarget = targetId;
|
|
4511
|
+
dedupReason = dedupResult.reason;
|
|
4512
|
+
}
|
|
4247
4513
|
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4514
|
+
else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
|
|
4515
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4516
|
+
if (targetId) {
|
|
4517
|
+
this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, content);
|
|
4518
|
+
try {
|
|
4519
|
+
const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
|
|
4520
|
+
if (newEmb)
|
|
4521
|
+
this.store.upsertEmbedding(targetId, newEmb);
|
|
4522
|
+
}
|
|
4523
|
+
catch { /* best-effort */ }
|
|
4524
|
+
dedupStatus = "merged";
|
|
4525
|
+
dedupTarget = targetId;
|
|
4526
|
+
dedupReason = dedupResult.reason;
|
|
4257
4527
|
}
|
|
4258
|
-
catch { /* best-effort */ }
|
|
4259
|
-
dedupStatus = "merged";
|
|
4260
|
-
dedupTarget = targetId;
|
|
4261
|
-
dedupReason = dedupResult.reason;
|
|
4262
4528
|
}
|
|
4263
4529
|
}
|
|
4530
|
+
catch (err) {
|
|
4531
|
+
stepFailures.push("dedup");
|
|
4532
|
+
this.log.warn(`Migration dedup judgment failed: ${err}`);
|
|
4533
|
+
}
|
|
4264
4534
|
}
|
|
4265
4535
|
}
|
|
4266
4536
|
}
|
|
@@ -4277,7 +4547,13 @@ class ViewerServer {
|
|
|
4277
4547
|
if (embedding && dedupStatus === "active")
|
|
4278
4548
|
this.store.upsertEmbedding(chunkId, embedding);
|
|
4279
4549
|
totalStored++;
|
|
4280
|
-
send("item", { index: idx, total: totalMsgs, status: dedupStatus === "active" ? "stored" : dedupStatus, preview: content.slice(0, 120), summary: summary.slice(0, 80), source: file, agent: agentId, role: msgRole });
|
|
4550
|
+
send("item", { index: idx, total: totalMsgs, status: dedupStatus === "active" ? "stored" : dedupStatus, preview: content.slice(0, 120), summary: summary.slice(0, 80), source: file, agent: agentId, role: msgRole, stepFailures });
|
|
4551
|
+
if (stepFailures.length > 0) {
|
|
4552
|
+
this.log.warn(`[MIGRATION] session item imported with step failures: ${stepFailures.join(",")}`);
|
|
4553
|
+
}
|
|
4554
|
+
else {
|
|
4555
|
+
this.log.info("[MIGRATION] session item imported successfully (all steps)");
|
|
4556
|
+
}
|
|
4281
4557
|
}
|
|
4282
4558
|
catch (err) {
|
|
4283
4559
|
totalErrors++;
|
|
@@ -4320,7 +4596,14 @@ class ViewerServer {
|
|
|
4320
4596
|
}
|
|
4321
4597
|
}
|
|
4322
4598
|
send("progress", { total: totalProcessed, processed: totalProcessed, phase: "done" });
|
|
4323
|
-
send("summary", {
|
|
4599
|
+
send("summary", {
|
|
4600
|
+
totalProcessed,
|
|
4601
|
+
totalStored,
|
|
4602
|
+
totalSkipped,
|
|
4603
|
+
totalErrors,
|
|
4604
|
+
success: computeMigrationSuccess(this.migrationState),
|
|
4605
|
+
stepFailures: this.migrationState.stepFailures,
|
|
4606
|
+
});
|
|
4324
4607
|
}
|
|
4325
4608
|
// ─── Post-processing: independent task/skill generation ───
|
|
4326
4609
|
handlePostprocess(req, res) {
|
|
@@ -4579,6 +4862,20 @@ class ViewerServer {
|
|
|
4579
4862
|
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
4580
4863
|
req.on("end", () => cb(body));
|
|
4581
4864
|
}
|
|
4865
|
+
jsonResponseAndRestart(res, data, source, delayMs = 1500, statusCode = 200) {
|
|
4866
|
+
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
4867
|
+
res.end(JSON.stringify(data), () => {
|
|
4868
|
+
setTimeout(() => {
|
|
4869
|
+
this.log.info(`${source}: triggering gateway restart via SIGUSR1...`);
|
|
4870
|
+
try {
|
|
4871
|
+
process.kill(process.pid, "SIGUSR1");
|
|
4872
|
+
}
|
|
4873
|
+
catch (sig) {
|
|
4874
|
+
this.log.warn(`SIGUSR1 failed: ${sig}`);
|
|
4875
|
+
}
|
|
4876
|
+
}, delayMs);
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4582
4879
|
jsonResponse(res, data, statusCode = 200) {
|
|
4583
4880
|
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
4584
4881
|
res.end(JSON.stringify(data));
|