chatlab-cli 0.26.3 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +10 -4
  2. package/dist/{chat-command-D37MMEJ7.mjs → chat-command-B7POB3C3.mjs} +6 -5
  3. package/dist/{chat-command-D37MMEJ7.mjs.map → chat-command-B7POB3C3.mjs.map} +1 -1
  4. package/dist/{chunk-K5SGXTVJ.mjs → chunk-3FUF3PEY.mjs} +21 -19
  5. package/dist/{chunk-K5SGXTVJ.mjs.map → chunk-3FUF3PEY.mjs.map} +1 -1
  6. package/dist/{chunk-742TAKEP.mjs → chunk-3JNVGW62.mjs} +2 -2
  7. package/dist/{chunk-VSOWNKPA.mjs → chunk-GFHSSCCR.mjs} +11 -1
  8. package/dist/{chunk-YPTJVTVJ.mjs → chunk-I7BHOZPX.mjs} +13159 -19038
  9. package/dist/chunk-I7BHOZPX.mjs.map +1 -0
  10. package/dist/chunk-N3G43SCQ.mjs +7337 -0
  11. package/dist/chunk-N3G43SCQ.mjs.map +1 -0
  12. package/dist/{chunk-GNGZHBKM.mjs → chunk-RJ4D4QWK.mjs} +42 -7
  13. package/dist/chunk-RJ4D4QWK.mjs.map +1 -0
  14. package/dist/{chunk-VCN64OD6.mjs → chunk-SAE2H2LN.mjs} +7 -5
  15. package/dist/{chunk-VCN64OD6.mjs.map → chunk-SAE2H2LN.mjs.map} +1 -1
  16. package/dist/{chunk-QMXNJRTT.mjs → chunk-SIVERPF5.mjs} +280 -104
  17. package/dist/chunk-SIVERPF5.mjs.map +1 -0
  18. package/dist/{chunk-RMITK4IC.mjs → chunk-WQMKKUSD.mjs} +468 -118
  19. package/dist/chunk-WQMKKUSD.mjs.map +1 -0
  20. package/dist/{chunk-D5MWER7N.mjs → chunk-YUFYTDRH.mjs} +6 -4
  21. package/dist/{chunk-D5MWER7N.mjs.map → chunk-YUFYTDRH.mjs.map} +1 -1
  22. package/dist/cli.mjs +5 -4
  23. package/dist/{http-6F6TYWCI.mjs → http-A54ZBBAY.mjs} +10 -9
  24. package/dist/{import-VYPOMSSA.mjs → import-EA3TB3S2.mjs} +6 -5
  25. package/dist/index.mjs +10 -9
  26. package/dist/{mcp-AR7L5FA3.mjs → mcp-DJDOWHH3.mjs} +11 -9
  27. package/dist/{mcp-AR7L5FA3.mjs.map → mcp-DJDOWHH3.mjs.map} +1 -1
  28. package/dist/onnxruntime_binding-6Q6HXASN.node +0 -0
  29. package/dist/onnxruntime_binding-EKZT2NRK.node +0 -0
  30. package/dist/onnxruntime_binding-P6S7V3CI.node +0 -0
  31. package/dist/onnxruntime_binding-PJNNIIUO.node +0 -0
  32. package/dist/onnxruntime_binding-UN6SPTQK.node +0 -0
  33. package/dist/semantic-index-worker.mjs +34 -0
  34. package/dist/semantic-index-worker.mjs.map +1 -0
  35. package/dist/{service-KZTHBDQ5.mjs → service-KGL3RAIA.mjs} +2 -2
  36. package/dist/{src-6S7JPKHX.mjs → src-EKPMJRG6.mjs} +65 -109
  37. package/dist/{stream-import-KZFC3XT2.mjs → stream-import-FQFNRQ2B.mjs} +5 -4
  38. package/dist/transformers.node-PUMHAODN.mjs +42889 -0
  39. package/dist/transformers.node-PUMHAODN.mjs.map +1 -0
  40. package/dist/{update-checker-3N7HQEP2.mjs → update-checker-LGMTEKMT.mjs} +2 -2
  41. package/dist/worker-runtime-UH2OBO3T.mjs +71 -0
  42. package/dist/worker-runtime-UH2OBO3T.mjs.map +1 -0
  43. package/dist-web/assets/{DebugToolsPanel-DzZmSzIU.css → DebugToolsPanel-DFw6_G5V.css} +1 -1
  44. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-NL1sTF6i.js +110 -0
  45. package/dist-web/assets/{MessageExportModal-CF6yaor7.js → MessageExportModal-B60rvfCh.js} +1 -1
  46. package/dist-web/assets/{electron-DKVw61Ww.js → electron-BRUg9LnY.js} +1 -1
  47. package/dist-web/assets/{fetch-CUw-PUYP.js → fetch-BIpjJHxj.js} +1 -1
  48. package/dist-web/assets/{fetch-BMdgfBVS.js → fetch-BRsMdFcn.js} +1 -1
  49. package/dist-web/assets/{fetch-CPeSfqsU.js → fetch-C-m3IysH.js} +1 -1
  50. package/dist-web/assets/{fetch-CpYSD089.js → fetch-Cma-mZjd.js} +1 -1
  51. package/dist-web/assets/{fetch-D2jNBqs8.js → fetch-DE8gj5Ke.js} +1 -1
  52. package/dist-web/assets/{fetch-DqEMAea3.js → fetch-DLAp7h55.js} +1 -1
  53. package/dist-web/assets/{fetch-BvVfWR-P.js → fetch-DNSaxRnL.js} +1 -1
  54. package/dist-web/assets/{fetch-BC8jyJ_U.js → fetch-DOEbyLSD.js} +1 -1
  55. package/dist-web/assets/{fetch-BQlEQsqi.js → fetch-DSN-QYZE.js} +1 -1
  56. package/dist-web/assets/{fetch-Bh6HC-IR.js → fetch-woAa3H2L.js} +1 -1
  57. package/dist-web/assets/index-B3mDjury.js +11 -0
  58. package/dist-web/assets/{index-Qh0XLt3R.js → index-B9I-Y3fR.js} +1 -1
  59. package/dist-web/assets/{index-DqybGdJH.css → index-BX3tqfLz.css} +1 -1
  60. package/dist-web/assets/index-GAU7LI1r.js +173 -0
  61. package/dist-web/assets/index-I3SWZjnW.css +1 -0
  62. package/dist-web/assets/index-YmzTgkb2.css +1 -0
  63. package/dist-web/assets/index-iwqJDVMJ.js +2 -0
  64. package/dist-web/assets/index-o6veAyTB.js +64 -0
  65. package/dist-web/assets/{index-BHQnAj6l.js → index-r_zHR2t9.js} +1 -1
  66. package/dist-web/assets/{web-Bx2TiHDk.js → web-C2UtGskd.js} +1 -1
  67. package/dist-web/index.html +2 -2
  68. package/package.json +1 -1
  69. package/dist/chunk-GNGZHBKM.mjs.map +0 -1
  70. package/dist/chunk-QMXNJRTT.mjs.map +0 -1
  71. package/dist/chunk-RMITK4IC.mjs.map +0 -1
  72. package/dist/chunk-YPTJVTVJ.mjs.map +0 -1
  73. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BTdqmm0y.js +0 -107
  74. package/dist-web/assets/index-BwzuCu5R.js +0 -2
  75. package/dist-web/assets/index-CG746E15.js +0 -64
  76. package/dist-web/assets/index-CXoQ_U22.js +0 -173
  77. package/dist-web/assets/index-HgIuP1Td.css +0 -1
  78. package/dist-web/assets/index-R82N2_0b.css +0 -1
  79. package/dist-web/assets/index-wPAmuQZY.js +0 -11
  80. /package/dist/{chunk-742TAKEP.mjs.map → chunk-3JNVGW62.mjs.map} +0 -0
  81. /package/dist/{chunk-VSOWNKPA.mjs.map → chunk-GFHSSCCR.mjs.map} +0 -0
  82. /package/dist/{http-6F6TYWCI.mjs.map → http-A54ZBBAY.mjs.map} +0 -0
  83. /package/dist/{import-VYPOMSSA.mjs.map → import-EA3TB3S2.mjs.map} +0 -0
  84. /package/dist/{service-KZTHBDQ5.mjs.map → service-KGL3RAIA.mjs.map} +0 -0
  85. /package/dist/{src-6S7JPKHX.mjs.map → src-EKPMJRG6.mjs.map} +0 -0
  86. /package/dist/{stream-import-KZFC3XT2.mjs.map → stream-import-FQFNRQ2B.mjs.map} +0 -0
  87. /package/dist/{update-checker-3N7HQEP2.mjs.map → update-checker-LGMTEKMT.mjs.map} +0 -0
