@memtensor/memos-local-openclaw-plugin 1.0.5 → 1.0.6-beta.2

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 (60) 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 +33 -5
  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 +2 -0
  11. package/dist/hub/server.d.ts.map +1 -1
  12. package/dist/hub/server.js +116 -54
  13. package/dist/hub/server.js.map +1 -1
  14. package/dist/ingest/providers/index.d.ts +4 -0
  15. package/dist/ingest/providers/index.d.ts.map +1 -1
  16. package/dist/ingest/providers/index.js +32 -86
  17. package/dist/ingest/providers/index.js.map +1 -1
  18. package/dist/ingest/providers/openai.d.ts.map +1 -1
  19. package/dist/ingest/providers/openai.js +29 -13
  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 +33 -32
  23. package/dist/recall/engine.js.map +1 -1
  24. package/dist/storage/sqlite.d.ts +43 -7
  25. package/dist/storage/sqlite.d.ts.map +1 -1
  26. package/dist/storage/sqlite.js +179 -58
  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/html.d.ts.map +1 -1
  35. package/dist/viewer/html.js +71 -21
  36. package/dist/viewer/html.js.map +1 -1
  37. package/dist/viewer/server.d.ts +24 -0
  38. package/dist/viewer/server.d.ts.map +1 -1
  39. package/dist/viewer/server.js +398 -144
  40. package/dist/viewer/server.js.map +1 -1
  41. package/index.ts +86 -34
  42. package/package.json +1 -1
  43. package/scripts/postinstall.cjs +21 -5
  44. package/src/capture/index.ts +36 -0
  45. package/src/client/connector.ts +32 -5
  46. package/src/client/hub.ts +4 -0
  47. package/src/hub/server.ts +110 -50
  48. package/src/ingest/providers/index.ts +37 -92
  49. package/src/ingest/providers/openai.ts +31 -13
  50. package/src/recall/engine.ts +32 -30
  51. package/src/storage/sqlite.ts +196 -63
  52. package/src/tools/memory-get.ts +4 -1
  53. package/src/types.ts +2 -0
  54. package/src/viewer/html.ts +71 -21
  55. package/src/viewer/server.ts +387 -139
  56. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  57. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  58. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  59. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  60. 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,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 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);
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 = { phase: "", stored: 0, skipped: 0, merged: 0, errors: 0, processed: 0, total: 0, lastItem: null, done: false, stopped: false };
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
- 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)) {
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 sharedTask = db.prepare("SELECT visibility FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(t.id);
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: sharedTask?.visibility ?? null,
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 sharedTask = db.prepare("SELECT visibility, group_id FROM hub_tasks WHERE source_task_id = ? ORDER BY updated_at DESC LIMIT 1").get(taskId);
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: sharedTask?.visibility ?? null,
721
- sharingGroupId: sharedTask?.group_id ?? null,
722
- hubTaskId: sharedTask ? true : false,
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 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 };
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 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);
991
+ const hubSkill = this.getHubSkillForLocal(skillId);
947
992
  this.jsonResponse(res, {
948
- skill: { ...skill, sharingVisibility: sharedSkill?.visibility ?? null, sharingGroupId: sharedSkill?.group_id ?? null },
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.upsertTeamSharedChunk(chunkId, { hubMemoryId: memoryId, visibility: "public", groupId: null });
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
- if (hubClient.userId) {
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: response?.taskId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
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
- if (hubClient.userId) {
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: response?.skillId ?? existing?.id ?? node_crypto_1.default.randomUUID(),
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
- 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;
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
- const db = this.store.db;
1670
- return db.prepare("SELECT * FROM hub_tasks WHERE source_task_id = ? LIMIT 1").get(taskId);
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
- const db = this.store.db;
1674
- return db.prepare("SELECT * FROM hub_skills WHERE source_skill_id = ? LIMIT 1").get(skillId);
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
- this.jsonResponse(res, { ok: false, error: String(err) });
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
- if (hubUserId) {
2350
+ const hubTaskId = String(response?.taskId ?? task.id);
2351
+ if (this.sharingRole === "hub" && hubUserId) {
2231
2352
  this.store.upsertHubTask({
2232
- id: task.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.upsertTeamSharedChunk(chunk.id, { hubMemoryId: mid, visibility, groupId });
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
- this.store.deleteTeamSharedChunk(chunkId);
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 token cleared (switched to different Hub), identity preserved");
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,14 +3114,23 @@ 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
- this.log.warn(`Auto-join on save failed: ${e}`);
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
  }
3130
+ if (joinError) {
3131
+ this.jsonResponse(res, { ok: true, joinError, restart: false });
3132
+ return;
3133
+ }
2981
3134
  this.jsonResponse(res, { ok: true, joinStatus, restart: true });
2982
3135
  setTimeout(() => {
2983
3136
  this.log.info("config-save: triggering gateway restart via SIGUSR1...");
@@ -3003,17 +3156,42 @@ class ViewerServer {
3003
3156
  if (!hubAddress || !teamToken)
3004
3157
  return undefined;
3005
3158
  const hubUrl = (0, hub_1.normalizeHubUrl)(hubAddress);
3159
+ try {
3160
+ await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
3161
+ }
3162
+ catch {
3163
+ throw new Error("hub_unreachable");
3164
+ }
3006
3165
  const os = await Promise.resolve().then(() => __importStar(require("os")));
3007
3166
  const nickname = String(clientCfg?.nickname || "");
3008
3167
  const username = nickname || os.userInfo().username || "user";
3009
3168
  const hostname = os.hostname() || "unknown";
3010
3169
  const persisted = this.store.getClientHubConnection();
3011
3170
  const existingIdentityKey = persisted?.identityKey || "";
3012
- const result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
3013
- method: "POST",
3014
- body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
3015
- });
3171
+ let result;
3172
+ try {
3173
+ result = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/join", {
3174
+ method: "POST",
3175
+ body: JSON.stringify({ teamToken, username, deviceName: hostname, identityKey: existingIdentityKey }),
3176
+ });
3177
+ }
3178
+ catch (err) {
3179
+ const errStr = String(err);
3180
+ if (errStr.includes("(409)") || errStr.includes("username_taken")) {
3181
+ throw new Error("username_taken");
3182
+ }
3183
+ if (errStr.includes("(403)") || errStr.includes("invalid_team_token")) {
3184
+ throw new Error("invalid_team_token");
3185
+ }
3186
+ throw err;
3187
+ }
3016
3188
  const returnedIdentityKey = String(result.identityKey || existingIdentityKey || "");
3189
+ let hubInstanceId = persisted?.hubInstanceId || "";
3190
+ try {
3191
+ const info = await (0, hub_1.hubRequestJson)(hubUrl, "", "/api/v1/hub/info", { method: "GET" });
3192
+ hubInstanceId = String(info?.hubInstanceId ?? hubInstanceId);
3193
+ }
3194
+ catch { /* best-effort */ }
3017
3195
  this.store.setClientHubConnection({
3018
3196
  hubUrl,
3019
3197
  userId: String(result.userId || ""),
@@ -3023,6 +3201,7 @@ class ViewerServer {
3023
3201
  connectedAt: Date.now(),
3024
3202
  identityKey: returnedIdentityKey,
3025
3203
  lastKnownStatus: result.status || "",
3204
+ hubInstanceId,
3026
3205
  });
3027
3206
  this.log.info(`Auto-join on save: status=${result.status}, userId=${result.userId}`);
3028
3207
  if (result.userToken) {
@@ -3034,6 +3213,7 @@ class ViewerServer {
3034
3213
  this.readBody(_req, async () => {
3035
3214
  try {
3036
3215
  await this.withdrawOrLeaveHub();
3216
+ this.store.clearAllTeamSharingState();
3037
3217
  this.store.clearClientHubConnection();
3038
3218
  const configPath = this.getOpenClawConfigPath();
3039
3219
  if (configPath && node_fs_1.default.existsSync(configPath)) {
@@ -3233,17 +3413,45 @@ class ViewerServer {
3233
3413
  }
3234
3414
  }
3235
3415
  catch { }
3236
- const url = hubUrl.replace(/\/+$/, "") + "/api/v1/hub/info";
3416
+ const baseUrl = hubUrl.replace(/\/+$/, "");
3417
+ const infoUrl = baseUrl + "/api/v1/hub/info";
3237
3418
  const ctrl = new AbortController();
3238
3419
  const timeout = setTimeout(() => ctrl.abort(), 8000);
3239
3420
  try {
3240
- const r = await fetch(url, { signal: ctrl.signal });
3421
+ const r = await fetch(infoUrl, { signal: ctrl.signal });
3241
3422
  clearTimeout(timeout);
3242
3423
  if (!r.ok) {
3243
3424
  this.jsonResponse(res, { ok: false, error: `HTTP ${r.status}` });
3244
3425
  return;
3245
3426
  }
3246
3427
  const info = await r.json();
3428
+ const { teamToken, nickname } = JSON.parse(body);
3429
+ if (teamToken) {
3430
+ const username = (typeof nickname === "string" && nickname.trim()) || node_os_1.default.userInfo().username || "user";
3431
+ const persisted = this.store.getClientHubConnection();
3432
+ const identityKey = persisted?.identityKey || "";
3433
+ try {
3434
+ const joinR = await fetch(baseUrl + "/api/v1/hub/join", {
3435
+ method: "POST",
3436
+ headers: { "content-type": "application/json" },
3437
+ body: JSON.stringify({ teamToken, username, identityKey, deviceName: node_os_1.default.hostname(), dryRun: true }),
3438
+ });
3439
+ const joinData = await joinR.json();
3440
+ if (!joinR.ok && joinData.error === "username_taken") {
3441
+ this.jsonResponse(res, { ok: false, error: "username_taken", teamName: info.teamName || "" });
3442
+ return;
3443
+ }
3444
+ if (!joinR.ok && joinData.error === "invalid_team_token") {
3445
+ this.jsonResponse(res, { ok: false, error: "invalid_team_token", teamName: info.teamName || "" });
3446
+ return;
3447
+ }
3448
+ if (joinR.ok && joinData.status === "blocked") {
3449
+ this.jsonResponse(res, { ok: false, error: "blocked", teamName: info.teamName || "" });
3450
+ return;
3451
+ }
3452
+ }
3453
+ catch { /* join check is best-effort; connection itself is OK */ }
3454
+ }
3247
3455
  this.jsonResponse(res, { ok: true, teamName: info.teamName || "", apiVersion: info.apiVersion || "" });
3248
3456
  }
3249
3457
  catch (e) {
@@ -3445,7 +3653,8 @@ class ViewerServer {
3445
3653
  node_fs_1.default.renameSync(srcDir, extDir);
3446
3654
  // Install dependencies
3447
3655
  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) => {
3656
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
3657
+ (0, node_child_process_1.execFile)(npmCmd, ["install", "--omit=dev", "--ignore-scripts"], { cwd: extDir, timeout: 120_000 }, (npmErr, npmOut, npmStderr) => {
3449
3658
  if (npmErr) {
3450
3659
  try {
3451
3660
  node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
@@ -3455,18 +3664,15 @@ class ViewerServer {
3455
3664
  this.jsonResponse(res, { ok: false, error: `Dependency install failed: ${npmStderr || npmErr.message}` });
3456
3665
  return;
3457
3666
  }
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) => {
3667
+ (0, node_child_process_1.execFile)(npmCmd, ["rebuild", "better-sqlite3"], { cwd: extDir, timeout: 60_000 }, (rebuildErr, rebuildOut, rebuildStderr) => {
3460
3668
  if (rebuildErr) {
3461
3669
  this.log.warn(`update-install: better-sqlite3 rebuild failed: ${rebuildErr.message}`);
3462
3670
  const stderr = String(rebuildStderr || "").trim();
3463
3671
  if (stderr)
3464
3672
  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
3673
  }
3467
- // Run postinstall.cjs: legacy cleanup, skill install, version marker, and optional sqlite re-check
3468
3674
  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) => {
3675
+ (0, node_child_process_1.execFile)(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
3470
3676
  try {
3471
3677
  node_fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
3472
3678
  }
@@ -3476,7 +3682,6 @@ class ViewerServer {
3476
3682
  const postStderrStr = String(postStderr || "").trim();
3477
3683
  if (postStderrStr)
3478
3684
  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
3685
  }
3481
3686
  // Read new version
3482
3687
  let newVersion = "unknown";
@@ -3833,7 +4038,7 @@ class ViewerServer {
3833
4038
  else if (this.migrationState.done) {
3834
4039
  const evtName = this.migrationState.stopped ? "stopped" : "done";
3835
4040
  res.write(`event: state\ndata: ${JSON.stringify(this.migrationState)}\n\n`);
3836
- res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: true })}\n\n`);
4041
+ res.write(`event: ${evtName}\ndata: ${JSON.stringify({ ok: this.migrationState.success, ...this.migrationState })}\n\n`);
3837
4042
  res.end();
3838
4043
  }
3839
4044
  else {
@@ -3872,22 +4077,11 @@ class ViewerServer {
3872
4077
  res.on("close", () => {
3873
4078
  this.migrationSSEClients = this.migrationSSEClients.filter(c => c !== res);
3874
4079
  });
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 };
4080
+ this.migrationState = createInitialMigrationState();
3877
4081
  const send = (event, data) => {
3878
4082
  if (event === "item") {
3879
4083
  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;
4084
+ applyMigrationItemToState(this.migrationState, d);
3891
4085
  }
3892
4086
  else if (event === "phase") {
3893
4087
  this.migrationState.phase = data.phase;
@@ -3901,12 +4095,14 @@ class ViewerServer {
3901
4095
  this.runMigration(send, opts.sources, concurrency).finally(() => {
3902
4096
  this.migrationRunning = false;
3903
4097
  this.migrationState.done = true;
4098
+ this.migrationState.success = computeMigrationSuccess(this.migrationState);
4099
+ const donePayload = { ok: this.migrationState.success, ...this.migrationState };
3904
4100
  if (this.migrationAbort) {
3905
4101
  this.migrationState.stopped = true;
3906
- this.broadcastSSE("stopped", { ok: true, ...this.migrationState });
4102
+ this.broadcastSSE("stopped", donePayload);
3907
4103
  }
3908
4104
  else {
3909
- this.broadcastSSE("done", { ok: true });
4105
+ this.broadcastSSE("done", donePayload);
3910
4106
  }
3911
4107
  this.migrationAbort = false;
3912
4108
  const clientsToClose = [...this.migrationSSEClients];
@@ -3992,12 +4188,25 @@ class ViewerServer {
3992
4188
  continue;
3993
4189
  }
3994
4190
  try {
3995
- const summary = await summarizer.summarize(row.text);
4191
+ const stepFailures = [];
4192
+ let summary = "";
4193
+ try {
4194
+ summary = await summarizer.summarize(row.text);
4195
+ }
4196
+ catch (err) {
4197
+ stepFailures.push("summarization");
4198
+ this.log.warn(`Migration summarization failed: ${err}`);
4199
+ }
4200
+ if (!summary) {
4201
+ stepFailures.push("summarization");
4202
+ summary = row.text.slice(0, 200);
4203
+ }
3996
4204
  let embedding = null;
3997
4205
  try {
3998
4206
  [embedding] = await this.embedder.embed([summary]);
3999
4207
  }
4000
4208
  catch (err) {
4209
+ stepFailures.push("embedding");
4001
4210
  this.log.warn(`Migration embed failed: ${err}`);
4002
4211
  }
4003
4212
  let dedupStatus = "active";
@@ -4013,30 +4222,36 @@ class ViewerServer {
4013
4222
  return { index: idx + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
4014
4223
  }).filter(c => c.summary);
4015
4224
  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;
4225
+ try {
4226
+ const dedupResult = await summarizer.judgeDedup(summary, candidates);
4227
+ if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4228
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4229
+ if (targetId) {
4230
+ dedupStatus = "duplicate";
4231
+ dedupTarget = targetId;
4232
+ dedupReason = dedupResult.reason;
4233
+ }
4023
4234
  }
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);
4235
+ else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4236
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4237
+ if (targetId) {
4238
+ this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, row.text);
4239
+ try {
4240
+ const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4241
+ if (newEmb)
4242
+ this.store.upsertEmbedding(targetId, newEmb);
4243
+ }
4244
+ catch { /* best-effort */ }
4245
+ dedupStatus = "merged";
4246
+ dedupTarget = targetId;
4247
+ dedupReason = dedupResult.reason;
4033
4248
  }
4034
- catch { /* best-effort */ }
4035
- dedupStatus = "merged";
4036
- dedupTarget = targetId;
4037
- dedupReason = dedupResult.reason;
4038
4249
  }
4039
4250
  }
4251
+ catch (err) {
4252
+ stepFailures.push("dedup");
4253
+ this.log.warn(`Migration dedup judgment failed: ${err}`);
4254
+ }
4040
4255
  }
4041
4256
  }
4042
4257
  }
@@ -4060,8 +4275,8 @@ class ViewerServer {
4060
4275
  mergeCount: 0,
4061
4276
  lastHitAt: null,
4062
4277
  mergeHistory: "[]",
4063
- createdAt: normalizeTimestamp(row.updated_at),
4064
- updatedAt: normalizeTimestamp(row.updated_at),
4278
+ createdAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
4279
+ updatedAt: Number(row.updated_at) < 1e12 ? Number(row.updated_at) * 1000 : Number(row.updated_at),
4065
4280
  };
4066
4281
  this.store.insertChunk(chunk);
4067
4282
  if (embedding && dedupStatus === "active") {
@@ -4075,7 +4290,14 @@ class ViewerServer {
4075
4290
  preview: row.text.slice(0, 120),
4076
4291
  summary: summary.slice(0, 80),
4077
4292
  source: file,
4293
+ stepFailures,
4078
4294
  });
4295
+ if (stepFailures.length > 0) {
4296
+ this.log.warn(`[MIGRATION] sqlite item imported with step failures: ${stepFailures.join(",")}`);
4297
+ }
4298
+ else {
4299
+ this.log.info("[MIGRATION] sqlite item imported successfully (all steps)");
4300
+ }
4079
4301
  }
4080
4302
  catch (err) {
4081
4303
  totalErrors++;
@@ -4216,12 +4438,25 @@ class ViewerServer {
4216
4438
  continue;
4217
4439
  }
4218
4440
  try {
4219
- const summary = await summarizer.summarize(content);
4441
+ const stepFailures = [];
4442
+ let summary = "";
4443
+ try {
4444
+ summary = await summarizer.summarize(content);
4445
+ }
4446
+ catch (err) {
4447
+ stepFailures.push("summarization");
4448
+ this.log.warn(`Migration summarization failed: ${err}`);
4449
+ }
4450
+ if (!summary) {
4451
+ stepFailures.push("summarization");
4452
+ summary = content.slice(0, 200);
4453
+ }
4220
4454
  let embedding = null;
4221
4455
  try {
4222
4456
  [embedding] = await this.embedder.embed([summary]);
4223
4457
  }
4224
4458
  catch (err) {
4459
+ stepFailures.push("embedding");
4225
4460
  this.log.warn(`Migration embed failed: ${err}`);
4226
4461
  }
4227
4462
  let dedupStatus = "active";
@@ -4237,30 +4472,36 @@ class ViewerServer {
4237
4472
  return { index: i + 1, summary: chunk?.summary ?? "", chunkId: s.chunkId };
4238
4473
  }).filter(c => c.summary);
4239
4474
  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;
4475
+ try {
4476
+ const dedupResult = await summarizer.judgeDedup(summary, candidates);
4477
+ if (dedupResult?.action === "DUPLICATE" && dedupResult.targetIndex) {
4478
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4479
+ if (targetId) {
4480
+ dedupStatus = "duplicate";
4481
+ dedupTarget = targetId;
4482
+ dedupReason = dedupResult.reason;
4483
+ }
4247
4484
  }
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);
4485
+ else if (dedupResult?.action === "UPDATE" && dedupResult.targetIndex && dedupResult.mergedSummary) {
4486
+ const targetId = candidates[dedupResult.targetIndex - 1]?.chunkId;
4487
+ if (targetId) {
4488
+ this.store.updateChunkSummaryAndContent(targetId, dedupResult.mergedSummary, content);
4489
+ try {
4490
+ const [newEmb] = await this.embedder.embed([dedupResult.mergedSummary]);
4491
+ if (newEmb)
4492
+ this.store.upsertEmbedding(targetId, newEmb);
4493
+ }
4494
+ catch { /* best-effort */ }
4495
+ dedupStatus = "merged";
4496
+ dedupTarget = targetId;
4497
+ dedupReason = dedupResult.reason;
4257
4498
  }
4258
- catch { /* best-effort */ }
4259
- dedupStatus = "merged";
4260
- dedupTarget = targetId;
4261
- dedupReason = dedupResult.reason;
4262
4499
  }
4263
4500
  }
4501
+ catch (err) {
4502
+ stepFailures.push("dedup");
4503
+ this.log.warn(`Migration dedup judgment failed: ${err}`);
4504
+ }
4264
4505
  }
4265
4506
  }
4266
4507
  }
@@ -4277,7 +4518,13 @@ class ViewerServer {
4277
4518
  if (embedding && dedupStatus === "active")
4278
4519
  this.store.upsertEmbedding(chunkId, embedding);
4279
4520
  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 });
4521
+ 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 });
4522
+ if (stepFailures.length > 0) {
4523
+ this.log.warn(`[MIGRATION] session item imported with step failures: ${stepFailures.join(",")}`);
4524
+ }
4525
+ else {
4526
+ this.log.info("[MIGRATION] session item imported successfully (all steps)");
4527
+ }
4281
4528
  }
4282
4529
  catch (err) {
4283
4530
  totalErrors++;
@@ -4320,7 +4567,14 @@ class ViewerServer {
4320
4567
  }
4321
4568
  }
4322
4569
  send("progress", { total: totalProcessed, processed: totalProcessed, phase: "done" });
4323
- send("summary", { totalProcessed, totalStored, totalSkipped, totalErrors });
4570
+ send("summary", {
4571
+ totalProcessed,
4572
+ totalStored,
4573
+ totalSkipped,
4574
+ totalErrors,
4575
+ success: computeMigrationSuccess(this.migrationState),
4576
+ stepFailures: this.migrationState.stepFailures,
4577
+ });
4324
4578
  }
4325
4579
  // ─── Post-processing: independent task/skill generation ───
4326
4580
  handlePostprocess(req, res) {