chatlab-cli 0.26.1 → 0.26.3

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 (76) hide show
  1. package/dist/{chat-command-T3Z4O4M5.mjs → chat-command-D37MMEJ7.mjs} +4 -4
  2. package/dist/{chunk-MZG2GLJR.mjs → chunk-742TAKEP.mjs} +2 -2
  3. package/dist/{chunk-3APSMVOM.mjs → chunk-D5MWER7N.mjs} +2 -2
  4. package/dist/{chunk-6O2SW5GL.mjs → chunk-GNGZHBKM.mjs} +3 -3
  5. package/dist/{chunk-SC77N66X.mjs → chunk-K5SGXTVJ.mjs} +31 -16
  6. package/dist/chunk-K5SGXTVJ.mjs.map +1 -0
  7. package/dist/{chunk-AZZYKUBR.mjs → chunk-QMXNJRTT.mjs} +448 -209
  8. package/dist/chunk-QMXNJRTT.mjs.map +1 -0
  9. package/dist/{chunk-FACKH2BR.mjs → chunk-RMITK4IC.mjs} +8 -4
  10. package/dist/chunk-RMITK4IC.mjs.map +1 -0
  11. package/dist/{chunk-FAOZNMPK.mjs → chunk-VCN64OD6.mjs} +2 -2
  12. package/dist/{chunk-GUGKY2GV.mjs → chunk-YPTJVTVJ.mjs} +9213 -6374
  13. package/dist/chunk-YPTJVTVJ.mjs.map +1 -0
  14. package/dist/cli.mjs +3 -3
  15. package/dist/{http-6LQUOYQ6.mjs → http-6F6TYWCI.mjs} +8 -8
  16. package/dist/{import-B2MAIBAK.mjs → import-VYPOMSSA.mjs} +4 -4
  17. package/dist/index.mjs +8 -8
  18. package/dist/{mcp-ZMMYW46I.mjs → mcp-AR7L5FA3.mjs} +7 -7
  19. package/dist/src-6S7JPKHX.mjs +443 -0
  20. package/dist/{stream-import-FHFBCEDV.mjs → stream-import-KZFC3XT2.mjs} +3 -3
  21. package/dist/stream-import-KZFC3XT2.mjs.map +1 -0
  22. package/dist/{update-checker-JIBA7T4U.mjs → update-checker-3N7HQEP2.mjs} +36 -12
  23. package/dist/update-checker-3N7HQEP2.mjs.map +1 -0
  24. package/dist-web/assets/{DebugToolsPanel-CrqULdXp.css → DebugToolsPanel-DzZmSzIU.css} +1 -1
  25. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-BTdqmm0y.js +107 -0
  26. package/dist-web/assets/{MessageExportModal-B8DKeSAH.js → MessageExportModal-CF6yaor7.js} +1 -1
  27. package/dist-web/assets/{electron-C-aMus7e.js → electron-DKVw61Ww.js} +1 -1
  28. package/dist-web/assets/{fetch-xB6cl_1L.js → fetch-BC8jyJ_U.js} +1 -1
  29. package/dist-web/assets/{fetch-B26JuD67.js → fetch-BMdgfBVS.js} +1 -1
  30. package/dist-web/assets/fetch-BQlEQsqi.js +5 -0
  31. package/dist-web/assets/{fetch-CvnJ4Jgf.js → fetch-Bh6HC-IR.js} +1 -1
  32. package/dist-web/assets/{fetch-phbrO2bY.js → fetch-BvVfWR-P.js} +1 -1
  33. package/dist-web/assets/{fetch-BLKibnXa.js → fetch-CPeSfqsU.js} +1 -1
  34. package/dist-web/assets/fetch-CUw-PUYP.js +1 -0
  35. package/dist-web/assets/{fetch-CV8jezsM.js → fetch-CpYSD089.js} +1 -1
  36. package/dist-web/assets/{fetch-DP3yNfJM.js → fetch-D2jNBqs8.js} +1 -1
  37. package/dist-web/assets/{fetch-BSJY7tRo.js → fetch-DqEMAea3.js} +1 -1
  38. package/dist-web/assets/{index-DJQFNVUJ.js → index-BHQnAj6l.js} +1 -1
  39. package/dist-web/assets/index-BwzuCu5R.js +2 -0
  40. package/dist-web/assets/index-CG746E15.js +64 -0
  41. package/dist-web/assets/index-CXoQ_U22.js +173 -0
  42. package/dist-web/assets/index-DqybGdJH.css +1 -0
  43. package/dist-web/assets/index-HgIuP1Td.css +1 -0
  44. package/dist-web/assets/index-Qh0XLt3R.js +24 -0
  45. package/dist-web/assets/index-R82N2_0b.css +1 -0
  46. package/dist-web/assets/index-wPAmuQZY.js +11 -0
  47. package/dist-web/assets/{web-DUQU-v8U.js → web-Bx2TiHDk.js} +1 -1
  48. package/dist-web/favicon.ico +0 -0
  49. package/dist-web/index.html +3 -3
  50. package/package.json +1 -1
  51. package/dist/chunk-AZZYKUBR.mjs.map +0 -1
  52. package/dist/chunk-FACKH2BR.mjs.map +0 -1
  53. package/dist/chunk-GUGKY2GV.mjs.map +0 -1
  54. package/dist/chunk-SC77N66X.mjs.map +0 -1
  55. package/dist/update-checker-JIBA7T4U.mjs.map +0 -1
  56. package/dist-web/assets/DebugToolsPanel.vue_vue_type_script_setup_true_lang-CgtTQTfN.js +0 -182
  57. package/dist-web/assets/electron-dBbXcMDB.js +0 -1
  58. package/dist-web/assets/fetch-B-LdQP9s.js +0 -1
  59. package/dist-web/assets/fetch-BNNqdS2B.js +0 -1
  60. package/dist-web/assets/index-8Zcs8peI.css +0 -1
  61. package/dist-web/assets/index-B_lOzUD8.js +0 -2
  62. package/dist-web/assets/index-BvWcJc3c.js +0 -173
  63. package/dist-web/assets/index-CSDI5pAA.js +0 -24
  64. package/dist-web/assets/index-CX2FucK5.css +0 -1
  65. package/dist-web/assets/index-DZ6Szt_y.js +0 -163
  66. package/dist-web/assets/index-DhpclWeF.js +0 -11
  67. package/dist-web/assets/index-Dhql4DJr.css +0 -1
  68. /package/dist/{chat-command-T3Z4O4M5.mjs.map → chat-command-D37MMEJ7.mjs.map} +0 -0
  69. /package/dist/{chunk-MZG2GLJR.mjs.map → chunk-742TAKEP.mjs.map} +0 -0
  70. /package/dist/{chunk-3APSMVOM.mjs.map → chunk-D5MWER7N.mjs.map} +0 -0
  71. /package/dist/{chunk-6O2SW5GL.mjs.map → chunk-GNGZHBKM.mjs.map} +0 -0
  72. /package/dist/{chunk-FAOZNMPK.mjs.map → chunk-VCN64OD6.mjs.map} +0 -0
  73. /package/dist/{http-6LQUOYQ6.mjs.map → http-6F6TYWCI.mjs.map} +0 -0
  74. /package/dist/{import-B2MAIBAK.mjs.map → import-VYPOMSSA.mjs.map} +0 -0
  75. /package/dist/{mcp-ZMMYW46I.mjs.map → mcp-AR7L5FA3.mjs.map} +0 -0
  76. /package/dist/{stream-import-FHFBCEDV.mjs.map → src-6S7JPKHX.mjs.map} +0 -0
@@ -7,28 +7,31 @@ const __dirname = __pathDirname(__filename);
7
7
  import {
8
8
  importData,
9
9
  parseFile
10
- } from "./chunk-3APSMVOM.mjs";
10
+ } from "./chunk-D5MWER7N.mjs";
11
11
  import {
12
12
  analyzeIncrementalImport,
13
13
  analyzeNewImport,
14
14
  incrementalImport,
15
15
  streamImport
16
- } from "./chunk-FAOZNMPK.mjs";
16
+ } from "./chunk-VCN64OD6.mjs";
17
17
  import {
18
18
  assertCliDataDirCompatible
19
- } from "./chunk-MZG2GLJR.mjs";
19
+ } from "./chunk-742TAKEP.mjs";
20
20
  import {
21
21
  getVersion
22
22
  } from "./chunk-5ADKYYNA.mjs";
