chatlab-cli 0.26.2 → 0.27.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 (87) hide show
  1. package/README.md +10 -4
  2. package/dist/{chat-command-BVIG2LCO.mjs → chat-command-SKGPS3PG.mjs} +5 -5
  3. package/dist/{chunk-GBQQJJKK.mjs → chunk-3HOTLLFC.mjs} +476 -186
  4. package/dist/chunk-3HOTLLFC.mjs.map +1 -0
  5. package/dist/{chunk-QKQRV7RV.mjs → chunk-6FXGC6HE.mjs} +2 -2
  6. package/dist/{chunk-JFSTXSX2.mjs → chunk-7LPVQR77.mjs} +42 -7
  7. package/dist/chunk-7LPVQR77.mjs.map +1 -0
  8. package/dist/{chunk-6AOEMUCM.mjs → chunk-FHLPXRDL.mjs} +2 -2
  9. package/dist/{chunk-VSOWNKPA.mjs → chunk-GFHSSCCR.mjs} +11 -1
  10. package/dist/{chunk-TMZC3SNX.mjs → chunk-KCFVHVO4.mjs} +6128 -4255
  11. package/dist/chunk-KCFVHVO4.mjs.map +1 -0
  12. package/dist/{chunk-SCE4YL3M.mjs → chunk-M6ABWESL.mjs} +466 -116
  13. package/dist/chunk-M6ABWESL.mjs.map +1 -0
  14. package/dist/{chunk-GTXX72AQ.mjs → chunk-MJITS5CE.mjs} +16 -16
  15. package/dist/{chunk-3XFHIQIW.mjs → chunk-UBJHT3BU.mjs} +2 -2
  16. package/dist/cli.mjs +4 -4
  17. package/dist/{http-NLFLQDI7.mjs → http-CPFUTIUB.mjs} +9 -9
  18. package/dist/{import-ILK6MGYR.mjs → import-UJXKIKYN.mjs} +5 -5
  19. package/dist/index.mjs +9 -9
  20. package/dist/{mcp-FOLR5XJL.mjs → mcp-SW4DTCWJ.mjs} +8 -8
  21. package/dist/onnxruntime_binding-6Q6HXASN.node +0 -0
  22. package/dist/onnxruntime_binding-EKZT2NRK.node +0 -0
  23. package/dist/onnxruntime_binding-P6S7V3CI.node +0 -0
  24. package/dist/onnxruntime_binding-PJNNIIUO.node +0 -0
  25. package/dist/onnxruntime_binding-UN6SPTQK.node +0 -0
  26. package/dist/{service-KZTHBDQ5.mjs → service-KGL3RAIA.mjs} +2 -2
  27. package/dist/{src-OQN3VE73.mjs → src-M3MO3LEL.mjs} +15 -71
  28. package/dist/{stream-import-F5QI2HTO.mjs → stream-import-4VDJZHMY.mjs} +4 -4
  29. package/dist/transformers.node-PUMHAODN.mjs +42889 -0
  30. package/dist/transformers.node-PUMHAODN.mjs.map +1 -0
  31. package/dist/{update-checker-JIBA7T4U.mjs → update-checker-LGMTEKMT.mjs} +37 -13
  32. package/dist/update-checker-LGMTEKMT.mjs.map +1 -0
  33. package/dist-web/assets/{DebugToolsPanel-CrqULdXp.css → DebugToolsPanel-DzZmSzIU.css} +1 -1
  34. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-C8mwzxDa.js +110 -0
  35. package/dist-web/assets/{MessageExportModal-ttP_JPqO.js → MessageExportModal-DCB-mzf2.js} +1 -1
  36. package/dist-web/assets/{electron-BeJHY587.js → electron-DDUv0ohD.js} +1 -1
  37. package/dist-web/assets/{fetch-H4B1CK_T.js → fetch-B3gsbY_U.js} +1 -1
  38. package/dist-web/assets/{fetch-DBJ7Xkjt.js → fetch-B9Oy0bcD.js} +1 -1
  39. package/dist-web/assets/{fetch-rtQEc8Bu.js → fetch-Bry8iJpq.js} +1 -1
  40. package/dist-web/assets/fetch-C1aC5Fd1.js +1 -0
  41. package/dist-web/assets/{fetch-BETFYYe1.js → fetch-C69hj3wS.js} +1 -1
  42. package/dist-web/assets/{fetch-B7USXx9R.js → fetch-Ck4rGxkO.js} +1 -1
  43. package/dist-web/assets/{fetch-DW8r_JmP.js → fetch-Ckang-F8.js} +1 -1
  44. package/dist-web/assets/fetch-CwHncc6x.js +5 -0
  45. package/dist-web/assets/{fetch-4s20GRbb.js → fetch-D8Ng59an.js} +1 -1
  46. package/dist-web/assets/{fetch-DtXUkazy.js → fetch-DOtBAxUS.js} +1 -1
  47. package/dist-web/assets/index-B40SKtW8.js +173 -0
  48. package/dist-web/assets/index-BX3tqfLz.css +1 -0
  49. package/dist-web/assets/{index-Dov-rTKE.js → index-Bd9j73nw.js} +1 -1
  50. package/dist-web/assets/index-BfFj8Evo.js +64 -0
  51. package/dist-web/assets/index-C16V6EoP.js +11 -0
  52. package/dist-web/assets/{index-CYMTvtEI.js → index-D-J8dY7w.js} +1 -1
  53. package/dist-web/assets/index-I3SWZjnW.css +1 -0
  54. package/dist-web/assets/index-YmzTgkb2.css +1 -0
  55. package/dist-web/assets/index-zAi37ZQ4.js +2 -0
  56. package/dist-web/assets/{web-1-WNRDnP.js → web-DKFeBb_F.js} +1 -1
  57. package/dist-web/favicon.ico +0 -0
  58. package/dist-web/index.html +3 -3
  59. package/package.json +1 -1
  60. package/dist/chunk-GBQQJJKK.mjs.map +0 -1
  61. package/dist/chunk-JFSTXSX2.mjs.map +0 -1
  62. package/dist/chunk-SCE4YL3M.mjs.map +0 -1
  63. package/dist/chunk-TMZC3SNX.mjs.map +0 -1
  64. package/dist/update-checker-JIBA7T4U.mjs.map +0 -1
  65. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-D9mCNaYM.js +0 -182
  66. package/dist-web/assets/electron-Dbml-SPN.js +0 -1
  67. package/dist-web/assets/fetch-DcEBAz_3.js +0 -1
  68. package/dist-web/assets/fetch-DsCNr_M0.js +0 -1
  69. package/dist-web/assets/index-C2ts-hTr.js +0 -173
  70. package/dist-web/assets/index-CjnMyejM.css +0 -1
  71. package/dist-web/assets/index-CsxrXMkt.js +0 -2
  72. package/dist-web/assets/index-DkownpUj.js +0 -163
  73. package/dist-web/assets/index-Dtm2oajn.css +0 -1
  74. package/dist-web/assets/index-LCedyGl2.css +0 -1
  75. package/dist-web/assets/index-m7v4PP7r.js +0 -11
  76. /package/dist/{chat-command-BVIG2LCO.mjs.map → chat-command-SKGPS3PG.mjs.map} +0 -0
  77. /package/dist/{chunk-QKQRV7RV.mjs.map → chunk-6FXGC6HE.mjs.map} +0 -0
  78. /package/dist/{chunk-6AOEMUCM.mjs.map → chunk-FHLPXRDL.mjs.map} +0 -0
  79. /package/dist/{chunk-VSOWNKPA.mjs.map → chunk-GFHSSCCR.mjs.map} +0 -0
  80. /package/dist/{chunk-GTXX72AQ.mjs.map → chunk-MJITS5CE.mjs.map} +0 -0
  81. /package/dist/{chunk-3XFHIQIW.mjs.map → chunk-UBJHT3BU.mjs.map} +0 -0
  82. /package/dist/{http-NLFLQDI7.mjs.map → http-CPFUTIUB.mjs.map} +0 -0
  83. /package/dist/{import-ILK6MGYR.mjs.map → import-UJXKIKYN.mjs.map} +0 -0
  84. /package/dist/{mcp-FOLR5XJL.mjs.map → mcp-SW4DTCWJ.mjs.map} +0 -0
  85. /package/dist/{service-KZTHBDQ5.mjs.map → service-KGL3RAIA.mjs.map} +0 -0
  86. /package/dist/{src-OQN3VE73.mjs.map → src-M3MO3LEL.mjs.map} +0 -0
  87. /package/dist/{stream-import-F5QI2HTO.mjs.map → stream-import-4VDJZHMY.mjs.map} +0 -0
@@ -4,34 +4,34 @@ import { fileURLToPath as __fileURLToPath } from 'url';
4
4
  const require = __createRequire(import.meta.url);
5
5
  const __filename = __fileURLToPath(import.meta.url);
6
6
  const __dirname = __pathDirname(__filename);
