@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.
Files changed (56) hide show
  1. package/dist/capture/index.d.ts.map +1 -1
  2. package/dist/capture/index.js +24 -0
  3. package/dist/capture/index.js.map +1 -1
  4. package/dist/client/connector.d.ts.map +1 -1
  5. package/dist/client/connector.js +23 -1
  6. package/dist/client/connector.js.map +1 -1
  7. package/dist/client/hub.d.ts.map +1 -1
  8. package/dist/client/hub.js +4 -0
  9. package/dist/client/hub.js.map +1 -1
  10. package/dist/hub/server.d.ts +1 -1
  11. package/dist/hub/server.d.ts.map +1 -1
  12. package/dist/hub/server.js +39 -31
  13. package/dist/hub/server.js.map +1 -1
  14. package/dist/ingest/providers/index.d.ts.map +1 -1
  15. package/dist/ingest/providers/index.js +16 -86
  16. package/dist/ingest/providers/index.js.map +1 -1
  17. package/dist/ingest/providers/openai.d.ts +3 -0
  18. package/dist/ingest/providers/openai.d.ts.map +1 -1
  19. package/dist/ingest/providers/openai.js +34 -19
  20. package/dist/ingest/providers/openai.js.map +1 -1
  21. package/dist/recall/engine.d.ts.map +1 -1
  22. package/dist/recall/engine.js +28 -19
  23. package/dist/recall/engine.js.map +1 -1
  24. package/dist/storage/sqlite.d.ts +30 -7
  25. package/dist/storage/sqlite.d.ts.map +1 -1
  26. package/dist/storage/sqlite.js +139 -60
  27. package/dist/storage/sqlite.js.map +1 -1
  28. package/dist/tools/memory-get.d.ts.map +1 -1
  29. package/dist/tools/memory-get.js +4 -1
  30. package/dist/tools/memory-get.js.map +1 -1
  31. package/dist/types.d.ts +1 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/dist/viewer/server.d.ts +24 -0
  35. package/dist/viewer/server.d.ts.map +1 -1
  36. package/dist/viewer/server.js +332 -130
  37. package/dist/viewer/server.js.map +1 -1
  38. package/index.ts +65 -29
  39. package/package.json +1 -1
  40. package/scripts/postinstall.cjs +21 -5
  41. package/src/capture/index.ts +36 -0
  42. package/src/client/connector.ts +22 -1
  43. package/src/client/hub.ts +4 -0
  44. package/src/hub/server.ts +42 -26
  45. package/src/ingest/providers/index.ts +30 -93
  46. package/src/ingest/providers/openai.ts +32 -15
  47. package/src/recall/engine.ts +28 -19
  48. package/src/storage/sqlite.ts +156 -65
  49. package/src/tools/memory-get.ts +4 -1
  50. package/src/types.ts +2 -0
  51. package/src/viewer/server.ts +313 -125
  52. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  53. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  54. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  55. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  56. package/telemetry.credentials.json +0 -5
@@ -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,73 @@ 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 normalizeTimestamp(ts) {
61
- if (ts < 1e12)
62
- return ts * 1000;
63
- return ts;
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);
110
+ }
111
+ /** Epoch ms for Chunk; OpenClaw SQLite may store Unix seconds or ms. */
112
+ function normalizeTimestamp(value) {
113
+ if (value == null)
114
+ return Date.now();
115
+ if (typeof value === "string") {
116
+ const parsed = Date.parse(value.trim());
117
+ if (Number.isFinite(parsed))
118
+ return parsed;
119
+ const n = Number(value);
120
+ if (Number.isFinite(n))
121
+ return normalizeTimestamp(n);
122
+ return Date.now();
123
+ }
124
+ if (typeof value === "number" && Number.isFinite(value)) {
125
+ if (value > 0 && value < 5_000_000_000)
126
+ return Math.round(value * 1000);
127
+ return Math.round(value);
128
+ }
129
+ return Date.now();
64
130
  }
