chatlab-cli 0.29.0 → 0.30.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/{chat-command-JKG3FY5B.mjs → chat-command-VBDMWOKL.mjs} +8 -8
  2. package/dist/{chunk-6FPEP2ZX.mjs → chunk-326OCJOT.mjs} +2 -2
  3. package/dist/{chunk-QQK7MLCR.mjs → chunk-36JGKW6K.mjs} +710 -282
  4. package/dist/chunk-36JGKW6K.mjs.map +1 -0
  5. package/dist/{chunk-SKUG5XVD.mjs → chunk-5EE2EFPE.mjs} +5 -4
  6. package/dist/{chunk-KL2NGIMQ.mjs → chunk-D3DPCDII.mjs} +444 -274
  7. package/dist/chunk-D3DPCDII.mjs.map +1 -0
  8. package/dist/{chunk-I35WHWA6.mjs → chunk-KBSAVUFV.mjs} +7 -4
  9. package/dist/chunk-KBSAVUFV.mjs.map +1 -0
  10. package/dist/{chunk-WPI6YSAR.mjs → chunk-LVEJHB3X.mjs} +4570 -4412
  11. package/dist/chunk-LVEJHB3X.mjs.map +1 -0
  12. package/dist/chunk-M2PXDX4G.mjs +2325 -0
  13. package/dist/chunk-M2PXDX4G.mjs.map +1 -0
  14. package/dist/{chunk-CIB23U32.mjs → chunk-TCZHG3G7.mjs} +101 -41
  15. package/dist/chunk-TCZHG3G7.mjs.map +1 -0
  16. package/dist/{chunk-D4WTE7LV.mjs → chunk-TEX44HNL.mjs} +16 -12
  17. package/dist/chunk-TEX44HNL.mjs.map +1 -0
  18. package/dist/{chunk-7RW4CQAO.mjs → chunk-ULNRUS55.mjs} +5 -3
  19. package/dist/chunk-ULNRUS55.mjs.map +1 -0
  20. package/dist/{chunk-2MZ6G4AC.mjs → chunk-WV5GSRQM.mjs} +2 -2
  21. package/dist/cli.mjs +7 -7
  22. package/dist/contacts-worker.mjs +2 -2
  23. package/dist/{http-5E7ARYFW.mjs → http-L4AMELPZ.mjs} +11 -11
  24. package/dist/{import-3KG6VK6O.mjs → import-WWCSXGVS.mjs} +7 -7
  25. package/dist/index.mjs +11 -11
  26. package/dist/{mcp-JBI4CUQT.mjs → mcp-A4M3LT5O.mjs} +8 -8
  27. package/dist/people-relationships-worker.mjs +2 -2
  28. package/dist/semantic-index-worker.mjs +1 -1
  29. package/dist/{src-UTBQAOJ3.mjs → src-MPNBTFOE.mjs} +14 -6
  30. package/dist/{stream-import-7HZ4IRYJ.mjs → stream-import-HIE7PNL4.mjs} +7 -7
  31. package/dist/{worker-runtime-ALS4MR2A.mjs → worker-runtime-BJXO24XX.mjs} +5 -5
  32. package/dist-web/assets/{BufferResource-MsqP5sq3.js → BufferResource-DPkMC1wo.js} +1 -1
  33. package/dist-web/assets/CanvasRenderer-DokMtSX9.js +1 -0
  34. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-Ceg-0WOD.js +110 -0
  35. package/dist-web/assets/{Filter-wRJtdXx9.js → Filter-Bo_p3BVX.js} +1 -1
  36. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
  37. package/dist-web/assets/{MessageExportModal-BCrMf5Kw.js → MessageExportModal-Ch0tqQix.js} +1 -1
  38. package/dist-web/assets/{PageHeader-C_yw54le.js → PageHeader-CRlmZoKw.js} +1 -1
  39. package/dist-web/assets/{RelationshipGalaxyCanvas-DTzJq7q7.js → RelationshipGalaxyCanvas-W_6XfPOG.js} +3 -3
  40. package/dist-web/assets/{RelationshipGalaxyThreeCanvas-DIE-0_iM.js → RelationshipGalaxyThreeCanvas-bB1nCwQ_.js} +1 -1
  41. package/dist-web/assets/{RenderTargetSystem-BSAQFamI.js → RenderTargetSystem-obRi6fMT.js} +1 -1
  42. package/dist-web/assets/{WebGLRenderer-lfzmCZNT.js → WebGLRenderer-xcDlnrCZ.js} +1 -1
  43. package/dist-web/assets/{WebGPURenderer-BnEdfdr_.js → WebGPURenderer-DbAOiiYr.js} +1 -1
  44. package/dist-web/assets/{browserAll-gRNfDyPu.js → browserAll-DVRJimO8.js} +1 -1
  45. package/dist-web/assets/electron-BSGRuxX9.js +1 -0
  46. package/dist-web/assets/{fetch-BW3aO7PS.js → fetch-7UN4YW3A.js} +1 -1
  47. package/dist-web/assets/{fetch-CQt-gb-f.js → fetch-B2alAySK.js} +1 -1
  48. package/dist-web/assets/fetch-B2rVTMYY.js +17 -0
  49. package/dist-web/assets/{fetch-C1fI_KOX.js → fetch-BSShKcMt.js} +1 -1
  50. package/dist-web/assets/{fetch-mFlEBZyB.js → fetch-BmxMf3I6.js} +1 -1
  51. package/dist-web/assets/{fetch-BwF5hNuF.js → fetch-CR5UDFW7.js} +1 -1
  52. package/dist-web/assets/{fetch-CFRoSIAn.js → fetch-ChAsglT5.js} +1 -1
  53. package/dist-web/assets/{fetch-CN91TIwV.js → fetch-DWEjTdcP.js} +1 -1
  54. package/dist-web/assets/{fetch-DPCJ-1l-.js → fetch-DfoxedXR.js} +1 -1
  55. package/dist-web/assets/{fetch-gMB4wuBd.js → fetch-t0WsRKHW.js} +1 -1
  56. package/dist-web/assets/index-B4l3rHie.css +1 -0
  57. package/dist-web/assets/{index-ZdCB7xcf.js → index-BVUTV9_w.js} +4 -4
  58. package/dist-web/assets/index-BmXNdMV5.js +2 -0
  59. package/dist-web/assets/index-BsXljrlg.js +22 -0
  60. package/dist-web/assets/{index-MBRD3wPd.css → index-C1p9CbmN.css} +1 -1
  61. package/dist-web/assets/index-COpza1A4.css +1 -0
  62. package/dist-web/assets/index-CQ5lRCHt.js +173 -0
  63. package/dist-web/assets/index-CZv0ZOID.js +2 -0
  64. package/dist-web/assets/{index-DveHdPfK.js → index-CfgDqd_z.js} +1 -1
  65. package/dist-web/assets/{index-Bg4GGPXv.js → index-CtVR_KuP.js} +1 -1
  66. package/dist-web/assets/index-DT0Bu8mH.js +64 -0
  67. package/dist-web/assets/index-DjcWJv4b.js +1 -0
  68. package/dist-web/assets/{vendor-nuxt-ui-Dr2T8_OZ.js → vendor-nuxt-ui-DaLxYaC4.js} +1 -1
  69. package/dist-web/assets/{web-BakeV58u.js → web-knHSvvNj.js} +1 -1
  70. package/dist-web/assets/{webworkerAll-D_6PgURS.js → webworkerAll-CMKoykfD.js} +1 -1
  71. package/dist-web/index.html +3 -3
  72. package/package.json +1 -1
  73. package/dist/chunk-7RW4CQAO.mjs.map +0 -1
  74. package/dist/chunk-ARZM6HBX.mjs +0 -542
  75. package/dist/chunk-ARZM6HBX.mjs.map +0 -1
  76. package/dist/chunk-CIB23U32.mjs.map +0 -1
  77. package/dist/chunk-D4WTE7LV.mjs.map +0 -1
  78. package/dist/chunk-I35WHWA6.mjs.map +0 -1
  79. package/dist/chunk-KL2NGIMQ.mjs.map +0 -1
  80. package/dist/chunk-QQK7MLCR.mjs.map +0 -1
  81. package/dist/chunk-WPI6YSAR.mjs.map +0 -1
  82. package/dist-web/assets/CanvasRenderer-BKCvcc2p.js +0 -1
  83. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-HGJb0Et1.js +0 -110
  84. package/dist-web/assets/electron-BHaEaaZP.js +0 -1
  85. package/dist-web/assets/fetch-CBZbgZqk.js +0 -17
  86. package/dist-web/assets/index--6C0sK6a.css +0 -1
  87. package/dist-web/assets/index-B6wEGata.js +0 -173
  88. package/dist-web/assets/index-BgVbj5qX.js +0 -12
  89. package/dist-web/assets/index-BkoXaBV_.js +0 -64
  90. package/dist-web/assets/index-C56IuV5U.css +0 -1
  91. package/dist-web/assets/index-CLRaKwpT.js +0 -2
  92. package/dist-web/assets/index-Dj1BSD7O.js +0 -1
  93. package/dist-web/assets/index-RoDgJcHU.js +0 -2
  94. package/dist-web/assets/index-aljYO5ms.js +0 -1
  95. /package/dist/{chat-command-JKG3FY5B.mjs.map → chat-command-VBDMWOKL.mjs.map} +0 -0
  96. /package/dist/{chunk-6FPEP2ZX.mjs.map → chunk-326OCJOT.mjs.map} +0 -0
  97. /package/dist/{chunk-SKUG5XVD.mjs.map → chunk-5EE2EFPE.mjs.map} +0 -0
  98. /package/dist/{chunk-2MZ6G4AC.mjs.map → chunk-WV5GSRQM.mjs.map} +0 -0
  99. /package/dist/{http-5E7ARYFW.mjs.map → http-L4AMELPZ.mjs.map} +0 -0
  100. /package/dist/{import-3KG6VK6O.mjs.map → import-WWCSXGVS.mjs.map} +0 -0
  101. /package/dist/{mcp-JBI4CUQT.mjs.map → mcp-A4M3LT5O.mjs.map} +0 -0
  102. /package/dist/{src-UTBQAOJ3.mjs.map → src-MPNBTFOE.mjs.map} +0 -0
  103. /package/dist/{stream-import-7HZ4IRYJ.mjs.map → stream-import-HIE7PNL4.mjs.map} +0 -0
  104. /package/dist/{worker-runtime-ALS4MR2A.mjs.map → worker-runtime-BJXO24XX.mjs.map} +0 -0
