chatlab-cli 0.27.0 → 0.27.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 (69) hide show
  1. package/dist/{chat-command-SKGPS3PG.mjs → chat-command-QVCZVDSB.mjs} +5 -4
  2. package/dist/{chat-command-SKGPS3PG.mjs.map → chat-command-QVCZVDSB.mjs.map} +1 -1
  3. package/dist/{chunk-KCFVHVO4.mjs → chunk-B2VLK3FB.mjs} +11184 -17915
  4. package/dist/chunk-B2VLK3FB.mjs.map +1 -0
  5. package/dist/chunk-DHRSLYUM.mjs +7433 -0
  6. package/dist/chunk-DHRSLYUM.mjs.map +1 -0
  7. package/dist/{chunk-FHLPXRDL.mjs → chunk-H22VDCPB.mjs} +2 -2
  8. package/dist/{chunk-3HOTLLFC.mjs → chunk-JOXF6ROV.mjs} +250 -130
  9. package/dist/chunk-JOXF6ROV.mjs.map +1 -0
  10. package/dist/{chunk-MJITS5CE.mjs → chunk-K7UUN3HT.mjs} +15 -13
  11. package/dist/{chunk-MJITS5CE.mjs.map → chunk-K7UUN3HT.mjs.map} +1 -1
  12. package/dist/{chunk-6FXGC6HE.mjs → chunk-MDDWX2MG.mjs} +7 -5
  13. package/dist/{chunk-6FXGC6HE.mjs.map → chunk-MDDWX2MG.mjs.map} +1 -1
  14. package/dist/{chunk-M6ABWESL.mjs → chunk-OD3YH23N.mjs} +5 -5
  15. package/dist/chunk-OD3YH23N.mjs.map +1 -0
  16. package/dist/{chunk-7LPVQR77.mjs → chunk-PXWAYFII.mjs} +4 -4
  17. package/dist/chunk-PXWAYFII.mjs.map +1 -0
  18. package/dist/{chunk-UBJHT3BU.mjs → chunk-ST5APJB2.mjs} +6 -4
  19. package/dist/{chunk-UBJHT3BU.mjs.map → chunk-ST5APJB2.mjs.map} +1 -1
  20. package/dist/cli.mjs +4 -3
  21. package/dist/{http-CPFUTIUB.mjs → http-LSCVGWCI.mjs} +9 -8
  22. package/dist/{import-UJXKIKYN.mjs → import-E6HBJMMT.mjs} +5 -4
  23. package/dist/index.mjs +9 -8
  24. package/dist/{mcp-SW4DTCWJ.mjs → mcp-I7JUC5JV.mjs} +8 -6
  25. package/dist/{mcp-SW4DTCWJ.mjs.map → mcp-I7JUC5JV.mjs.map} +1 -1
  26. package/dist/semantic-index-worker.mjs +34 -0
  27. package/dist/semantic-index-worker.mjs.map +1 -0
  28. package/dist/{src-M3MO3LEL.mjs → src-HZILDI7C.mjs} +59 -41
  29. package/dist/{stream-import-4VDJZHMY.mjs → stream-import-IMRULRS2.mjs} +4 -3
  30. package/dist/worker-runtime-WWQ7W74Q.mjs +71 -0
  31. package/dist/worker-runtime-WWQ7W74Q.mjs.map +1 -0
  32. package/dist-web/assets/{DebugToolsPanel-DzZmSzIU.css → DebugToolsPanel-DFw6_G5V.css} +1 -1
  33. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Du1FDD8L.js +110 -0
  34. package/dist-web/assets/{MessageExportModal-DCB-mzf2.js → MessageExportModal-4ehLHG4x.js} +1 -1
  35. package/dist-web/assets/{electron-DDUv0ohD.js → electron-B3AteJC8.js} +1 -1
  36. package/dist-web/assets/electron-DGxVMsvC.js +1 -0
  37. package/dist-web/assets/{fetch-C1aC5Fd1.js → fetch-BQxv_UGQ.js} +1 -1
  38. package/dist-web/assets/{fetch-CwHncc6x.js → fetch-BUcl-Frr.js} +1 -1
  39. package/dist-web/assets/{fetch-Bry8iJpq.js → fetch-BwQh9WUD.js} +1 -1
  40. package/dist-web/assets/{fetch-DOtBAxUS.js → fetch-C2mSdG3h.js} +1 -1
  41. package/dist-web/assets/{fetch-Ck4rGxkO.js → fetch-CZDFhvxY.js} +1 -1
  42. package/dist-web/assets/{fetch-B9Oy0bcD.js → fetch-ChZEF_n3.js} +1 -1
  43. package/dist-web/assets/{fetch-D8Ng59an.js → fetch-DCQN6wfR.js} +1 -1
  44. package/dist-web/assets/{fetch-C69hj3wS.js → fetch-DZFx8FA4.js} +1 -1
  45. package/dist-web/assets/{fetch-Ckang-F8.js → fetch-DlJFgbum.js} +1 -1
  46. package/dist-web/assets/{fetch-B3gsbY_U.js → fetch-O8M5ZWsc.js} +1 -1
  47. package/dist-web/assets/index-B_uZZGqi.js +173 -0
  48. package/dist-web/assets/{index-YmzTgkb2.css → index-CoX0L-A8.css} +1 -1
  49. package/dist-web/assets/{index-C16V6EoP.js → index-DR78oafF.js} +9 -9
  50. package/dist-web/assets/{index-zAi37ZQ4.js → index-DTlfOd_u.js} +2 -2
  51. package/dist-web/assets/{index-Bd9j73nw.js → index-DlkH2E7N.js} +1 -1
  52. package/dist-web/assets/{index-D-J8dY7w.js → index-KAFPL6dl.js} +1 -1
  53. package/dist-web/assets/{index-BfFj8Evo.js → index-qVTX1tLg.js} +3 -3
  54. package/dist-web/assets/web-CoKgy4Oo.js +1 -0
  55. package/dist-web/index.html +2 -2
  56. package/package.json +1 -1
  57. package/dist/chunk-3HOTLLFC.mjs.map +0 -1
  58. package/dist/chunk-7LPVQR77.mjs.map +0 -1
  59. package/dist/chunk-KCFVHVO4.mjs.map +0 -1
  60. package/dist/chunk-M6ABWESL.mjs.map +0 -1
  61. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-C8mwzxDa.js +0 -110
  62. package/dist-web/assets/electron-CRSL7qPM.js +0 -1
  63. package/dist-web/assets/index-B40SKtW8.js +0 -173
  64. package/dist-web/assets/web-DKFeBb_F.js +0 -1
  65. /package/dist/{chunk-FHLPXRDL.mjs.map → chunk-H22VDCPB.mjs.map} +0 -0
  66. /package/dist/{http-CPFUTIUB.mjs.map → http-LSCVGWCI.mjs.map} +0 -0
  67. /package/dist/{import-UJXKIKYN.mjs.map → import-E6HBJMMT.mjs.map} +0 -0
  68. /package/dist/{src-M3MO3LEL.mjs.map → src-HZILDI7C.mjs.map} +0 -0
  69. /package/dist/{stream-import-4VDJZHMY.mjs.map → stream-import-IMRULRS2.mjs.map} +0 -0