65
131
  class ViewerServer {
66
132
  server = null;
@@ -87,7 +153,7 @@ class ViewerServer {
87
153
  resetToken;
88
154
  migrationRunning = false;
89
155
  migrationAbort = false;
90
- migrationState = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
156
+ migrationState = createInitialMigrationState();
91
157
  migrationSSEClients = [];
92
158
  ppRunning = false;
93
159
  ppAbort = false;
@@ -591,14 +657,15 @@ class ViewerServer {
591
657
  if (chunkIds.length > 0) {
592
658
  try {
593
659
  const placeholders = chunkIds.map(() => "?").join(",");
594
- const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
595
- for (const r of sharedRows)
596
- sharingMap.set(r.source_chunk_id, r);
597
- const teamMetaRows = db.prepare(`SELECT chunk_id, visibility, group_id FROM team_shared_chunks WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
598
- for (const r of teamMetaRows) {
599
- if (!sharingMap.has(r.chunk_id)) {
660
+ if (this.sharingRole === "hub") {
661
+ const sharedRows = db.prepare(`SELECT source_chunk_id, visibility, group_id FROM hub_memories WHERE source_chunk_id IN (${placeholders})`).all(...chunkIds);
662
+ for (const r of sharedRows)
663
+ sharingMap.set(r.source_chunk_id, r);
664
+ }
665
+ else {
666
+ const teamMetaRows = db.prepare(`SELECT chunk_id, visibility, group_id FROM team_shared_chunks WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
667
+ for (const r of teamMetaRows)
600
668
  sharingMap.set(r.chunk_id, { visibility: r.visibility, group_id: r.group_id });
601
- }
602
669
  }
603
670
  const localRows = db.prepare(`SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id IN (${placeholders})`).all(...chunkIds);
604
671
  for (const r of localRows)
@@ -662,7 +729,7 @@ class ViewerServer {
662
729
  const db = this.store.db;
663
730
  const items = tasks.map((t) => {
664
731
  const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id);
665
- const sharedTask = db.prepare("SELECT visibility FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(t.id);
732
+ const hubTask = this.getHubTaskForLocal(t.id);
666
733
  return {
667
734
  id: t.id,
668
735
  sessionKey: t.sessionKey,
@@ -674,7 +741,7 @@ class ViewerServer {
674
741
  chunkCount: this.store.countChunksByTask(t.id),
675
742
  skillStatus: meta?.skill_status ?? null,
676
743
  owner: meta?.owner ?? "agent:main",
677
- sharingVisibility: sharedTask?.visibility ?? null,
744
+ sharingVisibility: hubTask?.visibility ?? null,
678
745
  };
679
746
  });
680
747
  this.jsonResponse(res, { tasks: items, total, limit, offset });
@@ -703,7 +770,7 @@ class ViewerServer {
703
770
  }));
704
771
  const db = this.store.db;
705
772
  const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId);
706
- const sharedTask = db.prepare("SELECT visibility, group_id FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(taskId);
773
+ const hubTask = this.getHubTaskForLocal(taskId);
707
774
  this.jsonResponse(res, {
708
775
  id: task.id,
709
776
  sessionKey: task.sessionKey,
@@ -717,9 +784,9 @@ class ViewerServer {
717
784
  skillStatus: meta?.skill_status ?? null,
718
785
  skillReason: meta?.skill_reason ?? null,
719
786
  skillLinks,
720
- sharingVisibility: sharedTask?.visibility ?? null,
721
- sharingGroupId: sharedTask?.group_id ?? null,
722
- hubTaskId: sharedTask ? true : false,
787
+ sharingVisibility: hubTask?.visibility ?? null,
788
+ sharingGroupId: hubTask?.group_id ?? null,
789
+ hubTaskId: hubTask ? true : false,
723
790
  });
724
791
  }
725
792
  serveStats(res, url) {
@@ -924,10 +991,9 @@ class ViewerServer {
924
991
  if (visibility) {
925
992
  skills = skills.filter(s => s.visibility === visibility);
926
993
  }
927
- const db = this.store.db;
928
994
  const enriched = skills.map(s => {
929
- const hub = db.prepare("SELECT visibility FROM hub_skills WHERE source_skill_id = ? ORDER BY updated_at DESC LIMIT 1").get(s.id);
930
- return { ...s, sharingVisibility: hub?.visibility ?? null };
995
+ const hubSkill = this.getHubSkillForLocal(s.id);
996
+ return { ...s, sharingVisibility: hubSkill?.visibility ?? null };
931
997
  });
932
998
  this.jsonResponse(res, { skills: enriched });
933
999
  }
@@ -942,10 +1008,9 @@ class ViewerServer {
942
1008
  const versions = this.store.getSkillVersions(skillId);
943
1009
  const relatedTasks = this.store.getTasksBySkill(skillId);
944
1010
  const files = node_fs_1.default.existsSync(skill.dirPath) ? this.walkDir(skill.dirPath, skill.dirPath) : [];
945
- const db = this.store.db;
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);
1011
+ const hubSkill = this.getHubSkillForLocal(skillId);
947
1012
  this.jsonResponse(res, {
948
- skill: { ...skill, sharingVisibility: sharedSkill?.visibility ?? null, sharingGroupId: sharedSkill?.group_id ?? null },
1013
+ skill: { ...skill, sharingVisibility: hubSkill?.visibility ?? null, sharingGroupId: hubSkill?.group_id ?? null },
949
1014
  versions: versions.map(v => ({
950
1015
  id: v.id,
951
1016
  version: v.version,
@@ -1081,7 +1146,7 @@ class ViewerServer {
1081
1146
  method: "POST",
1082
1147
  body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
1083
1148
  });
1084
- if (hubClient.userId) {
1149
+ if (this.sharingRole === "hub" && hubClient.userId) {
1085
1150
  const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
1086
1151
  this.store.upsertHubSkill({
1087
1152
  id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
@@ -1092,6 +1157,15 @@ class ViewerServer {
1092
1157
  createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
1093
1158
  });
1094
1159
  }
1160
+ else {
1161
+ const conn = this.store.getClientHubConnection();
1162
+ this.store.upsertTeamSharedSkill(skillId, {
1163
+ hubSkillId: String(response?.skillId ?? ""),
1164
+ visibility: "public",
1165
+ groupId: null,
1166
+ hubInstanceId: conn?.hubInstanceId ?? "",
1167
+ });
1168
+ }
1095
1169
  hubSynced = true;
1096
1170
  this.log.info(`Skill "${skill.name}" published to Hub`);
1097
1171
  }
@@ -1100,8 +1174,10 @@ class ViewerServer {
1100
1174
  method: "POST",
1101
1175
  body: JSON.stringify({ sourceSkillId: skillId }),
1102
1176
  });
1103
- if (hubClient.userId)
1177
+ if (this.sharingRole === "hub" && hubClient.userId)
1104
1178
  this.store.deleteHubSkillBySource(hubClient.userId, skillId);
1179
+ else
1180
+ this.store.deleteTeamSharedSkill(skillId);
1105
1181
  hubSynced = true;
1106
1182
  this.log.info(`Skill "${skill.name}" unpublished from Hub`);
1107
1183
  }
@@ -1399,7 +1475,8 @@ class ViewerServer {
1399
1475
  });
1400
1476
  }
1401
1477
  else if (hubClient.userId) {
1402
- this.store.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null });
1478
+ const conn = this.store.getClientHubConnection();
1479
+ this.store.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
1403
1480
  }
1404
1481
  hubSynced = true;
1405
1482
  }
@@ -1415,7 +1492,7 @@ class ViewerServer {
1415
1492
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
1416
1493
  method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
1417
1494
  });
1418
- if (hubClient.userId)
1495
+ if (this.sharingRole === "hub" && hubClient.userId)
1419
1496
  this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
1420
1497
  this.store.deleteTeamSharedChunk(chunkId);
1421
1498
  hubSynced = true;
@@ -1434,7 +1511,7 @@ class ViewerServer {
1434
1511
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/unshare", {
1435
1512
  method: "POST", body: JSON.stringify({ sourceChunkId: chunkId }),
1436
1513
  });
1437
- if (hubClient.userId)
1514
+ if (this.sharingRole === "hub" && hubClient.userId)
1438
1515
  this.store.deleteHubMemoryBySource(hubClient.userId, chunkId);
1439
1516
  this.store.deleteTeamSharedChunk(chunkId);
1440
1517
  hubSynced = true;
@@ -1488,21 +1565,24 @@ class ViewerServer {
1488
1565
  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
1566
  }),