@@ -9,19 +9,20 @@ import {
9
9
  analyzeNewImport,
10
10
  incrementalImport,
11
11
  streamImport
12
- } from "./chunk-I35WHWA6.mjs";
12
+ } from "./chunk-KBSAVUFV.mjs";
13
13
  import {
14
14
  closeServerAiLogger,
15
15
  createCliRunAgentStream,
16
+ getServerAiLogger,
16
17
  initServerAiLogger
17
- } from "./chunk-SKUG5XVD.mjs";
18
+ } from "./chunk-5EE2EFPE.mjs";
18
19
  import {
19
20
  AGENT_TOOL_REGISTRY,
20
21
  CoreDataProvider
21
- } from "./chunk-CIB23U32.mjs";
22
+ } from "./chunk-TCZHG3G7.mjs";
22
23
  import {
23
24
  assertCliDataDirCompatible
24
- } from "./chunk-6FPEP2ZX.mjs";
25
+ } from "./chunk-326OCJOT.mjs";
25
26
  import {
26
27
  getVersion
27
28
  } from "./chunk-5ADKYYNA.mjs";
@@ -42,7 +43,7 @@ import {
42
43
  getPendingNodeDataDirMigration,
43
44
  hasPendingElectronDataWarning,
44
45
  serializeChatLabToJsonl
45
- } from "./chunk-7RW4CQAO.mjs";
46
+ } from "./chunk-ULNRUS55.mjs";
46
47
  import {
47
48
  AIChatManager,
48
49
  ArchiveImportError,
@@ -75,6 +76,7 @@ import {
75
76
  getSupportedFormats,
76
77
  isDictDownloaded,
77
78
  isSemanticIndexConfigured,
79
+ logNativeParserStatus,
78
80
  member_service_exports,
79
81
  mergeRulesForLocale,
80
82
  owner_profile_service_exports,
@@ -89,7 +91,7 @@ import {
89
91
  streamParseFileInfo,
90
92
  summary_service_exports,
91
93
  validateApiKey
92
- } from "./chunk-QQK7MLCR.mjs";
94
+ } from "./chunk-36JGKW6K.mjs";
93
95
  import {
94
96
  ALL_MIGRATIONS,
95
97
  BUILTIN_MODELS,
@@ -152,10 +154,10 @@ import {
152
154
  verifyCliDataPath,
153
155
  writeAuthProfile,
154
156
  writeConfigField
155
- } from "./chunk-WPI6YSAR.mjs";
157
+ } from "./chunk-LVEJHB3X.mjs";
156
158
 
157
159
  // src/http/index.ts
158
- import * as fs11 from "fs";
160
+ import * as fs12 from "fs";
159
161
  import * as crypto4 from "crypto";
160
162
 
161
163
  // src/http/server.ts
@@ -1097,7 +1099,14 @@ function registerSqlRoutes(server2, ctx) {
1097
1099
  return reply.code(400).send({ error: "Missing sql parameter" });
1098
1100
  }
1099
1101
  try {
1100
- return executeSql(db, sql, { timing: true });
1102
+ const result = executeSql(db, sql, { columnar: true, timing: true, maxRows: 0 });
1103
+ return {
1104
+ columns: result.columns,
1105
+ rows: result.rows,
1106
+ rowCount: result.rowCount,
1107
+ duration: result.duration ?? 0,
1108
+ limited: result.truncated
1109
+ };
1101
1110
  } catch (err) {
1102
1111
  const message = err instanceof Error ? err.message : "SQL execution error";
1103
1112
  return reply.code(400).send({ error: message });
@@ -1794,13 +1803,53 @@ function registerAiSummaryRoutes(server2, ctx) {
1794
1803
  });
1795
1804
  }
1796
1805
 
1797
- // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1806
+ // ../../packages/http-routes/src/routes/web/ai-logs.ts
1807
+ import fs3 from "fs";
1798
1808
  import path4 from "path";