23
+ import {
24
+ resolveCliPath
25
+ } from "./chunk-FJ35V3TE.mjs";
23
26
  import {
24
27
  closeServerAiLogger,
25
28
  createCliRunAgentStream,
26
29
  initServerAiLogger
27
- } from "./chunk-6O2SW5GL.mjs";
30
+ } from "./chunk-GNGZHBKM.mjs";
28
31
  import {
29
32
  AGENT_TOOL_REGISTRY,
30
33
  CoreDataProvider
31
- } from "./chunk-FACKH2BR.mjs";
34
+ } from "./chunk-RMITK4IC.mjs";
32
35
  import {
33
36
  AIChatManager,
34
37
  ALL_MIGRATIONS,
@@ -71,29 +74,41 @@ import {
71
74
  getAvailableYears,
72
75
  getBuiltinModelsByProvider,
73
76
  getCatchphraseAnalysis,
77
+ getCheckInAnalysis,
74
78
  getClusterGraph,
75
79
  getDailyActivity,
80
+ getDbFileVersion,
76
81
  getDefaultNodeUserDataDir,
77
82
  getDefaultRulesForLocale,
78
83
  getDictList,
84
+ getDivingAnalysis,
85
+ getDragonKingAnalysis,
79
86
  getHourlyActivity,
80
87
  getLanguagePreferenceAnalysis,
81
88
  getLaughAnalysis,
89
+ getLongMessageCount,
82
90
  getMemberActivity,
91
+ getMemberMonthlyTrend,
83
92
  getMembers,
84
93
  getMembersDetailed,
94
+ getMemeBattleAnalysis,
85
95
  getMentionAnalysis,
86
96
  getMentionGraph,
87
97
  getMessageLengthDistribution,
88
98
  getMessageTypeStats,
89
99
  getMonthlyActivity,
100
+ getNightOwlAnalysis,
101
+ getOrComputeAnalysisCache,
90
102
  getPendingNodeDataDirMigration,
91
103
  getRelationshipStats,
104
+ getRepeatAnalysis,
92
105
  getSchemaDetailed,
93
106
  getSessionInfo,
94
107
  getSessionMeta,
95
108
  getSessionOverview,
96
109
  getSupportedFormats,
110
+ getTextLengthPercentiles,
111
+ getTextStats,
97
112
  getTimeRange,
98
113
  getWeekdayActivity,
99
114
  getYearlyActivity,
@@ -104,6 +119,7 @@ import {
104
119
  loadConfig,
105
120
  member_service_exports,
106
121
  mergeRulesForLocale,
122
+ owner_profile_service_exports,
107
123
  queryMessages,
108
124
  resolveApiKey,
109
125
  runSimpleLlmStream,
@@ -119,13 +135,10 @@ import {
119
135
  verifyCliDataPath,
120
136
  writeAuthProfile,
121
137
  writeConfigField
122
- } from "./chunk-GUGKY2GV.mjs";
123
- import {
124
- resolveCliPath
125
- } from "./chunk-FJ35V3TE.mjs";
138
+ } from "./chunk-YPTJVTVJ.mjs";
126
139
 
127
140
  // src/http/index.ts
128
- import * as fs9 from "fs";
141
+ import * as fs11 from "fs";
129
142
  import * as crypto4 from "crypto";
130
143
 
131
144
  // src/http/server.ts
@@ -444,14 +457,25 @@ function registerRestSessionRoutes(server2, ctx) {
444
457
  // ../../packages/http-routes/src/routes/web/sessions.ts
445
458
  function registerSessionRoutes(server2, ctx) {
446
459
  const { sessionAdapter: adapter } = ctx;
460
+ let preferencesInstance = null;
461
+ const preferences = () => {
462
+ preferencesInstance ??= ctx.preferencesManager ?? new PreferencesManager(ctx.pathProvider.getSystemDir());
463
+ return preferencesInstance;
464
+ };
447
465
  server2.get("/_web/sessions", async () => {
448
- return session_service_exports.listAnalysisSessions(adapter);
466
+ const aiChatCounts = ctx.aiChatManager?.getAIChatCountsBySession();
467
+ return session_service_exports.listAnalysisSessions(adapter, {
468
+ enrichSession: aiChatCounts?.size ? (dto) => ({ ...dto, aiConversationCount: aiChatCounts.get(dto.id) ?? 0 }) : void 0
469
+ });
449
470
  });
450
471
  server2.get("/_web/sessions/:id", async (request) => {
451
472
  const session = session_service_exports.getAnalysisSession(adapter, request.params.id);
452
473
  if (!session) {
453
474
  throw Object.assign(new Error(`Session not found: ${request.params.id}`), { statusCode: 404 });
454
475
  }
476
+ if (ctx.aiChatManager) {
477
+ session.aiConversationCount = ctx.aiChatManager.getAIChats(request.params.id).length;
478
+ }
455
479
  return session;
456
480
  });
457
481
  server2.delete("/_web/sessions/:id", async (request, reply) => {
@@ -477,6 +501,24 @@ function registerSessionRoutes(server2, ctx) {
477
501
  return { success: true };
478
502
  }
479
503
  );
504
+ server2.post("/_web/sessions/:id/owner/apply-profile", async (request) => {
505
+ return owner_profile_service_exports.tryApplyOwnerProfile(adapter, preferences(), request.params.id);
506
+ });
507
+ server2.post(
508
+ "/_web/sessions/:id/owner/select",
509
+ async (request) => {
510
+ return owner_profile_service_exports.setOwnerAndApplyProfile(
511
+ adapter,
512
+ preferences(),
513
+ request.params.id,
514
+ request.body.ownerPlatformId
515
+ );
516
+ }
517
+ );
518
+ server2.post("/_web/sessions/:id/owner/dismiss-prompt", async (request) => {
519
+ owner_profile_service_exports.dismissOwnerPrompt(preferences(), request.params.id);
520
+ return { success: true };
521
+ });
480
522
  }
481
523
 
482
524
  // ../../packages/http-routes/src/routes/web/members.ts
@@ -565,6 +607,10 @@ function registerPreferencesRoutes(server2, ctx) {
565
607
  });
566
608
  }
567
609
 
610
+ // ../../packages/http-routes/src/routes/web/analytics.ts
611
+ import * as fs from "fs";
612
+ import * as path2 from "path";
613
+
568
614
  // ../../packages/http-routes/src/helpers.ts
569
615
  function parseTimeFilter(query) {
570
616
  const { startTs, endTs, memberId } = query;
@@ -576,9 +622,32 @@ function parseTimeFilter(query) {
576
622
  return filter;
577
623
  }
578
624
 
625
+ // ../../packages/http-routes/src/analytics-cache.ts
626
+ import * as path from "path";
627
+ function canonical(value) {
628
+ if (value === void 0) return "null";
629
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
630
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
631
+ const obj = value;
632
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
633
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
634
+ }
635
+ function buildAnalyticsCacheKey(namespace, params) {
636
+ return `${namespace}:${canonical(params)}`;
637
+ }
638
+ function withAnalyticsCache(ctx, sessionId, namespace, params, compute, options) {
639
+ const queryCacheDir = path.join(ctx.pathProvider.getCacheDir(), "query");
640
+ const dateStr = options?.dailyInvalidate ? `|date:${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}` : "";
641
+ const extraStr = options?.extraVersion ? `|${options.extraVersion}` : "";
642
+ const version = `${ctx.getVersion()}|${getDbFileVersion(ctx.sessionAdapter.getDbPath(sessionId))}${dateStr}${extraStr}`;
643
+ const key = buildAnalyticsCacheKey(namespace, params);
644
+ return getOrComputeAnalysisCache(sessionId, key, queryCacheDir, version, compute);
645
+ }
646
+
579
647
  // ../../packages/http-routes/src/routes/web/analytics.ts
580
648
  function registerAnalyticsRoutes(server2, ctx) {
581
649
  const { sessionAdapter: adapter } = ctx;
650
+ const cached = (name, sessionId, params, compute, options) => withAnalyticsCache(ctx, sessionId, `analytics.${name}`, params, compute, options);
582
651
  server2.get("/_web/sessions/:id/years", async (request) => {
583
652
  const db = adapter.ensureReadonly(request.params.id);
584
653
  return getAvailableYears(db);
@@ -590,111 +659,252 @@ function registerAnalyticsRoutes(server2, ctx) {
590
659
  server2.get(
591
660
  "/_web/sessions/:id/stats/member-activity",
592
661
  async (request) => {
593
- const db = adapter.ensureReadonly(request.params.id);
594
- return getMemberActivity(db, parseTimeFilter(request.query));
662
+ const id = request.params.id;
663
+ const filter = parseTimeFilter(request.query);
664
+ return cached("member-activity", id, { ...filter }, () => getMemberActivity(adapter.ensureReadonly(id), filter));
595
665
  }
596
666
  );
597
667
  server2.get(
598
668
  "/_web/sessions/:id/stats/hourly",
599
669
  async (request) => {
600
- const db = adapter.ensureReadonly(request.params.id);
601
- return getHourlyActivity(db, parseTimeFilter(request.query));
670
+ const id = request.params.id;
671
+ const filter = parseTimeFilter(request.query);
672
+ return cached("hourly", id, { ...filter }, () => getHourlyActivity(adapter.ensureReadonly(id), filter));
602
673
  }
603
674
  );
604
675
  server2.get(
605
676
  "/_web/sessions/:id/stats/daily",
606
677
  async (request) => {
607
- const db = adapter.ensureReadonly(request.params.id);
608
- return getDailyActivity(db, parseTimeFilter(request.query));
678
+ const id = request.params.id;
679
+ const filter = parseTimeFilter(request.query);
680
+ return cached("daily", id, { ...filter }, () => getDailyActivity(adapter.ensureReadonly(id), filter));
609
681
  }
610
682
  );
611
683
  server2.get(
612
684
  "/_web/sessions/:id/stats/weekday",
613
685
  async (request) => {
614
- const db = adapter.ensureReadonly(request.params.id);
615
- return getWeekdayActivity(db, parseTimeFilter(request.query));
686
+ const id = request.params.id;
687
+ const filter = parseTimeFilter(request.query);
688
+ return cached("weekday", id, { ...filter }, () => getWeekdayActivity(adapter.ensureReadonly(id), filter));
616
689
  }
617
690
  );
618
691
  server2.get(
619
692
  "/_web/sessions/:id/stats/message-types",
620
693
  async (request) => {
621
- const db = adapter.ensureReadonly(request.params.id);
622
- return getMessageTypeStats(db, parseTimeFilter(request.query));
694
+ const id = request.params.id;
695
+ const filter = parseTimeFilter(request.query);
696
+ return cached("message-types", id, { ...filter }, () => getMessageTypeStats(adapter.ensureReadonly(id), filter));
623
697
  }
624
698
  );
625
699
  server2.get(
626
700
  "/_web/sessions/:id/analytics/relationship",
627
701
  async (request) => {
628
- const db = adapter.ensureReadonly(request.params.id);
629
- return getRelationshipStats(db, parseTimeFilter(request.query));
702
+ const id = request.params.id;
703
+ const filter = parseTimeFilter(request.query);
704
+ return cached("relationship", id, { ...filter }, () => getRelationshipStats(adapter.ensureReadonly(id), filter));
630
705
  }
631
706
  );
632
707
  server2.get(
633
708
  "/_web/sessions/:id/analytics/catchphrase",
634
709
  async (request) => {
635
- const db = adapter.ensureReadonly(request.params.id);
636
- return getCatchphraseAnalysis(db, parseTimeFilter(request.query));
710
+ const id = request.params.id;
711
+ const filter = parseTimeFilter(request.query);
712
+ return cached("catchphrase", id, { ...filter }, () => getCatchphraseAnalysis(adapter.ensureReadonly(id), filter));
637
713
  }
638
714
  );
639
715
  server2.get(
640
716
  "/_web/sessions/:id/analytics/mention",
641
717
  async (request) => {
642
- const db = adapter.ensureReadonly(request.params.id);
643
- return getMentionAnalysis(db, parseTimeFilter(request.query));
718
+ const id = request.params.id;
719
+ const filter = parseTimeFilter(request.query);
720
+ return cached("mention", id, { ...filter }, () => getMentionAnalysis(adapter.ensureReadonly(id), filter));
644
721
  }
645
722
  );
646
723
  server2.get(
647
724
  "/_web/sessions/:id/analytics/mention-graph",
648
725
  async (request) => {
649
- const db = adapter.ensureReadonly(request.params.id);
650
- return getMentionGraph(db, parseTimeFilter(request.query));
726
+ const id = request.params.id;
727
+ const filter = parseTimeFilter(request.query);
728
+ return cached("mention-graph", id, { ...filter }, () => getMentionGraph(adapter.ensureReadonly(id), filter));
651
729
  }
652
730
  );
653
731
  server2.get(
654
732
  "/_web/sessions/:id/analytics/laugh",
655
733
  async (request) => {
656
- const db = adapter.ensureReadonly(request.params.id);
657
- return getLaughAnalysis(db, parseTimeFilter(request.query));
658
- }
659
- );
660
- server2.get(
661
- "/_web/sessions/:id/analytics/cluster",
662
- async (request) => {
663
- const db = adapter.ensureReadonly(request.params.id);
734
+ const id = request.params.id;
664
735
  const filter = parseTimeFilter(request.query);
665
- const topEdges = request.query.topEdges ? parseInt(request.query.topEdges, 10) : void 0;
666
- return getClusterGraph(db, filter, topEdges ? { topEdges } : void 0);
736
+ return cached("laugh", id, { ...filter }, () => getLaughAnalysis(adapter.ensureReadonly(id), filter));
667
737
  }
668
738
  );
739
+ server2.get("/_web/sessions/:id/analytics/cluster", async (request) => {
740
+ const id = request.params.id;
741
+ const filter = parseTimeFilter(request.query);
742
+ const options = {};
743
+ if (request.query.topEdges) options.topEdges = parseInt(request.query.topEdges, 10);
744
+ if (request.query.lookAhead) options.lookAhead = parseInt(request.query.lookAhead, 10);
745
+ if (request.query.decaySeconds) options.decaySeconds = parseInt(request.query.decaySeconds, 10);
746
+ const hasOptions = Object.keys(options).length > 0;
747
+ return cached(
748
+ "cluster",
749
+ id,
750
+ { ...filter, ...options },
751
+ () => getClusterGraph(adapter.ensureReadonly(id), filter, hasOptions ? options : void 0)
752
+ );
753
+ });
669
754
  server2.get(
670
755
  "/_web/sessions/:id/analytics/language-preference",
671
756
  async (request) => {
672
- const db = adapter.ensureReadonly(request.params.id);
757
+ const id = request.params.id;
673
758
  const filter = parseTimeFilter(request.query);
674
759
  const locale = request.query.locale || "zh-CN";
675
- const nlpProvider = createJiebaNlpProvider();
676
- return getLanguagePreferenceAnalysis(db, { locale, timeFilter: filter, nlpProvider });
760
+ const zhDictPath = path2.join(ctx.pathProvider.getSystemDir(), "nlp", "zh-CN.dict");
761
+ let zhDictVersion;
762
+ try {
763
+ const st = fs.statSync(zhDictPath);
764
+ zhDictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
765
+ } catch {
766
+ zhDictVersion = "-";
767
+ }
768
+ return cached(
769
+ "language-preference",
770
+ id,
771
+ { ...filter, locale },
772
+ () => getLanguagePreferenceAnalysis(adapter.ensureReadonly(id), {
773
+ locale,
774
+ timeFilter: filter,
775
+ nlpProvider: createJiebaNlpProvider()
776
+ }),
777
+ { extraVersion: `dict:${zhDictVersion}` }
778
+ );
677
779
  }
678
780
  );
679
781
  server2.get(
680
782
  "/_web/sessions/:id/analytics/monthly-activity",
681
783
  async (request) => {
682
- const db = adapter.ensureReadonly(request.params.id);
683
- return getMonthlyActivity(db, parseTimeFilter(request.query));
784
+ const id = request.params.id;
785
+ const filter = parseTimeFilter(request.query);
786
+ return cached("monthly-activity", id, { ...filter }, () => getMonthlyActivity(adapter.ensureReadonly(id), filter));
684
787
  }
685
788
  );
686
789
  server2.get(
687
790
  "/_web/sessions/:id/analytics/yearly-activity",
688
791
  async (request) => {
689
- const db = adapter.ensureReadonly(request.params.id);
690
- return getYearlyActivity(db, parseTimeFilter(request.query));
792
+ const id = request.params.id;
793
+ const filter = parseTimeFilter(request.query);
794
+ return cached("yearly-activity", id, { ...filter }, () => getYearlyActivity(adapter.ensureReadonly(id), filter));
691
795
  }
692
796
  );
693
797
  server2.get(
694
798
  "/_web/sessions/:id/analytics/message-length-distribution",
695
799
  async (request) => {
696
- const db = adapter.ensureReadonly(request.params.id);
697
- return getMessageLengthDistribution(db, parseTimeFilter(request.query));
800
+ const id = request.params.id;
801
+ const filter = parseTimeFilter(request.query);
802
+ return cached(
803
+ "message-length-distribution",
804
+ id,
805
+ { ...filter },
806
+ () => getMessageLengthDistribution(adapter.ensureReadonly(id), filter)
807
+ );
808
+ }
809
+ );
810
+ server2.get(
811
+ "/_web/sessions/:id/analytics/text-stats",
812
+ async (request) => {
813
+ const id = request.params.id;
814
+ const filter = parseTimeFilter(request.query);
815
+ return cached("text-stats", id, { ...filter }, () => getTextStats(adapter.ensureReadonly(id), filter));
816
+ }
817
+ );
818
+ server2.get(
819
+ "/_web/sessions/:id/analytics/long-message-count",
820
+ async (request) => {
821
+ const id = request.params.id;
822
+ const filter = parseTimeFilter(request.query);
823
+ const minLength = request.query.minLength ? parseInt(request.query.minLength, 10) : void 0;
824
+ return cached(
825
+ "long-message-count",
826
+ id,
827
+ { ...filter, minLength },
828
+ () => getLongMessageCount(adapter.ensureReadonly(id), filter, minLength)
829
+ );
830
+ }
831
+ );
832
+ server2.get(
833
+ "/_web/sessions/:id/analytics/member-monthly-trend",
834
+ async (request) => {
835
+ const id = request.params.id;
836
+ const filter = parseTimeFilter(request.query);
837
+ return cached(
838
+ "member-monthly-trend",
839
+ id,
840
+ { ...filter },
841
+ () => getMemberMonthlyTrend(adapter.ensureReadonly(id), filter)
842
+ );
843
+ }
844
+ );
845
+ server2.get(
846
+ "/_web/sessions/:id/analytics/text-length-percentiles",
847
+ async (request) => {
848
+ const id = request.params.id;
849
+ const filter = parseTimeFilter(request.query);
850
+ return cached(
851
+ "text-length-percentiles",
852
+ id,
853
+ { ...filter },
854
+ () => getTextLengthPercentiles(adapter.ensureReadonly(id), filter)
855
+ );
856
+ }
857
+ );
858
+ server2.get(
859
+ "/_web/sessions/:id/analytics/dragon-king",
860
+ async (request) => {
861
+ const id = request.params.id;
862
+ const filter = parseTimeFilter(request.query);
863
+ return cached("dragon-king", id, { ...filter }, () => getDragonKingAnalysis(adapter.ensureReadonly(id), filter));
864
+ }
865
+ );
866
+ server2.get(
867
+ "/_web/sessions/:id/analytics/diving",
868
+ async (request) => {
869
+ const id = request.params.id;
870
+ const filter = parseTimeFilter(request.query);
871
+ return cached("diving", id, { ...filter }, () => getDivingAnalysis(adapter.ensureReadonly(id), filter), {
872
+ dailyInvalidate: true
873
+ });
874
+ }
875
+ );
876
+ server2.get(
877
+ "/_web/sessions/:id/analytics/check-in",
878
+ async (request) => {
879
+ const id = request.params.id;
880
+ const filter = parseTimeFilter(request.query);
881
+ return cached("check-in", id, { ...filter }, () => getCheckInAnalysis(adapter.ensureReadonly(id), filter));
882
+ }
883
+ );
884
+ server2.get(
885
+ "/_web/sessions/:id/analytics/meme-battle",
886
+ async (request) => {
887
+ const id = request.params.id;
888
+ const filter = parseTimeFilter(request.query);
889
+ return cached("meme-battle", id, { ...filter }, () => getMemeBattleAnalysis(adapter.ensureReadonly(id), filter));
890
+ }
891
+ );
892
+ server2.get(
893
+ "/_web/sessions/:id/analytics/night-owl",
894
+ async (request) => {
895
+ const id = request.params.id;
896
+ const filter = parseTimeFilter(request.query);
897
+ return cached("night-owl", id, { ...filter }, () => getNightOwlAnalysis(adapter.ensureReadonly(id), filter), {
898
+ dailyInvalidate: true
899
+ });
900
+ }
901
+ );
902
+ server2.get(
903
+ "/_web/sessions/:id/analytics/repeat",
904
+ async (request) => {
905
+ const id = request.params.id;
906
+ const filter = parseTimeFilter(request.query);
907
+ return cached("repeat", id, { ...filter }, () => getRepeatAnalysis(adapter.ensureReadonly(id), filter));
698
908
  }
699
909
  );
700
910
  }
@@ -799,9 +1009,10 @@ function registerExportRoutes(server2, ctx) {
799
1009
  }
800
1010
 
801
1011
  // ../../packages/http-routes/src/routes/web/nlp.ts
802
- import * as path from "path";
1012
+ import * as fs2 from "fs";
1013
+ import * as path3 from "path";
803
1014
  function registerNlpRoutes(server2, ctx) {
804
- const nlpDir = path.join(ctx.pathProvider.getSystemDir(), "nlp");
1015
+ const nlpDir = path3.join(ctx.pathProvider.getSystemDir(), "nlp");
805
1016
  initNlpDir(nlpDir);
806
1017
  ensureDefaultDict(nlpDir).catch((err) => console.warn("[NLP] Auto-download zh-CN dict failed:", err));
807
1018
  server2.get("/_web/nlp/pos-tags", async () => {
@@ -825,7 +1036,19 @@ function registerNlpRoutes(server2, ctx) {
825
1036
  if (!db) {
826
1037
  throw Object.assign(new Error(`Session not found: ${params.sessionId}`), { statusCode: 404 });
827
1038
  }
828
- return computeWordFrequency(db, params);
1039
+ const { sessionId, ...keyParams } = params;
1040
+ const effectiveDictId = !params.dictType || params.dictType === "default" ? "zh-CN" : String(params.dictType);
1041
+ const dictFilePath = path3.join(nlpDir, `${effectiveDictId}.dict`);
1042
+ let dictVersion;
1043
+ try {
1044
+ const st = fs2.statSync(dictFilePath);
1045
+ dictVersion = `${Math.floor(st.mtimeMs)}:${st.size}`;
1046
+ } catch {
1047
+ dictVersion = "-";
1048
+ }
1049
+ return withAnalyticsCache(ctx, sessionId, "nlp.word-frequency", keyParams, () => computeWordFrequency(db, params), {
1050
+ extraVersion: `dict:${dictVersion}`
1051
+ });
829
1052
  });
830
1053
  server2.post(
831
1054
  "/_web/nlp/segment",
@@ -1390,9 +1613,9 @@ function registerAiSummaryRoutes(server2, ctx) {
1390
1613
  }
1391
1614
 
1392
1615
  // ../../packages/http-routes/src/routes/web/merge.ts
1393
- import * as fs from "fs";
1616
+ import * as fs3 from "fs";
1394
1617
  import * as os from "os";
1395
- import * as path2 from "path";
1618
+ import * as path4 from "path";
1396
1619
  function ensureDb2(ctx, sessionId) {
1397
1620
  const db = ctx.dbManager.open(sessionId);
1398
1621
  if (!db) throw sessionNotFound(sessionId);
@@ -1409,31 +1632,31 @@ function registerMergeRoutes(server2, ctx) {
1409
1632
  if (contentType.includes("multipart/form-data")) {
1410
1633
  const data = await request.file();
1411
1634
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1412
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merge-"));
1413
- const tmpPath = path2.join(tmpDir, data.filename || "upload");
1635
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merge-"));
1636
+ const tmpPath = path4.join(tmpDir, data.filename || "upload");
1414
1637
  const chunks = [];
1415
1638
  for await (const chunk of data.file) {
1416
1639
  chunks.push(chunk);
1417
1640
  }
1418
- fs.writeFileSync(tmpPath, Buffer.concat(chunks));
1641
+ fs3.writeFileSync(tmpPath, Buffer.concat(chunks));
1419
1642
  filePath = tmpPath;
1420
1643
  cleanupFile = true;
1421
1644
  cleanupDir = tmpDir;
1422
1645
  } else {
1423
1646
  const body = request.body;
1424
1647
  if (!body?.filePath) return reply.code(400).send({ error: "Missing filePath in body" });
1425
- if (!fs.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1648
+ if (!fs3.existsSync(body.filePath)) return reply.code(400).send({ error: "File not found" });
1426
1649
  filePath = body.filePath;
1427
1650
  }
1428
1651
  try {
1429
1652
  const result = await streamParseFileInfo(filePath, {
1430
1653
  createTempDatabase(sourceFilePath) {
1431
- return mergeCache.createTempDatabase(path2.basename(sourceFilePath));
1654
+ return mergeCache.createTempDatabase(path4.basename(sourceFilePath));
1432
1655
  },
1433
1656
  onProgress() {
1434
1657
  }
1435
1658
  });
1436
- const filename = path2.basename(filePath);
1659
+ const filename = path4.basename(filePath);
1437
1660
  const handle = mergeCache.store(filename, result.tempDbPath);
1438
1661
  return {
1439
1662
  handle,
@@ -1447,13 +1670,13 @@ function registerMergeRoutes(server2, ctx) {
1447
1670
  } finally {
1448
1671
  if (cleanupFile) {
1449
1672
  try {
1450
- fs.unlinkSync(filePath);
1673
+ fs3.unlinkSync(filePath);
1451
1674
  } catch {
1452
1675
  }
1453
1676
  }
1454
1677
  if (cleanupDir) {
1455
1678
  try {
1456
- fs.rmdirSync(cleanupDir);
1679
+ fs3.rmdirSync(cleanupDir);
1457
1680
  } catch {
1458
1681
  }
1459
1682
  }
@@ -1502,20 +1725,20 @@ function registerMergeRoutes(server2, ctx) {
1502
1725
  return reply.code(501).send({ error: "Import not available on this server" });
1503
1726
  }
1504
1727
  const jsonData = JSON.stringify(merged.chatLabData);
1505
- const tmpDir = fs.mkdtempSync(path2.join(os.tmpdir(), "chatlab-merged-"));
1506
- const safeName = path2.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1507
- const tmpPath = path2.join(tmpDir, `${safeName}.json`);
1508
- fs.writeFileSync(tmpPath, jsonData, "utf-8");
1728
+ const tmpDir = fs3.mkdtempSync(path4.join(os.tmpdir(), "chatlab-merged-"));
1729
+ const safeName = path4.basename(outputName).replace(/[/\\?%*:|"<>]/g, "_") || "merged";
1730
+ const tmpPath = path4.join(tmpDir, `${safeName}.json`);
1731
+ fs3.writeFileSync(tmpPath, jsonData, "utf-8");
1509
1732
  try {
1510
1733
  const importResult = await streamImport2(dbManager2, tmpPath);
1511
1734
  sessionId = importResult.sessionId;
1512
1735
  } finally {
1513
1736
  try {
1514
- fs.unlinkSync(tmpPath);
1737
+ fs3.unlinkSync(tmpPath);
1515
1738
  } catch {
1516
1739
  }
1517
1740
  try {
1518
- fs.rmdirSync(tmpDir);
1741
+ fs3.rmdirSync(tmpDir);
1519
1742
  } catch {
1520
1743
  }
1521
1744
  }
@@ -1589,16 +1812,16 @@ function registerMergeRoutes(server2, ctx) {
1589
1812
  }
1590
1813
 
1591
1814
  // ../../packages/http-routes/src/routes/web/cache.ts
1592
- import * as fs2 from "fs";
1815
+ import * as fs4 from "fs";
1593
1816
  import * as fsp from "fs/promises";
1594
- import * as path3 from "path";
1817
+ import * as path5 from "path";
1595
1818
  async function getDirSize(dirPath) {
1596
1819
  let totalSize = 0;
1597
1820
  try {
1598
- if (!fs2.existsSync(dirPath)) return 0;
1821
+ if (!fs4.existsSync(dirPath)) return 0;
1599
1822
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1600
1823
  for (const file of files) {
1601
- const filePath = path3.join(dirPath, file.name);
1824
+ const filePath = path5.join(dirPath, file.name);
1602
1825
  if (file.isDirectory()) {
1603
1826
  totalSize += await getDirSize(filePath);
1604
1827
  } else {
@@ -1613,10 +1836,10 @@ async function getDirSize(dirPath) {
1613
1836
  async function getFileCount(dirPath) {
1614
1837
  let count = 0;
1615
1838
  try {
1616
- if (!fs2.existsSync(dirPath)) return 0;
1839
+ if (!fs4.existsSync(dirPath)) return 0;
1617
1840
  const files = await fsp.readdir(dirPath, { withFileTypes: true });
1618
1841
  for (const file of files) {
1619
- const filePath = path3.join(dirPath, file.name);
1842
+ const filePath = path5.join(dirPath, file.name);
1620
1843
  if (file.isDirectory()) {
1621
1844
  count += await getFileCount(filePath);
1622
1845
  } else {
@@ -1629,10 +1852,10 @@ async function getFileCount(dirPath) {
1629
1852
  }
1630
1853
  function hasChatLabDatabases(dirPath) {
1631
1854
  try {
1632
- const markerPath = path3.join(dirPath, ".chatlab");
1633
- const dbDir = path3.join(dirPath, "databases");
1634
- if (!fs2.existsSync(markerPath) || !fs2.existsSync(dbDir)) return false;
1635
- return fs2.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1855
+ const markerPath = path5.join(dirPath, ".chatlab");
1856
+ const dbDir = path5.join(dirPath, "databases");
1857
+ if (!fs4.existsSync(markerPath) || !fs4.existsSync(dbDir)) return false;
1858
+ return fs4.readdirSync(dbDir).some((file) => file.endsWith(".db"));
1636
1859
  } catch {
1637
1860
  return false;
1638
1861
  }
@@ -1678,7 +1901,7 @@ function registerCacheRoutes(server2, ctx) {
1678
1901
  const results = await Promise.all(
1679
1902
  directories.map(async (dir) => {
1680
1903
  const [size, fileCount] = await Promise.all([getDirSize(dir.path), getFileCount(dir.path)]);
1681
- return { ...dir, size, fileCount, exists: fs2.existsSync(dir.path) };
1904
+ return { ...dir, size, fileCount, exists: fs4.existsSync(dir.path) };
1682
1905
  })
1683
1906
  );
1684
1907
  return {
@@ -1695,10 +1918,10 @@ function registerCacheRoutes(server2, ctx) {
1695
1918
  };
1696
1919
  const dirPath = allowedDirs[cacheId];
1697
1920
  if (!dirPath) return { success: false, error: "Not allowed to clear this directory" };
1698
- if (!fs2.existsSync(dirPath)) return { success: true };
1921
+ if (!fs4.existsSync(dirPath)) return { success: true };
1699
1922
  const files = await fsp.readdir(dirPath);
1700
1923
  for (const file of files) {
1701
- const filePath = path3.join(dirPath, file);
1924
+ const filePath = path5.join(dirPath, file);
1702
1925
  const stat2 = await fsp.stat(filePath);
1703
1926
  if (stat2.isDirectory()) {
1704
1927
  await fsp.rm(filePath, { recursive: true });
@@ -1715,7 +1938,7 @@ function registerCacheRoutes(server2, ctx) {
1715
1938
  defaultPath: ctx.defaultUserDataDir,
1716
1939
  isCustom: ctx.isCustomDataDir ?? false,
1717
1940
  canSetDataDir: ctx.canSetDataDir ?? Boolean(ctx.setDataDir),
1718
- hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path3.resolve(pp.getUserDataDir()) !== path3.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1941
+ hasLegacyDataAtDefaultDir: Boolean(ctx.defaultUserDataDir) && path5.resolve(pp.getUserDataDir()) !== path5.resolve(ctx.defaultUserDataDir ?? "") && hasChatLabDatabases(ctx.defaultUserDataDir ?? ""),
1719
1942
  pendingMigration: pending ? {
1720
1943
  from: pending.from,
1721
1944
  to: pending.to,
@@ -1732,8 +1955,8 @@ function registerCacheRoutes(server2, ctx) {
1732
1955
  return ctx.setDataDir(targetPath, migrate);
1733
1956
  });
1734
1957
  server2.get("/_web/cache/latest-import-log", async () => {
1735
- const importLogDir = path3.join(pp.getLogsDir(), "import");
1736
- if (!fs2.existsSync(importLogDir)) {
1958
+ const importLogDir = path5.join(pp.getLogsDir(), "import");
1959
+ if (!fs4.existsSync(importLogDir)) {
1737
1960
  return { success: false, error: "Log directory not found" };
1738
1961
  }
1739
1962
  const files = await fsp.readdir(importLogDir);
@@ -1743,7 +1966,7 @@ function registerCacheRoutes(server2, ctx) {
1743
1966
  }
1744
1967
  const fileStats = await Promise.all(
1745
1968
  logFiles.map(async (f) => {
1746
- const filePath = path3.join(importLogDir, f);
1969
+ const filePath = path5.join(importLogDir, f);
1747
1970
  const stat2 = await fsp.stat(filePath);
1748
1971
  return { name: f, path: filePath, mtime: stat2.mtime.getTime() };
1749
1972
  })
@@ -1767,11 +1990,11 @@ function registerCacheRoutes(server2, ctx) {
1767
1990
  const base64Data = dataUrl.replace(/^data:[^,]+,/, "");
1768
1991
  buffer = Buffer.from(base64Data, "base64");
1769
1992
  }
1770
- if (!fs2.existsSync(downloadsDir)) {
1771
- fs2.mkdirSync(downloadsDir, { recursive: true });
1993
+ if (!fs4.existsSync(downloadsDir)) {
1994
+ fs4.mkdirSync(downloadsDir, { recursive: true });
1772
1995
  }
1773
- const filePath = path3.join(downloadsDir, filename);
1774
- fs2.writeFileSync(filePath, buffer);
1996
+ const filePath = path5.join(downloadsDir, filename);
1997
+ fs4.writeFileSync(filePath, buffer);
1775
1998
  return { success: true, filePath };
1776
1999
  });
1777
2000
  server2.post("/_web/cache/open-dir", async (request, reply) => {
@@ -1787,7 +2010,7 @@ function registerCacheRoutes(server2, ctx) {
1787
2010
  };
1788
2011
  const dirPath = dirPaths[cacheId];
1789
2012
  if (!dirPath) return { success: false, error: "Unknown directory" };
1790
- if (!fs2.existsSync(dirPath)) {
2013
+ if (!fs4.existsSync(dirPath)) {
1791
2014
  await fsp.mkdir(dirPath, { recursive: true });
1792
2015
  }
1793
2016
  try {
@@ -1812,40 +2035,41 @@ function registerCacheRoutes(server2, ctx) {
1812
2035
 
1813
2036
  // ../../packages/http-routes/src/register.ts
1814
2037
  function registerSharedRoutes(server2, ctx, options) {
1815
- registerSystemRoutes(server2, ctx);
1816
- registerRestSessionRoutes(server2, ctx);
1817
- registerSessionRoutes(server2, ctx);
1818
- registerMemberRoutes(server2, ctx);
1819
- registerPreferencesRoutes(server2, ctx);
1820
- registerAnalyticsRoutes(server2, ctx);
1821
- registerSqlRoutes(server2, ctx);
1822
- registerSessionIndexRoutes(server2, ctx);
1823
- registerExportRoutes(server2, ctx);
1824
- registerNlpRoutes(server2, ctx);
2038
+ const resolvedCtx = ctx.preferencesManager ? ctx : { ...ctx, preferencesManager: new PreferencesManager(ctx.pathProvider.getSystemDir()) };
2039
+ registerSystemRoutes(server2, resolvedCtx);
2040
+ registerRestSessionRoutes(server2, resolvedCtx);
2041
+ registerSessionRoutes(server2, resolvedCtx);
2042
+ registerMemberRoutes(server2, resolvedCtx);
2043
+ registerPreferencesRoutes(server2, resolvedCtx);
2044
+ registerAnalyticsRoutes(server2, resolvedCtx);
2045
+ registerSqlRoutes(server2, resolvedCtx);
2046
+ registerSessionIndexRoutes(server2, resolvedCtx);
2047
+ registerExportRoutes(server2, resolvedCtx);
2048
+ registerNlpRoutes(server2, resolvedCtx);
1825
2049
  if (options?.requireAi) {
1826
2050
  const missing = [];
1827
- if (!ctx.aiDataDir) missing.push("aiDataDir");
1828
- if (!ctx.aiChatManager) missing.push("aiChatManager");
1829
- if (!ctx.assistantManager) missing.push("assistantManager");
1830
- if (!ctx.skillManagerCore) missing.push("skillManagerCore");
1831
- if (!ctx.llmConfigStore) missing.push("llmConfigStore");
1832
- if (!ctx.customProviderStore) missing.push("customProviderStore");
1833
- if (!ctx.customModelStore) missing.push("customModelStore");
1834
- if (!ctx.runAgentStream) missing.push("runAgentStream");
2051
+ if (!resolvedCtx.aiDataDir) missing.push("aiDataDir");
2052
+ if (!resolvedCtx.aiChatManager) missing.push("aiChatManager");
2053
+ if (!resolvedCtx.assistantManager) missing.push("assistantManager");
2054
+ if (!resolvedCtx.skillManagerCore) missing.push("skillManagerCore");
2055
+ if (!resolvedCtx.llmConfigStore) missing.push("llmConfigStore");
2056
+ if (!resolvedCtx.customProviderStore) missing.push("customProviderStore");
2057
+ if (!resolvedCtx.customModelStore) missing.push("customModelStore");
2058
+ if (!resolvedCtx.runAgentStream) missing.push("runAgentStream");
1835
2059
  if (missing.length > 0) {
1836
2060
  throw new Error(`[http-routes] requireAi is set but missing AI dependencies: ${missing.join(", ")}`);
1837
2061
  }
1838
2062
  }
1839
- registerAiAssistantRoutes(server2, ctx);
1840
- registerAiSkillRoutes(server2, ctx);
1841
- registerAiLlmRoutes(server2, ctx);
1842
- registerAiLlmStreamRoutes(server2, ctx);
1843
- registerAiAgentStreamRoutes(server2, ctx);
1844
- registerAiToolRoutes(server2, ctx);
1845
- registerAiChatRoutes(server2, ctx);
1846
- registerAiSummaryRoutes(server2, ctx);
1847
- registerMergeRoutes(server2, ctx);
1848
- registerCacheRoutes(server2, ctx);
2063
+ registerAiAssistantRoutes(server2, resolvedCtx);
2064
+ registerAiSkillRoutes(server2, resolvedCtx);
2065
+ registerAiLlmRoutes(server2, resolvedCtx);
2066
+ registerAiLlmStreamRoutes(server2, resolvedCtx);
2067
+ registerAiAgentStreamRoutes(server2, resolvedCtx);
2068
+ registerAiToolRoutes(server2, resolvedCtx);
2069
+ registerAiChatRoutes(server2, resolvedCtx);
2070
+ registerAiSummaryRoutes(server2, resolvedCtx);
2071
+ registerMergeRoutes(server2, resolvedCtx);
2072
+ registerCacheRoutes(server2, resolvedCtx);
1849
2073
  }
1850
2074
 
1851
2075
  // ../../packages/http-routes/src/auth.ts
@@ -1923,19 +2147,19 @@ function createServer() {
1923
2147
 
1924
2148
  // src/http/routes/web/index.ts
1925
2149
  import * as os3 from "os";
1926
- import * as path6 from "path";
2150
+ import * as path8 from "path";
1927
2151
 
1928
2152
  // src/http/routes/web/import.ts
1929
- import * as fs4 from "fs";
2153
+ import * as fs6 from "fs";
1930
2154
  import * as os2 from "os";
1931
- import * as path4 from "path";
2155
+ import * as path6 from "path";
1932
2156
 
1933
2157
  // src/http/routes/web/helpers.ts
1934
- import * as fs3 from "fs";
2158
+ import * as fs5 from "fs";
1935
2159
  function resolveNativeBinding() {
1936
2160
  if (process.versions.electron) return void 0;
1937
2161
  const nativePath = resolveCliPath("native/better_sqlite3.node");
1938
- if (fs3.existsSync(nativePath)) return nativePath;
2162
+ if (fs5.existsSync(nativePath)) return nativePath;
1939
2163
  return void 0;
1940
2164
  }
1941
2165
 
@@ -1944,11 +2168,11 @@ var DEMO_BASE_URL = "https://chatlab.fun/assets/demo";
1944
2168
  function cleanupTemp(...paths) {
1945
2169
  for (const p of paths) {
1946
2170
  try {
1947
- const stat2 = fs4.statSync(p);
2171
+ const stat2 = fs6.statSync(p);
1948
2172
  if (stat2.isDirectory()) {
1949
- fs4.rmSync(p, { recursive: true, force: true });
2173
+ fs6.rmSync(p, { recursive: true, force: true });
1950
2174
  } else {
1951
- fs4.unlinkSync(p);
2175
+ fs6.unlinkSync(p);
1952
2176
  }
1953
2177
  } catch {
1954
2178
  }
@@ -1961,14 +2185,14 @@ function registerImportRoutes(server2, dbManager2) {
1961
2185
  server2.post("/_web/detect-format", async (request, reply) => {
1962
2186
  const data = await request.file();
1963
2187
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1964
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-detect-"));
1965
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2188
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-detect-"));
2189
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1966
2190
  try {
1967
2191
  const chunks = [];
1968
2192
  for await (const chunk of data.file) {
1969
2193
  chunks.push(chunk);
1970
2194
  }
1971
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2195
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
1972
2196
  const format = detectFormat(tmpPath);
1973
2197
  const allFormats = detectAllFormats(tmpPath);
1974
2198
  return { format, allFormats };
@@ -1979,14 +2203,14 @@ function registerImportRoutes(server2, dbManager2) {
1979
2203
  server2.post("/_web/scan-multi-chat", async (request, reply) => {
1980
2204
  const data = await request.file();
1981
2205
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1982
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-scan-"));
1983
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2206
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-scan-"));
2207
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
1984
2208
  try {
1985
2209
  const chunks = [];
1986
2210
  for await (const chunk of data.file) {
1987
2211
  chunks.push(chunk);
1988
2212
  }
1989
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2213
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
1990
2214
  const chats = await scanMultiChatFile(tmpPath);
1991
2215
  return { chats };
1992
2216
  } finally {
@@ -1996,13 +2220,13 @@ function registerImportRoutes(server2, dbManager2) {
1996
2220
  server2.post("/_web/import", async (request, reply) => {
1997
2221
  const data = await request.file();
1998
2222
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
1999
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-import-"));
2000
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2223
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-import-"));
2224
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2001
2225
  const chunks = [];
2002
2226
  for await (const chunk of data.file) {
2003
2227
  chunks.push(chunk);
2004
2228
  }
2005
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2229
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2006
2230
  const formatId = data.fields?.formatId?.value;
2007
2231
  const chatIndexStr = data.fields?.chatIndex?.value;
2008
2232
  const chatIndex = chatIndexStr !== void 0 ? parseInt(chatIndexStr, 10) : void 0;
@@ -2045,7 +2269,7 @@ data: ${JSON.stringify(eventData)}
2045
2269
  server2.post("/_web/import-directory", async (request, reply) => {
2046
2270
  const parts = request.parts();
2047
2271
  if (!parts) return reply.code(400).send({ error: "No files uploaded" });
2048
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-dir-import-"));
2272
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-dir-import-"));
2049
2273
  const relativePaths = [];
2050
2274
  const fileBuffers = [];
2051
2275
  try {
@@ -2073,10 +2297,10 @@ data: ${JSON.stringify(eventData)}
2073
2297
  if (segments.length > 1) {
2074
2298
  relPath = segments.slice(1).join("/");
2075
2299
  }
2076
- const targetPath = path4.resolve(tmpDir, relPath);
2077
- if (!targetPath.startsWith(tmpDir + path4.sep)) continue;
2078
- fs4.mkdirSync(path4.dirname(targetPath), { recursive: true });
2079
- fs4.writeFileSync(targetPath, fileBuffers[i].data);
2300
+ const targetPath = path6.resolve(tmpDir, relPath);
2301
+ if (!targetPath.startsWith(tmpDir + path6.sep)) continue;
2302
+ fs6.mkdirSync(path6.dirname(targetPath), { recursive: true });
2303
+ fs6.writeFileSync(targetPath, fileBuffers[i].data);
2080
2304
  }
2081
2305
  const entryPath = findEntryFileInDirectory(tmpDir);
2082
2306
  if (!entryPath) {
@@ -2121,13 +2345,13 @@ data: ${JSON.stringify({ success: false, error: err instanceof Error ? err.messa
2121
2345
  const sessionId = request.params.id;
2122
2346
  const data = await request.file();
2123
2347
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2124
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-inc-"));
2125
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2348
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-inc-"));
2349
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2126
2350
  const chunks = [];
2127
2351
  for await (const chunk of data.file) {
2128
2352
  chunks.push(chunk);
2129
2353
  }
2130
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2354
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2131
2355
  reply.raw.writeHead(200, {
2132
2356
  "Content-Type": "text/event-stream",
2133
2357
  "Cache-Control": "no-cache",
@@ -2159,13 +2383,13 @@ data: ${JSON.stringify(eventData)}
2159
2383
  const sessionId = request.params.id;
2160
2384
  const data = await request.file();
2161
2385
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2162
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2163
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2386
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2387
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2164
2388
  const chunks = [];
2165
2389
  for await (const chunk of data.file) {
2166
2390
  chunks.push(chunk);
2167
2391
  }
2168
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2392
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2169
2393
  try {
2170
2394
  return await analyzeIncrementalImport(dbManager2, sessionId, tmpPath);
2171
2395
  } catch (err) {
@@ -2177,13 +2401,13 @@ data: ${JSON.stringify(eventData)}
2177
2401
  server2.post("/_web/import/analyze", async (request, reply) => {
2178
2402
  const data = await request.file();
2179
2403
  if (!data) return reply.code(400).send({ error: "No file uploaded" });
2180
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-analyze-"));
2181
- const tmpPath = path4.join(tmpDir, data.filename || "upload");
2404
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-analyze-"));
2405
+ const tmpPath = path6.join(tmpDir, data.filename || "upload");
2182
2406
  const chunks = [];
2183
2407
  for await (const chunk of data.file) {
2184
2408
  chunks.push(chunk);
2185
2409
  }
2186
- fs4.writeFileSync(tmpPath, Buffer.concat(chunks));
2410
+ fs6.writeFileSync(tmpPath, Buffer.concat(chunks));
2187
2411
  try {
2188
2412
  return await analyzeNewImport(tmpPath);
2189
2413
  } catch (err) {
@@ -2213,17 +2437,17 @@ data: ${JSON.stringify(eventData)}
2213
2437
 
2214
2438
  `);
2215
2439
  }
2216
- const tmpDir = fs4.mkdtempSync(path4.join(os2.tmpdir(), "chatlab-demo-"));
2440
+ const tmpDir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "chatlab-demo-"));
2217
2441
  try {
2218
2442
  const localPaths = [];
2219
2443
  for (let i = 0; i < total; i++) {
2220
2444
  sendEvent("progress", { stage: "downloading", current: i + 1, total });
2221
- const localPath = path4.join(tmpDir, DEMO_FILES[i]);
2445
+ const localPath = path6.join(tmpDir, DEMO_FILES[i]);
2222
2446
  const resp = await fetch(`${DEMO_BASE_URL}/${locale}/${DEMO_FILES[i]}`, {
2223
2447
  signal: AbortSignal.timeout(6e4)
2224
2448
  });
2225
2449
  if (!resp.ok) throw new Error(`Download demo failed (${DEMO_FILES[i]}): ${resp.status}`);
2226
- fs4.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2450
+ fs6.writeFileSync(localPath, Buffer.from(await resp.arrayBuffer()));
2227
2451
  localPaths.push(localPath);
2228
2452
  }
2229
2453
  sendEvent("progress", { stage: "importing", current: 1, total });
@@ -2254,7 +2478,7 @@ data: ${JSON.stringify(eventData)}
2254
2478
  }
2255
2479
 
2256
2480
  // src/http/routes/web/cache.ts
2257
- import * as path5 from "path";
2481
+ import * as path7 from "path";
2258
2482
  import { execFile } from "child_process";
2259
2483
  function runExecFile(file, args, runner = execFile) {
2260
2484
  return new Promise((resolve4, reject) => {
@@ -2282,7 +2506,7 @@ async function showPathInFolder(filePath, platform = process.platform, runner) {
2282
2506
  } else if (platform === "win32") {
2283
2507
  await runExecFile("explorer.exe", [`/select,${filePath}`], runner);
2284
2508
  } else {
2285
- await runExecFile("xdg-open", [path5.dirname(filePath)], runner);
2509
+ await runExecFile("xdg-open", [path7.dirname(filePath)], runner);
2286
2510
  }
2287
2511
  }
2288
2512
 
@@ -2305,26 +2529,26 @@ function registerWebRoutes(server2, dbManager2, options) {
2305
2529
  const mergeCache = options?.pathProvider ? new MergeSessionCache(options.pathProvider, { nativeBinding: options.nativeBinding }) : null;
2306
2530
  mergeCache?.cleanupOrphans();
2307
2531
  const fallbackPathProvider = {
2308
- getSystemDir: () => path6.join(os3.homedir(), ".chatlab"),
2309
- getUserDataDir: () => path6.join(os3.homedir(), ".chatlab", "data"),
2310
- getDatabaseDir: () => path6.join(os3.homedir(), ".chatlab", "data", "databases"),
2311
- getAiDataDir: () => path6.join(os3.homedir(), ".chatlab", "ai"),
2312
- getSettingsDir: () => path6.join(os3.homedir(), ".chatlab", "settings"),
2313
- getCacheDir: () => path6.join(os3.homedir(), ".chatlab", "cache"),
2314
- getTempDir: () => path6.join(os3.homedir(), ".chatlab", "temp"),
2315
- getLogsDir: () => path6.join(os3.homedir(), ".chatlab", "logs"),
2316
- getDownloadsDir: () => path6.join(os3.homedir(), "Downloads")
2532
+ getSystemDir: () => path8.join(os3.homedir(), ".chatlab"),
2533
+ getUserDataDir: () => path8.join(os3.homedir(), ".chatlab", "data"),
2534
+ getDatabaseDir: () => path8.join(os3.homedir(), ".chatlab", "data", "databases"),
2535
+ getAiDataDir: () => path8.join(os3.homedir(), ".chatlab", "ai"),
2536
+ getSettingsDir: () => path8.join(os3.homedir(), ".chatlab", "settings"),
2537
+ getCacheDir: () => path8.join(os3.homedir(), ".chatlab", "cache"),
2538
+ getTempDir: () => path8.join(os3.homedir(), ".chatlab", "temp"),
2539
+ getLogsDir: () => path8.join(os3.homedir(), ".chatlab", "logs"),
2540
+ getDownloadsDir: () => path8.join(os3.homedir(), "Downloads")
2317
2541
  };
2318
2542
  const resolvedPathProvider = options?.pathProvider ?? fallbackPathProvider;
2319
2543
  const ai = options?.aiContext;
2320
2544
  const cliStreamImport = async (dm, filePath) => {
2321
- const { streamImport: streamImport2 } = await import("./stream-import-FHFBCEDV.mjs");
2545
+ const { streamImport: streamImport2 } = await import("./stream-import-KZFC3XT2.mjs");
2322
2546
  const result = await streamImport2(dm, filePath);
2323
2547
  if (!result.sessionId) throw new Error("Import succeeded but no sessionId returned");
2324
2548
  return { sessionId: result.sessionId };
2325
2549
  };
2326
2550
  const defaultUserDataDir = getDefaultNodeUserDataDir();
2327
- const isCustom = path6.resolve(resolvedPathProvider.getUserDataDir()) !== path6.resolve(defaultUserDataDir);
2551
+ const isCustom = path8.resolve(resolvedPathProvider.getUserDataDir()) !== path8.resolve(defaultUserDataDir);
2328
2552
  registerSharedRoutes(
2329
2553
  server2,
2330
2554
  {
@@ -2415,31 +2639,31 @@ function registerProxyRoutes(server2) {
2415
2639
  }
2416
2640
 
2417
2641
  // src/ai/manager-factory.ts
2418
- import * as fs5 from "fs";
2419
- import * as path7 from "path";
2642
+ import * as fs7 from "fs";
2643
+ import * as path9 from "path";
2420
2644
  import { createHash, randomUUID } from "crypto";
2421
2645
  var nodeFs = {
2422
2646
  ensureDir(dir) {
2423
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
2647
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2424
2648
  },
2425
2649
  listFiles(dir, ext) {
2426
- if (!fs5.existsSync(dir)) return [];
2427
- return fs5.readdirSync(dir).filter((f) => f.endsWith(ext));
2650
+ if (!fs7.existsSync(dir)) return [];
2651
+ return fs7.readdirSync(dir).filter((f) => f.endsWith(ext));
2428
2652
  },
2429
2653
  readFile(filePath) {
2430
- return fs5.readFileSync(filePath, "utf-8");
2654
+ return fs7.readFileSync(filePath, "utf-8");
2431
2655
  },
2432
2656
  writeFile(filePath, content) {
2433
- fs5.writeFileSync(filePath, content, "utf-8");
2657
+ fs7.writeFileSync(filePath, content, "utf-8");
2434
2658
  },
2435
2659
  deleteFile(filePath) {
2436
- if (fs5.existsSync(filePath)) fs5.unlinkSync(filePath);
2660
+ if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
2437
2661
  },
2438
2662
  fileExists(filePath) {
2439
- return fs5.existsSync(filePath);
2663
+ return fs7.existsSync(filePath);
2440
2664
  },
2441
2665
  joinPath(...parts) {
2442
- return path7.join(...parts);
2666
+ return path9.join(...parts);
2443
2667
  }
2444
2668
  };
2445
2669
  var managerCache = /* @__PURE__ */ new Map();
@@ -2449,13 +2673,13 @@ function getAssistantManager(aiDataDir) {
2449
2673
  cached = {
2450
2674
  assistant: new AssistantManager({
2451
2675
  fs: nodeFs,
2452
- assistantsDir: path7.join(aiDataDir, "assistants"),
2676
+ assistantsDir: path9.join(aiDataDir, "assistants"),
2453
2677
  builtinRawConfigs: [],
2454
2678
  generateId: () => `custom_${randomUUID().replace(/-/g, "").slice(0, 12)}`
2455
2679
  }),
2456
2680
  skill: new SkillManagerCore({
2457
2681
  fs: nodeFs,
2458
- skillsDir: path7.join(aiDataDir, "skills"),
2682
+ skillsDir: path9.join(aiDataDir, "skills"),
2459
2683
  builtinRawSkills: [],
2460
2684
  contentHash: (content) => createHash("md5").update(content).digest("hex")
2461
2685
  })
@@ -2479,8 +2703,8 @@ var noop = () => {
2479
2703
  var NOOP_LOGGER = { info: noop, warn: noop, error: noop };
2480
2704
 
2481
2705
  // ../../packages/sync/src/data-source-manager.ts
2482
- import * as fs6 from "fs";
2483
- import * as path8 from "path";
2706
+ import * as fs8 from "fs";
2707
+ import * as path10 from "path";
2484
2708
  import * as crypto from "crypto";
2485
2709
  var CONFIG_FILE = "data-sources.json";
2486
2710
  function generateId(prefix = "ds") {
@@ -2508,17 +2732,17 @@ var DataSourceManager = class {
2508
2732
  this.logger = logger ?? NOOP_LOGGER;
2509
2733
  }
2510
2734
  getConfigPath() {
2511
- return path8.join(this.configDir, CONFIG_FILE);
2735
+ return path10.join(this.configDir, CONFIG_FILE);
2512
2736
  }
2513
2737
  ensureDir() {
2514
- fs6.mkdirSync(this.configDir, { recursive: true });
2738
+ fs8.mkdirSync(this.configDir, { recursive: true });
2515
2739
  }
2516
2740
  // ==================== Load / Save ====================
2517
2741
  loadAll() {
2518
2742
  try {
2519
2743
  const filePath = this.getConfigPath();
2520
- if (fs6.existsSync(filePath)) {
2521
- const raw = fs6.readFileSync(filePath, "utf-8");
2744
+ if (fs8.existsSync(filePath)) {
2745
+ const raw = fs8.readFileSync(filePath, "utf-8");
2522
2746
  const parsed = JSON.parse(raw);
2523
2747
  if (!isValidDataSourceArray(parsed)) {
2524
2748
  this.logger.warn("[DataSource] Incompatible config format detected, returning empty.");
@@ -2537,7 +2761,7 @@ var DataSourceManager = class {
2537
2761
  saveAll(sources) {
2538
2762
  try {
2539
2763
  this.ensureDir();
2540
- fs6.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2764
+ fs8.writeFileSync(this.getConfigPath(), JSON.stringify(sources, null, 2), "utf-8");
2541
2765
  } catch (err) {
2542
2766
  this.logger.error("[DataSource] Failed to save config", err);
2543
2767
  }
@@ -2662,7 +2886,7 @@ function parseRemoteSessionsResponse(body) {
2662
2886
  }
2663
2887
 
2664
2888
  // ../../packages/sync/src/pull-engine.ts
2665
- import * as fs7 from "fs";
2889
+ import * as fs9 from "fs";
2666
2890
  import * as crypto2 from "crypto";
2667
2891
  var MAX_PAGES_PER_PULL = 5e3;
2668
2892
  var PULL_OVERLAP_SECONDS = 60;
@@ -2682,7 +2906,7 @@ function parseSyncFromFile(filePath) {
2682
2906
  try {
2683
2907
  const isJsonl = filePath.endsWith(".jsonl");
2684
2908
  if (isJsonl) {
2685
- const content = fs7.readFileSync(filePath, "utf-8");
2909
+ const content = fs9.readFileSync(filePath, "utf-8");
2686
2910
  const lines = content.trimEnd().split("\n");
2687
2911
  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
2688
2912
  try {
@@ -2701,7 +2925,7 @@ function parseSyncFromFile(filePath) {
2701
2925
  }
2702
2926
  return null;
2703
2927
  }
2704
- const raw = fs7.readFileSync(filePath, "utf-8");
2928
+ const raw = fs9.readFileSync(filePath, "utf-8");
2705
2929
  const parsed = JSON.parse(raw);
2706
2930
  if (parsed.sync && typeof parsed.sync === "object") {
2707
2931
  const s = parsed.sync;
@@ -2715,7 +2939,7 @@ function parseSyncFromFile(filePath) {
2715
2939
  function fileContainsMessages(filePath) {
2716
2940
  try {
2717
2941
  if (filePath.endsWith(".jsonl")) {
2718
- const content = fs7.readFileSync(filePath, "utf-8");
2942
+ const content = fs9.readFileSync(filePath, "utf-8");
2719
2943
  for (const line of content.split("\n")) {
2720
2944
  const trimmed = line.trim();
2721
2945
  if (!trimmed) continue;
@@ -2728,7 +2952,7 @@ function fileContainsMessages(filePath) {
2728
2952
  }
2729
2953
  return false;
2730
2954
  }
2731
- const raw = fs7.readFileSync(filePath, "utf-8");
2955
+ const raw = fs9.readFileSync(filePath, "utf-8");
2732
2956
  const parsed = JSON.parse(raw);
2733
2957
  return Array.isArray(parsed.messages) && parsed.messages.length > 0;
2734
2958
  } catch {
@@ -2737,7 +2961,7 @@ function fileContainsMessages(filePath) {
2737
2961
  }
2738
2962
  function cleanupTempFile(filePath) {
2739
2963
  try {
2740
- if (fs7.existsSync(filePath)) fs7.unlinkSync(filePath);
2964
+ if (fs9.existsSync(filePath)) fs9.unlinkSync(filePath);
2741
2965
  } catch {
2742
2966
  }
2743
2967
  }
@@ -2748,6 +2972,7 @@ var PullEngine = class {
2748
2972
  dsManager;
2749
2973
  logger;
2750
2974
  isImporting;
2975
+ onSessionImported;
2751
2976
  pullingSourceIds = /* @__PURE__ */ new Set();
2752
2977
  progressMap = /* @__PURE__ */ new Map();
2753
2978
  constructor(options) {
@@ -2757,6 +2982,7 @@ var PullEngine = class {
2757
2982
  this.dsManager = options.dsManager;
2758
2983
  this.logger = options.logger ?? NOOP_LOGGER;
2759
2984
  this.isImporting = options.isImporting ?? (() => false);
2985
+ this.onSessionImported = options.onSessionImported;
2760
2986
  }
2761
2987
  getProgress() {
2762
2988
  return Array.from(this.progressMap.values());
@@ -2797,7 +3023,7 @@ var PullEngine = class {
2797
3023
  limit: ds.pullLimit
2798
3024
  });
2799
3025
  try {
2800
- const stat2 = fs7.statSync(tempFile);
3026
+ const stat2 = fs9.statSync(tempFile);
2801
3027
  this.logger.info(`[Pull] "${sess.name}" page ${pageCount}: fetched ${stat2.size} bytes`);
2802
3028
  const sync0 = parseSyncFromFile(tempFile);
2803
3029
  if (stat2.size < 1024) {
@@ -2815,7 +3041,7 @@ var PullEngine = class {
2815
3041
  since,
2816
3042
  limit: ds.pullLimit
2817
3043
  });
2818
- const retryStat = fs7.statSync(retryFile);
3044
+ const retryStat = fs9.statSync(retryFile);
2819
3045
  this.logger.info(`[Pull] "${sess.name}" retry ${ri + 1}: fetched ${retryStat.size} bytes`);
2820
3046
  const retrySync = parseSyncFromFile(retryFile);
2821
3047
  if (retryStat.size < 1024 && !fileContainsMessages(retryFile) && retrySync?.hasMore === false) {
@@ -2954,6 +3180,13 @@ var PullEngine = class {
2954
3180
  if (totalNewMessages > 0) this.notifier.onSessionListChanged();
2955
3181
  this.notifier.onPullResult(sourceId, sess.id, "success", `+${totalNewMessages} messages`);
2956
3182
  this.markProgressDone(sess.id);
3183
+ if (this.onSessionImported && sess.targetSessionId) {
3184
+ try {
3185
+ this.onSessionImported(sess.targetSessionId);
3186
+ } catch (hookErr) {
3187
+ this.logger.warn(`[Pull] onSessionImported hook failed for "${sess.name}": ${hookErr}`);
3188
+ }
3189
+ }
2957
3190
  return { success: true, newMessageCount: totalNewMessages };
2958
3191
  } catch (error) {
2959
3192
  const errMsg = error.message || "Pull failed";
@@ -3089,13 +3322,13 @@ function reloadTimer(dsId, skipInitialPull = false) {
3089
3322
  }
3090
3323
 
3091
3324
  // src/sync/adapters.ts
3092
- import * as fs8 from "fs";
3325
+ import * as fs10 from "fs";
3093
3326
  import * as os4 from "os";
3094
- import * as path9 from "path";
3327
+ import * as path11 from "path";
3095
3328
  import * as crypto3 from "crypto";
3096
3329
  function getTempFilePath(ext) {
3097
3330
  const id = crypto3.randomBytes(8).toString("hex");
3098
- return path9.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3331
+ return path11.join(os4.tmpdir(), `chatlab-pull-${id}${ext}`);
3099
3332
  }
3100
3333
  var NodeFetcher = class {
3101
3334
  async fetchToTempFile(baseUrl, remoteSessionId, token, params) {
@@ -3112,7 +3345,7 @@ var NodeFetcher = class {
3112
3345
  const isJsonl = contentType.includes("ndjson") || contentType.includes("jsonl");
3113
3346
  const tempFile = getTempFilePath(isJsonl ? ".jsonl" : ".json");
3114
3347
  const buffer = Buffer.from(await response.arrayBuffer());
3115
- fs8.writeFileSync(tempFile, buffer);
3348
+ fs10.writeFileSync(tempFile, buffer);
3116
3349
  return tempFile;
3117
3350
  }
3118
3351
  };
@@ -3125,7 +3358,7 @@ var DirectImporter = class {
3125
3358
  }
3126
3359
  sessionExists(sessionId) {
3127
3360
  const dbPath = this.dbManager.getDbPath(sessionId);
3128
- if (!fs8.existsSync(dbPath)) return false;
3361
+ if (!fs10.existsSync(dbPath)) return false;
3129
3362
  try {
3130
3363
  const db = this.dbManager.openRawSessionDatabase(sessionId, { readonly: true });
3131
3364
  const row = db.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name='message'").get();
@@ -3133,7 +3366,7 @@ var DirectImporter = class {
3133
3366
  if (row.cnt === 0) {
3134
3367
  this.logger.warn(`[DirectImporter] DB file exists but has no message table: ${sessionId}, removing`);
3135
3368
  try {
3136
- fs8.unlinkSync(dbPath);
3369
+ fs10.unlinkSync(dbPath);
3137
3370
  } catch {
3138
3371
  }
3139
3372
  return false;
@@ -3143,7 +3376,7 @@ var DirectImporter = class {
3143
3376
  if (error instanceof DataDirCompatibilityError) throw error;
3144
3377
  this.logger.warn(`[DirectImporter] Cannot validate DB file: ${sessionId}, removing`);
3145
3378
  try {
3146
- fs8.unlinkSync(dbPath);
3379
+ fs10.unlinkSync(dbPath);
3147
3380
  } catch {
3148
3381
  }
3149
3382
  return false;
@@ -3259,10 +3492,7 @@ function registerAutomationRoutes(server2, ctx) {
3259
3492
  if (!removed) return reply.code(404).send({ error: "Session not found" });
3260
3493
  reloadTimer(request.params.id);
3261
3494
  if (request.query.deleteData === "true" && removed.targetSessionId) {
3262
- dbManager2.close(removed.targetSessionId);
3263
- const dbPath = dbManager2.getDbPath(removed.targetSessionId);
3264
- const fs10 = await import("fs");
3265
- if (fs10.existsSync(dbPath)) fs10.unlinkSync(dbPath);
3495
+ dbManager2.deleteSessionDatabaseFiles(removed.targetSessionId);
3266
3496
  }
3267
3497
  return { success: true };
3268
3498
  });
@@ -3314,12 +3544,21 @@ function initSync(server2, dbManager2, pathProvider, serverInfo) {
3314
3544
  const fetcher = new NodeFetcher();
3315
3545
  const importer = new DirectImporter(dbManager2, syncLogger);
3316
3546
  const notifier = new NoopNotifier();
3547
+ const sessionAdapter = createDatabaseManagerAdapter(dbManager2);
3548
+ const preferences = new PreferencesManager(pathProvider.getSystemDir());
3317
3549
  pullEngine = new PullEngine({
3318
3550
  fetcher,
3319
3551
  importer,
3320
3552
  notifier,
3321
3553
  dsManager,
3322
- logger: syncLogger
3554
+ logger: syncLogger,
3555
+ onSessionImported: (localSessionId) => {
3556
+ preferences.invalidateCache();
3557
+ const result = owner_profile_service_exports.tryApplyOwnerProfile(sessionAdapter, preferences, localSessionId);
3558
+ if (result.applied) {
3559
+ syncLogger.info(`[Pull] Applied owner profile to session ${localSessionId} (owner: ${result.ownerId})`);
3560
+ }
3561
+ }
3323
3562
  });
3324
3563
  registerAutomationRoutes(server2, { dsManager, pullEngine, dbManager: dbManager2, serverInfo });
3325
3564
  initScheduler({
@@ -3342,21 +3581,21 @@ function createFileConfigStorage(aiDataDir) {
3342
3581
  return {
3343
3582
  readJson(key) {
3344
3583
  try {
3345
- return JSON.parse(fs9.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3584
+ return JSON.parse(fs11.readFileSync(`${aiDataDir}/${key}.json`, "utf-8"));
3346
3585
  } catch {
3347
3586
  return null;
3348
3587
  }
3349
3588
  },
3350
3589
  writeJson(key, data) {
3351
- if (!fs9.existsSync(aiDataDir)) fs9.mkdirSync(aiDataDir, { recursive: true });
3352
- fs9.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3590
+ if (!fs11.existsSync(aiDataDir)) fs11.mkdirSync(aiDataDir, { recursive: true });
3591
+ fs11.writeFileSync(`${aiDataDir}/${key}.json`, JSON.stringify(data, null, 2), "utf-8");
3353
3592
  }
3354
3593
  };
3355
3594
  }
3356
3595
  function resolveNativeBinding2() {
3357
3596
  if (process.versions.electron) return void 0;
3358
3597
  const nativePath = resolveCliPath("native/better_sqlite3.node");
3359
- if (fs9.existsSync(nativePath)) return nativePath;
3598
+ if (fs11.existsSync(nativePath)) return nativePath;
3360
3599
  return void 0;
3361
3600
  }
3362
3601
  function ensureToken(config) {
@@ -3442,7 +3681,7 @@ async function startHttpServer(options) {
3442
3681
  }
3443
3682
  });
3444
3683
  initSync(server, dbManager, pathProvider, { port, host, token });
3445
- if (options?.webRoot && fs9.existsSync(options.webRoot)) {
3684
+ if (options?.webRoot && fs11.existsSync(options.webRoot)) {
3446
3685
  registerProxyRoutes(server);
3447
3686
  const fastifyStatic = await import("@fastify/static");
3448
3687
  await server.register(fastifyStatic.default, {
@@ -3483,4 +3722,4 @@ export {
3483
3722
  startHttpServer,
3484
3723
  stopHttpServer
3485
3724
  };
3486
- //# sourceMappingURL=chunk-AZZYKUBR.mjs.map
3725
+ //# sourceMappingURL=chunk-QMXNJRTT.mjs.map