1490
1567
  });
1491
- if (hubClient.userId) {
1568
+ const hubTaskId = String(response?.taskId ?? "");
1569
+ if (this.sharingRole === "hub" && hubClient.userId) {
1492
1570
  const existing = this.store.getHubTaskBySource(hubClient.userId, taskId);
1493
1571
  this.store.upsertHubTask({
1494
- id: response?.taskId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
1572
+ id: hubTaskId || existing?.id || node_crypto_1.default.randomUUID(),
1495
1573
  sourceTaskId: taskId, sourceUserId: hubClient.userId, title: refreshedTask.title ?? "",
1496
1574
  summary: refreshedTask.summary ?? "", groupId: null, visibility: "public",
1497
1575
  createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
1498
1576
  });
1499
1577
  }
1578
+ const conn = this.store.getClientHubConnection();
1579
+ this.store.markTaskShared(taskId, hubTaskId, chunks.length, "public", null, conn?.hubInstanceId ?? "");
1500
1580
  hubSynced = true;
1501
1581
  }
1502
1582
  if (!isLocalShared) {
1503
1583
  const originalOwner = task.owner;
1504
1584
  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());
1585
+ 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
1586
  db.prepare("UPDATE tasks SET owner = 'public' WHERE id = ?").run(taskId);
1507
1587
  }
1508
1588
  }
@@ -1520,8 +1600,10 @@ class ViewerServer {
1520
1600
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
1521
1601
  method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
1522
1602
  });
1523
- if (hubClient.userId)
1603
+ if (this.sharingRole === "hub" && hubClient.userId)
1524
1604
  this.store.deleteHubTaskBySource(hubClient.userId, taskId);
1605
+ else
1606
+ this.store.downgradeTeamSharedTaskToLocal(taskId);
1525
1607
  hubSynced = true;
1526
1608
  }
1527
1609
  catch (err) {
@@ -1535,8 +1617,10 @@ class ViewerServer {
1535
1617
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/tasks/unshare", {
1536
1618
  method: "POST", body: JSON.stringify({ sourceTaskId: taskId }),
1537
1619
  });
1538
- if (hubClient.userId)
1620
+ if (this.sharingRole === "hub" && hubClient.userId)
1539
1621
  this.store.deleteHubTaskBySource(hubClient.userId, taskId);
1622
+ else if (!isLocalShared)
1623
+ this.store.unmarkTaskShared(taskId);
1540
1624
  hubSynced = true;
1541
1625
  }
1542
1626
  catch (err) {
@@ -1591,10 +1675,11 @@ class ViewerServer {
1591
1675
  method: "POST",
1592
1676
  body: JSON.stringify({ visibility: "public", groupId: null, metadata: bundle.metadata, bundle: bundle.bundle }),
1593
1677
  });