@@ -5,18 +5,17 @@ const require = __createRequire(import.meta.url);
5
5
  const __filename = __fileURLToPath(import.meta.url);
6
6
  const __dirname = __pathDirname(__filename);
7
7
  import {
8
- importData,
9
- parseFile
10
- } from "./chunk-D5MWER7N.mjs";
8
+ closeServerAiLogger,
9
+ createCliRunAgentStream,
10
+ initServerAiLogger
11
+ } from "./chunk-RJ4D4QWK.mjs";
11
12
  import {
12
- analyzeIncrementalImport,
13
- analyzeNewImport,
14
- incrementalImport,
15
- streamImport
16
- } from "./chunk-VCN64OD6.mjs";
13
+ AGENT_TOOL_REGISTRY,
14
+ CoreDataProvider
15
+ } from "./chunk-WQMKKUSD.mjs";
17
16
  import {
18
17
  assertCliDataDirCompatible
19
- } from "./chunk-742TAKEP.mjs";
18
+ } from "./chunk-3JNVGW62.mjs";
20
19
  import {
21
20
  getVersion
22
21
  } from "./chunk-5ADKYYNA.mjs";
@@ -24,14 +23,37 @@ import {
24
23
  resolveCliPath
25
24
  } from "./chunk-FJ35V3TE.mjs";
