chatlab-cli 0.21.0 → 0.21.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 (51) hide show
  1. package/dist/{chunk-3DIWXYMJ.mjs → chunk-4GI2UPCS.mjs} +2 -2
  2. package/dist/{chunk-EIRLEPXP.mjs → chunk-7HQWGLX6.mjs} +6 -1
  3. package/dist/{chunk-EIRLEPXP.mjs.map → chunk-7HQWGLX6.mjs.map} +1 -1
  4. package/dist/{chunk-EWCSGYVT.mjs → chunk-AIR7EAF4.mjs} +2 -2
  5. package/dist/{chunk-FBTHZU55.mjs → chunk-LI4X52E7.mjs} +190 -29
  6. package/dist/chunk-LI4X52E7.mjs.map +1 -0
  7. package/dist/{chunk-XUHPCRVT.mjs → chunk-OQVKZTVD.mjs} +2 -2
  8. package/dist/{chunk-NB4BIAYM.mjs → chunk-OZWXLRJL.mjs} +9 -9
  9. package/dist/cli.mjs +2 -2
  10. package/dist/{http-QVYJ5HER.mjs → http-DHQI4G5J.mjs} +6 -6
  11. package/dist/{import-AJKL6JPR.mjs → import-CQWGIMZQ.mjs} +4 -4
  12. package/dist/index.mjs +6 -6
  13. package/dist/{mcp-FRF3YAN5.mjs → mcp-7Q3FEI3Z.mjs} +3 -3
  14. package/dist/{src-KOIGBZJX.mjs → src-QBT5SYW5.mjs} +2 -2
  15. package/dist/{stream-import-URGOCXUH.mjs → stream-import-VG7RP6KJ.mjs} +3 -3
  16. package/dist-web/assets/{DebugToolsPanel-dUV6pVqD.css → DebugToolsPanel-CoBtqb--.css} +1 -1
  17. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-CpFGhsl5.js +147 -0
  18. package/dist-web/assets/{MessageExportModal-BAf68-wB.js → MessageExportModal-DTgxz1eQ.js} +1 -1
  19. package/dist-web/assets/electron-H65bUnhW.js +1 -0
  20. package/dist-web/assets/{fetch-Ayp337Ng.js → fetch-C2vUkdnA.js} +1 -1
  21. package/dist-web/assets/fetch-CTZXLLcn.js +1 -0
  22. package/dist-web/assets/index-B8867dX7.css +1 -0
  23. package/dist-web/assets/{index-CmU6x-uJ.css → index-BE64tUWq.css} +1 -1
  24. package/dist-web/assets/{index-Bd5r2oFG.js → index-C4B0hv6C.js} +3 -3
  25. package/dist-web/assets/index-CbQMfheg.css +1 -0
  26. package/dist-web/assets/index-DUXf21MT.js +173 -0
  27. package/dist-web/assets/index-DZiH2cC1.js +7 -0
  28. package/dist-web/assets/index-DeG0EHOT.js +24 -0
  29. package/dist-web/assets/index-DuhN8jAf.js +2 -0
  30. package/dist-web/assets/{web-C5yfcwjn.js → web-DImo6Kjz.js} +1 -1
  31. package/dist-web/index.html +2 -2
  32. package/package.json +1 -7
  33. package/dist/chunk-FBTHZU55.mjs.map +0 -1
  34. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-DFN1Co1H.js +0 -155
  35. package/dist-web/assets/electron-BHjOx6B6.js +0 -1
  36. package/dist-web/assets/fetch-BWtpQwBZ.js +0 -1
  37. package/dist-web/assets/index-BfZM-q0T.js +0 -7
  38. package/dist-web/assets/index-CLGv8Den.css +0 -1
  39. package/dist-web/assets/index-CLYHD_f4.js +0 -2
  40. package/dist-web/assets/index-CaTRpNBK.js +0 -16
  41. package/dist-web/assets/index-CzR1sElx.css +0 -1
  42. package/dist-web/assets/index-Ksa--o23.js +0 -173
  43. /package/dist/{chunk-3DIWXYMJ.mjs.map → chunk-4GI2UPCS.mjs.map} +0 -0
  44. /package/dist/{chunk-EWCSGYVT.mjs.map → chunk-AIR7EAF4.mjs.map} +0 -0
  45. /package/dist/{chunk-XUHPCRVT.mjs.map → chunk-OQVKZTVD.mjs.map} +0 -0
  46. /package/dist/{chunk-NB4BIAYM.mjs.map → chunk-OZWXLRJL.mjs.map} +0 -0
  47. /package/dist/{http-QVYJ5HER.mjs.map → http-DHQI4G5J.mjs.map} +0 -0
  48. /package/dist/{import-AJKL6JPR.mjs.map → import-CQWGIMZQ.mjs.map} +0 -0
  49. /package/dist/{mcp-FRF3YAN5.mjs.map → mcp-7Q3FEI3Z.mjs.map} +0 -0
  50. /package/dist/{src-KOIGBZJX.mjs.map → src-QBT5SYW5.mjs.map} +0 -0
  51. /package/dist/{stream-import-URGOCXUH.mjs.map → stream-import-VG7RP6KJ.mjs.map} +0 -0
