chatlab-cli 0.29.0 → 0.30.0

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 (101) hide show
  1. package/dist/{chat-command-JKG3FY5B.mjs → chat-command-UZ23GKO7.mjs} +8 -8
  2. package/dist/{chunk-KL2NGIMQ.mjs → chunk-4IZ3WKYU.mjs} +429 -272
  3. package/dist/chunk-4IZ3WKYU.mjs.map +1 -0
  4. package/dist/{chunk-CIB23U32.mjs → chunk-5UBHNQFZ.mjs} +101 -41
  5. package/dist/chunk-5UBHNQFZ.mjs.map +1 -0
  6. package/dist/{chunk-WPI6YSAR.mjs → chunk-7BZLS2KW.mjs} +4114 -3962
  7. package/dist/chunk-7BZLS2KW.mjs.map +1 -0
  8. package/dist/{chunk-QQK7MLCR.mjs → chunk-A2SANKG4.mjs} +121 -32
  9. package/dist/chunk-A2SANKG4.mjs.map +1 -0
  10. package/dist/{chunk-D4WTE7LV.mjs → chunk-CNSYXRJP.mjs} +2 -2
  11. package/dist/{chunk-I35WHWA6.mjs → chunk-FCCL5Z4W.mjs} +3 -3
  12. package/dist/chunk-M55KW2HA.mjs +2323 -0
  13. package/dist/chunk-M55KW2HA.mjs.map +1 -0
  14. package/dist/{chunk-2MZ6G4AC.mjs → chunk-MUPILWAZ.mjs} +2 -2
  15. package/dist/{chunk-7RW4CQAO.mjs → chunk-P3ZOLTGS.mjs} +3 -3
  16. package/dist/{chunk-6FPEP2ZX.mjs → chunk-RDR6PS2R.mjs} +2 -2
  17. package/dist/{chunk-SKUG5XVD.mjs → chunk-ZFU2CHW3.mjs} +5 -4
  18. package/dist/cli.mjs +7 -7
  19. package/dist/contacts-worker.mjs +2 -2
  20. package/dist/{http-5E7ARYFW.mjs → http-UWRA5FLB.mjs} +11 -11
  21. package/dist/{import-3KG6VK6O.mjs → import-745KQL7N.mjs} +7 -7
  22. package/dist/index.mjs +11 -11
  23. package/dist/{mcp-JBI4CUQT.mjs → mcp-UHN5JOHC.mjs} +8 -8
  24. package/dist/people-relationships-worker.mjs +2 -2
  25. package/dist/semantic-index-worker.mjs +1 -1
  26. package/dist/{src-UTBQAOJ3.mjs → src-Y7MDRXLJ.mjs} +12 -6
  27. package/dist/{stream-import-7HZ4IRYJ.mjs → stream-import-3JMXQD7P.mjs} +7 -7
  28. package/dist/{worker-runtime-ALS4MR2A.mjs → worker-runtime-63RYMGZI.mjs} +5 -5
  29. package/dist-web/assets/{BufferResource-MsqP5sq3.js → BufferResource-BVdJ_I5X.js} +1 -1
  30. package/dist-web/assets/CanvasRenderer-BaRuT54R.js +1 -0
  31. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-N5CoiJzN.js +110 -0
  32. package/dist-web/assets/{Filter-wRJtdXx9.js → Filter-B7cNFtuE.js} +1 -1
  33. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-D_dJuLWJ.js +1 -0
  34. package/dist-web/assets/{MessageExportModal-BCrMf5Kw.js → MessageExportModal-B2Qm6VVj.js} +1 -1
  35. package/dist-web/assets/{PageHeader-C_yw54le.js → PageHeader-PjlWy9fC.js} +1 -1
  36. package/dist-web/assets/{RelationshipGalaxyCanvas-DTzJq7q7.js → RelationshipGalaxyCanvas-11MafciW.js} +3 -3
  37. package/dist-web/assets/{RelationshipGalaxyThreeCanvas-DIE-0_iM.js → RelationshipGalaxyThreeCanvas-C1I4B8NX.js} +1 -1
  38. package/dist-web/assets/{RenderTargetSystem-BSAQFamI.js → RenderTargetSystem-Chkz1WlI.js} +1 -1
  39. package/dist-web/assets/{WebGLRenderer-lfzmCZNT.js → WebGLRenderer-xK19ru58.js} +1 -1
  40. package/dist-web/assets/{WebGPURenderer-BnEdfdr_.js → WebGPURenderer-CL5ZaKsn.js} +1 -1
  41. package/dist-web/assets/{browserAll-gRNfDyPu.js → browserAll-CyyNzRD_.js} +1 -1
  42. package/dist-web/assets/electron-MXaTKaMu.js +1 -0
  43. package/dist-web/assets/{fetch-BW3aO7PS.js → fetch-D1dDiFO4.js} +1 -1
  44. package/dist-web/assets/{fetch-DPCJ-1l-.js → fetch-DDnVXGte.js} +1 -1
  45. package/dist-web/assets/{fetch-C1fI_KOX.js → fetch-DTwdRTXM.js} +1 -1
  46. package/dist-web/assets/{fetch-CQt-gb-f.js → fetch-DaaJwT-5.js} +1 -1
  47. package/dist-web/assets/{fetch-gMB4wuBd.js → fetch-DeA0ztwT.js} +1 -1
  48. package/dist-web/assets/fetch-DpnIGZSN.js +17 -0
  49. package/dist-web/assets/{fetch-CFRoSIAn.js → fetch-DsE-xcP2.js} +1 -1
  50. package/dist-web/assets/{fetch-mFlEBZyB.js → fetch-DsTgYu_X.js} +1 -1
  51. package/dist-web/assets/{fetch-CN91TIwV.js → fetch-hKE53UO4.js} +1 -1
  52. package/dist-web/assets/{fetch-BwF5hNuF.js → fetch-u9GTMhMF.js} +1 -1
  53. package/dist-web/assets/index-BVkE4qis.js +22 -0
  54. package/dist-web/assets/index-BcXLcFnV.css +1 -0
  55. package/dist-web/assets/{index-Bg4GGPXv.js → index-BnHKyESZ.js} +1 -1
  56. package/dist-web/assets/index-Btr5H7d-.js +2 -0
  57. package/dist-web/assets/{index-MBRD3wPd.css → index-C1p9CbmN.css} +1 -1
  58. package/dist-web/assets/index-COpza1A4.css +1 -0
  59. package/dist-web/assets/index-D04LWJop.js +173 -0
  60. package/dist-web/assets/index-DFCYmzGE.js +2 -0
  61. package/dist-web/assets/{index-DveHdPfK.js → index-DqXHRIhI.js} +1 -1
  62. package/dist-web/assets/{index-ZdCB7xcf.js → index-DqtXt3wl.js} +4 -4
  63. package/dist-web/assets/index-Dszv-QFp.js +64 -0
  64. package/dist-web/assets/index-DzLrWGXu.js +1 -0
  65. package/dist-web/assets/{vendor-nuxt-ui-Dr2T8_OZ.js → vendor-nuxt-ui-DaLxYaC4.js} +1 -1
  66. package/dist-web/assets/{web-BakeV58u.js → web-DPVWfPS-.js} +1 -1
  67. package/dist-web/assets/{webworkerAll-D_6PgURS.js → webworkerAll-DF5QV_vN.js} +1 -1
  68. package/dist-web/index.html +3 -3
  69. package/package.json +1 -1
  70. package/dist/chunk-ARZM6HBX.mjs +0 -542
  71. package/dist/chunk-ARZM6HBX.mjs.map +0 -1
  72. package/dist/chunk-CIB23U32.mjs.map +0 -1
  73. package/dist/chunk-KL2NGIMQ.mjs.map +0 -1
  74. package/dist/chunk-QQK7MLCR.mjs.map +0 -1
  75. package/dist/chunk-WPI6YSAR.mjs.map +0 -1
  76. package/dist-web/assets/CanvasRenderer-BKCvcc2p.js +0 -1
  77. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-HGJb0Et1.js +0 -110
  78. package/dist-web/assets/electron-BHaEaaZP.js +0 -1
  79. package/dist-web/assets/fetch-CBZbgZqk.js +0 -17
  80. package/dist-web/assets/index--6C0sK6a.css +0 -1
  81. package/dist-web/assets/index-B6wEGata.js +0 -173
  82. package/dist-web/assets/index-BgVbj5qX.js +0 -12
  83. package/dist-web/assets/index-BkoXaBV_.js +0 -64
  84. package/dist-web/assets/index-C56IuV5U.css +0 -1
  85. package/dist-web/assets/index-CLRaKwpT.js +0 -2
  86. package/dist-web/assets/index-Dj1BSD7O.js +0 -1
  87. package/dist-web/assets/index-RoDgJcHU.js +0 -2
  88. package/dist-web/assets/index-aljYO5ms.js +0 -1
  89. /package/dist/{chat-command-JKG3FY5B.mjs.map → chat-command-UZ23GKO7.mjs.map} +0 -0
  90. /package/dist/{chunk-D4WTE7LV.mjs.map → chunk-CNSYXRJP.mjs.map} +0 -0
  91. /package/dist/{chunk-I35WHWA6.mjs.map → chunk-FCCL5Z4W.mjs.map} +0 -0
  92. /package/dist/{chunk-2MZ6G4AC.mjs.map → chunk-MUPILWAZ.mjs.map} +0 -0
  93. /package/dist/{chunk-7RW4CQAO.mjs.map → chunk-P3ZOLTGS.mjs.map} +0 -0
  94. /package/dist/{chunk-6FPEP2ZX.mjs.map → chunk-RDR6PS2R.mjs.map} +0 -0
  95. /package/dist/{chunk-SKUG5XVD.mjs.map → chunk-ZFU2CHW3.mjs.map} +0 -0
  96. /package/dist/{http-5E7ARYFW.mjs.map → http-UWRA5FLB.mjs.map} +0 -0
  97. /package/dist/{import-3KG6VK6O.mjs.map → import-745KQL7N.mjs.map} +0 -0
  98. /package/dist/{mcp-JBI4CUQT.mjs.map → mcp-UHN5JOHC.mjs.map} +0 -0
  99. /package/dist/{src-UTBQAOJ3.mjs.map → src-Y7MDRXLJ.mjs.map} +0 -0
  100. /package/dist/{stream-import-7HZ4IRYJ.mjs.map → stream-import-3JMXQD7P.mjs.map} +0 -0
  101. /package/dist/{worker-runtime-ALS4MR2A.mjs.map → worker-runtime-63RYMGZI.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-FCCL5Z4W.mjs";
13
13
  import {
14
14
  closeServerAiLogger,
15
15
  createCliRunAgentStream,
16
+ getServerAiLogger,
16
17
  initServerAiLogger
17
- } from "./chunk-SKUG5XVD.mjs";
18
+ } from "./chunk-ZFU2CHW3.mjs";
18
19
  import {
19
20
  AGENT_TOOL_REGISTRY,
20
21
  CoreDataProvider
21
- } from "./chunk-CIB23U32.mjs";
22
+ } from "./chunk-5UBHNQFZ.mjs";
22
23
  import {
23
24
  assertCliDataDirCompatible
24
- } from "./chunk-6FPEP2ZX.mjs";
25
+ } from "./chunk-RDR6PS2R.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-P3ZOLTGS.mjs";
46
47
  import {
47
48
  AIChatManager,
48
49
  ArchiveImportError,
@@ -89,7 +90,7 @@ import {
89
90
  streamParseFileInfo,
90
91
  summary_service_exports,
91
92
  validateApiKey
92
- } from "./chunk-QQK7MLCR.mjs";
93
+ } from "./chunk-A2SANKG4.mjs";
93
94
  import {
94
95
  ALL_MIGRATIONS,
95
96
  BUILTIN_MODELS,
@@ -152,10 +153,10 @@ import {
152
153
  verifyCliDataPath,
153
154
  writeAuthProfile,
154
155
  writeConfigField
155
- } from "./chunk-WPI6YSAR.mjs";
156
+ } from "./chunk-7BZLS2KW.mjs";
156
157
 