1594
- if (hubClient.userId) {
1678
+ const hubSkillId = String(response?.skillId ?? "");
1679
+ if (this.sharingRole === "hub" && hubClient.userId) {
1595
1680
  const existing = this.store.getHubSkillBySource(hubClient.userId, skillId);
1596
1681
  this.store.upsertHubSkill({
1597
- id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
1682
+ id: hubSkillId || existing?.id || node_crypto_1.default.randomUUID(),
1598
1683
  sourceSkillId: skillId, sourceUserId: hubClient.userId,
1599
1684
  name: skill.name, description: skill.description, version: skill.version,
1600
1685
  groupId: null, visibility: "public",
@@ -1602,6 +1687,10 @@ class ViewerServer {
1602
1687
  createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
1603
1688
  });
1604
1689
  }
1690
+ else {
1691
+ const conn = this.store.getClientHubConnection();
1692
+ this.store.upsertTeamSharedSkill(skillId, { hubSkillId, visibility: "public", groupId: null, hubInstanceId: conn?.hubInstanceId ?? "" });
1693
+ }
1605
1694
  hubSynced = true;
1606
1695
  }
1607
1696
  if (!isLocalShared)
@@ -1617,8 +1706,10 @@ class ViewerServer {
1617
1706
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
1618
1707
  method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
1619
1708
  });
1620
- if (hubClient.userId)
1709
+ if (this.sharingRole === "hub" && hubClient.userId)
1621
1710
  this.store.deleteHubSkillBySource(hubClient.userId, skillId);
1711
+ else
1712
+ this.store.deleteTeamSharedSkill(skillId);
1622
1713
  hubSynced = true;
1623
1714
  }
1624
1715
  catch (err) {
@@ -1632,8 +1723,10 @@ class ViewerServer {
1632
1723
  await (0, hub_1.hubRequestJson)(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/skills/unpublish", {
1633
1724
  method: "POST", body: JSON.stringify({ sourceSkillId: skillId }),
1634
1725
  });
1635
- if (hubClient.userId)
1726
+ if (this.sharingRole === "hub" && hubClient.userId)
1636
1727
  this.store.deleteHubSkillBySource(hubClient.userId, skillId);
1728
+ else
1729
+ this.store.deleteTeamSharedSkill(skillId);
1637
1730
  hubSynced = true;
1638
1731
  }
1639
1732
  catch (err) {
@@ -1650,28 +1743,52 @@ class ViewerServer {
1650
1743
  }
1651
1744
  });
1652
1745
  }
1746
+ get sharingRole() {
1747
+ return this.ctx?.config?.sharing?.role;
1748
+ }
1749
+ isCurrentClientHubInstance(hubInstanceId) {
1750
+ if (this.sharingRole !== "client")
1751
+ return true;
1752
+ const scopedHubInstanceId = String(hubInstanceId ?? "");
1753
+ if (!scopedHubInstanceId)
1754
+ return true;
1755
+ const currentHubInstanceId = this.store.getClientHubConnection()?.hubInstanceId ?? "";
1756
+ if (!currentHubInstanceId)
1757
+ return true;
1758
+ return scopedHubInstanceId === currentHubInstanceId;
1759
+ }
1653
1760
  getHubMemoryForChunk(chunkId) {
1654
- const db = this.store.db;
1655
- const hub = db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
1656
- if (hub)
1657
- return hub;
1761
+ if (this.sharingRole === "hub") {
1762
+ const db = this.store.db;
1763
+ return db.prepare("SELECT * FROM hub_memories WHERE source_chunk_id = ? LIMIT 1").get(chunkId);
1764
+ }
1658
1765
  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
- };
1766
+ if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
1767
+ return { source_chunk_id: chunkId, visibility: ts.visibility, group_id: ts.groupId };
1665
1768
  }
1666
1769
  return undefined;
1667
1770
  }
1668
1771
  getHubTaskForLocal(taskId) {
1669
- const db = this.store.db;
1670
- return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
1772
+ if (this.sharingRole === "hub") {
1773
+ const db = this.store.db;
1774
+ return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
1775
+ }
1776
+ const shared = this.store.getLocalSharedTask(taskId);
1777
+ if (shared && shared.hubTaskId && this.isCurrentClientHubInstance(shared.hubInstanceId)) {
1778
+ return { source_task_id: taskId, visibility: shared.visibility, group_id: shared.groupId };
1779
+ }
1780
+ return undefined;
1671
1781
  }
1672
1782
  getHubSkillForLocal(skillId) {
1673
- const db = this.store.db;
1674
- return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
1783
+ if (this.sharingRole === "hub") {
1784
+ const db = this.store.db;
1785
+ return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
1786
+ }
1787
+ const ts = this.store.getTeamSharedSkill(skillId);
1788
+ if (ts && this.isCurrentClientHubInstance(ts.hubInstanceId)) {
1789
+ return { source_skill_id: skillId, visibility: ts.visibility, group_id: ts.groupId };
1790
+ }
1791
+ return undefined;
1675
1792
  }
1676
1793
  handleDeleteSession(res, url) {
1677
1794
  const key = url.searchParams.get("key");
@@ -1996,6 +2113,12 @@ class ViewerServer {
1996
2113
  body: JSON.stringify({ teamToken, username, deviceName: hostname, reapply: true, identityKey: existingIdentityKey }),
1997
2114
  });
1998
2115
  const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