7
+ import {
8
+ closeServerAiLogger,
9
+ createCliRunAgentStream,
10
+ initServerAiLogger
11
+ } from "./chunk-7LPVQR77.mjs";
7
12
  import {
8
13
  importData,
9
14
  parseFile
10
- } from "./chunk-3XFHIQIW.mjs";
15
+ } from "./chunk-UBJHT3BU.mjs";
11
16
  import {
12
17
  analyzeIncrementalImport,
13
18
  analyzeNewImport,
14
19
  incrementalImport,
15
20
  streamImport
16
- } from "./chunk-QKQRV7RV.mjs";
21
+ } from "./chunk-6FXGC6HE.mjs";
22
+ import {
23
+ AGENT_TOOL_REGISTRY,
24
+ CoreDataProvider
25
+ } from "./chunk-M6ABWESL.mjs";
17
26
  import {
18
27
  assertCliDataDirCompatible
19
- } from "./chunk-6AOEMUCM.mjs";
28
+ } from "./chunk-FHLPXRDL.mjs";
20
29
  import {
21
30
  getVersion
22
31
  } from "./chunk-5ADKYYNA.mjs";
23
32
  import {
24
33
  resolveCliPath
25
34
  } from "./chunk-FJ35V3TE.mjs";
26
- import {
27
- closeServerAiLogger,
28
- createCliRunAgentStream,
29
- initServerAiLogger
30
- } from "./chunk-JFSTXSX2.mjs";
31
- import {
32
- AGENT_TOOL_REGISTRY,
33
- CoreDataProvider
34
- } from "./chunk-SCE4YL3M.mjs";
35
35
  import {
36
36
  AIChatManager,
37
37
  ALL_MIGRATIONS,
@@ -60,6 +60,7 @@ import {
60
60
  createDatabaseManagerAdapter,
61
61
  createJiebaNlpProvider,
62
62
  createNodeDataDirSwitch,
63
+ createSemanticIndexService,
63
64
  deleteDict,
64
65
  detectAllFormats,
65
66
  detectFormat,
@@ -74,29 +75,41 @@ import {
74
75
  getAvailableYears,
75
76
  getBuiltinModelsByProvider,
76
77
  getCatchphraseAnalysis,
78
+ getCheckInAnalysis,
77
79
  getClusterGraph,
78
80
  getDailyActivity,
81
+ getDbFileVersion,
79
82
  getDefaultNodeUserDataDir,
80
83
  getDefaultRulesForLocale,
81
84
  getDictList,
85
+ getDivingAnalysis,
86
+ getDragonKingAnalysis,
82
87
  getHourlyActivity,
83
88
  getLanguagePreferenceAnalysis,
84
89
  getLaughAnalysis,
90
+ getLongMessageCount,
85
91
  getMemberActivity,
92
+ getMemberMonthlyTrend,
86
93
  getMembers,
87
94
  getMembersDetailed,
95
+ getMemeBattleAnalysis,
88
96
  getMentionAnalysis,
89
97
  getMentionGraph,
90
98
  getMessageLengthDistribution,
91
99
  getMessageTypeStats,
92
100
  getMonthlyActivity,
101
+ getNightOwlAnalysis,
102
+ getOrComputeAnalysisCache,
93
103
  getPendingNodeDataDirMigration,
94
104
  getRelationshipStats,
105
+ getRepeatAnalysis,
95
106
  getSchemaDetailed,
96
107
  getSessionInfo,
97
108
  getSessionMeta,
98
109
  getSessionOverview,
99
110
  getSupportedFormats,
111
+ getTextLengthPercentiles,
112
+ getTextStats,
100
113
  getTimeRange,
101
114
  getWeekdayActivity,
102
115
  getYearlyActivity,
@@ -123,10 +136,10 @@ import {
123
136
  verifyCliDataPath,
124
137
  writeAuthProfile,
125
138
  writeConfigField
126
- } from "./chunk-TMZC3SNX.mjs";
139
+ } from "./chunk-KCFVHVO4.mjs";
127
140
 
128
141
  // src/http/index.ts
129
- import * as fs9 from "fs";
142
+ import * as fs11 from "fs";
130
143
  import * as crypto4 from "crypto";
131
144
 
132
145
  // src/http/server.ts
@@ -451,13 +464,19 @@ function registerSessionRoutes(server2, ctx) {
451
464
  return preferencesInstance;
452
465
  };
453
466
  server2.get("/_web/sessions", async () => {
454
- return session_service_exports.listAnalysisSessions(adapter);
467
+ const aiChatCounts = ctx.aiChatManager?.getAIChatCountsBySession();
468
+ return session_service_exports.listAnalysisSessions(adapter, {
469
+ enrichSession: aiChatCounts?.size ? (dto) => ({ ...dto, aiConversationCount: aiChatCounts.get(dto.id) ?? 0 }) : void 0
470
+ });
455
471
  });
456
472
  server2.get("/_web/sessions/:id", async (request) => {
457
473
  const session = session_service_exports.getAnalysisSession(adapter, request.params.id);
458
474
  if (!session) {
459
475
  throw Object.assign(new Error(`Session not found: ${request.params.id}`), { statusCode: 404 });
460
476
  }
477
+ if (ctx.aiChatManager) {
478
+ session.aiConversationCount = ctx.aiChatManager.getAIChats(request.params.id).length;
479
+ }
461
480
  return session;
462
481
  });
463
482
  server2.delete("/_web/sessions/:id", async (request, reply) => {
@@ -589,6 +608,10 @@ function registerPreferencesRoutes(server2, ctx) {
589
608
  });
590
609
  }
591
610
 
611
+ // ../../packages/http-routes/src/routes/web/analytics.ts
612
+ import * as fs from "fs";
613
+ import * as path2 from "path";
614
+
592
615
  // ../../packages/http-routes/src/helpers.ts
593
616
  function parseTimeFilter(query) {
594
617
  const { startTs, endTs, memberId } = query;
@@ -600,9 +623,32 @@ function parseTimeFilter(query) {
600
623
  return filter;
601
624
  }
602
625
 
626
+ // ../../packages/http-routes/src/analytics-cache.ts
627
+ import * as path from "path";
628
+ function canonical(value) {
629
+ if (value === void 0) return "null";
630
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
631
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
632
+ const obj = value;
633
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
634
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
635
+ }
636
+ function buildAnalyticsCacheKey(namespace, params) {
637
+ return `${namespace}:${canonical(params)}`;
638
+ }
639
+ function withAnalyticsCache(ctx, sessionId, namespace, params, compute, options) {
640
+ const queryCacheDir = path.join(ctx.pathProvider.getCacheDir(), "query");
641
+ const dateStr = options?.dailyInvalidate ? `|date:${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}` : "";
642
+ const extraStr = options?.extraVersion ? `|${options.extraVersion}` : "";
643
+ const version = `${ctx.getVersion()}|${getDbFileVersion(ctx.sessionAdapter.getDbPath(sessionId))}${dateStr}${extraStr}`;
644
+ const key = buildAnalyticsCacheKey(namespace, params);
645
+ return getOrComputeAnalysisCache(sessionId, key, queryCacheDir, version, compute);
646
+ }
647
+
603
648
  // ../../packages/http-routes/src/routes/web/analytics.ts
604
649
  function registerAnalyticsRoutes(server2, ctx) {
605
650
  const { sessionAdapter: adapter } = ctx;
651
+ const cached = (name, sessionId, params, compute, options) => withAnalyticsCache(ctx, sessionId, `analytics.${name}`, params, compute, options);
606
652
  server2.get("/_web/sessions/:id/years", async (request) => {
607
653
  const db = adapter.ensureReadonly(request.params.id);
608
654
  return getAvailableYears(db);
@@ -614,111 +660,252 @@ function registerAnalyticsRoutes(server2, ctx) {
614
660
  server2.get(
615
661
  "/_web/sessions/:id/stats/member-activity",
616
662
  async (request) => {
617
- const db = adapter.ensureReadonly(request.params.id);
618
- return getMemberActivity(db, parseTimeFilter(request.query));
663
+ const id = request.params.id;
664
+ const filter = parseTimeFilter(request.query);
665
+ return cached("member-activity", id, { ...filter }, () => getMemberActivity(adapter.ensureReadonly(id), filter));
619
666
  }
620
667
  );
621
668
  server2.get(
622
669
  "/_web/sessions/:id/stats/hourly",
623
670
  async (request) => {
624
- const db = adapter.ensureReadonly(request.params.id);
625
- return getHourlyActivity(db, parseTimeFilter(request.query));
671
+ const id = request.params.id;
672
+ const filter = parseTimeFilter(request.query);
673
+ return cached("hourly", id, { ...filter }, () => getHourlyActivity(adapter.ensureReadonly(id), filter));
626
674
  }
627
675
  );
628
676
  server2.get(
629
677
  "/_web/sessions/:id/stats/daily",
630
678
  async (request) => {
631
- const db = adapter.ensureReadonly(request.params.id);
632
- return getDailyActivity(db, parseTimeFilter(request.query));
679
+ const id = request.params.id;
680
+ const filter = parseTimeFilter(request.query);
681
+ return cached("daily", id, { ...filter }, () => getDailyActivity(adapter.ensureReadonly(id), filter));
633
682
  }
634
683
  );
635
684
  server2.get(
636
685
  "/_web/sessions/:id/stats/weekday",
637
686
  async (request) => {
638
- const db = adapter.ensureReadonly(request.params.id);
639
- return getWeekdayActivity(db, parseTimeFilter(request.query));
687
+ const id = request.params.id;
688
+ const filter = parseTimeFilter(request.query);
689
+ return cached("weekday", id, { ...filter }, () => getWeekdayActivity(adapter.ensureReadonly(id), filter));
640
690
  }
641
691
  );
642
692
  server2.get(
643
693
  "/_web/sessions/:id/stats/message-types",
644
694
  async (request) => {
645
- const db = adapter.ensureReadonly(request.params.id);
646
- return getMessageTypeStats(db, parseTimeFilter(request.query));
695
+ const id = request.params.id;
696
+ const filter = parseTimeFilter(request.query);
697
+ return cached("message-types", id, { ...filter }, () => getMessageTypeStats(adapter.ensureReadonly(id), filter));
647
698
  }
648
699
  );
649
700
  server2.get(
650
701
  "/_web/sessions/:id/analytics/relationship",
651
702
  async (request) => {
652
- const db = adapter.ensureReadonly(request.params.id);
653
- return getRelationshipStats(db, parseTimeFilter(request.query));
703
+ const id = request.params.id;
704
+ const filter = parseTimeFilter(request.query);
705
+ return cached("relationship", id, { ...filter }, () => getRelationshipStats(adapter.ensureReadonly(id), filter));
654
706
  }
655
707
  );
656
708
  server2.get(
657
709
  "/_web/sessions/:id/analytics/catchphrase",
658
710
  async (request) => {
659
- const db = adapter.ensureReadonly(request.params.id);
660
- return getCatchphraseAnalysis(db, parseTimeFilter(request.query));
711
+ const id = request.params.id;
712
+ const filter = parseTimeFilter(request.query);
713
+ return cached("catchphrase", id, { ...filter }, () => getCatchphraseAnalysis(adapter.ensureReadonly(id), filter));
661
714
  }
662
715
  );
663
716
  server2.get(
664
717
  "/_web/sessions/:id/analytics/mention",
665
718
  async (request) => {
666
- const db = adapter.ensureReadonly(request.params.id);
667
- return getMentionAnalysis(db, parseTimeFilter(request.query));
719
+ const id = request.params.id;
720
+ const filter = parseTimeFilter(request.query);
721
+ return cached("mention", id, { ...filter }, () => getMentionAnalysis(adapter.ensureReadonly(id), filter));
668
722
  }
669
723
  );
670
724
  server2.get(
671
725
  "/_web/sessions/:id/analytics/mention-graph",
672
726
  async (request) => {
673
- const db = adapter.ensureReadonly(request.params.id);
674
- return getMentionGraph(db, parseTimeFilter(request.query));
727
+ const id = request.params.id;
728
+ const filter = parseTimeFilter(request.query);
729
+ return cached("mention-graph", id, { ...filter }, () => getMentionGraph(adapter.ensureReadonly(id), filter));
675
730
  }
676
731
  );
677
732
  server2.get(
678
733
  "/_web/sessions/:id/analytics/laugh",
679
734
  async (request) => {
680
- const db = adapter.ensureReadonly(request.params.id);
681
- return getLaughAnalysis(db, parseTimeFilter(request.query));
682
- }
683
- );
684
- server2.get(
685
- "/_web/sessions/:id/analytics/cluster",
686
- async (request) => {
687
- const db = adapter.ensureReadonly(request.params.id);
735
+ const id = request.params.id;
688
736
  const filter = parseTimeFilter(request.query);
689
- const topEdges = request.query.topEdges ? parseInt(request.query.topEdges, 10) : void 0;
690
- return getClusterGraph(db, filter, topEdges ? { topEdges } : void 0);
737
+ return cached("laugh", id, { ...filter }, () => getLaughAnalysis(adapter.ensureReadonly(id), filter));
691
738
  }
692
739
  );
740
+ server2.get("/_web/sessions/:id/analytics/cluster", async (request) => {
741
+ const id = request.params.id;
742
+ const filter = parseTimeFilter(request.query);
743
+ const options = {};
744
+ if (request.query.topEdges) options.topEdges = parseInt(request.query.topEdges, 10);
745
+ if (request.query.lookAhead) options.lookAhead = parseInt(request.query.lookAhead, 10);
746
+ if (request.query.decaySeconds) options.decaySeconds = parseInt(request.query.decaySeconds, 10);
747
+ const hasOptions = Object.keys(options).length > 0;
748
+ return cached(
749
+ "cluster",
750
+ id,
751
+ { ...filter, ...options },
752
+ () => getClusterGraph(adapter.ensureReadonly(id), filter, hasOptions ? options : void 0)
753
+ );
754
+ });
693
755
  server2.get(
694
756
  "/_web/sessions/:id/analytics/language-preference",
695
757
  async (request) => {
696
- const db = adapter.ensureReadonly(request.params.id);
758
+ const id = request.params.id;
697
759
  const filter = parseTimeFilter(request.query);
698
760
  const locale = request.query.locale || "zh-CN";
699
- const nlpProvider = createJiebaNlpProvider();
700
- return getLanguagePreferenceAnalysis(db, { locale, timeFilter: filter, nlpProvider });
761
+ const zhDictPath = path2.join(ctx.pathProvider.getSystemDir(), "nlp", "zh-CN.dict");
762
+ let zhDictVersion;
763
+ try {
764
+ const st = fs.statSync(zhDictPath);
765
+ zhDictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
766
+ } catch {
767
+ zhDictVersion = "-";
768
+ }
769
+ return cached(
770
+ "language-preference",
771
+ id,
772
+ { ...filter, locale },
773
+ () => getLanguagePreferenceAnalysis(adapter.ensureReadonly(id), {
774
+ locale,
775
+ timeFilter: filter,
776
+ nlpProvider: createJiebaNlpProvider()
777
+ }),
778
+ { extraVersion: `dict:${zhDictVersion}` }
779
+ );
701
780
  }
702
781
  );
703
782
  server2.get(
704
783
  "/_web/sessions/:id/analytics/monthly-activity",
705
784
  async (request) => {
706
- const db = adapter.ensureReadonly(request.params.id);
707
- return getMonthlyActivity(db, parseTimeFilter(request.query));
785
+ const id = request.params.id;
786
+ const filter = parseTimeFilter(request.query);
787
+ return cached("monthly-activity", id, { ...filter }, () => getMonthlyActivity(adapter.ensureReadonly(id), filter));
708
788
  }
709
789
  );
710
790
  server2.get(
711
791
  "/_web/sessions/:id/analytics/yearly-activity",
712
792
  async (request) => {
713
- const db = adapter.ensureReadonly(request.params.id);
714
- return getYearlyActivity(db, parseTimeFilter(request.query));
793
+ const id = request.params.id;
794
+ const filter = parseTimeFilter(request.query);
795
+ return cached("yearly-activity", id, { ...filter }, () => getYearlyActivity(adapter.ensureReadonly(id), filter));
715
796
  }
716
797
  );
717
798
  server2.get(
718
799
  "/_web/sessions/:id/analytics/message-length-distribution",
719
800
  async (request) => {
720
- const db = adapter.ensureReadonly(request.params.id);
721
- return getMessageLengthDistribution(db, parseTimeFilter(request.query));
801
+ const id = request.params.id;
802
+ const filter = parseTimeFilter(request.query);
803
+ return cached(
804
+ "message-length-distribution",
805
+ id,
806
+ { ...filter },
807
+ () => getMessageLengthDistribution(adapter.ensureReadonly(id), filter)
808
+ );
809
+ }
810
+ );
811
+ server2.get(
812
+ "/_web/sessions/:id/analytics/text-stats",
813
+ async (request) => {
814
+ const id = request.params.id;
815
+ const filter = parseTimeFilter(request.query);
816
+ return cached("text-stats", id, { ...filter }, () => getTextStats(adapter.ensureReadonly(id), filter));
817
+ }
818
+ );
819
+ server2.get(
820
+ "/_web/sessions/:id/analytics/long-message-count",
821
+ async (request) => {
822
+ const id = request.params.id;
823
+ const filter = parseTimeFilter(request.query);
824
+ const minLength = request.query.minLength ? parseInt(request.query.minLength, 10) : void 0;
825
+ return cached(
826
+ "long-message-count",
827
+ id,
828
+ { ...filter, minLength },
829
+ () => getLongMessageCount(adapter.ensureReadonly(id), filter, minLength)
830
+ );
831
+ }
832
+ );
833
+ server2.get(
834
+ "/_web/sessions/:id/analytics/member-monthly-trend",
835
+ async (request) => {
836
+ const id = request.params.id;
837
+ const filter = parseTimeFilter(request.query);
838
+ return cached(
839
+ "member-monthly-trend",
840
+ id,
841
+ { ...filter },
842
+ () => getMemberMonthlyTrend(adapter.ensureReadonly(id), filter)
843
+ );
844
+ }
845
+ );
846
+ server2.get(
847
+ "/_web/sessions/:id/analytics/text-length-percentiles",
848
+ async (request) => {
849
+ const id = request.params.id;
850
+ const filter = parseTimeFilter(request.query);
851
+ return cached(
852
+ "text-length-percentiles",
853
+ id,
854
+ { ...filter },
855
+ () => getTextLengthPercentiles(adapter.ensureReadonly(id), filter)
856
+ );
857
+ }
858
+ );
859
+ server2.get(
860
+ "/_web/sessions/:id/analytics/dragon-king",
861
+ async (request) => {
862
+ const id = request.params.id;
863
+ const filter = parseTimeFilter(request.query);
864
+ return cached("dragon-king", id, { ...filter }, () => getDragonKingAnalysis(adapter.ensureReadonly(id), filter));
865
+ }
866
+ );
867
+ server2.get(
868
+ "/_web/sessions/:id/analytics/diving",
869
+ async (request) => {
870
+ const id = request.params.id;
871
+ const filter = parseTimeFilter(request.query);
872
+ return cached("diving", id, { ...filter }, () => getDivingAnalysis(adapter.ensureReadonly(id), filter), {
873
+ dailyInvalidate: true
874
+ });
875
+ }
876
+ );
877
+ server2.get(
878
+ "/_web/sessions/:id/analytics/check-in",
879
+ async (request) => {
880
+ const id = request.params.id;
881
+ const filter = parseTimeFilter(request.query);
882
+ return cached("check-in", id, { ...filter }, () => getCheckInAnalysis(adapter.ensureReadonly(id), filter));
883
+ }
884
+ );
885
+ server2.get(
886
+ "/_web/sessions/:id/analytics/meme-battle",
887
+ async (request) => {
888
+ const id = request.params.id;
889
+ const filter = parseTimeFilter(request.query);
890
+ return cached("meme-battle", id, { ...filter }, () => getMemeBattleAnalysis(adapter.ensureReadonly(id), filter));
891
+ }
892
+ );
893
+ server2.get(
894
+ "/_web/sessions/:id/analytics/night-owl",
895
+ async (request) => {
896
+ const id = request.params.id;
897
+ const filter = parseTimeFilter(request.query);
898
+ return cached("night-owl", id, { ...filter }, () => getNightOwlAnalysis(adapter.ensureReadonly(id), filter), {
899
+ dailyInvalidate: true
900
+ });
901
+ }
902
+ );
903
+ server2.get(
904
+ "/_web/sessions/:id/analytics/repeat",
905
+ async (request) => {
906
+ const id = request.params.id;
907
+ const filter = parseTimeFilter(request.query);
908
+ return cached("repeat", id, { ...filter }, () => getRepeatAnalysis(adapter.ensureReadonly(id), filter));
722
909
  }
723
910
  );
724
911
  }
@@ -823,9 +1010,10 @@ function registerExportRoutes(server2, ctx) {
823
1010
  }
824
1011
 
825
1012
  // ../../packages/http-routes/src/routes/web/nlp.ts
826
- import * as path from "path";
1013
+ import * as fs2 from "fs";
1014
+ import * as path3 from "path";
827
1015
  function registerNlpRoutes(server2, ctx) {
828
- const nlpDir = path.join(ctx.pathProvider.getSystemDir(), "nlp");
1016
+ const nlpDir = path3.join(ctx.pathProvider.getSystemDir(), "nlp");
829
1017
  initNlpDir(nlpDir);
830
1018
  ensureDefaultDict(nlpDir).catch((err) => console.warn("[NLP] Auto-download zh-CN dict failed:", err));
831
1019
  server2.get("/_web/nlp/pos-tags", async () => {
@@ -849,7 +1037,19 @@ function registerNlpRoutes(server2, ctx) {
849
1037
  if (!db) {
850
1038
  throw Object.assign(new Error(`Session not found: ${params.sessionId}`), { statusCode: 404 });
851
1039
  }
852
- return computeWordFrequency(db, params);
1040
+ const { sessionId, ...keyParams } = params;
1041
+ const effectiveDictId = !params.dictType || params.dictType === "default" ? "zh-CN" : String(params.dictType);
1042
+ const dictFilePath = path3.join(nlpDir, `${effectiveDictId}.dict`);
1043
+ let dictVersion;
1044
+ try {
1045
+ const st = fs2.statSync(dictFilePath);
1046
+ dictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
1047
+ } catch {
1048
+ dictVersion = "-";
1049
+ }
1050
+ return withAnalyticsCache(ctx, sessionId, "nlp.word-frequency", keyParams, () => computeWordFrequency(db, params), {
1051
+ extraVersion: `dict:${dictVersion}`
1052
+ });
853
1053
  });
854
1054
  server2.post(
855
1055
  "/_web/nlp/segment",
@@ -1202,17 +1402,21 @@ function registerAiToolRoutes(server2, ctx) {
1202
1402
  if (!entry) {
1203
1403
  return reply.code(404).send({ success: false, error: `Tool not found: ${toolName}` });
1204
1404
  }
1205
- const db = ctx.dbManager.open(sessionId);
1206
- if (!db) {
1207
- return reply.code(404).send({ success: false, error: `Session not found: ${sessionId}` });
1208
- }
1209
1405
  const abortController = new AbortController();
1210
1406
  activeToolTests.set(testId, abortController);
1211
1407
  try {
1408
+ if (ctx.executeAiTool) {
1409
+ return await ctx.executeAiTool({ testId, toolName, params, sessionId, abortSignal: abortController.signal });
1410
+ }
1411
+ const db = ctx.dbManager.open(sessionId);
1412
+ if (!db) {
1413
+ return reply.code(404).send({ success: false, error: `Session not found: ${sessionId}` });
1414
+ }
1212
1415
  const execCtx = {
1213
1416
  sessionId,
1214
1417
  db,
1215
- dataProvider: new CoreDataProvider(db)
1418
+ dataProvider: new CoreDataProvider(db),
1419
+ abortSignal: abortController.signal
1216
1420
  };
1217
1421
  const startTime = Date.now();
1218
1422
  const result = await entry.handler(params, execCtx);
@@ -1413,10 +1617,64 @@ function registerAiSummaryRoutes(server2, ctx) {
1413
1617
  });
1414
1618
  }
1415
1619
 
1620
+ // ../../packages/http-routes/src/routes/web/ai-semantic-index.ts
1621
+ function registerSemanticIndexRoutes(server2, ctx) {
1622
+ const service = ctx.semanticIndexService;
1623
+ if (!service) return;
1624
+ server2.get("/_web/ai/semantic-index/config", async () => {
1625
+ return { config: service.getConfig(), apiKeySet: service.hasApiKey(), configured: service.isConfigured() };
1626
+ });
1627
+ server2.put(
1628
+ "/_web/ai/semantic-index/config",
1629
+ async (request) => {
1630
+ const config = service.setConfig(request.body.config, { apiKey: request.body.apiKey });
1631
+ return { config, apiKeySet: service.hasApiKey(), configured: service.isConfigured() };
1632
+ }
1633
+ );
1634
+ server2.get("/_web/ai/semantic-index/enabled", async () => {
1635
+ return { sessions: service.listEnabledStatuses() };
1636
+ });
1637
+ server2.get("/_web/ai/semantic-index/status", async (request) => {
1638
+ return { status: service.status(request.query.sessionId) };
1639
+ });
1640
+ server2.post("/_web/ai/semantic-index/status", async (request) => {
1641
+ return { sessions: service.statusForSessions(request.body.sessionIds ?? []) };
1642
+ });
1643
+ const sessionAction = (path12, action) => {
1644
+ server2.post(path12, async (request, reply) => {
1645
+ const { sessionId } = request.body;
1646
+ if (!sessionId) return reply.code(400).send({ error: "sessionId is required" });
1647
+ await action(sessionId);
1648
+ return { status: service.status(sessionId) };
1649
+ });
1650
+ };
1651
+ sessionAction("/_web/ai/semantic-index/enable", (id) => service.enable(id));
1652
+ sessionAction("/_web/ai/semantic-index/disable", (id) => service.disable(id));
1653
+ sessionAction("/_web/ai/semantic-index/build", (id) => service.build(id));
1654
+ sessionAction("/_web/ai/semantic-index/pause", (id) => service.pause(id));
1655
+ sessionAction("/_web/ai/semantic-index/cancel", (id) => service.cancel(id));
1656
+ sessionAction("/_web/ai/semantic-index/rebuild", (id) => service.rebuild(id));
1657
+ server2.post("/_web/ai/semantic-index/build-pending", async () => {
1658
+ service.buildAllPending();
1659
+ return { sessions: service.listEnabledStatuses() };
1660
+ });
1661
+ server2.post("/_web/ai/semantic-index/cleanup", async () => {
1662
+ return service.cleanupUnused();
1663
+ });
1664
+ server2.post(
1665
+ "/_web/ai/semantic-index/search",
1666
+ async (request, reply) => {
1667
+ const { sessionId, query, finalTopK } = request.body;
1668
+ if (!sessionId || !query) return reply.code(400).send({ error: "sessionId and query are required" });
1669
+ return service.search(sessionId, query, { finalTopK });
1670
+ }
1671
+ );
1672
+ }
1673
+
1416
1674
  // ../../packages/http-routes/src/routes/web/merge.ts
1417
- import * as fs from "fs";
1675
+ import * as fs3 from "fs";
1418
1676
  import * as os from "os";
1419
- import * as path2 from "path";
1677
+ import * as path4 from "path";
1420
1678
  function ensureDb2(ctx, sessionId) {
1421
1679
  const db = ctx.dbManager.open(sessionId);
1422
1680
  if (!db) throw sessionNotFound(sessionId);
@@ -1433,31 +1691,31 @@ function registerMergeRoutes(server2, ctx) {
1433
1691
  if (contentType.includes("multipart/form-data")) {
1434
1692
  const data = await request.file();
1435
1693
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1436
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merge-"));
1437
- const tmpPath = path2.join(tmpDir, data.filename || "upload");
1694
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merge-"));
1695
+ const tmpPath = path4.join(tmpDir, data.filename || "upload");
1438
1696
  const chunks = [];
1439
1697
  for await (const chunk of data.file) {
1440
1698
  chunks.push(chunk);
1441
1699
  }
1442
- fs.writeFileSync(tmpPath, Buffer.concat(chunks));
1700
+ fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
1443
1701
  filePath = tmpPath;
1444
1702
  cleanupFile = true;
1445
1703
  cleanupDir = tmpDir;
1446
1704
  } else {
1447
1705
  const body = request.body;
1448
1706
  if (!body?.filePath) return reply.code(400).send({ error: "Missing filePath in body" });
1449
- if (!fs.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1707
+ if (!fs3.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1450
1708
  filePath = body.filePath;
1451
1709
  }
1452
1710
  try {
1453
1711
  const result = await streamParseFileInfo(filePath, {
1454
1712
  createTempDatabase(sourceFilePath) {
1455
- return mergeCache.createTempDatabase(path2.basename(sourceFilePath));
1713
+ return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1456
1714
  },
1457
1715
  onProgress() {
1458
1716
  }
1459
1717
  });
1460
- const filename = path2.basename(filePath);
1718
+ const filename = path4.basename(filePath);
1461
1719
  const handle = mergeCache.store(filename, result.tempDbPath);
1462
1720
  return {
1463
1721
  handle,
@@ -1471,13 +1729,13 @@ function registerMergeRoutes(server2, ctx) {
1471
1729
  } finally {
1472
1730
  if (cleanupFile) {
1473
1731
  try {
1474
- fs.unlinkSync(filePath);
1732
+ fs3.unlinkSync(filePath);
1475
1733
  } catch {
1476
1734
  }
1477
1735
  }
1478
1736
  if (cleanupDir) {
1479
1737
  try {
1480
- fs.rmdirSync(cleanupDir);
1738
+ fs3.rmdirSync(cleanupDir);
1481
1739
  } catch {
1482
1740
  }
1483
1741
  }
@@ -1526,20 +1784,20 @@ function registerMergeRoutes(server2, ctx) {
1526
1784
  return reply.code(501).send({ error: "Import not available on this server" });
1527
1785
  }
1528
1786
  const jsonData = JSON.stringify(merged.chatLabData);
1529
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merged-"));
1530
- const safeName = path2.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1531
- const tmpPath = path2.join(tmpDir, `${safeName}.json`);
1532
- fs.writeFileSync(tmpPath, jsonData, "utf-8");
1787
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merged-"));
1788
+ const safeName = path4.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1789
+ const tmpPath = path4.join(tmpDir, `${safeName}.json`);
1790
+ fs3.writeFileSync(tmpPath, jsonData, "utf-8");
1533
1791
  try {
1534
1792
  const importResult = await streamImport2(dbManager2, tmpPath);
1535
1793
  sessionId = importResult.sessionId;
1536
1794
  } finally {
1537
1795
  try {
1538
- fs.unlinkSync(tmpPath);
1796
+ fs3.unlinkSync(tmpPath);
1539
1797
  } catch {
1540
1798
  }
1541
1799
  try {
1542
- fs.rmdirSync(tmpDir);
1800
+ fs3.rmdirSync(tmpDir);
1543
1801
  } catch {
1544
1802
  }
1545
1803
  }
@@ -1613,16 +1871,16 @@ function registerMergeRoutes(server2, ctx) {
1613
1871
  }
1614
1872
 
1615
1873
  // ../../packages/http-routes/src/routes/web/cache.ts
1616
- import * as fs2 from "fs";
1874
+ import * as fs4 from "fs";
1617
1875
  import * as fsp from "fs/promises";
1618
- import * as path3 from "path";
1876
+ import * as path5 from "path";
1619
1877
  async function getDirSize(dirPath) {
1620
1878
  let totalSize = 0;
1621
1879
  try {
1622
- if (!fs2.existsSync(dirPath)) return 0;
1880
+ if (!fs4.existsSync(dirPath)) return 0;
1623
1881
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1624
1882
  for (const file of files) {
1625
- const filePath = path3.join(dirPath, file.name);
1883
+ const filePath = path5.join(dirPath, file.name);
1626
1884
  if (file.isDirectory()) {
1627
1885
  totalSize += await getDirSize(filePath);
1628
1886
  } else {
@@ -1637,10 +1895,10 @@ async function getDirSize(dirPath) {
1637
1895
  async function getFileCount(dirPath) {
1638
1896
  let count = 0;
1639
1897
  try {
1640
- if (!fs2.existsSync(dirPath)) return 0;
1898
+ if (!fs4.existsSync(dirPath)) return 0;
1641
1899
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1642
1900
  for (const file of files) {
1643
- const filePath = path3.join(dirPath, file.name);
1901
+ const filePath = path5.join(dirPath, file.name);
1644
1902
  if (file.isDirectory()) {
1645
1903
  count += await getFileCount(filePath);
1646
1904
  } else {
@@ -1653,10 +1911,10 @@ async function getFileCount(dirPath) {
1653
1911
  }
1654
1912
  function hasChatLabDatabases(dirPath) {
1655
1913
  try {
1656
- const markerPath = path3.join(dirPath, ".chatlab");
1657
- const dbDir = path3.join(dirPath, "databases");
1658
- if (!fs2.existsSync(markerPath) || !fs2.existsSync(dbDir)) return false;
1659
- return fs2.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1914
+ const markerPath = path5.join(dirPath, ".chatlab");
1915
+ const dbDir = path5.join(dirPath, "databases");
1916
+ if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
1917
+ return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1660
1918
  } catch {
1661
1919
  return false;
1662
1920
  }
@@ -1702,7 +1960,7 @@ function registerCacheRoutes(server2, ctx) {
1702
1960
  const results = await Promise.all(
1703
1961
  directories.map(async (dir) => {
1704
1962
  const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
1705
- return { ...dir, size, fileCount, exists: fs2.existsSync(dir.path) };
1963
+ return { ...dir, size, fileCount, exists: fs4.existsSync(dir.path) };
1706
1964
  })
1707
1965
  );
1708
1966
  return {
@@ -1719,10 +1977,10 @@ function registerCacheRoutes(server2, ctx) {
1719
1977
  };
1720
1978
  const dirPath = allowedDirs[cacheId];
1721
1979
  if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
1722
- if (!fs2.existsSync(dirPath)) return { success: true };
1980
+ if (!fs4.existsSync(dirPath)) return { success: true };
1723
1981
  const files = await fsp.readdir(dirPath);
1724
1982
  for (const file of files) {
1725
- const filePath = path3.join(dirPath, file);
1983
+ const filePath = path5.join(dirPath, file);
1726
1984
  const stat2 = await fsp.stat(filePath);
1727
1985
  if (stat2.isDirectory()) {
1728
1986
  await fsp.rm(filePath, { recursive: true });
@@ -1739,7 +1997,7 @@ function registerCacheRoutes(server2, ctx) {
1739
1997
  defaultPath: ctx.defaultUserDataDir,
1740
1998
  isCustom: ctx.isCustomDataDir ?? false,
1741
1999
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
1742
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path3.resolve(pp.getUserDataDir()) !== path3.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
2000
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path5.resolve(pp.getUserDataDir()) !== path5.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1743
2001
  pendingMigration: pending ? {
1744
2002
  from: pending.from,
1745
2003
  to: pending.to,
@@ -1756,8 +2014,8 @@ function registerCacheRoutes(server2, ctx) {
1756
2014
  return ctx.setDataDir(targetPath, migrate);
1757
2015
  });
1758
2016
  server2.get("/_web/cache/latest-import-log", async () => {
1759
- const importLogDir = path3.join(pp.getLogsDir(), "import");
1760
- if (!fs2.existsSync(importLogDir)) {
2017
+ const importLogDir = path5.join(pp.getLogsDir(), "import");
2018
+ if (!fs4.existsSync(importLogDir)) {
1761
2019
  return { success: false, error: "Log directory not found" };
1762
2020
  }
1763
2021
  const files = await fsp.readdir(importLogDir);
@@ -1767,7 +2025,7 @@ function registerCacheRoutes(server2, ctx) {
1767
2025
  }
1768
2026
  const fileStats = await Promise.all(
1769
2027
  logFiles.map(async (f) => {
1770
- const filePath = path3.join(importLogDir, f);
2028
+ const filePath = path5.join(importLogDir, f);
1771
2029
  const stat2 = await fsp.stat(filePath);
1772
2030
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
1773
2031
  })
@@ -1791,11 +2049,11 @@ function registerCacheRoutes(server2, ctx) {
1791
2049
  const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
1792
2050
  buffer = Buffer.from(base64Data, "base64");
1793
2051
  }
1794
- if (!fs2.existsSync(downloadsDir)) {
1795
- fs2.mkdirSync(downloadsDir, { recursive: true });
2052
+ if (!fs4.existsSync(downloadsDir)) {
2053
+ fs4.mkdirSync(downloadsDir, { recursive: true });
1796
2054
  }
1797
- const filePath = path3.join(downloadsDir, filename);
1798
- fs2.writeFileSync(filePath, buffer);
2055
+ const filePath = path5.join(downloadsDir, filename);
2056
+ fs4.writeFileSync(filePath, buffer);
1799
2057
  return { success: true, filePath };
1800
2058
  });
1801
2059
  server2.post("/_web/cache/open-dir", async (request, reply) => {
@@ -1811,7 +2069,7 @@ function registerCacheRoutes(server2, ctx) {
1811
2069
  };
1812
2070
  const dirPath = dirPaths[cacheId];
1813
2071
  if (!dirPath) return { success: false, error: "Unknown directory" };
1814
- if (!fs2.existsSync(dirPath)) {
2072
+ if (!fs4.existsSync(dirPath)) {
1815
2073
  await fsp.mkdir(dirPath, { recursive: true });
1816
2074
  }
1817
2075
  try {
@@ -1869,6 +2127,7 @@ function registerSharedRoutes(server2, ctx, options) {
1869
2127
  registerAiToolRoutes(server2, resolvedCtx);
1870
2128
  registerAiChatRoutes(server2, resolvedCtx);
1871
2129
  registerAiSummaryRoutes(server2, resolvedCtx);
2130
+ registerSemanticIndexRoutes(server2, resolvedCtx);
1872
2131
  registerMergeRoutes(server2, resolvedCtx);
1873
2132
  registerCacheRoutes(server2, resolvedCtx);
1874
2133
  }
@@ -1948,19 +2207,19 @@ function createServer() {
1948
2207
 
1949
2208
  // src/http/routes/web/index.ts
1950
2209
  import * as os3 from "os";
1951
- import * as path6 from "path";
2210
+ import * as path8 from "path";
1952
2211
 
1953
2212
  // src/http/routes/web/import.ts
1954
- import * as fs4 from "fs";
2213
+ import * as fs6 from "fs";
1955
2214
  import * as os2 from "os";
1956
- import * as path4 from "path";
2215
+ import * as path6 from "path";
1957
2216
 
1958
2217
  // src/http/routes/web/helpers.ts
1959
- import * as fs3 from "fs";
2218
+ import * as fs5 from "fs";
1960
2219
  function resolveNativeBinding() {
1961
2220
  if (process.versions.electron) return void 0;
1962
2221
  const nativePath = resolveCliPath("native/better_sqlite3.node");
1963
- if (fs3.existsSync(nativePath)) return nativePath;
2222
+ if (fs5.existsSync(nativePath)) return nativePath;
1964
2223
  return void 0;
1965
2224
  }
1966
2225
 
@@ -1969,11 +2228,11 @@ var DEMO_BASE_URL = "https://chatlab.fun/assets/demo";
1969
2228
  function cleanupTemp(...paths) {
1970
2229
  for (const p of paths) {
1971
2230
  try {
1972
- const stat2 = fs4.statSync(p);
2231
+ const stat2 = fs6.statSync(p);
1973
2232
  if (stat2.isDirectory()) {
1974
- fs4.rmSync(p, { recursive: true, force: true });
2233
+ fs6.rmSync(p, { recursive: true, force: true });
1975
2234
  } else {
1976
- fs4.unlinkSync(p);
2235
+ fs6.unlinkSync(p);
1977
2236
  }
1978
2237
  } catch {
1979
2238
  }
@@ -1986,14 +2245,14 @@ function registerImportRoutes(server2, dbManager2) {
1986
2245
  server2.post("/_web/detect-format", async (request, reply) => {
1987
2246
  const data = await request.file();
1988
2247
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1989
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-detect-"));
1990
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2248
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-detect-"));
2249
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1991
2250
  try {
1992
2251
  const chunks = [];
1993
2252
  for await (const chunk of data.file) {
1994
2253
  chunks.push(chunk);
1995
2254
  }
1996
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2255
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
1997
2256
  const format = detectFormat(tmpPath);
1998
2257
  const allFormats = detectAllFormats(tmpPath);
1999
2258
  return { format, allFormats };
@@ -2004,14 +2263,14 @@ function registerImportRoutes(server2, dbManager2) {
2004
2263
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
2005
2264
  const data = await request.file();
2006
2265
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2007
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-scan-"));
2008
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2266
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-scan-"));
2267
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2009
2268
  try {
2010
2269
  const chunks = [];
2011
2270
  for await (const chunk of data.file) {
2012
2271
  chunks.push(chunk);
2013
2272
  }
2014
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2273
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2015
2274
  const chats = await scanMultiChatFile(tmpPath);
2016
2275
  return { chats };
2017
2276
  } finally {
@@ -2021,13 +2280,13 @@ function registerImportRoutes(server2, dbManager2) {
2021
2280
  server2.post("/_web/import", async (request, reply) => {
2022
2281
  const data = await request.file();
2023
2282
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2024
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-import-"));
2025
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2283
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-import-"));
2284
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2026
2285
  const chunks = [];
2027
2286
  for await (const chunk of data.file) {
2028
2287
  chunks.push(chunk);
2029
2288
  }
2030
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2289
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2031
2290
  const formatId = data.fields?.formatId?.value;
2032
2291
  const chatIndexStr = data.fields?.chatIndex?.value;
2033
2292
  const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
@@ -2070,7 +2329,7 @@ data: ${JSON.stringify(eventData)}
2070
2329
  server2.post("/_web/import-directory", async (request, reply) => {
2071
2330
  const parts = request.parts();
2072
2331
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2073
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-dir-import-"));
2332
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-dir-import-"));
2074
2333
  const relativePaths = [];
2075
2334
  const fileBuffers = [];
2076
2335
  try {
@@ -2098,10 +2357,10 @@ data: ${JSON.stringify(eventData)}
2098
2357
  if (segments.length > 1) {
2099
2358
  relPath = segments.slice(1).join("/");
2100
2359
  }
2101
- const targetPath = path4.resolve(tmpDir, relPath);
2102
- if (!targetPath.startsWith(tmpDir + path4.sep)) continue;
2103
- fs4.mkdirSync(path4.dirname(targetPath), { recursive: true });
2104
- fs4.writeFileSync(targetPath, fileBuffers[i].data);
2360
+ const targetPath = path6.resolve(tmpDir, relPath);
2361
+ if (!targetPath.startsWith(tmpDir + path6.sep)) continue;
2362
+ fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
2363
+ fs6.writeFileSync(targetPath, fileBuffers[i].data);
2105
2364
  }
2106
2365
  const entryPath = findEntryFileInDirectory(tmpDir);
2107
2366
  if (!entryPath) {
@@ -2146,13 +2405,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2146
2405
  const sessionId = request.params.id;
2147
2406
  const data = await request.file();
2148
2407
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2149
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-inc-"));
2150
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2408
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-inc-"));
2409
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2151
2410
  const chunks = [];
2152
2411
  for await (const chunk of data.file) {
2153
2412
  chunks.push(chunk);
2154
2413
  }
2155
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2414
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2156
2415
  reply.raw.writeHead(200, {
2157
2416
  "Content-Type": "text/event-stream",
2158
2417
  "Cache-Control": "no-cache",
@@ -2184,13 +2443,13 @@ data: ${JSON.stringify(eventData)}
2184
2443
  const sessionId = request.params.id;
2185
2444
  const data = await request.file();
2186
2445
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2187
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2188
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2446
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2447
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2189
2448
  const chunks = [];
2190
2449
  for await (const chunk of data.file) {
2191
2450
  chunks.push(chunk);
2192
2451
  }
2193
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2452
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2194
2453
  try {
2195
2454
  return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
2196
2455
  } catch (err) {
@@ -2202,13 +2461,13 @@ data: ${JSON.stringify(eventData)}
2202
2461
  server2.post("/_web/import/analyze", async (request, reply) => {
2203
2462
  const data = await request.file();
2204
2463
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2205
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2206
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2464
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2465
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2207
2466
  const chunks = [];
2208
2467
  for await (const chunk of data.file) {
2209
2468
  chunks.push(chunk);
2210
2469
  }
2211
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2470
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2212
2471
  try {
2213
2472
  return await analyzeNewImport(tmpPath);
2214
2473
  } catch (err) {
@@ -2238,17 +2497,17 @@ data: ${JSON.stringify(eventData)}
2238
2497
 
2239
2498
  `);
2240
2499
  }
2241
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-demo-"));
2500
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-demo-"));
2242
2501
  try {
2243
2502
  const localPaths = [];
2244
2503
  for (let i = 0; i < total; i++) {
2245
2504
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2246
- const localPath = path4.join(tmpDir, DEMO_FILES[i]);
2505
+ const localPath = path6.join(tmpDir, DEMO_FILES[i]);
2247
2506
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2248
2507
  signal: AbortSignal.timeout(6e4)
2249
2508
  });
2250
2509
  if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
2251
- fs4.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2510
+ fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2252
2511
  localPaths.push(localPath);
2253
2512
  }
2254
2513
  sendEvent("progress", { stage: "importing", current: 1, total });
@@ -2279,7 +2538,7 @@ data: ${JSON.stringify(eventData)}
2279
2538
  }
2280
2539
 
2281
2540
  // src/http/routes/web/cache.ts
2282
- import * as path5 from "path";
2541
+ import * as path7 from "path";
2283
2542
  import { execFile } from "child_process";
2284
2543
  function runExecFile(file, args, runner = execFile) {
2285
2544
  return new Promise((resolve4, reject) => {
@@ -2307,7 +2566,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2307
2566
  } else if (platform === "win32") {
2308
2567
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2309
2568
  } else {
2310
- await runExecFile("xdg-open", [path5.dirname(filePath)], runner);
2569
+ await runExecFile("xdg-open", [path7.dirname(filePath)], runner);
2311
2570
  }
2312
2571
  }
2313
2572
 
@@ -2330,26 +2589,42 @@ function registerWebRoutes(server2, dbManager2, options) {
2330
2589
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2331
2590
  mergeCache?.cleanupOrphans();
2332
2591
  const fallbackPathProvider = {
2333
- getSystemDir: () => path6.join(os3.homedir(), ".chatlab"),
2334
- getUserDataDir: () => path6.join(os3.homedir(), ".chatlab", "data"),
2335
- getDatabaseDir: () => path6.join(os3.homedir(), ".chatlab", "data", "databases"),
2336
- getAiDataDir: () => path6.join(os3.homedir(), ".chatlab", "ai"),
2337
- getSettingsDir: () => path6.join(os3.homedir(), ".chatlab", "settings"),
2338
- getCacheDir: () => path6.join(os3.homedir(), ".chatlab", "cache"),
2339
- getTempDir: () => path6.join(os3.homedir(), ".chatlab", "temp"),
2340
- getLogsDir: () => path6.join(os3.homedir(), ".chatlab", "logs"),
2341
- getDownloadsDir: () => path6.join(os3.homedir(), "Downloads")
2592
+ getSystemDir: () => path8.join(os3.homedir(), ".chatlab"),
2593
+ getUserDataDir: () => path8.join(os3.homedir(), ".chatlab", "data"),
2594
+ getDatabaseDir: () => path8.join(os3.homedir(), ".chatlab", "data", "databases"),
2595
+ getVectorDir: () => path8.join(os3.homedir(), ".chatlab", "data", "vector"),
2596
+ getAiDataDir: () => path8.join(os3.homedir(), ".chatlab", "ai"),
2597
+ getSettingsDir: () => path8.join(os3.homedir(), ".chatlab", "settings"),
2598
+ getCacheDir: () => path8.join(os3.homedir(), ".chatlab", "cache"),
2599
+ getTempDir: () => path8.join(os3.homedir(), ".chatlab", "temp"),
2600
+ getLogsDir: () => path8.join(os3.homedir(), ".chatlab", "logs"),
2601
+ getDownloadsDir: () => path8.join(os3.homedir(), "Downloads")
2342
2602
  };
2343
2603
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2344
2604
  const ai = options?.aiContext;
2345
2605
  const cliStreamImport = async (dm, filePath) => {
2346
- const { streamImport: streamImport2 } = await import("./stream-import-F5QI2HTO.mjs");
2606
+ const { streamImport: streamImport2 } = await import("./stream-import-4VDJZHMY.mjs");
2347
2607
  const result = await streamImport2(dm, filePath);
2348
2608
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2349
2609
  return { sessionId: result.sessionId };
2350
2610
  };
2351
2611
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2352
- const isCustom = path6.resolve(resolvedPathProvider.getUserDataDir()) !== path6.resolve(defaultUserDataDir);
2612
+ const isCustom = path8.resolve(resolvedPathProvider.getUserDataDir()) !== path8.resolve(defaultUserDataDir);
2613
+ let semanticIndexService = options?.semanticIndexService;
2614
+ if (!semanticIndexService) {
2615
+ try {
2616
+ semanticIndexService = createSemanticIndexService({
2617
+ pathProvider: resolvedPathProvider,
2618
+ sessionAdapter: adapter,
2619
+ nativeBinding: options?.nativeBinding
2620
+ });
2621
+ semanticIndexService.recover();
2622
+ server2.addHook("onClose", async () => semanticIndexService?.close());
2623
+ } catch (err) {
2624
+ console.warn("[semantic-index] service unavailable:", err instanceof Error ? err.message : String(err));
2625
+ semanticIndexService = void 0;
2626
+ }
2627
+ }
2353
2628
  registerSharedRoutes(
2354
2629
  server2,
2355
2630
  {
@@ -2358,6 +2633,7 @@ function registerWebRoutes(server2, dbManager2, options) {
2358
2633
  pathProvider: resolvedPathProvider,
2359
2634
  getVersion,
2360
2635
  nativeBinding: options?.nativeBinding,
2636
+ semanticIndexService,
2361
2637
  openDirectory: openDirectoryPath,
2362
2638
  showInFolder: showPathInFolder,
2363
2639
  downloadsDir: resolvedPathProvider.getDownloadsDir(),
@@ -2440,31 +2716,31 @@ function registerProxyRoutes(server2) {
2440
2716
  }
2441
2717
 
2442
2718
  // src/ai/manager-factory.ts
2443
- import * as fs5 from "fs";
2444
- import * as path7 from "path";
2719
+ import * as fs7 from "fs";
2720
+ import * as path9 from "path";
2445
2721
  import { createHash, randomUUID } from "crypto";
2446
2722
  var nodeFs = {
2447
2723
  ensureDir(dir) {
2448
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
2724
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2449
2725
  },
2450
2726
  listFiles(dir, ext) {
2451
- if (!fs5.existsSync(dir)) return [];
2452
- return fs5.readdirSync(dir).filter((f) => f.endsWith(ext));
2727
+ if (!fs7.existsSync(dir)) return [];
2728
+ return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
2453
2729
  },
2454
2730
  readFile(filePath) {
2455
- return fs5.readFileSync(filePath, "utf-8");
2731
+ return fs7.readFileSync(filePath, "utf-8");
2456
2732
  },
2457
2733
  writeFile(filePath, content) {
2458
- fs5.writeFileSync(filePath, content, "utf-8");
2734
+ fs7.writeFileSync(filePath, content, "utf-8");
2459
2735
  },
2460
2736
  deleteFile(filePath) {
2461
- if (fs5.existsSync(filePath)) fs5.unlinkSync(filePath);
2737
+ if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
2462
2738
  },
2463
2739
  fileExists(filePath) {
2464
- return fs5.existsSync(filePath);
2740
+ return fs7.existsSync(filePath);
2465
2741
  },
2466
2742
  joinPath(...parts) {
2467
- return path7.join(...parts);
2743
+ return path9.join(...parts);
2468
2744
  }
2469
2745
  };
2470
2746
  var managerCache = /* @__PURE__ */ new Map();
@@ -2474,13 +2750,13 @@ function getAssistantManager(aiDataDir) {
2474
2750
  cached = {
2475
2751
  assistant: new AssistantManager({
2476
2752
  fs: nodeFs,
2477
- assistantsDir: path7.join(aiDataDir, "assistants"),
2753
+ assistantsDir: path9.join(aiDataDir, "assistants"),
2478
2754
  builtinRawConfigs: [],
2479
2755
  generateId: () => `custom_${randomUUID().replace(/-/g, "").slice(0, 12)}`
2480
2756
  }),
2481
2757
  skill: new SkillManagerCore({
2482
2758
  fs: nodeFs,
2483
- skillsDir: path7.join(aiDataDir, "skills"),
2759
+ skillsDir: path9.join(aiDataDir, "skills"),
2484
2760
  builtinRawSkills: [],
2485
2761
  contentHash: (content) => createHash("md5").update(content).digest("hex")
2486
2762
  })
@@ -2504,8 +2780,8 @@ var noop = () => {
2504
2780
  var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2505
2781
 
2506
2782
  // ../../packages/sync/src/data-source-manager.ts
2507
- import * as fs6 from "fs";
2508
- import * as path8 from "path";
2783
+ import * as fs8 from "fs";
2784
+ import * as path10 from "path";
2509
2785
  import * as crypto from "crypto";
2510
2786
  var CONFIG_FILE = "data-sources.json";
2511
2787
  function generateId(prefix = "ds") {
@@ -2533,17 +2809,17 @@ var DataSourceManager = class {
2533
2809
  this.logger = logger ?? NOOP_LOGGER;
2534
2810
  }
2535
2811
  getConfigPath() {
2536
- return path8.join(this.configDir, CONFIG_FILE);
2812
+ return path10.join(this.configDir, CONFIG_FILE);
2537
2813
  }
2538
2814
  ensureDir() {
2539
- fs6.mkdirSync(this.configDir, { recursive: true });
2815
+ fs8.mkdirSync(this.configDir, { recursive: true });
2540
2816
  }
2541
2817
  // ==================== Load / Save ====================
2542
2818
  loadAll() {
2543
2819
  try {
2544
2820
  const filePath = this.getConfigPath();
2545
- if (fs6.existsSync(filePath)) {
2546
- const raw = fs6.readFileSync(filePath, "utf-8");
2821
+ if (fs8.existsSync(filePath)) {
2822
+ const raw = fs8.readFileSync(filePath, "utf-8");
2547
2823
  const parsed = JSON.parse(raw);
2548
2824
  if (!isValidDataSourceArray(parsed)) {
2549
2825
  this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
@@ -2562,7 +2838,7 @@ var DataSourceManager = class {
2562
2838
  saveAll(sources) {
2563
2839
  try {
2564
2840
  this.ensureDir();
2565
- fs6.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2841
+ fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2566
2842
  } catch (err) {
2567
2843
  this.logger.error("[DataSource] Failed to save config", err);
2568
2844
  }
@@ -2687,7 +2963,7 @@ function parseRemoteSessionsResponse(body) {
2687
2963
  }
2688
2964
 
2689
2965
  // ../../packages/sync/src/pull-engine.ts
2690
- import * as fs7 from "fs";
2966
+ import * as fs9 from "fs";
2691
2967
  import * as crypto2 from "crypto";
2692
2968
  var MAX_PAGES_PER_PULL = 5e3;
2693
2969
  var PULL_OVERLAP_SECONDS = 60;
@@ -2707,7 +2983,7 @@ function parseSyncFromFile(filePath) {
2707
2983
  try {
2708
2984
  const isJsonl = filePath.endsWith(".jsonl");
2709
2985
  if (isJsonl) {
2710
- const content = fs7.readFileSync(filePath, "utf-8");
2986
+ const content = fs9.readFileSync(filePath, "utf-8");
2711
2987
  const lines = content.trimEnd().split("\n");
2712
2988
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
2713
2989
  try {
@@ -2726,7 +3002,7 @@ function parseSyncFromFile(filePath) {
2726
3002
  }
2727
3003
  return null;
2728
3004
  }
2729
- const raw = fs7.readFileSync(filePath, "utf-8");
3005
+ const raw = fs9.readFileSync(filePath, "utf-8");
2730
3006
  const parsed = JSON.parse(raw);
2731
3007
  if (parsed.sync && typeof parsed.sync === "object") {
2732
3008
  const s = parsed.sync;
@@ -2740,7 +3016,7 @@ function parseSyncFromFile(filePath) {
2740
3016
  function fileContainsMessages(filePath) {
2741
3017
  try {
2742
3018
  if (filePath.endsWith(".jsonl")) {
2743
- const content = fs7.readFileSync(filePath, "utf-8");
3019
+ const content = fs9.readFileSync(filePath, "utf-8");
2744
3020
  for (const line of content.split("\n")) {
2745
3021
  const trimmed = line.trim();
2746
3022
  if (!trimmed) continue;
@@ -2753,7 +3029,7 @@ function fileContainsMessages(filePath) {
2753
3029
  }
2754
3030
  return false;
2755
3031
  }
2756
- const raw = fs7.readFileSync(filePath, "utf-8");
3032
+ const raw = fs9.readFileSync(filePath, "utf-8");
2757
3033
  const parsed = JSON.parse(raw);
2758
3034
  return Array.isArray(parsed.messages) && parsed.messages.length > 0;
2759
3035
  } catch {
@@ -2762,7 +3038,7 @@ function fileContainsMessages(filePath) {
2762
3038
  }
2763
3039
  function cleanupTempFile(filePath) {
2764
3040
  try {
2765
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
3041
+ if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
2766
3042
  } catch {
2767
3043
  }
2768
3044
  }
@@ -2824,7 +3100,7 @@ var PullEngine = class {
2824
3100
  limit: ds.pullLimit
2825
3101
  });
2826
3102
  try {
2827
- const stat2 = fs7.statSync(tempFile);
3103
+ const stat2 = fs9.statSync(tempFile);
2828
3104
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
2829
3105
  const sync0 = parseSyncFromFile(tempFile);
2830
3106
  if (stat2.size < 1024) {
@@ -2842,7 +3118,7 @@ var PullEngine = class {
2842
3118
  since,
2843
3119
  limit: ds.pullLimit
2844
3120
  });
2845
- const retryStat = fs7.statSync(retryFile);
3121
+ const retryStat = fs9.statSync(retryFile);
2846
3122
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
2847
3123
  const retrySync = parseSyncFromFile(retryFile);
2848
3124
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
@@ -3123,13 +3399,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
3123
3399
  }
3124
3400
 
3125
3401
  // src/sync/adapters.ts
3126
- import * as fs8 from "fs";
3402
+ import * as fs10 from "fs";
3127
3403
  import * as os4 from "os";
3128
- import * as path9 from "path";
3404
+ import * as path11 from "path";
3129
3405
  import * as crypto3 from "crypto";
3130
3406
  function getTempFilePath(ext) {
3131
3407
  const id = crypto3.randomBytes(8).toString("hex");
3132
- return path9.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3408
+ return path11.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3133
3409
  }
3134
3410
  var NodeFetcher = class {
3135
3411
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3146,7 +3422,7 @@ var NodeFetcher = class {
3146
3422
  const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
3147
3423
  const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
3148
3424
  const buffer = Buffer.from(await response.arrayBuffer());
3149
- fs8.writeFileSync(tempFile, buffer);
3425
+ fs10.writeFileSync(tempFile, buffer);
3150
3426
  return tempFile;
3151
3427
  }
3152
3428
  };
@@ -3159,7 +3435,7 @@ var DirectImporter = class {
3159
3435
  }
3160
3436
  sessionExists(sessionId) {
3161
3437
  const dbPath = this.dbManager.getDbPath(sessionId);
3162
- if (!fs8.existsSync(dbPath)) return false;
3438
+ if (!fs10.existsSync(dbPath)) return false;
3163
3439
  try {
3164
3440
  const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
3165
3441
  const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
@@ -3167,7 +3443,7 @@ var DirectImporter = class {
3167
3443
  if (row.cnt === 0) {
3168
3444
  this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3169
3445
  try {
3170
- fs8.unlinkSync(dbPath);
3446
+ fs10.unlinkSync(dbPath);
3171
3447
  } catch {
3172
3448
  }
3173
3449
  return false;
@@ -3177,7 +3453,7 @@ var DirectImporter = class {
3177
3453
  if (error instanceof DataDirCompatibilityError) throw error;
3178
3454
  this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3179
3455
  try {
3180
- fs8.unlinkSync(dbPath);
3456
+ fs10.unlinkSync(dbPath);
3181
3457
  } catch {
3182
3458
  }
3183
3459
  return false;
@@ -3382,21 +3658,21 @@ function createFileConfigStorage(aiDataDir) {
3382
3658
  return {
3383
3659
  readJson(key) {
3384
3660
  try {
3385
- return JSON.parse(fs9.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3661
+ return JSON.parse(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3386
3662
  } catch {
3387
3663
  return null;
3388
3664
  }
3389
3665
  },
3390
3666
  writeJson(key, data) {
3391
- if (!fs9.existsSync(aiDataDir)) fs9.mkdirSync(aiDataDir, { recursive: true });
3392
- fs9.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3667
+ if (!fs11.existsSync(aiDataDir)) fs11.mkdirSync(aiDataDir, { recursive: true });
3668
+ fs11.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3393
3669
  }
3394
3670
  };
3395
3671
  }
3396
3672
  function resolveNativeBinding2() {
3397
3673
  if (process.versions.electron) return void 0;
3398
3674
  const nativePath = resolveCliPath("native/better_sqlite3.node");
3399
- if (fs9.existsSync(nativePath)) return nativePath;
3675
+ if (fs11.existsSync(nativePath)) return nativePath;
3400
3676
  return void 0;
3401
3677
  }
3402
3678
  function ensureToken(config) {
@@ -3467,9 +3743,23 @@ async function startHttpServer(options) {
3467
3743
  limits: { fileSize: 1024 * 1024 * 1024 }
3468
3744
  // 1GB
3469
3745
  });
3746
+ let semanticIndexService;
3747
+ try {
3748
+ semanticIndexService = createSemanticIndexService({
3749
+ pathProvider,
3750
+ sessionAdapter: createDatabaseManagerAdapter(dbManager),
3751
+ nativeBinding
3752
+ });
3753
+ semanticIndexService.recover();
3754
+ server.addHook("onClose", async () => semanticIndexService?.close());
3755
+ } catch (err) {
3756
+ console.warn("[semantic-index] service unavailable:", err instanceof Error ? err.message : String(err));
3757
+ semanticIndexService = void 0;
3758
+ }
3470
3759
  registerWebRoutes(server, dbManager, {
3471
3760
  pathProvider,
3472
3761
  nativeBinding,
3762
+ semanticIndexService,
3473
3763
  aiContext: {
3474
3764
  aiDataDir,
3475
3765
  aiChatManager,
@@ -3478,11 +3768,11 @@ async function startHttpServer(options) {
3478
3768
  llmConfigStore,
3479
3769
  customProviderStore: new CustomProviderStore(createFileConfigStorage(aiDataDir)),
3480
3770
  customModelStore: new CustomModelStore(createFileConfigStorage(aiDataDir)),
3481
- runAgentStream: createCliRunAgentStream(dbManager, aiChatManager)
3771
+ runAgentStream: createCliRunAgentStream(dbManager, aiChatManager, semanticIndexService)
3482
3772
  }
3483
3773
  });
3484
3774
  initSync(server, dbManager, pathProvider, { port, host, token });
3485
- if (options?.webRoot && fs9.existsSync(options.webRoot)) {
3775
+ if (options?.webRoot && fs11.existsSync(options.webRoot)) {
3486
3776
  registerProxyRoutes(server);
3487
3777
  const fastifyStatic = await import("@fastify/static");
3488
3778
  await server.register(fastifyStatic.default, {
@@ -3523,4 +3813,4 @@ export {
3523
3813
  startHttpServer,
3524
3814
  stopHttpServer
3525
3815
  };
3526
- //# sourceMappingURL=chunk-GBQQJJKK.mjs.map
3816
+ //# sourceMappingURL=chunk-3HOTLLFC.mjs.map