157
158
  // src/http/index.ts
158
- import * as fs11 from "fs";
159
+ import * as fs12 from "fs";
159
160
  import * as crypto4 from "crypto";
160
161
 
161
162
  // src/http/server.ts
@@ -1097,7 +1098,14 @@ function registerSqlRoutes(server2, ctx) {
1097
1098
  return reply.code(400).send({ error: "Missing sql parameter" });
1098
1099
  }
1099
1100
  try {
1100
- return executeSql(db, sql, { timing: true });
1101
+ const result = executeSql(db, sql, { columnar: true, timing: true, maxRows: 0 });
1102
+ return {
1103
+ columns: result.columns,
1104
+ rows: result.rows,
1105
+ rowCount: result.rowCount,
1106
+ duration: result.duration ?? 0,
1107
+ limited: result.truncated
1108
+ };
1101
1109
  } catch (err) {
1102
1110
  const message = err instanceof Error ? err.message : "SQL execution error";
1103
1111
  return reply.code(400).send({ error: message });
@@ -1794,13 +1802,53 @@ function registerAiSummaryRoutes(server2, ctx) {
1794
1802
  });
1795
1803
  }
1796
1804
 
1797
- // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1805
+ // ../../packages/http-routes/src/routes/web/ai-logs.ts
1806
+ import fs3 from "fs";
1798
1807
  import path4 from "path";