2116
+ let hubInstanceId = persisted?.hubInstanceId || "";
2117
+ try {
2118
+ const info = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
2119
+ hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
2120
+ }
2121
+ catch { /* best-effort */ }
1999
2122
  this.store.setClientHubConnection({
2000
2123
  hubUrl,
2001
2124
  userId: String(result.userId || ""),
@@ -2005,6 +2128,7 @@ class ViewerServer {
2005
2128
  connectedAt: Date.now(),
2006
2129
  identityKey: returnedIdentityKey,
2007
2130
  lastKnownStatus: result.status || "",
2131
+ hubInstanceId,
2008
2132
  });
2009
2133
  this.jsonResponse(res, { ok: true, status: result.status || "pending" });
2010
2134
  }
@@ -2227,9 +2351,10 @@ class ViewerServer {
2227
2351
  }),
2228
2352
  });
2229
2353
  const hubUserId = hubClient.userId;
2230
- if (hubUserId) {
2354
+ const hubTaskId = String(response?.taskId ?? task.id);
2355
+ if (this.sharingRole === "hub" && hubUserId) {
2231
2356
  this.store.upsertHubTask({
2232
- id: task.id,
2357
+ id: hubTaskId,
2233
2358
  sourceTaskId: task.id,
2234
2359
  sourceUserId: hubUserId,
2235
2360
  title: task.title,
@@ -2240,6 +2365,10 @@ class ViewerServer {
2240
2365
  updatedAt: task.updatedAt ?? Date.now(),
2241
2366
  });
2242
2367
  }
2368
+ else {
2369
+ const conn = this.store.getClientHubConnection();
2370
+ this.store.markTaskShared(task.id, hubTaskId, chunks.length, visibility, groupId, conn?.hubInstanceId ?? "");
2371
+ }
2243
2372
  this.jsonResponse(res, { ok: true, taskId, visibility, response });
2244
2373
  }
2245
2374
  catch (err) {
@@ -2263,8 +2392,12 @@ class ViewerServer {
2263
2392
  body: JSON.stringify({ sourceTaskId: task.id }),
2264
2393
  });
2265
2394
  const hubUserId = hubClient.userId;
2266
- if (hubUserId)
2395
+ if (this.sharingRole === "hub" && hubUserId)
2267
2396
  this.store.deleteHubTaskBySource(hubUserId, task.id);
2397
+ else if (task.owner === "public")
2398
+ this.store.downgradeTeamSharedTaskToLocal(task.id);
2399
+ else
2400
+ this.store.unmarkTaskShared(task.id);
2268
2401
  this.jsonResponse(res, { ok: true, taskId });
2269
2402
  }
2270
2403
  catch (err) {
@@ -2319,7 +2452,8 @@ class ViewerServer {
2319
2452
  });
2320
2453
  }
2321
2454
  else if (hubClient.userId) {
2322
- this.store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId });
2455
+ const conn = this.store.getClientHubConnection();
2456
+ this.store.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId, hubInstanceId: conn?.hubInstanceId ?? "" });
2323
2457
  }
2324
2458
  this.jsonResponse(res, { ok: true, chunkId, visibility, response });
2325
2459
  }
@@ -2341,9 +2475,10 @@ class ViewerServer {
2341
2475
  body: JSON.stringify({ sourceChunkId: chunkId }),
2342
2476
  });
2343
2477
  const hubUserId = hubClient.userId;
2344
- if (hubUserId)
2478
+ if (this.sharingRole === "hub" && hubUserId)
2345
2479
  this.store.deleteHubMemoryBySource(hubUserId, chunkId);
2346
- this.store.deleteTeamSharedChunk(chunkId);
2480
+ else
2481
+ this.store.deleteTeamSharedChunk(chunkId);
2347
2482
  this.jsonResponse(res, { ok: true, chunkId });
2348
2483
  }
2349
2484
  catch (err) {
@@ -2391,7 +2526,7 @@ class ViewerServer {
2391
2526
  }),
2392
2527
  });
2393
2528
  const hubUserId = hubClient.userId;
2394
- if (hubUserId) {
2529
+ if (this.sharingRole === "hub" && hubUserId) {
2395
2530
  const existing = this.store.getHubSkillBySource(hubUserId, skillId);
2396
2531
  this.store.upsertHubSkill({
2397
2532
  id: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
@@ -2408,6 +2543,15 @@ class ViewerServer {
2408
2543
  updatedAt: Date.now(),
2409
2544
  });
2410
2545
  }
2546
+ else {
2547
+ const conn = this.store.getClientHubConnection();
2548
+ this.store.upsertTeamSharedSkill(skillId, {
2549
+ hubSkillId: String(response?.skillId ?? ""),
2550
+ visibility,
2551
+ groupId,
2552
+ hubInstanceId: conn?.hubInstanceId ?? "",
2553
+ });
2554
+ }
2411
2555
  this.jsonResponse(res, { ok: true, skillId, visibility, response });
2412
2556
  }
2413
2557
  catch (err) {
@@ -2431,8 +2575,10 @@ class ViewerServer {
2431
2575
  body: JSON.stringify({ sourceSkillId: skill.id }),
2432
2576
  });
2433
2577
  const hubUserId = hubClient.userId;
2434
- if (hubUserId)
2578
+ if (this.sharingRole === "hub" && hubUserId)
2435
2579
  this.store.deleteHubSkillBySource(hubUserId, skill.id);
2580
+ else
2581
+ this.store.deleteTeamSharedSkill(skill.id);
2436
2582
  this.jsonResponse(res, { ok: true, skillId });
2437
2583
  }