@@ -13,7 +13,7 @@ import {
13
13
  incrementalImport,
14
14
  openBetterSqliteDatabase,
15
15
  streamingImport
16
- } from "./chunk-EIRLEPXP.mjs";
16
+ } from "./chunk-7HQWGLX6.mjs";
17
17
 
18
18
  // src/import/stream-import.ts
19
19
  import * as fs from "fs";
@@ -141,4 +141,4 @@ export {
141
141
  analyzeIncrementalImport2 as analyzeIncrementalImport,
142
142
  analyzeNewImport2 as analyzeNewImport
143
143
  };
144
- //# sourceMappingURL=chunk-EWCSGYVT.mjs.map
144
+ //# sourceMappingURL=chunk-AIR7EAF4.mjs.map
@@ -7,17 +7,17 @@ const __dirname = __pathDirname(__filename);
7
7
  import {
8
8
  importData,
9
9
  parseFile
10
- } from "./chunk-XUHPCRVT.mjs";
10
+ } from "./chunk-OQVKZTVD.mjs";
11
11
  import {
12
12
  analyzeIncrementalImport,
13
13
  analyzeNewImport,
14
14
  incrementalImport,
15
15
  streamImport
16
- } from "./chunk-EWCSGYVT.mjs";
16
+ } from "./chunk-AIR7EAF4.mjs";
17
17
  import {
18
18
  AGENT_TOOL_REGISTRY,
19
19
  CoreDataProvider
20
- } from "./chunk-3DIWXYMJ.mjs";
20
+ } from "./chunk-4GI2UPCS.mjs";
21
21
  import {
22
22
  getVersion
23
23
  } from "./chunk-2AIOU3PW.mjs";
@@ -114,7 +114,7 @@ import {
114
114
  verifyCliDataPath,
115
115
  writeAuthProfile,
116
116
  writeConfigField
117
- } from "./chunk-EIRLEPXP.mjs";
117
+ } from "./chunk-7HQWGLX6.mjs";
118
118
 
119
119
  // src/http/index.ts
120
120
  import * as fs14 from "fs";
@@ -849,6 +849,11 @@ function registerSessionIndexRoutes(server2, adapter) {
849
849
  const sessionCount = session_index_service_exports.generateIndex(adapter, request.params.id, gapThreshold);
850
850
  return { sessionCount };
851
851
  });