1808
+ function findLatestAiLogPath(logsDir) {
1809
+ const aiLogDir = path4.join(logsDir, "ai");
1810
+ if (!fs3.existsSync(aiLogDir)) return null;
1811
+ const latest = fs3.readdirSync(aiLogDir).filter((name) => name.startsWith("ai_") && name.endsWith(".log")).map((name) => {
1812
+ const filePath = path4.join(aiLogDir, name);
1813
+ const stat2 = fs3.statSync(filePath);
1814
+ return { path: filePath, mtimeMs: stat2.mtimeMs };
1815
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
1816
+ return latest?.path ?? null;
1817
+ }
1818
+ function resolveAiLogPath(ctx) {
1819
+ const currentPath = ctx.getCurrentAiLogPath?.();
1820
+ if (currentPath && fs3.existsSync(currentPath)) return currentPath;
1821
+ return findLatestAiLogPath(ctx.pathProvider.getLogsDir());
1822
+ }
1823
+ function isLoopbackAddress(address) {
1824
+ if (!address) return true;
1825
+ const normalized = address.replace(/^::ffff:/, "");
1826
+ return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || normalized.startsWith("127.");
1827
+ }
1828
+ function registerAiLogRoutes(server2, ctx) {
1829
+ server2.post("/_web/ai/logs/show", async (request, reply) => {
1830
+ if (!ctx.showInFolder) return reply.code(501).send({ success: false, error: "Not supported" });
1831
+ if (!isLoopbackAddress(request.ip)) {
1832
+ return { success: false, error: "Opening AI logs is only supported on this device" };
1833
+ }
1834
+ const logPath = resolveAiLogPath(ctx);
1835
+ if (!logPath) return { success: false, error: "No AI log files found" };
1836
+ try {
1837
+ await ctx.showInFolder(logPath);
1838
+ return { success: true, path: logPath };
1839
+ } catch (error) {
1840
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1841
+ }
1842
+ });
1843
+ }
1844
+
1845
+ // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1846
+ import path5 from "path";
1799
1847
  function registerSemanticIndexRoutes(server2, ctx) {
1800
1848
  const service = ctx.semanticIndexService;
1801
1849
  if (!service) {
1802
1850
  if (ctx.aiDataDir) {
1803
- const configStore = new SemanticIndexConfigStore(path4.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1851
+ const configStore = new SemanticIndexConfigStore(path5.join(ctx.aiDataDir, SEMANTIC_INDEX_CONFIG_FILE));
1804
1852
  const configResponse = (config) => ({
1805
1853
  config,
1806
1854
  apiKeySet: resolveSemanticIndexApiKeySet(config, ctx.resolveApiKey),
@@ -1855,8 +1903,8 @@ function registerSemanticIndexRoutes(server2, ctx) {
1855
1903
  server2.post("/_web/ai/semantic-index/status", async (request) => {
1856
1904
  return { sessions: await service.statusForSessions(request.body.sessionIds ?? []) };
1857
1905
  });
1858
- const sessionAction = (path13, action) => {
1859
- server2.post(path13, async (request, reply) => {
1906
+ const sessionAction = (path14, action) => {
1907
+ server2.post(path14, async (request, reply) => {
1860
1908
  const { sessionId } = request.body;
1861
1909
  if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
1862
1910
  await action(sessionId);
@@ -1887,9 +1935,9 @@ function registerSemanticIndexRoutes(server2, ctx) {
1887
1935
  }
1888
1936
 
1889
1937
  // ../../packages/http-routes/src/routes/web/merge.ts
1890
- import * as fs3 from "fs";
1938
+ import * as fs4 from "fs";
1891
1939
  import * as os from "os";
1892
- import * as path5 from "path";
1940
+ import * as path6 from "path";
1893
1941
  function ensureDb2(ctx, sessionId) {
1894
1942
  const db = ctx.dbManager.open(sessionId);
1895
1943
  if (!db) throw sessionNotFound(sessionId);
@@ -1906,31 +1954,31 @@ function registerMergeRoutes(server2, ctx) {
1906
1954
  if (contentType.includes("multipart/form-data")) {
1907
1955
  const data = await request.file();
1908
1956
  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");
1957
+ const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merge-"));
1958
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1911
1959
  const chunks = [];
1912
1960
  for await (const chunk of data.file) {
1913
1961
  chunks.push(chunk);
1914
1962
  }
1915
- fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
1963
+ fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
1916
1964
  filePath = tmpPath;
1917
1965
  cleanupFile = true;
1918
1966
  cleanupDir = tmpDir;
1919
1967
  } else {
1920
1968
  const body = request.body;
1921
1969
  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" });
1970
+ if (!fs4.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1923
1971
  filePath = body.filePath;
1924
1972
  }
1925
1973
  try {
1926
1974
  const result = await streamParseFileInfo(filePath, {
1927
1975
  createTempDatabase(sourceFilePath) {
1928
- return mergeCache.createTempDatabase(path5.basename(sourceFilePath));
1976
+ return mergeCache.createTempDatabase(path6.basename(sourceFilePath));
1929
1977
  },
1930
1978
  onProgress() {
1931
1979
  }
1932
1980
  });
1933
- const filename = path5.basename(filePath);
1981
+ const filename = path6.basename(filePath);
1934
1982
  const handle = mergeCache.store(filename, result.tempDbPath);
1935
1983
  return {
1936
1984
  handle,
@@ -1944,13 +1992,13 @@ function registerMergeRoutes(server2, ctx) {
1944
1992
  } finally {
1945
1993
  if (cleanupFile) {
1946
1994
  try {
1947
- fs3.unlinkSync(filePath);
1995
+ fs4.unlinkSync(filePath);
1948
1996
  } catch {
1949
1997
  }
1950
1998
  }
1951
1999
  if (cleanupDir) {
1952
2000
  try {
1953
- fs3.rmdirSync(cleanupDir);
2001
+ fs4.rmdirSync(cleanupDir);
1954
2002
  } catch {
1955
2003
  }
1956
2004
  }
@@ -1999,20 +2047,20 @@ function registerMergeRoutes(server2, ctx) {
1999
2047
  return reply.code(501).send({ error: "Import not available on this server" });
2000
2048
  }
2001
2049
  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");
2050
+ const tmpDir = fs4.mkdtempSync(path6.join(os.tmpdir(), "chatlab-merged-"));
2051
+ const safeName = path6.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
2052
+ const tmpPath = path6.join(tmpDir, `${safeName}.json`);
2053
+ fs4.writeFileSync(tmpPath, jsonData, "utf-8");
2006
2054
  try {
2007
2055
  const importResult = await streamImport2(dbManager2, tmpPath);
2008
2056
  sessionId = importResult.sessionId;
2009
2057
  } finally {
2010
2058
  try {
2011
- fs3.unlinkSync(tmpPath);
2059
+ fs4.unlinkSync(tmpPath);
2012
2060
  } catch {
2013
2061
  }
2014
2062
  try {
2015
- fs3.rmdirSync(tmpDir);
2063
+ fs4.rmdirSync(tmpDir);
2016
2064
  } catch {
2017
2065
  }
2018
2066
  }
@@ -2086,16 +2134,16 @@ function registerMergeRoutes(server2, ctx) {
2086
2134
  }
2087
2135
 
2088
2136
  // ../../packages/http-routes/src/routes/web/cache.ts
2089
- import * as fs4 from "fs";
2137
+ import * as fs5 from "fs";
2090
2138
  import * as fsp from "fs/promises";
2091
- import * as path6 from "path";
2139
+ import * as path7 from "path";
2092
2140
  async function getDirSize(dirPath) {
2093
2141
  let totalSize = 0;
2094
2142
  try {
2095
- if (!fs4.existsSync(dirPath)) return 0;
2143
+ if (!fs5.existsSync(dirPath)) return 0;
2096
2144
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
2097
2145
  for (const file of files) {
2098
- const filePath = path6.join(dirPath, file.name);
2146
+ const filePath = path7.join(dirPath, file.name);
2099
2147
  if (file.isDirectory()) {
2100
2148
  totalSize += await getDirSize(filePath);
2101
2149
  } else {
@@ -2110,10 +2158,10 @@ async function getDirSize(dirPath) {
2110
2158
  async function getFileCount(dirPath) {
2111
2159
  let count = 0;
2112
2160
  try {
2113
- if (!fs4.existsSync(dirPath)) return 0;
2161
+ if (!fs5.existsSync(dirPath)) return 0;
2114
2162
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
2115
2163
  for (const file of files) {
2116
- const filePath = path6.join(dirPath, file.name);
2164
+ const filePath = path7.join(dirPath, file.name);
2117
2165
  if (file.isDirectory()) {
2118
2166
  count += await getFileCount(filePath);
2119
2167
  } else {
@@ -2126,10 +2174,10 @@ async function getFileCount(dirPath) {
2126
2174
  }
2127
2175
  function hasChatLabDatabases(dirPath) {
2128
2176
  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"));
2177
+ const markerPath = path7.join(dirPath, ".chatlab");
2178
+ const dbDir = path7.join(dirPath, "databases");
2179
+ if (!fs5.existsSync(markerPath) || !fs5.existsSync(dbDir)) return false;
2180
+ return fs5.readdirSync(dbDir).some((file) => file.endsWith(".db"));
2133
2181
  } catch {
2134
2182
  return false;
2135
2183
  }
@@ -2175,7 +2223,7 @@ function registerCacheRoutes(server2, ctx) {
2175
2223
  const results = await Promise.all(
2176
2224
  directories.map(async (dir) => {
2177
2225
  const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
2178
- return { ...dir, size, fileCount, exists: fs4.existsSync(dir.path) };
2226
+ return { ...dir, size, fileCount, exists: fs5.existsSync(dir.path) };
2179
2227
  })
2180
2228
  );
2181
2229
  return {
@@ -2192,10 +2240,10 @@ function registerCacheRoutes(server2, ctx) {
2192
2240
  };
2193
2241
  const dirPath = allowedDirs[cacheId];
2194
2242
  if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
2195
- if (!fs4.existsSync(dirPath)) return { success: true };
2243
+ if (!fs5.existsSync(dirPath)) return { success: true };
2196
2244
  const files = await fsp.readdir(dirPath);
2197
2245
  for (const file of files) {
2198
- const filePath = path6.join(dirPath, file);
2246
+ const filePath = path7.join(dirPath, file);
2199
2247
  const stat2 = await fsp.stat(filePath);
2200
2248
  if (stat2.isDirectory()) {
2201
2249
  await fsp.rm(filePath, { recursive: true });
@@ -2212,7 +2260,7 @@ function registerCacheRoutes(server2, ctx) {
2212
2260
  defaultPath: ctx.defaultUserDataDir,
2213
2261
  isCustom: ctx.isCustomDataDir ?? false,
2214
2262
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
2215
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path6.resolve(pp.getUserDataDir()) !== path6.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2263
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path7.resolve(pp.getUserDataDir()) !== path7.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2216
2264
  pendingMigration: pending ? {
2217
2265
  from: pending.from,
2218
2266
  to: pending.to,
@@ -2229,8 +2277,8 @@ function registerCacheRoutes(server2, ctx) {
2229
2277
  return ctx.setDataDir(targetPath, migrate);
2230
2278
  });
2231
2279
  server2.get("/_web/cache/latest-import-log", async () => {
2232
- const importLogDir = path6.join(pp.getLogsDir(), "import");
2233
- if (!fs4.existsSync(importLogDir)) {
2280
+ const importLogDir = path7.join(pp.getLogsDir(), "import");
2281
+ if (!fs5.existsSync(importLogDir)) {
2234
2282
  return { success: false, error: "Log directory not found" };
2235
2283
  }
2236
2284
  const files = await fsp.readdir(importLogDir);
@@ -2240,7 +2288,7 @@ function registerCacheRoutes(server2, ctx) {
2240
2288
  }
2241
2289
  const fileStats = await Promise.all(
2242
2290
  logFiles.map(async (f) => {
2243
- const filePath = path6.join(importLogDir, f);
2291
+ const filePath = path7.join(importLogDir, f);
2244
2292
  const stat2 = await fsp.stat(filePath);
2245
2293
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
2246
2294
  })
@@ -2264,11 +2312,11 @@ function registerCacheRoutes(server2, ctx) {
2264
2312
  const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
2265
2313
  buffer = Buffer.from(base64Data, "base64");
2266
2314
  }
2267
- if (!fs4.existsSync(downloadsDir)) {
2268
- fs4.mkdirSync(downloadsDir, { recursive: true });
2315
+ if (!fs5.existsSync(downloadsDir)) {
2316
+ fs5.mkdirSync(downloadsDir, { recursive: true });
2269
2317
  }
2270
- const filePath = path6.join(downloadsDir, filename);
2271
- fs4.writeFileSync(filePath, buffer);
2318
+ const filePath = path7.join(downloadsDir, filename);
2319
+ fs5.writeFileSync(filePath, buffer);
2272
2320
  return { success: true, filePath };
2273
2321
  });
2274
2322
  server2.post("/_web/cache/open-dir", async (request, reply) => {
@@ -2284,7 +2332,7 @@ function registerCacheRoutes(server2, ctx) {
2284
2332
  };
2285
2333
  const dirPath = dirPaths[cacheId];
2286
2334
  if (!dirPath) return { success: false, error: "Unknown directory" };
2287
- if (!fs4.existsSync(dirPath)) {
2335
+ if (!fs5.existsSync(dirPath)) {
2288
2336
  await fsp.mkdir(dirPath, { recursive: true });
2289
2337
  }
2290
2338
  try {
@@ -2384,6 +2432,7 @@ function registerSharedRoutes(server2, ctx, options) {
2384
2432
  registerAiToolRoutes(server2, resolvedCtx);
2385
2433
  registerAiChatRoutes(server2, resolvedCtx);
2386
2434
  registerAiSummaryRoutes(server2, resolvedCtx);
2435
+ registerAiLogRoutes(server2, resolvedCtx);
2387
2436
  registerSemanticIndexRoutes(server2, resolvedCtx);
2388
2437
  registerMergeRoutes(server2, resolvedCtx);
2389
2438
  registerCacheRoutes(server2, resolvedCtx);
@@ -2434,6 +2483,150 @@ function requireBearerToken(request, reply) {
2434
2483
  }
2435
2484
  }
2436
2485
 
2486
+ // ../../packages/http-routes/src/routes/web/automation.ts
2487
+ function requireAutomation(ctx) {
2488
+ return ctx.automation ?? null;
2489
+ }
2490
+ function normalizeBaseUrl(input) {
2491
+ let url = input.trim().replace(/\/+$/, "");
2492
+ if (url && !/^https?:\/\//i.test(url)) url = `http://${url}`;
2493
+ if (url && !url.endsWith("/api/v1")) {
2494
+ url = url.replace(/\/api\/v1$/, "") + "/api/v1";
2495
+ }
2496
+ return url;
2497
+ }
2498
+ function buildRemoteSessionsUrl(baseUrl, query = {}) {
2499
+ const searchParams = new URLSearchParams();
2500
+ searchParams.set("format", "chatlab");
2501
+ if (query.keyword?.trim()) searchParams.set("keyword", query.keyword.trim());
2502
+ if (query.limit && Number(query.limit) > 0) searchParams.set("limit", String(query.limit));
2503
+ if (query.cursor) searchParams.set("cursor", query.cursor);
2504
+ return `${baseUrl}/sessions?${searchParams.toString()}`;
2505
+ }
2506
+ function asRecord(value) {
2507
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2508
+ }
2509
+ function parseRemoteSessionsResponse(body) {
2510
+ const parsed = JSON.parse(body);
2511
+ let sessions;
2512
+ let pageSource;
2513
+ if (Array.isArray(parsed)) {
2514
+ sessions = parsed;
2515
+ } else {
2516
+ const record = asRecord(parsed);
2517
+ const data = asRecord(record?.data);
2518
+ const sessionsSource = record?.sessions ?? data?.sessions ?? record?.data;
2519
+ sessions = Array.isArray(sessionsSource) ? sessionsSource : [];
2520
+ pageSource = asRecord(record?.page) ?? asRecord(data?.page);
2521
+ }
2522
+ return {
2523
+ sessions,
2524
+ page: pageSource && typeof pageSource === "object" ? {
2525
+ hasMore: Boolean(pageSource.hasMore),
2526
+ nextCursor: typeof pageSource.nextCursor === "string" ? pageSource.nextCursor : void 0
2527
+ } : void 0
2528
+ };
2529
+ }
2530
+ function registerAutomationRoutes(server2, ctx) {
2531
+ server2.get("/_web/automation/config", async (_request, reply) => {
2532
+ const automation = requireAutomation(ctx);
2533
+ if (!automation?.serverInfo) return reply.code(404).send({ error: "Automation is not enabled" });
2534
+ return {
2535
+ enabled: true,
2536
+ port: automation.serverInfo.port,
2537
+ token: automation.serverInfo.token,
2538
+ host: automation.serverInfo.host
2539
+ };
2540
+ });
2541
+ server2.get("/_web/automation/data-sources", async (_request, reply) => {
2542
+ const automation = requireAutomation(ctx);
2543
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2544
+ return automation.dsManager.loadAll();
2545
+ });
2546
+ server2.post("/_web/automation/data-sources", async (request, reply) => {
2547
+ const automation = requireAutomation(ctx);
2548
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2549
+ const ds = automation.dsManager.add(request.body);
2550
+ automation.reloadTimer?.(ds.id);
2551
+ return ds;
2552
+ });
2553
+ server2.patch("/_web/automation/data-sources/:id", async (request, reply) => {
2554
+ const automation = requireAutomation(ctx);
2555
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2556
+ const ds = automation.dsManager.update(request.params.id, request.body);
2557
+ if (!ds) return reply.code(404).send({ error: "Data source not found" });
2558
+ automation.reloadTimer?.(ds.id);
2559
+ return ds;
2560
+ });
2561
+ server2.delete("/_web/automation/data-sources/:id", async (request, reply) => {
2562
+ const automation = requireAutomation(ctx);
2563
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2564
+ automation.stopTimer?.(request.params.id);
2565
+ const ok = automation.dsManager.delete(request.params.id);
2566
+ if (!ok) return reply.code(404).send({ error: "Data source not found" });
2567
+ return { success: true };
2568
+ });
2569
+ server2.post("/_web/automation/data-sources/:id/sessions", async (request, reply) => {
2570
+ const automation = requireAutomation(ctx);
2571
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2572
+ const added = automation.dsManager.addSessions(request.params.id, request.body.sessions);
2573
+ if (added.length === 0 && !automation.dsManager.get(request.params.id)) {
2574
+ return reply.code(404).send({ error: "Data source not found" });
2575
+ }
2576
+ automation.reloadTimer?.(request.params.id, true);
2577
+ for (const sess of added) {
2578
+ void automation.pullEngine.triggerPull(request.params.id, sess.id).catch(() => void 0);
2579
+ }
2580
+ return added;
2581
+ });
2582
+ server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
2583
+ const automation = requireAutomation(ctx);
2584
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2585
+ const removed = automation.dsManager.removeSession(request.params.id, request.params.sessId);
2586
+ if (!removed) return reply.code(404).send({ error: "Session not found" });
2587
+ automation.reloadTimer?.(request.params.id);
2588
+ if (request.query.deleteData === "true" && removed.targetSessionId) {
2589
+ automation.deleteSessionData?.(removed.targetSessionId);
2590
+ }
2591
+ return { success: true };
2592
+ });
2593
+ server2.post(
2594
+ "/_web/automation/data-sources/:id/pull",
2595
+ async (request, reply) => {
2596
+ const automation = requireAutomation(ctx);
2597
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2598
+ return automation.pullEngine.triggerPull(request.params.id, request.body?.sessionId);
2599
+ }
2600
+ );
2601
+ server2.post("/_web/automation/data-sources/:id/pull-all", async (request, reply) => {
2602
+ const automation = requireAutomation(ctx);
2603
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2604
+ return automation.pullEngine.triggerPullAll(request.params.id);
2605
+ });
2606
+ server2.get("/_web/automation/sync-progress", async (_request, reply) => {
2607
+ const automation = requireAutomation(ctx);
2608
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2609
+ return automation.pullEngine.getProgress();
2610
+ });
2611
+ server2.get("/_web/automation/remote-sessions", async (request, reply) => {
2612
+ const automation = requireAutomation(ctx);
2613
+ if (!automation) return reply.code(404).send({ error: "Automation is not enabled" });
2614
+ const { baseUrl, token, keyword, limit, cursor } = request.query;
2615
+ if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
2616
+ const url = buildRemoteSessionsUrl(normalizeBaseUrl(baseUrl), { keyword, limit, cursor });
2617
+ const headers = { Accept: "application/json" };
2618
+ if (token) headers.Authorization = `Bearer ${token}`;
2619
+ try {
2620
+ const resp = await fetch(url, { headers, signal: AbortSignal.timeout(3e4) });
2621
+ if (!resp.ok) throw new Error(`Remote server returned HTTP ${resp.status}`);
2622
+ const body = await resp.text();
2623
+ return parseRemoteSessionsResponse(body);
2624
+ } catch (err) {
2625
+ return reply.code(502).send({ error: err instanceof Error ? err.message : "Failed to fetch remote sessions" });
2626
+ }
2627
+ });
2628
+ }
2629
+
2437
2630
  // src/http/server.ts
2438
2631
  var JSON_BODY_LIMIT = 50 * 1024 * 1024;
2439
2632
  function createServer() {
@@ -2467,21 +2660,21 @@ function createServer() {
2467
2660
 
2468
2661
  // src/http/routes/web/index.ts
2469
2662
  import * as os3 from "os";
2470
- import * as path9 from "path";
2663
+ import * as path10 from "path";
2471
2664
 
2472
2665
  // src/http/routes/web/import.ts
2473
- import * as fs6 from "fs";
2666
+ import * as fs7 from "fs";
2474
2667
  import * as os2 from "os";
2475
- import * as path7 from "path";
2668
+ import * as path8 from "path";
2476
2669
  import { randomUUID } from "crypto";
2477
2670
  import { pipeline } from "stream/promises";
2478
2671
 
2479
2672
  // src/http/routes/web/helpers.ts
2480
- import * as fs5 from "fs";
2673
+ import * as fs6 from "fs";
2481
2674
  function resolveNativeBinding() {
2482
2675
  if (process.versions.electron) return void 0;
2483
2676
  const nativePath = resolveCliPath("native/better_sqlite3.node");
2484
- if (fs5.existsSync(nativePath)) return nativePath;
2677
+ if (fs6.existsSync(nativePath)) return nativePath;
2485
2678
  return void 0;
2486
2679
  }
2487
2680
 
@@ -2491,11 +2684,11 @@ var ARCHIVE_UPLOAD_LIMIT = 50 * 1024 * 1024 * 1024;
2491
2684
  function cleanupTemp(...paths) {
2492
2685
  for (const p of paths) {
2493
2686
  try {
2494
- const stat2 = fs6.statSync(p);
2687
+ const stat2 = fs7.statSync(p);
2495
2688
  if (stat2.isDirectory()) {
2496
- fs6.rmSync(p, { recursive: true, force: true });
2689
+ fs7.rmSync(p, { recursive: true, force: true });
2497
2690
  } else {
2498
- fs6.unlinkSync(p);
2691
+ fs7.unlinkSync(p);
2499
2692
  }
2500
2693
  } catch {
2501
2694
  }
@@ -2503,7 +2696,7 @@ function cleanupTemp(...paths) {
2503
2696
  }
2504
2697
  function registerImportRoutes2(server2, dbManager2, options = {}) {
2505
2698
  const sourceManager = options.sourceManager ?? new ArchiveImportSourceManager({
2506
- tempRoot: fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-import-sources-"))
2699
+ tempRoot: fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-sources-"))
2507
2700
  });
2508
2701
  const runPreparedImport = options.runPreparedImport ?? (async (manifestPath, onProgress) => {
2509
2702
  const result = await streamImport(dbManager2, manifestPath, {
@@ -2527,9 +2720,9 @@ function registerImportRoutes2(server2, dbManager2, options = {}) {
2527
2720
  limits: { fileSize: ARCHIVE_UPLOAD_LIMIT }
2528
2721
  });
2529
2722
  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`);
2723
+ const uploadPath = path8.join(os2.tmpdir(), `chatlab-archive-${randomUUID()}.zip`);
2531
2724
  try {
2532
- await pipeline(data.file, fs6.createWriteStream(uploadPath, { flags: "wx" }));
2725
+ await pipeline(data.file, fs7.createWriteStream(uploadPath, { flags: "wx" }));
2533
2726
  if (data.file.truncated) {
2534
2727
  cleanupTemp(uploadPath);
2535
2728
  return reply.code(413).send({ success: false, error: "error.archive_limit_exceeded" });
@@ -2594,14 +2787,14 @@ data: ${JSON.stringify(eventData)}
2594
2787
  server2.post("/_web/detect-format", async (request, reply) => {
2595
2788
  const data = await request.file();
2596
2789
  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");
2790
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-detect-"));
2791
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2599
2792
  try {
2600
2793
  const chunks = [];
2601
2794
  for await (const chunk of data.file) {
2602
2795
  chunks.push(chunk);
2603
2796
  }
2604
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2797
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2605
2798
  const format = detectFormat(tmpPath);
2606
2799
  const allFormats = detectAllFormats(tmpPath);
2607
2800
  return { format, allFormats };
@@ -2612,14 +2805,14 @@ data: ${JSON.stringify(eventData)}
2612
2805
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2613
2806
  const data = await request.file();
2614
2807
  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");
2808
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-scan-"));
2809
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2617
2810
  try {
2618
2811
  const chunks = [];
2619
2812
  for await (const chunk of data.file) {
2620
2813
  chunks.push(chunk);
2621
2814
  }
2622
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2815
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2623
2816
  const chats = await scanMultiChatFile(tmpPath);
2624
2817
  return { chats };
2625
2818
  } finally {
@@ -2629,13 +2822,13 @@ data: ${JSON.stringify(eventData)}
2629
2822
  server2.post("/_web/import", async (request, reply) => {
2630
2823
  const data = await request.file();
2631
2824
  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");
2825
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-import-"));
2826
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2634
2827
  const chunks = [];
2635
2828
  for await (const chunk of data.file) {
2636
2829
  chunks.push(chunk);
2637
2830
  }
2638
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2831
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2639
2832
  const formatId = data.fields?.formatId?.value;
2640
2833
  const chatIndexStr = data.fields?.chatIndex?.value;
2641
2834
  const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
@@ -2678,7 +2871,7 @@ data: ${JSON.stringify(eventData)}
2678
2871
  server2.post("/_web/import-directory", async (request, reply) => {
2679
2872
  const parts = request.parts();
2680
2873
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2681
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-dir-import-"));
2874
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-dir-import-"));
2682
2875
  const relativePaths = [];
2683
2876
  const fileBuffers = [];
2684
2877
  try {
@@ -2706,10 +2899,10 @@ data: ${JSON.stringify(eventData)}
2706
2899
  if (segments.length > 1) {
2707
2900
  relPath = segments.slice(1).join("/");
2708
2901
  }
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);
2902
+ const targetPath = path8.resolve(tmpDir, relPath);
2903
+ if (!targetPath.startsWith(tmpDir + path8.sep)) continue;
2904
+ fs7.mkdirSync(path8.dirname(targetPath), { recursive: true });
2905
+ fs7.writeFileSync(targetPath, fileBuffers[i].data);
2713
2906
  }
2714
2907
  const entryPath = findEntryFileInDirectory(tmpDir);
2715
2908
  if (!entryPath) {
@@ -2754,13 +2947,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2754
2947
  const sessionId = request.params.id;
2755
2948
  const data = await request.file();
2756
2949
  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");
2950
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-inc-"));
2951
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2759
2952
  const chunks = [];
2760
2953
  for await (const chunk of data.file) {
2761
2954
  chunks.push(chunk);
2762
2955
  }
2763
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2956
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2764
2957
  reply.raw.writeHead(200, {
2765
2958
  "Content-Type": "text/event-stream",
2766
2959
  "Cache-Control": "no-cache",
@@ -2792,13 +2985,13 @@ data: ${JSON.stringify(eventData)}
2792
2985
  const sessionId = request.params.id;
2793
2986
  const data = await request.file();
2794
2987
  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");
2988
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
2989
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2797
2990
  const chunks = [];
2798
2991
  for await (const chunk of data.file) {
2799
2992
  chunks.push(chunk);
2800
2993
  }
2801
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2994
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2802
2995
  try {
2803
2996
  return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
2804
2997
  } catch (err) {
@@ -2810,13 +3003,13 @@ data: ${JSON.stringify(eventData)}
2810
3003
  server2.post("/_web/import/analyze", async (request, reply) => {
2811
3004
  const data = await request.file();
2812
3005
  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");
3006
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-analyze-"));
3007
+ const tmpPath = path8.join(tmpDir, data.filename || "upload");
2815
3008
  const chunks = [];
2816
3009
  for await (const chunk of data.file) {
2817
3010
  chunks.push(chunk);
2818
3011
  }
2819
- fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
3012
+ fs7.writeFileSync(tmpPath, Buffer.concat(chunks));
2820
3013
  try {
2821
3014
  return await analyzeNewImport(tmpPath);
2822
3015
  } catch (err) {
@@ -2846,17 +3039,17 @@ data: ${JSON.stringify(eventData)}
2846
3039
 
2847
3040
  `);
2848
3041
  }
2849
- const tmpDir = fs6.mkdtempSync(path7.join(os2.tmpdir(), "chatlab-demo-"));
3042
+ const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "chatlab-demo-"));
2850
3043
  try {
2851
3044
  const localPaths = [];
2852
3045
  for (let i = 0; i < total; i++) {
2853
3046
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2854
- const localPath = path7.join(tmpDir, DEMO_FILES[i]);
3047
+ const localPath = path8.join(tmpDir, DEMO_FILES[i]);
2855
3048
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2856
3049
  signal: AbortSignal.timeout(6e4)
2857
3050
  });
2858
3051
  if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
2859
- fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
3052
+ fs7.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2860
3053
  localPaths.push(localPath);
2861
3054
  }
2862
3055
  sendEvent("progress", { stage: "importing", current: 1, total });
@@ -2887,7 +3080,7 @@ data: ${JSON.stringify(eventData)}
2887
3080
  }
2888
3081
 
2889
3082
  // src/http/routes/web/cache.ts
2890
- import * as path8 from "path";
3083
+ import * as path9 from "path";
2891
3084
  import { execFile } from "child_process";
2892
3085
  function runExecFile(file, args, runner = execFile) {
2893
3086
  return new Promise((resolve4, reject) => {
@@ -2915,7 +3108,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2915
3108
  } else if (platform === "win32") {
2916
3109
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2917
3110
  } else {
2918
- await runExecFile("xdg-open", [path8.dirname(filePath)], runner);
3111
+ await runExecFile("xdg-open", [path9.dirname(filePath)], runner);
2919
3112
  }
2920
3113
  }
2921
3114
 
@@ -2938,27 +3131,27 @@ function registerWebRoutes(server2, dbManager2, options) {
2938
3131
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2939
3132
  mergeCache?.cleanupOrphans();
2940
3133
  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")
3134
+ getSystemDir: () => path10.join(os3.homedir(), ".chatlab"),
3135
+ getUserDataDir: () => path10.join(os3.homedir(), ".chatlab", "data"),
3136
+ getDatabaseDir: () => path10.join(os3.homedir(), ".chatlab", "data", "databases"),
3137
+ getVectorDir: () => path10.join(os3.homedir(), ".chatlab", "data", "vector"),
3138
+ getAiDataDir: () => path10.join(os3.homedir(), ".chatlab", "ai"),
3139
+ getSettingsDir: () => path10.join(os3.homedir(), ".chatlab", "settings"),
3140
+ getCacheDir: () => path10.join(os3.homedir(), ".chatlab", "cache"),
3141
+ getTempDir: () => path10.join(os3.homedir(), ".chatlab", "temp"),
3142
+ getLogsDir: () => path10.join(os3.homedir(), ".chatlab", "logs"),
3143
+ getDownloadsDir: () => path10.join(os3.homedir(), "Downloads")
2951
3144
  };
2952
3145
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2953
3146
  const ai = options?.aiContext;
2954
3147
  const cliStreamImport = async (dm, filePath) => {
2955
- const { streamImport: streamImport2 } = await import("./stream-import-7HZ4IRYJ.mjs");
3148
+ const { streamImport: streamImport2 } = await import("./stream-import-3JMXQD7P.mjs");
2956
3149
  const result = await streamImport2(dm, filePath);
2957
3150
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2958
3151
  return { sessionId: result.sessionId };
2959
3152
  };
2960
3153
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2961
- const isCustom = path9.resolve(resolvedPathProvider.getUserDataDir()) !== path9.resolve(defaultUserDataDir);
3154
+ const isCustom = path10.resolve(resolvedPathProvider.getUserDataDir()) !== path10.resolve(defaultUserDataDir);
2962
3155
  const semanticIndexService = options?.semanticIndexService;
2963
3156
  const analyticsService = "A-US-0629338532" ? new AnalyticsService(resolvedPathProvider.getSystemDir(), "A-US-0629338532", getVersion()) : void 0;
2964
3157
  registerSharedRoutes(
@@ -2972,6 +3165,7 @@ function registerWebRoutes(server2, dbManager2, options) {
2972
3165
  nativeBinding: options?.nativeBinding,
2973
3166
  semanticIndexService,
2974
3167
  analyticsService,
3168
+ getCurrentAiLogPath: () => getServerAiLogger()?.getExistingLogPath() ?? null,
2975
3169
  openDirectory: openDirectoryPath,
2976
3170
  showInFolder: showPathInFolder,
2977
3171
  downloadsDir: resolvedPathProvider.getDownloadsDir(),
@@ -3054,31 +3248,31 @@ function registerProxyRoutes(server2) {
3054
3248
  }
3055
3249
 
3056
3250
  // src/ai/manager-factory.ts
3057
- import * as fs7 from "fs";
3058
- import * as path10 from "path";
3251
+ import * as fs8 from "fs";
3252
+ import * as path11 from "path";
3059
3253
  import { createHash, randomUUID as randomUUID2 } from "crypto";
3060
3254
  var nodeFs = {
3061
3255
  ensureDir(dir) {
3062
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
3256
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
3063
3257
  },
3064
3258
  listFiles(dir, ext) {
3065
- if (!fs7.existsSync(dir)) return [];
3066
- return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
3259
+ if (!fs8.existsSync(dir)) return [];
3260
+ return fs8.readdirSync(dir).filter((f) => f.endsWith(ext));
3067
3261
  },
3068
3262
  readFile(filePath) {
3069
- return fs7.readFileSync(filePath, "utf-8");
3263
+ return fs8.readFileSync(filePath, "utf-8");
3070
3264
  },
3071
3265
  writeFile(filePath, content) {
3072
- fs7.writeFileSync(filePath, content, "utf-8");
3266
+ fs8.writeFileSync(filePath, content, "utf-8");
3073
3267
  },
3074
3268
  deleteFile(filePath) {
3075
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
3269
+ if (fs8.existsSync(filePath)) fs8.unlinkSync(filePath);
3076
3270
  },
3077
3271
  fileExists(filePath) {
3078
- return fs7.existsSync(filePath);
3272
+ return fs8.existsSync(filePath);
3079
3273
  },
3080
3274
  joinPath(...parts) {
3081
- return path10.join(...parts);
3275
+ return path11.join(...parts);
3082
3276
  }
3083
3277
  };
3084
3278
  var managerCache = /* @__PURE__ */ new Map();
@@ -3088,13 +3282,13 @@ function getAssistantManager(aiDataDir) {
3088
3282
  cached = {
3089
3283
  assistant: new AssistantManager({
3090
3284
  fs: nodeFs,
3091
- assistantsDir: path10.join(aiDataDir, "assistants"),
3285
+ assistantsDir: path11.join(aiDataDir, "assistants"),
3092
3286
  builtinRawConfigs: [],
3093
3287
  generateId: () => `custom_${randomUUID2().replace(/-/g, "").slice(0, 12)}`
3094
3288
  }),
3095
3289
  skill: new SkillManagerCore({
3096
3290
  fs: nodeFs,
3097
- skillsDir: path10.join(aiDataDir, "skills"),
3291
+ skillsDir: path11.join(aiDataDir, "skills"),
3098
3292
  builtinRawSkills: [],
3099
3293
  contentHash: (content) => createHash("md5").update(content).digest("hex")
3100
3294
  })
@@ -3118,14 +3312,14 @@ var noop = () => {
3118
3312
  var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
3119
3313
 
3120
3314
  // ../../packages/sync/src/data-source-manager.ts
3121
- import * as fs8 from "fs";
3122
- import * as path11 from "path";
3315
+ import * as fs9 from "fs";
3316
+ import * as path12 from "path";
3123
3317
  import * as crypto from "crypto";
3124
3318
  var CONFIG_FILE = "data-sources.json";
3125
3319
  function generateId(prefix = "ds") {
3126
3320
  return `${prefix}_${crypto.randomBytes(6).toString("hex")}`;
3127
3321
  }
3128
- function normalizeBaseUrl(input) {
3322
+ function normalizeBaseUrl2(input) {
3129
3323
  let url = input.trim().replace(/\/+$/, "");
3130
3324
  if (url && !/^https?:\/\//i.test(url)) {
3131
3325
  url = `http://${url}`;
@@ -3147,17 +3341,17 @@ var DataSourceManager = class {
3147
3341
  this.logger = logger ?? NOOP_LOGGER;
3148
3342
  }
3149
3343
  getConfigPath() {
3150
- return path11.join(this.configDir, CONFIG_FILE);
3344
+ return path12.join(this.configDir, CONFIG_FILE);
3151
3345
  }
3152
3346
  ensureDir() {
3153
- fs8.mkdirSync(this.configDir, { recursive: true });
3347
+ fs9.mkdirSync(this.configDir, { recursive: true });
3154
3348
  }
3155
3349
  // ==================== Load / Save ====================
3156
3350
  loadAll() {
3157
3351
  try {
3158
3352
  const filePath = this.getConfigPath();
3159
- if (fs8.existsSync(filePath)) {
3160
- const raw = fs8.readFileSync(filePath, "utf-8");
3353
+ if (fs9.existsSync(filePath)) {
3354
+ const raw = fs9.readFileSync(filePath, "utf-8");
3161
3355
  const parsed = JSON.parse(raw);
3162
3356
  if (!isValidDataSourceArray(parsed)) {
3163
3357
  this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
@@ -3176,7 +3370,7 @@ var DataSourceManager = class {
3176
3370
  saveAll(sources) {
3177
3371
  try {
3178
3372
  this.ensureDir();
3179
- fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
3373
+ fs9.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
3180
3374
  } catch (err) {
3181
3375
  this.logger.error("[DataSource] Failed to save config", err);
3182
3376
  }
@@ -3190,7 +3384,7 @@ var DataSourceManager = class {
3190
3384
  const ds = {
3191
3385
  id: generateId("src"),
3192
3386
  name: partial.name || "",
3193
- baseUrl: normalizeBaseUrl(partial.baseUrl),
3387
+ baseUrl: normalizeBaseUrl2(partial.baseUrl),
3194
3388
  token: partial.token,
3195
3389
  intervalMinutes: partial.intervalMinutes,
3196
3390
  pullLimit: partial.pullLimit || 1e3,
@@ -3208,7 +3402,7 @@ var DataSourceManager = class {
3208
3402
  if (idx === -1) return null;
3209
3403
  const ds = sources[idx];
3210
3404
  if (updates.name !== void 0) ds.name = updates.name;
3211
- if (updates.baseUrl !== void 0) ds.baseUrl = normalizeBaseUrl(updates.baseUrl);
3405
+ if (updates.baseUrl !== void 0) ds.baseUrl = normalizeBaseUrl2(updates.baseUrl);
3212
3406
  if (updates.token !== void 0) ds.token = updates.token;
3213
3407
  if (updates.intervalMinutes !== void 0) ds.intervalMinutes = updates.intervalMinutes;
3214
3408
  if (updates.pullLimit !== void 0) ds.pullLimit = updates.pullLimit;
@@ -3269,42 +3463,13 @@ var DataSourceManager = class {
3269
3463
  }
3270
3464
  };
3271
3465
 
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
3466
  // ../../packages/sync/src/pull-engine.ts
3304
- import * as fs9 from "fs";
3467
+ import * as fs10 from "fs";
3305
3468
  import * as crypto2 from "crypto";
3306
3469
  var MAX_PAGES_PER_PULL = 5e3;
3307
3470
  var PULL_OVERLAP_SECONDS = 60;
3471
+ var SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS = 5 * 60 * 1e3;
3472
+ var SOURCE_UNAVAILABLE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "ECONNRESET", "ETIMEDOUT", "ABORT_ERR"]);
3308
3473
  function buildPullUrl(baseUrl, remoteSessionId, params) {
3309
3474
  const base = `${baseUrl}/sessions/${remoteSessionId}/messages`;
3310
3475
  const qs = ["format=chatlab"];
@@ -3321,7 +3486,7 @@ function parseSyncFromFile(filePath) {
3321
3486
  try {
3322
3487
  const isJsonl = filePath.endsWith(".jsonl");
3323
3488
  if (isJsonl) {
3324
- const content = fs9.readFileSync(filePath, "utf-8");
3489
+ const content = fs10.readFileSync(filePath, "utf-8");
3325
3490
  const lines = content.trimEnd().split("\n");
3326
3491
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
3327
3492
  try {
@@ -3340,7 +3505,7 @@ function parseSyncFromFile(filePath) {
3340
3505
  }
3341
3506
  return null;
3342
3507
  }
3343
- const raw = fs9.readFileSync(filePath, "utf-8");
3508
+ const raw = fs10.readFileSync(filePath, "utf-8");
3344
3509
  const parsed = JSON.parse(raw);
3345
3510
  if (parsed.sync && typeof parsed.sync === "object") {
3346
3511
  const s = parsed.sync;
@@ -3354,7 +3519,7 @@ function parseSyncFromFile(filePath) {
3354
3519
  function fileContainsMessages(filePath) {
3355
3520
  try {
3356
3521
  if (filePath.endsWith(".jsonl")) {
3357
- const content = fs9.readFileSync(filePath, "utf-8");
3522
+ const content = fs10.readFileSync(filePath, "utf-8");
3358
3523
  for (const line of content.split("\n")) {
3359
3524
  const trimmed = line.trim();
3360
3525
  if (!trimmed) continue;
@@ -3367,7 +3532,7 @@ function fileContainsMessages(filePath) {
3367
3532
  }
3368
3533
  return false;
3369
3534
  }
3370
- const raw = fs9.readFileSync(filePath, "utf-8");
3535
+ const raw = fs10.readFileSync(filePath, "utf-8");
3371
3536
  const parsed = JSON.parse(raw);
3372
3537
  return Array.isArray(parsed.messages) && parsed.messages.length > 0;
3373
3538
  } catch {
@@ -3384,7 +3549,7 @@ function getMaxMessageTimestampFromFile(filePath) {
3384
3549
  }
3385
3550
  };
3386
3551
  if (filePath.endsWith(".jsonl")) {
3387
- const content = fs9.readFileSync(filePath, "utf-8");
3552
+ const content = fs10.readFileSync(filePath, "utf-8");
3388
3553
  for (const line of content.split("\n")) {
3389
3554
  const trimmed = line.trim();
3390
3555
  if (!trimmed) continue;
@@ -3397,7 +3562,7 @@ function getMaxMessageTimestampFromFile(filePath) {
3397
3562
  }
3398
3563
  return maxTs;
3399
3564
  }
3400
- const raw = fs9.readFileSync(filePath, "utf-8");
3565
+ const raw = fs10.readFileSync(filePath, "utf-8");
3401
3566
  const parsed = JSON.parse(raw);
3402
3567
  if (Array.isArray(parsed.messages)) {
3403
3568
  for (const message of parsed.messages) {
@@ -3411,10 +3576,35 @@ function getMaxMessageTimestampFromFile(filePath) {
3411
3576
  }
3412
3577
  function cleanupTempFile(filePath) {
3413
3578
  try {
3414
- if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
3579
+ if (fs10.existsSync(filePath)) fs10.unlinkSync(filePath);
3415
3580
  } catch {
3416
3581
  }
3417
3582
  }
3583
+ function getErrorProp(error, prop) {
3584
+ return error && typeof error === "object" ? error[prop] : void 0;
3585
+ }
3586
+ function getErrorMessage(error) {
3587
+ return error instanceof Error ? error.message : String(error || "Pull failed");
3588
+ }
3589
+ function describeSourceUnavailableError(error) {
3590
+ let current = error;
3591
+ while (current && typeof current === "object") {
3592
+ const code = getErrorProp(current, "code");
3593
+ const name = getErrorProp(current, "name");
3594
+ if (typeof code === "string" && SOURCE_UNAVAILABLE_ERROR_CODES.has(code)) {
3595
+ const address = getErrorProp(current, "address");
3596
+ const port = getErrorProp(current, "port");
3597
+ if (typeof address === "string" && (typeof port === "number" || typeof port === "string")) {
3598
+ return `${code} ${address}:${port}`;
3599
+ }
3600
+ const host = getErrorProp(current, "hostname") ?? getErrorProp(current, "host");
3601
+ return typeof host === "string" ? `${code} ${host}` : code;
3602
+ }
3603
+ if (name === "AbortError") return "Request timeout";
3604
+ current = getErrorProp(current, "cause");
3605
+ }
3606
+ return null;
3607
+ }
3418
3608
  var PullEngine = class {
3419
3609
  fetcher;
3420
3610
  importer;
@@ -3425,6 +3615,7 @@ var PullEngine = class {
3425
3615
  onSessionImported;
3426
3616
  pullingSourceIds = /* @__PURE__ */ new Set();
3427
3617
  progressMap = /* @__PURE__ */ new Map();
3618
+ sourceUnavailableLogs = /* @__PURE__ */ new Map();
3428
3619
  constructor(options) {
3429
3620
  this.fetcher = options.fetcher;
3430
3621
  this.importer = options.importer;
@@ -3460,7 +3651,6 @@ var PullEngine = class {
3460
3651
  this.logger.info(`[Pull] Skipping "${sess.name}": import in progress`);
3461
3652
  return { success: false, newMessageCount: 0, error: "Import in progress" };
3462
3653
  }
3463
- this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
3464
3654
  let totalNewMessages = 0;
3465
3655
  let since = sess.lastPullAt;
3466
3656
  let offset;
@@ -3477,7 +3667,7 @@ var PullEngine = class {
3477
3667
  limit: ds.pullLimit
3478
3668
  });
3479
3669
  try {
3480
- const stat2 = fs9.statSync(tempFile);
3670
+ const stat2 = fs10.statSync(tempFile);
3481
3671
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
3482
3672
  const sync0 = parseSyncFromFile(tempFile);
3483
3673
  if (stat2.size < 1024) {
@@ -3497,7 +3687,7 @@ var PullEngine = class {
3497
3687
  offset,
3498
3688
  limit: ds.pullLimit
3499
3689
  });
3500
- const retryStat = fs9.statSync(retryFile);
3690
+ const retryStat = fs10.statSync(retryFile);
3501
3691
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
3502
3692
  const retrySync = parseSyncFromFile(retryFile);
3503
3693
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
@@ -3668,8 +3858,11 @@ var PullEngine = class {
3668
3858
  }
3669
3859
  return { success: true, newMessageCount: totalNewMessages };
3670
3860
  } catch (error) {
3671
- const errMsg = error.message || "Pull failed";
3672
- this.logger.error(`[Pull] Pull failed for "${sess.name}"`, error);
3861
+ const sourceUnavailable = describeSourceUnavailableError(error);
3862
+ const errMsg = sourceUnavailable ?? getErrorMessage(error);
3863
+ if (!sourceUnavailable) {
3864
+ this.logger.error(`[Pull] Pull failed for "${sess.name}": ${errMsg}`);
3865
+ }
3673
3866
  this.dsManager.updateSession(sourceId, sess.id, {
3674
3867
  lastPullAt: Math.floor(Date.now() / 1e3),
3675
3868
  lastStatus: "error",
@@ -3677,7 +3870,7 @@ var PullEngine = class {
3677
3870
  });
3678
3871
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3679
3872
  this.markProgressDone(sess.id);
3680
- return { success: false, newMessageCount: 0, error: errMsg };
3873
+ return { success: false, newMessageCount: 0, error: errMsg, sourceUnavailable: !!sourceUnavailable };
3681
3874
  }
3682
3875
  }
3683
3876
  markProgressDone(sessionId) {
@@ -3694,13 +3887,43 @@ var PullEngine = class {
3694
3887
  }
3695
3888
  this.pullingSourceIds.add(ds.id);
3696
3889
  try {
3697
- for (const sess of ds.sessions) {
3698
- await this.executePullSession(ds.id, ds, sess);
3890
+ let sourceUnavailable = false;
3891
+ for (let index = 0; index < ds.sessions.length; index++) {
3892
+ const sess = ds.sessions[index];
3893
+ const result = await this.executePullSession(ds.id, ds, sess);
3894
+ if (result.sourceUnavailable && result.error) {
3895
+ sourceUnavailable = true;
3896
+ this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
3897
+ this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
3898
+ break;
3899
+ }
3699
3900
  }
3901
+ if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
3700
3902
  } finally {
3701
3903
  this.pullingSourceIds.delete(ds.id);
3702
3904
  }
3703
3905
  }
3906
+ markRemainingSessionsSourceUnavailable(ds, startIndex, detail) {
3907
+ const now = Math.floor(Date.now() / 1e3);
3908
+ for (const sess of ds.sessions.slice(startIndex)) {
3909
+ this.dsManager.updateSession(ds.id, sess.id, {
3910
+ lastPullAt: now,
3911
+ lastStatus: "error",
3912
+ lastError: detail
3913
+ });
3914
+ this.notifier.onPullResult(ds.id, sess.id, "error", detail);
3915
+ this.markProgressDone(sess.id);
3916
+ }
3917
+ }
3918
+ logSourceUnavailable(ds, detail, affectedSessions) {
3919
+ const now = Date.now();
3920
+ const previous = this.sourceUnavailableLogs.get(ds.id);
3921
+ if (previous && previous.detail === detail && now - previous.lastLoggedAt < SOURCE_UNAVAILABLE_LOG_COOLDOWN_MS) {
3922
+ return;
3923
+ }
3924
+ this.sourceUnavailableLogs.set(ds.id, { detail, lastLoggedAt: now });
3925
+ this.logger.warn(`[Pull] Source ${ds.baseUrl} unavailable: ${detail}, skipped ${affectedSessions} sessions`);
3926
+ }
3704
3927
  async triggerPull(sourceId, sessionId) {
3705
3928
  const ds = this.dsManager.get(sourceId);
3706
3929
  if (!ds) return { success: false, error: "Data source not found" };
@@ -3708,6 +3931,8 @@ var PullEngine = class {
3708
3931
  const sess = ds.sessions.find((s) => s.id === sessionId);
3709
3932
  if (!sess) return { success: false, error: "Session not found" };
3710
3933
  const result = await this.executePullSession(sourceId, ds, sess);
3934
+ if (result.sourceUnavailable && result.error) this.logSourceUnavailable(ds, result.error, 1);
3935
+ if (result.success) this.sourceUnavailableLogs.delete(ds.id);
3711
3936
  return { success: result.success, error: result.error };
3712
3937
  }
3713
3938
  if (this.pullingSourceIds.has(sourceId)) {
@@ -3717,10 +3942,19 @@ var PullEngine = class {
3717
3942
  this.pullingSourceIds.add(sourceId);
3718
3943
  try {
3719
3944
  const errors = [];
3720
- for (const sess of ds.sessions) {
3945
+ let sourceUnavailable = false;
3946
+ for (let index = 0; index < ds.sessions.length; index++) {
3947
+ const sess = ds.sessions[index];
3721
3948
  const result = await this.executePullSession(sourceId, ds, sess);
3722
3949
  if (!result.success && result.error) errors.push(`${sess.name}: ${result.error}`);
3950
+ if (result.sourceUnavailable && result.error) {
3951
+ sourceUnavailable = true;
3952
+ this.markRemainingSessionsSourceUnavailable(ds, index + 1, result.error);
3953
+ this.logSourceUnavailable(ds, result.error, ds.sessions.length - index);
3954
+ break;
3955
+ }
3723
3956
  }
3957
+ if (!sourceUnavailable) this.sourceUnavailableLogs.delete(ds.id);
3724
3958
  if (errors.length > 0) {
3725
3959
  return { success: false, error: errors.join("; ") };
3726
3960
  }
@@ -3801,13 +4035,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
3801
4035
  }
3802
4036
 
3803
4037
  // src/sync/adapters.ts
3804
- import * as fs10 from "fs";
4038
+ import * as fs11 from "fs";
3805
4039
  import * as os4 from "os";
3806
- import * as path12 from "path";
4040
+ import * as path13 from "path";
3807
4041
  import * as crypto3 from "crypto";
3808
4042
  function getTempFilePath(ext) {
3809
4043
  const id = crypto3.randomBytes(8).toString("hex");
3810
- return path12.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
4044
+ return path13.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3811
4045
  }
3812
4046
  var NodeFetcher = class {
3813
4047
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3824,7 +4058,7 @@ var NodeFetcher = class {
3824
4058
  const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
3825
4059
  const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
3826
4060
  const buffer = Buffer.from(await response.arrayBuffer());
3827
- fs10.writeFileSync(tempFile, buffer);
4061
+ fs11.writeFileSync(tempFile, buffer);
3828
4062
  return tempFile;
3829
4063
  }
3830
4064
  };
@@ -3837,7 +4071,7 @@ var DirectImporter = class {
3837
4071
  }
3838
4072
  sessionExists(sessionId) {
3839
4073
  const dbPath = this.dbManager.getDbPath(sessionId);
3840
- if (!fs10.existsSync(dbPath)) return false;
4074
+ if (!fs11.existsSync(dbPath)) return false;
3841
4075
  try {
3842
4076
  const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
3843
4077
  const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
@@ -3845,7 +4079,7 @@ var DirectImporter = class {
3845
4079
  if (row.cnt === 0) {
3846
4080
  this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3847
4081
  try {
3848
- fs10.unlinkSync(dbPath);
4082
+ fs11.unlinkSync(dbPath);
3849
4083
  } catch {
3850
4084
  }
3851
4085
  return false;
@@ -3855,7 +4089,7 @@ var DirectImporter = class {
3855
4089
  if (error instanceof DataDirCompatibilityError) throw error;
3856
4090
  this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3857
4091
  try {
3858
- fs10.unlinkSync(dbPath);
4092
+ fs11.unlinkSync(dbPath);
3859
4093
  } catch {
3860
4094
  }
3861
4095
  return false;
@@ -3914,92 +4148,6 @@ var NoopNotifier = class {
3914
4148
  onPullResult = noop2;
3915
4149
  };
3916
4150
 
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
4151
  // src/sync/index.ts
4004
4152
  var syncLogger = {
4005
4153
  info: (msg) => console.log(`[Sync] ${msg}`),
@@ -4030,7 +4178,16 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
4030
4178
  }
4031
4179
  }
4032
4180
  });
4033
- registerAutomationRoutes(server2, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
4181
+ registerAutomationRoutes(server2, {
4182
+ automation: {
4183
+ dsManager,
4184
+ pullEngine,
4185
+ serverInfo,
4186
+ deleteSessionData: (sessionId) => dbManager2.deleteSessionDatabaseFiles(sessionId),
4187
+ reloadTimer,
4188
+ stopTimer
4189
+ }
4190
+ });
4034
4191
  initScheduler({
4035
4192
  dsManager,
4036
4193
  pullEngine,
@@ -4051,21 +4208,21 @@ function createFileConfigStorage(aiDataDir) {
4051
4208
  return {
4052
4209
  readJson(key) {
4053
4210
  try {
4054
- return JSON.parse(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
4211
+ return JSON.parse(fs12.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
4055
4212
  } catch {
4056
4213
  return null;
4057
4214
  }
4058
4215
  },
4059
4216
  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");
4217
+ if (!fs12.existsSync(aiDataDir)) fs12.mkdirSync(aiDataDir, { recursive: true });
4218
+ fs12.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
4062
4219
  }
4063
4220
  };
4064
4221
  }
4065
4222
  function resolveNativeBinding2() {
4066
4223
  if (process.versions.electron) return void 0;
4067
4224
  const nativePath = resolveCliPath("native/better_sqlite3.node");
4068
- if (fs11.existsSync(nativePath)) return nativePath;
4225
+ if (fs12.existsSync(nativePath)) return nativePath;
4069
4226
  return void 0;
4070
4227
  }
4071
4228
  function ensureToken(config) {
@@ -4172,7 +4329,7 @@ async function startHttpServer(options) {
4172
4329
  }
4173
4330
  });
4174
4331
  initSync(server, dbManager, pathProvider, { port, host, token });
4175
- if (options?.webRoot && fs11.existsSync(options.webRoot)) {
4332
+ if (options?.webRoot && fs12.existsSync(options.webRoot)) {
4176
4333
  registerProxyRoutes(server);
4177
4334
  const fastifyStatic = await import("@fastify/static");
4178
4335
  await server.register(fastifyStatic.default, {
@@ -4213,4 +4370,4 @@ export {
4213
4370
  startHttpServer,
4214
4371
  stopHttpServer
4215
4372
  };
4216
- //# sourceMappingURL=chunk-KL2NGIMQ.mjs.map
4373
+ //# sourceMappingURL=chunk-4IZ3WKYU.mjs.map