@@ -8,30 +8,53 @@ import {
8
8
  closeServerAiLogger,
9
9
  createCliRunAgentStream,
10
10
  initServerAiLogger
11
- } from "./chunk-7LPVQR77.mjs";
12
- import {
13
- importData,
14
- parseFile
15
- } from "./chunk-UBJHT3BU.mjs";
16
- import {
17
- analyzeIncrementalImport,
18
- analyzeNewImport,
19
- incrementalImport,
20
- streamImport
21
- } from "./chunk-6FXGC6HE.mjs";
11
+ } from "./chunk-PXWAYFII.mjs";
22
12
  import {
23
13
  AGENT_TOOL_REGISTRY,
24
14
  CoreDataProvider
25
- } from "./chunk-M6ABWESL.mjs";
15
+ } from "./chunk-OD3YH23N.mjs";
26
16
  import {
27
17
  assertCliDataDirCompatible
28
- } from "./chunk-FHLPXRDL.mjs";
18
+ } from "./chunk-H22VDCPB.mjs";
29
19
  import {
30
20
  getVersion
31
21
  } from "./chunk-5ADKYYNA.mjs";
32
22
  import {
33
23
  resolveCliPath
34
24
  } from "./chunk-FJ35V3TE.mjs";
25
+ import {
26
+ importData,
27
+ parseFile
28
+ } from "./chunk-ST5APJB2.mjs";
29
+ import {
30
+ analyzeIncrementalImport,
31
+ analyzeNewImport,
32
+ incrementalImport,
33
+ streamImport
34
+ } from "./chunk-MDDWX2MG.mjs";
35
+ import {
36
+ AnalyticsService,
37
+ NodePathProvider,
38
+ PreferencesManager,
39
+ applyPendingNodeDataDirMigrationIfNeeded,
40
+ buildMergedOutput,
41
+ checkConflictsFromSources,
42
+ createJiebaNlpProvider,
43
+ createNodeDataDirSwitch,
44
+ createSemanticIndexWorkerRuntimeClient,
45
+ detectAllFormats,
46
+ detectFormat,
47
+ findEntryFileInDirectory,
48
+ getDbFileVersion,
49
+ getDefaultNodeUserDataDir,
50
+ getOrComputeAnalysisCache,
51
+ getPendingNodeDataDirMigration,
52
+ getSupportedFormats,
53
+ hasPendingElectronDataWarning,
54
+ scanMultiChatFile,
55
+ serializeChatLabToJsonl,
56
+ streamParseFileInfo
57
+ } from "./chunk-DHRSLYUM.mjs";
35
58
  import {
36
59
  AIChatManager,
37
60
  ALL_MIGRATIONS,
@@ -46,24 +69,17 @@ import {
46
69
  LLMConfigStore,
47
70
  MergeSessionCache,
48
71
  MigrationRunner,
49
- NodePathProvider,
50
72
  POS_TAG_DEFINITIONS,
51
- PreferencesManager,
73
+ SEMANTIC_INDEX_CONFIG_FILE,
74
+ SemanticIndexConfigStore,
52
75
  SkillManagerCore,
53
76
  TempDbWriter,
54
- applyPendingNodeDataDirMigrationIfNeeded,
55
- buildMergedOutput,
56
77
  buildPiModel,
57
- checkConflictsFromSources,
58
78
  computeWordFrequency,
59
79
  countMessagesTokens,
60
80
  createDatabaseManagerAdapter,
61
- createJiebaNlpProvider,
62
- createNodeDataDirSwitch,
63
- createSemanticIndexService,
81
+ deleteAuthProfile,
64
82
  deleteDict,
65
- detectAllFormats,
66
- detectFormat,
67
83
  downloadDict,
68
84
  ensureDefaultDict,
69
85
  executeReadonlySql,
@@ -71,15 +87,12 @@ import {
71
87
  exportSessionToJson,
72
88
  export_service_exports,
73
89
  fetchRemoteModels,
74
- findEntryFileInDirectory,
75
90
  getAvailableYears,
76
91
  getBuiltinModelsByProvider,
77
92
  getCatchphraseAnalysis,
78
93
  getCheckInAnalysis,
79
94
  getClusterGraph,
80
95
  getDailyActivity,
81
- getDbFileVersion,
82
- getDefaultNodeUserDataDir,
83
96
  getDefaultRulesForLocale,
84
97
  getDictList,
85
98
  getDivingAnalysis,
@@ -99,44 +112,40 @@ import {
99
112
  getMessageTypeStats,
100
113
  getMonthlyActivity,
101
114
  getNightOwlAnalysis,
102
- getOrComputeAnalysisCache,
103
- getPendingNodeDataDirMigration,
104
115
  getRelationshipStats,
105
116
  getRepeatAnalysis,
106
117
  getSchemaDetailed,
107
118
  getSessionInfo,
108
119
  getSessionMeta,
109
120
  getSessionOverview,
110
- getSupportedFormats,
111
121
  getTextLengthPercentiles,
112
122
  getTextStats,
113
123
  getTimeRange,
114
124
  getWeekdayActivity,
115
125
  getYearlyActivity,
116
- hasPendingElectronDataWarning,
117
126
  initNlpDir,
118
127
  isDictDownloaded,
119
128
  isNewerStableVersion,
129
+ isSemanticIndexConfigured,
120
130
  loadConfig,
121
131
  member_service_exports,
122
132
  mergeRulesForLocale,
123
133
  owner_profile_service_exports,
134
+ persistSemanticIndexConfig,
124
135
  queryMessages,
125
136
  resolveApiKey,
137
+ resolveSemanticIndexApiKeySet,
126
138
  runSimpleLlmStream,
127
- scanMultiChatFile,
128
139
  segmentText,
129
- serializeChatLabToJsonl,
130
140
  session_index_service_exports,
131
141
  session_service_exports,
132
- streamParseFileInfo,
133
142
  stripAvatarFields,
134
143
  summary_service_exports,
135
144
  validateApiKey,
136
145
  verifyCliDataPath,
137
146
  writeAuthProfile,
138
147
  writeConfigField
139
- } from "./chunk-KCFVHVO4.mjs";
148
+ } from "./chunk-B2VLK3FB.mjs";
140
149
 
141
150
  // src/http/index.ts
142
151
  import * as fs11 from "fs";
@@ -1618,34 +1627,63 @@ function registerAiSummaryRoutes(server2, ctx) {
1618
1627
  }
1619
1628
 
1620
1629
  // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1630
+ import path4 from "path";
1621
1631
  function registerSemanticIndexRoutes(server2, ctx) {
1622
1632
  const service = ctx.semanticIndexService;
1623
- if (!service) return;
1633
+ if (!service) {
1634
+ if (ctx.aiDataDir) {
1635
+ const configStore = new SemanticIndexConfigStore(path4.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1636
+ const configResponse = (config) => ({
1637
+ config,
1638
+ apiKeySet: resolveSemanticIndexApiKeySet(config, ctx.resolveApiKey),
1639
+ configured: isSemanticIndexConfigured(config)
1640
+ });
1641
+ server2.get("/_web/ai/semantic-index/config", async () => configResponse(configStore.get()));
1642
+ server2.put(
1643
+ "/_web/ai/semantic-index/config",
1644
+ async (request) => {
1645
+ const config = persistSemanticIndexConfig(configStore, request.body.config, {
1646
+ apiKey: request.body.apiKey,
1647
+ writeAuthProfile: ctx.writeAuthProfile
1648
+ });
1649
+ return configResponse(config);
1650
+ }
1651
+ );
1652
+ }
1653
+ server2.get("/_web/ai/semantic-index/enabled", async () => ({ sessions: [] }));
1654
+ server2.get("/_web/ai/semantic-index/status", async () => ({ status: null }));
1655
+ server2.post("/_web/ai/semantic-index/status", async () => ({ sessions: [] }));
1656
+ return;
1657
+ }
1624
1658
  server2.get("/_web/ai/semantic-index/config", async () => {
1625
- return { config: service.getConfig(), apiKeySet: service.hasApiKey(), configured: service.isConfigured() };
1659
+ return {
1660
+ config: await service.getConfig(),
1661
+ apiKeySet: await service.hasApiKey(),
1662
+ configured: await service.isConfigured()
1663
+ };
1626
1664
  });
1627
1665
  server2.put(
1628
1666
  "/_web/ai/semantic-index/config",
1629
1667
  async (request) => {
1630
- const config = service.setConfig(request.body.config, { apiKey: request.body.apiKey });
1631
- return { config, apiKeySet: service.hasApiKey(), configured: service.isConfigured() };
1668
+ const config = await service.setConfig(request.body.config, { apiKey: request.body.apiKey });
1669
+ return { config, apiKeySet: await service.hasApiKey(), configured: await service.isConfigured() };
1632
1670
  }
1633
1671
  );
1634
1672
  server2.get("/_web/ai/semantic-index/enabled", async () => {
1635
- return { sessions: service.listEnabledStatuses() };
1673
+ return { sessions: await service.listEnabledStatuses() };
1636
1674
  });
1637
1675
  server2.get("/_web/ai/semantic-index/status", async (request) => {
1638
- return { status: service.status(request.query.sessionId) };
1676
+ return { status: await service.status(request.query.sessionId) };
1639
1677
  });
1640
1678
  server2.post("/_web/ai/semantic-index/status", async (request) => {
1641
- return { sessions: service.statusForSessions(request.body.sessionIds ?? []) };
1679
+ return { sessions: await service.statusForSessions(request.body.sessionIds ?? []) };
1642
1680
  });
1643
- const sessionAction = (path12, action) => {
1644
- server2.post(path12, async (request, reply) => {
1681
+ const sessionAction = (path13, action) => {
1682
+ server2.post(path13, async (request, reply) => {
1645
1683
  const { sessionId } = request.body;
1646
1684
  if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
1647
1685
  await action(sessionId);
1648
- return { status: service.status(sessionId) };
1686
+ return { status: await service.status(sessionId) };
1649
1687
  });
1650
1688
  };
1651
1689
  sessionAction("/_web/ai/semantic-index/enable", (id) => service.enable(id));
@@ -1655,11 +1693,11 @@ function registerSemanticIndexRoutes(server2, ctx) {
1655
1693
  sessionAction("/_web/ai/semantic-index/cancel", (id) => service.cancel(id));
1656
1694
  sessionAction("/_web/ai/semantic-index/rebuild", (id) => service.rebuild(id));
1657
1695
  server2.post("/_web/ai/semantic-index/build-pending", async () => {
1658
- service.buildAllPending();
1659
- return { sessions: service.listEnabledStatuses() };
1696
+ await service.buildAllPending();
1697
+ return { sessions: await service.listEnabledStatuses() };
1660
1698
  });
1661
1699
  server2.post("/_web/ai/semantic-index/cleanup", async () => {
1662
- return service.cleanupUnused();
1700
+ return await service.cleanupUnused();
1663
1701
  });
1664
1702
  server2.post(
1665
1703
  "/_web/ai/semantic-index/search",
@@ -1674,7 +1712,7 @@ function registerSemanticIndexRoutes(server2, ctx) {
1674
1712
  // ../../packages/http-routes/src/routes/web/merge.ts
1675
1713
  import * as fs3 from "fs";
1676
1714
  import * as os from "os";
1677
- import * as path4 from "path";
1715
+ import * as path5 from "path";
1678
1716
  function ensureDb2(ctx, sessionId) {
1679
1717
  const db = ctx.dbManager.open(sessionId);
1680
1718
  if (!db) throw sessionNotFound(sessionId);
@@ -1691,8 +1729,8 @@ function registerMergeRoutes(server2, ctx) {
1691
1729
  if (contentType.includes("multipart/form-data")) {
1692
1730
  const data = await request.file();
1693
1731
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1694
- const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merge-"));
1695
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1732
+ const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merge-"));
1733
+ const tmpPath = path5.join(tmpDir, data.filename || "upload");
1696
1734
  const chunks = [];
1697
1735
  for await (const chunk of data.file) {
1698
1736
  chunks.push(chunk);
@@ -1710,12 +1748,12 @@ function registerMergeRoutes(server2, ctx) {
1710
1748
  try {
1711
1749
  const result = await streamParseFileInfo(filePath, {
1712
1750
  createTempDatabase(sourceFilePath) {
1713
- return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1751
+ return mergeCache.createTempDatabase(path5.basename(sourceFilePath));
1714
1752
  },
1715
1753
  onProgress() {
1716
1754
  }
1717
1755
  });
1718
- const filename = path4.basename(filePath);
1756
+ const filename = path5.basename(filePath);
1719
1757
  const handle = mergeCache.store(filename, result.tempDbPath);
1720
1758
  return {
1721
1759
  handle,
@@ -1784,9 +1822,9 @@ function registerMergeRoutes(server2, ctx) {
1784
1822
  return reply.code(501).send({ error: "Import not available on this server" });
1785
1823
  }
1786
1824
  const jsonData = JSON.stringify(merged.chatLabData);
1787
- const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merged-"));
1788
- const safeName = path4.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1789
- const tmpPath = path4.join(tmpDir, `${safeName}.json`);
1825
+ const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merged-"));
1826
+ const safeName = path5.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1827
+ const tmpPath = path5.join(tmpDir, `${safeName}.json`);
1790
1828
  fs3.writeFileSync(tmpPath, jsonData, "utf-8");
1791
1829
  try {
1792
1830
  const importResult = await streamImport2(dbManager2, tmpPath);
@@ -1873,14 +1911,14 @@ function registerMergeRoutes(server2, ctx) {
1873
1911
  // ../../packages/http-routes/src/routes/web/cache.ts
1874
1912
  import * as fs4 from "fs";
1875
1913
  import * as fsp from "fs/promises";
1876
- import * as path5 from "path";
1914
+ import * as path6 from "path";
1877
1915
  async function getDirSize(dirPath) {
1878
1916
  let totalSize = 0;
1879
1917
  try {
1880
1918
  if (!fs4.existsSync(dirPath)) return 0;
1881
1919
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1882
1920
  for (const file of files) {
1883
- const filePath = path5.join(dirPath, file.name);
1921
+ const filePath = path6.join(dirPath, file.name);
1884
1922
  if (file.isDirectory()) {
1885
1923
  totalSize += await getDirSize(filePath);
1886
1924
  } else {
@@ -1898,7 +1936,7 @@ async function getFileCount(dirPath) {
1898
1936
  if (!fs4.existsSync(dirPath)) return 0;
1899
1937
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1900
1938
  for (const file of files) {
1901
- const filePath = path5.join(dirPath, file.name);
1939
+ const filePath = path6.join(dirPath, file.name);
1902
1940
  if (file.isDirectory()) {
1903
1941
  count += await getFileCount(filePath);
1904
1942
  } else {
@@ -1911,8 +1949,8 @@ async function getFileCount(dirPath) {
1911
1949
  }
1912
1950
  function hasChatLabDatabases(dirPath) {
1913
1951
  try {
1914
- const markerPath = path5.join(dirPath, ".chatlab");
1915
- const dbDir = path5.join(dirPath, "databases");
1952
+ const markerPath = path6.join(dirPath, ".chatlab");
1953
+ const dbDir = path6.join(dirPath, "databases");
1916
1954
  if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
1917
1955
  return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1918
1956
  } catch {
@@ -1980,7 +2018,7 @@ function registerCacheRoutes(server2, ctx) {
1980
2018
  if (!fs4.existsSync(dirPath)) return { success: true };
1981
2019
  const files = await fsp.readdir(dirPath);
1982
2020
  for (const file of files) {
1983
- const filePath = path5.join(dirPath, file);
2021
+ const filePath = path6.join(dirPath, file);
1984
2022
  const stat2 = await fsp.stat(filePath);
1985
2023
  if (stat2.isDirectory()) {
1986
2024
  await fsp.rm(filePath, { recursive: true });
@@ -1997,7 +2035,7 @@ function registerCacheRoutes(server2, ctx) {
1997
2035
  defaultPath: ctx.defaultUserDataDir,
1998
2036
  isCustom: ctx.isCustomDataDir ?? false,
1999
2037
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
2000
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path5.resolve(pp.getUserDataDir()) !== path5.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2038
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path6.resolve(pp.getUserDataDir()) !== path6.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2001
2039
  pendingMigration: pending ? {
2002
2040
  from: pending.from,
2003
2041
  to: pending.to,
@@ -2014,7 +2052,7 @@ function registerCacheRoutes(server2, ctx) {
2014
2052
  return ctx.setDataDir(targetPath, migrate);
2015
2053
  });
2016
2054
  server2.get("/_web/cache/latest-import-log", async () => {
2017
- const importLogDir = path5.join(pp.getLogsDir(), "import");
2055
+ const importLogDir = path6.join(pp.getLogsDir(), "import");
2018
2056
  if (!fs4.existsSync(importLogDir)) {
2019
2057
  return { success: false, error: "Log directory not found" };
2020
2058
  }
@@ -2025,7 +2063,7 @@ function registerCacheRoutes(server2, ctx) {
2025
2063
  }
2026
2064
  const fileStats = await Promise.all(
2027
2065
  logFiles.map(async (f) => {
2028
- const filePath = path5.join(importLogDir, f);
2066
+ const filePath = path6.join(importLogDir, f);
2029
2067
  const stat2 = await fsp.stat(filePath);
2030
2068
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
2031
2069
  })
@@ -2052,7 +2090,7 @@ function registerCacheRoutes(server2, ctx) {
2052
2090
  if (!fs4.existsSync(downloadsDir)) {
2053
2091
  fs4.mkdirSync(downloadsDir, { recursive: true });
2054
2092
  }
2055
- const filePath = path5.join(downloadsDir, filename);
2093
+ const filePath = path6.join(downloadsDir, filename);
2056
2094
  fs4.writeFileSync(filePath, buffer);
2057
2095
  return { success: true, filePath };
2058
2096
  });
@@ -2092,6 +2130,32 @@ function registerCacheRoutes(server2, ctx) {
2092
2130
  });
2093
2131
  }
2094
2132
 
2133
+ // ../../packages/http-routes/src/routes/web/telemetry.ts
2134
+ function registerTelemetryRoutes(server2, ctx) {
2135
+ server2.get("/_web/telemetry/enabled", async () => {
2136
+ return { enabled: ctx.analyticsService?.getEnabled() ?? false };
2137
+ });
2138
+ server2.post("/_web/telemetry/enabled", async (req) => {
2139
+ if (!ctx.analyticsService || typeof req.body?.enabled !== "boolean") return { success: false };
2140
+ ctx.analyticsService.setEnabled(req.body.enabled);
2141
+ return { success: true };
2142
+ });
2143
+ server2.post(
2144
+ "/_web/telemetry/track",
2145
+ async (req, reply) => {
2146
+ if (!ctx.analyticsService) return { ok: true };
2147
+ const { eventName, properties } = req.body ?? {};
2148
+ if (!eventName) return reply.code(400).send({ error: "eventName required" });
2149
+ if (eventName === "app_active") {
2150
+ await ctx.analyticsService.trackDailyActive(properties);
2151
+ } else {
2152
+ await ctx.analyticsService.track(eventName, properties);
2153
+ }
2154
+ return { ok: true };
2155
+ }
2156
+ );
2157
+ }
2158
+
2095
2159
  // ../../packages/http-routes/src/register.ts
2096
2160
  function registerSharedRoutes(server2, ctx, options) {
2097
2161
  const resolvedCtx = ctx.preferencesManager ? ctx : { ...ctx, preferencesManager: new PreferencesManager(ctx.pathProvider.getSystemDir()) };
@@ -2130,6 +2194,7 @@ function registerSharedRoutes(server2, ctx, options) {
2130
2194
  registerSemanticIndexRoutes(server2, resolvedCtx);
2131
2195
  registerMergeRoutes(server2, resolvedCtx);
2132
2196
  registerCacheRoutes(server2, resolvedCtx);
2197
+ registerTelemetryRoutes(server2, resolvedCtx);
2133
2198
  }
2134
2199
 
2135
2200
  // ../../packages/http-routes/src/auth.ts
@@ -2207,12 +2272,12 @@ function createServer() {
2207
2272
 
2208
2273
  // src/http/routes/web/index.ts
2209
2274
  import * as os3 from "os";
2210
- import * as path8 from "path";
2275
+ import * as path9 from "path";
2211
2276
 
2212
2277
  // src/http/routes/web/import.ts
2213
2278
  import * as fs6 from "fs";
2214
2279
  import * as os2 from "os";
2215
- import * as path6 from "path";
2280
+ import * as path7 from "path";
2216
2281
 
2217
2282
  // src/http/routes/web/helpers.ts
2218
2283
  import * as fs5 from "fs";
@@ -2245,8 +2310,8 @@ function registerImportRoutes(server2, dbManager2) {
2245
2310
  server2.post("/_web/detect-format", async (request, reply) => {
2246
2311
  const data = await request.file();
2247
2312
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2248
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-detect-"));
2249
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2313
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-detect-"));
2314
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2250
2315
  try {
2251
2316
  const chunks = [];
2252
2317
  for await (const chunk of data.file) {
@@ -2263,8 +2328,8 @@ function registerImportRoutes(server2, dbManager2) {
2263
2328
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2264
2329
  const data = await request.file();
2265
2330
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2266
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-scan-"));
2267
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2331
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-scan-"));
2332
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2268
2333
  try {
2269
2334
  const chunks = [];
2270
2335
  for await (const chunk of data.file) {
@@ -2280,8 +2345,8 @@ function registerImportRoutes(server2, dbManager2) {
2280
2345
  server2.post("/_web/import", async (request, reply) => {
2281
2346
  const data = await request.file();
2282
2347
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2283
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-import-"));
2284
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2348
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-"));
2349
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2285
2350
  const chunks = [];
2286
2351
  for await (const chunk of data.file) {
2287
2352
  chunks.push(chunk);
@@ -2329,7 +2394,7 @@ data: ${JSON.stringify(eventData)}
2329
2394
  server2.post("/_web/import-directory", async (request, reply) => {
2330
2395
  const parts = request.parts();
2331
2396
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2332
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-dir-import-"));
2397
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-dir-import-"));
2333
2398
  const relativePaths = [];
2334
2399
  const fileBuffers = [];
2335
2400
  try {
@@ -2357,9 +2422,9 @@ data: ${JSON.stringify(eventData)}
2357
2422
  if (segments.length > 1) {
2358
2423
  relPath = segments.slice(1).join("/");
2359
2424
  }
2360
- const targetPath = path6.resolve(tmpDir, relPath);
2361
- if (!targetPath.startsWith(tmpDir + path6.sep)) continue;
2362
- fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
2425
+ const targetPath = path7.resolve(tmpDir, relPath);
2426
+ if (!targetPath.startsWith(tmpDir + path7.sep)) continue;
2427
+ fs6.mkdirSync(path7.dirname(targetPath), { recursive: true });
2363
2428
  fs6.writeFileSync(targetPath, fileBuffers[i].data);
2364
2429
  }
2365
2430
  const entryPath = findEntryFileInDirectory(tmpDir);
@@ -2405,8 +2470,8 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2405
2470
  const sessionId = request.params.id;
2406
2471
  const data = await request.file();
2407
2472
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2408
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-inc-"));
2409
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2473
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-inc-"));
2474
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2410
2475
  const chunks = [];
2411
2476
  for await (const chunk of data.file) {
2412
2477
  chunks.push(chunk);
@@ -2443,8 +2508,8 @@ data: ${JSON.stringify(eventData)}
2443
2508
  const sessionId = request.params.id;
2444
2509
  const data = await request.file();
2445
2510
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2446
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2447
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2511
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2512
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2448
2513
  const chunks = [];
2449
2514
  for await (const chunk of data.file) {
2450
2515
  chunks.push(chunk);
@@ -2461,8 +2526,8 @@ data: ${JSON.stringify(eventData)}
2461
2526
  server2.post("/_web/import/analyze", async (request, reply) => {
2462
2527
  const data = await request.file();
2463
2528
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2464
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2465
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2529
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2530
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2466
2531
  const chunks = [];
2467
2532
  for await (const chunk of data.file) {
2468
2533
  chunks.push(chunk);
@@ -2497,12 +2562,12 @@ data: ${JSON.stringify(eventData)}
2497
2562
 
2498
2563
  `);
2499
2564
  }
2500
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-demo-"));
2565
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-demo-"));
2501
2566
  try {
2502
2567
  const localPaths = [];
2503
2568
  for (let i = 0; i < total; i++) {
2504
2569
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2505
- const localPath = path6.join(tmpDir, DEMO_FILES[i]);
2570
+ const localPath = path7.join(tmpDir, DEMO_FILES[i]);
2506
2571
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2507
2572
  signal: AbortSignal.timeout(6e4)
2508
2573
  });
@@ -2538,7 +2603,7 @@ data: ${JSON.stringify(eventData)}
2538
2603
  }
2539
2604
 
2540
2605
  // src/http/routes/web/cache.ts
2541
- import * as path7 from "path";
2606
+ import * as path8 from "path";
2542
2607
  import { execFile } from "child_process";
2543
2608
  function runExecFile(file, args, runner = execFile) {
2544
2609
  return new Promise((resolve4, reject) => {
@@ -2566,7 +2631,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2566
2631
  } else if (platform === "win32") {
2567
2632
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2568
2633
  } else {
2569
- await runExecFile("xdg-open", [path7.dirname(filePath)], runner);
2634
+ await runExecFile("xdg-open", [path8.dirname(filePath)], runner);
2570
2635
  }
2571
2636
  }
2572
2637
 
@@ -2589,42 +2654,29 @@ function registerWebRoutes(server2, dbManager2, options) {
2589
2654
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2590
2655
  mergeCache?.cleanupOrphans();
2591
2656
  const fallbackPathProvider = {
2592
- getSystemDir: () => path8.join(os3.homedir(), ".chatlab"),
2593
- getUserDataDir: () => path8.join(os3.homedir(), ".chatlab", "data"),
2594
- getDatabaseDir: () => path8.join(os3.homedir(), ".chatlab", "data", "databases"),
2595
- getVectorDir: () => path8.join(os3.homedir(), ".chatlab", "data", "vector"),
2596
- getAiDataDir: () => path8.join(os3.homedir(), ".chatlab", "ai"),
2597
- getSettingsDir: () => path8.join(os3.homedir(), ".chatlab", "settings"),
2598
- getCacheDir: () => path8.join(os3.homedir(), ".chatlab", "cache"),
2599
- getTempDir: () => path8.join(os3.homedir(), ".chatlab", "temp"),
2600
- getLogsDir: () => path8.join(os3.homedir(), ".chatlab", "logs"),
2601
- getDownloadsDir: () => path8.join(os3.homedir(), "Downloads")
2657
+ getSystemDir: () => path9.join(os3.homedir(), ".chatlab"),
2658
+ getUserDataDir: () => path9.join(os3.homedir(), ".chatlab", "data"),
2659
+ getDatabaseDir: () => path9.join(os3.homedir(), ".chatlab", "data", "databases"),
2660
+ getVectorDir: () => path9.join(os3.homedir(), ".chatlab", "data", "vector"),
2661
+ getAiDataDir: () => path9.join(os3.homedir(), ".chatlab", "ai"),
2662
+ getSettingsDir: () => path9.join(os3.homedir(), ".chatlab", "settings"),
2663
+ getCacheDir: () => path9.join(os3.homedir(), ".chatlab", "cache"),
2664
+ getTempDir: () => path9.join(os3.homedir(), ".chatlab", "temp"),
2665
+ getLogsDir: () => path9.join(os3.homedir(), ".chatlab", "logs"),
2666
+ getDownloadsDir: () => path9.join(os3.homedir(), "Downloads")
2602
2667
  };
2603
2668
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2604
2669
  const ai = options?.aiContext;
2605
2670
  const cliStreamImport = async (dm, filePath) => {
2606
- const { streamImport: streamImport2 } = await import("./stream-import-4VDJZHMY.mjs");
2671
+ const { streamImport: streamImport2 } = await import("./stream-import-IMRULRS2.mjs");
2607
2672
  const result = await streamImport2(dm, filePath);
2608
2673
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2609
2674
  return { sessionId: result.sessionId };
2610
2675
  };
2611
2676
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2612
- const isCustom = path8.resolve(resolvedPathProvider.getUserDataDir()) !== path8.resolve(defaultUserDataDir);
2613
- let semanticIndexService = options?.semanticIndexService;
2614
- if (!semanticIndexService) {
2615
- try {
2616
- semanticIndexService = createSemanticIndexService({
2617
- pathProvider: resolvedPathProvider,
2618
- sessionAdapter: adapter,
2619
- nativeBinding: options?.nativeBinding
2620
- });
2621
- semanticIndexService.recover();
2622
- server2.addHook("onClose", async () => semanticIndexService?.close());
2623
- } catch (err) {
2624
- console.warn("[semantic-index] service unavailable:", err instanceof Error ? err.message : String(err));
2625
- semanticIndexService = void 0;
2626
- }
2627
- }
2677
+ const isCustom = path9.resolve(resolvedPathProvider.getUserDataDir()) !== path9.resolve(defaultUserDataDir);
2678
+ const semanticIndexService = options?.semanticIndexService;
2679
+ const analyticsService = "A-US-0629338532" ? new AnalyticsService(resolvedPathProvider.getSystemDir(), "A-US-0629338532", getVersion()) : void 0;
2628
2680
  registerSharedRoutes(
2629
2681
  server2,
2630
2682
  {
@@ -2634,6 +2686,7 @@ function registerWebRoutes(server2, dbManager2, options) {
2634
2686
  getVersion,
2635
2687
  nativeBinding: options?.nativeBinding,
2636
2688
  semanticIndexService,
2689
+ analyticsService,
2637
2690
  openDirectory: openDirectoryPath,
2638
2691
  showInFolder: showPathInFolder,
2639
2692
  downloadsDir: resolvedPathProvider.getDownloadsDir(),
@@ -2717,7 +2770,7 @@ function registerProxyRoutes(server2) {
2717
2770
 
2718
2771
  // src/ai/manager-factory.ts
2719
2772
  import * as fs7 from "fs";
2720
- import * as path9 from "path";
2773
+ import * as path10 from "path";
2721
2774
  import { createHash, randomUUID } from "crypto";
2722
2775
  var nodeFs = {
2723
2776
  ensureDir(dir) {
@@ -2740,7 +2793,7 @@ var nodeFs = {
2740
2793
  return fs7.existsSync(filePath);
2741
2794
  },
2742
2795
  joinPath(...parts) {
2743
- return path9.join(...parts);
2796
+ return path10.join(...parts);
2744
2797
  }
2745
2798
  };
2746
2799
  var managerCache = /* @__PURE__ */ new Map();
@@ -2750,13 +2803,13 @@ function getAssistantManager(aiDataDir) {
2750
2803
  cached = {
2751
2804
  assistant: new AssistantManager({
2752
2805
  fs: nodeFs,
2753
- assistantsDir: path9.join(aiDataDir, "assistants"),
2806
+ assistantsDir: path10.join(aiDataDir, "assistants"),
2754
2807
  builtinRawConfigs: [],
2755
2808
  generateId: () => `custom_${randomUUID().replace(/-/g, "").slice(0, 12)}`
2756
2809
  }),
2757
2810
  skill: new SkillManagerCore({
2758
2811
  fs: nodeFs,
2759
- skillsDir: path9.join(aiDataDir, "skills"),
2812
+ skillsDir: path10.join(aiDataDir, "skills"),
2760
2813
  builtinRawSkills: [],
2761
2814
  contentHash: (content) => createHash("md5").update(content).digest("hex")
2762
2815
  })
@@ -2781,7 +2834,7 @@ var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2781
2834
 
2782
2835
  // ../../packages/sync/src/data-source-manager.ts
2783
2836
  import * as fs8 from "fs";
2784
- import * as path10 from "path";
2837
+ import * as path11 from "path";
2785
2838
  import * as crypto from "crypto";
2786
2839
  var CONFIG_FILE = "data-sources.json";
2787
2840
  function generateId(prefix = "ds") {
@@ -2809,7 +2862,7 @@ var DataSourceManager = class {
2809
2862
  this.logger = logger ?? NOOP_LOGGER;
2810
2863
  }
2811
2864
  getConfigPath() {
2812
- return path10.join(this.configDir, CONFIG_FILE);
2865
+ return path11.join(this.configDir, CONFIG_FILE);
2813
2866
  }
2814
2867
  ensureDir() {
2815
2868
  fs8.mkdirSync(this.configDir, { recursive: true });
@@ -3036,6 +3089,41 @@ function fileContainsMessages(filePath) {
3036
3089
  return false;
3037
3090
  }
3038
3091
  }
3092
+ function getMaxMessageTimestampFromFile(filePath) {
3093
+ try {
3094
+ let maxTs = null;
3095
+ const visitTimestamp = (value) => {
3096
+ const ts = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
3097
+ if (typeof ts === "number" && Number.isFinite(ts)) {
3098
+ maxTs = maxTs === null ? ts : Math.max(maxTs, ts);
3099
+ }
3100
+ };
3101
+ if (filePath.endsWith(".jsonl")) {
3102
+ const content = fs9.readFileSync(filePath, "utf-8");
3103
+ for (const line of content.split("\n")) {
3104
+ const trimmed = line.trim();
3105
+ if (!trimmed) continue;
3106
+ try {
3107
+ const obj = JSON.parse(trimmed);
3108
+ if (obj._type === "message") visitTimestamp(obj.timestamp);
3109
+ } catch {
3110
+ continue;
3111
+ }
3112
+ }
3113
+ return maxTs;
3114
+ }
3115
+ const raw = fs9.readFileSync(filePath, "utf-8");
3116
+ const parsed = JSON.parse(raw);
3117
+ if (Array.isArray(parsed.messages)) {
3118
+ for (const message of parsed.messages) {
3119
+ visitTimestamp(message?.timestamp);
3120
+ }
3121
+ }
3122
+ return maxTs;
3123
+ } catch {
3124
+ return null;
3125
+ }
3126
+ }
3039
3127
  function cleanupTempFile(filePath) {
3040
3128
  try {
3041
3129
  if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
@@ -3089,6 +3177,8 @@ var PullEngine = class {
3089
3177
  this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
3090
3178
  let totalNewMessages = 0;
3091
3179
  let since = sess.lastPullAt;
3180
+ let offset;
3181
+ let nextPullSince = sess.lastPullAt;
3092
3182
  let pageCount = 0;
3093
3183
  let resyncAttempted = false;
3094
3184
  this.progressMap.set(sess.id, { sessionId: sess.id, sessionName: sess.name, current: 0, pages: 0, done: false });
@@ -3097,6 +3187,7 @@ var PullEngine = class {
3097
3187
  pageCount++;
3098
3188
  const tempFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
3099
3189
  since,
3190
+ offset,
3100
3191
  limit: ds.pullLimit
3101
3192
  });
3102
3193
  try {
@@ -3106,6 +3197,7 @@ var PullEngine = class {
3106
3197
  if (stat2.size < 1024) {
3107
3198
  if (!fileContainsMessages(tempFile) && sync0?.hasMore === false) {
3108
3199
  cleanupTempFile(tempFile);
3200
+ if (sync0?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, sync0.nextSince);
3109
3201
  const retryDelays = [2e3, 3e3, 5e3];
3110
3202
  let retrySuccess = false;
3111
3203
  let retryHasMore = false;
@@ -3116,21 +3208,26 @@ var PullEngine = class {
3116
3208
  await new Promise((r) => setTimeout(r, retryDelays[ri]));
3117
3209
  const retryFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
3118
3210
  since,
3211
+ offset,
3119
3212
  limit: ds.pullLimit
3120
3213
  });
3121
3214
  const retryStat = fs9.statSync(retryFile);
3122
3215
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
3123
3216
  const retrySync = parseSyncFromFile(retryFile);
3124
3217
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
3218
+ if (retrySync.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, retrySync.nextSince);
3125
3219
  cleanupTempFile(retryFile);
3126
3220
  continue;
3127
3221
  }
3222
+ const retryMaxTs = getMaxMessageTimestampFromFile(retryFile);
3128
3223
  const retryResult = await this.importTempFile(ds.baseUrl, sess, retryFile);
3129
3224
  cleanupTempFile(retryFile);
3130
3225
  if (retryResult.needFullResync && !resyncAttempted) {
3131
3226
  resyncAttempted = true;
3132
3227
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
3133
3228
  since = 0;
3229
+ offset = void 0;
3230
+ nextPullSince = 0;
3134
3231
  pageCount = 0;
3135
3232
  sess.targetSessionId = "";
3136
3233
  sess.lastPullAt = 0;
@@ -3167,6 +3264,8 @@ var PullEngine = class {
3167
3264
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: retryResult.sessionId });
3168
3265
  }
3169
3266
  totalNewMessages += retryResult.newMessageCount;
3267
+ if (retryMaxTs !== null) nextPullSince = Math.max(nextPullSince, retryMaxTs);
3268
+ if (retrySync?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, retrySync.nextSince);
3170
3269
  this.progressMap.set(sess.id, {
3171
3270
  sessionId: sess.id,
3172
3271
  sessionName: sess.name,
@@ -3176,6 +3275,9 @@ var PullEngine = class {
3176
3275
  });
3177
3276
  if (retrySync?.hasMore && retrySync.nextSince !== void 0) {
3178
3277
  since = retrySync.nextSince;
3278
+ offset = void 0;
3279
+ } else if (retrySync?.hasMore && retrySync.nextOffset !== void 0) {
3280
+ offset = retrySync.nextOffset;
3179
3281
  }
3180
3282
  retryHasMore = !!retrySync?.hasMore;
3181
3283
  retrySuccess = true;
@@ -3191,12 +3293,15 @@ var PullEngine = class {
3191
3293
  break;
3192
3294
  }
3193
3295
  const sync = sync0;
3296
+ const maxMessageTs = getMaxMessageTimestampFromFile(tempFile);
3194
3297
  const result = await this.importTempFile(ds.baseUrl, sess, tempFile);
3195
3298
  cleanupTempFile(tempFile);
3196
3299
  if (result.needFullResync && !resyncAttempted) {
3197
3300
  resyncAttempted = true;
3198
3301
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
3199
3302
  since = 0;
3303
+ offset = void 0;
3304
+ nextPullSince = 0;
3200
3305
  pageCount = 0;
3201
3306
  sess.targetSessionId = "";
3202
3307
  sess.lastPullAt = 0;
@@ -3231,6 +3336,7 @@ var PullEngine = class {
3231
3336
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: result.sessionId });
3232
3337
  }
3233
3338
  totalNewMessages += result.newMessageCount;
3339
+ if (maxMessageTs !== null) nextPullSince = Math.max(nextPullSince, maxMessageTs);
3234
3340
  this.progressMap.set(sess.id, {
3235
3341
  sessionId: sess.id,
3236
3342
  sessionName: sess.name,
@@ -3238,8 +3344,17 @@ var PullEngine = class {
3238
3344
  pages: pageCount,
3239
3345
  done: false
3240
3346
  });
3347
+ if (sync?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, sync.nextSince);
3241
3348
  if (!sync || !sync.hasMore) break;
3242
- if (sync.nextSince !== void 0) since = sync.nextSince;
3349
+ if (sync.nextSince !== void 0) {
3350
+ since = sync.nextSince;
3351
+ offset = void 0;
3352
+ } else if (sync.nextOffset !== void 0) {
3353
+ offset = sync.nextOffset;
3354
+ } else {
3355
+ this.logger.warn(`[Pull] "${sess.name}" returned hasMore=true without nextSince or nextOffset, stopping`);
3356
+ break;
3357
+ }
3243
3358
  } catch (importErr) {
3244
3359
  cleanupTempFile(tempFile);
3245
3360
  throw importErr;
@@ -3248,8 +3363,9 @@ var PullEngine = class {
3248
3363
  if (pageCount >= MAX_PAGES_PER_PULL) {
3249
3364
  this.logger.warn(`[Pull] "${sess.name}" reached page limit (${MAX_PAGES_PER_PULL}), data may be incomplete`);
3250
3365
  }
3366
+ const savedLastPullAt = Math.max(sess.lastPullAt, Math.max(0, nextPullSince - PULL_OVERLAP_SECONDS));
3251
3367
  this.dsManager.updateSession(sourceId, sess.id, {
3252
- lastPullAt: Math.floor(Date.now() / 1e3) - PULL_OVERLAP_SECONDS,
3368
+ lastPullAt: savedLastPullAt,
3253
3369
  lastStatus: "success",
3254
3370
  lastNewMessages: totalNewMessages,
3255
3371
  lastError: ""
@@ -3401,11 +3517,11 @@ function reloadTimer(dsId, skipInitialPull = false) {
3401
3517
  // src/sync/adapters.ts
3402
3518
  import * as fs10 from "fs";
3403
3519
  import * as os4 from "os";
3404
- import * as path11 from "path";
3520
+ import * as path12 from "path";
3405
3521
  import * as crypto3 from "crypto";
3406
3522
  function getTempFilePath(ext) {
3407
3523
  const id = crypto3.randomBytes(8).toString("hex");
3408
- return path11.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3524
+ return path12.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3409
3525
  }
3410
3526
  var NodeFetcher = class {
3411
3527
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3732,6 +3848,10 @@ async function startHttpServer(options) {
3732
3848
  const profileName = config2.name?.toLowerCase().replace(/\s+/g, "-") || config2.provider;
3733
3849
  writeAuthProfile(profileName, { type: "api_key", provider: config2.provider, key: apiKey });
3734
3850
  return profileName;
3851
+ },
3852
+ onApiKeyDeleted: (config2) => {
3853
+ const profileName = config2.authProfile;
3854
+ if (profileName) deleteAuthProfile(profileName);
3735
3855
  }
3736
3856
  });
3737
3857
  initServerAiLogger(pathProvider.getLogsDir());
@@ -3745,15 +3865,15 @@ async function startHttpServer(options) {
3745
3865
  });
3746
3866
  let semanticIndexService;
3747
3867
  try {
3748
- semanticIndexService = createSemanticIndexService({
3868
+ semanticIndexService = createSemanticIndexWorkerRuntimeClient({
3749
3869
  pathProvider,
3750
- sessionAdapter: createDatabaseManagerAdapter(dbManager),
3751
- nativeBinding
3870
+ runtime,
3871
+ nativeBinding,
3872
+ workerEntryUrl: import.meta.url.endsWith(".ts") ? void 0 : new URL("./semantic-index-worker.mjs", import.meta.url)
3752
3873
  });
3753
- semanticIndexService.recover();
3754
3874
  server.addHook("onClose", async () => semanticIndexService?.close());
3755
3875
  } catch (err) {
3756
- console.warn("[semantic-index] service unavailable:", err instanceof Error ? err.message : String(err));
3876
+ console.warn("[semantic-index] worker client unavailable:", err instanceof Error ? err.message : String(err));
3757
3877
  semanticIndexService = void 0;
3758
3878
  }
3759
3879
  registerWebRoutes(server, dbManager, {
@@ -3813,4 +3933,4 @@ export {
3813
3933
  startHttpServer,
3814
3934
  stopHttpServer
3815
3935
  };
3816
- //# sourceMappingURL=chunk-3HOTLLFC.mjs.map
3936
+ //# sourceMappingURL=chunk-JOXF6ROV.mjs.map