852
+ server2.post("/_web/sessions/:id/generate-incremental-index", async (request) => {
853
+ const gapThreshold = request.body?.gapThreshold ?? 1800;
854
+ const sessionCount = session_index_service_exports.generateIncrementalIndex(adapter, request.params.id, gapThreshold);
855
+ return { sessionCount };
856
+ });
852
857
  server2.post("/_web/sessions/:id/clear-index", async (request) => {
853
858
  session_index_service_exports.clearIndex(adapter, request.params.id);
854
859
  return { success: true };
@@ -1472,7 +1477,7 @@ function registerMergeRoutes(server2, dbManager2, mergeCache) {
1472
1477
  const merged = buildMergedOutput(dataSources, outputName);
1473
1478
  let sessionId;
1474
1479
  if (andImport) {
1475
- const { streamImport: serverStreamImport } = await import("./stream-import-URGOCXUH.mjs");
1480
+ const { streamImport: serverStreamImport } = await import("./stream-import-VG7RP6KJ.mjs");
1476
1481
  const jsonData = JSON.stringify(merged.chatLabData);
1477
1482
  const tmpDir = fs5.mkdtempSync(path5.join(os2.tmpdir(), "chatlab-merged-"));
1478
1483
  const tmpPath = path5.join(tmpDir, `${outputName}.json`);
@@ -1525,7 +1530,7 @@ function registerMergeRoutes(server2, dbManager2, mergeCache) {
1525
1530
  const db = ensureDb2(dbManager2, sid);
1526
1531
  const exported = exportSessionToJson(db);
1527
1532
  const { db: tempDb, tempDbPath } = mergeCache.createTempDatabase(exported.meta.name);
1528
- const { TempDbWriter } = await import("./src-KOIGBZJX.mjs");
1533
+ const { TempDbWriter } = await import("./src-QBT5SYW5.mjs");
1529
1534
  const writer = new TempDbWriter(tempDb);
1530
1535
  writer.writeMeta({
1531
1536
  name: exported.meta.name,
@@ -2713,12 +2718,12 @@ var DataSourceManager = class {
2713
2718
  removeSession(sourceId, sessionId) {
2714
2719
  const sources = this.loadAll();
2715
2720
  const ds = sources.find((s) => s.id === sourceId);
2716
- if (!ds) return false;
2717
- const before = ds.sessions.length;
2721
+ if (!ds) return null;
2722
+ const removed = ds.sessions.find((s) => s.id === sessionId);
2723
+ if (!removed) return null;
2718
2724
  ds.sessions = ds.sessions.filter((s) => s.id !== sessionId);
2719
- if (ds.sessions.length === before) return false;
2720
2725
  this.saveAll(sources);
2721
- return true;
2726
+ return removed;
2722
2727
  }
2723
2728
  updateSession(sourceId, sessionId, updates) {
2724
2729
  const sources = this.loadAll();
@@ -2766,14 +2771,13 @@ function parseRemoteSessionsResponse(body) {
2766
2771
  // ../../packages/sync/src/pull-engine.ts
2767
2772
  import * as fs12 from "fs";
2768
2773
  import * as crypto3 from "crypto";
2769
- var MAX_PAGES_PER_PULL = 50;
2774
+ var MAX_PAGES_PER_PULL = 5e3;
2770
2775
  var PULL_OVERLAP_SECONDS = 60;
2771
2776
  function buildPullUrl(baseUrl, remoteSessionId, params) {
2772
2777
  const base = `${baseUrl}/sessions/${remoteSessionId}/messages`;
2773
2778
  const qs = ["format=chatlab"];
2774
2779
  if (params.since !== void 0 && params.since > 0) qs.push(`since=${params.since}`);
2775
2780
  if (params.offset !== void 0 && params.offset > 0) qs.push(`offset=${params.offset}`);
2776
- if (params.end !== void 0 && params.end > 0) qs.push(`end=${params.end}`);
2777
2781
  if (params.limit !== void 0 && params.limit > 0) qs.push(`limit=${params.limit}`);
2778
2782
  return base + "?" + qs.join("&");
2779
2783
  }
@@ -2815,6 +2819,29 @@ function parseSyncFromFile(filePath) {
2815
2819
  return null;
2816
2820
  }
2817
2821
  }
2822
+ function fileContainsMessages(filePath) {
2823
+ try {
2824
+ if (filePath.endsWith(".jsonl")) {
2825
+ const content = fs12.readFileSync(filePath, "utf-8");
2826
+ for (const line of content.split("\n")) {
2827
+ const trimmed = line.trim();
2828
+ if (!trimmed) continue;
2829
+ try {
2830
+ const obj = JSON.parse(trimmed);
2831
+ if (obj._type === "message") return true;
2832
+ } catch {
2833
+ continue;
2834
+ }
2835
+ }
2836
+ return false;
2837
+ }
2838
+ const raw = fs12.readFileSync(filePath, "utf-8");
2839
+ const parsed = JSON.parse(raw);
2840
+ return Array.isArray(parsed.messages) && parsed.messages.length > 0;
2841
+ } catch {
2842
+ return false;
2843
+ }
2844
+ }
2818
2845
  function cleanupTempFile(filePath) {
2819
2846
  try {
2820
2847
  if (fs12.existsSync(filePath)) fs12.unlinkSync(filePath);
@@ -2829,6 +2856,7 @@ var PullEngine = class {
2829
2856
  logger;
2830
2857
  isImporting;
2831
2858
  pullingSourceIds = /* @__PURE__ */ new Set();
2859
+ progressMap = /* @__PURE__ */ new Map();
2832
2860
  constructor(options) {
2833
2861
  this.fetcher = options.fetcher;
2834
2862
  this.importer = options.importer;
@@ -2837,6 +2865,9 @@ var PullEngine = class {
2837
2865
  this.logger = options.logger ?? NOOP_LOGGER;
2838
2866
  this.isImporting = options.isImporting ?? (() => false);
2839
2867
  }
2868
+ getProgress() {
2869
+ return Array.from(this.progressMap.values());
2870
+ }
2840
2871
  async importTempFile(baseUrl, sess, tempFile) {
2841
2872
  let targetId = sess.targetSessionId;
2842
2873
  if (!targetId) {
@@ -2850,6 +2881,11 @@ var PullEngine = class {
2850
2881
  return this.importer.importFile(tempFile, targetId || void 0, externalId);
2851
2882
  }
2852
2883
  async executePullSession(sourceId, ds, sess) {
2884
+ const currentDs = this.dsManager.get(sourceId);
2885
+ if (!currentDs || !currentDs.sessions.some((s) => s.id === sess.id)) {
2886
+ this.logger.info(`[Pull] Skipping "${sess.name}": session no longer exists`);
2887
+ return { success: true, newMessageCount: 0 };
2888
+ }
2853
2889
  if (this.isImporting()) {
2854
2890
  this.logger.info(`[Pull] Skipping "${sess.name}": import in progress`);
2855
2891
  return { success: false, newMessageCount: 0, error: "Import in progress" };
@@ -2857,34 +2893,114 @@ var PullEngine = class {
2857
2893
  this.logger.info(`[Pull] Pulling "${sess.name}" from ${ds.baseUrl}`);
2858
2894
  let totalNewMessages = 0;
2859
2895
  let since = sess.lastPullAt;
2860
- let offset = 0;
2861
- let end;
2862
2896
  let pageCount = 0;
2863
2897
  let resyncAttempted = false;
2898
+ this.progressMap.set(sess.id, { sessionId: sess.id, sessionName: sess.name, current: 0, pages: 0, done: false });
2864
2899
  try {
2865
2900
  while (pageCount < MAX_PAGES_PER_PULL) {
2866
2901
  pageCount++;
2867
2902
  const tempFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
2868
2903
  since,
2869
- offset,
2870
- end,
2871
2904
  limit: ds.pullLimit
2872
2905
  });
2873
2906
  try {
2874
2907
  const stat = fs12.statSync(tempFile);
2875
2908
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat.size} bytes`);
2909
+ const sync0 = parseSyncFromFile(tempFile);
2910
+ if (stat.size < 1024) {
2911
+ if (!fileContainsMessages(tempFile) && sync0?.hasMore === false) {
2912
+ cleanupTempFile(tempFile);
2913
+ const retryDelays = [2e3, 3e3, 5e3];
2914
+ let retrySuccess = false;
2915
+ let retryHasMore = false;
2916
+ for (let ri = 0; ri < retryDelays.length; ri++) {
2917
+ this.logger.info(
2918
+ `[Pull] "${sess.name}" page ${pageCount} got empty response, retry ${ri + 1}/${retryDelays.length} after ${retryDelays[ri]}ms`
2919
+ );
2920
+ await new Promise((r) => setTimeout(r, retryDelays[ri]));
2921
+ const retryFile = await this.fetcher.fetchToTempFile(ds.baseUrl, sess.remoteSessionId, ds.token, {
2922
+ since,
2923
+ limit: ds.pullLimit
2924
+ });
2925
+ const retryStat = fs12.statSync(retryFile);
2926
+ this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
2927
+ const retrySync = parseSyncFromFile(retryFile);
2928
+ if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
2929
+ cleanupTempFile(retryFile);
2930
+ continue;
2931
+ }
2932
+ const retryResult = await this.importTempFile(ds.baseUrl, sess, retryFile);
2933
+ cleanupTempFile(retryFile);
2934
+ if (retryResult.needFullResync && !resyncAttempted) {
2935
+ resyncAttempted = true;
2936
+ this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
2937
+ since = 0;
2938
+ pageCount = 0;
2939
+ sess.targetSessionId = "";
2940
+ sess.lastPullAt = 0;
2941
+ this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: "", lastPullAt: 0 });
2942
+ retrySuccess = true;
2943
+ retryHasMore = true;
2944
+ break;
2945
+ }
2946
+ if (retryResult.needFullResync) {
2947
+ const errMsg = "Full resync failed";
2948
+ this.logger.error(`[Pull] Full resync already attempted for "${sess.name}", aborting`);
2949
+ this.dsManager.updateSession(sourceId, sess.id, {
2950
+ lastPullAt: Math.floor(Date.now() / 1e3),
2951
+ lastStatus: "error",
2952
+ lastError: errMsg
2953
+ });
2954
+ this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
2955
+ this.markProgressDone(sess.id);
2956
+ return { success: false, newMessageCount: 0, error: errMsg };
2957
+ }
2958
+ if (!retryResult.success) {
2959
+ const errMsg = retryResult.error || "Import failed";
2960
+ this.dsManager.updateSession(sourceId, sess.id, {
2961
+ lastPullAt: Math.floor(Date.now() / 1e3),
2962
+ lastStatus: "error",
2963
+ lastError: errMsg
2964
+ });
2965
+ this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
2966
+ this.markProgressDone(sess.id);
2967
+ return { success: false, newMessageCount: 0, error: errMsg };
2968
+ }
2969
+ if (retryResult.success && retryResult.sessionId && !sess.targetSessionId) {
2970
+ sess.targetSessionId = retryResult.sessionId;
2971
+ this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: retryResult.sessionId });
2972
+ }
2973
+ totalNewMessages += retryResult.newMessageCount;
2974
+ this.progressMap.set(sess.id, {
2975
+ sessionId: sess.id,
2976
+ sessionName: sess.name,
2977
+ current: totalNewMessages,
2978
+ pages: pageCount,
2979
+ done: false
2980
+ });
2981
+ if (retrySync?.hasMore && retrySync.nextSince !== void 0) {
2982
+ since = retrySync.nextSince;
2983
+ }
2984
+ retryHasMore = !!retrySync?.hasMore;
2985
+ retrySuccess = true;
2986
+ break;
2987
+ }
2988
+ if (!retrySuccess) break;
2989
+ if (!retryHasMore) break;
2990
+ continue;
2991
+ }
2992
+ }
2876
2993
  if (stat.size === 0) {
2877
2994
  cleanupTempFile(tempFile);
2878
2995
  break;
2879
2996
  }
2880
- const sync = parseSyncFromFile(tempFile);
2997
+ const sync = sync0;
2881
2998
  const result = await this.importTempFile(ds.baseUrl, sess, tempFile);
2882
2999
  cleanupTempFile(tempFile);
2883
3000
  if (result.needFullResync && !resyncAttempted) {
2884
3001
  resyncAttempted = true;
2885
3002
  this.logger.info(`[Pull] Resetting since=0 for "${sess.name}" full resync`);
2886
3003
  since = 0;
2887
- offset = 0;
2888
3004
  pageCount = 0;
2889
3005
  sess.targetSessionId = "";
2890
3006
  sess.lastPullAt = 0;
@@ -2900,6 +3016,7 @@ var PullEngine = class {
2900
3016
  lastError: errMsg
2901
3017
  });
2902
3018
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3019
+ this.markProgressDone(sess.id);
2903
3020
  return { success: false, newMessageCount: 0, error: errMsg };
2904
3021
  }
2905
3022
  if (!result.success) {
@@ -2910,6 +3027,7 @@ var PullEngine = class {
2910
3027
  lastError: errMsg
2911
3028
  });
2912
3029
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3030
+ this.markProgressDone(sess.id);
2913
3031
  return { success: false, newMessageCount: 0, error: errMsg };
2914
3032
  }
2915
3033
  if (!sess.targetSessionId && result.sessionId) {
@@ -2917,11 +3035,15 @@ var PullEngine = class {
2917
3035
  this.dsManager.updateSession(sourceId, sess.id, { targetSessionId: result.sessionId });
2918
3036
  }
2919
3037
  totalNewMessages += result.newMessageCount;
3038
+ this.progressMap.set(sess.id, {
3039
+ sessionId: sess.id,
3040
+ sessionName: sess.name,
3041
+ current: totalNewMessages,
3042
+ pages: pageCount,
3043
+ done: false
3044
+ });
2920
3045
  if (!sync || !sync.hasMore) break;
2921
3046
  if (sync.nextSince !== void 0) since = sync.nextSince;
2922
- if (sync.nextOffset !== void 0) offset = sync.nextOffset;
2923
- else offset = 0;
2924
- if (sync.watermark !== void 0 && !end) end = sync.watermark;
2925
3047
  } catch (importErr) {
2926
3048
  cleanupTempFile(tempFile);
2927
3049
  throw importErr;
@@ -2938,6 +3060,7 @@ var PullEngine = class {
2938
3060
  });
2939
3061
  if (totalNewMessages > 0) this.notifier.onSessionListChanged();
2940
3062
  this.notifier.onPullResult(sourceId, sess.id, "success", `+${totalNewMessages} messages`);
3063
+ this.markProgressDone(sess.id);
2941
3064
  return { success: true, newMessageCount: totalNewMessages };
2942
3065
  } catch (error) {
2943
3066
  const errMsg = error.message || "Pull failed";
@@ -2948,9 +3071,17 @@ var PullEngine = class {
2948
3071
  lastError: errMsg
2949
3072
  });
2950
3073
  this.notifier.onPullResult(sourceId, sess.id, "error", errMsg);
3074
+ this.markProgressDone(sess.id);
2951
3075
  return { success: false, newMessageCount: 0, error: errMsg };
2952
3076
  }
2953
3077
  }
3078
+ markProgressDone(sessionId) {
3079
+ const p = this.progressMap.get(sessionId);
3080
+ if (p) {
3081
+ p.done = true;
3082
+ setTimeout(() => this.progressMap.delete(sessionId), 5e3);
3083
+ }
3084
+ }
2954
3085
  async pullAllSessions(ds) {
2955
3086
  if (this.pullingSourceIds.has(ds.id)) {
2956
3087
  this.logger.info(`[Pull] Skipping pullAllSessions for "${ds.baseUrl}": pull already in progress`);
@@ -3077,7 +3208,7 @@ var NodeFetcher = class {
3077
3208
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
3078
3209
  const url = buildPullUrl(baseUrl, remoteSessionId, params);
3079
3210
  const headers = {
3080
- Accept: "application/json, application/x-ndjson"
3211
+ Accept: "application/json"
3081
3212
  };
3082
3213
  if (token) headers["Authorization"] = `Bearer ${token}`;
3083
3214
  const response = await fetch(url, { headers, signal: AbortSignal.timeout(12e4) });
@@ -3109,7 +3240,28 @@ var DirectImporter = class {
3109
3240
  }
3110
3241
  sessionExists(sessionId) {
3111
3242
  const dbPath = this.dbManager.getDbPath(sessionId);
3112
- return fs13.existsSync(dbPath);
3243
+ if (!fs13.existsSync(dbPath)) return false;
3244
+ try {
3245
+ const db = openBetterSqliteDatabase(dbPath, { readonly: true, nativeBinding: this.nativeBinding });
3246
+ const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
3247
+ db.close();
3248
+ if (row.cnt === 0) {
3249
+ this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3250
+ try {
3251
+ fs13.unlinkSync(dbPath);
3252
+ } catch {
3253
+ }
3254
+ return false;
3255
+ }
3256
+ return true;
3257
+ } catch {
3258
+ this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3259
+ try {
3260
+ fs13.unlinkSync(dbPath);
3261
+ } catch {
3262
+ }
3263
+ return false;
3264
+ }
3113
3265
  }
3114
3266
  async importFile(tempFile, targetSessionId, externalId) {
3115
3267
  if (targetSessionId && this.sessionExists(targetSessionId)) {
@@ -3139,7 +3291,7 @@ var DirectImporter = class {
3139
3291
  sessionId
3140
3292
  };
3141
3293
  }
3142
- if (result.error?.includes("not found") || result.error?.includes("session_not_found")) {
3294
+ if (result.error?.includes("not found") || result.error?.includes("session_not_found") || result.error?.includes("no such table")) {
3143
3295
  return { success: false, newMessageCount: 0, sessionId, needFullResync: true };
3144
3296
  }
3145
3297
  return { success: false, newMessageCount: 0, sessionId, error: result.error };
@@ -3177,7 +3329,7 @@ var NoopNotifier = class {
3177
3329
 
3178
3330
  // src/sync/routes.ts
3179
3331
  function registerAutomationRoutes(server2, ctx) {
3180
- const { dsManager: dsManager2, pullEngine: pullEngine2, serverInfo } = ctx;
3332
+ const { dsManager: dsManager2, pullEngine: pullEngine2, dbManager: dbManager2, serverInfo } = ctx;
3181
3333
  server2.get("/_web/automation/config", async () => {
3182
3334
  return {
3183
3335
  enabled: true,
@@ -3219,9 +3371,15 @@ function registerAutomationRoutes(server2, ctx) {
3219
3371
  return added;
3220
3372
  });
3221
3373
  server2.delete("/_web/automation/data-sources/:id/sessions/:sessId", async (request, reply) => {
3222
- const ok = dsManager2.removeSession(request.params.id, request.params.sessId);
3223
- if (!ok) return reply.code(404).send({ error: "Session not found" });
3374
+ const removed = dsManager2.removeSession(request.params.id, request.params.sessId);
3375
+ if (!removed) return reply.code(404).send({ error: "Session not found" });
3224
3376
  reloadTimer(request.params.id);
3377
+ if (request.query.deleteData === "true" && removed.targetSessionId) {
3378
+ dbManager2.close(removed.targetSessionId);
3379
+ const dbPath = dbManager2.getDbPath(removed.targetSessionId);
3380
+ const fs15 = await import("fs");
3381
+ if (fs15.existsSync(dbPath)) fs15.unlinkSync(dbPath);
3382
+ }
3225
3383
  return { success: true };
3226
3384
  });
3227
3385
  server2.post(
@@ -3233,6 +3391,9 @@ function registerAutomationRoutes(server2, ctx) {
3233
3391
  server2.post("/_web/automation/data-sources/:id/pull-all", async (request) => {
3234
3392
  return pullEngine2.triggerPullAll(request.params.id);
3235
3393
  });
3394
+ server2.get("/_web/automation/sync-progress", async () => {
3395
+ return pullEngine2.getProgress();
3396
+ });
3236
3397
  server2.get("/_web/automation/remote-sessions", async (request, reply) => {
3237
3398
  const { baseUrl, token, keyword, limit, cursor } = request.query;
3238
3399
  if (!baseUrl) return reply.code(400).send({ error: "baseUrl is required" });
@@ -3276,7 +3437,7 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
3276
3437
  dsManager,
3277
3438
  logger: syncLogger
3278
3439
  });
3279
- registerAutomationRoutes(server2, { dsManager, pullEngine, serverInfo });
3440
+ registerAutomationRoutes(server2, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
3280
3441
  initScheduler({
3281
3442
  dsManager,
3282
3443
  pullEngine,
@@ -3397,4 +3558,4 @@ export {
3397
3558
  startHttpServer,
3398
3559
  stopHttpServer
3399
3560
  };
3400
- //# sourceMappingURL=chunk-FBTHZU55.mjs.map
3561
+ //# sourceMappingURL=chunk-LI4X52E7.mjs.map