@memtensor/memos-local-openclaw-plugin 1.0.5 → 1.0.6-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +23 -1
- 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 +1 -1
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +39 -31
- package/dist/hub/server.js.map +1 -1
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +16 -86
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +3 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +34 -19
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +28 -19
- package/dist/recall/engine.js.map +1 -1
- package/dist/storage/sqlite.d.ts +30 -7
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +139 -60
- 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/viewer/server.d.ts +24 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +332 -130
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +65 -29
- package/package.json +1 -1
- package/scripts/postinstall.cjs +21 -5
- package/src/capture/index.ts +36 -0
- package/src/client/connector.ts +22 -1
- package/src/client/hub.ts +4 -0
- package/src/hub/server.ts +42 -26
- package/src/ingest/providers/index.ts +30 -93
- package/src/ingest/providers/openai.ts +32 -15
- package/src/recall/engine.ts +28 -19
- package/src/storage/sqlite.ts +156 -65
- package/src/tools/memory-get.ts +4 -1
- package/src/types.ts +2 -0
- package/src/viewer/server.ts +313 -125
- 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/src/viewer/server.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
-
import { execSync, exec } from "node:child_process";
|
|
4
|
+
import { execSync, exec, execFile } from "node:child_process";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import readline from "node:readline";
|
|
@@ -22,9 +22,89 @@ import type { Logger, Chunk, PluginContext, MemosLocalConfig } from "../types";
|
|
|
22
22
|
import { viewerHTML } from "./html";
|
|
23
23
|
import { v4 as uuid } from "uuid";
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
export interface MigrationStepFailureCounts {
|
|
26
|
+
summarization: number;
|
|
27
|
+
dedup: number;
|
|
28
|
+
embedding: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MigrationStateSnapshot {
|
|
32
|
+
phase: string;
|
|
33
|
+
stored: number;
|
|
34
|
+
skipped: number;
|
|
35
|
+
merged: number;
|
|
36
|
+
errors: number;
|
|
37
|
+
processed: number;
|
|
38
|
+
total: number;
|
|
39
|
+
lastItem: any;
|
|
40
|
+
done: boolean;
|
|
41
|
+
stopped: boolean;
|
|
42
|
+
stepFailures: MigrationStepFailureCounts;
|
|
43
|
+
success: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createInitialStepFailures(): MigrationStepFailureCounts {
|
|
47
|
+
return { summarization: 0, dedup: 0, embedding: 0 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function computeMigrationSuccess(state: Pick<MigrationStateSnapshot, "errors" | "stepFailures">): boolean {
|
|
51
|
+
const sf = state.stepFailures;
|
|
52
|
+
return state.errors === 0 && sf.summarization === 0 && sf.dedup === 0 && sf.embedding === 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createInitialMigrationState(): MigrationStateSnapshot {
|
|
56
|
+
const stepFailures = createInitialStepFailures();
|
|
57
|
+
return {
|
|
58
|
+
phase: "",
|
|
59
|
+
stored: 0,
|
|
60
|
+
skipped: 0,
|
|
61
|
+
merged: 0,
|
|
62
|
+
errors: 0,
|
|
63
|
+
processed: 0,
|
|
64
|
+
total: 0,
|
|
65
|
+
lastItem: null,
|
|
66
|
+
done: false,
|
|
67
|
+
stopped: false,
|
|
68
|
+
stepFailures,
|
|
69
|
+
success: computeMigrationSuccess({ errors: 0, stepFailures }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function applyMigrationItemToState(state: MigrationStateSnapshot, d: any): void {
|
|
74
|
+
if (d.status === "stored") state.stored++;
|
|
75
|
+
else if (d.status === "skipped" || d.status === "duplicate") state.skipped++;
|
|
76
|
+
else if (d.status === "merged") state.merged++;
|
|
77
|
+
else if (d.status === "error") state.errors++;
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(d.stepFailures)) {
|
|
80
|
+
for (const step of d.stepFailures) {
|
|
81
|
+
if (step === "summarization") state.stepFailures.summarization++;
|
|
82
|
+
else if (step === "dedup") state.stepFailures.dedup++;
|
|
83
|
+
else if (step === "embedding") state.stepFailures.embedding++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
state.processed = d.index ?? state.processed + 1;
|
|
88
|
+
state.total = d.total ?? state.total;
|
|
89
|
+
state.lastItem = d;
|
|
90
|
+
state.success = computeMigrationSuccess(state);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Epoch ms for Chunk; OpenClaw SQLite may store Unix seconds or ms. */
|
|
94
|
+
function normalizeTimestamp(value: unknown): number {
|
|
95
|
+
if (value == null) return Date.now();
|
|
96
|
+
if (typeof value === "string") {
|
|
97
|
+
const parsed = Date.parse(value.trim());
|
|
98
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
99
|
+
const n = Number(value);
|
|
100
|
+
if (Number.isFinite(n)) return normalizeTimestamp(n);
|
|
101
|
+
return Date.now();
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
104
|
+
if (value > 0 && value < 5_000_000_000) return Math.round(value * 1000);
|
|
105
|
+
return Math.round(value);
|
|
106
|
+
}
|
|
107
|
+
return Date.now();
|
|
28
108
|
}
|
|
29
109
|
|
|
30
110
|
export interface ViewerServerOptions {
|
|
@@ -67,18 +147,7 @@ export class ViewerServer {
|
|
|
67
147
|
private resetToken: string;
|
|
68
148
|
private migrationRunning = false;
|
|
69
149
|
private migrationAbort = false;
|
|
70
|
-
private migrationState:
|
|
71
|
-
phase: string;
|
|
72
|
-
stored: number;
|
|
73
|
-
skipped: number;
|
|
74
|
-
merged: number;
|
|
75
|
-
errors: number;
|
|
76
|
-
processed: number;
|
|
77
|
-
total: number;
|
|
78
|
-
lastItem: any;
|
|
79
|
-
done: boolean;
|
|
80
|
-
stopped: boolean;
|
|
81
|
-
} = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
|
|
150
|
+
private migrationState: MigrationStateSnapshot = createInitialMigrationState();
|
|
82
151
|
private migrationSSEClients: http.ServerResponse[] = [];
|
|
83
152
|
|
|
84
153
|
private ppRunning = false;
|
|
@@ -491,13 +560,12 @@ export class ViewerServer {
|
|
|
491
560
|
if (chunkIds.length > 0) {
|
|
492
561
|
try {
|
|
493
562
|
const placeholders = chunkIds.map(() => "?").join(",");
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
563
|
+
if (this.sharingRole === "hub") {
|
|
564
|
+
const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ source_chunk_id: string; visibility: string; group_id: string | null }>;
|
|
565
|
+
for (const r of sharedRows) sharingMap.set(r.source_chunk_id, r);
|
|
566
|
+
} else {
|
|
567
|
+
const teamMetaRows = db.prepare(`SELECT chunk_id, visibility, group_id FROM team_shared_chunks WHERE chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ chunk_id: string; visibility: string; group_id: string | null }>;
|
|
568
|
+
for (const r of teamMetaRows) sharingMap.set(r.chunk_id, { visibility: r.visibility, group_id: r.group_id });
|
|
501
569
|
}
|
|
502
570
|
const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds) as Array<{ chunk_id: string; original_owner: string; shared_at: number }>;
|
|
503
571
|
for (const r of localRows) localShareMap.set(r.chunk_id, r);
|
|
@@ -564,7 +632,7 @@ export class ViewerServer {
|
|
|
564
632
|
const db = (this.store as any).db;
|
|
565
633
|
const items = tasks.map((t) => {
|
|
566
634
|
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id) as { skill_status: string | null; owner: string | null } | undefined;
|
|
567
|
-
const
|
|
635
|
+
const hubTask = this.getHubTaskForLocal(t.id);
|
|
568
636
|
return {
|
|
569
637
|
id: t.id,
|
|
570
638
|
sessionKey: t.sessionKey,
|
|
@@ -576,7 +644,7 @@ export class ViewerServer {
|
|
|
576
644
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
577
645
|
skillStatus: meta?.skill_status ?? null,
|
|
578
646
|
owner: meta?.owner ?? "agent:main",
|
|
579
|
-
sharingVisibility:
|
|
647
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
580
648
|
};
|
|
581
649
|
});
|
|
582
650
|
|
|
@@ -611,7 +679,7 @@ export class ViewerServer {
|
|
|
611
679
|
const db = (this.store as any).db;
|
|
612
680
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId) as
|
|
613
681
|
{ skill_status: string | null; skill_reason: string | null } | undefined;
|
|
614
|
-
const
|
|
682
|
+
const hubTask = this.getHubTaskForLocal(taskId);
|
|
615
683
|
|
|
616
684
|
this.jsonResponse(res, {
|
|
617
685
|
id: task.id,
|
|
@@ -626,9 +694,9 @@ export class ViewerServer {
|
|
|
626
694
|
skillStatus: meta?.skill_status ?? null,
|
|
627
695
|
skillReason: meta?.skill_reason ?? null,
|
|
628
696
|
skillLinks,
|
|
629
|
-
sharingVisibility:
|
|
630
|
-
sharingGroupId:
|
|
631
|
-
hubTaskId:
|
|
697
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
698
|
+
sharingGroupId: hubTask?.group_id ?? null,
|
|
699
|
+
hubTaskId: hubTask ? true : false,
|
|
632
700
|
});
|
|
633
701
|
}
|
|
634
702
|
|
|
@@ -818,10 +886,9 @@ export class ViewerServer {
|
|
|
818
886
|
if (visibility) {
|
|
819
887
|
skills = skills.filter(s => s.visibility === visibility);
|
|
820
888
|
}
|
|
821
|
-
const db = (this.store as any).db;
|
|
822
889
|
const enriched = skills.map(s => {
|
|
823
|
-
const
|
|
824
|
-
return { ...s, sharingVisibility:
|
|
890
|
+
const hubSkill = this.getHubSkillForLocal(s.id);
|
|
891
|
+
return { ...s, sharingVisibility: hubSkill?.visibility ?? null };
|
|
825
892
|
});
|
|
826
893
|
this.jsonResponse(res, { skills: enriched });
|
|
827
894
|
}
|
|
@@ -839,11 +906,10 @@ export class ViewerServer {
|
|
|
839
906
|
const relatedTasks = this.store.getTasksBySkill(skillId);
|
|
840
907
|
const files = fs.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
|
|
841
908
|
|
|
842
|
-
const
|
|
843
|
-
const sharedSkill = db.prepare("SELECT visibility, group_id FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(skillId) as { visibility: string | null; group_id: string | null } | undefined;
|
|
909
|
+
const hubSkill = this.getHubSkillForLocal(skillId);
|
|
844
910
|
|
|
845
911
|
this.jsonResponse(res, {
|
|
846
|
-
skill: { ...skill, sharingVisibility:
|
|
912
|
+
skill: { ...skill, sharingVisibility: hubSkill?.visibility ?? null, sharingGroupId: hubSkill?.group_id ?? null },
|
|
847
913
|
versions: versions.map(v => ({
|
|
848
914
|
id: v.id,
|
|
849
915
|
version: v.version,
|
|
@@ -982,7 +1048,7 @@ export class ViewerServer {
|
|
|
982
1048
|
method: "POST",
|
|
983
1049
|
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
984
1050
|
}) as any;
|
|
985
|
-
if (hubClient.userId) {
|
|
1051
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
986
1052
|
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
987
1053
|
this.store.upsertHubSkill({
|
|
988
1054
|
id: response?.skillId ?? existing?.id ?? crypto.randomUUID(),
|
|
@@ -992,6 +1058,14 @@ export class ViewerServer {
|
|
|
992
1058
|
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
993
1059
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
994
1060
|
});
|
|
1061
|
+
} else {
|
|
1062
|
+
const conn = this.store.getClientHubConnection();
|
|
1063
|
+
this.store.upsertTeamSharedSkill(skillId, {
|
|
1064
|
+
hubSkillId: String(response?.skillId ?? ""),
|
|
1065
|
+
visibility: "public",
|
|
1066
|
+
groupId: null,
|
|
1067
|
+
hubInstanceId: conn?.hubInstanceId ?? "",
|
|
1068
|
+
});
|
|
995
1069
|
}
|
|
996
1070
|
hubSynced = true;
|
|
997
1071
|
this.log.info(`Skill "${skill.name}" published to Hub`);
|
|
@@ -1000,7 +1074,8 @@ export class ViewerServer {
|
|
|
1000
1074
|
method: "POST",
|
|
1001
1075
|
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1002
1076
|
});
|
|
1003
|
-
if (hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1077
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1078
|
+
else this.store.deleteTeamSharedSkill(skillId);
|
|
1004
1079
|
hubSynced = true;
|
|
1005
1080
|
this.log.info(`Skill "${skill.name}" unpublished from Hub`);
|
|
1006
1081
|
}
|
|
@@ -1271,7 +1346,8 @@ export class ViewerServer {
|
|
|
1271
1346
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1272
1347
|
});
|
|
1273
1348
|
} else if (hubClient.userId) {
|
|
1274
|
-
this.store.
|
|
1349
|
+
const conn = this.store.getClientHubConnection();
|
|
1350
|
+
this.store.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
1275
1351
|
}
|
|
1276
1352
|
hubSynced = true;
|
|
1277
1353
|
} else {
|
|
@@ -1284,7 +1360,7 @@ export class ViewerServer {
|
|
|
1284
1360
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1285
1361
|
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1286
1362
|
});
|
|
1287
|
-
if (hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1363
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1288
1364
|
this.store.deleteTeamSharedChunk(chunkId);
|
|
1289
1365
|
hubSynced = true;
|
|
1290
1366
|
} catch (err) { this.log.warn(`Failed to unshare memory from team: ${err}`); }
|
|
@@ -1297,7 +1373,7 @@ export class ViewerServer {
|
|
|
1297
1373
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
|
|
1298
1374
|
method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
1299
1375
|
});
|
|
1300
|
-
if (hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1376
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
|
|
1301
1377
|
this.store.deleteTeamSharedChunk(chunkId);
|
|
1302
1378
|
hubSynced = true;
|
|
1303
1379
|
} catch (err) { this.log.warn(`Failed to unshare memory from team: ${err}`); }
|
|
@@ -1351,21 +1427,24 @@ export class ViewerServer {
|
|
|
1351
1427
|
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() })),
|
|
1352
1428
|
}),
|
|
1353
1429
|
});
|
|
1354
|
-
|
|
1430
|
+
const hubTaskId = String((response as any)?.taskId ?? "");
|
|
1431
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
1355
1432
|
const existing = this.store.getHubTaskBySource(hubClient.userId, taskId);
|
|
1356
1433
|
this.store.upsertHubTask({
|
|
1357
|
-
id:
|
|
1434
|
+
id: hubTaskId || existing?.id || crypto.randomUUID(),
|
|
1358
1435
|
sourceTaskId: taskId, sourceUserId: hubClient.userId, title: refreshedTask.title ?? "",
|
|
1359
1436
|
summary: refreshedTask.summary ?? "", groupId: null, visibility: "public",
|
|
1360
1437
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1361
1438
|
});
|
|
1362
1439
|
}
|
|
1440
|
+
const conn = this.store.getClientHubConnection();
|
|
1441
|
+
this.store.markTaskShared(taskId, hubTaskId, chunks.length, "public", null, conn?.hubInstanceId ?? "");
|
|
1363
1442
|
hubSynced = true;
|
|
1364
1443
|
}
|
|
1365
1444
|
if (!isLocalShared) {
|
|
1366
1445
|
const originalOwner = task.owner;
|
|
1367
1446
|
const db = (this.store as any).db;
|
|
1368
|
-
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());
|
|
1447
|
+
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());
|
|
1369
1448
|
db.prepare("UPDATE tasks SET owner = 'public' WHERE id = ?").run(taskId);
|
|
1370
1449
|
}
|
|
1371
1450
|
}
|
|
@@ -1385,7 +1464,8 @@ export class ViewerServer {
|
|
|
1385
1464
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1386
1465
|
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1387
1466
|
});
|
|
1388
|
-
if (hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1467
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1468
|
+
else this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1389
1469
|
hubSynced = true;
|
|
1390
1470
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1391
1471
|
}
|
|
@@ -1397,7 +1477,8 @@ export class ViewerServer {
|
|
|
1397
1477
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
|
|
1398
1478
|
method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
|
|
1399
1479
|
});
|
|
1400
|
-
if (hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1480
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1481
|
+
else if (!isLocalShared) this.store.unmarkTaskShared(taskId);
|
|
1401
1482
|
hubSynced = true;
|
|
1402
1483
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1403
1484
|
}
|
|
@@ -1454,16 +1535,20 @@ export class ViewerServer {
|
|
|
1454
1535
|
method: "POST",
|
|
1455
1536
|
body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
|
|
1456
1537
|
});
|
|
1457
|
-
|
|
1538
|
+
const hubSkillId = String((response as any)?.skillId ?? "");
|
|
1539
|
+
if (this.sharingRole === "hub" && hubClient.userId) {
|
|
1458
1540
|
const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
|
|
1459
1541
|
this.store.upsertHubSkill({
|
|
1460
|
-
id:
|
|
1542
|
+
id: hubSkillId || existing?.id || crypto.randomUUID(),
|
|
1461
1543
|
sourceSkillId: skillId, sourceUserId: hubClient.userId,
|
|
1462
1544
|
name: skill.name, description: skill.description, version: skill.version,
|
|
1463
1545
|
groupId: null, visibility: "public",
|
|
1464
1546
|
bundle: JSON.stringify(bundle.bundle), qualityScore: skill.qualityScore,
|
|
1465
1547
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
1466
1548
|
});
|
|
1549
|
+
} else {
|
|
1550
|
+
const conn = this.store.getClientHubConnection();
|
|
1551
|
+
this.store.upsertTeamSharedSkill(skillId, { hubSkillId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
1467
1552
|
}
|
|
1468
1553
|
hubSynced = true;
|
|
1469
1554
|
}
|
|
@@ -1480,7 +1565,8 @@ export class ViewerServer {
|
|
|
1480
1565
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1481
1566
|
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1482
1567
|
});
|
|
1483
|
-
if (hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1568
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1569
|
+
else this.store.deleteTeamSharedSkill(skillId);
|
|
1484
1570
|
hubSynced = true;
|
|
1485
1571
|
} catch (err) { this.log.warn(`Failed to unpublish skill from team: ${err}`); }
|
|
1486
1572
|
}
|
|
@@ -1492,7 +1578,8 @@ export class ViewerServer {
|
|
|
1492
1578
|
await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1493
1579
|
method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1494
1580
|
});
|
|
1495
|
-
if (hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1581
|
+
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubSkillBySource(hubClient.userId, skillId);
|
|
1582
|
+
else this.store.deleteTeamSharedSkill(skillId);
|
|
1496
1583
|
hubSynced = true;
|
|
1497
1584
|
} catch (err) { this.log.warn(`Failed to unpublish skill from team: ${err}`); }
|
|
1498
1585
|
}
|
|
@@ -1506,29 +1593,53 @@ export class ViewerServer {
|
|
|
1506
1593
|
});
|
|
1507
1594
|
}
|
|
1508
1595
|
|
|
1596
|
+
private get sharingRole(): string | undefined {
|
|
1597
|
+
return this.ctx?.config?.sharing?.role;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
private isCurrentClientHubInstance(hubInstanceId?: string): boolean {
|
|
1601
|
+
if (this.sharingRole !== "client") return true;
|
|
1602
|
+
const scopedHubInstanceId = String(hubInstanceId ?? "");
|
|
1603
|
+
if (!scopedHubInstanceId) return true;
|
|
1604
|
+
const currentHubInstanceId = this.store.getClientHubConnection()?.hubInstanceId ?? "";
|
|
1605
|
+
if (!currentHubInstanceId) return true;
|
|
1606
|
+
return scopedHubInstanceId === currentHubInstanceId;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1509
1609
|
private getHubMemoryForChunk(chunkId: string): any {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1610
|
+
if (this.sharingRole === "hub") {
|
|
1611
|
+
const db = (this.store as any).db;
|
|
1612
|
+
return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
|
|
1613
|
+
}
|
|
1513
1614
|
const ts = this.store.getTeamSharedChunk(chunkId);
|
|
1514
|
-
if (ts) {
|
|
1515
|
-
return {
|
|
1516
|
-
source_chunk_id: chunkId,
|
|
1517
|
-
visibility: ts.visibility,
|
|
1518
|
-
group_id: ts.groupId,
|
|
1519
|
-
};
|
|
1615
|
+
if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
|
|
1616
|
+
return { source_chunk_id: chunkId, visibility: ts.visibility, group_id: ts.groupId };
|
|
1520
1617
|
}
|
|
1521
1618
|
return undefined;
|
|
1522
1619
|
}
|
|
1523
1620
|
|
|
1524
1621
|
private getHubTaskForLocal(taskId: string): any {
|
|
1525
|
-
|
|
1526
|
-
|
|
1622
|
+
if (this.sharingRole === "hub") {
|
|
1623
|
+
const db = (this.store as any).db;
|
|
1624
|
+
return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
|
|
1625
|
+
}
|
|
1626
|
+
const shared = this.store.getLocalSharedTask(taskId);
|
|
1627
|
+
if (shared && shared.hubTaskId && this.isCurrentClientHubInstance(shared.hubInstanceId)) {
|
|
1628
|
+
return { source_task_id: taskId, visibility: shared.visibility, group_id: shared.groupId };
|
|
1629
|
+
}
|
|
1630
|
+
return undefined;
|
|
1527
1631
|
}
|
|
1528
1632
|
|
|
1529
1633
|
private getHubSkillForLocal(skillId: string): any {
|
|
1530
|
-
|
|
1531
|
-
|
|
1634
|
+
if (this.sharingRole === "hub") {
|
|
1635
|
+
const db = (this.store as any).db;
|
|
1636
|
+
return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
|
|
1637
|
+
}
|
|
1638
|
+
const ts = this.store.getTeamSharedSkill(skillId);
|
|
1639
|
+
if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
|
|
1640
|
+
return { source_skill_id: skillId, visibility: ts.visibility, group_id: ts.groupId };
|
|
1641
|
+
}
|
|
1642
|
+
return undefined;
|
|
1532
1643
|
}
|
|
1533
1644
|
|
|
1534
1645
|
private handleDeleteSession(res: http.ServerResponse, url: URL): void {
|
|
@@ -1844,6 +1955,11 @@ export class ViewerServer {
|
|
|
1844
1955
|
body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true, identityKey: existingIdentityKey }),
|
|
1845
1956
|
}) as any;
|
|
1846
1957
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
1958
|
+
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
1959
|
+
try {
|
|
1960
|
+
const info = await hubRequestJson(hubUrl, "", "/api/v1/hub/info", { method: "GET" }) as any;
|
|
1961
|
+
hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
|
|
1962
|
+
} catch { /* best-effort */ }
|
|
1847
1963
|
this.store.setClientHubConnection({
|
|
1848
1964
|
hubUrl,
|
|
1849
1965
|
userId: String(result.userId || ""),
|
|
@@ -1853,6 +1969,7 @@ export class ViewerServer {
|
|
|
1853
1969
|
connectedAt: Date.now(),
|
|
1854
1970
|
identityKey: returnedIdentityKey,
|
|
1855
1971
|
lastKnownStatus: result.status || "",
|
|
1972
|
+
hubInstanceId,
|
|
1856
1973
|
});
|
|
1857
1974
|
this.jsonResponse(res, { ok: true, status: result.status || "pending" });
|
|
1858
1975
|
} catch (err) {
|
|
@@ -2060,9 +2177,10 @@ export class ViewerServer {
|
|
|
2060
2177
|
}),
|
|
2061
2178
|
});
|
|
2062
2179
|
const hubUserId = hubClient.userId;
|
|
2063
|
-
|
|
2180
|
+
const hubTaskId = String((response as any)?.taskId ?? task.id);
|
|
2181
|
+
if (this.sharingRole === "hub" && hubUserId) {
|
|
2064
2182
|
this.store.upsertHubTask({
|
|
2065
|
-
id:
|
|
2183
|
+
id: hubTaskId,
|
|
2066
2184
|
sourceTaskId: task.id,
|
|
2067
2185
|
sourceUserId: hubUserId,
|
|
2068
2186
|
title: task.title,
|
|
@@ -2072,6 +2190,9 @@ export class ViewerServer {
|
|
|
2072
2190
|
createdAt: task.startedAt ?? Date.now(),
|
|
2073
2191
|
updatedAt: task.updatedAt ?? Date.now(),
|
|
2074
2192
|
});
|
|
2193
|
+
} else {
|
|
2194
|
+
const conn = this.store.getClientHubConnection();
|
|
2195
|
+
this.store.markTaskShared(task.id, hubTaskId, chunks.length, visibility, groupId, conn?.hubInstanceId ?? "");
|
|
2075
2196
|
}
|
|
2076
2197
|
this.jsonResponse(res, { ok: true, taskId, visibility, response });
|
|
2077
2198
|
} catch (err) {
|
|
@@ -2094,7 +2215,9 @@ export class ViewerServer {
|
|
|
2094
2215
|
body: JSON.stringify({ sourceTaskId: task.id }),
|
|
2095
2216
|
});
|
|
2096
2217
|
const hubUserId = hubClient.userId;
|
|
2097
|
-
if (hubUserId) this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
2218
|
+
if (this.sharingRole === "hub" && hubUserId) this.store.deleteHubTaskBySource(hubUserId, task.id);
|
|
2219
|
+
else if (task.owner === "public") this.store.downgradeTeamSharedTaskToLocal(task.id);
|
|
2220
|
+
else this.store.unmarkTaskShared(task.id);
|
|
2098
2221
|
this.jsonResponse(res, { ok: true, taskId });
|
|
2099
2222
|
} catch (err) {
|
|
2100
2223
|
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
@@ -2146,7 +2269,8 @@ export class ViewerServer {
|
|
|
2146
2269
|
updatedAt: now,
|
|
2147
2270
|
});
|
|
2148
2271
|
} else if (hubClient.userId) {
|
|
2149
|
-
this.store.
|
|
2272
|
+
const conn = this.store.getClientHubConnection();
|
|
2273
|
+
this.store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId, hubInstanceId: conn?.hubInstanceId ?? "" });
|
|
2150
2274
|
}
|
|
2151
2275
|
this.jsonResponse(res, { ok: true, chunkId, visibility, response });
|
|
2152
2276
|
} catch (err) {
|
|
@@ -2167,8 +2291,8 @@ export class ViewerServer {
|
|
|
2167
2291
|
body: JSON.stringify({ sourceChunkId: chunkId }),
|
|
2168
2292
|
});
|
|
2169
2293
|
const hubUserId = hubClient.userId;
|
|
2170
|
-
if (hubUserId) this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
2171
|
-
this.store.deleteTeamSharedChunk(chunkId);
|
|
2294
|
+
if (this.sharingRole === "hub" && hubUserId) this.store.deleteHubMemoryBySource(hubUserId, chunkId);
|
|
2295
|
+
else this.store.deleteTeamSharedChunk(chunkId);
|
|
2172
2296
|
this.jsonResponse(res, { ok: true, chunkId });
|
|
2173
2297
|
} catch (err) {
|
|
2174
2298
|
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
@@ -2213,7 +2337,7 @@ export class ViewerServer {
|
|
|
2213
2337
|
}),
|
|
2214
2338
|
});
|
|
2215
2339
|
const hubUserId = hubClient.userId;
|
|
2216
|
-
if (hubUserId) {
|
|
2340
|
+
if (this.sharingRole === "hub" && hubUserId) {
|
|
2217
2341
|
const existing = this.store.getHubSkillBySource(hubUserId, skillId);
|
|
2218
2342
|
this.store.upsertHubSkill({
|
|
2219
2343
|
id: (response as any)?.skillId ?? existing?.id ?? crypto.randomUUID(),
|
|
@@ -2229,6 +2353,14 @@ export class ViewerServer {
|
|
|
2229
2353
|
createdAt: existing?.createdAt ?? Date.now(),
|
|
2230
2354
|
updatedAt: Date.now(),
|
|
2231
2355
|
});
|
|
2356
|
+
} else {
|
|
2357
|
+
const conn = this.store.getClientHubConnection();
|
|
2358
|
+
this.store.upsertTeamSharedSkill(skillId, {
|
|
2359
|
+
hubSkillId: String((response as any)?.skillId ?? ""),
|
|
2360
|
+
visibility,
|
|
2361
|
+
groupId,
|
|
2362
|
+
hubInstanceId: conn?.hubInstanceId ?? "",
|
|
2363
|
+
});
|
|
2232
2364
|
}
|
|
2233
2365
|
this.jsonResponse(res, { ok: true, skillId, visibility, response });
|
|
2234
2366
|
} catch (err) {
|
|
@@ -2251,7 +2383,8 @@ export class ViewerServer {
|
|
|
2251
2383
|
body: JSON.stringify({ sourceSkillId: skill.id }),
|
|
2252
2384
|
});
|
|
2253
2385
|
const hubUserId = hubClient.userId;
|
|
2254
|
-
if (hubUserId) this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
2386
|
+
if (this.sharingRole === "hub" && hubUserId) this.store.deleteHubSkillBySource(hubUserId, skill.id);
|
|
2387
|
+
else this.store.deleteTeamSharedSkill(skill.id);
|
|
2255
2388
|
this.jsonResponse(res, { ok: true, skillId });
|
|
2256
2389
|
} catch (err) {
|
|
2257
2390
|
this.jsonResponse(res, { ok: false, error: String(err) });
|
|
@@ -2717,19 +2850,21 @@ export class ViewerServer {
|
|
|
2717
2850
|
const isClient = newEnabled && newRole === "client";
|
|
2718
2851
|
if (wasClient && !isClient) {
|
|
2719
2852
|
await this.withdrawOrLeaveHub();
|
|
2853
|
+
this.store.clearAllTeamSharingState();
|
|
2720
2854
|
this.store.clearClientHubConnection();
|
|
2721
|
-
this.log.info("Client hub connection cleared (sharing disabled or role changed)");
|
|
2855
|
+
this.log.info("Client hub connection and team sharing state cleared (sharing disabled or role changed)");
|
|
2722
2856
|
}
|
|
2723
2857
|
|
|
2724
2858
|
if (wasClient && isClient) {
|
|
2725
2859
|
const newClientAddr = String((merged.client as Record<string, unknown>)?.hubAddress || "");
|
|
2726
2860
|
if (newClientAddr && oldClientHubAddress && normalizeHubUrl(newClientAddr) !== normalizeHubUrl(oldClientHubAddress)) {
|
|
2727
2861
|
this.notifyHubLeave();
|
|
2862
|
+
this.store.clearAllTeamSharingState();
|
|
2728
2863
|
const oldConn = this.store.getClientHubConnection();
|
|
2729
2864
|
if (oldConn) {
|
|
2730
|
-
this.store.setClientHubConnection({ ...oldConn, hubUrl: normalizeHubUrl(newClientAddr), userToken: "", lastKnownStatus: "hub_changed" });
|
|
2865
|
+
this.store.setClientHubConnection({ ...oldConn, hubUrl: normalizeHubUrl(newClientAddr), userToken: "", hubInstanceId: "", lastKnownStatus: "hub_changed" });
|
|
2731
2866
|
}
|
|
2732
|
-
this.log.info("Client hub connection
|
|
2867
|
+
this.log.info("Client hub connection and team sharing state cleared (switched to different Hub)");
|
|
2733
2868
|
}
|
|
2734
2869
|
}
|
|
2735
2870
|
|
|
@@ -2790,6 +2925,11 @@ export class ViewerServer {
|
|
|
2790
2925
|
body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
|
|
2791
2926
|
}) as any;
|
|
2792
2927
|
const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
|
|
2928
|
+
let hubInstanceId = persisted?.hubInstanceId || "";
|
|
2929
|
+
try {
|
|
2930
|
+
const info = await hubRequestJson(hubUrl, "", "/api/v1/hub/info", { method: "GET" }) as any;
|
|
2931
|
+
hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
|
|
2932
|
+
} catch { /* best-effort */ }
|
|
2793
2933
|
this.store.setClientHubConnection({
|
|
2794
2934
|
hubUrl,
|
|
2795
2935
|
userId: String(result.userId || ""),
|
|
@@ -2799,6 +2939,7 @@ export class ViewerServer {
|
|
|
2799
2939
|
connectedAt: Date.now(),
|
|
2800
2940
|
identityKey: returnedIdentityKey,
|
|
2801
2941
|
lastKnownStatus: result.status || "",
|
|
2942
|
+
hubInstanceId,
|
|
2802
2943
|
});
|
|
2803
2944
|
this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
|
|
2804
2945
|
if (result.userToken) {
|
|
@@ -2811,6 +2952,7 @@ export class ViewerServer {
|
|
|
2811
2952
|
this.readBody(_req, async () => {
|
|
2812
2953
|
try {
|
|
2813
2954
|
await this.withdrawOrLeaveHub();
|
|
2955
|
+
this.store.clearAllTeamSharingState();
|
|
2814
2956
|
this.store.clearClientHubConnection();
|
|
2815
2957
|
|
|
2816
2958
|
const configPath = this.getOpenClawConfigPath();
|
|
@@ -3196,7 +3338,8 @@ export class ViewerServer {
|
|
|
3196
3338
|
|
|
3197
3339
|
// Install dependencies
|
|
3198
3340
|
this.log.info(`update-install: installing dependencies...`);
|
|
3199
|
-
|
|
3341
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3342
|
+
execFile(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
|
|
3200
3343
|
if (npmErr) {
|
|
3201
3344
|
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
3202
3345
|
this.log.warn(`update-install: npm install failed: ${npmErr.message}`);
|
|
@@ -3204,25 +3347,21 @@ export class ViewerServer {
|
|
|
3204
3347
|
return;
|
|
3205
3348
|
}
|
|
3206
3349
|
|
|
3207
|
-
|
|
3208
|
-
exec(`cd ${extDir} && npm rebuild better-sqlite3`, { timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
|
|
3350
|
+
execFile(npmCmd, ["rebuild", "better-sqlite3"], { cwd: extDir, timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
|
|
3209
3351
|
if (rebuildErr) {
|
|
3210
3352
|
this.log.warn(`update-install: better-sqlite3 rebuild failed: ${rebuildErr.message}`);
|
|
3211
3353
|
const stderr = String(rebuildStderr || "").trim();
|
|
3212
3354
|
if (stderr) this.log.warn(`update-install: rebuild stderr: ${stderr.slice(0, 500)}`);
|
|
3213
|
-
// Continue so postinstall.cjs can run (it will try rebuild again and show user guidance)
|
|
3214
3355
|
}
|
|
3215
3356
|
|
|
3216
|
-
// Run postinstall.cjs: legacy cleanup, skill install, version marker, and optional sqlite re-check
|
|
3217
3357
|
this.log.info(`update-install: running postinstall...`);
|
|
3218
|
-
|
|
3358
|
+
execFile(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
3219
3359
|
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
3220
3360
|
|
|
3221
3361
|
if (postErr) {
|
|
3222
3362
|
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
3223
3363
|
const postStderrStr = String(postStderr || "").trim();
|
|
3224
3364
|
if (postStderrStr) this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
|
|
3225
|
-
// Still report success; plugin is updated, user can run postinstall manually if needed
|
|
3226
3365
|
}
|
|
3227
3366
|
|
|
3228
3367
|
// Read new version
|
|
@@ -3575,7 +3714,7 @@ export class ViewerServer {
|
|
|
3575
3714
|
} else if (this.migrationState.done) {
|
|
3576
3715
|
const evtName = this.migrationState.stopped ? "stopped" : "done";
|
|
3577
3716
|
res.write(`event: state\ndata: ${JSON.stringify(this.migrationState)}\n\n`);
|
|
3578
|
-
res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok:
|
|
3717
|
+
res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: this.migrationState.success, ...this.migrationState })}\n\n`);
|
|
3579
3718
|
res.end();
|
|
3580
3719
|
} else {
|
|
3581
3720
|
res.end();
|
|
@@ -3616,19 +3755,12 @@ export class ViewerServer {
|
|
|
3616
3755
|
this.migrationSSEClients = this.migrationSSEClients.filter(c => c !== res);
|
|
3617
3756
|
});
|
|
3618
3757
|
|
|
3619
|
-
this.
|
|
3620
|
-
this.migrationState = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
|
|
3758
|
+
this.migrationState = createInitialMigrationState();
|
|
3621
3759
|
|
|
3622
3760
|
const send = (event: string, data: unknown) => {
|
|
3623
3761
|
if (event === "item") {
|
|
3624
3762
|
const d = data as any;
|
|
3625
|
-
|
|
3626
|
-
else if (d.status === "skipped" || d.status === "duplicate") this.migrationState.skipped++;
|
|
3627
|
-
else if (d.status === "merged") this.migrationState.merged++;
|
|
3628
|
-
else if (d.status === "error") this.migrationState.errors++;
|
|
3629
|
-
this.migrationState.processed = d.index ?? this.migrationState.processed + 1;
|
|
3630
|
-
this.migrationState.total = d.total ?? this.migrationState.total;
|
|
3631
|
-
this.migrationState.lastItem = d;
|
|
3763
|
+
applyMigrationItemToState(this.migrationState, d);
|
|
3632
3764
|
} else if (event === "phase") {
|
|
3633
3765
|
this.migrationState.phase = (data as any).phase;
|
|
3634
3766
|
} else if (event === "progress") {
|
|
@@ -3641,11 +3773,13 @@ export class ViewerServer {
|
|
|
3641
3773
|
this.runMigration(send, opts.sources, concurrency).finally(() => {
|
|
3642
3774
|
this.migrationRunning = false;
|
|
3643
3775
|
this.migrationState.done = true;
|
|
3776
|
+
this.migrationState.success = computeMigrationSuccess(this.migrationState);
|
|
3777
|
+
const donePayload = { ok: this.migrationState.success, ...this.migrationState };
|
|
3644
3778
|
if (this.migrationAbort) {
|
|
3645
3779
|
this.migrationState.stopped = true;
|
|
3646
|
-
this.broadcastSSE("stopped",
|
|
3780
|
+
this.broadcastSSE("stopped", donePayload);
|
|
3647
3781
|
} else {
|
|
3648
|
-
this.broadcastSSE("done",
|
|
3782
|
+
this.broadcastSSE("done", donePayload);
|
|
3649
3783
|
}
|
|
3650
3784
|
this.migrationAbort = false;
|
|
3651
3785
|
const clientsToClose = [...this.migrationSSEClients];
|
|
@@ -3742,11 +3876,24 @@ export class ViewerServer {
|
|
|
3742
3876
|
}
|
|
3743
3877
|
|
|
3744
3878
|
try {
|
|
3745
|
-
const
|
|
3879
|
+
const stepFailures: Array<"summarization" | "dedup" | "embedding"> = [];
|
|
3880
|
+
let summary = "";
|
|
3881
|
+
try {
|
|
3882
|
+
summary = await summarizer.summarize(row.text);
|
|
3883
|
+
} catch (err) {
|
|
3884
|
+
stepFailures.push("summarization");
|
|
3885
|
+
this.log.warn(`Migration summarization failed: ${err}`);
|
|
3886
|
+
}
|
|
3887
|
+
if (!summary) {
|
|
3888
|
+
stepFailures.push("summarization");
|
|
3889
|
+
summary = row.text.slice(0, 200);
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3746
3892
|
let embedding: number[] | null = null;
|
|
3747
3893
|
try {
|
|
3748
3894
|
[embedding] = await this.embedder.embed([summary]);
|
|
3749
3895
|
} catch (err) {
|
|
3896
|
+
stepFailures.push("embedding");
|
|
3750
3897
|
this.log.warn(`Migration embed failed: ${err}`);
|
|
3751
3898
|
}
|
|
3752
3899
|
|
|
@@ -3765,26 +3912,31 @@ export class ViewerServer {
|
|
|
3765
3912
|
}).filter(c => c.summary);
|
|
3766
3913
|
|
|
3767
3914
|
if (candidates.length > 0) {
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3915
|
+
try {
|
|
3916
|
+
const dedupResult = await summarizer.judgeDedup(summary, candidates);
|
|
3917
|
+
if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
|
|
3918
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
3919
|
+
if (targetId) {
|
|
3920
|
+
dedupStatus = "duplicate";
|
|
3921
|
+
dedupTarget = targetId;
|
|
3922
|
+
dedupReason = dedupResult.reason;
|
|
3923
|
+
}
|
|
3924
|
+
} else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
|
|
3925
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
3926
|
+
if (targetId) {
|
|
3927
|
+
this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, row.text);
|
|
3928
|
+
try {
|
|
3929
|
+
const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
|
|
3930
|
+
if (newEmb) this.store.upsertEmbedding(targetId, newEmb);
|
|
3931
|
+
} catch { /* best-effort */ }
|
|
3932
|
+
dedupStatus = "merged";
|
|
3933
|
+
dedupTarget = targetId;
|
|
3934
|
+
dedupReason = dedupResult.reason;
|
|
3935
|
+
}
|
|
3787
3936
|
}
|
|
3937
|
+
} catch (err) {
|
|
3938
|
+
stepFailures.push("dedup");
|
|
3939
|
+
this.log.warn(`Migration dedup judgment failed: ${err}`);
|
|
3788
3940
|
}
|
|
3789
3941
|
}
|
|
3790
3942
|
}
|
|
@@ -3827,7 +3979,13 @@ export class ViewerServer {
|
|
|
3827
3979
|
preview: row.text.slice(0, 120),
|
|
3828
3980
|
summary: summary.slice(0, 80),
|
|
3829
3981
|
source: file,
|
|
3982
|
+
stepFailures,
|
|
3830
3983
|
});
|
|
3984
|
+
if (stepFailures.length > 0) {
|
|
3985
|
+
this.log.warn(`[MIGRATION] sqlite item imported with step failures: ${stepFailures.join(",")}`);
|
|
3986
|
+
} else {
|
|
3987
|
+
this.log.info("[MIGRATION] sqlite item imported successfully (all steps)");
|
|
3988
|
+
}
|
|
3831
3989
|
} catch (err) {
|
|
3832
3990
|
totalErrors++;
|
|
3833
3991
|
send("item", {
|
|
@@ -3957,11 +4115,24 @@ export class ViewerServer {
|
|
|
3957
4115
|
}
|
|
3958
4116
|
|
|
3959
4117
|
try {
|
|
3960
|
-
const
|
|
4118
|
+
const stepFailures: Array<"summarization" | "dedup" | "embedding"> = [];
|
|
4119
|
+
let summary = "";
|
|
4120
|
+
try {
|
|
4121
|
+
summary = await summarizer.summarize(content);
|
|
4122
|
+
} catch (err) {
|
|
4123
|
+
stepFailures.push("summarization");
|
|
4124
|
+
this.log.warn(`Migration summarization failed: ${err}`);
|
|
4125
|
+
}
|
|
4126
|
+
if (!summary) {
|
|
4127
|
+
stepFailures.push("summarization");
|
|
4128
|
+
summary = content.slice(0, 200);
|
|
4129
|
+
}
|
|
4130
|
+
|
|
3961
4131
|
let embedding: number[] | null = null;
|
|
3962
4132
|
try {
|
|
3963
4133
|
[embedding] = await this.embedder.embed([summary]);
|
|
3964
4134
|
} catch (err) {
|
|
4135
|
+
stepFailures.push("embedding");
|
|
3965
4136
|
this.log.warn(`Migration embed failed: ${err}`);
|
|
3966
4137
|
}
|
|
3967
4138
|
|
|
@@ -3980,17 +4151,22 @@ export class ViewerServer {
|
|
|
3980
4151
|
}).filter(c => c.summary);
|
|
3981
4152
|
|
|
3982
4153
|
if (candidates.length > 0) {
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
4154
|
+
try {
|
|
4155
|
+
const dedupResult = await summarizer.judgeDedup(summary, candidates);
|
|
4156
|
+
if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
|
|
4157
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4158
|
+
if (targetId) { dedupStatus = "duplicate"; dedupTarget = targetId; dedupReason = dedupResult.reason; }
|
|
4159
|
+
} else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
|
|
4160
|
+
const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
|
|
4161
|
+
if (targetId) {
|
|
4162
|
+
this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, content);
|
|
4163
|
+
try { const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]); if (newEmb) this.store.upsertEmbedding(targetId, newEmb); } catch { /* best-effort */ }
|
|
4164
|
+
dedupStatus = "merged"; dedupTarget = targetId; dedupReason = dedupResult.reason;
|
|
4165
|
+
}
|
|
3993
4166
|
}
|
|
4167
|
+
} catch (err) {
|
|
4168
|
+
stepFailures.push("dedup");
|
|
4169
|
+
this.log.warn(`Migration dedup judgment failed: ${err}`);
|
|
3994
4170
|
}
|
|
3995
4171
|
}
|
|
3996
4172
|
}
|
|
@@ -4010,7 +4186,12 @@ export class ViewerServer {
|
|
|
4010
4186
|
if (embedding && dedupStatus === "active") this.store.upsertEmbedding(chunkId, embedding);
|
|
4011
4187
|
|
|
4012
4188
|
totalStored++;
|
|
4013
|
-
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 });
|
|
4189
|
+
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 });
|
|
4190
|
+
if (stepFailures.length > 0) {
|
|
4191
|
+
this.log.warn(`[MIGRATION] session item imported with step failures: ${stepFailures.join(",")}`);
|
|
4192
|
+
} else {
|
|
4193
|
+
this.log.info("[MIGRATION] session item imported successfully (all steps)");
|
|
4194
|
+
}
|
|
4014
4195
|
} catch (err) {
|
|
4015
4196
|
totalErrors++;
|
|
4016
4197
|
send("item", { index: idx, total: totalMsgs, status: "error", preview: content.slice(0, 120), source: file, agent: agentId, error: String(err).slice(0, 200) });
|
|
@@ -4051,7 +4232,14 @@ export class ViewerServer {
|
|
|
4051
4232
|
}
|
|
4052
4233
|
|
|
4053
4234
|
send("progress", { total: totalProcessed, processed: totalProcessed, phase: "done" });
|
|
4054
|
-
send("summary", {
|
|
4235
|
+
send("summary", {
|
|
4236
|
+
totalProcessed,
|
|
4237
|
+
totalStored,
|
|
4238
|
+
totalSkipped,
|
|
4239
|
+
totalErrors,
|
|
4240
|
+
success: computeMigrationSuccess(this.migrationState),
|
|
4241
|
+
stepFailures: this.migrationState.stepFailures,
|
|
4242
|
+
});
|
|
4055
4243
|
}
|
|
4056
4244
|
|
|
4057
4245
|
// ─── Post-processing: independent task/skill generation ───
|