2438
2584
  catch (err) {
@@ -2939,18 +3085,20 @@ class ViewerServer {
2939
3085
  const isClient = newEnabled && newRole === "client";
2940
3086
  if (wasClient && !isClient) {
2941
3087
  await this.withdrawOrLeaveHub();
3088
+ this.store.clearAllTeamSharingState();
2942
3089
  this.store.clearClientHubConnection();
2943
- this.log.info("Client hub connection cleared (sharing disabled or role changed)");
3090
+ this.log.info("Client hub connection and team sharing state cleared (sharing disabled or role changed)");
2944
3091
  }
2945
3092
  if (wasClient && isClient) {
2946
3093
  const newClientAddr = String(merged.client?.hubAddress || "");
2947
3094
  if (newClientAddr && oldClientHubAddress && (0, hub_1.normalizeHubUrl)(newClientAddr) !== (0, hub_1.normalizeHubUrl)(oldClientHubAddress)) {
2948
3095
  this.notifyHubLeave();
3096
+ this.store.clearAllTeamSharingState();
2949
3097
  const oldConn = this.store.getClientHubConnection();
2950
3098
  if (oldConn) {
2951
- this.store.setClientHubConnection({ ...oldConn, hubUrl: (0, hub_1.normalizeHubUrl)(newClientAddr), userToken: "", lastKnownStatus: "hub_changed" });
3099
+ this.store.setClientHubConnection({ ...oldConn, hubUrl: (0, hub_1.normalizeHubUrl)(newClientAddr), userToken: "", hubInstanceId: "", lastKnownStatus: "hub_changed" });
2952
3100
  }
2953
- this.log.info("Client hub connection token cleared (switched to different Hub), identity preserved");
3101
+ this.log.info("Client hub connection and team sharing state cleared (switched to different Hub)");
2954
3102
  }
2955
3103
  }
2956
3104
  if (merged.role === "hub") {
@@ -3014,6 +3162,12 @@ class ViewerServer {
3014
3162
  body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
3015
3163
  });
3016
3164
  const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
3165
+ let hubInstanceId = persisted?.hubInstanceId || "";
3166
+ try {
3167
+ const info = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
3168
+ hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
3169
+ }
3170
+ catch { /* best-effort */ }
3017
3171
  this.store.setClientHubConnection({
3018
3172
  hubUrl,
3019
3173
  userId: String(result.userId || ""),
@@ -3023,6 +3177,7 @@ class ViewerServer {
3023
3177
  connectedAt: Date.now(),
3024
3178
  identityKey: returnedIdentityKey,
3025
3179
  lastKnownStatus: result.status || "",
3180
+ hubInstanceId,
3026
3181
  });
3027
3182
  this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
3028
3183
  if (result.userToken) {
@@ -3034,6 +3189,7 @@ class ViewerServer {
3034
3189
  this.readBody(_req, async () => {
3035
3190
  try {
3036
3191
  await this.withdrawOrLeaveHub();
3192
+ this.store.clearAllTeamSharingState();
3037
3193
  this.store.clearClientHubConnection();
3038
3194
  const configPath = this.getOpenClawConfigPath();
3039
3195
  if (configPath && node_fs_1.default.existsSync(configPath)) {
@@ -3445,7 +3601,8 @@ class ViewerServer {
3445
3601
  node_fs_1.default.renameSync(srcDir, extDir);
3446
3602
  // Install dependencies
3447
3603
  this.log.info(`update-install: installing dependencies...`);
3448
- (0, node_child_process_1.exec)(`cd ${extDir} && npm install --omit=dev --ignore-scripts`, { timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
3604
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
3605
+ (0, node_child_process_1.execFile)(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
3449
3606
  if (npmErr) {
3450
3607
  try {
3451
3608
  node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
@@ -3455,18 +3612,15 @@ class ViewerServer {
3455
3612
  this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
3456
3613
  return;
3457
3614
  }
3458
- // Rebuild native modules (do not swallow errors)
3459
- (0, node_child_process_1.exec)(`cd ${extDir} && npm rebuild better-sqlite3`, { timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
3615
+ (0, node_child_process_1.execFile)(npmCmd, ["rebuild", "better-sqlite3"], { cwd: extDir, timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
3460
3616
  if (rebuildErr) {
3461
3617
  this.log.warn(`update-install: better-sqlite3 rebuild failed: ${rebuildErr.message}`);
3462
3618
  const stderr = String(rebuildStderr || "").trim();
3463
3619
  if (stderr)
3464
3620
  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
3621
  }
3467
- // Run postinstall.cjs: legacy cleanup, skill install, version marker, and optional sqlite re-check
3468
3622
  this.log.info(`update-install: running postinstall...`);
3469
- (0, node_child_process_1.exec)(`cd ${extDir} && node scripts/postinstall.cjs`, { timeout: 180_000 }, (postErr, postOut, postStderr) => {
3623
+ (0, node_child_process_1.execFile)(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
3470
3624
  try {
3471
3625
  node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
3472
3626
  }
@@ -3476,7 +3630,6 @@ class ViewerServer {
3476
3630
  const postStderrStr = String(postStderr || "").trim();
3477
3631
  if (postStderrStr)
3478
3632
  this.log.warn(`update-install: postinstall stderr: ${postStderrStr.slice(0, 500)}`);
3479
- // Still report success; plugin is updated, user can run postinstall manually if needed
3480
3633
  }
3481
3634
  // Read new version
3482
3635
  let newVersion = "unknown";
@@ -3833,7 +3986,7 @@ class ViewerServer {
3833
3986
  else if (this.migrationState.done) {
3834
3987
  const evtName = this.migrationState.stopped ? "stopped" : "done";
3835
3988
  res.write(`event: state\ndata: ${JSON.stringify(this.migrationState)}\n\n`);
3836
- res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: true })}\n\n`);
3989
+ res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: this.migrationState.success, ...this.migrationState })}\n\n`);
3837
3990
  res.end();
3838
3991
  }
3839
3992
  else {
@@ -3872,22 +4025,11 @@ class ViewerServer {
3872
4025
  res.on("close", () => {
3873
4026
  this.migrationSSEClients = this.migrationSSEClients.filter(c => c !== res);
3874
4027
  });
3875
- this.migrationAbort = false;
3876
- this.migrationState = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
4028
+ this.migrationState = createInitialMigrationState();
3877
4029
  const send = (event, data) => {
3878
4030
  if (event === "item") {
3879
4031
  const d = data;
3880
- if (d.status === "stored")
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;
4032
+ applyMigrationItemToState(this.migrationState, d);
3891
4033
  }
3892
4034
  else if (event === "phase") {
3893
4035
  this.migrationState.phase = data.phase;
@@ -3901,12 +4043,14 @@ class ViewerServer {
3901
4043
  this.runMigration(send, opts.sources, concurrency).finally(() => {
3902
4044
  this.migrationRunning = false;
3903
4045
  this.migrationState.done = true;
4046
+ this.migrationState.success = computeMigrationSuccess(this.migrationState);
4047
+ const donePayload = { ok: this.migrationState.success, ...this.migrationState };
3904
4048
  if (this.migrationAbort) {
3905
4049
  this.migrationState.stopped = true;
3906
- this.broadcastSSE("stopped", { ok: true, ...this.migrationState });
4050
+ this.broadcastSSE("stopped", donePayload);
3907
4051
  }
3908
4052
  else {
3909
- this.broadcastSSE("done", { ok: true });
4053
+ this.broadcastSSE("done", donePayload);
3910
4054
  }
3911
4055
  this.migrationAbort = false;
3912
4056
  const clientsToClose = [...this.migrationSSEClients];
@@ -3992,12 +4136,25 @@ class ViewerServer {
3992
4136
  continue;
3993
4137
  }
3994
4138
  try {
3995
- const summary = await summarizer.summarize(row.text);
4139
+ const stepFailures = [];
4140
+ let summary = "";
4141
+ try {
4142
+ summary = await summarizer.summarize(row.text);
4143
+ }
4144
+ catch (err) {
4145
+ stepFailures.push("summarization");
4146
+ this.log.warn(`Migration summarization failed: ${err}`);
4147
+ }
4148
+ if (!summary) {
4149
+ stepFailures.push("summarization");
4150
+ summary = row.text.slice(0, 200);
4151
+ }
3996
4152
  let embedding = null;
3997
4153
  try {
3998
4154
  [embedding] = await this.embedder.embed([summary]);
3999
4155
  }
4000
4156
  catch (err) {
4157
+ stepFailures.push("embedding");
4001
4158
  this.log.warn(`Migration embed failed: ${err}`);
4002
4159
  }
4003
4160
  let dedupStatus = "active";
@@ -4013,30 +4170,36 @@ class ViewerServer {
4013
4170
  return { index: idx + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
4014
4171
  }).filter(c => c.summary);
4015
4172
  if (candidates.length > 0) {
4016
- const dedupResult = await summarizer.judgeDedup(summary, candidates);
4017
- if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4018
- const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4019
- if (targetId) {
4020
- dedupStatus = "duplicate";
4021
- dedupTarget = targetId;
4022
- dedupReason = dedupResult.reason;
4173
+ try {
4174
+ const dedupResult = await summarizer.judgeDedup(summary, candidates);
4175
+ if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4176
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4177
+ if (targetId) {
4178
+ dedupStatus = "duplicate";
4179
+ dedupTarget = targetId;
4180
+ dedupReason = dedupResult.reason;
4181
+ }
4023
4182
  }
4024
- }
4025
- else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4026
- const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4027
- if (targetId) {
4028
- this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, row.text);
4029
- try {
4030
- const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4031
- if (newEmb)
4032
- this.store.upsertEmbedding(targetId, newEmb);
4183
+ else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4184
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4185
+ if (targetId) {
4186
+ this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, row.text);
4187
+ try {
4188
+ const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4189
+ if (newEmb)
4190
+ this.store.upsertEmbedding(targetId, newEmb);
4191
+ }
4192
+ catch { /* best-effort */ }
4193
+ dedupStatus = "merged";
4194
+ dedupTarget = targetId;
4195
+ dedupReason = dedupResult.reason;
4033
4196
  }
4034
- catch { /* best-effort */ }
4035
- dedupStatus = "merged";
4036
- dedupTarget = targetId;
4037
- dedupReason = dedupResult.reason;
4038
4197
  }
4039
4198
  }
4199
+ catch (err) {
4200
+ stepFailures.push("dedup");
4201
+ this.log.warn(`Migration dedup judgment failed: ${err}`);
4202
+ }
4040
4203
  }
4041
4204
  }
4042
4205
  }
@@ -4075,7 +4238,14 @@ class ViewerServer {
4075
4238
  preview: row.text.slice(0, 120),
4076
4239
  summary: summary.slice(0, 80),
4077
4240
  source: file,
4241
+ stepFailures,
4078
4242
  });
4243
+ if (stepFailures.length > 0) {
4244
+ this.log.warn(`[MIGRATION] sqlite item imported with step failures: ${stepFailures.join(",")}`);
4245
+ }
4246
+ else {
4247
+ this.log.info("[MIGRATION] sqlite item imported successfully (all steps)");
4248
+ }
4079
4249
  }
4080
4250
  catch (err) {
4081
4251
  totalErrors++;
@@ -4216,12 +4386,25 @@ class ViewerServer {
4216
4386
  continue;
4217
4387
  }
4218
4388
  try {
4219
- const summary = await summarizer.summarize(content);
4389
+ const stepFailures = [];
4390
+ let summary = "";
4391
+ try {
4392
+ summary = await summarizer.summarize(content);
4393
+ }
4394
+ catch (err) {
4395
+ stepFailures.push("summarization");
4396
+ this.log.warn(`Migration summarization failed: ${err}`);
4397
+ }
4398
+ if (!summary) {
4399
+ stepFailures.push("summarization");
4400
+ summary = content.slice(0, 200);
4401
+ }
4220
4402
  let embedding = null;
4221
4403
  try {
4222
4404
  [embedding] = await this.embedder.embed([summary]);
4223
4405
  }
4224
4406
  catch (err) {
4407
+ stepFailures.push("embedding");
4225
4408
  this.log.warn(`Migration embed failed: ${err}`);
4226
4409
  }
4227
4410
  let dedupStatus = "active";
@@ -4237,30 +4420,36 @@ class ViewerServer {
4237
4420
  return { index: i + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
4238
4421
  }).filter(c => c.summary);
4239
4422
  if (candidates.length > 0) {
4240
- const dedupResult = await summarizer.judgeDedup(summary, candidates);
4241
- if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4242
- const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4243
- if (targetId) {
4244
- dedupStatus = "duplicate";
4245
- dedupTarget = targetId;
4246
- dedupReason = dedupResult.reason;
4423
+ try {
4424
+ const dedupResult = await summarizer.judgeDedup(summary, candidates);
4425
+ if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4426
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4427
+ if (targetId) {
4428
+ dedupStatus = "duplicate";
4429
+ dedupTarget = targetId;
4430
+ dedupReason = dedupResult.reason;
4431
+ }
4247
4432
  }
4248
- }
4249
- else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4250
- const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4251
- if (targetId) {
4252
- this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, content);
4253
- try {
4254
- const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4255
- if (newEmb)
4256
- this.store.upsertEmbedding(targetId, newEmb);
4433
+ else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4434
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4435
+ if (targetId) {
4436
+ this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, content);
4437
+ try {
4438
+ const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4439
+ if (newEmb)
4440
+ this.store.upsertEmbedding(targetId, newEmb);
4441
+ }
4442
+ catch { /* best-effort */ }
4443
+ dedupStatus = "merged";
4444
+ dedupTarget = targetId;
4445
+ dedupReason = dedupResult.reason;
4257
4446
  }
4258
- catch { /* best-effort */ }
4259
- dedupStatus = "merged";
4260
- dedupTarget = targetId;
4261
- dedupReason = dedupResult.reason;
4262
4447
  }
4263
4448
  }
4449
+ catch (err) {
4450
+ stepFailures.push("dedup");
4451
+ this.log.warn(`Migration dedup judgment failed: ${err}`);
4452
+ }
4264
4453
  }
4265
4454
  }
4266
4455
  }
@@ -4277,7 +4466,13 @@ class ViewerServer {
4277
4466
  if (embedding && dedupStatus === "active")
4278
4467
  this.store.upsertEmbedding(chunkId, embedding);
4279
4468
  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 });
4469
+ 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 });
4470
+ if (stepFailures.length > 0) {
4471
+ this.log.warn(`[MIGRATION] session item imported with step failures: ${stepFailures.join(",")}`);
4472
+ }
4473
+ else {
4474
+ this.log.info("[MIGRATION] session item imported successfully (all steps)");
4475
+ }
4281
4476
  }
4282
4477
  catch (err) {
4283
4478
  totalErrors++;
@@ -4320,7 +4515,14 @@ class ViewerServer {
4320
4515
  }
4321
4516
  }
4322
4517
  send("progress", { total: totalProcessed, processed: totalProcessed, phase: "done" });
4323
- send("summary", { totalProcessed, totalStored, totalSkipped, totalErrors });
4518
+ send("summary", {
4519
+ totalProcessed,
4520
+ totalStored,
4521
+ totalSkipped,
4522
+ totalErrors,
4523
+ success: computeMigrationSuccess(this.migrationState),
4524
+ stepFailures: this.migrationState.stepFailures,
4525
+ });
4324
4526
  }
4325
4527
  // ─── Post-processing: independent task/skill generation ───
4326
4528
  handlePostprocess(req, res) {