1809
+ function findLatestAiLogPath(logsDir) {
1810
+ const aiLogDir = path4.join(logsDir, "ai");
1811
+ if (!fs3.existsSync(aiLogDir)) return null;
1812
+ const latest = fs3.readdirSync(aiLogDir).filter((name) => name.startsWith("ai_") && name.endsWith(".log")).map((name) => {
1813
+ const filePath = path4.join(aiLogDir, name);
1814
+ const stat2 = fs3.statSync(filePath);
1815
+ return { path: filePath, mtimeMs: stat2.mtimeMs };
1816
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
1817
+ return latest?.path ?? null;
1818
+ }
1819
+ function resolveAiLogPath(ctx) {
1820
+ const currentPath = ctx.getCurrentAiLogPath?.();
1821
+ if (currentPath && fs3.existsSync(currentPath)) return currentPath;
1822
+ return findLatestAiLogPath(ctx.pathProvider.getLogsDir());
1823
+ }
1824
+ function isLoopbackAddress(address) {
1825
+ if (!address) return true;
1826
+ const normalized = address.replace(/^::ffff:/, "");
1827
+ return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || normalized.startsWith("127.");
1828
+ }
1829
+ function registerAiLogRoutes(server2, ctx) {
1830
+ server2.post("/_web/ai/logs/show", async (request, reply) => {
1831
+ if (!ctx.showInFolder) return reply.code(501).send({ success: false, error: "Not supported" });
1832
+ if (!isLoopbackAddress(request.ip)) {
1833
+ return { success: false, error: "Opening AI logs is only supported on this device" };
1834
+ }
1835
+ const logPath = resolveAiLogPath(ctx);
1836
+ if (!logPath) return { success: false, error: "No AI log files found" };
1837
+ try {
1838
+ await ctx.showInFolder(logPath);
1839
+ return { success: true, path: logPath };
1840
+ } catch (error) {
1841
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1842
+ }
1843
+ });
1844
+ }
1845
+
1846
+ // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1847
+ import path5 from "path";
1799
1848
  function registerSemanticIndexRoutes(server2, ctx) {
1800
1849
  const service = ctx.semanticIndexService;
1801
1850
  if (!service) {
1802
1851
  if (ctx.aiDataDir) {
1803
- const configStore = new SemanticIndexConfigStore(path4.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1852
+ const configStore = new SemanticIndexConfigStore(path5.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1804
1853
  const configResponse = (config) => ({
1805
1854
  config,
1806
1855
  apiKeySet: resolveSemanticIndexApiKeySet(config, ctx.resolveApiKey),
@@ -1855,8 +1904,8 @@ function registerSemanticIndexRoutes(server2, ctx) {
1855
1904
  server2.post("/_web/ai/semantic-index/status", async (request) => {
1856
1905
  return { sessions: await service.statusForSessions(request.body.sessionIds ?? []) };
1857
1906
  });
1858
- const sessionAction = (path13, action) => {
1859
- server2.post(path13, async (request, reply) => {
1907
+ const sessionAction = (path14, action) => {
1908
+ server2.post(path14, async (request, reply) => {
1860
1909
  const { sessionId } = request.body;
1861
1910
  if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
1862
1911
  await action(sessionId);
@@ -1887,9 +1936,9 @@ function registerSemanticIndexRoutes(server2, ctx) {
1887
1936
  }
1888
1937
 
1889
1938
  // ../../packages/http-routes/src/routes/web/merge.ts
1890
- import * as fs3 from "fs";
1939
+ import * as fs4 from "fs";
1891
1940
  import * as os from "os";
1892
- import * as path5 from "path";
1941
+ import * as path6 from "path";
1893
1942
  function ensureDb2(ctx, sessionId) {
1894
1943
  const db = ctx.dbManager.open(sessionId);
1895
1944
  if (!db) throw sessionNotFound(sessionId);
@@ -1906,31 +1955,31 @@ function registerMergeRoutes(server2, ctx) {
1906
1955
  if (contentType.includes("multipart/form-data")) {
1907
1956
  const data = await request.file();
1908
1957
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1909
- const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merge-"));
1910
- const tmpPath = path5.join(tmpDir, data.filename || "upload");
1958
+ const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merge-"));
1959
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1911
1960
  const chunks = [];
1912
1961
  for await (const chunk of data.file) {
1913
1962
  chunks.push(chunk);
1914
1963
  }
1915
- fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
1964
+ fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
1916
1965
  filePath = tmpPath;
1917
1966
  cleanupFile = true;
1918
1967
  cleanupDir = tmpDir;
1919
1968
  } else {
1920
1969
  const body = request.body;
1921
1970
  if (!body?.filePath) return reply.code(400).send({ error: "Missing filePath in body" });
1922
- if (!fs3.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1971
+ if (!fs4.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1923
1972
  filePath = body.filePath;
1924
1973
  }
1925
1974
  try {
1926
1975
  const result = await streamParseFileInfo(filePath, {
1927
1976
  createTempDatabase(sourceFilePath) {
1928
- return mergeCache.createTempDatabase(path5.basename(sourceFilePath));
1977
+ return mergeCache.createTempDatabase(path6.basename(sourceFilePath));
1929
1978
  },
1930
1979
  onProgress() {
1931
1980
  }
1932
1981
  });
1933
- const filename = path5.basename(filePath);
1982
+ const filename = path6.basename(filePath);
1934
1983
  const handle = mergeCache.store(filename, result.tempDbPath);
1935
1984
  return {
1936
1985
  handle,
@@ -1944,13 +1993,13 @@ function registerMergeRoutes(server2, ctx) {
1944
1993
  } finally {
1945
1994
  if (cleanupFile) {
1946
1995
  try {
1947
- fs3.unlinkSync(filePath);
1996
+ fs4.unlinkSync(filePath);
1948
1997
  } catch {
1949
1998
  }
1950
1999
  }
1951
2000
  if (cleanupDir) {
1952
2001
  try {
1953
- fs3.rmdirSync(cleanupDir);
2002
+ fs4.rmdirSync(cleanupDir);
1954
2003
  } catch {
1955
2004
  }
1956
2005
  }
@@ -1999,20 +2048,20 @@ function registerMergeRoutes(server2, ctx) {
1999
2048
  return reply.code(501).send({ error: "Import not available on this server" });
2000
2049
  }
2001
2050
  const jsonData = JSON.stringify(merged.chatLabData);
2002
- const tmpDir = fs3.mkdtempSync(path5.join(os.tmpdir(), "chatlab-merged-"));
2003
- const safeName = path5.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
2004
- const tmpPath = path5.join(tmpDir, `${safeName}.json`);
2005
- fs3.writeFileSync(tmpPath, jsonData, "utf-8");
2051
+ const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merged-"));
2052
+ const safeName = path6.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
2053
+ const tmpPath = path6.join(tmpDir, `${safeName}.json`);
2054
+ fs4.writeFileSync(tmpPath, jsonData, "utf-8");
2006
2055
  try {
2007
2056
  const importResult = await streamImport2(dbManager2, tmpPath);
2008
2057
  sessionId = importResult.sessionId;
2009
2058
  } finally {
2010
2059
  try {
2011
- fs3.unlinkSync(tmpPath);
2060
+ fs4.unlinkSync(tmpPath);
2012
2061
  } catch {
2013
2062
  }
2014
2063
  try {
2015
- fs3.rmdirSync(tmpDir);
2064
+ fs4.rmdirSync(tmpDir);
2016
2065
  } catch {
2017
2066
  }
2018
2067
  }
@@ -2086,16 +2135,16 @@ function registerMergeRoutes(server2, ctx) {
2086
2135
  }
2087
2136
 
2088
2137
  // ../../packages/http-routes/src/routes/web/cache.ts
2089
- import * as fs4 from "fs";
2138
+ import * as fs5 from "fs";
2090
2139
  import * as fsp from "fs/promises";
2091
- import * as path6 from "path";
2140
+ import * as path7 from "path";
2092
2141
  async function getDirSize(dirPath) {
2093
2142
  let totalSize = 0;
2094
2143
  try {
2095
- if (!fs4.existsSync(dirPath)) return 0;
2144
+ if (!fs5.existsSync(dirPath)) return 0;
2096
2145
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
2097
2146
  for (const file of files) {
2098
- const filePath = path6.join(dirPath, file.name);
2147
+ const filePath = path7.join(dirPath, file.name);
2099
2148
  if (file.isDirectory()) {
2100
2149
  totalSize += await getDirSize(filePath);
2101
2150
  } else {
@@ -2110,10 +2159,10 @@ async function getDirSize(dirPath) {
2110
2159
  async function getFileCount(dirPath) {
2111
2160
  let count = 0;
2112
2161
  try {
2113
- if (!fs4.existsSync(dirPath)) return 0;
2162
+ if (!fs5.existsSync(dirPath)) return 0;
2114
2163
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
2115
2164
  for (const file of files) {
2116
- const filePath = path6.join(dirPath, file.name);
2165
+ const filePath = path7.join(dirPath, file.name);
2117
2166
  if (file.isDirectory()) {
2118
2167
  count += await getFileCount(filePath);
2119
2168
  } else {
@@ -2126,10 +2175,10 @@ async function getFileCount(dirPath) {
2126
2175
  }
2127
2176
  function hasChatLabDatabases(dirPath) {
2128
2177
  try {
2129
- const markerPath = path6.join(dirPath, ".chatlab");
2130
- const dbDir = path6.join(dirPath, "databases");
2131
- if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
2132
- return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
2178
+ const markerPath = path7.join(dirPath, ".chatlab");
2179
+ const dbDir = path7.join(dirPath, "databases");
2180
+ if (!fs5.existsSync(markerPath) || !fs5.existsSync(dbDir)) return false;
2181
+ return fs5.readdirSync(dbDir).some((file) => file.endsWith(".db"));
2133
2182
  } catch {
2134
2183
  return false;
2135
2184
  }
@@ -2144,6 +2193,8 @@ function registerCacheRoutes(server2, ctx) {
2144
2193
  name: "settings.storage.cache.databases.name",
2145
2194
  description: "settings.storage.cache.databases.description",
2146
2195
  path: pp.getDatabaseDir(),
2196
+ scope: "user-data",
2197
+ rootPath: pp.getUserDataDir(),
2147
2198
  icon: "i-heroicons-circle-stack",
2148
2199
  canClear: false
2149
2200
  },
@@ -2152,6 +2203,8 @@ function registerCacheRoutes(server2, ctx) {
2152
2203
  name: "settings.storage.cache.ai.name",
2153
2204
  description: "settings.storage.cache.ai.description",
2154
2205
  path: pp.getAiDataDir(),
2206
+ scope: "system-data",
2207
+ rootPath: pp.getSystemDir(),
2155
2208
  icon: "i-heroicons-sparkles",
2156
2209
  canClear: false
2157
2210
  },
@@ -2160,6 +2213,8 @@ function registerCacheRoutes(server2, ctx) {
2160
2213
  name: "settings.storage.cache.statsCache.name",
2161
2214
  description: "settings.storage.cache.statsCache.description",
2162
2215
  path: pp.getCacheDir(),
2216
+ scope: "system-data",
2217
+ rootPath: pp.getSystemDir(),
2163
2218
  icon: "i-heroicons-bolt",
2164
2219
  canClear: true
2165
2220
  },
@@ -2168,6 +2223,8 @@ function registerCacheRoutes(server2, ctx) {
2168
2223
  name: "settings.storage.cache.logs.name",
2169
2224
  description: "settings.storage.cache.logs.description",
2170
2225
  path: pp.getLogsDir(),
2226
+ scope: "system-data",
2227
+ rootPath: pp.getSystemDir(),
2171
2228
  icon: "i-heroicons-document-text",
2172
2229
  canClear: true
2173
2230
  }
@@ -2175,11 +2232,11 @@ function registerCacheRoutes(server2, ctx) {
2175
2232
  const results = await Promise.all(
2176
2233
  directories.map(async (dir) => {
2177
2234
  const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
2178
- return { ...dir, size, fileCount, exists: fs4.existsSync(dir.path) };
2235
+ return { ...dir, size, fileCount, exists: fs5.existsSync(dir.path) };
2179
2236
  })
2180
2237
  );
2181
2238
  return {
2182
- baseDir: pp.getUserDataDir(),
2239
+ baseDir: pp.getSystemDir(),
2183
2240
  directories: results,
2184
2241
  totalSize: results.reduce((sum, d) => sum + d.size, 0)
2185
2242
  };
@@ -2192,10 +2249,10 @@ function registerCacheRoutes(server2, ctx) {
2192
2249
  };
2193
2250
  const dirPath = allowedDirs[cacheId];
2194
2251
  if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
2195
- if (!fs4.existsSync(dirPath)) return { success: true };
2252
+ if (!fs5.existsSync(dirPath)) return { success: true };
2196
2253
  const files = await fsp.readdir(dirPath);
2197
2254
  for (const file of files) {
2198
- const filePath = path6.join(dirPath, file);
2255
+ const filePath = path7.join(dirPath, file);
2199
2256
  const stat2 = await fsp.stat(filePath);
2200
2257
  if (stat2.isDirectory()) {
2201
2258
  await fsp.rm(filePath, { recursive: true });
@@ -2212,7 +2269,9 @@ function registerCacheRoutes(server2, ctx) {
2212
2269
  defaultPath: ctx.defaultUserDataDir,
2213
2270
  isCustom: ctx.isCustomDataDir ?? false,
2214
2271
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
2215
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path6.resolve(pp.getUserDataDir()) !== path6.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2272
+ managedScope: "chat-databases",
2273
+ managedDescription: "settings.storage.dataLocation.managedDescription",
2274
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path7.resolve(pp.getUserDataDir()) !== path7.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2216
2275
  pendingMigration: pending ? {
2217
2276
  from: pending.from,
2218
2277
  to: pending.to,
@@ -2229,8 +2288,8 @@ function registerCacheRoutes(server2, ctx) {
2229
2288
  return ctx.setDataDir(targetPath, migrate);
2230
2289
  });
2231
2290
  server2.get("/_web/cache/latest-import-log", async () => {
2232
- const importLogDir = path6.join(pp.getLogsDir(), "import");
2233
- if (!fs4.existsSync(importLogDir)) {
2291
+ const importLogDir = path7.join(pp.getLogsDir(), "import");
2292
+ if (!fs5.existsSync(importLogDir)) {
2234
2293
  return { success: false, error: "Log directory not found" };
2235
2294
  }
2236
2295
  const files = await fsp.readdir(importLogDir);
@@ -2240,7 +2299,7 @@ function registerCacheRoutes(server2, ctx) {
2240
2299
  }
2241
2300
  const fileStats = await Promise.all(
2242
2301
  logFiles.map(async (f) => {
2243
- const filePath = path6.join(importLogDir, f);
2302
+ const filePath = path7.join(importLogDir, f);
2244
2303
  const stat2 = await fsp.stat(filePath);
2245
2304
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
2246
2305
  })
@@ -2264,18 +2323,19 @@ function registerCacheRoutes(server2, ctx) {
2264
2323
  const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
2265
2324
  buffer = Buffer.from(base64Data, "base64");
2266
2325
  }
2267
- if (!fs4.existsSync(downloadsDir)) {
2268
- fs4.mkdirSync(downloadsDir, { recursive: true });
2326
+ if (!fs5.existsSync(downloadsDir)) {
2327
+ fs5.mkdirSync(downloadsDir, { recursive: true });
2269
2328
  }
2270
- const filePath = path6.join(downloadsDir, filename);
2271
- fs4.writeFileSync(filePath, buffer);
2329
+ const filePath = path7.join(downloadsDir, filename);
2330
+ fs5.writeFileSync(filePath, buffer);
2272
2331
  return { success: true, filePath };
2273
2332
  });
2274
2333
  server2.post("/_web/cache/open-dir", async (request, reply) => {
2275
2334
  if (!ctx.openDirectory) return reply.code(501).send({ success: false, error: "Not supported" });
2276
2335
  const { cacheId } = request.body;
2277
2336
  const dirPaths = {
2278
- base: pp.getUserDataDir(),
2337
+ base: pp.getSystemDir(),
2338
+ userData: pp.getUserDataDir(),
2279
2339
  databases: pp.getDatabaseDir(),
2280
2340
  cache: pp.getCacheDir(),
2281
2341
  ai: pp.getAiDataDir(),
@@ -2284,7 +2344,7 @@ function registerCacheRoutes(server2, ctx) {
2284
2344
  };
2285
2345
  const dirPath = dirPaths[cacheId];
2286
2346
  if (!dirPath) return { success: false, error: "Unknown directory" };
2287
- if (!fs4.existsSync(dirPath)) {
2347
+ if (!fs5.existsSync(dirPath)) {
2288
2348
  await fsp.mkdir(dirPath, { recursive: true });
2289
2349
  }
2290
2350
  try {
@@ -2384,6 +2444,7 @@ function registerSharedRoutes(server2, ctx, options) {
2384
2444
  registerAiToolRoutes(server2, resolvedCtx);
2385
2445
  registerAiChatRoutes(server2, resolvedCtx);
2386
2446
  registerAiSummaryRoutes(server2, resolvedCtx);
2447
+ registerAiLogRoutes(server2, resolvedCtx);
2387
2448
  registerSemanticIndexRoutes(server2, resolvedCtx);
2388
2449
  registerMergeRoutes(server2, resolvedCtx);
2389
2450
  registerCacheRoutes(server2, resolvedCtx);
@@ -2434,6 +2495,150 @@ function requireBearerToken(request, reply) {
2434
2495
  }
2435
2496
  }
2436
2497
 
2498
+ // ../../packages/http-routes/src/routes/web/automation.ts
2499
+ function requireAutomation(ctx) {
2500
+ return ctx.automation ?? null;
2501
+ }
2502
+ function normalizeBaseUrl(input) {
2503
+ let url = input.trim().replace(/\/+$/, "");
2504
+ if (url && !/^https?:\/\//i.test(url)) url = `http://${url}`;
2505
+ if (url && !url.endsWith("/api/v1")) {
2506
+ url = url.replace(/\/api\/v1$/, "") + "/api/v1";
2507
+ }
2508
+ return url;
2509
+ }
2510
+ function buildRemoteSessionsUrl(baseUrl, query = {}) {
2511
+ const searchParams = new URLSearchParams();
2512
+ searchParams.set("format", "chatlab");
2513
+ if (query.keyword?.trim()) searchParams.set("keyword", query.keyword.trim());
2514
+ if (query.limit && Number(query.limit) > 0) searchParams.set("limit", String(query.limit));
2515
+ if (query.cursor) searchParams.set("cursor", query.cursor);
2516
+ return `${baseUrl}/sessions?${searchParams.toString()}`;
2517
+ }
2518
+ function asRecord(value) {
2519
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2520
+ }
2521
+ function parseRemoteSessionsResponse(body) {
2522
+ const parsed = JSON.parse(body);
2523
+ let sessions;
2524
+ let pageSource;
2525
+ if (Array.isArray(parsed)) {
2526
+ sessions = parsed;
2527
+ } else {
2528
+ const record = asRecord(parsed);
2529
+ const data = asRecord(record?.data);
2530
+ const sessionsSource = record?.sessions ?? data?.sessions ?? record?.data;
2531
+ sessions = Array.isArray(sessionsSource) ? sessionsSource : [];
2532
+ pageSource = asRecord(record?.page) ?? asRecord(data?.page);
2533
+ }
2534
+ return {
2535
+ sessions,
2536
+ page: pageSource && typeof pageSource === "object" ? {
2537
+ hasMore: Boolean(pageSource.hasMore),
2538
+ nextCursor: typeof pageSource.nextCursor === "string" ? pageSource.nextCursor : void 0
2539
+ } : void 0
2540
+ };
2541
+ }
2542
+ function registerAutomationRoutes(server2, ctx) {
2543
+ server2.get("/_web/automation/config", async (_request, reply) => {
2544
+ const automation = requireAutomation(ctx);
2545
+ if (!automation?.serverInfo) return reply.code(404).send({ error: "Automation is not enabled" });
2546
+ return {
2547
+ enabled: true,
2548
+ port: automation.serverInfo.port,
2549
+ token: automation.serverInfo.token,
2550
+ host: automation.serverInfo.host
2551
+ };
2552
+ });
2553
+ server2.get("/_web/automation/data-sources", async (_request, reply) => {
2554
+ const automation = requireAutomation(ctx);
2555
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2556
+ return automation.dsManager.loadAll();
2557
+ });
2558
+ server2.post("/_web/automation/data-sources", async (request, reply) => {
2559
+ const automation = requireAutomation(ctx);
2560
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2561
+ const ds = automation.dsManager.add(request.body);
2562
+ automation.reloadTimer?.(ds.id);
2563
+ return ds;
2564
+ });
2565
+ server2.patch("/_web/automation/data-sources/:id", async (request, reply) => {
2566
+ const automation = requireAutomation(ctx);
2567
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2568
+ const ds = automation.dsManager.update(request.params.id, request.body);
2569
+ if (!ds) return reply.code(404).send({ error: "Data source not found" });
2570
+ automation.reloadTimer?.(ds.id);
2571
+ return ds;
2572
+ });
2573
+ server2.delete("/_web/automation/data-sources/:id", async (request, reply) => {
2574
+ const automation = requireAutomation(ctx);
2575
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2576
+ automation.stopTimer?.(request.params.id);
2577
+ const ok = automation.dsManager.delete(request.params.id);
2578
+ if (!ok) return reply.code(404).send({ error: "Data source not found" });
2579
+ return { success: true };
2580
+ });
2581
+ server2.post("/_web/automation/data-sources/:id/sessions", async (request, reply) => {
2582
+ const automation = requireAutomation(ctx);
2583
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2584
+ const added = automation.dsManager.addSessions(request.params.id, request.body.sessions);
2585
+ if (added.length === 0 && !automation.dsManager.get(request.params.id)) {
2586
+ return reply.code(404).send({ error: "Data source not found" });
2587
+ }
2588
+ automation.reloadTimer?.(request.params.id, true);
2589
+ for (const sess of added) {
2590
+ void automation.pullEngine.triggerPull(request.params.id, sess.id).catch(() => void 0);
2591
+ }
2592
+ return added;
2593
+ });
2594
+ server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
2595
+ const automation = requireAutomation(ctx);
2596
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2597
+ const removed = automation.dsManager.removeSession(request.params.id, request.params.sessId);
2598
+ if (!removed) return reply.code(404).send({ error: "Session not found" });
2599
+ automation.reloadTimer?.(request.params.id);
2600
+ if (request.query.deleteData === "true" && removed.targetSessionId) {
2601
+ automation.deleteSessionData?.(removed.targetSessionId);
2602
+ }
2603
+ return { success: true };
2604
+ });
2605
+ server2.post(
2606
+ "/_web/automation/data-sources/:id/pull",
2607
+ async (request, reply) => {
2608
+ const automation = requireAutomation(ctx);
2609
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2610
+ return automation.pullEngine.triggerPull(request.params.id, request.body?.sessionId);
2611
+ }
2612
+ );
2613
+ server2.post("/_web/automation/data-sources/:id/pull-all", async (request, reply) => {
2614
+ const automation = requireAutomation(ctx);
2615
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2616
+ return automation.pullEngine.triggerPullAll(request.params.id);
2617
+ });
2618
+ server2.get("/_web/automation/sync-progress", async (_request, reply) => {
2619
+ const automation = requireAutomation(ctx);
2620
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2621
+ return automation.pullEngine.getProgress();
2622
+ });
2623
+ server2.get("/_web/automation/remote-sessions", async (request, reply) => {
2624
+ const automation = requireAutomation(ctx);
2625
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2626
+ const { baseUrl, token, keyword, limit, cursor } = request.query;
2627
+ if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
2628
+ const url = buildRemoteSessionsUrl(normalizeBaseUrl(baseUrl), { keyword, limit, cursor });
2629
+ const headers = { Accept: "application/json" };
2630
+ if (token) headers.Authorization = `Bearer ${token}`;
2631
+ try {
2632
+ const resp = await fetch(url, { headers, signal: AbortSignal.timeout(3e4) });
2633
+ if (!resp.ok) throw new Error(`Remote server returned HTTP ${resp.status}`);
2634
+ const body = await resp.text();
2635
+ return parseRemoteSessionsResponse(body);
2636
+ } catch (err) {
2637
+ return reply.code(502).send({ error: err instanceof Error ? err.message : "Failed to fetch remote sessions" });
2638
+ }
2639
+ });
2640
+ }
2641
+
2437
2642
  // src/http/server.ts
2438
2643
  var JSON_BODY_LIMIT = 50 * 1024 * 1024;
2439
2644
  function createServer() {
@@ -2467,21 +2672,21 @@ function createServer() {
2467
2672
 
2468
2673
  // src/http/routes/web/index.ts
2469
2674
  import * as os3 from "os";
2470
- import * as path9 from "path";
2675
+ import * as path10 from "path";
2471
2676
 
2472
2677
  // src/http/routes/web/import.ts
2473
- import * as fs6 from "fs";
2678
+ import * as fs7 from "fs";
2474
2679
  import * as os2 from "os";
2475
- import * as path7 from "path";
2680
+ import * as path8 from "path";
2476
2681
  import { randomUUID } from "crypto";
2477
2682
  import { pipeline } from "stream/promises";
2478
2683
 
2479
2684
  // src/http/routes/web/helpers.ts
2480
- import * as fs5 from "fs";
2685
+ import * as fs6 from "fs";
2481
2686
  function resolveNativeBinding() {
2482
2687
  if (process.versions.electron) return void 0;
2483
2688
  const nativePath = resolveCliPath("native/better_sqlite3.node");
2484
- if (fs5.existsSync(nativePath)) return nativePath;
2689
+ if (fs6.existsSync(nativePath)) return nativePath;
2485
2690
  return void 0;
2486
2691
  }
2487
2692
 
@@ -2491,11 +2696,11 @@ var ARCHIVE_UPLOAD_LIMIT = 50 * 1024 * 1024 * 1024;
2491
2696
  function cleanupTemp(...paths) {
2492
2697
  for (const p of paths) {
2493
2698
  try {
2494
- const stat2 = fs6.statSync(p);
2699
+ const stat2 = fs7.statSync(p);
2495
2700
  if (stat2.isDirectory()) {
2496
- fs6.rmSync(p, { recursive: true, force: true });
2701
+ fs7.rmSync(p, { recursive: true, force: true });
2497
2702
  } else {
2498
- fs6.unlinkSync(p);
2703
+ fs7.unlinkSync(p);
2499
2704
  }
2500
2705
  } catch {
2501
2706
  }
@@ -2503,7 +2708,7 @@ function cleanupTemp(...paths) {
2503
2708
  }
2504
2709
  function registerImportRoutes2(server2, dbManager2, options = {}) {
2505
2710
  const sourceManager = options.sourceManager ?? new ArchiveImportSourceManager({
2506
- tempRoot: fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-sources-"))
2711
+ tempRoot: fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-sources-"))
2507
2712
  });
2508
2713
  const runPreparedImport = options.runPreparedImport ?? (async (manifestPath, onProgress) => {
2509
2714
  const result = await streamImport(dbManager2, manifestPath, {
@@ -2527,9 +2732,9 @@ function registerImportRoutes2(server2, dbManager2, options = {}) {
2527
2732
  limits: { fileSize: ARCHIVE_UPLOAD_LIMIT }
2528
2733
  });
2529
2734
  if (!data) return reply.code(400).send({ success: false, error: "error.no_file_selected" });
2530
- const uploadPath = path7.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
2735
+ const uploadPath = path8.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
2531
2736
  try {
2532
- await pipeline(data.file, fs6.createWriteStream(uploadPath, { flags: "wx" }));
2737
+ await pipeline(data.file, fs7.createWriteStream(uploadPath, { flags: "wx" }));
2533
2738
  if (data.file.truncated) {
2534
2739
  cleanupTemp(uploadPath);
2535
2740
  return reply.code(413).send({ success: false, error: "error.archive_limit_exceeded" });
@@ -2594,14 +2799,14 @@ data: ${JSON.stringify(eventData)}
2594
2799
  server2.post("/_web/detect-format", async (request, reply) => {
2595
2800
  const data = await request.file();
2596
2801
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2597
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-detect-"));
2598
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
2802
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-detect-"));
2803
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2599
2804
  try {
2600
2805
  const chunks = [];
2601
2806
  for await (const chunk of data.file) {
2602
2807
  chunks.push(chunk);
2603
2808
  }
2604
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2809
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2605
2810
  const format = detectFormat(tmpPath);
2606
2811
  const allFormats = detectAllFormats(tmpPath);
2607
2812
  return { format, allFormats };
@@ -2612,14 +2817,14 @@ data: ${JSON.stringify(eventData)}
2612
2817
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2613
2818
  const data = await request.file();
2614
2819
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2615
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-scan-"));
2616
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
2820
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-scan-"));
2821
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2617
2822
  try {
2618
2823
  const chunks = [];
2619
2824
  for await (const chunk of data.file) {
2620
2825
  chunks.push(chunk);
2621
2826
  }
2622
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2827
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2623
2828
  const chats = await scanMultiChatFile(tmpPath);
2624
2829
  return { chats };
2625
2830
  } finally {
@@ -2629,13 +2834,13 @@ data: ${JSON.stringify(eventData)}
2629
2834
  server2.post("/_web/import", async (request, reply) => {
2630
2835
  const data = await request.file();
2631
2836
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2632
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-"));
2633
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
2837
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-"));
2838
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2634
2839
  const chunks = [];
2635
2840
  for await (const chunk of data.file) {
2636
2841
  chunks.push(chunk);
2637
2842
  }
2638
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2843
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2639
2844
  const formatId = data.fields?.formatId?.value;
2640
2845
  const chatIndexStr = data.fields?.chatIndex?.value;
2641
2846
  const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
@@ -2678,7 +2883,7 @@ data: ${JSON.stringify(eventData)}
2678
2883
  server2.post("/_web/import-directory", async (request, reply) => {
2679
2884
  const parts = request.parts();
2680
2885
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2681
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-dir-import-"));
2886
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-dir-import-"));
2682
2887
  const relativePaths = [];
2683
2888
  const fileBuffers = [];
2684
2889
  try {
@@ -2706,10 +2911,10 @@ data: ${JSON.stringify(eventData)}
2706
2911
  if (segments.length > 1) {
2707
2912
  relPath = segments.slice(1).join("/");
2708
2913
  }
2709
- const targetPath = path7.resolve(tmpDir, relPath);
2710
- if (!targetPath.startsWith(tmpDir + path7.sep)) continue;
2711
- fs6.mkdirSync(path7.dirname(targetPath), { recursive: true });
2712
- fs6.writeFileSync(targetPath, fileBuffers[i].data);
2914
+ const targetPath = path8.resolve(tmpDir, relPath);
2915
+ if (!targetPath.startsWith(tmpDir + path8.sep)) continue;
2916
+ fs7.mkdirSync(path8.dirname(targetPath), { recursive: true });
2917
+ fs7.writeFileSync(targetPath, fileBuffers[i].data);
2713
2918
  }
2714
2919
  const entryPath = findEntryFileInDirectory(tmpDir);
2715
2920
  if (!entryPath) {
@@ -2754,13 +2959,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2754
2959
  const sessionId = request.params.id;
2755
2960
  const data = await request.file();
2756
2961
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2757
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-inc-"));
2758
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
2962
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-inc-"));
2963
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2759
2964
  const chunks = [];
2760
2965
  for await (const chunk of data.file) {
2761
2966
  chunks.push(chunk);
2762
2967
  }
2763
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2968
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2764
2969
  reply.raw.writeHead(200, {
2765
2970
  "Content-Type": "text/event-stream",
2766
2971
  "Cache-Control": "no-cache",
@@ -2792,13 +2997,13 @@ data: ${JSON.stringify(eventData)}
2792
2997
  const sessionId = request.params.id;
2793
2998
  const data = await request.file();
2794
2999
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2795
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2796
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
3000
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
3001
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2797
3002
  const chunks = [];
2798
3003
  for await (const chunk of data.file) {
2799
3004
  chunks.push(chunk);
2800
3005
  }
2801
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
3006
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2802
3007
  try {
2803
3008
  return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
2804
3009
  } catch (err) {
@@ -2810,13 +3015,13 @@ data: ${JSON.stringify(eventData)}
2810
3015
  server2.post("/_web/import/analyze", async (request, reply) => {
2811
3016
  const data = await request.file();
2812
3017
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2813
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-analyze-"));
2814
- const tmpPath = path7.join(tmpDir, data.filename || "upload");
3018
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
3019
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2815
3020
  const chunks = [];
2816
3021
  for await (const chunk of data.file) {
2817
3022
  chunks.push(chunk);
2818
3023
  }
2819
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
3024
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2820
3025
  try {
2821
3026
  return await analyzeNewImport(tmpPath);
2822
3027
  } catch (err) {
@@ -2846,17 +3051,17 @@ data: ${JSON.stringify(eventData)}
2846
3051
 
2847
3052
  `);
2848
3053
  }
2849
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-demo-"));
3054
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-demo-"));
2850
3055
  try {
2851
3056
  const localPaths = [];
2852
3057
  for (let i = 0; i < total; i++) {
2853
3058
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2854
- const localPath = path7.join(tmpDir, DEMO_FILES[i]);
3059
+ const localPath = path8.join(tmpDir, DEMO_FILES[i]);
2855
3060
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2856
3061
  signal: AbortSignal.timeout(6e4)
2857
3062
  });
2858
3063
  if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
2859
- fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
3064
+ fs7.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2860
3065
  localPaths.push(localPath);
2861
3066
  }
2862
3067
  sendEvent("progress", { stage: "importing", current: 1, total });
@@ -2887,7 +3092,7 @@ data: ${JSON.stringify(eventData)}
2887
3092
  }
2888
3093
 
2889
3094
  // src/http/routes/web/cache.ts
2890
- import * as path8 from "path";
3095
+ import * as path9 from "path";
2891
3096
  import { execFile } from "child_process";
2892
3097
  function runExecFile(file, args, runner = execFile) {
2893
3098
  return new Promise((resolve4, reject) => {
@@ -2915,7 +3120,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2915
3120
  } else if (platform === "win32") {
2916
3121
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2917
3122
  } else {
2918
- await runExecFile("xdg-open", [path8.dirname(filePath)], runner);
3123
+ await runExecFile("xdg-open", [path9.dirname(filePath)], runner);
2919
3124
  }
2920
3125
  }
2921
3126
 
@@ -2938,27 +3143,27 @@ function registerWebRoutes(server2, dbManager2, options) {
2938
3143
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2939
3144
  mergeCache?.cleanupOrphans();
2940
3145
  const fallbackPathProvider = {
2941
- getSystemDir: () => path9.join(os3.homedir(), ".chatlab"),
2942
- getUserDataDir: () => path9.join(os3.homedir(), ".chatlab", "data"),
2943
- getDatabaseDir: () => path9.join(os3.homedir(), ".chatlab", "data", "databases"),
2944
- getVectorDir: () => path9.join(os3.homedir(), ".chatlab", "data", "vector"),
2945
- getAiDataDir: () => path9.join(os3.homedir(), ".chatlab", "ai"),
2946
- getSettingsDir: () => path9.join(os3.homedir(), ".chatlab", "settings"),
2947
- getCacheDir: () => path9.join(os3.homedir(), ".chatlab", "cache"),
2948
- getTempDir: () => path9.join(os3.homedir(), ".chatlab", "temp"),
2949
- getLogsDir: () => path9.join(os3.homedir(), ".chatlab", "logs"),
2950
- getDownloadsDir: () => path9.join(os3.homedir(), "Downloads")
3146
+ getSystemDir: () => path10.join(os3.homedir(), ".chatlab"),
3147
+ getUserDataDir: () => path10.join(os3.homedir(), ".chatlab", "data"),
3148
+ getDatabaseDir: () => path10.join(os3.homedir(), ".chatlab", "data", "databases"),
3149
+ getVectorDir: () => path10.join(os3.homedir(), ".chatlab", "data", "vector"),
3150
+ getAiDataDir: () => path10.join(os3.homedir(), ".chatlab", "ai"),
3151
+ getSettingsDir: () => path10.join(os3.homedir(), ".chatlab", "settings"),
3152
+ getCacheDir: () => path10.join(os3.homedir(), ".chatlab", "cache"),
3153
+ getTempDir: () => path10.join(os3.homedir(), ".chatlab", "temp"),
3154
+ getLogsDir: () => path10.join(os3.homedir(), ".chatlab", "logs"),
3155
+ getDownloadsDir: () => path10.join(os3.homedir(), "Downloads")
2951
3156
  };
2952
3157
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2953
3158
  const ai = options?.aiContext;
2954
3159
  const cliStreamImport = async (dm, filePath) => {
2955
- const { streamImport: streamImport2 } = await import("./stream-import-7HZ4IRYJ.mjs");
3160
+ const { streamImport: streamImport2 } = await import("./stream-import-HIE7PNL4.mjs");
2956
3161
  const result = await streamImport2(dm, filePath);
2957
3162
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2958
3163
  return { sessionId: result.sessionId };
2959
3164
  };
2960
3165
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2961
- const isCustom = path9.resolve(resolvedPathProvider.getUserDataDir()) !== path9.resolve(defaultUserDataDir);
3166
+ const isCustom = path10.resolve(resolvedPathProvider.getUserDataDir()) !== path10.resolve(defaultUserDataDir);
2962
3167
  const semanticIndexService = options?.semanticIndexService;
2963
3168
  const analyticsService = "A-US-0629338532" ? new AnalyticsService(resolvedPathProvider.getSystemDir(), "A-US-0629338532", getVersion()) : void 0;
2964
3169
  registerSharedRoutes(
@@ -2972,6 +3177,7 @@ function registerWebRoutes(server2, dbManager2, options) {
2972
3177
  nativeBinding: options?.nativeBinding,
2973
3178
  semanticIndexService,
2974
3179
  analyticsService,
3180
+ getCurrentAiLogPath: () => getServerAiLogger()?.getExistingLogPath() ?? null,
2975
3181
  openDirectory: openDirectoryPath,
2976
3182
  showInFolder: showPathInFolder,
2977
3183
  downloadsDir: resolvedPathProvider.getDownloadsDir(),
@@ -3054,31 +3260,31 @@ function registerProxyRoutes(server2) {
3054
3260
  }
3055
3261
 
3056
3262
  // src/ai/manager-factory.ts
3057
- import * as fs7 from "fs";
3058
- import * as path10 from "path";
3263
+ import * as fs8 from "fs";
3264
+ import * as path11 from "path";
3059
3265
  import { createHash, randomUUID as randomUUID2 } from "crypto";
3060
3266
  var nodeFs = {
3061
3267
  ensureDir(dir) {
3062
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
3268
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
3063
3269
  },
3064
3270
  listFiles(dir, ext) {
3065
- if (!fs7.existsSync(dir)) return [];
3066
- return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
3271
+ if (!fs8.existsSync(dir)) return [];
3272
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(ext));
3067
3273
  },
3068
3274
  readFile(filePath) {
3069
- return fs7.readFileSync(filePath, "utf-8");
3275
+ return fs8.readFileSync(filePath, "utf-8");
3070
3276
  },
3071
3277
  writeFile(filePath, content) {
3072
- fs7.writeFileSync(filePath, content, "utf-8");
3278
+ fs8.writeFileSync(filePath, content, "utf-8");
3073
3279
  },
3074
3280
  deleteFile(filePath) {
3075
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
3281
+ if (fs8.existsSync(filePath)) fs8.unlinkSync(filePath);
3076
3282
  },
3077
3283
  fileExists(filePath) {
3078
- return fs7.existsSync(filePath);
3284
+ return fs8.existsSync(filePath);
3079
3285
  },
3080
3286
  joinPath(...parts) {
3081
- return path10.join(...parts);
3287
+ return path11.join(...parts);
3082
3288
  }
3083
3289
  };
3084
3290
  var managerCache = /* @__PURE__ */ new Map();
@@ -3088,13 +3294,13 @@ function getAssistantManager(aiDataDir) {
3088
3294
  cached = {
3089
3295
  assistant: new AssistantManager({
3090
3296
  fs: nodeFs,
3091
- assistantsDir: path10.join(aiDataDir, "assistants"),
3297
+ assistantsDir: path11.join(aiDataDir, "assistants"),
3092
3298
  builtinRawConfigs: [],
3093
3299
  generateId: () => `custom_${randomUUID2().replace(/-/g, "").slice(0, 12)}`
3094
3300
  }),
3095
3301
  skill: new SkillManagerCore({
3096
3302
  fs: nodeFs,
3097
- skillsDir: path10.join(aiDataDir, "skills"),
3303
+ skillsDir: path11.join(aiDataDir, "skills"),
3098
3304
  builtinRawSkills: [],
3099
3305
  contentHash: (content) => createHash("md5").update(content).digest("hex")
3100
3306
  })
@@ -3118,14 +3324,14 @@ var noop = () => {
3118
3324
  var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
3119
3325
 
3120
3326
  // ../../packages/sync/src/data-source-manager.ts
3121
- import * as fs8 from "fs";
3122
- import * as path11 from "path";
3327
+ import * as fs9 from "fs";
3328
+ import * as path12 from "path";
3123
3329
  import * as crypto from "crypto";
3124
3330
  var CONFIG_FILE = "data-sources.json";
3125
3331
  function generateId(prefix = "ds") {
3126
3332
  return `${prefix}_${crypto.randomBytes(6).toString("hex")}`;
3127
3333
  }
3128
- function normalizeBaseUrl(input) {
3334
+ function normalizeBaseUrl2(input) {
3129
3335
  let url = input.trim().replace(/\/+$/, "");
3130
3336
  if (url && !/^https?:\/\//i.test(url)) {
3131
3337
  url = `http://${url}`;
@@ -3147,17 +3353,17 @@ var DataSourceManager = class {
3147
3353
  this.logger = logger ?? NOOP_LOGGER;
3148
3354
  }
3149
3355
  getConfigPath() {
3150
- return path11.join(this.configDir, CONFIG_FILE);
3356
+ return path12.join(this.configDir, CONFIG_FILE);
3151
3357
  }
3152
3358
  ensureDir() {
3153
- fs8.mkdirSync(this.configDir, { recursive: true });
3359
+ fs9.mkdirSync(this.configDir, { recursive: true });
3154
3360
  }
3155
3361
  // ==================== Load / Save ====================
3156
3362
  loadAll() {
3157
3363
  try {
3158
3364
  const filePath = this.getConfigPath();
3159
- if (fs8.existsSync(filePath)) {
3160
- const raw = fs8.readFileSync(filePath, "utf-8");
3365
+ if (fs9.existsSync(filePath)) {
3366
+ const raw = fs9.readFileSync(filePath, "utf-8");
3161
3367
  const parsed = JSON.parse(raw);
3162
3368
  if (!isValidDataSourceArray(parsed)) {
3163
3369
  this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
@@ -3176,7 +3382,7 @@ var DataSourceManager = class {
3176
3382
  saveAll(sources) {
3177
3383
  try {
3178
3384
  this.ensureDir();
3179
- fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
3385
+ fs9.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
3180
3386
  } catch (err) {
3181
3387
  this.logger.error("[DataSource] Failed to save config", err);
3182
3388
  }
@@ -3190,7 +3396,7 @@ var DataSourceManager = class {
3190
3396
  const ds = {
3191
3397
  id: generateId("src"),
3192
3398
  name: partial.name || "",
3193
- baseUrl: normalizeBaseUrl(partial.baseUrl),
3399
+ baseUrl: normalizeBaseUrl2(partial.baseUrl),
3194
3400
  token: partial.token,
3195
3401
  intervalMinutes: partial.intervalMinutes,
3196
3402
  pullLimit: partial.pullLimit || 1e3,
@@ -3208,7 +3414,7 @@ var DataSourceManager = class {
3208
3414
  if (idx === -1) return null;
3209
3415
  const ds = sources[idx];
3210
3416
  if (updates.name !== void 0) ds.name = updates.name;
3211
- if (updates.baseUrl !== void 0) ds.baseUrl = normalizeBaseUrl(updates.baseUrl);
3417
+ if (updates.baseUrl !== void 0) ds.baseUrl = normalizeBaseUrl2(updates.baseUrl);
3212
3418
  if (updates.token !== void 0) ds.token = updates.token;
3213
3419
  if (updates.intervalMinutes !== void 0) ds.intervalMinutes = updates.intervalMinutes;
3214
3420
  if (updates.pullLimit !== void 0) ds.pullLimit = updates.pullLimit;
@@ -3269,42 +3475,13 @@ var DataSourceManager = class {
3269
3475
  }
3270
3476
  };
3271
3477
 
3272
- // ../../packages/sync/src/discovery.ts
3273
- function buildRemoteSessionsUrl(baseUrl, query = {}) {
3274
- const searchParams = new URLSearchParams();
3275
- searchParams.set("format", "chatlab");
3276
- if (query.keyword?.trim()) searchParams.set("keyword", query.keyword.trim());
3277
- if (query.limit && query.limit > 0) searchParams.set("limit", String(query.limit));
3278
- if (query.cursor) searchParams.set("cursor", query.cursor);
3279
- return `${baseUrl}/sessions?${searchParams.toString()}`;
3280
- }
3281
- function parseRemoteSessionsResponse(body) {
3282
- const parsed = JSON.parse(body);
3283
- let sessions;
3284
- let pageSource;
3285
- if (Array.isArray(parsed)) {
3286
- sessions = parsed;
3287
- } else if (parsed && typeof parsed === "object") {
3288
- sessions = parsed.sessions ?? parsed.data?.sessions ?? parsed.data ?? [];
3289
- if (!Array.isArray(sessions)) sessions = [];
3290
- pageSource = parsed.page ?? parsed.data?.page;
3291
- } else {
3292
- sessions = [];
3293
- }
3294
- return {
3295
- sessions,
3296
- page: pageSource && typeof pageSource === "object" ? {
3297
- hasMore: Boolean(pageSource.hasMore),
3298
- nextCursor: typeof pageSource.nextCursor === "string" ? pageSource.nextCursor : void 0
3299
- } : void 0
3300
- };
3301
- }
3302
-
3303
3478
  // ../../packages/sync/src/pull-engine.ts
3304
- import * as fs9 from "fs";
3479
+ import * as fs10 from "fs";
3305
3480
  import * as crypto2 from "crypto";
3306
3481
  var MAX_PAGES_PER_PULL = 5e3;
3307
3482
  var PULL_OVERLAP_SECONDS = 60;
3483
+ var SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS = 5 * 60 * 1e3;
3484
+ var SOURCE_UNAVAILABLE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "ECONNRESET", "ETIMEDOUT", "ABORT_ERR"]);
3308
3485
  function buildPullUrl(baseUrl, remoteSessionId, params) {
3309
3486
  const base = `${baseUrl}/sessions/${remoteSessionId}/messages`;
3310
3487
  const qs = ["format=chatlab"];
@@ -3321,7 +3498,7 @@ function parseSyncFromFile(filePath) {
3321
3498
  try {
3322
3499
  const isJsonl = filePath.endsWith(".jsonl");
3323
3500
  if (isJsonl) {
3324
- const content = fs9.readFileSync(filePath, "utf-8");
3501
+ const content = fs10.readFileSync(filePath, "utf-8");
3325
3502
  const lines = content.trimEnd().split("\n");
3326
3503
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
3327
3504
  try {
@@ -3340,7 +3517,7 @@ function parseSyncFromFile(filePath) {
3340
3517
  }
3341
3518
  return null;
3342
3519
  }
3343
- const raw = fs9.readFileSync(filePath, "utf-8");
3520
+ const raw = fs10.readFileSync(filePath, "utf-8");
3344
3521
  const parsed = JSON.parse(raw);
3345
3522
  if (parsed.sync && typeof parsed.sync === "object") {
3346
3523
  const s = parsed.sync;
@@ -3354,7 +3531,7 @@ function parseSyncFromFile(filePath) {
3354
3531
  function fileContainsMessages(filePath) {
3355
3532
  try {
3356
3533
  if (filePath.endsWith(".jsonl")) {
3357
- const content = fs9.readFileSync(filePath, "utf-8");
3534
+ const content = fs10.readFileSync(filePath, "utf-8");
3358
3535
  for (const line of content.split("\n")) {
3359
3536
  const trimmed = line.trim();
3360
3537
  if (!trimmed) continue;
@@ -3367,7 +3544,7 @@ function fileContainsMessages(filePath) {
3367
3544
  }
3368
3545
  return false;
3369
3546
  }
3370
- const raw = fs9.readFileSync(filePath, "utf-8");
3547
+ const raw = fs10.readFileSync(filePath, "utf-8");
3371
3548
  const parsed = JSON.parse(raw);
3372
3549
  return Array.isArray(parsed.messages) && parsed.messages.length > 0;
3373
3550
  } catch {
@@ -3384,7 +3561,7 @@ function getMaxMessageTimestampFromFile(filePath) {
3384
3561
  }
3385
3562
  };
3386
3563
  if (filePath.endsWith(".jsonl")) {
3387
- const content = fs9.readFileSync(filePath, "utf-8");
3564
+ const content = fs10.readFileSync(filePath, "utf-8");
3388
3565
  for (const line of content.split("\n")) {
3389
3566
  const trimmed = line.trim();
3390
3567
  if (!trimmed) continue;
@@ -3397,7 +3574,7 @@ function getMaxMessageTimestampFromFile(filePath) {
3397
3574
  }
3398
3575
  return maxTs;
3399
3576
  }
3400
- const raw = fs9.readFileSync(filePath, "utf-8");
3577
+ const raw = fs10.readFileSync(filePath, "utf-8");
3401
3578
  const parsed = JSON.parse(raw);
3402
3579
  if (Array.isArray(parsed.messages)) {
3403
3580
  for (const message of parsed.messages) {
@@ -3411,10 +3588,35 @@ function getMaxMessageTimestampFromFile(filePath) {
3411
3588
  }
3412
3589
  function cleanupTempFile(filePath) {
3413
3590
  try {
3414
- if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
3591
+ if (fs10.existsSync(filePath)) fs10.unlinkSync(filePath);
3415
3592
  } catch {
3416
3593
  }
3417
3594
  }
3595
+ function getErrorProp(error, prop) {
3596
+ return error && typeof error === "object" ? error[prop] : void 0;
3597
+ }
3598
+ function getErrorMessage(error) {
3599
+ return error instanceof Error ? error.message : String(error || "Pull failed");
3600
+ }
3601
+ function describeSourceUnavailableError(error) {
3602
+ let current = error;
3603
+ while (current && typeof current === "object") {
3604
+ const code = getErrorProp(current, "code");
3605
+ const name = getErrorProp(current, "name");
3606
+ if (typeof code === "string" && SOURCE_UNAVAILABLE_ERROR_CODES.has(code)) {
3607
+ const address = getErrorProp(current, "address");
3608
+ const port = getErrorProp(current, "port");
3609
+ if (typeof address === "string" && (typeof port === "number" || typeof port === "string")) {
3610
+ return `${code} ${address}:${port}`;
3611
+ }
3612
+ const host = getErrorProp(current, "hostname") ?? getErrorProp(current, "host");
3613
+ return typeof host === "string" ? `${code} ${host}` : code;
3614
+ }
3615
+ if (name === "AbortError") return "Request timeout";
3616
+ current = getErrorProp(current, "cause");
3617
+ }
3618
+ return null;
3619
+ }
3418
3620
  var PullEngine = class {
3419
3621
  fetcher;
3420
3622
  importer;
@@ -3425,6 +3627,7 @@ var PullEngine = class {
3425
3627
  onSessionImported;
3426
3628
  pullingSourceIds = /* @__PURE__ */ new Set();
3427
3629
  progressMap = /* @__PURE__ */ new Map();
3630
+ sourceUnavailableLogs = /* @__PURE__ */ new Map();
3428
3631
  constructor(options) {
3429
3632
  this.fetcher = options.fetcher;
3430
3633
  this.importer = options.importer;
@@ -3460,7 +3663,6 @@ var PullEngine = class {
3460
3663
  this.logger.info(`[Pull] Skipping "${sess.name}": import in progress`);
3461
3664
  return { success: false, newMessageCount: 0, error: "Import in progress" };
3462
3665
  }
3463
- this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
3464
3666
  let totalNewMessages = 0;
3465
3667
  let since = sess.lastPullAt;
3466
3668
  let offset;
@@ -3477,7 +3679,7 @@ var PullEngine = class {
3477
3679
  limit: ds.pullLimit
3478
3680
  });
3479
3681
  try {
3480
- const stat2 = fs9.statSync(tempFile);
3682
+ const stat2 = fs10.statSync(tempFile);
3481
3683
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
3482
3684
  const sync0 = parseSyncFromFile(tempFile);
3483
3685
  if (stat2.size < 1024) {
@@ -3497,7 +3699,7 @@ var PullEngine = class {
3497
3699
  offset,
3498
3700
  limit: ds.pullLimit
3499
3701
  });
3500
- const retryStat = fs9.statSync(retryFile);
3702
+ const retryStat = fs10.statSync(retryFile);
3501
3703
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
3502
3704
  const retrySync = parseSyncFromFile(retryFile);
3503
3705
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
@@ -3668,8 +3870,11 @@ var PullEngine = class {
3668
3870
  }
3669
3871
  return { success: true, newMessageCount: totalNewMessages };
3670
3872
  } catch (error) {
3671
- const errMsg = error.message || "Pull failed";
3672
- this.logger.error(`[Pull] Pull failed for "${sess.name}"`, error);
3873
+ const sourceUnavailable = describeSourceUnavailableError(error);
3874
+ const errMsg = sourceUnavailable ?? getErrorMessage(error);
3875
+ if (!sourceUnavailable) {
3876
+ this.logger.error(`[Pull] Pull failed for "${sess.name}": ${errMsg}`);
3877
+ }
3673
3878
  this.dsManager.updateSession(sourceId, sess.id, {
3674
3879
  lastPullAt: Math.floor(Date.now() / 1e3),
3675
3880
  lastStatus: "error",
@@ -3677,7 +3882,7 @@ var PullEngine = class {
3677
3882
  });
3678
3883
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3679
3884
  this.markProgressDone(sess.id);
3680
- return { success: false, newMessageCount: 0, error: errMsg };
3885
+ return { success: false, newMessageCount: 0, error: errMsg, sourceUnavailable: !!sourceUnavailable };
3681
3886
  }
3682
3887
  }
3683
3888
  markProgressDone(sessionId) {
@@ -3694,13 +3899,43 @@ var PullEngine = class {
3694
3899
  }
3695
3900
  this.pullingSourceIds.add(ds.id);
3696
3901
  try {
3697
- for (const sess of ds.sessions) {
3698
- await this.executePullSession(ds.id, ds, sess);
3902
+ let sourceUnavailable = false;
3903
+ for (let index = 0; index < ds.sessions.length; index++) {
3904
+ const sess = ds.sessions[index];
3905
+ const result = await this.executePullSession(ds.id, ds, sess);
3906
+ if (result.sourceUnavailable && result.error) {
3907
+ sourceUnavailable = true;
3908
+ this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
3909
+ this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
3910
+ break;
3911
+ }
3699
3912
  }
3913
+ if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
3700
3914
  } finally {
3701
3915
  this.pullingSourceIds.delete(ds.id);
3702
3916
  }
3703
3917
  }
3918
+ markRemainingSessionsSourceUnavailable(ds, startIndex, detail) {
3919
+ const now = Math.floor(Date.now() / 1e3);
3920
+ for (const sess of ds.sessions.slice(startIndex)) {
3921
+ this.dsManager.updateSession(ds.id, sess.id, {
3922
+ lastPullAt: now,
3923
+ lastStatus: "error",
3924
+ lastError: detail
3925
+ });
3926
+ this.notifier.onPullResult(ds.id, sess.id, "error", detail);
3927
+ this.markProgressDone(sess.id);
3928
+ }
3929
+ }
3930
+ logSourceUnavailable(ds, detail, affectedSessions) {
3931
+ const now = Date.now();
3932
+ const previous = this.sourceUnavailableLogs.get(ds.id);
3933
+ if (previous && previous.detail === detail && now - previous.lastLoggedAt < SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS) {
3934
+ return;
3935
+ }
3936
+ this.sourceUnavailableLogs.set(ds.id, { detail, lastLoggedAt: now });
3937
+ this.logger.warn(`[Pull] Source ${ds.baseUrl} unavailable: ${detail}, skipped ${affectedSessions} sessions`);
3938
+ }
3704
3939
  async triggerPull(sourceId, sessionId) {
3705
3940
  const ds = this.dsManager.get(sourceId);
3706
3941
  if (!ds) return { success: false, error: "Data source not found" };
@@ -3708,6 +3943,8 @@ var PullEngine = class {
3708
3943
  const sess = ds.sessions.find((s) => s.id === sessionId);
3709
3944
  if (!sess) return { success: false, error: "Session not found" };
3710
3945
  const result = await this.executePullSession(sourceId, ds, sess);
3946
+ if (result.sourceUnavailable && result.error) this.logSourceUnavailable(ds, result.error, 1);
3947
+ if (result.success) this.sourceUnavailableLogs.delete(ds.id);
3711
3948
  return { success: result.success, error: result.error };
3712
3949
  }
3713
3950
  if (this.pullingSourceIds.has(sourceId)) {
@@ -3717,10 +3954,19 @@ var PullEngine = class {
3717
3954
  this.pullingSourceIds.add(sourceId);
3718
3955
  try {
3719
3956
  const errors = [];
3720
- for (const sess of ds.sessions) {
3957
+ let sourceUnavailable = false;
3958
+ for (let index = 0; index < ds.sessions.length; index++) {
3959
+ const sess = ds.sessions[index];
3721
3960
  const result = await this.executePullSession(sourceId, ds, sess);
3722
3961
  if (!result.success && result.error) errors.push(`${sess.name}: ${result.error}`);
3962
+ if (result.sourceUnavailable && result.error) {
3963
+ sourceUnavailable = true;
3964
+ this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
3965
+ this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
3966
+ break;
3967
+ }
3723
3968
  }
3969
+ if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
3724
3970
  if (errors.length > 0) {
3725
3971
  return { success: false, error: errors.join("; ") };
3726
3972
  }
@@ -3801,13 +4047,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
3801
4047
  }
3802
4048
 
3803
4049
  // src/sync/adapters.ts
3804
- import * as fs10 from "fs";
4050
+ import * as fs11 from "fs";
3805
4051
  import * as os4 from "os";
3806
- import * as path12 from "path";
4052
+ import * as path13 from "path";
3807
4053
  import * as crypto3 from "crypto";
3808
4054
  function getTempFilePath(ext) {
3809
4055
  const id = crypto3.randomBytes(8).toString("hex");
3810
- return path12.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
4056
+ return path13.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3811
4057
  }
3812
4058
  var NodeFetcher = class {
3813
4059
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3824,7 +4070,7 @@ var NodeFetcher = class {
3824
4070
  const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
3825
4071
  const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
3826
4072
  const buffer = Buffer.from(await response.arrayBuffer());
3827
- fs10.writeFileSync(tempFile, buffer);
4073
+ fs11.writeFileSync(tempFile, buffer);
3828
4074
  return tempFile;
3829
4075
  }
3830
4076
  };
@@ -3837,7 +4083,7 @@ var DirectImporter = class {
3837
4083
  }
3838
4084
  sessionExists(sessionId) {
3839
4085
  const dbPath = this.dbManager.getDbPath(sessionId);
3840
- if (!fs10.existsSync(dbPath)) return false;
4086
+ if (!fs11.existsSync(dbPath)) return false;
3841
4087
  try {
3842
4088
  const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
3843
4089
  const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
@@ -3845,7 +4091,7 @@ var DirectImporter = class {
3845
4091
  if (row.cnt === 0) {
3846
4092
  this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3847
4093
  try {
3848
- fs10.unlinkSync(dbPath);
4094
+ fs11.unlinkSync(dbPath);
3849
4095
  } catch {
3850
4096
  }
3851
4097
  return false;
@@ -3855,7 +4101,7 @@ var DirectImporter = class {
3855
4101
  if (error instanceof DataDirCompatibilityError) throw error;
3856
4102
  this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3857
4103
  try {
3858
- fs10.unlinkSync(dbPath);
4104
+ fs11.unlinkSync(dbPath);
3859
4105
  } catch {
3860
4106
  }
3861
4107
  return false;
@@ -3914,92 +4160,6 @@ var NoopNotifier = class {
3914
4160
  onPullResult = noop2;
3915
4161
  };
3916
4162
 
3917
- // src/sync/routes.ts
3918
- function registerAutomationRoutes(server2, ctx) {
3919
- const { dsManager: dsManager2, pullEngine: pullEngine2, dbManager: dbManager2, serverInfo } = ctx;
3920
- server2.get("/_web/automation/config", async () => {
3921
- return {
3922
- enabled: true,
3923
- port: serverInfo.port,
3924
- token: serverInfo.token,
3925
- host: serverInfo.host
3926
- };
3927
- });
3928
- server2.get("/_web/automation/data-sources", async () => {
3929
- return dsManager2.loadAll();
3930
- });
3931
- server2.post("/_web/automation/data-sources", async (request) => {
3932
- const ds = dsManager2.add(request.body);
3933
- reloadTimer(ds.id);
3934
- return ds;
3935
- });
3936
- server2.patch("/_web/automation/data-sources/:id", async (request, reply) => {
3937
- const ds = dsManager2.update(request.params.id, request.body);
3938
- if (!ds) return reply.code(404).send({ error: "Data source not found" });
3939
- reloadTimer(ds.id);
3940
- return ds;
3941
- });
3942
- server2.delete("/_web/automation/data-sources/:id", async (request, reply) => {
3943
- stopTimer(request.params.id);
3944
- const ok = dsManager2.delete(request.params.id);
3945
- if (!ok) return reply.code(404).send({ error: "Data source not found" });
3946
- return { success: true };
3947
- });
3948
- server2.post("/_web/automation/data-sources/:id/sessions", async (request, reply) => {
3949
- const added = dsManager2.addSessions(request.params.id, request.body.sessions);
3950
- if (added.length === 0 && !dsManager2.get(request.params.id)) {
3951
- return reply.code(404).send({ error: "Data source not found" });
3952
- }
3953
- reloadTimer(request.params.id, true);
3954
- for (const sess of added) {
3955
- pullEngine2.triggerPull(request.params.id, sess.id).catch(() => {
3956
- });
3957
- }
3958
- return added;
3959
- });
3960
- server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
3961
- const removed = dsManager2.removeSession(request.params.id, request.params.sessId);
3962
- if (!removed) return reply.code(404).send({ error: "Session not found" });
3963
- reloadTimer(request.params.id);
3964
- if (request.query.deleteData === "true" && removed.targetSessionId) {
3965
- dbManager2.deleteSessionDatabaseFiles(removed.targetSessionId);
3966
- }
3967
- return { success: true };
3968
- });
3969
- server2.post(
3970
- "/_web/automation/data-sources/:id/pull",
3971
- async (request) => {
3972
- return pullEngine2.triggerPull(request.params.id, request.body?.sessionId);
3973
- }
3974
- );
3975
- server2.post("/_web/automation/data-sources/:id/pull-all", async (request) => {
3976
- return pullEngine2.triggerPullAll(request.params.id);
3977
- });
3978
- server2.get("/_web/automation/sync-progress", async () => {
3979
- return pullEngine2.getProgress();
3980
- });
3981
- server2.get("/_web/automation/remote-sessions", async (request, reply) => {
3982
- const { baseUrl, token, keyword, limit, cursor } = request.query;
3983
- if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
3984
- const normalizedUrl = normalizeBaseUrl(baseUrl);
3985
- const url = buildRemoteSessionsUrl(normalizedUrl, {
3986
- keyword,
3987
- limit: limit ? parseInt(limit, 10) : void 0,
3988
- cursor
3989
- });
3990
- const headers = { Accept: "application/json" };
3991
- if (token) headers["Authorization"] = `Bearer ${token}`;
3992
- try {
3993
- const resp = await fetch(url, { headers, signal: AbortSignal.timeout(3e4) });
3994
- if (!resp.ok) throw new Error(`Remote server returned HTTP ${resp.status}`);
3995
- const body = await resp.text();
3996
- return parseRemoteSessionsResponse(body);
3997
- } catch (err) {
3998
- return reply.code(502).send({ error: err.message || "Failed to fetch remote sessions" });
3999
- }
4000
- });
4001
- }
4002
-
4003
4163
  // src/sync/index.ts
4004
4164
  var syncLogger = {
4005
4165
  info: (msg) => console.log(`[Sync] ${msg}`),
@@ -4030,7 +4190,16 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
4030
4190
  }
4031
4191
  }
4032
4192
  });
4033
- registerAutomationRoutes(server2, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
4193
+ registerAutomationRoutes(server2, {
4194
+ automation: {
4195
+ dsManager,
4196
+ pullEngine,
4197
+ serverInfo,
4198
+ deleteSessionData: (sessionId) => dbManager2.deleteSessionDatabaseFiles(sessionId),
4199
+ reloadTimer,
4200
+ stopTimer
4201
+ }
4202
+ });
4034
4203
  initScheduler({
4035
4204
  dsManager,
4036
4205
  pullEngine,
@@ -4051,21 +4220,21 @@ function createFileConfigStorage(aiDataDir) {
4051
4220
  return {
4052
4221
  readJson(key) {
4053
4222
  try {
4054
- return JSON.parse(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
4223
+ return JSON.parse(fs12.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
4055
4224
  } catch {
4056
4225
  return null;
4057
4226
  }
4058
4227
  },
4059
4228
  writeJson(key, data) {
4060
- if (!fs11.existsSync(aiDataDir)) fs11.mkdirSync(aiDataDir, { recursive: true });
4061
- fs11.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
4229
+ if (!fs12.existsSync(aiDataDir)) fs12.mkdirSync(aiDataDir, { recursive: true });
4230
+ fs12.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
4062
4231
  }
4063
4232
  };
4064
4233
  }
4065
4234
  function resolveNativeBinding2() {
4066
4235
  if (process.versions.electron) return void 0;
4067
4236
  const nativePath = resolveCliPath("native/better_sqlite3.node");
4068
- if (fs11.existsSync(nativePath)) return nativePath;
4237
+ if (fs12.existsSync(nativePath)) return nativePath;
4069
4238
  return void 0;
4070
4239
  }
4071
4240
  function ensureToken(config) {
@@ -4134,6 +4303,7 @@ async function startHttpServer(options) {
4134
4303
  initAppLogger(pathProvider.getLogsDir());
4135
4304
  initServerAiLogger(pathProvider.getLogsDir());
4136
4305
  appLogger.info("server", `HTTP server starting on ${host}:${port}`);
4306
+ logNativeParserStatus();
4137
4307
  setAuthToken(token);
4138
4308
  setRequireAuth(!!(options?.requireAuth ?? config.api.require_auth));
4139
4309
  server = createServer();
@@ -4172,7 +4342,7 @@ async function startHttpServer(options) {
4172
4342
  }
4173
4343
  });
4174
4344
  initSync(server, dbManager, pathProvider, { port, host, token });
4175
- if (options?.webRoot && fs11.existsSync(options.webRoot)) {
4345
+ if (options?.webRoot && fs12.existsSync(options.webRoot)) {
4176
4346
  registerProxyRoutes(server);
4177
4347
  const fastifyStatic = await import("@fastify/static");
4178
4348
  await server.register(fastifyStatic.default, {
@@ -4213,4 +4383,4 @@ export {
4213
4383
  startHttpServer,
4214
4384
  stopHttpServer
4215
4385
  };
4216
- //# sourceMappingURL=chunk-KL2NGIMQ.mjs.map
4386
+ //# sourceMappingURL=chunk-D3DPCDII.mjs.map