26
25
  import {
27
- closeServerAiLogger,
28
- createCliRunAgentStream,
29
- initServerAiLogger
30
- } from "./chunk-GNGZHBKM.mjs";
26
+ importData,
27
+ parseFile
28
+ } from "./chunk-YUFYTDRH.mjs";
31
29
  import {
32
- AGENT_TOOL_REGISTRY,
33
- CoreDataProvider
34
- } from "./chunk-RMITK4IC.mjs";
30
+ analyzeIncrementalImport,
31
+ analyzeNewImport,
32
+ incrementalImport,
33
+ streamImport
34
+ } from "./chunk-SAE2H2LN.mjs";
35
+ import {
36
+ NodePathProvider,
37
+ PreferencesManager,
38
+ applyPendingNodeDataDirMigrationIfNeeded,
39
+ buildMergedOutput,
40
+ checkConflictsFromSources,
41
+ createJiebaNlpProvider,
42
+ createNodeDataDirSwitch,
43
+ createSemanticIndexWorkerRuntimeClient,
44
+ detectAllFormats,
45
+ detectFormat,
46
+ findEntryFileInDirectory,
47
+ getDbFileVersion,
48
+ getDefaultNodeUserDataDir,
49
+ getOrComputeAnalysisCache,
50
+ getPendingNodeDataDirMigration,
51
+ getSupportedFormats,
52
+ hasPendingElectronDataWarning,
53
+ scanMultiChatFile,
54
+ serializeChatLabToJsonl,
55
+ streamParseFileInfo
56
+ } from "./chunk-N3G43SCQ.mjs";
35
57
  import {
36
58
  AIChatManager,
37
59
  ALL_MIGRATIONS,
@@ -46,23 +68,16 @@ import {
46
68
  LLMConfigStore,
47
69
  MergeSessionCache,
48
70
  MigrationRunner,
49
- NodePathProvider,
50
71
  POS_TAG_DEFINITIONS,
51
- PreferencesManager,
72
+ SEMANTIC_INDEX_CONFIG_FILE,
73
+ SemanticIndexConfigStore,
52
74
  SkillManagerCore,
53
75
  TempDbWriter,
54
- applyPendingNodeDataDirMigrationIfNeeded,
55
- buildMergedOutput,
56
76
  buildPiModel,
57
- checkConflictsFromSources,
58
77
  computeWordFrequency,
59
78
  countMessagesTokens,
60
79
  createDatabaseManagerAdapter,
61
- createJiebaNlpProvider,
62
- createNodeDataDirSwitch,
63
80
  deleteDict,
64
- detectAllFormats,
65
- detectFormat,
66
81
  downloadDict,
67
82
  ensureDefaultDict,
68
83
  executeReadonlySql,
@@ -70,15 +85,12 @@ import {
70
85
  exportSessionToJson,
71
86
  export_service_exports,
72
87
  fetchRemoteModels,
73
- findEntryFileInDirectory,
74
88
  getAvailableYears,
75
89
  getBuiltinModelsByProvider,
76
90
  getCatchphraseAnalysis,
77
91
  getCheckInAnalysis,
78
92
  getClusterGraph,
79
93
  getDailyActivity,
80
- getDbFileVersion,
81
- getDefaultNodeUserDataDir,
82
94
  getDefaultRulesForLocale,
83
95
  getDictList,
84
96
  getDivingAnalysis,
@@ -98,44 +110,40 @@ import {
98
110
  getMessageTypeStats,
99
111
  getMonthlyActivity,
100
112
  getNightOwlAnalysis,
101
- getOrComputeAnalysisCache,
102
- getPendingNodeDataDirMigration,
103
113
  getRelationshipStats,
104
114
  getRepeatAnalysis,
105
115
  getSchemaDetailed,
106
116
  getSessionInfo,
107
117
  getSessionMeta,
108
118
  getSessionOverview,
109
- getSupportedFormats,
110
119
  getTextLengthPercentiles,
111
120
  getTextStats,
112
121
  getTimeRange,
113
122
  getWeekdayActivity,
114
123
  getYearlyActivity,
115
- hasPendingElectronDataWarning,
116
124
  initNlpDir,
117
125
  isDictDownloaded,
118
126
  isNewerStableVersion,
127
+ isSemanticIndexConfigured,
119
128
  loadConfig,
120
129
  member_service_exports,
121
130
  mergeRulesForLocale,
122
131
  owner_profile_service_exports,
132
+ persistSemanticIndexConfig,
123
133
  queryMessages,
124
134
  resolveApiKey,
135
+ resolveSemanticIndexApiKeySet,
125
136
  runSimpleLlmStream,
126
- scanMultiChatFile,
127
137
  segmentText,
128
- serializeChatLabToJsonl,
129
138
  session_index_service_exports,
130
139
  session_service_exports,
131
- streamParseFileInfo,
132
140
  stripAvatarFields,
133
141
  summary_service_exports,
134
142
  validateApiKey,
135
143
  verifyCliDataPath,
136
144
  writeAuthProfile,
137
145
  writeConfigField
138
- } from "./chunk-YPTJVTVJ.mjs";
146
+ } from "./chunk-I7BHOZPX.mjs";
139
147
 
140
148
  // src/http/index.ts
141
149
  import * as fs11 from "fs";
@@ -1401,17 +1409,21 @@ function registerAiToolRoutes(server2, ctx) {
1401
1409
  if (!entry) {
1402
1410
  return reply.code(404).send({ success: false, error: `Tool not found: ${toolName}` });
1403
1411
  }
1404
- const db = ctx.dbManager.open(sessionId);
1405
- if (!db) {
1406
- return reply.code(404).send({ success: false, error: `Session not found: ${sessionId}` });
1407
- }
1408
1412
  const abortController = new AbortController();
1409
1413
  activeToolTests.set(testId, abortController);
1410
1414
  try {
1415
+ if (ctx.executeAiTool) {
1416
+ return await ctx.executeAiTool({ testId, toolName, params, sessionId, abortSignal: abortController.signal });
1417
+ }
1418
+ const db = ctx.dbManager.open(sessionId);
1419
+ if (!db) {
1420
+ return reply.code(404).send({ success: false, error: `Session not found: ${sessionId}` });
1421
+ }
1411
1422
  const execCtx = {
1412
1423
  sessionId,
1413
1424
  db,
1414
- dataProvider: new CoreDataProvider(db)
1425
+ dataProvider: new CoreDataProvider(db),
1426
+ abortSignal: abortController.signal
1415
1427
  };
1416
1428
  const startTime = Date.now();
1417
1429
  const result = await entry.handler(params, execCtx);
@@ -1612,10 +1624,93 @@ function registerAiSummaryRoutes(server2, ctx) {
1612
1624
  });
1613
1625
  }
1614
1626
 
1627
+ // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1628
+ import path4 from "path";
1629
+ function registerSemanticIndexRoutes(server2, ctx) {
1630
+ const service = ctx.semanticIndexService;
1631
+ if (!service) {
1632
+ if (ctx.aiDataDir) {
1633
+ const configStore = new SemanticIndexConfigStore(path4.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1634
+ const configResponse = (config) => ({
1635
+ config,
1636
+ apiKeySet: resolveSemanticIndexApiKeySet(config, ctx.resolveApiKey),
1637
+ configured: isSemanticIndexConfigured(config)
1638
+ });
1639
+ server2.get("/_web/ai/semantic-index/config", async () => configResponse(configStore.get()));
1640
+ server2.put(
1641
+ "/_web/ai/semantic-index/config",
1642
+ async (request) => {
1643
+ const config = persistSemanticIndexConfig(configStore, request.body.config, {
1644
+ apiKey: request.body.apiKey,
1645
+ writeAuthProfile: ctx.writeAuthProfile
1646
+ });
1647
+ return configResponse(config);
1648
+ }
1649
+ );
1650
+ }
1651
+ server2.get("/_web/ai/semantic-index/enabled", async () => ({ sessions: [] }));
1652
+ server2.get("/_web/ai/semantic-index/status", async () => ({ status: null }));
1653
+ server2.post("/_web/ai/semantic-index/status", async () => ({ sessions: [] }));
1654
+ return;
1655
+ }
1656
+ server2.get("/_web/ai/semantic-index/config", async () => {
1657
+ return {
1658
+ config: await service.getConfig(),
1659
+ apiKeySet: await service.hasApiKey(),
1660
+ configured: await service.isConfigured()
1661
+ };
1662
+ });
1663
+ server2.put(
1664
+ "/_web/ai/semantic-index/config",
1665
+ async (request) => {
1666
+ const config = await service.setConfig(request.body.config, { apiKey: request.body.apiKey });
1667
+ return { config, apiKeySet: await service.hasApiKey(), configured: await service.isConfigured() };
1668
+ }
1669
+ );
1670
+ server2.get("/_web/ai/semantic-index/enabled", async () => {
1671
+ return { sessions: await service.listEnabledStatuses() };
1672
+ });
1673
+ server2.get("/_web/ai/semantic-index/status", async (request) => {
1674
+ return { status: await service.status(request.query.sessionId) };
1675
+ });
1676
+ server2.post("/_web/ai/semantic-index/status", async (request) => {
1677
+ return { sessions: await service.statusForSessions(request.body.sessionIds ?? []) };
1678
+ });
1679
+ const sessionAction = (path13, action) => {
1680
+ server2.post(path13, async (request, reply) => {
1681
+ const { sessionId } = request.body;
1682
+ if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
1683
+ await action(sessionId);
1684
+ return { status: await service.status(sessionId) };
1685
+ });
1686
+ };
1687
+ sessionAction("/_web/ai/semantic-index/enable", (id) => service.enable(id));
1688
+ sessionAction("/_web/ai/semantic-index/disable", (id) => service.disable(id));
1689
+ sessionAction("/_web/ai/semantic-index/build", (id) => service.build(id));
1690
+ sessionAction("/_web/ai/semantic-index/pause", (id) => service.pause(id));
1691
+ sessionAction("/_web/ai/semantic-index/cancel", (id) => service.cancel(id));
1692
+ sessionAction("/_web/ai/semantic-index/rebuild", (id) => service.rebuild(id));
1693
+ server2.post("/_web/ai/semantic-index/build-pending", async () => {
1694
+ await service.buildAllPending();
1695
+ return { sessions: await service.listEnabledStatuses() };
1696
+ });
1697
+ server2.post("/_web/ai/semantic-index/cleanup", async () => {
1698
+ return await service.cleanupUnused();
1699
+ });
1700
+ server2.post(
1701
+ "/_web/ai/semantic-index/search",
1702
+ async (request, reply) => {
1703
+ const { sessionId, query, finalTopK } = request.body;
1704
+ if (!sessionId || !query) return reply.code(400).send({ error: "sessionId and query are required" });
1705
+ return service.search(sessionId, query, { finalTopK });
1706
+ }
1707
+ );
1708
+ }
1709
+
1615
1710
  // ../../packages/http-routes/src/routes/web/merge.ts
1616
1711
  import * as fs3 from "fs";
1617
1712
  import * as os from "os";
1618
- import * as path4 from "path";
1713
+ import * as path5 from "path";
1619
1714
  function ensureDb2(ctx, sessionId) {
1620
1715
  const db = ctx.dbManager.open(sessionId);
1621
1716
  if (!db) throw sessionNotFound(sessionId);
@@ -1632,8 +1727,8 @@ function registerMergeRoutes(server2, ctx) {
1632
1727
  if (contentType.includes("multipart/form-data")) {
1633
1728
  const data = await request.file();
1634
1729
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1635
- const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merge-"));
1636
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
1730
+ const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merge-"));
1731
+ const tmpPath = path5.join(tmpDir, data.filename || "upload");
1637
1732
  const chunks = [];
1638
1733
  for await (const chunk of data.file) {
1639
1734
  chunks.push(chunk);
@@ -1651,12 +1746,12 @@ function registerMergeRoutes(server2, ctx) {
1651
1746
  try {
1652
1747
  const result = await streamParseFileInfo(filePath, {
1653
1748
  createTempDatabase(sourceFilePath) {
1654
- return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1749
+ return mergeCache.createTempDatabase(path5.basename(sourceFilePath));
1655
1750
  },
1656
1751
  onProgress() {
1657
1752
  }
1658
1753
  });
1659
- const filename = path4.basename(filePath);
1754
+ const filename = path5.basename(filePath);
1660
1755
  const handle = mergeCache.store(filename, result.tempDbPath);
1661
1756
  return {
1662
1757
  handle,
@@ -1725,9 +1820,9 @@ function registerMergeRoutes(server2, ctx) {
1725
1820
  return reply.code(501).send({ error: "Import not available on this server" });
1726
1821
  }
1727
1822
  const jsonData = JSON.stringify(merged.chatLabData);
1728
- const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merged-"));
1729
- const safeName = path4.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1730
- const tmpPath = path4.join(tmpDir, `${safeName}.json`);
1823
+ const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merged-"));
1824
+ const safeName = path5.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1825
+ const tmpPath = path5.join(tmpDir, `${safeName}.json`);
1731
1826
  fs3.writeFileSync(tmpPath, jsonData, "utf-8");
1732
1827
  try {
1733
1828
  const importResult = await streamImport2(dbManager2, tmpPath);
@@ -1814,14 +1909,14 @@ function registerMergeRoutes(server2, ctx) {
1814
1909
  // ../../packages/http-routes/src/routes/web/cache.ts
1815
1910
  import * as fs4 from "fs";
1816
1911
  import * as fsp from "fs/promises";
1817
- import * as path5 from "path";
1912
+ import * as path6 from "path";
1818
1913
  async function getDirSize(dirPath) {
1819
1914
  let totalSize = 0;
1820
1915
  try {
1821
1916
  if (!fs4.existsSync(dirPath)) return 0;
1822
1917
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1823
1918
  for (const file of files) {
1824
- const filePath = path5.join(dirPath, file.name);
1919
+ const filePath = path6.join(dirPath, file.name);
1825
1920
  if (file.isDirectory()) {
1826
1921
  totalSize += await getDirSize(filePath);
1827
1922
  } else {
@@ -1839,7 +1934,7 @@ async function getFileCount(dirPath) {
1839
1934
  if (!fs4.existsSync(dirPath)) return 0;
1840
1935
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1841
1936
  for (const file of files) {
1842
- const filePath = path5.join(dirPath, file.name);
1937
+ const filePath = path6.join(dirPath, file.name);
1843
1938
  if (file.isDirectory()) {
1844
1939
  count += await getFileCount(filePath);
1845
1940
  } else {
@@ -1852,8 +1947,8 @@ async function getFileCount(dirPath) {
1852
1947
  }
1853
1948
  function hasChatLabDatabases(dirPath) {
1854
1949
  try {
1855
- const markerPath = path5.join(dirPath, ".chatlab");
1856
- const dbDir = path5.join(dirPath, "databases");
1950
+ const markerPath = path6.join(dirPath, ".chatlab");
1951
+ const dbDir = path6.join(dirPath, "databases");
1857
1952
  if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
1858
1953
  return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1859
1954
  } catch {
@@ -1921,7 +2016,7 @@ function registerCacheRoutes(server2, ctx) {
1921
2016
  if (!fs4.existsSync(dirPath)) return { success: true };
1922
2017
  const files = await fsp.readdir(dirPath);
1923
2018
  for (const file of files) {
1924
- const filePath = path5.join(dirPath, file);
2019
+ const filePath = path6.join(dirPath, file);
1925
2020
  const stat2 = await fsp.stat(filePath);
1926
2021
  if (stat2.isDirectory()) {
1927
2022
  await fsp.rm(filePath, { recursive: true });
@@ -1938,7 +2033,7 @@ function registerCacheRoutes(server2, ctx) {
1938
2033
  defaultPath: ctx.defaultUserDataDir,
1939
2034
  isCustom: ctx.isCustomDataDir ?? false,
1940
2035
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
1941
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path5.resolve(pp.getUserDataDir()) !== path5.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2036
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path6.resolve(pp.getUserDataDir()) !== path6.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1942
2037
  pendingMigration: pending ? {
1943
2038
  from: pending.from,
1944
2039
  to: pending.to,
@@ -1955,7 +2050,7 @@ function registerCacheRoutes(server2, ctx) {
1955
2050
  return ctx.setDataDir(targetPath, migrate);
1956
2051
  });
1957
2052
  server2.get("/_web/cache/latest-import-log", async () => {
1958
- const importLogDir = path5.join(pp.getLogsDir(), "import");
2053
+ const importLogDir = path6.join(pp.getLogsDir(), "import");
1959
2054
  if (!fs4.existsSync(importLogDir)) {
1960
2055
  return { success: false, error: "Log directory not found" };
1961
2056
  }
@@ -1966,7 +2061,7 @@ function registerCacheRoutes(server2, ctx) {
1966
2061
  }
1967
2062
  const fileStats = await Promise.all(
1968
2063
  logFiles.map(async (f) => {
1969
- const filePath = path5.join(importLogDir, f);
2064
+ const filePath = path6.join(importLogDir, f);
1970
2065
  const stat2 = await fsp.stat(filePath);
1971
2066
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
1972
2067
  })
@@ -1993,7 +2088,7 @@ function registerCacheRoutes(server2, ctx) {
1993
2088
  if (!fs4.existsSync(downloadsDir)) {
1994
2089
  fs4.mkdirSync(downloadsDir, { recursive: true });
1995
2090
  }
1996
- const filePath = path5.join(downloadsDir, filename);
2091
+ const filePath = path6.join(downloadsDir, filename);
1997
2092
  fs4.writeFileSync(filePath, buffer);
1998
2093
  return { success: true, filePath };
1999
2094
  });
@@ -2068,6 +2163,7 @@ function registerSharedRoutes(server2, ctx, options) {
2068
2163
  registerAiToolRoutes(server2, resolvedCtx);
2069
2164
  registerAiChatRoutes(server2, resolvedCtx);
2070
2165
  registerAiSummaryRoutes(server2, resolvedCtx);
2166
+ registerSemanticIndexRoutes(server2, resolvedCtx);
2071
2167
  registerMergeRoutes(server2, resolvedCtx);
2072
2168
  registerCacheRoutes(server2, resolvedCtx);
2073
2169
  }
@@ -2147,12 +2243,12 @@ function createServer() {
2147
2243
 
2148
2244
  // src/http/routes/web/index.ts
2149
2245
  import * as os3 from "os";
2150
- import * as path8 from "path";
2246
+ import * as path9 from "path";
2151
2247
 
2152
2248
  // src/http/routes/web/import.ts
2153
2249
  import * as fs6 from "fs";
2154
2250
  import * as os2 from "os";
2155
- import * as path6 from "path";
2251
+ import * as path7 from "path";
2156
2252
 
2157
2253
  // src/http/routes/web/helpers.ts
2158
2254
  import * as fs5 from "fs";
@@ -2185,8 +2281,8 @@ function registerImportRoutes(server2, dbManager2) {
2185
2281
  server2.post("/_web/detect-format", async (request, reply) => {
2186
2282
  const data = await request.file();
2187
2283
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2188
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-detect-"));
2189
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2284
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-detect-"));
2285
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2190
2286
  try {
2191
2287
  const chunks = [];
2192
2288
  for await (const chunk of data.file) {
@@ -2203,8 +2299,8 @@ function registerImportRoutes(server2, dbManager2) {
2203
2299
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2204
2300
  const data = await request.file();
2205
2301
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2206
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-scan-"));
2207
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2302
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-scan-"));
2303
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2208
2304
  try {
2209
2305
  const chunks = [];
2210
2306
  for await (const chunk of data.file) {
@@ -2220,8 +2316,8 @@ function registerImportRoutes(server2, dbManager2) {
2220
2316
  server2.post("/_web/import", async (request, reply) => {
2221
2317
  const data = await request.file();
2222
2318
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2223
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-import-"));
2224
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2319
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-"));
2320
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2225
2321
  const chunks = [];
2226
2322
  for await (const chunk of data.file) {
2227
2323
  chunks.push(chunk);
@@ -2269,7 +2365,7 @@ data: ${JSON.stringify(eventData)}
2269
2365
  server2.post("/_web/import-directory", async (request, reply) => {
2270
2366
  const parts = request.parts();
2271
2367
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2272
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-dir-import-"));
2368
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-dir-import-"));
2273
2369
  const relativePaths = [];
2274
2370
  const fileBuffers = [];
2275
2371
  try {
@@ -2297,9 +2393,9 @@ data: ${JSON.stringify(eventData)}
2297
2393
  if (segments.length > 1) {
2298
2394
  relPath = segments.slice(1).join("/");
2299
2395
  }
2300
- const targetPath = path6.resolve(tmpDir, relPath);
2301
- if (!targetPath.startsWith(tmpDir + path6.sep)) continue;
2302
- fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
2396
+ const targetPath = path7.resolve(tmpDir, relPath);
2397
+ if (!targetPath.startsWith(tmpDir + path7.sep)) continue;
2398
+ fs6.mkdirSync(path7.dirname(targetPath), { recursive: true });
2303
2399
  fs6.writeFileSync(targetPath, fileBuffers[i].data);
2304
2400
  }
2305
2401
  const entryPath = findEntryFileInDirectory(tmpDir);
@@ -2345,8 +2441,8 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2345
2441
  const sessionId = request.params.id;
2346
2442
  const data = await request.file();
2347
2443
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2348
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-inc-"));
2349
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2444
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-inc-"));
2445
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2350
2446
  const chunks = [];
2351
2447
  for await (const chunk of data.file) {
2352
2448
  chunks.push(chunk);
@@ -2383,8 +2479,8 @@ data: ${JSON.stringify(eventData)}
2383
2479
  const sessionId = request.params.id;
2384
2480
  const data = await request.file();
2385
2481
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2386
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2387
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2482
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2483
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2388
2484
  const chunks = [];
2389
2485
  for await (const chunk of data.file) {
2390
2486
  chunks.push(chunk);
@@ -2401,8 +2497,8 @@ data: ${JSON.stringify(eventData)}
2401
2497
  server2.post("/_web/import/analyze", async (request, reply) => {
2402
2498
  const data = await request.file();
2403
2499
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2404
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2405
- const tmpPath = path6.join(tmpDir, data.filename || "upload");
2500
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2501
+ const tmpPath = path7.join(tmpDir, data.filename || "upload");
2406
2502
  const chunks = [];
2407
2503
  for await (const chunk of data.file) {
2408
2504
  chunks.push(chunk);
@@ -2437,12 +2533,12 @@ data: ${JSON.stringify(eventData)}
2437
2533
 
2438
2534
  `);
2439
2535
  }
2440
- const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-demo-"));
2536
+ const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-demo-"));
2441
2537
  try {
2442
2538
  const localPaths = [];
2443
2539
  for (let i = 0; i < total; i++) {
2444
2540
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2445
- const localPath = path6.join(tmpDir, DEMO_FILES[i]);
2541
+ const localPath = path7.join(tmpDir, DEMO_FILES[i]);
2446
2542
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2447
2543
  signal: AbortSignal.timeout(6e4)
2448
2544
  });
@@ -2478,7 +2574,7 @@ data: ${JSON.stringify(eventData)}
2478
2574
  }
2479
2575
 
2480
2576
  // src/http/routes/web/cache.ts
2481
- import * as path7 from "path";
2577
+ import * as path8 from "path";
2482
2578
  import { execFile } from "child_process";
2483
2579
  function runExecFile(file, args, runner = execFile) {
2484
2580
  return new Promise((resolve4, reject) => {
@@ -2506,7 +2602,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2506
2602
  } else if (platform === "win32") {
2507
2603
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2508
2604
  } else {
2509
- await runExecFile("xdg-open", [path7.dirname(filePath)], runner);
2605
+ await runExecFile("xdg-open", [path8.dirname(filePath)], runner);
2510
2606
  }
2511
2607
  }
2512
2608
 
@@ -2529,26 +2625,28 @@ function registerWebRoutes(server2, dbManager2, options) {
2529
2625
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2530
2626
  mergeCache?.cleanupOrphans();
2531
2627
  const fallbackPathProvider = {
2532
- getSystemDir: () => path8.join(os3.homedir(), ".chatlab"),
2533
- getUserDataDir: () => path8.join(os3.homedir(), ".chatlab", "data"),
2534
- getDatabaseDir: () => path8.join(os3.homedir(), ".chatlab", "data", "databases"),
2535
- getAiDataDir: () => path8.join(os3.homedir(), ".chatlab", "ai"),
2536
- getSettingsDir: () => path8.join(os3.homedir(), ".chatlab", "settings"),
2537
- getCacheDir: () => path8.join(os3.homedir(), ".chatlab", "cache"),
2538
- getTempDir: () => path8.join(os3.homedir(), ".chatlab", "temp"),
2539
- getLogsDir: () => path8.join(os3.homedir(), ".chatlab", "logs"),
2540
- getDownloadsDir: () => path8.join(os3.homedir(), "Downloads")
2628
+ getSystemDir: () => path9.join(os3.homedir(), ".chatlab"),
2629
+ getUserDataDir: () => path9.join(os3.homedir(), ".chatlab", "data"),
2630
+ getDatabaseDir: () => path9.join(os3.homedir(), ".chatlab", "data", "databases"),
2631
+ getVectorDir: () => path9.join(os3.homedir(), ".chatlab", "data", "vector"),
2632
+ getAiDataDir: () => path9.join(os3.homedir(), ".chatlab", "ai"),
2633
+ getSettingsDir: () => path9.join(os3.homedir(), ".chatlab", "settings"),
2634
+ getCacheDir: () => path9.join(os3.homedir(), ".chatlab", "cache"),
2635
+ getTempDir: () => path9.join(os3.homedir(), ".chatlab", "temp"),
2636
+ getLogsDir: () => path9.join(os3.homedir(), ".chatlab", "logs"),
2637
+ getDownloadsDir: () => path9.join(os3.homedir(), "Downloads")
2541
2638
  };
2542
2639
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2543
2640
  const ai = options?.aiContext;
2544
2641
  const cliStreamImport = async (dm, filePath) => {
2545
- const { streamImport: streamImport2 } = await import("./stream-import-KZFC3XT2.mjs");
2642
+ const { streamImport: streamImport2 } = await import("./stream-import-FQFNRQ2B.mjs");
2546
2643
  const result = await streamImport2(dm, filePath);
2547
2644
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2548
2645
  return { sessionId: result.sessionId };
2549
2646
  };
2550
2647
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2551
- const isCustom = path8.resolve(resolvedPathProvider.getUserDataDir()) !== path8.resolve(defaultUserDataDir);
2648
+ const isCustom = path9.resolve(resolvedPathProvider.getUserDataDir()) !== path9.resolve(defaultUserDataDir);
2649
+ const semanticIndexService = options?.semanticIndexService;
2552
2650
  registerSharedRoutes(
2553
2651
  server2,
2554
2652
  {
@@ -2557,6 +2655,7 @@ function registerWebRoutes(server2, dbManager2, options) {
2557
2655
  pathProvider: resolvedPathProvider,
2558
2656
  getVersion,
2559
2657
  nativeBinding: options?.nativeBinding,
2658
+ semanticIndexService,
2560
2659
  openDirectory: openDirectoryPath,
2561
2660
  showInFolder: showPathInFolder,
2562
2661
  downloadsDir: resolvedPathProvider.getDownloadsDir(),
@@ -2640,7 +2739,7 @@ function registerProxyRoutes(server2) {
2640
2739
 
2641
2740
  // src/ai/manager-factory.ts
2642
2741
  import * as fs7 from "fs";
2643
- import * as path9 from "path";
2742
+ import * as path10 from "path";
2644
2743
  import { createHash, randomUUID } from "crypto";
2645
2744
  var nodeFs = {
2646
2745
  ensureDir(dir) {
@@ -2663,7 +2762,7 @@ var nodeFs = {
2663
2762
  return fs7.existsSync(filePath);
2664
2763
  },
2665
2764
  joinPath(...parts) {
2666
- return path9.join(...parts);
2765
+ return path10.join(...parts);
2667
2766
  }
2668
2767
  };
2669
2768
  var managerCache = /* @__PURE__ */ new Map();
@@ -2673,13 +2772,13 @@ function getAssistantManager(aiDataDir) {
2673
2772
  cached = {
2674
2773
  assistant: new AssistantManager({
2675
2774
  fs: nodeFs,
2676
- assistantsDir: path9.join(aiDataDir, "assistants"),
2775
+ assistantsDir: path10.join(aiDataDir, "assistants"),
2677
2776
  builtinRawConfigs: [],
2678
2777
  generateId: () => `custom_${randomUUID().replace(/-/g, "").slice(0, 12)}`
2679
2778
  }),
2680
2779
  skill: new SkillManagerCore({
2681
2780
  fs: nodeFs,
2682
- skillsDir: path9.join(aiDataDir, "skills"),
2781
+ skillsDir: path10.join(aiDataDir, "skills"),
2683
2782
  builtinRawSkills: [],
2684
2783
  contentHash: (content) => createHash("md5").update(content).digest("hex")
2685
2784
  })
@@ -2704,7 +2803,7 @@ var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2704
2803
 
2705
2804
  // ../../packages/sync/src/data-source-manager.ts
2706
2805
  import * as fs8 from "fs";
2707
- import * as path10 from "path";
2806
+ import * as path11 from "path";
2708
2807
  import * as crypto from "crypto";
2709
2808
  var CONFIG_FILE = "data-sources.json";
2710
2809
  function generateId(prefix = "ds") {
@@ -2732,7 +2831,7 @@ var DataSourceManager = class {
2732
2831
  this.logger = logger ?? NOOP_LOGGER;
2733
2832
  }
2734
2833
  getConfigPath() {
2735
- return path10.join(this.configDir, CONFIG_FILE);
2834
+ return path11.join(this.configDir, CONFIG_FILE);
2736
2835
  }
2737
2836
  ensureDir() {
2738
2837
  fs8.mkdirSync(this.configDir, { recursive: true });
@@ -2959,6 +3058,41 @@ function fileContainsMessages(filePath) {
2959
3058
  return false;
2960
3059
  }
2961
3060
  }
3061
+ function getMaxMessageTimestampFromFile(filePath) {
3062
+ try {
3063
+ let maxTs = null;
3064
+ const visitTimestamp = (value) => {
3065
+ const ts = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
3066
+ if (typeof ts === "number" && Number.isFinite(ts)) {
3067
+ maxTs = maxTs === null ? ts : Math.max(maxTs, ts);
3068
+ }
3069
+ };
3070
+ if (filePath.endsWith(".jsonl")) {
3071
+ const content = fs9.readFileSync(filePath, "utf-8");
3072
+ for (const line of content.split("\n")) {
3073
+ const trimmed = line.trim();
3074
+ if (!trimmed) continue;
3075
+ try {
3076
+ const obj = JSON.parse(trimmed);
3077
+ if (obj._type === "message") visitTimestamp(obj.timestamp);
3078
+ } catch {
3079
+ continue;
3080
+ }
3081
+ }
3082
+ return maxTs;
3083
+ }
3084
+ const raw = fs9.readFileSync(filePath, "utf-8");
3085
+ const parsed = JSON.parse(raw);
3086
+ if (Array.isArray(parsed.messages)) {
3087
+ for (const message of parsed.messages) {
3088
+ visitTimestamp(message?.timestamp);
3089
+ }
3090
+ }
3091
+ return maxTs;
3092
+ } catch {
3093
+ return null;
3094
+ }
3095
+ }
2962
3096
  function cleanupTempFile(filePath) {
2963
3097
  try {
2964
3098
  if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
@@ -3012,6 +3146,8 @@ var PullEngine = class {
3012
3146
  this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
3013
3147
  let totalNewMessages = 0;
3014
3148
  let since = sess.lastPullAt;
3149
+ let offset;
3150
+ let nextPullSince = sess.lastPullAt;
3015
3151
  let pageCount = 0;
3016
3152
  let resyncAttempted = false;
3017
3153
  this.progressMap.set(sess.id, { sessionId: sess.id, sessionName: sess.name, current: 0, pages: 0, done: false });
@@ -3020,6 +3156,7 @@ var PullEngine = class {
3020
3156
  pageCount++;
3021
3157
  const tempFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
3022
3158
  since,
3159
+ offset,
3023
3160
  limit: ds.pullLimit
3024
3161
  });
3025
3162
  try {
@@ -3029,6 +3166,7 @@ var PullEngine = class {
3029
3166
  if (stat2.size < 1024) {
3030
3167
  if (!fileContainsMessages(tempFile) && sync0?.hasMore === false) {
3031
3168
  cleanupTempFile(tempFile);
3169
+ if (sync0?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, sync0.nextSince);
3032
3170
  const retryDelays = [2e3, 3e3, 5e3];
3033
3171
  let retrySuccess = false;
3034
3172
  let retryHasMore = false;
@@ -3039,21 +3177,26 @@ var PullEngine = class {
3039
3177
  await new Promise((r) => setTimeout(r, retryDelays[ri]));
3040
3178
  const retryFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
3041
3179
  since,
3180
+ offset,
3042
3181
  limit: ds.pullLimit
3043
3182
  });
3044
3183
  const retryStat = fs9.statSync(retryFile);
3045
3184
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
3046
3185
  const retrySync = parseSyncFromFile(retryFile);
3047
3186
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
3187
+ if (retrySync.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, retrySync.nextSince);
3048
3188
  cleanupTempFile(retryFile);
3049
3189
  continue;
3050
3190
  }
3191
+ const retryMaxTs = getMaxMessageTimestampFromFile(retryFile);
3051
3192
  const retryResult = await this.importTempFile(ds.baseUrl, sess, retryFile);
3052
3193
  cleanupTempFile(retryFile);
3053
3194
  if (retryResult.needFullResync && !resyncAttempted) {
3054
3195
  resyncAttempted = true;
3055
3196
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
3056
3197
  since = 0;
3198
+ offset = void 0;
3199
+ nextPullSince = 0;
3057
3200
  pageCount = 0;
3058
3201
  sess.targetSessionId = "";
3059
3202
  sess.lastPullAt = 0;
@@ -3090,6 +3233,8 @@ var PullEngine = class {
3090
3233
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: retryResult.sessionId });
3091
3234
  }
3092
3235
  totalNewMessages += retryResult.newMessageCount;
3236
+ if (retryMaxTs !== null) nextPullSince = Math.max(nextPullSince, retryMaxTs);
3237
+ if (retrySync?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, retrySync.nextSince);
3093
3238
  this.progressMap.set(sess.id, {
3094
3239
  sessionId: sess.id,
3095
3240
  sessionName: sess.name,
@@ -3099,6 +3244,9 @@ var PullEngine = class {
3099
3244
  });
3100
3245
  if (retrySync?.hasMore && retrySync.nextSince !== void 0) {
3101
3246
  since = retrySync.nextSince;
3247
+ offset = void 0;
3248
+ } else if (retrySync?.hasMore && retrySync.nextOffset !== void 0) {
3249
+ offset = retrySync.nextOffset;
3102
3250
  }
3103
3251
  retryHasMore = !!retrySync?.hasMore;
3104
3252
  retrySuccess = true;
@@ -3114,12 +3262,15 @@ var PullEngine = class {
3114
3262
  break;
3115
3263
  }
3116
3264
  const sync = sync0;
3265
+ const maxMessageTs = getMaxMessageTimestampFromFile(tempFile);
3117
3266
  const result = await this.importTempFile(ds.baseUrl, sess, tempFile);
3118
3267
  cleanupTempFile(tempFile);
3119
3268
  if (result.needFullResync && !resyncAttempted) {
3120
3269
  resyncAttempted = true;
3121
3270
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
3122
3271
  since = 0;
3272
+ offset = void 0;
3273
+ nextPullSince = 0;
3123
3274
  pageCount = 0;
3124
3275
  sess.targetSessionId = "";
3125
3276
  sess.lastPullAt = 0;
@@ -3154,6 +3305,7 @@ var PullEngine = class {
3154
3305
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: result.sessionId });
3155
3306
  }
3156
3307
  totalNewMessages += result.newMessageCount;
3308
+ if (maxMessageTs !== null) nextPullSince = Math.max(nextPullSince, maxMessageTs);
3157
3309
  this.progressMap.set(sess.id, {
3158
3310
  sessionId: sess.id,
3159
3311
  sessionName: sess.name,
@@ -3161,8 +3313,17 @@ var PullEngine = class {
3161
3313
  pages: pageCount,
3162
3314
  done: false
3163
3315
  });
3316
+ if (sync?.nextSince !== void 0) nextPullSince = Math.max(nextPullSince, sync.nextSince);
3164
3317
  if (!sync || !sync.hasMore) break;
3165
- if (sync.nextSince !== void 0) since = sync.nextSince;
3318
+ if (sync.nextSince !== void 0) {
3319
+ since = sync.nextSince;
3320
+ offset = void 0;
3321
+ } else if (sync.nextOffset !== void 0) {
3322
+ offset = sync.nextOffset;
3323
+ } else {
3324
+ this.logger.warn(`[Pull] "${sess.name}" returned hasMore=true without nextSince or nextOffset, stopping`);
3325
+ break;
3326
+ }
3166
3327
  } catch (importErr) {
3167
3328
  cleanupTempFile(tempFile);
3168
3329
  throw importErr;
@@ -3171,8 +3332,9 @@ var PullEngine = class {
3171
3332
  if (pageCount >= MAX_PAGES_PER_PULL) {
3172
3333
  this.logger.warn(`[Pull] "${sess.name}" reached page limit (${MAX_PAGES_PER_PULL}), data may be incomplete`);
3173
3334
  }
3335
+ const savedLastPullAt = Math.max(sess.lastPullAt, Math.max(0, nextPullSince - PULL_OVERLAP_SECONDS));
3174
3336
  this.dsManager.updateSession(sourceId, sess.id, {
3175
- lastPullAt: Math.floor(Date.now() / 1e3) - PULL_OVERLAP_SECONDS,
3337
+ lastPullAt: savedLastPullAt,
3176
3338
  lastStatus: "success",
3177
3339
  lastNewMessages: totalNewMessages,
3178
3340
  lastError: ""
@@ -3324,11 +3486,11 @@ function reloadTimer(dsId, skipInitialPull = false) {
3324
3486
  // src/sync/adapters.ts
3325
3487
  import * as fs10 from "fs";
3326
3488
  import * as os4 from "os";
3327
- import * as path11 from "path";
3489
+ import * as path12 from "path";
3328
3490
  import * as crypto3 from "crypto";
3329
3491
  function getTempFilePath(ext) {
3330
3492
  const id = crypto3.randomBytes(8).toString("hex");
3331
- return path11.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3493
+ return path12.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3332
3494
  }
3333
3495
  var NodeFetcher = class {
3334
3496
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3666,9 +3828,23 @@ async function startHttpServer(options) {
3666
3828
  limits: { fileSize: 1024 * 1024 * 1024 }
3667
3829
  // 1GB
3668
3830
  });
3831
+ let semanticIndexService;
3832
+ try {
3833
+ semanticIndexService = createSemanticIndexWorkerRuntimeClient({
3834
+ pathProvider,
3835
+ runtime,
3836
+ nativeBinding,
3837
+ workerEntryUrl: import.meta.url.endsWith(".ts") ? void 0 : new URL("./semantic-index-worker.mjs", import.meta.url)
3838
+ });
3839
+ server.addHook("onClose", async () => semanticIndexService?.close());
3840
+ } catch (err) {
3841
+ console.warn("[semantic-index] worker client unavailable:", err instanceof Error ? err.message : String(err));
3842
+ semanticIndexService = void 0;
3843
+ }
3669
3844
  registerWebRoutes(server, dbManager, {
3670
3845
  pathProvider,
3671
3846
  nativeBinding,
3847
+ semanticIndexService,
3672
3848
  aiContext: {
3673
3849
  aiDataDir,
3674
3850
  aiChatManager,
@@ -3677,7 +3853,7 @@ async function startHttpServer(options) {
3677
3853
  llmConfigStore,
3678
3854
  customProviderStore: new CustomProviderStore(createFileConfigStorage(aiDataDir)),
3679
3855
  customModelStore: new CustomModelStore(createFileConfigStorage(aiDataDir)),
3680
- runAgentStream: createCliRunAgentStream(dbManager, aiChatManager)
3856
+ runAgentStream: createCliRunAgentStream(dbManager, aiChatManager, semanticIndexService)
3681
3857
  }
3682
3858
  });
3683
3859
  initSync(server, dbManager, pathProvider, { port, host, token });
@@ -3722,4 +3898,4 @@ export {
3722
3898
  startHttpServer,
3723
3899
  stopHttpServer
3724
3900
  };
3725
- //# sourceMappingURL=chunk-QMXNJRTT.mjs.map
3901
+ //# sourceMappingURL=chunk-SIVERPF5.mjs.map