chatlab-cli 0.32.0 → 0.34.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 (158) hide show
  1. package/README.md +2 -1
  2. package/dist/{chat-command-3KUWKS53.mjs → chat-command-XPY3WURC.mjs} +9 -9
  3. package/dist/{chunk-LNU4RW5G.mjs → chunk-AQPDZAWB.mjs} +1604 -1327
  4. package/dist/chunk-AQPDZAWB.mjs.map +1 -0
  5. package/dist/{chunk-UVCH2ICD.mjs → chunk-BQ3QN4QT.mjs} +3 -3
  6. package/dist/{chunk-DV4U34MR.mjs → chunk-DGF3S3TZ.mjs} +2 -2
  7. package/dist/{chunk-3J7WTUEE.mjs → chunk-IGEHMLHM.mjs} +2 -2
  8. package/dist/{chunk-CANPJOIE.mjs → chunk-IGK6UMN2.mjs} +112 -16
  9. package/dist/chunk-IGK6UMN2.mjs.map +1 -0
  10. package/dist/{chunk-BLOBEOCG.mjs → chunk-ISRLGLUL.mjs} +2 -2
  11. package/dist/{chunk-27DWKA3L.mjs → chunk-JCSPDTZL.mjs} +15 -62
  12. package/dist/chunk-JCSPDTZL.mjs.map +1 -0
  13. package/dist/{chunk-23AIEWCD.mjs → chunk-JTOHAMIW.mjs} +2 -2
  14. package/dist/{chunk-YVNZ73O5.mjs → chunk-OLE2USEN.mjs} +4492 -4038
  15. package/dist/chunk-OLE2USEN.mjs.map +1 -0
  16. package/dist/{chunk-YAQLYL52.mjs → chunk-QZCNVAH6.mjs} +2 -2
  17. package/dist/{chunk-YAQLYL52.mjs.map → chunk-QZCNVAH6.mjs.map} +1 -1
  18. package/dist/{chunk-3IGBPOJM.mjs → chunk-S5NCEESV.mjs} +5 -4
  19. package/dist/chunk-S5NCEESV.mjs.map +1 -0
  20. package/dist/{chunk-PZM3FHVZ.mjs → chunk-UO2XVFY6.mjs} +37 -26
  21. package/dist/chunk-UO2XVFY6.mjs.map +1 -0
  22. package/dist/cli.mjs +9 -9
  23. package/dist/contacts-worker.mjs +2 -2
  24. package/dist/global-insight-worker.mjs +2 -2
  25. package/dist/{http-YR26GF7X.mjs → http-HV6XWU6X.mjs} +12 -12
  26. package/dist/{import-Q3DUACU4.mjs → import-DP2XLZ2H.mjs} +8 -8
  27. package/dist/index.mjs +12 -12
  28. package/dist/{mcp-HTVZSGTI.mjs → mcp-QDPFBPGW.mjs} +9 -9
  29. package/dist/people-relationships-worker.mjs +2 -2
  30. package/dist/semantic-index-worker.mjs +1 -1
  31. package/dist/{src-HJL7YZZJ.mjs → src-B3A2QHP5.mjs} +12 -10
  32. package/dist/{stream-import-MXETSEPW.mjs → stream-import-KEGNP3QK.mjs} +8 -8
  33. package/dist/{worker-runtime-DZQDL2E2.mjs → worker-runtime-MDYKX3BW.mjs} +6 -6
  34. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-JxPR2O6b.js +110 -0
  35. package/{dist-web/assets/BufferResource-BXfwfGam.js → dist-cli-web/assets/BufferResource--dSbJb0k.js} +1 -1
  36. package/dist-cli-web/assets/CanvasRenderer-Cm_dmJXZ.js +1 -0
  37. package/dist-cli-web/assets/DataDirCleanupNotice-B0s5txX3.js +1 -0
  38. package/dist-cli-web/assets/EChartWordcloud-CwMVJLBO.js +1 -0
  39. package/{dist-web/assets/Filter-Irxx0f6j.js → dist-cli-web/assets/Filter-BjYScRpK.js} +1 -1
  40. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-BshQybD_.js +1 -0
  41. package/dist-cli-web/assets/MessageExportModal-DAJW2mFj.js +1 -0
  42. package/dist-cli-web/assets/PageHeader-BKAjCuqs.js +1 -0
  43. package/{dist-web/assets/RelationshipGalaxyCanvas-ByDU6ZS_.js → dist-cli-web/assets/RelationshipGalaxyCanvas-mlZs7k4L.js} +14 -14
  44. package/{dist-web/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-CeYJABxI.css} +1 -1
  45. package/{dist-web/assets/RelationshipGalaxyThreeCanvas-DgAlyKjc.js → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-DgMG3jdU.js} +14 -14
  46. package/{dist-web/assets/RenderTargetSystem-Cx5U5HAM.js → dist-cli-web/assets/RenderTargetSystem-D00Ex44y.js} +1 -1
  47. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-Dsb6lzs-.js +1 -0
  48. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-BfR3tlkz.js +1 -0
  49. package/{dist-web/assets/WebGLRenderer-B3Fnp1WX.js → dist-cli-web/assets/WebGLRenderer-CTTusm2n.js} +2 -2
  50. package/{dist-web/assets/WebGPURenderer-DZ0X9N86.js → dist-cli-web/assets/WebGPURenderer-pyv3EXor.js} +2 -2
  51. package/dist-cli-web/assets/annual-summary-time-range-C_d1oZrj.js +1 -0
  52. package/dist-cli-web/assets/base-D91aVUwM.js +1 -0
  53. package/{dist-web/assets/browserAll-BqC9ELNO.js → dist-cli-web/assets/browserAll-C9fW936Y.js} +1 -1
  54. package/dist-cli-web/assets/cli-web-C7uGKz_E.js +1 -0
  55. package/{dist-web/assets/dateFormat-CX9atL2g.js → dist-cli-web/assets/dateFormat-rY43adcv.js} +1 -1
  56. package/{dist-web/assets/fetch-CUT9JdXT.js → dist-cli-web/assets/fetch-C6po4XYB.js} +3 -3
  57. package/dist-cli-web/assets/fetch-CNy9OQ2g.js +1 -0
  58. package/dist-cli-web/assets/fetch-CS3T-v-1.js +1 -0
  59. package/{dist-web/assets/fetch-C3KuMfIU.js → dist-cli-web/assets/fetch-CbnX5VoM.js} +3 -3
  60. package/dist-cli-web/assets/fetch-CrSz5KJy.js +1 -0
  61. package/dist-cli-web/assets/fetch-DG3AeEjW.js +1 -0
  62. package/dist-cli-web/assets/fetch-DYNgtIcU.js +1 -0
  63. package/{dist-web/assets/fetch-th_egarZ.js → dist-cli-web/assets/fetch-DZPC3D8R.js} +2 -2
  64. package/dist-cli-web/assets/fetch-Kk2knkEo.js +1 -0
  65. package/dist-cli-web/assets/fetch-PLrI3V67.js +1 -0
  66. package/dist-cli-web/assets/index-2-OaYdzt.js +1 -0
  67. package/dist-cli-web/assets/index-2S80bo1s.js +2 -0
  68. package/dist-cli-web/assets/index-3YO6RI5e.js +2 -0
  69. package/dist-cli-web/assets/index-BKo68Iu5.js +1 -0
  70. package/{dist-web/assets/index-Bm_U0hSr.js → dist-cli-web/assets/index-BrkDgcJ6.js} +4 -4
  71. package/dist-cli-web/assets/index-CGI3Y8pq.js +1 -0
  72. package/dist-cli-web/assets/index-CW9H_fBd.js +53 -0
  73. package/dist-cli-web/assets/index-DA3Q0TGT.js +1 -0
  74. package/dist-cli-web/assets/index-DFHYr_-u.js +1 -0
  75. package/dist-cli-web/assets/index-DmrrdYXH.css +1 -0
  76. package/dist-cli-web/assets/index-DmwA85vG.js +173 -0
  77. package/dist-cli-web/assets/index-DrjoMv9s.css +1 -0
  78. package/dist-cli-web/assets/index-DsdMsKYT.js +1 -0
  79. package/dist-cli-web/assets/index-JYfjMRb-.js +1 -0
  80. package/dist-cli-web/assets/index-Jhwca9kg.css +1 -0
  81. package/dist-cli-web/assets/index-ZTgfCZvY.js +64 -0
  82. package/dist-cli-web/assets/index-vtF05fh7.css +1 -0
  83. package/dist-cli-web/assets/people-page-header-D-LWaEqn.js +1 -0
  84. package/{dist-web/assets/webworkerAll-a_fveCLR.js → dist-cli-web/assets/webworkerAll-CPOVECup.js} +1 -1
  85. package/{dist-web → dist-cli-web}/index.html +2 -4
  86. package/package.json +4 -4
  87. package/dist/chunk-27DWKA3L.mjs.map +0 -1
  88. package/dist/chunk-3IGBPOJM.mjs.map +0 -1
  89. package/dist/chunk-CANPJOIE.mjs.map +0 -1
  90. package/dist/chunk-LNU4RW5G.mjs.map +0 -1
  91. package/dist/chunk-PZM3FHVZ.mjs.map +0 -1
  92. package/dist/chunk-YVNZ73O5.mjs.map +0 -1
  93. package/dist-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-DfQa9E2W.js +0 -110
  94. package/dist-web/assets/CanvasRenderer-Bvpsbxds.js +0 -1
  95. package/dist-web/assets/DataDirCleanupNotice-C0sCmNL2.js +0 -1
  96. package/dist-web/assets/EChartWordcloud-C5hLT1vS.js +0 -1
  97. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-8s6XRB3S.js +0 -1
  98. package/dist-web/assets/MessageExportModal-tTI358HU.js +0 -1
  99. package/dist-web/assets/PageHeader-D5QQI9io.js +0 -1
  100. package/dist-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-CpNjFhpm.js +0 -1
  101. package/dist-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-DtCKfbaz.js +0 -1
  102. package/dist-web/assets/annual-summary-time-range-CvG74N2l.js +0 -1
  103. package/dist-web/assets/base-DdQRTliS.js +0 -1
  104. package/dist-web/assets/electron-ChQkD5ht.js +0 -1
  105. package/dist-web/assets/electron-DGxVMsvC.js +0 -1
  106. package/dist-web/assets/electron-hcWiB7Gg.js +0 -1
  107. package/dist-web/assets/fetch-3Ifr5YJN.js +0 -1
  108. package/dist-web/assets/fetch-74o_kRZf.js +0 -1
  109. package/dist-web/assets/fetch-BKFznq4l.js +0 -1
  110. package/dist-web/assets/fetch-BPj6IlT-.js +0 -1
  111. package/dist-web/assets/fetch-CoYaXr5K.js +0 -1
  112. package/dist-web/assets/fetch-Dcg6Q6Ei.js +0 -1
  113. package/dist-web/assets/fetch-DxWfYzkB.js +0 -1
  114. package/dist-web/assets/index-B3riRaJA.js +0 -1
  115. package/dist-web/assets/index-BKFGuwJu.css +0 -1
  116. package/dist-web/assets/index-BRDdozcm.js +0 -173
  117. package/dist-web/assets/index-Bg-OiHMa.js +0 -1
  118. package/dist-web/assets/index-BhewGKey.js +0 -2
  119. package/dist-web/assets/index-BpeJ40uK.js +0 -1
  120. package/dist-web/assets/index-C6bwt1H4.js +0 -1
  121. package/dist-web/assets/index-CEJqRkis.js +0 -2
  122. package/dist-web/assets/index-CKLCaqdT.js +0 -1
  123. package/dist-web/assets/index-D8Uq3_Dc.css +0 -1
  124. package/dist-web/assets/index-DBb03wVF.css +0 -1
  125. package/dist-web/assets/index-Ds5PiVyB.js +0 -64
  126. package/dist-web/assets/index-GXMxhYc9.js +0 -1
  127. package/dist-web/assets/index-JyB6Cr8r.js +0 -25
  128. package/dist-web/assets/index-UJl03IYE.css +0 -1
  129. package/dist-web/assets/index-tx-Oal36.js +0 -1
  130. package/dist-web/assets/people-page-header-DGkK0AMO.js +0 -1
  131. package/dist-web/assets/types-CZnACqaB.js +0 -1
  132. package/dist-web/assets/vendor-nuxt-ui-DMdbdSFy.js +0 -15
  133. package/dist-web/assets/vendor-reka-ui-8_x7iKfi.js +0 -15
  134. package/dist-web/assets/web-aZcioccr.js +0 -1
  135. /package/dist/{chat-command-3KUWKS53.mjs.map → chat-command-XPY3WURC.mjs.map} +0 -0
  136. /package/dist/{chunk-UVCH2ICD.mjs.map → chunk-BQ3QN4QT.mjs.map} +0 -0
  137. /package/dist/{chunk-DV4U34MR.mjs.map → chunk-DGF3S3TZ.mjs.map} +0 -0
  138. /package/dist/{chunk-3J7WTUEE.mjs.map → chunk-IGEHMLHM.mjs.map} +0 -0
  139. /package/dist/{chunk-BLOBEOCG.mjs.map → chunk-ISRLGLUL.mjs.map} +0 -0
  140. /package/dist/{chunk-23AIEWCD.mjs.map → chunk-JTOHAMIW.mjs.map} +0 -0
  141. /package/dist/{http-YR26GF7X.mjs.map → http-HV6XWU6X.mjs.map} +0 -0
  142. /package/dist/{import-Q3DUACU4.mjs.map → import-DP2XLZ2H.mjs.map} +0 -0
  143. /package/dist/{mcp-HTVZSGTI.mjs.map → mcp-QDPFBPGW.mjs.map} +0 -0
  144. /package/dist/{src-HJL7YZZJ.mjs.map → src-B3A2QHP5.mjs.map} +0 -0
  145. /package/dist/{stream-import-MXETSEPW.mjs.map → stream-import-KEGNP3QK.mjs.map} +0 -0
  146. /package/dist/{worker-runtime-DZQDL2E2.mjs.map → worker-runtime-MDYKX3BW.mjs.map} +0 -0
  147. /package/{dist-web → dist-cli-web}/assets/ActionToolsPanel-Dp-ojXjO.css +0 -0
  148. /package/{dist-web → dist-cli-web}/assets/PageHeader-XIXMTomn.css +0 -0
  149. /package/{dist-web → dist-cli-web}/assets/index-BfO_LpDv.css +0 -0
  150. /package/{dist-web → dist-cli-web}/assets/index-ByUaiVQK.css +0 -0
  151. /package/{dist-web → dist-cli-web}/assets/index-ZsZwPLWy.css +0 -0
  152. /package/{dist-web → dist-cli-web}/assets/logo-DXIuG_WE.svg +0 -0
  153. /package/{dist-web → dist-cli-web}/assets/relationship-galaxy-viewport-Cwfa51ER.js +0 -0
  154. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-B-i_56Av.js +0 -0
  155. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-wordcloud-Ca8n6qS6.js +0 -0
  156. /package/{dist-web → dist-cli-web}/assets/vendor-snapdom--nZvo8Sg.js +0 -0
  157. /package/{dist-web → dist-cli-web}/assets/vendor-zrender-5DcYjSZk.js +0 -0
  158. /package/{dist-web → dist-cli-web}/favicon.ico +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  createEmptyContactsDiagnostics,
10
10
  normalizeContactsTimeRangePreset,
11
11
  resolveContactsTimeRange
12
- } from "./chunk-3J7WTUEE.mjs";
12
+ } from "./chunk-IGEHMLHM.mjs";
13
13
  import {
14
14
  PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
15
15
  buildPeopleRelationshipsNeighborhoodGraph,
@@ -18,15 +18,17 @@ import {
18
18
  computePeopleRelationshipsSnapshot,
19
19
  normalizePeopleRelationshipsTimeRangePreset,
20
20
  resolvePeopleRelationshipsTimeRange
21
- } from "./chunk-23AIEWCD.mjs";
21
+ } from "./chunk-JTOHAMIW.mjs";
22
22
  import {
23
23
  ANNUAL_SUMMARY_ALGORITHM_VERSION
24
- } from "./chunk-YAQLYL52.mjs";
24
+ } from "./chunk-QZCNVAH6.mjs";
25
25
  import {
26
26
  BUILTIN_MODELS,
27
27
  BUILTIN_PROVIDERS,
28
28
  BUILTIN_TOOL_CATALOG,
29
29
  CHART_CAPABILITY_SKILL_ID,
30
+ CHATLAB_FORMAT_VERSION,
31
+ CHATLAB_SUPPORTED_FORMAT_VERSIONS,
30
32
  CHAT_DB_INDEXES,
31
33
  CHAT_DB_SCHEMA,
32
34
  DEFAULT_GENERAL_ASSISTANT_ID,
@@ -34,6 +36,7 @@ import {
34
36
  FTS_TABLE_SCHEMA,
35
37
  GENERAL_ASSISTANT_IDS,
36
38
  KNOWN_PLATFORMS,
39
+ MessageType,
37
40
  StreamingThinkTagParser,
38
41
  appLogger,
39
42
  batchSegmentChineseWithStats,
@@ -48,11 +51,14 @@ import {
48
51
  deleteAuthProfile,
49
52
  deleteMember,
50
53
  deleteMembers,
54
+ deriveAuthProfileName,
51
55
  generateIncrementalSessionIndex,
52
56
  generateMessageKey,
53
57
  generateSessionIndex,
54
58
  getAnnualSummarySnapshotPath,
59
+ getBuiltinModelById,
55
60
  getBuiltinModelsByProvider,
61
+ getBuiltinProviderById,
56
62
  getChatSessionList,
57
63
  getContactsDir,
58
64
  getContactsFactsCacheDir,
@@ -76,6 +82,8 @@ import {
76
82
  isChatSessionDb,
77
83
  isNameMatchPlatform,
78
84
  isReasoningModel,
85
+ isSupportedChatLabFormatVersion,
86
+ loadAuthProfiles,
79
87
  matchOwnerProfile,
80
88
  mergeConfirmedNames,
81
89
  mergeMembers,
@@ -84,6 +92,7 @@ import {
84
92
  normalizeBuiltinToolNames,
85
93
  openBetterSqliteDatabase,
86
94
  readAnnualSummarySnapshot,
95
+ readJsonFile,
87
96
  renameSession,
88
97
  resolveApiKey,
89
98
  saveSegmentSummary,
@@ -96,9 +105,12 @@ import {
96
105
  truncateToolResultText,
97
106
  updateMemberAliases,
98
107
  updateSessionOwnerId,
108
+ withFileLock,
99
109
  writeAnnualSummarySnapshot,
100
- writeAuthProfile
101
- } from "./chunk-YVNZ73O5.mjs";
110
+ writeAuthProfile,
111
+ writeJsonFileAtomically,
112
+ writeParseResultToDb
113
+ } from "./chunk-OLE2USEN.mjs";
102
114
  import {
103
115
  __commonJS,
104
116
  __export,
@@ -714,8 +726,8 @@ var require_FilterBase = __commonJS({
714
726
  var FilterBase = class _FilterBase extends Transform {
715
727
  static stringFilter(string, separator) {
716
728
  return (stack) => {
717
- const path33 = stack.join(separator);
718
- return path33.length === string.length && path33 === string || path33.length > string.length && path33.substr(0, string.length) === string && path33.substr(string.length, separator.length) === separator;
729
+ const path30 = stack.join(separator);
730
+ return path30.length === string.length && path30 === string || path30.length > string.length && path30.substr(0, string.length) === string && path30.substr(string.length, separator.length) === separator;
719
731
  };
720
732
  }
721
733
  static regExpFilter(regExp, separator) {
@@ -1057,9 +1069,9 @@ var require_stream_chain = __commonJS({
1057
1069
  var require_withParser = __commonJS({
1058
1070
  "../../node_modules/stream-json/utils/withParser.js"(exports, module) {
1059
1071
  "use strict";
1060
- var { chain: chain10 } = require_stream_chain();
1072
+ var { chain: chain9 } = require_stream_chain();
1061
1073
  var Parser = require_Parser();
1062
- var withParser = (fn, options2) => chain10([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1074
+ var withParser = (fn, options2) => chain9([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1063
1075
  module.exports = withParser;
1064
1076
  }
1065
1077
  });
@@ -1137,7 +1149,7 @@ var require_Assembler = __commonJS({
1137
1149
  this.current = new Ctr();
1138
1150
  this.key = null;
1139
1151
  };
1140
- var Assembler = class _Assembler extends EventEmitter {
1152
+ var Assembler2 = class _Assembler extends EventEmitter {
1141
1153
  static connectTo(stream, options2) {
1142
1154
  return new _Assembler(options2).connectTo(stream);
1143
1155
  }
@@ -1169,12 +1181,12 @@ var require_Assembler = __commonJS({
1169
1181
  return (this.stack.length >> 1) + (this.done ? 0 : 1);
1170
1182
  }
1171
1183
  get path() {
1172
- const path33 = [];
1184
+ const path30 = [];
1173
1185
  for (let i = 0; i < this.stack.length; i += 2) {
1174
1186
  const key = this.stack[i + 1];
1175
- path33.push(key === null ? this.stack[i].length : key);
1187
+ path30.push(key === null ? this.stack[i].length : key);
1176
1188
  }
1177
- return path33;
1189
+ return path30;
1178
1190
  }
1179
1191
  dropToLevel(level) {
1180
1192
  if (level < this.depth) {
@@ -1256,11 +1268,11 @@ var require_Assembler = __commonJS({
1256
1268
  }
1257
1269
  }
1258
1270
  };
1259
- Assembler.prototype.stringValue = Assembler.prototype._saveValue;
1260
- Assembler.prototype.startObject = startObject(Object);
1261
- Assembler.prototype.startArray = startObject(Array);
1262
- Assembler.prototype.endArray = Assembler.prototype.endObject;
1263
- module.exports = Assembler;
1271
+ Assembler2.prototype.stringValue = Assembler2.prototype._saveValue;
1272
+ Assembler2.prototype.startObject = startObject(Object);
1273
+ Assembler2.prototype.startArray = startObject(Array);
1274
+ Assembler2.prototype.endArray = Assembler2.prototype.endObject;
1275
+ module.exports = Assembler2;
1264
1276
  }
1265
1277
  });
1266
1278
 
@@ -1269,7 +1281,7 @@ var require_StreamBase = __commonJS({
1269
1281
  "../../node_modules/stream-json/streamers/StreamBase.js"(exports, module) {
1270
1282
  "use strict";
1271
1283
  var { Transform } = __require("stream");
1272
- var Assembler = require_Assembler();
1284
+ var Assembler2 = require_Assembler();
1273
1285
  var Counter = class {
1274
1286
  constructor(initialDepth) {
1275
1287
  this.depth = initialDepth;
@@ -1298,7 +1310,7 @@ var require_StreamBase = __commonJS({
1298
1310
  this._filter = this._transform;
1299
1311
  }
1300
1312
  this._transform = this._wait || this._filter;
1301
- this._assembler = new Assembler(options2);
1313
+ this._assembler = new Assembler2(options2);
1302
1314
  }
1303
1315
  _transform(chunk, encoding, callback) {
1304
1316
  if (this._assembler[chunk.name]) {
@@ -1456,7 +1468,7 @@ var require_pend = __commonJS({
1456
1468
  var require_fd_slicer = __commonJS({
1457
1469
  "../../packages/node-runtime/node_modules/yauzl/fd-slicer.js"(exports) {
1458
1470
  "use strict";
1459
- var fs37 = __require("fs");
1471
+ var fs36 = __require("fs");
1460
1472
  var util = __require("util");
1461
1473
  var stream = __require("stream");
1462
1474
  var Readable = stream.Readable;
@@ -1476,7 +1488,7 @@ var require_fd_slicer = __commonJS({
1476
1488
  FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
1477
1489
  var self = this;
1478
1490
  self.pend.go(function(cb) {
1479
- fs37.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
1491
+ fs36.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
1480
1492
  cb();
1481
1493
  callback(err, bytesRead, buffer2);
1482
1494
  });
@@ -1493,7 +1505,7 @@ var require_fd_slicer = __commonJS({
1493
1505
  self.refCount -= 1;
1494
1506
  if (self.refCount < 0) throw new Error("invalid unref");
1495
1507
  if (self.refCount > 0) return;
1496
- fs37.close(self.fd, onCloseDone);
1508
+ fs36.close(self.fd, onCloseDone);
1497
1509
  function onCloseDone(err) {
1498
1510
  if (err) {
1499
1511
  self.emit("error", err);
@@ -1525,7 +1537,7 @@ var require_fd_slicer = __commonJS({
1525
1537
  }
1526
1538
  self.context.pend.go(function(cb) {
1527
1539
  var buffer = Buffer.allocUnsafe(toRead);
1528
- fs37.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
1540
+ fs36.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
1529
1541
  if (err) {
1530
1542
  self.destroy(err);
1531
1543
  } else if (bytesRead === 0) {
@@ -1887,7 +1899,7 @@ var require_crc32 = __commonJS({
1887
1899
  var require_yauzl = __commonJS({
1888
1900
  "../../packages/node-runtime/node_modules/yauzl/index.js"(exports) {
1889
1901
  "use strict";
1890
- var fs37 = __require("fs");
1902
+ var fs36 = __require("fs");
1891
1903
  var zlib = __require("zlib");
1892
1904
  var fd_slicer = require_fd_slicer();
1893
1905
  var util = __require("util");
@@ -1912,9 +1924,9 @@ var require_yauzl = __commonJS({
1912
1924
  exports.Entry = Entry;
1913
1925
  exports.LocalFileHeader = LocalFileHeader;
1914
1926
  exports.RandomAccessReader = RandomAccessReader;
1915
- function openPromise2(path33, options2) {
1927
+ function openPromise2(path30, options2) {
1916
1928
  return new Promise((resolve4, reject) => {
1917
- open(path33, { ...options2, lazyEntries: true }, function(err, zipfile) {
1929
+ open(path30, { ...options2, lazyEntries: true }, function(err, zipfile) {
1918
1930
  if (err) return reject(err);
1919
1931
  resolve4(zipfile);
1920
1932
  });
@@ -1944,7 +1956,7 @@ var require_yauzl = __commonJS({
1944
1956
  });
1945
1957
  });
1946
1958
  }
1947
- function open(path33, options2, callback) {
1959
+ function open(path30, options2, callback) {
1948
1960
  if (typeof options2 === "function") {
1949
1961
  callback = options2;
1950
1962
  options2 = null;
@@ -1956,10 +1968,10 @@ var require_yauzl = __commonJS({
1956
1968
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
1957
1969
  if (options2.strictFileNames == null) options2.strictFileNames = false;
1958
1970
  if (callback == null) callback = defaultCallback;
1959
- fs37.open(path33, "r", function(err, fd) {
1971
+ fs36.open(path30, "r", function(err, fd) {
1960
1972
  if (err) return callback(err);
1961
1973
  fromFd(fd, options2, function(err2, zipfile) {
1962
- if (err2) fs37.close(fd, defaultCallback);
1974
+ if (err2) fs36.close(fd, defaultCallback);
1963
1975
  callback(err2, zipfile);
1964
1976
  });
1965
1977
  });
@@ -1976,7 +1988,7 @@ var require_yauzl = __commonJS({
1976
1988
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
1977
1989
  if (options2.strictFileNames == null) options2.strictFileNames = false;
1978
1990
  if (callback == null) callback = defaultCallback;
1979
- fs37.fstat(fd, function(err, stats) {
1991
+ fs36.fstat(fd, function(err, stats) {
1980
1992
  if (err) return callback(err);
1981
1993
  var reader = new fd_slicer.FdSlicer(fd);
1982
1994
  fromRandomAccessReader(reader, stats.size, options2, callback);
@@ -2810,122 +2822,9 @@ function removeChatLabTempDir(dirPath, scope, options2 = {}) {
2810
2822
  fs.rmSync(resolvedDir, { recursive: true, force: true });
2811
2823
  }
2812
2824
 
2813
- // ../../packages/node-runtime/src/import/write-parse-result.ts
2814
- function writeParseResultToDb(db, meta, members, messages) {
2815
- let messageCount = 0;
2816
- let skippedCount = 0;
2817
- db.transaction(() => {
2818
- db.prepare(
2819
- `INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id)
2820
- VALUES (?, ?, ?, ?, ?, ?, ?)`
2821
- ).run(
2822
- meta.name,
2823
- meta.platform,
2824
- meta.type,
2825
- Math.floor(Date.now() / 1e3),
2826
- meta.groupId || null,
2827
- meta.groupAvatar || null,
2828
- meta.ownerId || null
2829
- );
2830
- const insertMember = db.prepare(
2831
- `INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles) VALUES (?, ?, ?, ?, ?, ?)`
2832
- );
2833
- const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`);
2834
- const memberIdMap = /* @__PURE__ */ new Map();
2835
- for (const member of members) {
2836
- insertMember.run(
2837
- member.platformId,
2838
- member.accountName || null,
2839
- member.groupNickname || null,
2840
- member.aliases ? JSON.stringify(member.aliases) : "[]",
2841
- member.avatar || null,
2842
- member.roles ? JSON.stringify(member.roles) : "[]"
2843
- );
2844
- const row = getMemberId.get(member.platformId);
2845
- memberIdMap.set(member.platformId, row.id);
2846
- }
2847
- const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
2848
- const accountNameTracker = /* @__PURE__ */ new Map();
2849
- const groupNicknameTracker = /* @__PURE__ */ new Map();
2850
- const insertMessage = db.prepare(
2851
- `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)
2852
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
2853
- );
2854
- const insertNameHistory = db.prepare(
2855
- `INSERT INTO member_name_history (member_id, name_type, name, start_ts, end_ts) VALUES (?, ?, ?, ?, ?)`
2856
- );
2857
- const updateMemberAccountName = db.prepare(`UPDATE member SET account_name = ? WHERE platform_id = ?`);
2858
- const updateMemberGroupNickname = db.prepare(`UPDATE member SET group_nickname = ? WHERE platform_id = ?`);
2859
- const updateNameHistoryEndTs = db.prepare(
2860
- `UPDATE member_name_history SET end_ts = ? WHERE member_id = ? AND name_type = ? AND end_ts IS NULL`
2861
- );
2862
- for (const msg of sortedMessages) {
2863
- const senderId = memberIdMap.get(msg.senderPlatformId);
2864
- if (senderId === void 0) {
2865
- skippedCount++;
2866
- continue;
2867
- }
2868
- insertMessage.run(
2869
- senderId,
2870
- msg.senderAccountName || null,
2871
- msg.senderGroupNickname || null,
2872
- msg.timestamp,
2873
- msg.type,
2874
- msg.content,
2875
- msg.replyToMessageId || null,
2876
- msg.platformMessageId || null
2877
- );
2878
- messageCount++;
2879
- const accountName = msg.senderAccountName;
2880
- if (accountName) {
2881
- const tracker = accountNameTracker.get(msg.senderPlatformId);
2882
- if (!tracker) {
2883
- accountNameTracker.set(msg.senderPlatformId, {
2884
- currentName: accountName,
2885
- lastSeenTs: msg.timestamp
2886
- });
2887
- insertNameHistory.run(senderId, "account_name", accountName, msg.timestamp, null);
2888
- } else if (tracker.currentName !== accountName) {
2889
- updateNameHistoryEndTs.run(msg.timestamp, senderId, "account_name");
2890
- insertNameHistory.run(senderId, "account_name", accountName, msg.timestamp, null);
2891
- tracker.currentName = accountName;
2892
- tracker.lastSeenTs = msg.timestamp;
2893
- } else {
2894
- tracker.lastSeenTs = msg.timestamp;
2895
- }
2896
- }
2897
- const groupNickname = msg.senderGroupNickname;
2898
- if (groupNickname) {
2899
- const tracker = groupNicknameTracker.get(msg.senderPlatformId);
2900
- if (!tracker) {
2901
- groupNicknameTracker.set(msg.senderPlatformId, {
2902
- currentName: groupNickname,
2903
- lastSeenTs: msg.timestamp
2904
- });
2905
- insertNameHistory.run(senderId, "group_nickname", groupNickname, msg.timestamp, null);
2906
- } else if (tracker.currentName !== groupNickname) {
2907
- updateNameHistoryEndTs.run(msg.timestamp, senderId, "group_nickname");
2908
- insertNameHistory.run(senderId, "group_nickname", groupNickname, msg.timestamp, null);
2909
- tracker.currentName = groupNickname;
2910
- tracker.lastSeenTs = msg.timestamp;
2911
- } else {
2912
- tracker.lastSeenTs = msg.timestamp;
2913
- }
2914
- }
2915
- }
2916
- for (const [platformId, tracker] of accountNameTracker.entries()) {
2917
- updateMemberAccountName.run(tracker.currentName, platformId);
2918
- }
2919
- for (const [platformId, tracker] of groupNicknameTracker.entries()) {
2920
- updateMemberGroupNickname.run(tracker.currentName, platformId);
2921
- }
2922
- });
2923
- return { messageCount, memberCount: members.length, skippedCount };
2924
- }
2925
-
2926
2825
  // ../../packages/parser/src/index.ts
2927
2826
  import * as fs18 from "fs";
2928
- import * as path14 from "path";
2827
+ import * as path11 from "path";
2929
2828
 
2930
2829
  // ../../packages/parser/src/sniffer.ts
2931
2830
  import * as fs2 from "fs";
@@ -3315,8 +3214,8 @@ var { pick } = import_Pick.default;
3315
3214
  var { streamValues } = import_StreamValues.default;
3316
3215
  var { chain } = import_stream_chain.default;
3317
3216
  function extractNameFromFilePath(filePath) {
3318
- const basename13 = path3.basename(filePath);
3319
- const name = basename13.replace(/\.json$/i, "");
3217
+ const basename10 = path3.basename(filePath);
3218
+ const name = basename10.replace(/\.json$/i, "");
3320
3219
  return name || "\u672A\u77E5\u7FA4\u804A";
3321
3220
  }
3322
3221
  var feature = {
@@ -3486,8 +3385,8 @@ import * as fs5 from "fs";
3486
3385
  import * as readline from "readline";
3487
3386
  import * as path4 from "path";
3488
3387
  function extractNameFromFilePath2(filePath) {
3489
- const basename13 = path4.basename(filePath);
3490
- const name = basename13.replace(/\.jsonl$/i, "");
3388
+ const basename10 = path4.basename(filePath);
3389
+ const name = basename10.replace(/\.jsonl$/i, "");
3491
3390
  return name || "\u672A\u77E5\u7FA4\u804A";
3492
3391
  }
3493
3392
  function parseLine(line) {
@@ -3509,8 +3408,10 @@ var feature2 = {
3509
3408
  // 低优先级,让其他格式先匹配
3510
3409
  extensions: [".jsonl"],
3511
3410
  signatures: {
3512
- // 第一行必须是 header 类型,包含 chatlab 信息
3513
- head: [/^\s*\{.*"_type"\s*:\s*"header".*"chatlab"/]
3411
+ // 跳过格式允许的空行和注释后,分别匹配首条数据中的 header 类型和 chatlab 对象。
3412
+ head: [
3413
+ /^\uFEFF?(?:[ \t]*(?:#[^\r\n]*)?\r?\n)*[ \t]*\{(?=[^\r\n]*"_type"\s*:\s*"header")(?=[^\r\n]*"chatlab"\s*:)[^\r\n]*\}/
3414
+ ]
3514
3415
  }
3515
3416
  };
3516
3417
  async function* parseChatLabJsonl(options2) {
@@ -3561,7 +3462,9 @@ async function* parseChatLabJsonl(options2) {
3561
3462
  platformId: parsed.platformId,
3562
3463
  accountName: parsed.accountName,
3563
3464
  groupNickname: parsed.groupNickname,
3564
- avatar: parsed.avatar
3465
+ aliases: parsed.aliases,
3466
+ avatar: parsed.avatar,
3467
+ roles: parsed.roles
3565
3468
  };
3566
3469
  members.push(member);
3567
3470
  memberMap.set(parsed.platformId, member);
@@ -3960,8 +3863,8 @@ function extractJsonObject2(content, key) {
3960
3863
  return null;
3961
3864
  }
3962
3865
  function extractNameFromFilePath3(filePath) {
3963
- const basename13 = path6.basename(filePath);
3964
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3866
+ const basename10 = path6.basename(filePath);
3867
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3965
3868
  }
3966
3869
  function isPlaceholderSenderId(id) {
3967
3870
  return id === "0" || id === "\u672A\u77E5";
@@ -4561,8 +4464,8 @@ var { pick: pick4 } = import_Pick4.default;
4561
4464
  var { streamValues: streamValues4 } = import_StreamValues4.default;
4562
4465
  var { chain: chain4 } = import_stream_chain4.default;
4563
4466
  function extractNameFromFilePath4(filePath) {
4564
- const basename13 = path8.basename(filePath);
4565
- const name = basename13.replace(/\.json$/i, "");
4467
+ const basename10 = path8.basename(filePath);
4468
+ const name = basename10.replace(/\.json$/i, "");
4566
4469
  return name || "\u672A\u77E5\u804A\u5929";
4567
4470
  }
4568
4471
  var feature5 = {
@@ -5473,13 +5376,16 @@ var module_8 = {
5473
5376
  var telegram_native_default = module_8;
5474
5377
 
5475
5378
  // ../../packages/parser/src/formats/telegram-native-single.ts
5476
- var import_stream_chain7 = __toESM(require_stream_chain(), 1);
5379
+ var import_Assembler = __toESM(require_Assembler(), 1);
5477
5380
  var import_stream_json7 = __toESM(require_stream_json(), 1);
5478
- var import_StreamValues7 = __toESM(require_StreamValues(), 1);
5479
5381
  import * as fs12 from "fs";
5480
- var { chain: chain7 } = import_stream_chain7.default;
5382
+
5383
+ // ../../packages/parser/src/browser/telegram.ts
5384
+ var TELEGRAM_SINGLE_CHAT_TYPE_PATTERN = /"type"\s*:\s*"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)"/;
5385
+ var TELEGRAM_SINGLE_HEAD_SIGNATURES = [/^\s*\{\s*(?:\r?\n\s*)?"name"\s*:/];
5386
+
5387
+ // ../../packages/parser/src/formats/telegram-native-single.ts
5481
5388
  var { parser: parser8 } = import_stream_json7.default;
5482
- var { streamValues: streamValues7 } = import_StreamValues7.default;
5483
5389
  var feature9 = {
5484
5390
  id: "telegram-native-single",
5485
5391
  name: "Telegram \u5355\u804A\u5929\u5BFC\u51FA (JSON)",
@@ -5487,98 +5393,97 @@ var feature9 = {
5487
5393
  priority: 23,
5488
5394
  extensions: [".json"],
5489
5395
  signatures: {
5490
- // Telegram 单聊天导出特征:文件以 "name" 作为第一个 JSON 键
5491
- // 这与全量导出(以 "about" 开头)有效区分,避免嵌套字段误匹配
5492
- head: [/^\s*\{\s*\r?\n\s*"name"\s*:/],
5396
+ head: TELEGRAM_SINGLE_HEAD_SIGNATURES,
5493
5397
  requiredFields: ["messages"],
5494
- fieldPatterns: {
5495
- // Telegram 特有的聊天类型值,精确区分
5496
- telegramChatType: /"type"\s*:\s*"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)"/
5497
- }
5398
+ fieldPatterns: { telegramChatType: TELEGRAM_SINGLE_CHAT_TYPE_PATTERN }
5498
5399
  }
5499
5400
  };
5500
5401
  async function* parseTelegramSingle(options2) {
5501
5402
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5502
5403
  const totalBytes = getFileSize(filePath);
5503
- let messagesProcessed = 0;
5504
5404
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5505
5405
  yield { type: "progress", data: initialProgress };
5506
5406
  onProgress?.(initialProgress);
5507
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 Telegram \u5355\u804A\u5929 JSON \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5508
- const chatData = await new Promise((resolve4, reject) => {
5509
- const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5510
- const pipeline2 = chain7([readStream, parser8(), streamValues7()]);
5511
- let found = false;
5512
- pipeline2.on("data", ({ value }) => {
5513
- if (!found) {
5514
- found = true;
5515
- resolve4(value);
5516
- }
5517
- });
5518
- pipeline2.on("end", () => {
5519
- if (!found) resolve4(null);
5520
- });
5521
- pipeline2.on("error", reject);
5522
- });
5523
- if (!chatData) {
5524
- onLog?.("error", "\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6");
5525
- yield { type: "error", data: new Error("\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6") };
5526
- return;
5527
- }
5528
- onLog?.("info", `\u804A\u5929: "${chatData.name}", \u7C7B\u578B: ${chatData.type}, \u6D88\u606F\u6570: ${chatData.messages?.length || 0}`);
5529
- const chatType = mapChatType(chatData.type);
5530
- const meta = {
5531
- name: chatData.name || `Telegram Chat ${chatData.id}`,
5532
- platform: KNOWN_PLATFORMS.TELEGRAM,
5533
- type: chatType,
5534
- groupId: chatType === "group" /* GROUP */ ? String(chatData.id) : void 0
5535
- };
5536
- yield { type: "meta", data: meta };
5407
+ onLog?.("info", `Starting Telegram single-chat JSON parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5408
+ let messagesProcessed = 0;
5537
5409
  const memberMap = /* @__PURE__ */ new Map();
5538
5410
  const messageBatch = [];
5539
- const messages = chatData.messages || [];
5540
- for (const msg of messages) {
5541
- let senderPlatformId;
5542
- let senderName;
5543
- if (msg.type === "service") {
5544
- senderPlatformId = extractPlatformId(msg.actor_id);
5545
- senderName = msg.actor || "\u7CFB\u7EDF";
5546
- } else {
5547
- senderPlatformId = extractPlatformId(msg.from_id);
5548
- senderName = msg.from || senderPlatformId;
5549
- }
5550
- if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
5551
- memberMap.set(senderPlatformId, {
5552
- platformId: senderPlatformId,
5553
- accountName: senderName
5554
- });
5411
+ try {
5412
+ const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5413
+ const tokenStream = readStream.pipe(parser8());
5414
+ let depth = 0;
5415
+ let rootKey = null;
5416
+ let chatName = null;
5417
+ let telegramType = null;
5418
+ let chatId = null;
5419
+ let messagesArrayDepth = null;
5420
+ let messageAssembler = null;
5421
+ let meta = null;
5422
+ for await (const rawToken of tokenStream) {
5423
+ const token = rawToken;
5424
+ if (messageAssembler) {
5425
+ messageAssembler.consume(token);
5426
+ if (messageAssembler.done) {
5427
+ const message = messageAssembler.current;
5428
+ messageAssembler = null;
5429
+ const parsedMessage = parseTelegramMessage(message, memberMap);
5430
+ if (parsedMessage) {
5431
+ messageBatch.push(parsedMessage);
5432
+ messagesProcessed++;
5433
+ }
5434
+ if (messageBatch.length >= batchSize) {
5435
+ yield { type: "messages", data: messageBatch.splice(0) };
5436
+ const progress = createProgress(
5437
+ "parsing",
5438
+ readStream.bytesRead,
5439
+ totalBytes,
5440
+ messagesProcessed,
5441
+ `Processed ${messagesProcessed} messages`
5442
+ );
5443
+ yield { type: "progress", data: progress };
5444
+ onProgress?.(progress);
5445
+ }
5446
+ }
5447
+ } else if (token.name === "keyValue" && depth === 1) {
5448
+ rootKey = token.value ?? null;
5449
+ } else if (token.name === "stringValue" && depth === 1) {
5450
+ if (rootKey === "name") chatName = token.value ?? "";
5451
+ if (rootKey === "type") telegramType = token.value ?? "";
5452
+ } else if (token.name === "numberValue" && depth === 1 && rootKey === "id") {
5453
+ const parsedId = Number(token.value);
5454
+ if (Number.isFinite(parsedId)) chatId = parsedId;
5455
+ }
5456
+ if (token.name === "startArray" && depth === 1 && rootKey === "messages") {
5457
+ if (chatName === null || telegramType === null || chatId === null) {
5458
+ throw new Error("Invalid Telegram single-chat JSON export");
5459
+ }
5460
+ const chatType = mapChatType(telegramType);
5461
+ meta = {
5462
+ name: chatName || `Telegram Chat ${chatId}`,
5463
+ platform: KNOWN_PLATFORMS.TELEGRAM,
5464
+ type: chatType,
5465
+ groupId: chatType === "group" /* GROUP */ ? String(chatId) : void 0
5466
+ };
5467
+ messagesArrayDepth = depth + 1;
5468
+ yield { type: "meta", data: meta };
5469
+ } else if (token.name === "startObject" && messagesArrayDepth !== null && depth === messagesArrayDepth) {
5470
+ messageAssembler = new import_Assembler.default();
5471
+ messageAssembler.consume(token);
5472
+ }
5473
+ if (token.name === "startObject" || token.name === "startArray") {
5474
+ depth++;
5475
+ } else if (token.name === "endObject" || token.name === "endArray") {
5476
+ depth--;
5477
+ }
5555
5478
  }
5556
- const timestamp = parseInt(msg.date_unixtime, 10);
5557
- if (isNaN(timestamp)) continue;
5558
- const parsedMsg = {
5559
- platformMessageId: String(msg.id),
5560
- senderPlatformId,
5561
- senderAccountName: senderName,
5562
- timestamp,
5563
- type: detectMessageType(msg),
5564
- content: buildContent(msg),
5565
- replyToMessageId: msg.reply_to_message_id ? String(msg.reply_to_message_id) : void 0
5566
- };
5567
- messageBatch.push(parsedMsg);
5568
- messagesProcessed++;
5569
- if (messageBatch.length >= batchSize) {
5570
- yield { type: "messages", data: [...messageBatch] };
5571
- messageBatch.length = 0;
5572
- const progress = createProgress(
5573
- "parsing",
5574
- 0,
5575
- totalBytes,
5576
- messagesProcessed,
5577
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
5578
- );
5579
- yield { type: "progress", data: progress };
5580
- onProgress?.(progress);
5479
+ if (!meta || messagesArrayDepth === null || messageAssembler) {
5480
+ throw new Error("Invalid Telegram single-chat JSON export");
5581
5481
  }
5482
+ } catch (error) {
5483
+ const parseError = error instanceof Error ? error : new Error(String(error));
5484
+ onLog?.("error", "Telegram single-chat JSON parsing failed");
5485
+ yield { type: "error", data: parseError };
5486
+ return;
5582
5487
  }
5583
5488
  yield { type: "members", data: Array.from(memberMap.values()) };
5584
5489
  if (messageBatch.length > 0) {
@@ -5587,77 +5492,63 @@ async function* parseTelegramSingle(options2) {
5587
5492
  const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5588
5493
  yield { type: "progress", data: doneProgress };
5589
5494
  onProgress?.(doneProgress);
5590
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5495
+ onLog?.(
5496
+ "info",
5497
+ `Telegram single-chat JSON parsing completed: ${messagesProcessed} messages, ${memberMap.size} members`
5498
+ );
5591
5499
  yield {
5592
5500
  type: "done",
5593
5501
  data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5594
5502
  };
5595
5503
  }
5596
- var parser_8 = {
5597
- feature: feature9,
5598
- parse: parseTelegramSingle
5599
- };
5600
- var module_9 = {
5601
- feature: feature9,
5602
- parser: parser_8
5603
- };
5504
+ function parseTelegramMessage(message, memberMap) {
5505
+ const isService = message.type === "service";
5506
+ const senderPlatformId = extractPlatformId(isService ? message.actor_id : message.from_id);
5507
+ const senderName = isService ? message.actor || "System" : message.from || senderPlatformId;
5508
+ if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
5509
+ memberMap.set(senderPlatformId, {
5510
+ platformId: senderPlatformId,
5511
+ accountName: senderName
5512
+ });
5513
+ }
5514
+ const timestamp = Number.parseInt(message.date_unixtime, 10);
5515
+ if (Number.isNaN(timestamp)) return null;
5516
+ return {
5517
+ platformMessageId: String(message.id),
5518
+ senderPlatformId,
5519
+ senderAccountName: senderName,
5520
+ timestamp,
5521
+ type: detectMessageType(message),
5522
+ content: buildContent(message),
5523
+ replyToMessageId: message.reply_to_message_id ? String(message.reply_to_message_id) : void 0
5524
+ };
5525
+ }
5526
+ var parser_8 = { feature: feature9, parse: parseTelegramSingle };
5527
+ var module_9 = { feature: feature9, parser: parser_8 };
5604
5528
  var telegram_native_single_default = module_9;
5605
5529
 
5606
5530
  // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5607
5531
  import * as fs13 from "fs";
5608
- import * as path9 from "path";
5609
5532
  import * as readline3 from "readline";
5610
- function extractNameFromFilePath5(filePath) {
5611
- const basename13 = path9.basename(filePath);
5612
- const matchZhCn = basename13.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5613
- if (matchZhCn) return matchZhCn[1].trim();
5614
- const matchZhTw = basename13.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
5615
- if (matchZhTw) return matchZhTw[1].trim();
5616
- return basename13.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
5617
- }
5618
- function shouldInferPrivateChatName(filePath, fallbackName) {
5619
- const basename13 = path9.basename(filePath);
5620
- return /^_chat\.txt$/i.test(basename13) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
5621
- }
5622
- function inferPrivateChatName(memberMap, fallbackName) {
5623
- const member = Array.from(memberMap.values()).find((item) => item.platformId !== "system");
5624
- return member?.nickname || fallbackName;
5625
- }
5626
- var feature10 = {
5627
- id: "whatsapp-native-txt",
5628
- name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
5629
- platform: KNOWN_PLATFORMS.WHATSAPP,
5630
- priority: 25,
5631
- extensions: [".txt"],
5632
- signatures: {
5633
- // WhatsApp 导出文件的特征(中文/英文)
5634
- // 注意:仅保留 WhatsApp 独有的特征,避免误匹配其他 TXT 格式(如 LINE)
5635
- head: [
5636
- /消息和通话已进行端到端加密/,
5637
- // 简体中文加密提示(WhatsApp 独有)
5638
- /訊息與通話已受端對端加密保護/,
5639
- // 繁体中文加密提示(WhatsApp 独有)
5640
- /Messages and calls are end-to-end encrypted/i,
5641
- // 英文加密提示(WhatsApp 独有)
5642
- /你发送给自己的消息已进行端到端加密/,
5643
- // 简体中文自己对话提示(WhatsApp 独有)
5644
- /\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?\s+- /i,
5645
- // 消息行格式特征(无方括号,含 " - " 分隔符,WhatsApp 独有)
5646
- /\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
5647
- // 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)
5648
- ],
5649
- // 文件名特征:与xxx的 WhatsApp 聊天.txt
5650
- filename: [/^与.+的\s*WhatsApp\s*聊天\.txt$/i, /^與.+的\s*WhatsApp\s*對話\.txt$/i, /WhatsApp/i]
5651
- }
5652
- };
5653
- function cleanLine(line) {
5654
- return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
5655
- }
5656
- var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?) - (.+)$/i;
5533
+
5534
+ // ../../packages/parser/src/browser/whatsapp.ts
5535
+ var WHATSAPP_HEAD_SIGNATURES = [
5536
+ /消息和通话已进行端到端加密/,
5537
+ /訊息與通話已受端對端加密保護/,
5538
+ /Messages and calls are end-to-end encrypted/i,
5539
+ /你发送给自己的消息已进行端到端加密/,
5540
+ /\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|\u4e0a午|\u4e0b午|\u5348前|\u5348後|오전|오후))?\s+- /i,
5541
+ /\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
5542
+ ];
5543
+ var WHATSAPP_FILENAME_SIGNATURES = [
5544
+ /^与.+的\s*WhatsApp\s*聊天\.txt$/i,
5545
+ /^與.+的\s*WhatsApp\s*對話\.txt$/i,
5546
+ /WhatsApp/i
5547
+ ];
5548
+ var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|\u4e0a午|\u4e0b午|\u5348前|\u5348後|오전|오후))?) - (.+)$/i;
5657
5549
  var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
5658
5550
  var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
5659
5551
  var SYSTEM_MESSAGE_PATTERNS = [
5660
- // 简体中文系统消息
5661
5552
  /消息和通话已进行端到端加密/,
5662
5553
  /创建了此群组/,
5663
5554
  /加入群组/,
@@ -5669,7 +5560,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5669
5560
  /这条消息已删除/,
5670
5561
  /限时消息功能/,
5671
5562
  /正在等待此消息/,
5672
- // 繁体中文系统消息
5673
5563
  /訊息與通話已受端對端加密保護/,
5674
5564
  /建立了此群組/,
5675
5565
  /加入了群組/,
@@ -5679,7 +5569,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5679
5569
  /已變更本群組/,
5680
5570
  /此訊息已刪除/,
5681
5571
  /限時訊息/,
5682
- // 英文系统消息
5683
5572
  /Messages and calls are end-to-end encrypted/i,
5684
5573
  /created this group/i,
5685
5574
  /joined the group/i,
@@ -5690,21 +5579,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5690
5579
  /This message was deleted/i,
5691
5580
  /disappearing messages/i
5692
5581
  ];
5693
- function isSystemMessage(content) {
5694
- return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
5695
- }
5696
- function detectMessageType2(content) {
5697
- const trimmed = content.trim();
5698
- if (trimmed === "<\u7701\u7565\u5F71\u97F3\u5185\u5BB9>" || trimmed === "<\u5DF2\u7701\u7565\u591A\u5A92\u9AD4\u6A94\u6848>" || trimmed === "\u5716\u7247\u5DF2\u7565\u53BB" || trimmed === "\u5F71\u7247\u5DF2\u7565\u53BB" || trimmed === "\u97F3\u8A0A\u5DF2\u7565\u53BB" || trimmed === "image omitted" || trimmed === "video omitted" || trimmed === "audio omitted")
5699
- return 1 /* IMAGE */;
5700
- if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A"))
5701
- return 4 /* FILE */;
5702
- if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
5703
- if (trimmed === "\u8FD9\u6761\u6D88\u606F\u5DF2\u5220\u9664" || trimmed.startsWith("\u6B64\u8A0A\u606F\u5DF2\u522A\u9664") || trimmed.startsWith("\u4F60\u5DF2\u522A\u9664\u6B64\u8A0A\u606F"))
5704
- return 81 /* RECALL */;
5705
- if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
5706
- return 0 /* TEXT */;
5707
- }
5708
5582
  var AMPM_MARKERS = [
5709
5583
  [/\bPM\b/i, true],
5710
5584
  [/\bP\.M\.(?!\w)/i, true],
@@ -5717,191 +5591,226 @@ var AMPM_MARKERS = [
5717
5591
  [/午前/, false],
5718
5592
  [/오전/, false]
5719
5593
  ];
5720
- function parseFlexibleV2Timestamp(raw) {
5721
- let str = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
5722
- let isPM = null;
5723
- for (const [pattern, pm] of AMPM_MARKERS) {
5724
- if (pattern.test(str)) {
5725
- isPM = pm;
5726
- str = str.replace(pattern, "").trim();
5727
- break;
5594
+ var WhatsAppTextAccumulator = class {
5595
+ constructor(fileName) {
5596
+ this.fileName = fileName;
5597
+ this.fileChatName = extractNameFromFileName(fileName);
5598
+ }
5599
+ fileName;
5600
+ fileChatName;
5601
+ memberMap = /* @__PURE__ */ new Map();
5602
+ messages = [];
5603
+ currentMessage = null;
5604
+ skippedLines = 0;
5605
+ get messageCount() {
5606
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5607
+ }
5608
+ pushLine(line) {
5609
+ const cleanedLine = cleanLine(line);
5610
+ const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
5611
+ if (v1Match) {
5612
+ this.saveCurrentMessage();
5613
+ this.currentMessage = createPendingMessage(parseFlexibleTimestamp(v1Match[1]) ?? 0, v1Match[2]);
5614
+ return;
5615
+ }
5616
+ const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
5617
+ if (v2Match) {
5618
+ const timestamp = parseFlexibleTimestamp(v2Match[1]);
5619
+ if (timestamp !== null) {
5620
+ this.saveCurrentMessage();
5621
+ this.currentMessage = createPendingMessage(timestamp, v2Match[2]);
5622
+ return;
5623
+ }
5624
+ }
5625
+ if (this.currentMessage && cleanedLine) {
5626
+ this.currentMessage.contentLines.push(cleanedLine);
5627
+ } else if (cleanedLine) {
5628
+ this.skippedLines += 1;
5629
+ }
5630
+ }
5631
+ finish() {
5632
+ this.saveCurrentMessage();
5633
+ const chatType = this.memberMap.size > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
5634
+ const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(this.fileName, this.fileChatName) ? inferPrivateChatName(this.memberMap, this.fileChatName) : this.fileChatName;
5635
+ return {
5636
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.WHATSAPP, type: chatType },
5637
+ members: Array.from(this.memberMap.values(), (member) => ({
5638
+ platformId: member.platformId,
5639
+ accountName: member.nickname
5640
+ })),
5641
+ messages: this.messages,
5642
+ skippedLines: this.skippedLines
5643
+ };
5644
+ }
5645
+ saveCurrentMessage() {
5646
+ if (!this.currentMessage) return;
5647
+ const content = this.currentMessage.contentLines.join("\n").trim();
5648
+ const senderPlatformId = this.currentMessage.sender ?? "system";
5649
+ const senderName = this.currentMessage.sender ?? "\u7CFB\u7EDF\u6D88\u606F";
5650
+ this.messages.push({
5651
+ senderPlatformId,
5652
+ senderAccountName: senderName,
5653
+ timestamp: this.currentMessage.timestamp,
5654
+ type: detectMessageType2(content),
5655
+ content: content || null
5656
+ });
5657
+ if (this.currentMessage.sender) {
5658
+ this.memberMap.set(senderPlatformId, { platformId: senderPlatformId, nickname: senderName });
5728
5659
  }
5660
+ this.currentMessage = null;
5661
+ }
5662
+ };
5663
+ function createPendingMessage(timestamp, restContent) {
5664
+ const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5665
+ if (senderMatch && !isSystemMessage(restContent)) {
5666
+ return { timestamp, sender: senderMatch[1].trim(), contentLines: [senderMatch[2]] };
5667
+ }
5668
+ return { timestamp, sender: null, contentLines: [restContent] };
5669
+ }
5670
+ function extractNameFromFileName(fileName) {
5671
+ const basename10 = getBasename(fileName);
5672
+ const simplifiedChinese = basename10.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5673
+ if (simplifiedChinese) return simplifiedChinese[1].trim();
5674
+ const traditionalChinese = basename10.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
5675
+ if (traditionalChinese) return traditionalChinese[1].trim();
5676
+ return basename10.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
5677
+ }
5678
+ function shouldInferPrivateChatName(fileName, fallbackName) {
5679
+ const basename10 = getBasename(fileName);
5680
+ return /^_chat\.txt$/i.test(basename10) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
5681
+ }
5682
+ function inferPrivateChatName(memberMap, fallbackName) {
5683
+ return Array.from(memberMap.values()).find((member) => member.platformId !== "system")?.nickname ?? fallbackName;
5684
+ }
5685
+ function getBasename(fileName) {
5686
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5687
+ }
5688
+ function cleanLine(line) {
5689
+ return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
5690
+ }
5691
+ function isSystemMessage(content) {
5692
+ return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
5693
+ }
5694
+ function detectMessageType2(content) {
5695
+ const trimmed = content.trim();
5696
+ if (trimmed === "<\u7701\u7565\u5F71\u97F3\u5185\u5BB9>" || trimmed === "<\u5DF2\u7701\u7565\u591A\u5A92\u9AD4\u6A94\u6848>" || trimmed === "\u5716\u7247\u5DF2\u7565\u53BB" || trimmed === "\u5F71\u7247\u5DF2\u7565\u53BB" || trimmed === "\u97F3\u8A0A\u5DF2\u7565\u53BB" || trimmed === "image omitted" || trimmed === "video omitted" || trimmed === "audio omitted") {
5697
+ return 1 /* IMAGE */;
5698
+ }
5699
+ if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A")) {
5700
+ return 4 /* FILE */;
5701
+ }
5702
+ if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
5703
+ if (trimmed === "\u8FD9\u6761\u6D88\u606F\u5DF2\u5220\u9664" || trimmed.startsWith("\u6B64\u8A0A\u606F\u5DF2\u522A\u9664") || trimmed.startsWith("\u4F60\u5DF2\u522A\u9664\u6B64\u8A0A\u606F")) {
5704
+ return 81 /* RECALL */;
5705
+ }
5706
+ if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
5707
+ return 0 /* TEXT */;
5708
+ }
5709
+ function parseFlexibleTimestamp(raw) {
5710
+ let value = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
5711
+ let isPm = null;
5712
+ for (const [pattern, pm] of AMPM_MARKERS) {
5713
+ if (!pattern.test(value)) continue;
5714
+ isPm = pm;
5715
+ value = value.replace(pattern, "").trim();
5716
+ break;
5729
5717
  }
5730
- str = str.replace(/,/g, " ").replace(/\s+/g, " ").trim();
5731
- const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
5718
+ value = value.replace(/,/g, " ").replace(/\s+/g, " ").trim();
5719
+ const match = value.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
5732
5720
  if (!match) return null;
5733
- const [, datePart, timePart] = match;
5734
- const dateParts = datePart.split("/").map((s) => parseInt(s, 10));
5735
- const timeParts = timePart.split(":").map((s) => parseInt(s, 10));
5736
- let year, month, day;
5721
+ const dateParts = match[1].split("/").map((part) => Number.parseInt(part, 10));
5722
+ const timeParts = match[2].split(":").map((part) => Number.parseInt(part, 10));
5723
+ let year;
5724
+ let month;
5725
+ let day;
5737
5726
  if (dateParts[0] > 31) {
5738
- year = dateParts[0];
5739
- month = dateParts[1];
5740
- day = dateParts[2];
5727
+ ;
5728
+ [year, month, day] = dateParts;
5741
5729
  } else if (dateParts[2] > 31) {
5742
- day = dateParts[0];
5743
- month = dateParts[1];
5744
- year = dateParts[2];
5730
+ ;
5731
+ [day, month, year] = dateParts;
5745
5732
  } else {
5746
- month = dateParts[0];
5747
- day = dateParts[1];
5733
+ ;
5734
+ [month, day] = dateParts;
5748
5735
  year = 2e3 + dateParts[2];
5749
5736
  }
5750
5737
  let hour = timeParts[0];
5751
5738
  const minute = timeParts[1];
5752
5739
  const second = timeParts[2] ?? 0;
5753
- if (isPM === true && hour !== 12) hour += 12;
5754
- if (isPM === false && hour === 12) hour = 0;
5740
+ if (isPm === true && hour !== 12) hour += 12;
5741
+ if (isPm === false && hour === 12) hour = 0;
5755
5742
  const date = new Date(year, month - 1, day, hour, minute, second);
5756
- const ts = Math.floor(date.getTime() / 1e3);
5757
- return isNaN(ts) ? null : ts;
5758
- }
5759
- function parseV1Timestamp(timeStr) {
5760
- return parseFlexibleV2Timestamp(timeStr) ?? 0;
5743
+ const timestamp = Math.floor(date.getTime() / 1e3);
5744
+ return Number.isNaN(timestamp) ? null : timestamp;
5761
5745
  }
5746
+
5747
+ // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5748
+ var feature10 = {
5749
+ id: "whatsapp-native-txt",
5750
+ name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
5751
+ platform: KNOWN_PLATFORMS.WHATSAPP,
5752
+ priority: 25,
5753
+ extensions: [".txt"],
5754
+ signatures: {
5755
+ head: WHATSAPP_HEAD_SIGNATURES,
5756
+ filename: WHATSAPP_FILENAME_SIGNATURES
5757
+ }
5758
+ };
5762
5759
  async function* parseWhatsApp(options2) {
5763
5760
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5764
5761
  const totalBytes = getFileSize(filePath);
5762
+ const accumulator = new WhatsAppTextAccumulator(filePath);
5765
5763
  let bytesRead = 0;
5766
- let messagesProcessed = 0;
5767
- let skippedLines = 0;
5764
+ let lastReportedMessages = 0;
5768
5765
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5769
5766
  yield { type: "progress", data: initialProgress };
5770
5767
  onProgress?.(initialProgress);
5771
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 WhatsApp TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5772
- const fileChatName = extractNameFromFilePath5(filePath);
5773
- const memberMap = /* @__PURE__ */ new Map();
5774
- const messages = [];
5775
- let currentMessage = null;
5776
- const saveCurrentMessage = () => {
5777
- if (currentMessage) {
5778
- const content = currentMessage.contentLines.join("\n").trim();
5779
- const type = detectMessageType2(content);
5780
- const senderPlatformId = currentMessage.sender || "system";
5781
- const senderName = currentMessage.sender || "\u7CFB\u7EDF\u6D88\u606F";
5782
- messages.push({
5783
- senderPlatformId,
5784
- senderAccountName: senderName,
5785
- timestamp: currentMessage.timestamp,
5786
- type,
5787
- content: content || null
5788
- });
5789
- if (currentMessage.sender) {
5790
- memberMap.set(senderPlatformId, {
5791
- platformId: senderPlatformId,
5792
- nickname: senderName
5793
- });
5794
- }
5795
- messagesProcessed++;
5796
- }
5797
- };
5768
+ onLog?.("info", `Starting WhatsApp TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5798
5769
  const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
5799
- const rl = readline3.createInterface({
5800
- input: fileStream,
5801
- crlfDelay: Infinity
5802
- });
5770
+ const lines = readline3.createInterface({ input: fileStream, crlfDelay: Infinity });
5803
5771
  fileStream.on("data", (chunk) => {
5804
5772
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
5805
5773
  });
5806
- for await (const line of rl) {
5807
- const cleanedLine = cleanLine(line);
5808
- const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
5809
- if (v1Match) {
5810
- saveCurrentMessage();
5811
- const restContent = v1Match[2];
5812
- const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5813
- if (senderMatch && !isSystemMessage(restContent)) {
5814
- currentMessage = {
5815
- timestamp: parseV1Timestamp(v1Match[1]),
5816
- sender: senderMatch[1].trim(),
5817
- contentLines: [senderMatch[2]]
5818
- };
5819
- } else {
5820
- currentMessage = {
5821
- timestamp: parseV1Timestamp(v1Match[1]),
5822
- sender: null,
5823
- contentLines: [restContent]
5824
- };
5825
- }
5826
- if (messagesProcessed % 500 === 0) {
5827
- onProgress?.(
5828
- createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
5829
- );
5830
- }
5831
- continue;
5832
- }
5833
- const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
5834
- if (v2Match) {
5835
- const timestamp = parseFlexibleV2Timestamp(v2Match[1]);
5836
- if (timestamp !== null) {
5837
- saveCurrentMessage();
5838
- const restContent = v2Match[2];
5839
- const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5840
- if (senderMatch && !isSystemMessage(restContent)) {
5841
- currentMessage = {
5842
- timestamp,
5843
- sender: senderMatch[1].trim(),
5844
- contentLines: [senderMatch[2]]
5845
- };
5846
- } else {
5847
- currentMessage = {
5848
- timestamp,
5849
- sender: null,
5850
- contentLines: [restContent]
5851
- };
5852
- }
5853
- if (messagesProcessed % 500 === 0) {
5854
- onProgress?.(
5855
- createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
5856
- );
5857
- }
5858
- continue;
5859
- }
5860
- }
5861
- if (currentMessage && cleanedLine) {
5862
- currentMessage.contentLines.push(cleanedLine);
5863
- } else if (cleanedLine) {
5864
- skippedLines++;
5774
+ for await (const line of lines) {
5775
+ accumulator.pushLine(line);
5776
+ if (accumulator.messageCount >= lastReportedMessages + 500) {
5777
+ lastReportedMessages = accumulator.messageCount;
5778
+ onProgress?.(
5779
+ createProgress(
5780
+ "parsing",
5781
+ bytesRead,
5782
+ totalBytes,
5783
+ accumulator.messageCount,
5784
+ `Processed ${accumulator.messageCount} messages`
5785
+ )
5786
+ );
5865
5787
  }
5866
5788
  }
5867
- saveCurrentMessage();
5868
- const hasSystemMember = memberMap.has("system");
5869
- const realMemberCount = hasSystemMember ? memberMap.size - 1 : memberMap.size;
5870
- const chatType = realMemberCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
5871
- const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(filePath, fileChatName) ? inferPrivateChatName(memberMap, fileChatName) : fileChatName;
5872
- const meta = {
5873
- name: chatName,
5874
- platform: KNOWN_PLATFORMS.WHATSAPP,
5875
- type: chatType
5876
- };
5877
- yield { type: "meta", data: meta };
5878
- const members = Array.from(memberMap.values()).map((m) => ({
5879
- platformId: m.platformId,
5880
- accountName: m.nickname
5881
- }));
5882
- yield { type: "members", data: members };
5883
- for (let i = 0; i < messages.length; i += batchSize) {
5884
- const batch = messages.slice(i, i + batchSize);
5885
- yield { type: "messages", data: batch };
5789
+ const result = accumulator.finish();
5790
+ yield { type: "meta", data: result.meta };
5791
+ yield { type: "members", data: result.members };
5792
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5793
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5886
5794
  }
5887
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5795
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
5888
5796
  yield { type: "progress", data: doneProgress };
5889
5797
  onProgress?.(doneProgress);
5890
5798
  const typeCounts = /* @__PURE__ */ new Map();
5891
- for (const msg of messages) {
5892
- typeCounts.set(msg.type, (typeCounts.get(msg.type) || 0) + 1);
5799
+ for (const message of result.messages) {
5800
+ typeCounts.set(message.type, (typeCounts.get(message.type) ?? 0) + 1);
5893
5801
  }
5894
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5895
5802
  onLog?.(
5896
5803
  "info",
5897
- `\u6D88\u606F\u7C7B\u578B\u7EDF\u8BA1: ${Array.from(typeCounts.entries()).map(([type, count]) => `${type}=${count}`).join(", ")}`
5804
+ `WhatsApp TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members, type ${result.meta.type}`
5898
5805
  );
5899
- if (skippedLines > 0) {
5900
- onLog?.("info", `\u8DF3\u8FC7 ${skippedLines} \u884C\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9`);
5901
- }
5806
+ onLog?.(
5807
+ "info",
5808
+ `Message types: ${Array.from(typeCounts.entries(), ([type, count]) => `${type}=${count}`).join(", ")}`
5809
+ );
5810
+ if (result.skippedLines > 0) onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines`);
5902
5811
  yield {
5903
5812
  type: "done",
5904
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5813
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
5905
5814
  };
5906
5815
  }
5907
5816
  var parser_9 = {
@@ -5911,32 +5820,110 @@ var parser_9 = {
5911
5820
  var module_10 = {
5912
5821
  feature: feature10,
5913
5822
  parser: parser_9
5914
- // TXT 格式不需要预处理器
5915
5823
  };
5916
5824
  var whatsapp_native_txt_default = module_10;
5917
5825
 
5918
5826
  // ../../packages/parser/src/formats/qq-native-txt.ts
5919
5827
  import * as fs14 from "fs";
5920
- import * as path10 from "path";
5921
5828
  import * as readline4 from "readline";
5922
- function extractNameFromFilePath6(filePath) {
5923
- const basename13 = path10.basename(filePath);
5924
- const name = basename13.replace(/\.txt$/i, "");
5925
- return name || "\u672A\u77E5\u7FA4\u804A";
5926
- }
5927
- var feature11 = {
5928
- id: "qq-native-txt",
5929
- name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
5930
- platform: KNOWN_PLATFORMS.QQ,
5931
- priority: 30,
5932
- extensions: [".txt"],
5933
- signatures: {
5934
- // 支持群聊导出和多人聊天(讨论组)导出
5935
- head: [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/]
5936
- }
5937
- };
5829
+
5830
+ // ../../packages/parser/src/browser/qq.ts
5831
+ var QQ_HEAD_SIGNATURES = [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/];
5938
5832
  var MESSAGE_HEADER_REGEX = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.+?)(?:\(([^)]+)\)|<([^>]+)>)?$/;
5939
5833
  var GROUP_NAME_REGEX = /^消息对象:(.+)$/;
5834
+ var QqTextAccumulator = class {
5835
+ fallbackName;
5836
+ memberMap = /* @__PURE__ */ new Map();
5837
+ lastValidNickname = /* @__PURE__ */ new Map();
5838
+ messages = [];
5839
+ groupName = "\u672A\u77E5\u7FA4\u804A";
5840
+ currentMessage = null;
5841
+ skippedLines = 0;
5842
+ constructor(fileName) {
5843
+ this.fallbackName = extractNameFromFileName2(fileName);
5844
+ }
5845
+ get messageCount() {
5846
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5847
+ }
5848
+ pushLine(line) {
5849
+ const groupMatch = line.match(GROUP_NAME_REGEX);
5850
+ if (groupMatch) {
5851
+ this.groupName = groupMatch[1].trim();
5852
+ return;
5853
+ }
5854
+ const headerMatch = line.match(MESSAGE_HEADER_REGEX);
5855
+ if (headerMatch) {
5856
+ this.saveCurrentMessage();
5857
+ const platformId = headerMatch[3] || headerMatch[4] || cleanNickname(headerMatch[2].trim());
5858
+ let nickname = cleanNickname(headerMatch[2].trim());
5859
+ if (nickname === platformId && headerMatch[3]) {
5860
+ nickname = this.lastValidNickname.get(platformId) ?? nickname;
5861
+ } else if (headerMatch[3] || headerMatch[4]) {
5862
+ this.lastValidNickname.set(platformId, nickname);
5863
+ }
5864
+ this.currentMessage = {
5865
+ timestamp: parseLocalTime(headerMatch[1]),
5866
+ platformId,
5867
+ nickname,
5868
+ contentLines: []
5869
+ };
5870
+ return;
5871
+ }
5872
+ if (this.currentMessage) {
5873
+ if (line.startsWith("=====") || line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) return;
5874
+ this.currentMessage.contentLines.push(line);
5875
+ return;
5876
+ }
5877
+ const trimmed = line.trim();
5878
+ if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
5879
+ this.skippedLines += 1;
5880
+ }
5881
+ }
5882
+ finish() {
5883
+ this.saveCurrentMessage();
5884
+ return {
5885
+ meta: {
5886
+ name: this.groupName === "\u672A\u77E5\u7FA4\u804A" ? this.fallbackName : this.groupName,
5887
+ platform: KNOWN_PLATFORMS.QQ,
5888
+ type: "group" /* GROUP */
5889
+ },
5890
+ members: Array.from(this.memberMap.values(), (member) => ({
5891
+ platformId: member.platformId,
5892
+ accountName: member.nickname
5893
+ })),
5894
+ messages: this.messages,
5895
+ skippedLines: this.skippedLines
5896
+ };
5897
+ }
5898
+ saveCurrentMessage() {
5899
+ if (!this.currentMessage) return;
5900
+ const content = this.currentMessage.contentLines.join("\n").trim();
5901
+ this.messages.push({
5902
+ senderPlatformId: this.currentMessage.platformId,
5903
+ senderAccountName: this.currentMessage.nickname,
5904
+ timestamp: this.currentMessage.timestamp,
5905
+ type: detectMessageType3(content),
5906
+ content: content || null
5907
+ });
5908
+ this.memberMap.set(this.currentMessage.platformId, {
5909
+ platformId: this.currentMessage.platformId,
5910
+ nickname: this.currentMessage.nickname
5911
+ });
5912
+ this.currentMessage = null;
5913
+ }
5914
+ };
5915
+ function extractNameFromFileName2(fileName) {
5916
+ return getBasename2(fileName).replace(/\.txt$/i, "") || "\u672A\u77E5\u7FA4\u804A";
5917
+ }
5918
+ function getBasename2(fileName) {
5919
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5920
+ }
5921
+ function cleanNickname(nickname) {
5922
+ return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
5923
+ }
5924
+ function parseLocalTime(timeText) {
5925
+ return Math.floor(new Date(timeText.replace(" ", "T")).getTime() / 1e3);
5926
+ }
5940
5927
  function detectMessageType3(content) {
5941
5928
  const trimmed = content.trim();
5942
5929
  if (trimmed === "[\u56FE\u7247]") return 1 /* IMAGE */;
@@ -5949,7 +5936,9 @@ function detectMessageType3(content) {
5949
5936
  if (trimmed === "[\u7EA2\u5305]" || trimmed.includes("\u53D1\u51FA\u4E86\u7EA2\u5305")) return 20 /* RED_PACKET */;
5950
5937
  if (trimmed === "[\u8F6C\u8D26]" || trimmed.includes("\u5411\u4F60\u8F6C\u8D26")) return 21 /* TRANSFER */;
5951
5938
  if (trimmed.includes("\u62CD\u4E86\u62CD") || trimmed === "[\u62CD\u4E00\u62CD]") return 22 /* POKE */;
5952
- if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) return 23 /* CALL */;
5939
+ if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) {
5940
+ return 23 /* CALL */;
5941
+ }
5953
5942
  if (trimmed === "[\u5206\u4EAB]" || trimmed === "[\u97F3\u4E50]" || trimmed === "[\u5C0F\u7A0B\u5E8F]") return 24 /* SHARE */;
5954
5943
  if (trimmed.startsWith("[\u56DE\u590D]")) return 25 /* REPLY */;
5955
5944
  if (trimmed === "[\u8F6C\u53D1]" || trimmed === "[\u804A\u5929\u8BB0\u5F55]") return 26 /* FORWARD */;
@@ -5960,150 +5949,64 @@ function detectMessageType3(content) {
5960
5949
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) return 99 /* OTHER */;
5961
5950
  return 0 /* TEXT */;
5962
5951
  }
5963
- function parseLocalTime(timeStr) {
5964
- const date = new Date(timeStr.replace(" ", "T"));
5965
- return Math.floor(date.getTime() / 1e3);
5966
- }
5967
- function cleanNickname(nickname) {
5968
- return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
5969
- }
5970
- var lastValidNickname = /* @__PURE__ */ new Map();
5971
- async function* parseTxt(options2) {
5952
+
5953
+ // ../../packages/parser/src/formats/qq-native-txt.ts
5954
+ var feature11 = {
5955
+ id: "qq-native-txt",
5956
+ name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
5957
+ platform: KNOWN_PLATFORMS.QQ,
5958
+ priority: 30,
5959
+ extensions: [".txt"],
5960
+ signatures: { head: QQ_HEAD_SIGNATURES }
5961
+ };
5962
+ async function* parseQq(options2) {
5972
5963
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5973
5964
  const totalBytes = getFileSize(filePath);
5974
5965
  let bytesRead = 0;
5975
- let messagesProcessed = 0;
5976
- let skippedLines = 0;
5977
5966
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5978
5967
  yield { type: "progress", data: initialProgress };
5979
5968
  onProgress?.(initialProgress);
5980
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 QQ TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5981
- let groupName = "\u672A\u77E5\u7FA4\u804A";
5982
- const memberMap = /* @__PURE__ */ new Map();
5983
- const messages = [];
5984
- let currentMessage = null;
5985
- const saveCurrentMessage = () => {
5986
- if (currentMessage) {
5987
- const content = currentMessage.contentLines.join("\n").trim();
5988
- const type = detectMessageType3(content);
5989
- messages.push({
5990
- senderPlatformId: currentMessage.platformId,
5991
- senderAccountName: currentMessage.nickname,
5992
- // QQ TXT 格式只有一个昵称,作为账号名称追踪历史
5993
- // 不设置 senderGroupNickname,避免同一昵称被重复追踪
5994
- timestamp: currentMessage.timestamp,
5995
- type,
5996
- content: content || null
5997
- });
5998
- memberMap.set(currentMessage.platformId, {
5999
- platformId: currentMessage.platformId,
6000
- nickname: currentMessage.nickname
6001
- });
6002
- messagesProcessed++;
6003
- }
6004
- };
5969
+ onLog?.("info", `Starting QQ TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5970
+ const accumulator = new QqTextAccumulator(filePath);
6005
5971
  const fileStream = fs14.createReadStream(filePath, { encoding: "utf-8" });
6006
- const rl = readline4.createInterface({
6007
- input: fileStream,
6008
- crlfDelay: Infinity
6009
- });
5972
+ const lines = readline4.createInterface({ input: fileStream, crlfDelay: Infinity });
6010
5973
  fileStream.on("data", (chunk) => {
6011
5974
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6012
5975
  });
6013
- for await (const line of rl) {
6014
- const groupMatch = line.match(GROUP_NAME_REGEX);
6015
- if (groupMatch) {
6016
- groupName = groupMatch[1].trim();
6017
- continue;
6018
- }
6019
- const headerMatch = line.match(MESSAGE_HEADER_REGEX);
6020
- if (headerMatch) {
6021
- saveCurrentMessage();
6022
- const timeStr = headerMatch[1];
6023
- const rawNickname = headerMatch[2].trim();
6024
- let nickname = cleanNickname(rawNickname);
6025
- const platformId = headerMatch[3] || headerMatch[4] || nickname;
6026
- if (nickname === platformId && headerMatch[3]) {
6027
- const previousNickname = lastValidNickname.get(platformId);
6028
- if (previousNickname) {
6029
- nickname = previousNickname;
6030
- }
6031
- } else if (headerMatch[3] || headerMatch[4]) {
6032
- lastValidNickname.set(platformId, nickname);
6033
- }
6034
- currentMessage = {
6035
- timestamp: parseLocalTime(timeStr),
6036
- platformId,
6037
- nickname,
6038
- contentLines: []
6039
- };
6040
- if (messagesProcessed % 1e3 === 0) {
6041
- const progress = createProgress(
6042
- "parsing",
6043
- bytesRead,
6044
- totalBytes,
6045
- messagesProcessed,
6046
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
6047
- );
6048
- onProgress?.(progress);
6049
- }
6050
- continue;
6051
- }
6052
- if (currentMessage) {
6053
- if (line.startsWith("=====")) continue;
6054
- if (line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) continue;
6055
- currentMessage.contentLines.push(line);
6056
- } else {
6057
- const trimmed = line.trim();
6058
- if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
6059
- skippedLines++;
6060
- }
6061
- }
6062
- }
6063
- saveCurrentMessage();
6064
- const meta = {
6065
- name: groupName === "\u672A\u77E5\u7FA4\u804A" ? extractNameFromFilePath6(filePath) : groupName,
6066
- platform: KNOWN_PLATFORMS.QQ,
6067
- type: "group" /* GROUP */
6068
- };
6069
- yield { type: "meta", data: meta };
6070
- const members = Array.from(memberMap.values()).map((m) => ({
6071
- platformId: m.platformId,
6072
- accountName: m.nickname
6073
- // QQ TXT 格式只有昵称,作为账号名称
6074
- // 不设置 groupNickname,避免同一昵称被重复追踪
6075
- }));
6076
- yield { type: "members", data: members };
6077
- for (let i = 0; i < messages.length; i += batchSize) {
6078
- const batch = messages.slice(i, i + batchSize);
6079
- yield { type: "messages", data: batch };
6080
- }
6081
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5976
+ let lastReportedCount = -1;
5977
+ for await (const line of lines) {
5978
+ accumulator.pushLine(line);
5979
+ const messageCount = accumulator.messageCount;
5980
+ if (messageCount > 0 && messageCount % 1e3 === 0 && messageCount !== lastReportedCount) {
5981
+ lastReportedCount = messageCount;
5982
+ onProgress?.(createProgress("parsing", bytesRead, totalBytes, messageCount, `Processed ${messageCount} messages`));
5983
+ }
5984
+ }
5985
+ const result = accumulator.finish();
5986
+ yield { type: "meta", data: result.meta };
5987
+ yield { type: "members", data: result.members };
5988
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5989
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5990
+ }
5991
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6082
5992
  yield { type: "progress", data: doneProgress };
6083
5993
  onProgress?.(doneProgress);
6084
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458`);
6085
- if (skippedLines > 0) {
6086
- onLog?.("info", `\u8DF3\u8FC7 ${skippedLines} \u884C\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9`);
5994
+ onLog?.("info", `QQ TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
5995
+ if (result.skippedLines > 0) {
5996
+ onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines while parsing QQ TXT`);
6087
5997
  }
6088
5998
  yield {
6089
5999
  type: "done",
6090
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
6000
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6091
6001
  };
6092
6002
  }
6093
- var parser_10 = {
6094
- feature: feature11,
6095
- parse: parseTxt
6096
- };
6097
- var module_11 = {
6098
- feature: feature11,
6099
- parser: parser_10
6100
- // TXT 格式不需要预处理器
6101
- };
6003
+ var parser_10 = { feature: feature11, parse: parseQq };
6004
+ var module_11 = { feature: feature11, parser: parser_10 };
6102
6005
  var qq_native_txt_default = module_11;
6103
6006
 
6104
6007
  // ../../packages/parser/src/formats/instagram-native.ts
6105
6008
  import * as fs15 from "fs";
6106
- import * as path11 from "path";
6009
+ import * as path9 from "path";
6107
6010
  var feature12 = {
6108
6011
  id: "instagram-native",
6109
6012
  name: "Instagram \u5B98\u65B9\u5BFC\u51FA",
@@ -6128,9 +6031,9 @@ function decodeInstagramText(text) {
6128
6031
  return text;
6129
6032
  }
6130
6033
  }
6131
- function extractNameFromFilePath7(filePath) {
6132
- const basename13 = path11.basename(filePath);
6133
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6034
+ function extractNameFromFilePath5(filePath) {
6035
+ const basename10 = path9.basename(filePath);
6036
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6134
6037
  }
6135
6038
  function isSystemMessage2(content) {
6136
6039
  const systemPatterns = [
@@ -6216,7 +6119,7 @@ async function* parseInstagram(options2) {
6216
6119
  ownerId = createMsg ? decodeInstagramText(createMsg.sender_name) : void 0;
6217
6120
  }
6218
6121
  const meta = {
6219
- name: decodeInstagramText(data.title) || extractNameFromFilePath7(filePath),
6122
+ name: decodeInstagramText(data.title) || extractNameFromFilePath5(filePath),
6220
6123
  platform: KNOWN_PLATFORMS.INSTAGRAM,
6221
6124
  type: chatType,
6222
6125
  ownerId
@@ -6289,16 +6192,16 @@ var module_12 = {
6289
6192
  var instagram_native_default = module_12;
6290
6193
 
6291
6194
  // ../../packages/parser/src/formats/google-chat-takeout.ts
6292
- var import_stream_chain8 = __toESM(require_stream_chain(), 1);
6195
+ var import_stream_chain7 = __toESM(require_stream_chain(), 1);
6293
6196
  var import_stream_json8 = __toESM(require_stream_json(), 1);
6294
6197
  var import_Pick7 = __toESM(require_Pick(), 1);
6295
- var import_StreamValues8 = __toESM(require_StreamValues(), 1);
6198
+ var import_StreamValues7 = __toESM(require_StreamValues(), 1);
6296
6199
  import * as fs16 from "fs";
6297
- import * as path12 from "path";
6298
- var { chain: chain8 } = import_stream_chain8.default;
6200
+ import * as path10 from "path";
6201
+ var { chain: chain7 } = import_stream_chain7.default;
6299
6202
  var { parser: parser9 } = import_stream_json8.default;
6300
6203
  var { pick: pick7 } = import_Pick7.default;
6301
- var { streamValues: streamValues8 } = import_StreamValues8.default;
6204
+ var { streamValues: streamValues7 } = import_StreamValues7.default;
6302
6205
  var ENGLISH_MONTHS = /* @__PURE__ */ new Map([
6303
6206
  ["january", 0],
6304
6207
  ["february", 1],
@@ -6380,9 +6283,9 @@ function readJson(filePath) {
6380
6283
  return JSON.parse(fs16.readFileSync(filePath, "utf8"));
6381
6284
  }
6382
6285
  function resolveManifestFile(manifestDir, relativePath) {
6383
- const resolved = path12.resolve(manifestDir, relativePath);
6384
- const relative2 = path12.relative(manifestDir, resolved);
6385
- if (relative2.startsWith("..") || path12.isAbsolute(relative2)) {
6286
+ const resolved = path10.resolve(manifestDir, relativePath);
6287
+ const relative2 = path10.relative(manifestDir, resolved);
6288
+ if (relative2.startsWith("..") || path10.isAbsolute(relative2)) {
6386
6289
  throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`);
6387
6290
  }
6388
6291
  return resolved;
@@ -6395,7 +6298,7 @@ function readManifest2(filePath) {
6395
6298
  if (manifest.chatType !== "private" && manifest.chatType !== "group") {
6396
6299
  throw new Error("Invalid Google Chat chat type");
6397
6300
  }
6398
- const manifestDir = path12.dirname(filePath);
6301
+ const manifestDir = path10.dirname(filePath);
6399
6302
  return {
6400
6303
  manifest,
6401
6304
  userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),
@@ -6479,7 +6382,7 @@ async function* parseGoogleChat(options2) {
6479
6382
  readStream.on("data", (chunk) => {
6480
6383
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6481
6384
  });
6482
- const pipeline2 = chain8([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues8()]);
6385
+ const pipeline2 = chain7([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues7()]);
6483
6386
  const messageBatch = [];
6484
6387
  try {
6485
6388
  for await (const item of pipeline2) {
@@ -6527,456 +6430,700 @@ var google_chat_takeout_default = module_13;
6527
6430
 
6528
6431
  // ../../packages/parser/src/formats/line-native-txt.ts
6529
6432
  import * as fs17 from "fs";
6530
- import * as path13 from "path";
6531
- var feature14 = {
6532
- id: "line-native-txt",
6533
- name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
6534
- platform: KNOWN_PLATFORMS.LINE,
6535
- priority: 35,
6536
- extensions: [".txt"],
6537
- signatures: {
6538
- head: [
6539
- // 头部标识(多语言)
6540
- /^\[LINE\] /m,
6541
- /^(?:\[LINE\] )?Chat history (?:with|in) /m,
6542
- // Tab 分隔的消息格式(支持多种时间格式)
6543
- /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
6544
- // 空格分隔的消息格式(部分 LINE 导出)
6545
- /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
6546
- // LINE 独有的日期行格式:YYYY.MM.DD DayOfWeek(英文星期全称)
6547
- /^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
6548
- // LINE 日文/中文日期行格式:YYYY/M/D(曜日)
6549
- /^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
6550
- // LINE 中文日期行格式:YYYY/M/D周X
6551
- /^\d{4}\/\d{1,2}\/\d{1,2}周/m
6552
- ],
6553
- // 文件名特征:[LINE] 出现在文件名中
6554
- filename: [/\[LINE\]/i]
6555
- }
6556
- };
6557
- function extractNameFromFilePath8(filePath) {
6558
- const basename13 = path13.basename(filePath, ".txt");
6559
- const name = basename13.replace(/^\[LINE\]\s*/i, "").trim();
6560
- return name || "\u672A\u77E5\u804A\u5929";
6561
- }
6562
- function extractNameFromHeader(header) {
6563
- const enPrivateMatch = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
6564
- if (enPrivateMatch) return { name: enPrivateMatch[1].trim(), isGroup: false };
6565
- const enGroupMatch = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
6566
- if (enGroupMatch) return { name: enGroupMatch[1].trim(), isGroup: true };
6567
- const jaPrivateMatch = header.match(/^\[LINE\] (.+)とのトーク履歴/);
6568
- if (jaPrivateMatch) return { name: jaPrivateMatch[1].trim(), isGroup: false };
6569
- const jaGroupMatch = header.match(/^\[LINE\] (.+)のトーク履歴/);
6570
- if (jaGroupMatch) return { name: jaGroupMatch[1].trim(), isGroup: true };
6571
- const zhCnPrivateMatch = header.match(/^\[LINE\] 与(.+)的聊天记录/);
6572
- if (zhCnPrivateMatch) return { name: zhCnPrivateMatch[1].trim(), isGroup: false };
6573
- const zhCnGroupMatch = header.match(/^\[LINE\] (.+)的聊天记录/);
6574
- if (zhCnGroupMatch) return { name: zhCnGroupMatch[1].trim(), isGroup: true };
6575
- const zhTwPrivateMatch = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
6576
- if (zhTwPrivateMatch) return { name: zhTwPrivateMatch[1].trim(), isGroup: false };
6577
- const zhTwGroupMatch = header.match(/^\[LINE\] (.+)的聊天記錄/);
6578
- if (zhTwGroupMatch) return { name: zhTwGroupMatch[1].trim(), isGroup: true };
6579
- return null;
6580
- }
6433
+
6434
+ // ../../packages/parser/src/browser/line.ts
6435
+ var LINE_HEAD_SIGNATURES = [
6436
+ /^\[LINE\] /m,
6437
+ /^(?:\[LINE\] )?Chat history (?:with|in) /m,
6438
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
6439
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
6440
+ /^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
6441
+ /^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
6442
+ /^\d{4}\/\d{1,2}\/\d{1,2}周/m
6443
+ ];
6444
+ var LINE_FILENAME_SIGNATURES = [/\[LINE\]/i];
6581
6445
  var DATE_PATTERNS = [
6582
- // 2025.12.10 Wednesday
6583
6446
  /^(\d{4})\.(\d{2})\.(\d{2})\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)?/,
6584
- // 2026/1/30周五 or 2026/1/30(金)
6585
6447
  /^(\d{4})\/(\d{1,2})\/(\d{1,2})/,
6586
- // Fri, 1/30/2026
6587
6448
  /^[A-Za-z]+,\s*(\d{1,2})\/(\d{1,2})\/(\d{4})/
6588
6449
  ];
6589
- function parseDateLine(line) {
6590
- for (const pattern of DATE_PATTERNS) {
6591
- const match = line.match(pattern);
6592
- if (match) {
6593
- if (pattern === DATE_PATTERNS[0] || pattern === DATE_PATTERNS[1]) {
6594
- const year = parseInt(match[1]);
6595
- const month = parseInt(match[2]) - 1;
6596
- const day = parseInt(match[3]);
6597
- return new Date(year, month, day);
6598
- } else if (pattern === DATE_PATTERNS[2]) {
6599
- const month = parseInt(match[1]) - 1;
6600
- const day = parseInt(match[2]);
6601
- const year = parseInt(match[3]);
6602
- return new Date(year, month, day);
6603
- }
6604
- }
6605
- }
6606
- return null;
6607
- }
6608
- var PRIVATE_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
6609
- var GROUP_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
6610
- var SYSTEM_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
6450
+ var TAB_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
6451
+ var SPACE_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
6452
+ var SYSTEM_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
6611
6453
  var SPECIAL_MESSAGE_TYPES = {
6612
- // 图片 / Photo
6613
6454
  "[Photo]": 1 /* IMAGE */,
6614
- // EN
6615
6455
  "[\u7167\u7247]": 1 /* IMAGE */,
6616
- // ZH-CN / ZH-TW
6617
6456
  "[\u5199\u771F]": 1 /* IMAGE */,
6618
- // JA
6619
6457
  Photos: 1 /* IMAGE */,
6620
- // EN (fallback)
6621
- // 语音 / Voice
6622
6458
  "[Voice message]": 2 /* VOICE */,
6623
- // EN
6624
6459
  "[\u8BED\u97F3\u4FE1\u606F]": 2 /* VOICE */,
6625
- // ZH-CN
6626
6460
  "[\u8A9E\u97F3\u8A0A\u606F]": 2 /* VOICE */,
6627
- // ZH-TW
6628
6461
  "[\u30DC\u30A4\u30B9\u30E1\u30C3\u30BB\u30FC\u30B8]": 2 /* VOICE */,
6629
- // JA
6630
6462
  Audio: 2 /* VOICE */,
6631
- // EN (fallback)
6632
- // 视频 / Video
6633
6463
  "[Video]": 3 /* VIDEO */,
6634
- // EN
6635
6464
  "[\u89C6\u9891]": 3 /* VIDEO */,
6636
- // ZH-CN
6637
6465
  "[\u5F71\u7247]": 3 /* VIDEO */,
6638
- // ZH-TW
6639
6466
  "[\u52D5\u753B]": 3 /* VIDEO */,
6640
- // JA
6641
6467
  Videos: 3 /* VIDEO */,
6642
- // EN (fallback)
6643
- // 文件 / File
6644
6468
  "[File]": 4 /* FILE */,
6645
- // EN
6646
6469
  "[\u6587\u4EF6]": 4 /* FILE */,
6647
- // ZH-CN
6648
6470
  "[\u6A94\u6848]": 4 /* FILE */,
6649
- // ZH-TW
6650
6471
  "[\u30D5\u30A1\u30A4\u30EB]": 4 /* FILE */,
6651
- // JA
6652
- // 贴纸 / Sticker
6653
6472
  "[Sticker]": 5 /* EMOJI */,
6654
- // EN
6655
6473
  "[\u8D34\u56FE]": 5 /* EMOJI */,
6656
- // ZH-CN
6657
6474
  "[\u8CBC\u5716]": 5 /* EMOJI */,
6658
- // ZH-TW
6659
6475
  "[\u30B9\u30BF\u30F3\u30D7]": 5 /* EMOJI */,
6660
- // JA
6661
6476
  Stickers: 5 /* EMOJI */,
6662
- // EN (fallback)
6663
- // 位置 / Location
6664
6477
  "[Location]": 8 /* LOCATION */,
6665
- // EN
6666
6478
  "[\u4F4D\u7F6E]": 8 /* LOCATION */,
6667
- // ZH-CN / ZH-TW
6668
6479
  "[\u4F4D\u7F6E\u60C5\u5831]": 8 /* LOCATION */,
6669
- // JA
6670
- // 记事本 / Notes
6671
6480
  "[Notes]": 0 /* TEXT */,
6672
- // EN
6673
6481
  "[\u8BB0\u4E8B\u672C]": 0 /* TEXT */,
6674
- // ZH-CN
6675
6482
  "[\u8A18\u4E8B\u672C]": 0 /* TEXT */,
6676
- // ZH-TW
6677
6483
  "[\u30CE\u30FC\u30C8]": 0 /* TEXT */
6678
- // JA
6679
6484
  };
6680
- function detectMessageType6(content) {
6681
- for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
6682
- if (content === pattern || content.startsWith(pattern)) {
6683
- return type;
6684
- }
6685
- }
6686
- if (content.startsWith("[null]") && content.includes("maps.google.com")) {
6687
- return 8 /* LOCATION */;
6688
- }
6689
- if (
6690
- // --- 加入群组 / Join group ---
6691
- content.includes(" joined the group") || // EN
6692
- content.includes("\u5DF2\u52A0\u5165\u8BE5\u7FA4") || // ZH-CN
6693
- content.includes("\u5DF2\u52A0\u5165\u7FA4\u7D44") || // ZH-TW
6694
- content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u306B\u53C2\u52A0\u3057\u307E\u3057\u305F") || // JA
6695
- // --- 拉人进群 / Added to group ---
6696
- content.includes(" added ") || // EN
6697
- content.includes(" to the group") || // EN
6698
- content.includes("\u5DF2\u5C06") || // ZH-CN
6699
- content.includes("\u6DFB\u52A0\u81F3\u7FA4") || // ZH-CN
6700
- content.includes("\u6DFB\u52A0\u5230\u7FA4") || // ZH-CN (另一格式)
6701
- content.includes("\u5DF2\u65B0\u589E") || // ZH-TW
6702
- content.includes("\u81F3\u7FA4\u7D44") || // ZH-TW
6703
- content.includes("\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F") || // JA
6704
- // --- 退出群组 / Left group ---
6705
- content.includes(" left the group") || // EN
6706
- content.includes("\u5DF2\u9000\u7FA4") || // ZH-CN
6707
- content.includes("\u5DF2\u96E2\u958B\u7FA4\u7D44") || // ZH-TW
6708
- content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u3092\u9000\u4F1A\u3057\u307E\u3057\u305F") || // JA
6709
- // --- 设定公告 / Announcement ---
6710
- content.includes("made an announcement") || // EN
6711
- content.includes("\u53D1\u5E03\u4E86\u901A\u544A") || // ZH-CN
6712
- content.includes("\u5DF2\u8A2D\u5B9A\u516C\u544A") || // ZH-TW
6713
- content.includes("\u304C\u30A2\u30CA\u30A6\u30F3\u30B9\u3057\u307E\u3057\u305F") || // JA
6714
- // --- 收回讯息 / Unsent message ---
6715
- content.includes("unsent a message") || // EN
6716
- content === "Message unsent." || // EN
6717
- content.includes("\u64A4\u56DE\u4E86\u4E00\u6761\u6D88\u606F") || // ZH-CN
6718
- content.includes("\u5DF2\u6536\u56DE\u8A0A\u606F") || // ZH-TW
6719
- content.includes("\u9001\u4FE1\u3092\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F") || // JA
6720
- // --- 其他 / Others ---
6721
- content.startsWith("Auto-reply")
6722
- ) {
6723
- return 80 /* SYSTEM */;
6724
- }
6725
- if (content.match(/^https?:\/\//)) {
6726
- return 7 /* LINK */;
6727
- }
6728
- return 0 /* TEXT */;
6729
- }
6730
- async function* parseLINE(options2) {
6731
- const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
6732
- const totalBytes = getFileSize(filePath);
6733
- let messagesProcessed = 0;
6734
- const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
6735
- yield { type: "progress", data: initialProgress };
6736
- onProgress?.(initialProgress);
6737
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 LINE \u5BFC\u51FA\u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
6738
- const content = fs17.readFileSync(filePath, "utf-8");
6485
+ async function parseLineText(content, fileName, options2 = {}) {
6739
6486
  const lines = content.split("\n").map((line) => line.replace(/\r$/, ""));
6740
- let currentDate = null;
6741
- let chatName = extractNameFromFilePath8(filePath);
6742
- let isPrivateChat = false;
6743
- let useTabSeparator = false;
6744
6487
  const memberMap = /* @__PURE__ */ new Map();
6745
6488
  const messages = [];
6489
+ const header = lines.length > 0 ? extractNameFromHeader(lines[0].trim()) : null;
6490
+ const chatName = header?.name ?? extractNameFromFileName3(fileName);
6491
+ let currentDate = null;
6746
6492
  let lastMessage = null;
6747
- let lineIndex = 0;
6748
- if (lines.length > 0) {
6749
- const firstLine = lines[0].trim();
6750
- onLog?.("debug", `LINE \u7B2C\u4E00\u884C: "${firstLine}"`);
6751
- const headerResult = extractNameFromHeader(firstLine);
6752
- if (headerResult) {
6753
- chatName = headerResult.name;
6754
- isPrivateChat = !headerResult.isGroup;
6755
- useTabSeparator = true;
6756
- lineIndex = 3;
6757
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u5934\u90E8\uFF0C\u540D\u79F0: ${headerResult.name}, \u7FA4\u804A: ${headerResult.isGroup}`);
6758
- }
6759
- }
6760
- if (!isPrivateChat && lines.length > 0) {
6493
+ let quotedMultiline = false;
6494
+ let lineIndex = header ? 3 : 0;
6495
+ let useTabSeparator = Boolean(header);
6496
+ const yieldEvery = Math.max(1, options2.yieldEvery ?? 1e3);
6497
+ if (!header) {
6761
6498
  for (const line of lines) {
6762
- if (PRIVATE_MSG_PATTERN.test(line)) {
6499
+ if (TAB_MESSAGE_PATTERN.test(line)) {
6763
6500
  useTabSeparator = true;
6764
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230 Tab \u5206\u9694\u683C\u5F0F`);
6765
6501
  break;
6766
6502
  }
6767
- if (GROUP_MSG_PATTERN.test(line)) {
6503
+ if (SPACE_MESSAGE_PATTERN.test(line)) {
6768
6504
  useTabSeparator = false;
6769
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u7A7A\u683C\u5206\u9694\u683C\u5F0F`);
6770
6505
  break;
6771
6506
  }
6772
6507
  }
6773
6508
  }
6774
- onLog?.("debug", `LINE \u89E3\u6790\u914D\u7F6E: useTabSeparator=${useTabSeparator}, lineIndex=${lineIndex}`);
6775
- let debugLogCount = 0;
6776
- for (let i = lineIndex; i < lines.length; i++) {
6777
- const line = lines[i];
6778
- const dateResult = parseDateLine(line);
6779
- if (dateResult) {
6780
- currentDate = dateResult;
6781
- if (debugLogCount < 5) {
6782
- onLog?.("debug", `LINE \u65E5\u671F\u884C[${i}]: ${line} -> ${dateResult.toISOString()}`);
6783
- }
6784
- continue;
6785
- }
6786
- const msgPattern = useTabSeparator ? PRIVATE_MSG_PATTERN : GROUP_MSG_PATTERN;
6787
- const msgMatch = line.match(msgPattern);
6788
- if (debugLogCount < 5 && line.trim()) {
6789
- onLog?.("debug", `LINE \u884C[${i}]: "${line.substring(0, 50)}..." match=${!!msgMatch}`);
6790
- debugLogCount++;
6791
- }
6792
- if (msgMatch) {
6793
- const [, timeStr, sender, contentRaw] = msgMatch;
6794
- let content2 = contentRaw.trim();
6795
- let isQuotedMultiline = false;
6796
- if (content2.startsWith('"')) {
6797
- content2 = content2.substring(1);
6798
- isQuotedMultiline = !content2.endsWith('"');
6799
- if (content2.endsWith('"')) {
6800
- content2 = content2.substring(0, content2.length - 1);
6509
+ for (; lineIndex < lines.length; lineIndex += 1) {
6510
+ options2.checkCancelled?.();
6511
+ const line = lines[lineIndex];
6512
+ const date = parseDateLine(line);
6513
+ if (date) {
6514
+ currentDate = date;
6515
+ } else {
6516
+ const messagePattern = useTabSeparator ? TAB_MESSAGE_PATTERN : SPACE_MESSAGE_PATTERN;
6517
+ const messageMatch = line.match(messagePattern);
6518
+ if (messageMatch) {
6519
+ const [, timeText, sender, rawContent] = messageMatch;
6520
+ let messageContent = rawContent.trim();
6521
+ quotedMultiline = false;
6522
+ if (messageContent.startsWith('"')) {
6523
+ messageContent = messageContent.slice(1);
6524
+ quotedMultiline = !messageContent.endsWith('"');
6525
+ if (!quotedMultiline) messageContent = messageContent.slice(0, -1);
6801
6526
  }
6802
- }
6803
- let hours = 0;
6804
- let minutes = 0;
6805
- const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1];
6806
- const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, "");
6807
- const partsMatch = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6808
- if (partsMatch) {
6809
- hours = parseInt(partsMatch[1]);
6810
- minutes = parseInt(partsMatch[2]);
6811
- const suffix = partsMatch[3]?.toLowerCase();
6812
- if (suffix === "pm" && hours < 12) hours += 12;
6813
- if (suffix === "am" && hours === 12) hours = 0;
6814
- if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6815
- if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6816
- } else {
6817
- const parts = timeStr.split(":").map(Number);
6818
- hours = parts[0];
6819
- minutes = parts[1];
6820
- }
6821
- let timestamp;
6822
- if (currentDate) {
6823
- const msgDate = new Date(currentDate);
6824
- msgDate.setHours(hours, minutes, 0, 0);
6825
- timestamp = Math.floor(msgDate.getTime() / 1e3);
6527
+ if (!memberMap.has(sender)) {
6528
+ memberMap.set(sender, { platformId: sender, accountName: sender });
6529
+ }
6530
+ lastMessage = {
6531
+ senderPlatformId: sender,
6532
+ senderAccountName: sender,
6533
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6534
+ type: detectMessageType6(messageContent),
6535
+ content: messageContent || null
6536
+ };
6537
+ messages.push(lastMessage);
6826
6538
  } else {
6827
- const now = /* @__PURE__ */ new Date();
6828
- now.setHours(hours, minutes, 0, 0);
6829
- timestamp = Math.floor(now.getTime() / 1e3);
6830
- }
6831
- const msgType = detectMessageType6(content2);
6832
- if (!memberMap.has(sender)) {
6833
- memberMap.set(sender, {
6834
- platformId: sender,
6835
- accountName: sender
6836
- });
6539
+ const systemMatch = line.match(SYSTEM_MESSAGE_PATTERN);
6540
+ if (systemMatch) {
6541
+ const [, timeText, rawContent] = systemMatch;
6542
+ const messageContent = rawContent.trim();
6543
+ quotedMultiline = false;
6544
+ lastMessage = {
6545
+ senderPlatformId: "system",
6546
+ senderAccountName: "\u7CFB\u7D71",
6547
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6548
+ type: 80 /* SYSTEM */,
6549
+ content: messageContent || null
6550
+ };
6551
+ messages.push(lastMessage);
6552
+ } else if (line.trim() && lastMessage) {
6553
+ let continuation = line;
6554
+ if (quotedMultiline && continuation.endsWith('"')) {
6555
+ continuation = continuation.slice(0, -1);
6556
+ quotedMultiline = false;
6557
+ }
6558
+ lastMessage.content = lastMessage.content ? `${lastMessage.content}
6559
+ ${continuation}` : continuation;
6560
+ }
6837
6561
  }
6838
- lastMessage = {
6839
- senderPlatformId: sender,
6840
- senderAccountName: sender,
6841
- timestamp,
6842
- type: msgType,
6843
- content: content2 || null,
6844
- _isQuotedMultiline: isQuotedMultiline
6845
- // 临时标记,用于追加多行内容时处理结尾引号
6846
- };
6847
- messages.push(lastMessage);
6848
- messagesProcessed++;
6849
- if (messagesProcessed % 1e3 === 0) {
6850
- const progress = createProgress(
6851
- "parsing",
6852
- i,
6853
- lines.length,
6854
- messagesProcessed,
6855
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
6562
+ }
6563
+ if (lineIndex > 0 && lineIndex % yieldEvery === 0) {
6564
+ options2.onProgress?.({ progress: lineIndex / lines.length, messagesProcessed: messages.length });
6565
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
6566
+ options2.checkCancelled?.();
6567
+ }
6568
+ }
6569
+ options2.checkCancelled?.();
6570
+ const memberCount = memberMap.size;
6571
+ const chatType = header ? header.isGroup ? "group" /* GROUP */ : "private" /* PRIVATE */ : memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
6572
+ const result = {
6573
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.LINE, type: chatType },
6574
+ members: Array.from(memberMap.values()),
6575
+ messages
6576
+ };
6577
+ options2.onProgress?.({ progress: 1, messagesProcessed: messages.length });
6578
+ return result;
6579
+ }
6580
+ function extractNameFromFileName3(fileName) {
6581
+ const basename10 = getBasename3(fileName).replace(/\.txt$/, "");
6582
+ return basename10.replace(/^\[LINE\]\s*/i, "").trim() || "\u672A\u77E5\u804A\u5929";
6583
+ }
6584
+ function extractNameFromHeader(header) {
6585
+ const englishPrivate = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
6586
+ if (englishPrivate) return { name: englishPrivate[1].trim(), isGroup: false };
6587
+ const englishGroup = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
6588
+ if (englishGroup) return { name: englishGroup[1].trim(), isGroup: true };
6589
+ const japanesePrivate = header.match(/^\[LINE\] (.+)とのトーク履歴/);
6590
+ if (japanesePrivate) return { name: japanesePrivate[1].trim(), isGroup: false };
6591
+ const japaneseGroup = header.match(/^\[LINE\] (.+)のトーク履歴/);
6592
+ if (japaneseGroup) return { name: japaneseGroup[1].trim(), isGroup: true };
6593
+ const simplifiedPrivate = header.match(/^\[LINE\] 与(.+)的聊天记录/);
6594
+ if (simplifiedPrivate) return { name: simplifiedPrivate[1].trim(), isGroup: false };
6595
+ const simplifiedGroup = header.match(/^\[LINE\] (.+)的聊天记录/);
6596
+ if (simplifiedGroup) return { name: simplifiedGroup[1].trim(), isGroup: true };
6597
+ const traditionalPrivate = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
6598
+ if (traditionalPrivate) return { name: traditionalPrivate[1].trim(), isGroup: false };
6599
+ const traditionalGroup = header.match(/^\[LINE\] (.+)的聊天記錄/);
6600
+ return traditionalGroup ? { name: traditionalGroup[1].trim(), isGroup: true } : null;
6601
+ }
6602
+ function getBasename3(fileName) {
6603
+ return fileName.split(/[\\/]/).pop() ?? fileName;
6604
+ }
6605
+ function parseDateLine(line) {
6606
+ for (let index = 0; index < DATE_PATTERNS.length; index += 1) {
6607
+ const match = line.match(DATE_PATTERNS[index]);
6608
+ if (!match) continue;
6609
+ if (index < 2) {
6610
+ return new Date(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10) - 1, Number.parseInt(match[3], 10));
6611
+ }
6612
+ return new Date(Number.parseInt(match[3], 10), Number.parseInt(match[1], 10) - 1, Number.parseInt(match[2], 10));
6613
+ }
6614
+ return null;
6615
+ }
6616
+ function buildTimestamp(currentDate, timeText, now) {
6617
+ const { hours, minutes } = parseTime(timeText);
6618
+ const value = currentDate ? new Date(currentDate) : now?.() ?? /* @__PURE__ */ new Date();
6619
+ value.setHours(hours, minutes, 0, 0);
6620
+ return Math.floor(value.getTime() / 1e3);
6621
+ }
6622
+ function parseTime(timeText) {
6623
+ const prefix = timeText.match(/^(上午|下午|午前|午後)/)?.[1];
6624
+ const cleanTime = timeText.replace(/^(上午|下午|午前|午後)/, "");
6625
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6626
+ if (!match) {
6627
+ const [hours2, minutes2] = timeText.split(":").map(Number);
6628
+ return { hours: hours2, minutes: minutes2 };
6629
+ }
6630
+ let hours = Number.parseInt(match[1], 10);
6631
+ const minutes = Number.parseInt(match[2], 10);
6632
+ const suffix = match[3]?.toLowerCase();
6633
+ if (suffix === "pm" && hours < 12) hours += 12;
6634
+ if (suffix === "am" && hours === 12) hours = 0;
6635
+ if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6636
+ if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6637
+ return { hours, minutes };
6638
+ }
6639
+ function detectMessageType6(content) {
6640
+ for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
6641
+ if (content === pattern || content.startsWith(pattern)) return type;
6642
+ }
6643
+ if (content.startsWith("[null]") && content.includes("maps.google.com")) return 8 /* LOCATION */;
6644
+ if (content.includes(" joined the group") || content.includes("\u5DF2\u52A0\u5165\u8BE5\u7FA4") || content.includes("\u5DF2\u52A0\u5165\u7FA4\u7D44") || content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u306B\u53C2\u52A0\u3057\u307E\u3057\u305F") || content.includes(" added ") || content.includes(" to the group") || content.includes("\u5DF2\u5C06") || content.includes("\u6DFB\u52A0\u81F3\u7FA4") || content.includes("\u6DFB\u52A0\u5230\u7FA4") || content.includes("\u5DF2\u65B0\u589E") || content.includes("\u81F3\u7FA4\u7D44") || content.includes("\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F") || content.includes(" left the group") || content.includes("\u5DF2\u9000\u7FA4") || content.includes("\u5DF2\u96E2\u958B\u7FA4\u7D44") || content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u3092\u9000\u4F1A\u3057\u307E\u3057\u305F") || content.includes("made an announcement") || content.includes("\u53D1\u5E03\u4E86\u901A\u544A") || content.includes("\u5DF2\u8A2D\u5B9A\u516C\u544A") || content.includes("\u304C\u30A2\u30CA\u30A6\u30F3\u30B9\u3057\u307E\u3057\u305F") || content.includes("unsent a message") || content === "Message unsent." || content.includes("\u64A4\u56DE\u4E86\u4E00\u6761\u6D88\u606F") || content.includes("\u5DF2\u6536\u56DE\u8A0A\u606F") || content.includes("\u9001\u4FE1\u3092\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F") || content.startsWith("Auto-reply")) {
6645
+ return 80 /* SYSTEM */;
6646
+ }
6647
+ return /^https?:\/\//.test(content) ? 7 /* LINK */ : 0 /* TEXT */;
6648
+ }
6649
+
6650
+ // ../../packages/parser/src/formats/line-native-txt.ts
6651
+ var feature14 = {
6652
+ id: "line-native-txt",
6653
+ name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
6654
+ platform: KNOWN_PLATFORMS.LINE,
6655
+ priority: 35,
6656
+ extensions: [".txt"],
6657
+ signatures: {
6658
+ head: LINE_HEAD_SIGNATURES,
6659
+ filename: LINE_FILENAME_SIGNATURES
6660
+ }
6661
+ };
6662
+ async function* parseLine2(options2) {
6663
+ const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
6664
+ const totalBytes = getFileSize(filePath);
6665
+ const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
6666
+ yield { type: "progress", data: initialProgress };
6667
+ onProgress?.(initialProgress);
6668
+ onLog?.("info", `Starting LINE TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
6669
+ const result = await parseLineText(fs17.readFileSync(filePath, "utf-8"), filePath, {
6670
+ onProgress: (progress) => onProgress?.(
6671
+ createProgress(
6672
+ "parsing",
6673
+ Math.round(progress.progress * totalBytes),
6674
+ totalBytes,
6675
+ progress.messagesProcessed,
6676
+ `Processed ${progress.messagesProcessed} messages`
6677
+ )
6678
+ )
6679
+ });
6680
+ yield { type: "meta", data: result.meta };
6681
+ yield { type: "members", data: result.members };
6682
+ for (let index = 0; index < result.messages.length; index += batchSize) {
6683
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
6684
+ }
6685
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6686
+ yield { type: "progress", data: doneProgress };
6687
+ onProgress?.(doneProgress);
6688
+ onLog?.("info", `LINE TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
6689
+ yield {
6690
+ type: "done",
6691
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6692
+ };
6693
+ }
6694
+ var parser_13 = {
6695
+ feature: feature14,
6696
+ parse: parseLine2
6697
+ };
6698
+ var module_14 = {
6699
+ feature: feature14,
6700
+ parser: parser_13
6701
+ };
6702
+ var line_native_txt_default = module_14;
6703
+
6704
+ // ../../packages/parser/src/formats/index.ts
6705
+ var formats = [
6706
+ shuakami_qq_exporter_chunked_default,
6707
+ // 优先级 5 - shuakami/qq-chat-exporter chunked-jsonl
6708
+ shuakami_qq_exporter_default,
6709
+ // 优先级 10 - shuakami/qq-chat-exporter
6710
+ weflow_default,
6711
+ // 优先级 15 - WeFlow 微信导出
6712
+ ycccccccy_echotrace_default,
6713
+ // 优先级 16 - ycccccccy/echotrace 微信导出
6714
+ tyrrrz_discord_exporter_default,
6715
+ // 优先级 20 - Tyrrrz/DiscordChatExporter
6716
+ telegram_native_default,
6717
+ // 优先级 22 - Telegram 官方全量导出 JSON
6718
+ telegram_native_single_default,
6719
+ // 优先级 23 - Telegram 单聊天导出 JSON
6720
+ google_chat_takeout_default,
6721
+ // 优先级 24 - Google Chat Takeout 内部 manifest
6722
+ instagram_native_default,
6723
+ // 优先级 25 - Instagram 官方导出
6724
+ whatsapp_native_txt_default,
6725
+ // 优先级 26 - WhatsApp 官方导出 TXT
6726
+ qq_native_txt_default,
6727
+ // 优先级 30 - QQ 官方导出 TXT
6728
+ line_native_txt_default,
6729
+ // 优先级 35 - LINE 官方导出 TXT
6730
+ chatlab_default,
6731
+ // 优先级 50 - ChatLab JSON
6732
+ chatlab_jsonl_default
6733
+ // 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
6734
+ ];
6735
+
6736
+ // ../../packages/parser/src/chatlab-validator.ts
6737
+ import { createReadStream as createReadStream13 } from "fs";
6738
+ import { readFile, stat } from "fs/promises";
6739
+ import { extname as extname2 } from "path";
6740
+ import { createInterface as createInterface5 } from "readline";
6741
+ var MAX_REPORTED_ISSUES = 100;
6742
+ var MAX_UNIX_SECONDS = 1e11;
6743
+ var KNOWN_MESSAGE_TYPES = new Set(
6744
+ Object.values(MessageType).filter((value) => typeof value === "number")
6745
+ );
6746
+ function createState(format, fileSize) {
6747
+ return {
6748
+ format,
6749
+ fileSize,
6750
+ errorCount: 0,
6751
+ warningCount: 0,
6752
+ issues: [],
6753
+ stats: {
6754
+ headerCount: 0,
6755
+ memberCount: 0,
6756
+ messageCount: 0,
6757
+ uniqueSenderCount: 0,
6758
+ commentLineCount: 0,
6759
+ blankLineCount: 0
6760
+ },
6761
+ memberIds: /* @__PURE__ */ new Set(),
6762
+ ownerIds: /* @__PURE__ */ new Set(),
6763
+ senderIds: /* @__PURE__ */ new Set(),
6764
+ messageIds: /* @__PURE__ */ new Set(),
6765
+ replyTargetIds: /* @__PURE__ */ new Set()
6766
+ };
6767
+ }
6768
+ function addIssue(state, severity, code, message, location = {}) {
6769
+ if (severity === "error") state.errorCount++;
6770
+ else state.warningCount++;
6771
+ if (state.issues.length < MAX_REPORTED_ISSUES) {
6772
+ state.issues.push({ severity, code, message, ...location });
6773
+ }
6774
+ }
6775
+ function isRecord(value) {
6776
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6777
+ }
6778
+ function hasOwn(value, key) {
6779
+ return Object.prototype.hasOwnProperty.call(value, key);
6780
+ }
6781
+ function childLocation(location, path30) {
6782
+ return { ...location, path: path30 };
6783
+ }
6784
+ function validateRequiredString(state, value, path30, location) {
6785
+ if (typeof value === "string" && value.trim().length > 0) return true;
6786
+ addIssue(state, "error", "INVALID_REQUIRED_STRING", "Expected a non-empty string.", childLocation(location, path30));
6787
+ return false;
6788
+ }
6789
+ function validateOptionalString(state, object, key, path30, location) {
6790
+ if (!hasOwn(object, key)) return;
6791
+ if (typeof object[key] !== "string") {
6792
+ addIssue(state, "error", "INVALID_OPTIONAL_STRING", "Expected a string.", childLocation(location, path30));
6793
+ }
6794
+ }
6795
+ function validateUnixSeconds(state, value, path30, location) {
6796
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value < MAX_UNIX_SECONDS) {
6797
+ return true;
6798
+ }
6799
+ addIssue(
6800
+ state,
6801
+ "error",
6802
+ "INVALID_UNIX_SECONDS",
6803
+ "Expected a non-negative integer Unix timestamp in seconds, not milliseconds.",
6804
+ childLocation(location, path30)
6805
+ );
6806
+ return false;
6807
+ }
6808
+ function validateHeader(state, chatlab, meta, location, basePath = "") {
6809
+ state.stats.headerCount++;
6810
+ if (!isRecord(chatlab)) {
6811
+ addIssue(state, "error", "INVALID_CHATLAB_HEADER", "Expected a chatlab object.", {
6812
+ ...location,
6813
+ path: `${basePath}chatlab`
6814
+ });
6815
+ } else {
6816
+ const versionPath = `${basePath}chatlab.version`;
6817
+ if (typeof chatlab.version !== "string") {
6818
+ addIssue(state, "error", "INVALID_VERSION", "Expected a string format version.", {
6819
+ ...location,
6820
+ path: versionPath
6821
+ });
6822
+ } else {
6823
+ state.version = chatlab.version;
6824
+ if (!isSupportedChatLabFormatVersion(chatlab.version)) {
6825
+ addIssue(
6826
+ state,
6827
+ "error",
6828
+ "UNSUPPORTED_VERSION",
6829
+ `Supported ChatLab format versions: ${CHATLAB_SUPPORTED_FORMAT_VERSIONS.join(", ")}.`,
6830
+ {
6831
+ ...location,
6832
+ path: versionPath
6833
+ }
6856
6834
  );
6857
- onProgress?.(progress);
6858
6835
  }
6836
+ }
6837
+ validateUnixSeconds(state, chatlab.exportedAt, `${basePath}chatlab.exportedAt`, location);
6838
+ validateOptionalString(state, chatlab, "generator", `${basePath}chatlab.generator`, location);
6839
+ validateOptionalString(state, chatlab, "description", `${basePath}chatlab.description`, location);
6840
+ }
6841
+ if (!isRecord(meta)) {
6842
+ addIssue(state, "error", "INVALID_META", "Expected a meta object.", {
6843
+ ...location,
6844
+ path: `${basePath}meta`
6845
+ });
6846
+ return;
6847
+ }
6848
+ validateRequiredString(state, meta.name, `${basePath}meta.name`, location);
6849
+ validateRequiredString(state, meta.platform, `${basePath}meta.platform`, location);
6850
+ if (meta.type !== "group" && meta.type !== "private") {
6851
+ addIssue(state, "error", "INVALID_CHAT_TYPE", 'Expected chat type "group" or "private".', {
6852
+ ...location,
6853
+ path: `${basePath}meta.type`
6854
+ });
6855
+ }
6856
+ validateOptionalString(state, meta, "groupId", `${basePath}meta.groupId`, location);
6857
+ validateOptionalString(state, meta, "groupAvatar", `${basePath}meta.groupAvatar`, location);
6858
+ if (hasOwn(meta, "ownerId")) {
6859
+ if (validateRequiredString(state, meta.ownerId, `${basePath}meta.ownerId`, location)) {
6860
+ state.ownerIds.add(meta.ownerId);
6861
+ }
6862
+ }
6863
+ validateOptionalString(state, meta, "sourceSessionId", `${basePath}meta.sourceSessionId`, location);
6864
+ }
6865
+ function validateMember(state, value, path30, location) {
6866
+ state.stats.memberCount++;
6867
+ if (!isRecord(value)) {
6868
+ addIssue(state, "error", "INVALID_MEMBER", "Expected a member object.", childLocation(location, path30));
6869
+ return;
6870
+ }
6871
+ if (validateRequiredString(state, value.platformId, `${path30}.platformId`, location)) {
6872
+ if (state.memberIds.has(value.platformId)) {
6873
+ addIssue(state, "error", "DUPLICATE_MEMBER_ID", "Member platformId must be unique.", {
6874
+ ...location,
6875
+ path: `${path30}.platformId`
6876
+ });
6859
6877
  } else {
6860
- const systemMatch = line.match(SYSTEM_MSG_PATTERN);
6861
- if (systemMatch) {
6862
- const [, timeStr, contentRaw] = systemMatch;
6863
- const content2 = contentRaw.trim();
6864
- let hours = 0;
6865
- let minutes = 0;
6866
- const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1];
6867
- const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, "");
6868
- const partsMatch = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6869
- if (partsMatch) {
6870
- hours = parseInt(partsMatch[1]);
6871
- minutes = parseInt(partsMatch[2]);
6872
- const suffix = partsMatch[3]?.toLowerCase();
6873
- if (suffix === "pm" && hours < 12) hours += 12;
6874
- if (suffix === "am" && hours === 12) hours = 0;
6875
- if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6876
- if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6877
- } else {
6878
- const parts = timeStr.split(":").map(Number);
6879
- hours = parts[0];
6880
- minutes = parts[1];
6881
- }
6882
- let timestamp;
6883
- if (currentDate) {
6884
- const msgDate = new Date(currentDate);
6885
- msgDate.setHours(hours, minutes, 0, 0);
6886
- timestamp = Math.floor(msgDate.getTime() / 1e3);
6887
- } else {
6888
- const now = /* @__PURE__ */ new Date();
6889
- now.setHours(hours, minutes, 0, 0);
6890
- timestamp = Math.floor(now.getTime() / 1e3);
6878
+ state.memberIds.add(value.platformId);
6879
+ }
6880
+ }
6881
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6882
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6883
+ validateOptionalString(state, value, "avatar", `${path30}.avatar`, location);
6884
+ if (hasOwn(value, "aliases")) {
6885
+ if (!Array.isArray(value.aliases) || value.aliases.some((alias) => typeof alias !== "string")) {
6886
+ addIssue(state, "error", "INVALID_ALIASES", "Expected aliases to be an array of strings.", {
6887
+ ...location,
6888
+ path: `${path30}.aliases`
6889
+ });
6890
+ }
6891
+ }
6892
+ if (hasOwn(value, "roles")) {
6893
+ if (!Array.isArray(value.roles)) {
6894
+ addIssue(state, "error", "INVALID_ROLES", "Expected roles to be an array.", {
6895
+ ...location,
6896
+ path: `${path30}.roles`
6897
+ });
6898
+ } else {
6899
+ value.roles.forEach((role, index) => {
6900
+ const rolePath = `${path30}.roles[${index}]`;
6901
+ if (!isRecord(role)) {
6902
+ addIssue(state, "error", "INVALID_ROLE", "Expected a role object.", {
6903
+ ...location,
6904
+ path: rolePath
6905
+ });
6906
+ return;
6891
6907
  }
6892
- lastMessage = {
6893
- senderPlatformId: "system",
6894
- senderAccountName: "\u7CFB\u7D71",
6895
- timestamp,
6896
- type: 80 /* SYSTEM */,
6897
- content: content2 || null
6898
- };
6899
- messages.push(lastMessage);
6900
- messagesProcessed++;
6901
- } else if (line.trim() && lastMessage) {
6902
- let appendLine = line;
6903
- const quotedMsg = lastMessage;
6904
- if (quotedMsg._isQuotedMultiline && appendLine.endsWith('"')) {
6905
- appendLine = appendLine.substring(0, appendLine.length - 1);
6906
- delete quotedMsg._isQuotedMultiline;
6908
+ validateRequiredString(state, role.id, `${rolePath}.id`, location);
6909
+ validateOptionalString(state, role, "name", `${rolePath}.name`, location);
6910
+ });
6911
+ }
6912
+ }
6913
+ }
6914
+ function validateMessage(state, value, path30, location) {
6915
+ state.stats.messageCount++;
6916
+ if (!isRecord(value)) {
6917
+ addIssue(state, "error", "INVALID_MESSAGE", "Expected a message object.", childLocation(location, path30));
6918
+ return;
6919
+ }
6920
+ if (validateRequiredString(state, value.sender, `${path30}.sender`, location)) {
6921
+ state.senderIds.add(value.sender);
6922
+ }
6923
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6924
+ if (validateUnixSeconds(state, value.timestamp, `${path30}.timestamp`, location)) {
6925
+ if (state.lastTimestamp !== void 0 && value.timestamp < state.lastTimestamp) {
6926
+ addIssue(
6927
+ state,
6928
+ "warning",
6929
+ "OUT_OF_ORDER_TIMESTAMP",
6930
+ "Messages should be ordered by timestamp ascending.",
6931
+ childLocation(location, `${path30}.timestamp`)
6932
+ );
6933
+ }
6934
+ state.lastTimestamp = value.timestamp;
6935
+ }
6936
+ if (typeof value.type !== "number" || !Number.isInteger(value.type) || !KNOWN_MESSAGE_TYPES.has(value.type)) {
6937
+ addIssue(state, "error", "INVALID_MESSAGE_TYPE", "Expected a supported numeric ChatLab message type.", {
6938
+ ...location,
6939
+ path: `${path30}.type`
6940
+ });
6941
+ }
6942
+ if (!hasOwn(value, "content") || typeof value.content !== "string" && value.content !== null) {
6943
+ addIssue(state, "error", "INVALID_MESSAGE_CONTENT", "Expected content to be a string or null.", {
6944
+ ...location,
6945
+ path: `${path30}.content`
6946
+ });
6947
+ }
6948
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6949
+ if (hasOwn(value, "platformMessageId")) {
6950
+ if (validateRequiredString(state, value.platformMessageId, `${path30}.platformMessageId`, location)) {
6951
+ if (state.messageIds.has(value.platformMessageId)) {
6952
+ addIssue(state, "error", "DUPLICATE_MESSAGE_ID", "platformMessageId must be unique.", {
6953
+ ...location,
6954
+ path: `${path30}.platformMessageId`
6955
+ });
6956
+ } else {
6957
+ state.messageIds.add(value.platformMessageId);
6958
+ }
6959
+ }
6960
+ }
6961
+ if (hasOwn(value, "replyToMessageId")) {
6962
+ if (validateRequiredString(state, value.replyToMessageId, `${path30}.replyToMessageId`, location)) {
6963
+ state.replyTargetIds.add(value.replyToMessageId);
6964
+ }
6965
+ }
6966
+ }
6967
+ function finalizeState(state, requireExplicitMembers, enforceRequiredSections = true) {
6968
+ if (enforceRequiredSections) {
6969
+ if (state.stats.headerCount === 0) {
6970
+ addIssue(state, "error", "MISSING_HEADER", "A ChatLab header is required.");
6971
+ }
6972
+ if (state.stats.messageCount === 0) {
6973
+ addIssue(state, "error", "MISSING_MESSAGES", "At least one message is required.");
6974
+ }
6975
+ if (requireExplicitMembers && state.stats.memberCount === 0) {
6976
+ addIssue(state, "error", "MISSING_MEMBERS", "The JSON format requires a members array with sender identities.");
6977
+ }
6978
+ }
6979
+ const ownerMemberIds = state.memberIds.size > 0 ? state.memberIds : requireExplicitMembers ? void 0 : state.senderIds;
6980
+ if (ownerMemberIds) {
6981
+ for (const ownerId of state.ownerIds) {
6982
+ if (!ownerMemberIds.has(ownerId)) {
6983
+ addIssue(state, "error", "UNKNOWN_OWNER", "meta.ownerId must reference a member platformId.");
6984
+ }
6985
+ }
6986
+ }
6987
+ if (state.memberIds.size > 0) {
6988
+ for (const senderId of state.senderIds) {
6989
+ if (!state.memberIds.has(senderId)) {
6990
+ addIssue(
6991
+ state,
6992
+ "error",
6993
+ "UNKNOWN_MESSAGE_SENDER",
6994
+ "Every message sender must reference an explicit member platformId."
6995
+ );
6996
+ }
6997
+ }
6998
+ }
6999
+ for (const replyTargetId of state.replyTargetIds) {
7000
+ if (!state.messageIds.has(replyTargetId)) {
7001
+ addIssue(
7002
+ state,
7003
+ "warning",
7004
+ "UNKNOWN_REPLY_TARGET",
7005
+ "A replyToMessageId does not match any platformMessageId in this file."
7006
+ );
7007
+ }
7008
+ }
7009
+ state.stats.uniqueSenderCount = state.senderIds.size;
7010
+ const totalIssueCount = state.errorCount + state.warningCount;
7011
+ return {
7012
+ valid: state.errorCount === 0,
7013
+ format: state.format,
7014
+ version: state.version,
7015
+ fileSize: state.fileSize,
7016
+ errorCount: state.errorCount,
7017
+ warningCount: state.warningCount,
7018
+ issues: state.issues,
7019
+ truncatedIssueCount: Math.max(0, totalIssueCount - state.issues.length),
7020
+ stats: state.stats
7021
+ };
7022
+ }
7023
+ async function validateJson(filePath, fileSize) {
7024
+ const state = createState("json", fileSize);
7025
+ let root;
7026
+ try {
7027
+ root = JSON.parse(await readFile(filePath, "utf8"));
7028
+ } catch {
7029
+ addIssue(state, "error", "INVALID_JSON", "The file is not valid JSON.");
7030
+ return finalizeState(state, true, false);
7031
+ }
7032
+ if (!isRecord(root)) {
7033
+ addIssue(state, "error", "INVALID_ROOT", "Expected a top-level JSON object.");
7034
+ return finalizeState(state, true, false);
7035
+ }
7036
+ validateHeader(state, root.chatlab, root.meta, {});
7037
+ if (!Array.isArray(root.members)) {
7038
+ addIssue(state, "error", "INVALID_MEMBERS", "Expected members to be an array.", { path: "members" });
7039
+ } else {
7040
+ root.members.forEach((member, index) => validateMember(state, member, `members[${index}]`, {}));
7041
+ }
7042
+ if (!Array.isArray(root.messages)) {
7043
+ addIssue(state, "error", "INVALID_MESSAGES", "Expected messages to be an array.", { path: "messages" });
7044
+ } else {
7045
+ root.messages.forEach((message, index) => validateMessage(state, message, `messages[${index}]`, {}));
7046
+ }
7047
+ return finalizeState(state, true);
7048
+ }
7049
+ async function validateJsonl(filePath, fileSize) {
7050
+ const state = createState("jsonl", fileSize);
7051
+ const input = createReadStream13(filePath, { encoding: "utf8" });
7052
+ const lines = createInterface5({ input, crlfDelay: Infinity });
7053
+ let lineNumber = 0;
7054
+ let dataLineCount = 0;
7055
+ let sawMessage = false;
7056
+ for await (const line of lines) {
7057
+ lineNumber++;
7058
+ const trimmed = line.trim();
7059
+ if (!trimmed) {
7060
+ state.stats.blankLineCount++;
7061
+ continue;
7062
+ }
7063
+ if (trimmed.startsWith("#")) {
7064
+ state.stats.commentLineCount++;
7065
+ continue;
7066
+ }
7067
+ dataLineCount++;
7068
+ let value;
7069
+ try {
7070
+ value = JSON.parse(trimmed);
7071
+ } catch {
7072
+ addIssue(state, "error", "INVALID_JSONL_LINE", "The line is not valid JSON.", { line: lineNumber });
7073
+ continue;
7074
+ }
7075
+ if (!isRecord(value)) {
7076
+ addIssue(state, "error", "INVALID_JSONL_VALUE", "Expected a JSON object on every data line.", {
7077
+ line: lineNumber
7078
+ });
7079
+ continue;
7080
+ }
7081
+ if (dataLineCount === 1 && value._type !== "header") {
7082
+ addIssue(state, "error", "HEADER_NOT_FIRST", "The first data line must be a header.", {
7083
+ line: lineNumber,
7084
+ path: "_type"
7085
+ });
7086
+ }
7087
+ switch (value._type) {
7088
+ case "header":
7089
+ if (state.stats.headerCount > 0) {
7090
+ addIssue(state, "error", "DUPLICATE_HEADER", "Only one header line is allowed.", {
7091
+ line: lineNumber,
7092
+ path: "_type"
7093
+ });
6907
7094
  }
6908
- if (lastMessage.content) {
6909
- lastMessage.content += "\n" + appendLine;
6910
- } else {
6911
- lastMessage.content = appendLine;
7095
+ validateHeader(state, value.chatlab, value.meta, { line: lineNumber });
7096
+ break;
7097
+ case "member":
7098
+ if (sawMessage) {
7099
+ addIssue(state, "error", "MEMBER_AFTER_MESSAGE", "Member lines must appear before message lines.", {
7100
+ line: lineNumber
7101
+ });
6912
7102
  }
6913
- }
7103
+ validateMember(state, value, `line[${lineNumber}]`, { line: lineNumber });
7104
+ break;
7105
+ case "message":
7106
+ sawMessage = true;
7107
+ validateMessage(state, value, `line[${lineNumber}]`, { line: lineNumber });
7108
+ break;
7109
+ default:
7110
+ addIssue(state, "error", "UNKNOWN_LINE_TYPE", "Expected _type to be header, member, or message.", {
7111
+ line: lineNumber,
7112
+ path: "_type"
7113
+ });
6914
7114
  }
6915
7115
  }
6916
- const memberCount = memberMap.size;
6917
- const chatType = memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
6918
- const meta = {
6919
- name: chatName,
6920
- platform: KNOWN_PLATFORMS.LINE,
6921
- type: chatType
6922
- };
6923
- yield { type: "meta", data: meta };
6924
- const members = Array.from(memberMap.values());
6925
- yield { type: "members", data: members };
6926
- for (let i = 0; i < messages.length; i += batchSize) {
6927
- const batch = messages.slice(i, i + batchSize);
6928
- yield { type: "messages", data: batch };
6929
- }
6930
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
6931
- yield { type: "progress", data: doneProgress };
6932
- onProgress?.(doneProgress);
6933
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberCount} \u4E2A\u6210\u5458`);
6934
- yield {
6935
- type: "done",
6936
- data: { messageCount: messagesProcessed, memberCount }
6937
- };
7116
+ return finalizeState(state, false);
7117
+ }
7118
+ async function validateChatLabFile(filePath) {
7119
+ const fileStat = await stat(filePath);
7120
+ const extension = extname2(filePath).toLowerCase();
7121
+ if (extension === ".json") return validateJson(filePath, fileStat.size);
7122
+ if (extension === ".jsonl") return validateJsonl(filePath, fileStat.size);
7123
+ const state = createState("unknown", fileStat.size);
7124
+ addIssue(state, "error", "UNSUPPORTED_EXTENSION", "Expected a .json or .jsonl file.");
7125
+ return finalizeState(state, false, false);
6938
7126
  }
6939
- var parser_13 = {
6940
- feature: feature14,
6941
- parse: parseLINE
6942
- };
6943
- var module_14 = {
6944
- feature: feature14,
6945
- parser: parser_13
6946
- };
6947
- var line_native_txt_default = module_14;
6948
-
6949
- // ../../packages/parser/src/formats/index.ts
6950
- var formats = [
6951
- shuakami_qq_exporter_chunked_default,
6952
- // 优先级 5 - shuakami/qq-chat-exporter chunked-jsonl
6953
- shuakami_qq_exporter_default,
6954
- // 优先级 10 - shuakami/qq-chat-exporter
6955
- weflow_default,
6956
- // 优先级 15 - WeFlow 微信导出
6957
- ycccccccy_echotrace_default,
6958
- // 优先级 16 - ycccccccy/echotrace 微信导出
6959
- tyrrrz_discord_exporter_default,
6960
- // 优先级 20 - Tyrrrz/DiscordChatExporter
6961
- telegram_native_default,
6962
- // 优先级 22 - Telegram 官方全量导出 JSON
6963
- telegram_native_single_default,
6964
- // 优先级 23 - Telegram 单聊天导出 JSON
6965
- google_chat_takeout_default,
6966
- // 优先级 24 - Google Chat Takeout 内部 manifest
6967
- instagram_native_default,
6968
- // 优先级 25 - Instagram 官方导出
6969
- whatsapp_native_txt_default,
6970
- // 优先级 26 - WhatsApp 官方导出 TXT
6971
- qq_native_txt_default,
6972
- // 优先级 30 - QQ 官方导出 TXT
6973
- line_native_txt_default,
6974
- // 优先级 35 - LINE 官方导出 TXT
6975
- chatlab_default,
6976
- // 优先级 50 - ChatLab JSON
6977
- chatlab_jsonl_default
6978
- // 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
6979
- ];
6980
7127
 
6981
7128
  // ../../packages/parser/src/index.ts
6982
7129
  var sniffer = createSniffer();
@@ -7051,15 +7198,15 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
7051
7198
  return null;
7052
7199
  }
7053
7200
  for (const entry of entries) {
7054
- const full = path14.join(current, entry.name);
7055
- if (entry.isFile() && supportedExtensions.has(path14.extname(entry.name).toLowerCase())) {
7201
+ const full = path11.join(current, entry.name);
7202
+ if (entry.isFile() && supportedExtensions.has(path11.extname(entry.name).toLowerCase())) {
7056
7203
  const format = detectFormat(full);
7057
7204
  if (format) return full;
7058
7205
  }
7059
7206
  }
7060
7207
  for (const entry of entries) {
7061
7208
  if (entry.isDirectory()) {
7062
- const found = walk(path14.join(current, entry.name), depth + 1);
7209
+ const found = walk(path11.join(current, entry.name), depth + 1);
7063
7210
  if (found) return found;
7064
7211
  }
7065
7212
  }
@@ -7109,7 +7256,7 @@ function logNativeParserStatus() {
7109
7256
 
7110
7257
  // ../../packages/node-runtime/src/import/perf-logger.ts
7111
7258
  import * as fs19 from "fs";
7112
- import * as path15 from "path";
7259
+ import * as path12 from "path";
7113
7260
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7114
7261
  LogLevel2["ERROR"] = "ERROR";
7115
7262
  LogLevel2["INFO"] = "INFO";
@@ -7124,7 +7271,7 @@ function initPerfLog(sessionId, logDir) {
7124
7271
  if (!fs19.existsSync(logDir)) {
7125
7272
  fs19.mkdirSync(logDir, { recursive: true });
7126
7273
  }
7127
- currentLogFile = path15.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7274
+ currentLogFile = path12.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7128
7275
  fs19.writeFileSync(currentLogFile, `=== Import Log ===
7129
7276
  Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
7130
7277
 
@@ -7301,7 +7448,7 @@ function segmentText(text, locale, minLength) {
7301
7448
 
7302
7449
  // ../../packages/node-runtime/src/nlp/dict-manager.ts
7303
7450
  import * as fs20 from "fs";
7304
- import * as path16 from "path";
7451
+ import * as path13 from "path";
7305
7452
  import { createHash } from "crypto";
7306
7453
  var DICT_DOWNLOAD_URL_BASE = "https://chatlab.fun/assets/nlp";
7307
7454
  var DICT_SHA256 = {
@@ -7313,7 +7460,7 @@ var AVAILABLE_DICTS = [
7313
7460
  { id: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", locale: "zh-TW" }
7314
7461
  ];
7315
7462
  function getDictFilePath(nlpDir, dictId) {
7316
- return path16.join(nlpDir, `${dictId}.dict`);
7463
+ return path13.join(nlpDir, `${dictId}.dict`);
7317
7464
  }
7318
7465
  function isDictDownloaded(nlpDir, dictId) {
7319
7466
  return fs20.existsSync(getDictFilePath(nlpDir, dictId));
@@ -8779,7 +8926,7 @@ async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
8779
8926
 
8780
8927
  // ../../packages/node-runtime/src/import/import-lock.ts
8781
8928
  import * as fs22 from "fs";
8782
- import * as path17 from "path";
8929
+ import * as path14 from "path";
8783
8930
  import { randomUUID } from "crypto";
8784
8931
  var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
8785
8932
  var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
@@ -8848,7 +8995,7 @@ function releaseLock(lockPath, fd, owner) {
8848
8995
  }
8849
8996
  async function withDataDirImportLock(userDataDir, task) {
8850
8997
  fs22.mkdirSync(userDataDir, { recursive: true });
8851
- const lockPath = path17.join(userDataDir, IMPORT_LOCK_FILENAME);
8998
+ const lockPath = path14.join(userDataDir, IMPORT_LOCK_FILENAME);
8852
8999
  let fd = null;
8853
9000
  for (let attempt = 0; attempt < 2; attempt++) {
8854
9001
  try {
@@ -8893,7 +9040,7 @@ var ArchiveImportError = class extends Error {
8893
9040
  // ../../packages/node-runtime/src/import/archive/archive-reader.ts
8894
9041
  var import_yauzl = __toESM(require_yauzl(), 1);
8895
9042
  import * as fs23 from "fs";
8896
- import * as path18 from "path";
9043
+ import * as path15 from "path";
8897
9044
  import { pipeline } from "stream/promises";
8898
9045
  var DEFAULT_MAX_ENTRIES = 1e5;
8899
9046
  var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
@@ -9012,7 +9159,7 @@ var ZipArchiveReader = class {
9012
9159
  await this.visitEntries(async (entry, openStream) => {
9013
9160
  const destination = targets.get(entry.name);
9014
9161
  if (!destination || entry.isDirectory) return;
9015
- fs23.mkdirSync(path18.dirname(destination), { recursive: true });
9162
+ fs23.mkdirSync(path15.dirname(destination), { recursive: true });
9016
9163
  await pipeline(await openStream(), fs23.createWriteStream(destination, { flags: "wx" }));
9017
9164
  remaining.delete(entry.name);
9018
9165
  });
@@ -9026,16 +9173,16 @@ var ZipArchiveReader = class {
9026
9173
  };
9027
9174
 
9028
9175
  // ../../packages/node-runtime/src/import/archive/google-chat-resolver.ts
9029
- var import_stream_chain9 = __toESM(require_stream_chain(), 1);
9176
+ var import_stream_chain8 = __toESM(require_stream_chain(), 1);
9030
9177
  var import_stream_json9 = __toESM(require_stream_json(), 1);
9031
9178
  var import_Pick8 = __toESM(require_Pick(), 1);
9032
- var import_StreamValues9 = __toESM(require_StreamValues(), 1);
9179
+ var import_StreamValues8 = __toESM(require_StreamValues(), 1);
9033
9180
  import * as fs24 from "fs";
9034
- import * as path19 from "path";
9035
- var { chain: chain9 } = import_stream_chain9.default;
9181
+ import * as path16 from "path";
9182
+ var { chain: chain8 } = import_stream_chain8.default;
9036
9183
  var { parser: parser10 } = import_stream_json9.default;
9037
9184
  var { pick: pick8 } = import_Pick8.default;
9038
- var { streamValues: streamValues9 } = import_StreamValues9.default;
9185
+ var { streamValues: streamValues8 } = import_StreamValues8.default;
9039
9186
  var GROUP_FILE = /^Takeout\/Google Chat\/Groups\/((DM|Space) [^/]+)\/(group_info|messages)\.json$/;
9040
9187
  var USER_INFO_FILE = /^Takeout\/Google Chat\/Users\/[^/]+\/user_info\.json$/;
9041
9188
  var TAKEOUT_ROOT = /^Takeout\/([^/]+)\//;
@@ -9058,7 +9205,7 @@ async function readSmallJson(stream, maxBytes = MAX_METADATA_BYTES) {
9058
9205
  }
9059
9206
  }
9060
9207
  async function countMessages(stream) {
9061
- const pipeline2 = chain9([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues9()]);
9208
+ const pipeline2 = chain8([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues8()]);
9062
9209
  let count = 0;
9063
9210
  try {
9064
9211
  for await (const _item of pipeline2) count++;
@@ -9163,12 +9310,12 @@ var GoogleChatTakeoutResolver = class {
9163
9310
  fs24.mkdirSync(targetDir, { recursive: true });
9164
9311
  await reader.pipeEntries(
9165
9312
  /* @__PURE__ */ new Map([
9166
- [userInfoEntry, path19.join(targetDir, "user_info.json")],
9167
- [groupInfoEntry, path19.join(targetDir, "group_info.json")],
9168
- [messagesEntry, path19.join(targetDir, "messages.json")]
9313
+ [userInfoEntry, path16.join(targetDir, "user_info.json")],
9314
+ [groupInfoEntry, path16.join(targetDir, "group_info.json")],
9315
+ [messagesEntry, path16.join(targetDir, "messages.json")]
9169
9316
  ])
9170
9317
  );
9171
- const manifestPath = path19.join(targetDir, "google-chat-import.json");
9318
+ const manifestPath = path16.join(targetDir, "google-chat-import.json");
9172
9319
  fs24.writeFileSync(
9173
9320
  manifestPath,
9174
9321
  JSON.stringify(
@@ -9193,7 +9340,7 @@ var GoogleChatTakeoutResolver = class {
9193
9340
 
9194
9341
  // ../../packages/node-runtime/src/import/archive/source-manager.ts
9195
9342
  import * as fs25 from "fs";
9196
- import * as path20 from "path";
9343
+ import * as path17 from "path";
9197
9344
  import { randomUUID as randomUUID2 } from "crypto";
9198
9345
  var DEFAULT_TTL_MS = 30 * 60 * 1e3;
9199
9346
  var ArchiveImportSourceManager = class {
@@ -9265,7 +9412,7 @@ var ArchiveImportSourceManager = class {
9265
9412
  `Archive conversation was not found: ${chatId}`
9266
9413
  );
9267
9414
  }
9268
- const materializedDir = fs25.mkdtempSync(path20.join(this.tempRoot, `materialized-${sourceId}-`));
9415
+ const materializedDir = fs25.mkdtempSync(path17.join(this.tempRoot, `materialized-${sourceId}-`));
9269
9416
  try {
9270
9417
  const reader = new ZipArchiveReader(record.archivePath);
9271
9418
  const materialized = await record.resolver.materialize(reader, chat, materializedDir);
@@ -9386,12 +9533,12 @@ ${rawErrorLabel}: ${details}`;
9386
9533
  }
9387
9534
 
9388
9535
  // ../../packages/node-runtime/src/ai/assistant-parser.ts
9389
- import * as path21 from "path";
9536
+ import * as path18 from "path";
9390
9537
  import matter from "gray-matter";
9391
9538
  function parseAssistantFile(content, filePath) {
9392
9539
  try {
9393
9540
  const { data: fm, content: body } = matter(content);
9394
- const id = fm.id ?? path21.basename(filePath, ".md");
9541
+ const id = fm.id ?? path18.basename(filePath, ".md");
9395
9542
  const name = fm.name;
9396
9543
  if (!name) return null;
9397
9544
  return {
@@ -9611,8 +9758,8 @@ var AssistantManager = class {
9611
9758
  }
9612
9759
  // ==================== Init ====================
9613
9760
  init() {
9614
- const { fs: fs37, assistantsDir } = this.deps;
9615
- fs37.ensureDir(assistantsDir);
9761
+ const { fs: fs36, assistantsDir } = this.deps;
9762
+ fs36.ensureDir(assistantsDir);
9616
9763
  const { generalCreated, generalUpdated } = this.syncGeneralAssistants();
9617
9764
  this.loadAll();
9618
9765
  this.initialized = true;
@@ -9624,25 +9771,25 @@ var AssistantManager = class {
9624
9771
  return { total: this.cache.size, generalCreated, generalUpdated };
9625
9772
  }
9626
9773
  syncGeneralAssistants() {
9627
- const { fs: fs37, assistantsDir } = this.deps;
9774
+ const { fs: fs36, assistantsDir } = this.deps;
9628
9775
  let generalCreated = false;
9629
9776
  let generalUpdated = false;
9630
9777
  for (const id of this.generalIds) {
9631
9778
  const config = this.getBuiltinConfig(id);
9632
9779
  if (!config) continue;
9633
- const filePath = fs37.joinPath(assistantsDir, `${id}.md`);
9634
- if (!fs37.fileExists(filePath)) {
9635
- fs37.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
9780
+ const filePath = fs36.joinPath(assistantsDir, `${id}.md`);
9781
+ if (!fs36.fileExists(filePath)) {
9782
+ fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
9636
9783
  generalCreated = true;
9637
9784
  continue;
9638
9785
  }
9639
9786
  try {
9640
- const existing = parseAssistantFile(fs37.readFile(filePath), filePath);
9787
+ const existing = parseAssistantFile(fs36.readFile(filePath), filePath);
9641
9788
  if (!existing) continue;
9642
9789
  if (existing.builtinId !== id) {
9643
9790
  try {
9644
9791
  const backupId = this.backupAssistantOccupyingReservedId(existing);
9645
- fs37.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
9792
+ fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
9646
9793
  generalCreated = true;
9647
9794
  this.deps.logger?.info("AssistantManager", "Backed up assistant occupying reserved default ID", {
9648
9795
  reservedId: id,
@@ -9658,7 +9805,7 @@ var AssistantManager = class {
9658
9805
  continue;
9659
9806
  }
9660
9807
  if (!this.shouldUpgradeBuiltin(existing, config)) continue;
9661
- fs37.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config, existing.id)));
9808
+ fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config, existing.id)));
9662
9809
  generalUpdated = true;
9663
9810
  this.deps.logger?.info("AssistantManager", "Updated unmodified default assistant template", {
9664
9811
  id,
@@ -9674,14 +9821,14 @@ var AssistantManager = class {
9674
9821
  return { generalCreated, generalUpdated };
9675
9822
  }
9676
9823
  backupAssistantOccupyingReservedId(config) {
9677
- const { fs: fs37, assistantsDir } = this.deps;
9824
+ const { fs: fs36, assistantsDir } = this.deps;
9678
9825
  const baseId = this.deps.generateId?.() || `custom_${Date.now().toString(36)}`;
9679
9826
  let backupId = baseId;
9680
9827
  let suffix = 2;
9681
- while (this.generalIds.includes(backupId) || fs37.fileExists(fs37.joinPath(assistantsDir, `${backupId}.md`))) {
9828
+ while (this.generalIds.includes(backupId) || fs36.fileExists(fs36.joinPath(assistantsDir, `${backupId}.md`))) {
9682
9829
  backupId = `${baseId}_${suffix++}`;
9683
9830
  }
9684
- fs37.writeFile(fs37.joinPath(assistantsDir, `${backupId}.md`), serializeAssistant({ ...config, id: backupId }));
9831
+ fs36.writeFile(fs36.joinPath(assistantsDir, `${backupId}.md`), serializeAssistant({ ...config, id: backupId }));
9685
9832
  return backupId;
9686
9833
  }
9687
9834
  getConfigDigest(config) {
@@ -9718,13 +9865,13 @@ var AssistantManager = class {
9718
9865
  return { isUnmodified, isOutdated };
9719
9866
  }
9720
9867
  loadAll() {
9721
- const { fs: fs37, assistantsDir } = this.deps;
9868
+ const { fs: fs36, assistantsDir } = this.deps;
9722
9869
  this.cache.clear();
9723
- const files = fs37.listFiles(assistantsDir, ".md");
9870
+ const files = fs36.listFiles(assistantsDir, ".md");
9724
9871
  for (const file of files) {
9725
9872
  try {
9726
- const filePath = fs37.joinPath(assistantsDir, file);
9727
- const content = fs37.readFile(filePath);
9873
+ const filePath = fs36.joinPath(assistantsDir, file);
9874
+ const content = fs36.readFile(filePath);
9728
9875
  const config = parseAssistantFile(content, filePath);
9729
9876
  if (config) {
9730
9877
  this.cache.set(config.id, config);
@@ -9901,12 +10048,12 @@ var AssistantManager = class {
9901
10048
  };
9902
10049
 
9903
10050
  // ../../packages/node-runtime/src/ai/skill-parser.ts
9904
- import * as path22 from "path";
10051
+ import * as path19 from "path";
9905
10052
  import matter2 from "gray-matter";
9906
10053
  function parseSkillFile(content, filePath) {
9907
10054
  try {
9908
10055
  const { data: fm, content: prompt } = matter2(content);
9909
- const id = fm.id ?? path22.basename(filePath, ".md");
10056
+ const id = fm.id ?? path19.basename(filePath, ".md");
9910
10057
  const name = fm.name;
9911
10058
  if (!name) return null;
9912
10059
  return {
@@ -9925,7 +10072,7 @@ function parseSkillFile(content, filePath) {
9925
10072
  function extractSkillId(content, filePath) {
9926
10073
  try {
9927
10074
  const { data: fm } = matter2(content);
9928
- return fm.id ?? path22.basename(filePath, ".md");
10075
+ return fm.id ?? path19.basename(filePath, ".md");
9929
10076
  } catch {
9930
10077
  return null;
9931
10078
  }
@@ -9945,7 +10092,7 @@ function validateChatScope(raw) {
9945
10092
  // ../../packages/node-runtime/src/ai/chats.ts
9946
10093
  import Database from "better-sqlite3";
9947
10094
  import * as fs26 from "fs";
9948
- import * as path23 from "path";
10095
+ import * as path20 from "path";
9949
10096
  var DEFAULT_GENERAL_ID = DEFAULT_GENERAL_ASSISTANT_ID;
9950
10097
  var defaultLogger = {
9951
10098
  warn(_category, message, extra) {
@@ -9971,7 +10118,7 @@ var AIChatManager = class {
9971
10118
  getDb() {
9972
10119
  if (this.db) return this.db;
9973
10120
  this.ensureDir(this.aiDataDir);
9974
- const dbPath = path23.join(this.aiDataDir, "conversations.db");
10121
+ const dbPath = path20.join(this.aiDataDir, "conversations.db");
9975
10122
  this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
9976
10123
  this.db.pragma("journal_mode = WAL");
9977
10124
  this.db.exec(`
@@ -10214,16 +10361,16 @@ var AIChatManager = class {
10214
10361
  if (allRows.length === 0) return [];
10215
10362
  const rowMap = new Map(allRows.map((row) => [row.id, row]));
10216
10363
  let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
10217
- const path33 = [];
10364
+ const path30 = [];
10218
10365
  const seen = /* @__PURE__ */ new Set();
10219
10366
  while (currentId && !seen.has(currentId)) {
10220
10367
  seen.add(currentId);
10221
10368
  const row = rowMap.get(currentId);
10222
10369
  if (!row) break;
10223
- path33.push(row);
10370
+ path30.push(row);
10224
10371
  currentId = row.parentId;
10225
10372
  }
10226
- return path33.length > 0 ? path33.reverse() : allRows;
10373
+ return path30.length > 0 ? path30.reverse() : allRows;
10227
10374
  }
10228
10375
  // ==================== 生命周期 ====================
10229
10376
  close() {
@@ -10793,21 +10940,21 @@ var SkillManagerCore = class {
10793
10940
  }
10794
10941
  // ==================== Init ====================
10795
10942
  init() {
10796
- const { fs: fs37, skillsDir } = this.deps;
10797
- fs37.ensureDir(skillsDir);
10943
+ const { fs: fs36, skillsDir } = this.deps;
10944
+ fs36.ensureDir(skillsDir);
10798
10945
  this.loadAll();
10799
10946
  this.initialized = true;
10800
10947
  this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
10801
10948
  return { total: this.cache.size };
10802
10949
  }
10803
10950
  loadAll() {
10804
- const { fs: fs37, skillsDir } = this.deps;
10951
+ const { fs: fs36, skillsDir } = this.deps;
10805
10952
  this.cache.clear();
10806
- const files = fs37.listFiles(skillsDir, ".md");
10953
+ const files = fs36.listFiles(skillsDir, ".md");
10807
10954
  for (const file of files) {
10808
10955
  try {
10809
- const filePath = fs37.joinPath(skillsDir, file);
10810
- const content = fs37.readFile(filePath);
10956
+ const filePath = fs36.joinPath(skillsDir, file);
10957
+ const content = fs36.readFile(filePath);
10811
10958
  const def = parseSkillFile(content, filePath);
10812
10959
  if (def) {
10813
10960
  this.cache.set(def.id, def);
@@ -10960,7 +11107,7 @@ var SkillManagerCore = class {
10960
11107
 
10961
11108
  // ../../packages/node-runtime/src/ai/skill-manager.ts
10962
11109
  import * as fs27 from "fs";
10963
- import * as path24 from "path";
11110
+ import * as path21 from "path";
10964
11111
  var SKILLS_DIR_NAME = "skills";
10965
11112
  var defaultLogger2 = {
10966
11113
  info: () => {
@@ -10989,7 +11136,7 @@ var nodeFs = {
10989
11136
  return fs27.existsSync(filePath);
10990
11137
  },
10991
11138
  joinPath(...parts) {
10992
- return path24.join(...parts);
11139
+ return path21.join(...parts);
10993
11140
  }
10994
11141
  };
10995
11142
  var SkillManager = class {
@@ -10999,7 +11146,7 @@ var SkillManager = class {
10999
11146
  this.logger = logger ?? defaultLogger2;
11000
11147
  this.core = new SkillManagerCore({
11001
11148
  fs: nodeFs,
11002
- skillsDir: path24.join(aiDataDir, SKILLS_DIR_NAME),
11149
+ skillsDir: path21.join(aiDataDir, SKILLS_DIR_NAME),
11003
11150
  builtinRawSkills: [],
11004
11151
  logger: {
11005
11152
  info: () => {
@@ -12861,7 +13008,7 @@ var PLAN_INTENTS = [
12861
13008
  "mixed"
12862
13009
  ];
12863
13010
  var STREAM_MARKER_HOLDBACK_CHARS = Math.max("</draft>".length, "</plan>".length, "<json>".length) - 1;
12864
- function isRecord(value) {
13011
+ function isRecord2(value) {
12865
13012
  return typeof value === "object" && value !== null && !Array.isArray(value);
12866
13013
  }
12867
13014
  function isIntent(value) {
@@ -12883,10 +13030,10 @@ function extractJsonObject4(text) {
12883
13030
  function parsePlanJson(rawText, availableTools) {
12884
13031
  try {
12885
13032
  const parsed = JSON.parse(extractJsonObject4(rawText));
12886
- if (!isRecord(parsed)) return null;
13033
+ if (!isRecord2(parsed)) return null;
12887
13034
  const title = normalizeString(parsed.title);
12888
13035
  if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null;
12889
- const steps = parsed.steps.filter(isRecord).map((step) => {
13036
+ const steps = parsed.steps.filter(isRecord2).map((step) => {
12890
13037
  const goal = normalizeString(step.goal);
12891
13038
  const evidenceNeeded = normalizeString(step.evidenceNeeded);
12892
13039
  if (!goal || !evidenceNeeded) return null;
@@ -14865,9 +15012,9 @@ var localeMap = {
14865
15012
  "ja-JP": ja_JP_default,
14866
15013
  "zh-TW": zh_TW_default
14867
15014
  };
14868
- function getNestedValue(obj, path33) {
15015
+ function getNestedValue(obj, path30) {
14869
15016
  let current = obj;
14870
- for (const segment2 of path33.split(".")) {
15017
+ for (const segment2 of path30.split(".")) {
14871
15018
  if (current == null || typeof current !== "object") return void 0;
14872
15019
  current = current[segment2];
14873
15020
  }
@@ -15213,19 +15360,26 @@ var LLMConfigStore = class {
15213
15360
  this.resolveApiKey = deps.resolveApiKey;
15214
15361
  this.onStoreLoaded = deps.onStoreLoaded;
15215
15362
  }
15363
+ withStoreLock(action) {
15364
+ return this.storage.withLock ? this.storage.withLock("llm-config", action) : action();
15365
+ }
15216
15366
  loadStore() {
15217
15367
  const store = this.storage.readJson("llm-config");
15218
15368
  if (!store) {
15219
15369
  return { configs: [], defaultAssistant: null, fastModel: null };
15220
15370
  }
15221
15371
  this.onStoreLoaded?.(store.configs);
15222
- const resolvedConfigs = this.resolveApiKey ? store.configs.map((config) => {
15372
+ const sanitizedConfigs = store.configs.map((config) => ({
15373
+ ...config,
15374
+ apiKey: config.apiKey?.startsWith("enc:") ? "" : config.apiKey || ""
15375
+ }));
15376
+ const resolvedConfigs = this.resolveApiKey ? sanitizedConfigs.map((config) => {
15223
15377
  const profileKey = this.resolveApiKey(
15224
15378
  config.provider,
15225
15379
  config.authProfile
15226
15380
  );
15227
- return { ...config, apiKey: profileKey || config.apiKey || "" };
15228
- }) : store.configs;
15381
+ return { ...config, apiKey: profileKey || config.apiKey };
15382
+ }) : sanitizedConfigs;
15229
15383
  return {
15230
15384
  ...store,
15231
15385
  configs: resolvedConfigs,
@@ -15277,103 +15431,113 @@ var LLMConfigStore = class {
15277
15431
  return store.configs.find((c) => c.id === id) || null;
15278
15432
  }
15279
15433
  addConfig(config) {
15280
- const store = this.loadStore();
15281
- if (store.configs.length >= MAX_CONFIG_COUNT) {
15282
- return { success: false, error: this.t("llm.maxConfigs", { count: MAX_CONFIG_COUNT }) };
15283
- }
15284
- const now = Date.now();
15285
- const newConfig = {
15286
- ...config,
15287
- id: this.generateId(),
15288
- createdAt: now,
15289
- updatedAt: now
15290
- };
15291
- store.configs.push(newConfig);
15292
- if (store.configs.length === 1) {
15293
- store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || "" };
15294
- }
15295
- if (newConfig.apiKey && this.onApiKeyCreated) {
15296
- const profileName = this.onApiKeyCreated(newConfig, newConfig.apiKey);
15297
- if (profileName) {
15298
- ;
15299
- newConfig.authProfile = profileName;
15434
+ return this.withStoreLock(() => {
15435
+ const store = this.loadStore();
15436
+ if (store.configs.length >= MAX_CONFIG_COUNT) {
15437
+ return { success: false, error: this.t("llm.maxConfigs", { count: MAX_CONFIG_COUNT }) };
15300
15438
  }
15301
- }
15302
- this.saveStore(store);
15303
- return { success: true, config: { ...newConfig, apiKey: "" } };
15439
+ const now = Date.now();
15440
+ const newConfig = {
15441
+ ...config,
15442
+ id: this.generateId(),
15443
+ createdAt: now,
15444
+ updatedAt: now
15445
+ };
15446
+ store.configs.push(newConfig);
15447
+ if (store.configs.length === 1) {
15448
+ store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || "" };
15449
+ }
15450
+ if (newConfig.apiKey && this.onApiKeyCreated) {
15451
+ const profileName = this.onApiKeyCreated(newConfig, newConfig.apiKey);
15452
+ if (profileName) {
15453
+ ;
15454
+ newConfig.authProfile = profileName;
15455
+ }
15456
+ }
15457
+ this.saveStore(store);
15458
+ return { success: true, config: { ...newConfig, apiKey: "" } };
15459
+ });
15304
15460
  }
15305
15461
  updateConfig(id, updates) {
15306
- const store = this.loadStore();
15307
- const index = store.configs.findIndex((c) => c.id === id);
15308
- if (index === -1) {
15309
- return { success: false, error: this.t("llm.configNotFound") };
15310
- }
15311
- const oldConfig = store.configs[index];
15312
- const oldProfileName = oldConfig.authProfile;
15313
- const updated = {
15314
- ...oldConfig,
15315
- ...updates,
15316
- updatedAt: Date.now()
15317
- };
15318
- store.configs[index] = updated;
15319
- let oldProfileToDelete = null;
15320
- if (updates.apiKey && this.onApiKeyCreated) {
15321
- const profileName = this.onApiKeyCreated(updated, updates.apiKey);
15322
- if (profileName) {
15323
- ;
15324
- store.configs[index].authProfile = profileName;
15325
- if (oldProfileName && oldProfileName !== profileName && !isAuthProfileUsed(store.configs, oldProfileName, oldConfig.provider)) {
15326
- oldProfileToDelete = oldConfig;
15462
+ return this.withStoreLock(() => {
15463
+ const store = this.loadStore();
15464
+ const index = store.configs.findIndex((c) => c.id === id);
15465
+ if (index === -1) {
15466
+ return { success: false, error: this.t("llm.configNotFound") };
15467
+ }
15468
+ const oldConfig = store.configs[index];
15469
+ const oldProfileName = oldConfig.authProfile;
15470
+ const updated = {
15471
+ ...oldConfig,
15472
+ ...updates,
15473
+ updatedAt: Date.now()
15474
+ };
15475
+ store.configs[index] = updated;
15476
+ let oldProfileToDelete = null;
15477
+ if (updates.apiKey && this.onApiKeyCreated) {
15478
+ const profileName = this.onApiKeyCreated(updated, updates.apiKey);
15479
+ if (profileName) {
15480
+ ;
15481
+ store.configs[index].authProfile = profileName;
15482
+ if (oldProfileName && oldProfileName !== profileName && !isAuthProfileUsed(store.configs, oldProfileName, oldConfig.provider)) {
15483
+ oldProfileToDelete = oldConfig;
15484
+ }
15327
15485
  }
15328
15486
  }
15329
- }
15330
- this.saveStore(store);
15331
- if (oldProfileToDelete) this.onApiKeyDeleted?.(oldProfileToDelete);
15332
- return { success: true };
15487
+ this.saveStore(store);
15488
+ if (oldProfileToDelete) this.onApiKeyDeleted?.(oldProfileToDelete);
15489
+ return { success: true };
15490
+ });
15333
15491
  }
15334
15492
  deleteConfig(id) {
15335
- const store = this.loadStore();
15336
- const index = store.configs.findIndex((c) => c.id === id);
15337
- if (index === -1) {
15338
- return { success: false, error: this.t("llm.configNotFound") };
15339
- }
15340
- const deleted = store.configs[index];
15341
- store.configs.splice(index, 1);
15342
- const fallback = store.configs[0];
15343
- if (store.defaultAssistant?.configId === id) {
15344
- store.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
15345
- }
15346
- if (store.fastModel?.configId === id) {
15347
- store.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
15348
- }
15349
- this.saveStore(store);
15350
- const deletedProfileName = getAuthProfile(deleted);
15351
- if (!deletedProfileName || !isAuthProfileUsed(store.configs, deletedProfileName, deleted.provider)) {
15352
- this.onApiKeyDeleted?.(deleted);
15353
- }
15354
- return { success: true };
15493
+ return this.withStoreLock(() => {
15494
+ const store = this.loadStore();
15495
+ const index = store.configs.findIndex((c) => c.id === id);
15496
+ if (index === -1) {
15497
+ return { success: false, error: this.t("llm.configNotFound") };
15498
+ }
15499
+ const deleted = store.configs[index];
15500
+ store.configs.splice(index, 1);
15501
+ const fallback = store.configs[0];
15502
+ if (store.defaultAssistant?.configId === id) {
15503
+ store.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
15504
+ }
15505
+ if (store.fastModel?.configId === id) {
15506
+ store.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
15507
+ }
15508
+ this.saveStore(store);
15509
+ const deletedProfileName = getAuthProfile(deleted);
15510
+ if (!deletedProfileName || !isAuthProfileUsed(store.configs, deletedProfileName, deleted.provider)) {
15511
+ this.onApiKeyDeleted?.(deleted);
15512
+ }
15513
+ return { success: true };
15514
+ });
15355
15515
  }
15356
15516
  setDefaultAssistantModel(configId, modelId) {
15357
- const store = this.loadStore();
15358
- const config = store.configs.find((c) => c.id === configId);
15359
- if (!config) {
15360
- return { success: false, error: this.t("llm.configNotFound") };
15361
- }
15362
- store.defaultAssistant = { configId, modelId };
15363
- this.saveStore(store);
15364
- return { success: true };
15365
- }
15366
- setFastModel(slot) {
15367
- const store = this.loadStore();
15368
- if (slot !== null) {
15369
- const config = store.configs.find((c) => c.id === slot.configId);
15517
+ return this.withStoreLock(() => {
15518
+ const store = this.loadStore();
15519
+ const config = store.configs.find((c) => c.id === configId);
15370
15520
  if (!config) {
15371
15521
  return { success: false, error: this.t("llm.configNotFound") };
15372
15522
  }
15373
- }
15374
- store.fastModel = slot;
15375
- this.saveStore(store);
15376
- return { success: true };
15523
+ store.defaultAssistant = { configId, modelId };
15524
+ this.saveStore(store);
15525
+ return { success: true };
15526
+ });
15527
+ }
15528
+ setFastModel(slot) {
15529
+ return this.withStoreLock(() => {
15530
+ const store = this.loadStore();
15531
+ if (slot !== null) {
15532
+ const config = store.configs.find((c) => c.id === slot.configId);
15533
+ if (!config) {
15534
+ return { success: false, error: this.t("llm.configNotFound") };
15535
+ }
15536
+ }
15537
+ store.fastModel = slot;
15538
+ this.saveStore(store);
15539
+ return { success: true };
15540
+ });
15377
15541
  }
15378
15542
  hasActiveConfig() {
15379
15543
  return this.getDefaultAssistantConfig() !== null;
@@ -15381,20 +15545,30 @@ var LLMConfigStore = class {
15381
15545
  };
15382
15546
 
15383
15547
  // ../../packages/node-runtime/src/ai/file-config-storage.ts
15384
- import * as fs28 from "fs";
15385
- import * as path25 from "path";
15548
+ import * as path22 from "path";
15386
15549
  function createFileConfigStorage(baseDir) {
15550
+ const heldLocks = /* @__PURE__ */ new Set();
15551
+ const getFilePath = (key) => path22.join(baseDir, `${key}.json`);
15387
15552
  return {
15388
15553
  readJson(key) {
15389
- try {
15390
- return JSON.parse(fs28.readFileSync(path25.join(baseDir, `${key}.json`), "utf-8"));
15391
- } catch {
15392
- return null;
15393
- }
15554
+ return readJsonFile(getFilePath(key));
15394
15555
  },
15395
15556
  writeJson(key, data) {
15396
- fs28.mkdirSync(baseDir, { recursive: true });
15397
- fs28.writeFileSync(path25.join(baseDir, `${key}.json`), JSON.stringify(data, null, 2), "utf-8");
15557
+ const filePath = getFilePath(key);
15558
+ const write = () => writeJsonFileAtomically(filePath, data);
15559
+ if (heldLocks.has(key)) write();
15560
+ else withFileLock(filePath, write);
15561
+ },
15562
+ withLock(key, action) {
15563
+ if (heldLocks.has(key)) return action();
15564
+ return withFileLock(getFilePath(key), () => {
15565
+ heldLocks.add(key);
15566
+ try {
15567
+ return action();
15568
+ } finally {
15569
+ heldLocks.delete(key);
15570
+ }
15571
+ });
15398
15572
  }
15399
15573
  };
15400
15574
  }
@@ -15402,23 +15576,57 @@ function createFileConfigStorage(baseDir) {
15402
15576
  // ../../packages/node-runtime/src/ai/auth-profile-llm-config-store.ts
15403
15577
  var defaultDeps = {
15404
15578
  resolveApiKey: (provider, authProfile) => resolveApiKey(provider, authProfile) || void 0,
15579
+ loadAuthProfiles,
15405
15580
  writeAuthProfile,
15406
15581
  deleteAuthProfile
15407
15582
  };
15408
15583
  function getAuthProfile2(config) {
15409
15584
  return config.authProfile;
15410
15585
  }
15411
- function createAuthProfileLlmConfigStore(storage, deps = defaultDeps) {
15586
+ function getNamedAuthProfile(config) {
15587
+ return config.name?.toLowerCase().replace(/\s+/g, "-") || config.provider;
15588
+ }
15589
+ function restoreLegacyAuthProfileReferences(configs, authProfiles) {
15590
+ const usedProfiles = new Set(configs.map(getAuthProfile2).filter((name) => Boolean(name)));
15591
+ for (const config of configs) {
15592
+ if (getAuthProfile2(config)) continue;
15593
+ const namedProfile = getNamedAuthProfile(config);
15594
+ const namedProfileValue = authProfiles.profiles[namedProfile];
15595
+ if (namedProfileValue?.provider === config.provider && !usedProfiles.has(namedProfile)) {
15596
+ ;
15597
+ config.authProfile = namedProfile;
15598
+ usedProfiles.add(namedProfile);
15599
+ continue;
15600
+ }
15601
+ const baseName = deriveAuthProfileName(config.provider, config);
15602
+ for (let index = 1; index <= Object.keys(authProfiles.profiles).length; index++) {
15603
+ const candidate = index === 1 ? baseName : `${baseName}-${index}`;
15604
+ const profile = authProfiles.profiles[candidate];
15605
+ if (!profile || profile.provider !== config.provider || usedProfiles.has(candidate)) continue;
15606
+ config.authProfile = candidate;
15607
+ usedProfiles.add(candidate);
15608
+ break;
15609
+ }
15610
+ }
15611
+ }
15612
+ function createAuthProfileLlmConfigStore(storage, deps = {}) {
15613
+ const resolvedDeps = { ...defaultDeps, ...deps };
15412
15614
  return new LLMConfigStore(storage, {
15413
- resolveApiKey: deps.resolveApiKey,
15615
+ t: deps.t,
15616
+ generateId: deps.generateId,
15617
+ onStoreLoaded: (configs) => {
15618
+ restoreLegacyAuthProfileReferences(configs, resolvedDeps.loadAuthProfiles());
15619
+ deps.onStoreLoaded?.(configs);
15620
+ },
15621
+ resolveApiKey: resolvedDeps.resolveApiKey,
15414
15622
  onApiKeyCreated: (config, apiKey) => {
15415
- const profileName = config.name?.toLowerCase().replace(/\s+/g, "-") || config.provider;
15416
- deps.writeAuthProfile(profileName, { type: "api_key", provider: config.provider, key: apiKey });
15623
+ const profileName = getNamedAuthProfile(config);
15624
+ resolvedDeps.writeAuthProfile(profileName, { type: "api_key", provider: config.provider, key: apiKey });
15417
15625
  return profileName;
15418
15626
  },
15419
15627
  onApiKeyDeleted: (config) => {
15420
15628
  const profileName = getAuthProfile2(config);
15421
- if (profileName) deps.deleteAuthProfile(profileName);
15629
+ if (profileName) resolvedDeps.deleteAuthProfile(profileName);
15422
15630
  }
15423
15631
  });
15424
15632
  }
@@ -15432,43 +15640,52 @@ var CustomProviderStore = class {
15432
15640
  this.storage = storage;
15433
15641
  }
15434
15642
  storage;
15643
+ withLock(action) {
15644
+ return this.storage.withLock ? this.storage.withLock("custom-providers", action) : action();
15645
+ }
15435
15646
  getAll() {
15436
15647
  return this.storage.readJson("custom-providers") ?? [];
15437
15648
  }
15438
15649
  add(input) {
15439
- const providers = this.getAll();
15440
- const newProvider = {
15441
- id: generateCustomId(),
15442
- name: input.name,
15443
- kind: input.kind || "openai-compatible",
15444
- defaultBaseUrl: input.defaultBaseUrl,
15445
- authMode: "api-key",
15446
- supportsCustomModels: input.supportsCustomModels ?? true,
15447
- modelIds: input.modelIds ?? [],
15448
- builtin: false,
15449
- enabledByDefault: false,
15450
- website: input.website,
15451
- consoleUrl: input.consoleUrl
15452
- };
15453
- providers.push(newProvider);
15454
- this.storage.writeJson("custom-providers", providers);
15455
- return newProvider;
15650
+ return this.withLock(() => {
15651
+ const providers = this.getAll();
15652
+ const newProvider = {
15653
+ id: generateCustomId(),
15654
+ name: input.name,
15655
+ kind: input.kind || "openai-compatible",
15656
+ defaultBaseUrl: input.defaultBaseUrl,
15657
+ authMode: "api-key",
15658
+ supportsCustomModels: input.supportsCustomModels ?? true,
15659
+ modelIds: input.modelIds ?? [],
15660
+ builtin: false,
15661
+ enabledByDefault: false,
15662
+ website: input.website,
15663
+ consoleUrl: input.consoleUrl
15664
+ };
15665
+ providers.push(newProvider);
15666
+ this.storage.writeJson("custom-providers", providers);
15667
+ return newProvider;
15668
+ });
15456
15669
  }
15457
15670
  update(id, updates) {
15458
- const providers = this.getAll();
15459
- const index = providers.findIndex((p) => p.id === id);
15460
- if (index === -1) return { success: false, error: "Custom provider not found" };
15461
- providers[index] = { ...providers[index], ...updates };
15462
- this.storage.writeJson("custom-providers", providers);
15463
- return { success: true };
15671
+ return this.withLock(() => {
15672
+ const providers = this.getAll();
15673
+ const index = providers.findIndex((p) => p.id === id);
15674
+ if (index === -1) return { success: false, error: "Custom provider not found" };
15675
+ providers[index] = { ...providers[index], ...updates };
15676
+ this.storage.writeJson("custom-providers", providers);
15677
+ return { success: true };
15678
+ });
15464
15679
  }
15465
15680
  delete(id) {
15466
- const providers = this.getAll();
15467
- const index = providers.findIndex((p) => p.id === id);
15468
- if (index === -1) return { success: false, error: "Custom provider not found" };
15469
- providers.splice(index, 1);
15470
- this.storage.writeJson("custom-providers", providers);
15471
- return { success: true };
15681
+ return this.withLock(() => {
15682
+ const providers = this.getAll();
15683
+ const index = providers.findIndex((p) => p.id === id);
15684
+ if (index === -1) return { success: false, error: "Custom provider not found" };
15685
+ providers.splice(index, 1);
15686
+ this.storage.writeJson("custom-providers", providers);
15687
+ return { success: true };
15688
+ });
15472
15689
  }
15473
15690
  };
15474
15691
  var CustomModelStore = class {
@@ -15476,51 +15693,96 @@ var CustomModelStore = class {
15476
15693
  this.storage = storage;
15477
15694
  }
15478
15695
  storage;
15696
+ withLock(action) {
15697
+ return this.storage.withLock ? this.storage.withLock("custom-models", action) : action();
15698
+ }
15479
15699
  getAll() {
15480
15700
  return this.storage.readJson("custom-models") ?? [];
15481
15701
  }
15482
15702
  add(input) {
15483
- const models = this.getAll();
15484
- if (models.find((m) => m.id === input.id && m.providerId === input.providerId)) {
15485
- return {
15486
- success: false,
15487
- error: `Model "${input.id}" already exists under provider "${input.providerId}"`
15703
+ return this.withLock(() => {
15704
+ const models = this.getAll();
15705
+ if (models.find((m) => m.id === input.id && m.providerId === input.providerId)) {
15706
+ return {
15707
+ success: false,
15708
+ error: `Model "${input.id}" already exists under provider "${input.providerId}"`
15709
+ };
15710
+ }
15711
+ const newModel = {
15712
+ id: input.id,
15713
+ providerId: input.providerId,
15714
+ name: input.name,
15715
+ description: input.description,
15716
+ contextWindow: input.contextWindow,
15717
+ capabilities: input.capabilities ?? ["chat"],
15718
+ recommendedFor: input.recommendedFor ?? ["chat"],
15719
+ status: input.status ?? "stable",
15720
+ builtin: false,
15721
+ editable: true
15488
15722
  };
15489
- }
15490
- const newModel = {
15491
- id: input.id,
15492
- providerId: input.providerId,
15493
- name: input.name,
15494
- description: input.description,
15495
- contextWindow: input.contextWindow,
15496
- capabilities: input.capabilities ?? ["chat"],
15497
- recommendedFor: input.recommendedFor ?? ["chat"],
15498
- status: input.status ?? "stable",
15499
- builtin: false,
15500
- editable: true
15501
- };
15502
- models.push(newModel);
15503
- this.storage.writeJson("custom-models", models);
15504
- return { success: true, model: newModel };
15723
+ models.push(newModel);
15724
+ this.storage.writeJson("custom-models", models);
15725
+ return { success: true, model: newModel };
15726
+ });
15505
15727
  }
15506
15728
  update(providerId, modelId, updates) {
15507
- const models = this.getAll();
15508
- const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
15509
- if (index === -1) return { success: false, error: "Custom model not found" };
15510
- models[index] = { ...models[index], ...updates };
15511
- this.storage.writeJson("custom-models", models);
15512
- return { success: true };
15729
+ return this.withLock(() => {
15730
+ const models = this.getAll();
15731
+ const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
15732
+ if (index === -1) return { success: false, error: "Custom model not found" };
15733
+ models[index] = { ...models[index], ...updates };
15734
+ this.storage.writeJson("custom-models", models);
15735
+ return { success: true };
15736
+ });
15513
15737
  }
15514
15738
  delete(providerId, modelId) {
15515
- const models = this.getAll();
15516
- const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
15517
- if (index === -1) return { success: false, error: "Custom model not found" };
15518
- models.splice(index, 1);
15519
- this.storage.writeJson("custom-models", models);
15520
- return { success: true };
15739
+ return this.withLock(() => {
15740
+ const models = this.getAll();
15741
+ const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
15742
+ if (index === -1) return { success: false, error: "Custom model not found" };
15743
+ models.splice(index, 1);
15744
+ this.storage.writeJson("custom-models", models);
15745
+ return { success: true };
15746
+ });
15521
15747
  }
15522
15748
  };
15523
15749
 
15750
+ // ../../packages/node-runtime/src/ai/llm-runtime-stores.ts
15751
+ function ensureLegacyCustomModels(configs, customModelStore) {
15752
+ for (const config of configs) {
15753
+ const providerId = config.provider;
15754
+ if (config.model && getBuiltinProviderById(providerId) && !getBuiltinModelById(providerId, config.model)) {
15755
+ customModelStore.add({
15756
+ id: config.model,
15757
+ providerId,
15758
+ name: config.model,
15759
+ capabilities: ["chat"],
15760
+ recommendedFor: ["chat"],
15761
+ status: "stable"
15762
+ });
15763
+ }
15764
+ }
15765
+ }
15766
+ function createLlmRuntimeStores(aiDataDir, options2 = {}) {
15767
+ const storage = createFileConfigStorage(aiDataDir);
15768
+ const customProviderStore = new CustomProviderStore(storage);
15769
+ const customModelStore = new CustomModelStore(storage);
15770
+ const llmConfigStore = createAuthProfileLlmConfigStore(storage, {
15771
+ ...options2.authProfileDeps,
15772
+ t: options2.t,
15773
+ generateId: options2.generateId,
15774
+ onStoreLoaded: (configs) => {
15775
+ ensureLegacyCustomModels(configs, customModelStore);
15776
+ options2.onStoreLoaded?.(configs);
15777
+ }
15778
+ });
15779
+ return {
15780
+ llmConfigStore,
15781
+ customProviderStore,
15782
+ customModelStore
15783
+ };
15784
+ }
15785
+
15524
15786
  // ../../packages/node-runtime/src/ai/llm-builder.ts
15525
15787
  function normalizeAnthropicBaseUrl(url) {
15526
15788
  return url.replace(/\/v1\/?$/, "");
@@ -16133,7 +16395,7 @@ function exportAsJson(db, messages, sessionName, sourceSessionId) {
16133
16395
  ).all();
16134
16396
  const data = {
16135
16397
  chatlab: {
16136
- version: "0.0.2",
16398
+ version: CHATLAB_FORMAT_VERSION,
16137
16399
  exportedAt: Math.floor(Date.now() / 1e3),
16138
16400
  generator: "ChatLab"
16139
16401
  },
@@ -16242,8 +16504,8 @@ function exportWithFormat(params, openDatabase) {
16242
16504
  }
16243
16505
 
16244
16506
  // ../../packages/node-runtime/src/merger/temp-db.ts
16245
- import * as fs29 from "fs";
16246
- import * as path26 from "path";
16507
+ import * as fs28 from "fs";
16508
+ import * as path23 from "path";
16247
16509
  var TEMP_DB_SCHEMA = `
16248
16510
  CREATE TABLE IF NOT EXISTS meta (
16249
16511
  name TEXT NOT NULL,
@@ -16458,7 +16720,7 @@ function exportSessionToJson(db) {
16458
16720
  ).all();
16459
16721
  return {
16460
16722
  chatlab: {
16461
- version: "0.0.1",
16723
+ version: CHATLAB_FORMAT_VERSION,
16462
16724
  exportedAt: Math.floor(Date.now() / 1e3),
16463
16725
  generator: "ChatLab Export",
16464
16726
  description: `Exported from session: ${meta.name}`
@@ -16488,21 +16750,21 @@ function exportSessionToJson(db) {
16488
16750
  }
16489
16751
  function deleteTempDatabase(dbPath) {
16490
16752
  try {
16491
- if (fs29.existsSync(dbPath)) fs29.unlinkSync(dbPath);
16753
+ if (fs28.existsSync(dbPath)) fs28.unlinkSync(dbPath);
16492
16754
  const walPath = dbPath + "-wal";
16493
16755
  const shmPath = dbPath + "-shm";
16494
- if (fs29.existsSync(walPath)) fs29.unlinkSync(walPath);
16495
- if (fs29.existsSync(shmPath)) fs29.unlinkSync(shmPath);
16756
+ if (fs28.existsSync(walPath)) fs28.unlinkSync(walPath);
16757
+ if (fs28.existsSync(shmPath)) fs28.unlinkSync(shmPath);
16496
16758
  } catch {
16497
16759
  }
16498
16760
  }
16499
16761
  function cleanupTempDatabases(tempDir) {
16500
16762
  try {
16501
- if (!fs29.existsSync(tempDir)) return;
16502
- const files = fs29.readdirSync(tempDir);
16763
+ if (!fs28.existsSync(tempDir)) return;
16764
+ const files = fs28.readdirSync(tempDir);
16503
16765
  for (const file of files) {
16504
16766
  if (file.startsWith("merge_") && file.endsWith(".db")) {
16505
- deleteTempDatabase(path26.join(tempDir, file));
16767
+ deleteTempDatabase(path23.join(tempDir, file));
16506
16768
  }
16507
16769
  }
16508
16770
  } catch {
@@ -16887,8 +17149,8 @@ function clearSessionOwner(adapter, sessionId) {
16887
17149
  }
16888
17150
 
16889
17151
  // ../../packages/node-runtime/src/services/contacts/overrides.ts
16890
- import fs30 from "fs";
16891
- import path27 from "path";
17152
+ import fs29 from "fs";
17153
+ import path24 from "path";
16892
17154
  var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
16893
17155
  var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
16894
17156
  var CONTACT_OVERRIDES_VERSION = 1;
@@ -16899,30 +17161,30 @@ function createEmptyContactOverridesFile() {
16899
17161
  };
16900
17162
  }
16901
17163
  function getContactOverridesPath(snapshotDir) {
16902
- return path27.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
17164
+ return path24.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
16903
17165
  }
16904
17166
  function readContactOverrides(snapshotDir) {
16905
17167
  const filePath = getContactOverridesPath(snapshotDir);
16906
- if (!fs30.existsSync(filePath)) return createEmptyContactOverridesFile();
17168
+ if (!fs29.existsSync(filePath)) return createEmptyContactOverridesFile();
16907
17169
  try {
16908
- return normalizeContactOverrides(JSON.parse(fs30.readFileSync(filePath, "utf-8")));
17170
+ return normalizeContactOverrides(JSON.parse(fs29.readFileSync(filePath, "utf-8")));
16909
17171
  } catch (error) {
16910
17172
  appLogger.warn("contacts", "contact overrides file is unreadable", error);
16911
17173
  return createEmptyContactOverridesFile();
16912
17174
  }
16913
17175
  }
16914
17176
  function writeContactOverrides(snapshotDir, overrides) {
16915
- if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
16916
- const tmpPath = path27.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
16917
- fs30.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
16918
- fs30.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
17177
+ if (!fs29.existsSync(snapshotDir)) fs29.mkdirSync(snapshotDir, { recursive: true });
17178
+ const tmpPath = path24.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
17179
+ fs29.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
17180
+ fs29.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
16919
17181
  }
16920
17182
  function normalizeContactOverrides(value) {
16921
- const input = isRecord2(value) ? value : {};
16922
- const manualFriendsInput = isRecord2(input.manualFriends) ? input.manualFriends : {};
17183
+ const input = isRecord3(value) ? value : {};
17184
+ const manualFriendsInput = isRecord3(input.manualFriends) ? input.manualFriends : {};
16923
17185
  const manualFriends = {};
16924
17186
  for (const [key, entry] of Object.entries(manualFriendsInput)) {
16925
- if (!key || !isRecord2(entry)) continue;
17187
+ if (!key || !isRecord3(entry)) continue;
16926
17188
  const createdAt = Number.isFinite(entry.createdAt) ? Number(entry.createdAt) : Date.now();
16927
17189
  const updatedAt = Number.isFinite(entry.updatedAt) ? Number(entry.updatedAt) : createdAt;
16928
17190
  manualFriends[key] = { key, createdAt, updatedAt };
@@ -16932,7 +17194,7 @@ function normalizeContactOverrides(value) {
16932
17194
  manualFriends
16933
17195
  };
16934
17196
  }
16935
- function isRecord2(value) {
17197
+ function isRecord3(value) {
16936
17198
  return typeof value === "object" && value !== null && !Array.isArray(value);
16937
17199
  }
16938
17200
 
@@ -16950,23 +17212,23 @@ function buildContactsSignature(adapter, timeRangePreset) {
16950
17212
  }
16951
17213
 
16952
17214
  // ../../packages/node-runtime/src/services/contacts/snapshot.ts
16953
- import fs31 from "fs";
16954
- import path28 from "path";
17215
+ import fs30 from "fs";
17216
+ import path25 from "path";
16955
17217
  var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
16956
17218
  function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
16957
17219
  const preset = normalizeContactsTimeRangePreset(timeRangePreset);
16958
- return path28.join(snapshotDir, `contacts-snapshot-${preset}.json`);
17220
+ return path25.join(snapshotDir, `contacts-snapshot-${preset}.json`);
16959
17221
  }
16960
17222
  function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16961
17223
  const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
16962
- if (!fs31.existsSync(snapshotPath)) return null;
17224
+ if (!fs30.existsSync(snapshotPath)) return null;
16963
17225
  try {
16964
- return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
17226
+ return JSON.parse(fs30.readFileSync(snapshotPath, "utf-8"));
16965
17227
  } catch (error) {
16966
17228
  const ts = options2.now?.() ?? Date.now();
16967
- const backupPath = path28.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
17229
+ const backupPath = path25.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
16968
17230
  try {
16969
- fs31.renameSync(snapshotPath, backupPath);
17231
+ fs30.renameSync(snapshotPath, backupPath);
16970
17232
  } catch (renameError) {
16971
17233
  appLogger.warn("contacts", "failed to backup corrupt contacts snapshot", renameError);
16972
17234
  }
@@ -16975,17 +17237,17 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16975
17237
  }
16976
17238
  }
16977
17239
  function writeContactsSnapshot(snapshotDir, snapshot) {
16978
- if (!fs31.existsSync(snapshotDir)) fs31.mkdirSync(snapshotDir, { recursive: true });
16979
- const tmpPath = path28.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
16980
- fs31.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
16981
- fs31.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
17240
+ if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
17241
+ const tmpPath = path25.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17242
+ fs30.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
17243
+ fs30.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
16982
17244
  }
16983
17245
  function cleanupContactsSnapshotTempFiles(snapshotDir) {
16984
- if (!fs31.existsSync(snapshotDir)) return;
16985
- for (const name of fs31.readdirSync(snapshotDir)) {
17246
+ if (!fs30.existsSync(snapshotDir)) return;
17247
+ for (const name of fs30.readdirSync(snapshotDir)) {
16986
17248
  if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
16987
17249
  try {
16988
- fs31.rmSync(path28.join(snapshotDir, name), { force: true });
17250
+ fs30.rmSync(path25.join(snapshotDir, name), { force: true });
16989
17251
  } catch (error) {
16990
17252
  appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
16991
17253
  }
@@ -17483,23 +17745,23 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
17483
17745
  }
17484
17746
 
17485
17747
  // ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
17486
- import fs32 from "fs";
17487
- import path29 from "path";
17748
+ import fs31 from "fs";
17749
+ import path26 from "path";
17488
17750
  var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
17489
17751
  function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
17490
17752
  const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
17491
- return path29.join(snapshotDir, `graph-snapshot-${preset}.json`);
17753
+ return path26.join(snapshotDir, `graph-snapshot-${preset}.json`);
17492
17754
  }
17493
17755
  function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
17494
17756
  const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
17495
- if (!fs32.existsSync(snapshotPath)) return null;
17757
+ if (!fs31.existsSync(snapshotPath)) return null;
17496
17758
  try {
17497
- return JSON.parse(fs32.readFileSync(snapshotPath, "utf-8"));
17759
+ return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
17498
17760
  } catch (error) {
17499
17761
  const ts = options2.now?.() ?? Date.now();
17500
- const backupPath = path29.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17762
+ const backupPath = path26.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17501
17763
  try {
17502
- fs32.renameSync(snapshotPath, backupPath);
17764
+ fs31.renameSync(snapshotPath, backupPath);
17503
17765
  } catch (renameError) {
17504
17766
  appLogger.warn("people-relationships", "failed to backup corrupt people relationships snapshot", renameError);
17505
17767
  }
@@ -17508,17 +17770,17 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
17508
17770
  }
17509
17771
  }
17510
17772
  function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
17511
- if (!fs32.existsSync(snapshotDir)) fs32.mkdirSync(snapshotDir, { recursive: true });
17512
- const tmpPath = path29.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17513
- fs32.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
17514
- fs32.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
17773
+ if (!fs31.existsSync(snapshotDir)) fs31.mkdirSync(snapshotDir, { recursive: true });
17774
+ const tmpPath = path26.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17775
+ fs31.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
17776
+ fs31.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
17515
17777
  }
17516
17778
  function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
17517
- if (!fs32.existsSync(snapshotDir)) return;
17518
- for (const name of fs32.readdirSync(snapshotDir)) {
17779
+ if (!fs31.existsSync(snapshotDir)) return;
17780
+ for (const name of fs31.readdirSync(snapshotDir)) {
17519
17781
  if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
17520
17782
  try {
17521
- fs32.rmSync(path29.join(snapshotDir, name), { force: true });
17783
+ fs31.rmSync(path26.join(snapshotDir, name), { force: true });
17522
17784
  } catch (error) {
17523
17785
  appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
17524
17786
  }
@@ -17654,8 +17916,10 @@ var DefaultPeopleRelationshipsService = class {
17654
17916
  const snapshot = this.getSnapshotForResponse(timeRangePreset);
17655
17917
  const status = this.getCacheStatus(signature, timeRangePreset);
17656
17918
  const includeSnapshot = shouldIncludeSnapshot(status, options2.acceptStale);
17657
- const graph = includeSnapshot && snapshot ? buildPeopleRelationshipsNeighborhoodGraph(snapshot, key) : emptyGraph();
17658
- const contact = includeSnapshot ? snapshot?.nodes.find((node) => node.key === key) ?? null : null;
17919
+ const graphScope = normalizePeopleRelationshipsGraphScope(options2.graphScope);
17920
+ const scopedSnapshot = snapshot ? buildNeighborhoodSnapshotForScope(snapshot, graphScope) : null;
17921
+ const graph = includeSnapshot && scopedSnapshot ? buildPeopleRelationshipsNeighborhoodGraph(scopedSnapshot, key) : emptyGraph();
17922
+ const contact = includeSnapshot ? scopedSnapshot?.nodes.find((node) => node.key === key) ?? null : null;
17659
17923
  return {
17660
17924
  contact,
17661
17925
  graph,
@@ -17787,7 +18051,7 @@ var DefaultPeopleRelationshipsService = class {
17787
18051
  const graph = includeSnapshot && snapshot ? buildGraphForScope(snapshot, graphScope) : emptyGraph();
17788
18052
  return {
17789
18053
  graph,
17790
- searchResults: includeSnapshot && snapshot ? buildSearchResults(snapshot, options2.query, graph) : [],
18054
+ searchResults: includeSnapshot && snapshot ? buildSearchResults(snapshot, options2.query, graph, graphScope) : [],
17791
18055
  diagnostics: includeSnapshot ? sanitizePeopleRelationshipsDiagnostics(snapshot?.diagnostics ?? createEmptyDiagnostics()) : createEmptyDiagnostics(),
17792
18056
  algorithmVersion: includeSnapshot ? snapshot?.algorithmVersion ?? PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION : PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
17793
18057
  timeRange: snapshot?.timeRange ?? resolvePeopleRelationshipsTimeRange(timeRangePreset, null),
@@ -17845,6 +18109,16 @@ function buildFriendsRelationshipsGraph(snapshot) {
17845
18109
  communities: filterCommunitiesForNodes(snapshot.communities, nodes)
17846
18110
  };
17847
18111
  }
18112
+ function buildNeighborhoodSnapshotForScope(snapshot, scope) {
18113
+ if (scope !== "friends") return snapshot;
18114
+ const graph = buildFriendsRelationshipsGraph(snapshot);
18115
+ return {
18116
+ ...snapshot,
18117
+ nodes: graph.nodes,
18118
+ edges: graph.edges,
18119
+ communities: graph.communities
18120
+ };
18121
+ }
17848
18122
  function buildCloseRelationshipsGraph(snapshot) {
17849
18123
  const selectedKeys = /* @__PURE__ */ new Set();
17850
18124
  for (const node of snapshot.nodes) {
@@ -17885,11 +18159,12 @@ function filterCommunitiesForNodes(communities, nodes) {
17885
18159
  size: communitySizes.get(community.id) ?? community.size
17886
18160
  }));
17887
18161
  }
17888
- function buildSearchResults(snapshot, queryInput, graph) {
18162
+ function buildSearchResults(snapshot, queryInput, graph, graphScope) {
17889
18163
  const query = queryInput?.trim().toLowerCase() ?? "";
17890
18164
  if (!query) return [];
17891
18165
  const visibleKeys = new Set(graph.nodes.map((node) => node.key));
17892
- return snapshot.nodes.filter((node) => node.searchText.includes(query)).sort(compareNodes).slice(0, snapshot.limits.searchResultLimit).map((node) => ({
18166
+ const searchableNodes = graphScope === "friends" ? graph.nodes : snapshot.nodes;
18167
+ return searchableNodes.filter((node) => node.searchText.includes(query)).sort(compareNodes).slice(0, snapshot.limits.searchResultLimit).map((node) => ({
17893
18168
  key: node.key,
17894
18169
  kind: node.kind,
17895
18170
  displayName: node.displayName,
@@ -18040,7 +18315,7 @@ __export(relationships_exports, {
18040
18315
  });
18041
18316
 
18042
18317
  // ../../packages/node-runtime/src/services/global-insight/service.ts
18043
- import fs33 from "fs";
18318
+ import fs32 from "fs";
18044
18319
 
18045
18320
  // ../../packages/node-runtime/src/services/global-insight/signature.ts
18046
18321
  function buildAnnualSummarySignature(adapter, range) {
@@ -18273,6 +18548,7 @@ var DefaultGlobalInsightService = class {
18273
18548
  latestDataYear: includeSnapshot ? snapshot?.latestDataYear ?? null : null,
18274
18549
  metrics: includeSnapshot ? snapshot?.metrics ?? null : null,
18275
18550
  monthlyActivity: includeSnapshot ? snapshot?.monthlyActivity ?? [] : [],
18551
+ monthlyDirectContacts: includeSnapshot ? snapshot?.monthlyDirectContacts ?? [] : [],
18276
18552
  dailyActivity: includeSnapshot ? snapshot?.dailyActivity ?? [] : [],
18277
18553
  messageTypes: includeSnapshot ? snapshot?.messageTypes ?? [] : [],
18278
18554
  textLength: includeSnapshot ? snapshot?.textLength ?? null : null,
@@ -18293,7 +18569,7 @@ var DefaultGlobalInsightService = class {
18293
18569
  }
18294
18570
  getSnapshot(range) {
18295
18571
  const key = toAnnualSummaryRangeKey(range);
18296
- if (this.snapshots.has(key) && !fs33.existsSync(getAnnualSummarySnapshotPath(this.snapshotDir, range))) {
18572
+ if (this.snapshots.has(key) && !fs32.existsSync(getAnnualSummarySnapshotPath(this.snapshotDir, range))) {
18297
18573
  this.snapshots.delete(key);
18298
18574
  }
18299
18575
  if (!this.snapshots.has(key)) {
@@ -18341,8 +18617,8 @@ __export(global_insight_exports, {
18341
18617
  });
18342
18618
 
18343
18619
  // ../../packages/node-runtime/src/semantic-index/config.ts
18344
- import fs34 from "fs";
18345
- import path30 from "path";
18620
+ import fs33 from "fs";
18621
+ import path27 from "path";
18346
18622
  var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
18347
18623
  var SEMANTIC_INDEX_CONFIG_VERSION = 1;
18348
18624
  var SEARCH_MAX_RESULTS_DEFAULT = 10;
@@ -18431,9 +18707,9 @@ var SemanticIndexConfigStore = class {
18431
18707
  return this.set({ ...this.get(), enabled });
18432
18708
  }
18433
18709
  get() {
18434
- if (!fs34.existsSync(this.filePath)) return defaultSemanticIndexConfig();
18710
+ if (!fs33.existsSync(this.filePath)) return defaultSemanticIndexConfig();
18435
18711
  try {
18436
- const raw = JSON.parse(fs34.readFileSync(this.filePath, "utf-8"));
18712
+ const raw = JSON.parse(fs33.readFileSync(this.filePath, "utf-8"));
18437
18713
  return normalizeConfig(raw);
18438
18714
  } catch {
18439
18715
  return defaultSemanticIndexConfig();
@@ -18441,9 +18717,9 @@ var SemanticIndexConfigStore = class {
18441
18717
  }
18442
18718
  set(config) {
18443
18719
  const normalized = normalizeConfig(config);
18444
- const dir = path30.dirname(this.filePath);
18445
- if (!fs34.existsSync(dir)) fs34.mkdirSync(dir, { recursive: true });
18446
- fs34.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
18720
+ const dir = path27.dirname(this.filePath);
18721
+ if (!fs33.existsSync(dir)) fs33.mkdirSync(dir, { recursive: true });
18722
+ fs33.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
18447
18723
  return normalized;
18448
18724
  }
18449
18725
  resolveModelId() {
@@ -18452,7 +18728,7 @@ var SemanticIndexConfigStore = class {
18452
18728
  };
18453
18729
 
18454
18730
  // ../../packages/node-runtime/src/semantic-index/service.ts
18455
- import path31 from "path";
18731
+ import path28 from "path";
18456
18732
 
18457
18733
  // ../../packages/node-runtime/src/semantic-index/chunker-config.ts
18458
18734
  import { createHash as createHash2 } from "crypto";
@@ -19983,16 +20259,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
19983
20259
  return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
19984
20260
  }
19985
20261
  function sessionIdFromDbPath(dbPath) {
19986
- return path31.basename(dbPath, ".db");
20262
+ return path28.basename(dbPath, ".db");
19987
20263
  }
19988
20264
  var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
19989
20265
  var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
19990
20266
  function createSemanticIndexService(params) {
19991
20267
  const { pathProvider } = params;
19992
20268
  return new SemanticIndexService({
19993
- vectorDbPath: path31.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
19994
- configPath: path31.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
19995
- modelsCacheDir: path31.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
20269
+ vectorDbPath: path28.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
20270
+ configPath: path28.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
20271
+ modelsCacheDir: path28.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
19996
20272
  modelDownloadProxyUrl: params.modelDownloadProxyUrl,
19997
20273
  sessionAdapter: params.sessionAdapter,
19998
20274
  nativeBinding: params.nativeBinding,
@@ -20542,8 +20818,8 @@ ${text}`;
20542
20818
  };
20543
20819
 
20544
20820
  // ../../packages/node-runtime/src/services/merge-cache.ts
20545
- import * as fs35 from "fs";
20546
- import * as path32 from "path";
20821
+ import * as fs34 from "fs";
20822
+ import * as path29 from "path";
20547
20823
  import * as crypto from "crypto";
20548
20824
  var MergeSessionCache = class {
20549
20825
  cache = /* @__PURE__ */ new Map();
@@ -20552,15 +20828,15 @@ var MergeSessionCache = class {
20552
20828
  constructor(pathProvider, options2) {
20553
20829
  this.tempDir = pathProvider.getTempDir();
20554
20830
  this.nativeBinding = options2?.nativeBinding;
20555
- if (!fs35.existsSync(this.tempDir)) {
20556
- fs35.mkdirSync(this.tempDir, { recursive: true });
20831
+ if (!fs34.existsSync(this.tempDir)) {
20832
+ fs34.mkdirSync(this.tempDir, { recursive: true });
20557
20833
  }
20558
20834
  }
20559
20835
  generateTempDbPath(filename) {
20560
20836
  const ts = Date.now();
20561
20837
  const rand = Math.random().toString(36).slice(2, 8);
20562
20838
  const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
20563
- return path32.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20839
+ return path29.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20564
20840
  }
20565
20841
  /** Store a parsed file's temp DB and return a handle for subsequent requests. */
20566
20842
  store(filename, tempDbPath) {
@@ -20571,7 +20847,7 @@ var MergeSessionCache = class {
20571
20847
  /** Open a TempDbReader for a given handle. Caller must close the reader. */
20572
20848
  openReader(handle) {
20573
20849
  const entry = this.cache.get(handle);
20574
- if (!entry || !fs35.existsSync(entry.tempDbPath)) return null;
20850
+ if (!entry || !fs34.existsSync(entry.tempDbPath)) return null;
20575
20851
  const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
20576
20852
  readonly: true,
20577
20853
  nativeBinding: this.nativeBinding
@@ -20616,26 +20892,26 @@ var MergeSessionCache = class {
20616
20892
  };
20617
20893
 
20618
20894
  // ../../packages/node-runtime/src/services/push-importer.ts
20619
- import * as fs36 from "fs";
20895
+ import * as fs35 from "fs";
20620
20896
  var SYSTEM_SENDER_ID = "SYSTEM";
20621
20897
  var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
20622
- function isRecord3(value) {
20898
+ function isRecord4(value) {
20623
20899
  return typeof value === "object" && value !== null && !Array.isArray(value);
20624
20900
  }
20625
- function validateOptionalString(value, field) {
20901
+ function validateOptionalString2(value, field) {
20626
20902
  return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
20627
20903
  }
20628
20904
  function validatePayload(payload, isNew) {
20629
- if (!isRecord3(payload)) return "payload must be an object";
20905
+ if (!isRecord4(payload)) return "payload must be an object";
20630
20906
  const messages = payload.messages;
20631
20907
  if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
20632
20908
  if (payload.chatlab !== void 0) {
20633
- if (!isRecord3(payload.chatlab)) return "chatlab must be an object";
20909
+ if (!isRecord4(payload.chatlab)) return "chatlab must be an object";
20634
20910
  if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
20635
20911
  return "chatlab.version must be a string";
20636
20912
  if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
20637
20913
  return "chatlab.exportedAt must be a number";
20638
- const generatorError = validateOptionalString(payload.chatlab.generator, "chatlab.generator");
20914
+ const generatorError = validateOptionalString2(payload.chatlab.generator, "chatlab.generator");
20639
20915
  if (generatorError) return generatorError;
20640
20916
  }
20641
20917
  if (isNew) {
@@ -20643,17 +20919,17 @@ function validatePayload(payload, isNew) {
20643
20919
  if (!payload.meta) return "meta is required for new sessions";
20644
20920
  }
20645
20921
  if (payload.meta !== void 0) {
20646
- if (!isRecord3(payload.meta)) return "meta must be an object";
20922
+ if (!isRecord4(payload.meta)) return "meta must be an object";
20647
20923
  const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
20648
20924
  for (const field of metaFields) {
20649
- const error = validateOptionalString(payload.meta[field], `meta.${field}`);
20925
+ const error = validateOptionalString2(payload.meta[field], `meta.${field}`);
20650
20926
  if (error) return error;
20651
20927
  }
20652
20928
  if (isNew && !payload.meta.name) return "meta.name is required";
20653
20929
  if (isNew && !payload.meta.platform) return "meta.platform is required";
20654
20930
  if (isNew && !payload.meta.type) return "meta.type is required";
20655
20931
  }
20656
- if (payload.options !== void 0 && !isRecord3(payload.options)) return "options must be an object";
20932
+ if (payload.options !== void 0 && !isRecord4(payload.options)) return "options must be an object";
20657
20933
  const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
20658
20934
  if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
20659
20935
  return `options.metaUpdateMode must be 'patch' or 'none'`;
@@ -20665,18 +20941,18 @@ function validatePayload(payload, isNew) {
20665
20941
  if (payload.members) {
20666
20942
  for (let i = 0; i < payload.members.length; i++) {
20667
20943
  const member = payload.members[i];
20668
- if (!isRecord3(member)) return `members[${i}] must be an object`;
20944
+ if (!isRecord4(member)) return `members[${i}] must be an object`;
20669
20945
  if (typeof member.platformId !== "string" || member.platformId.length === 0)
20670
20946
  return `members[${i}].platformId must be a string`;
20671
20947
  for (const field of ["accountName", "groupNickname", "avatar"]) {
20672
- const error = validateOptionalString(member[field], `members[${i}].${field}`);
20948
+ const error = validateOptionalString2(member[field], `members[${i}].${field}`);
20673
20949
  if (error) return error;
20674
20950
  }
20675
20951
  if (member.roles !== void 0) {
20676
20952
  if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
20677
20953
  for (let j = 0; j < member.roles.length; j++) {
20678
20954
  const role = member.roles[j];
20679
- if (!isRecord3(role) || typeof role.id !== "string" || role.id.length === 0)
20955
+ if (!isRecord4(role) || typeof role.id !== "string" || role.id.length === 0)
20680
20956
  return `members[${i}].roles[${j}].id must be a string`;
20681
20957
  }
20682
20958
  }
@@ -20684,13 +20960,13 @@ function validatePayload(payload, isNew) {
20684
20960
  }
20685
20961
  for (let i = 0; i < messages.length; i++) {
20686
20962
  const msg = messages[i];
20687
- if (!isRecord3(msg)) return `messages[${i}] must be an object`;
20963
+ if (!isRecord4(msg)) return `messages[${i}] must be an object`;
20688
20964
  if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
20689
20965
  if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
20690
20966
  return `messages[${i}].timestamp must be a positive number`;
20691
20967
  if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
20692
20968
  for (const field of ["accountName", "groupNickname"]) {
20693
- const error = validateOptionalString(msg[field], `messages[${i}].${field}`);
20969
+ const error = validateOptionalString2(msg[field], `messages[${i}].${field}`);
20694
20970
  if (error) return error;
20695
20971
  }
20696
20972
  if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
@@ -20983,7 +21259,7 @@ async function executePushImportUnlocked(deps, sessionId, payload) {
20983
21259
  return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
20984
21260
  }
20985
21261
  const dbPath = deps.getDbPath(sessionId);
20986
- const isNew = !fs36.existsSync(dbPath);
21262
+ const isNew = !fs35.existsSync(dbPath);
20987
21263
  const validationError = validatePayload(payload, isNew);
20988
21264
  if (validationError) {
20989
21265
  return { ok: false, reason: "invalid_payload", message: validationError };
@@ -21089,7 +21365,7 @@ export {
21089
21365
  getChatLabTempScopeDir,
21090
21366
  createChatLabTempDir,
21091
21367
  removeChatLabTempDir,
21092
- writeParseResultToDb,
21368
+ validateChatLabFile,
21093
21369
  detectFormat,
21094
21370
  detectAllFormats,
21095
21371
  getSupportedFormats,
@@ -21217,6 +21493,7 @@ export {
21217
21493
  createAuthProfileLlmConfigStore,
21218
21494
  CustomProviderStore,
21219
21495
  CustomModelStore,
21496
+ createLlmRuntimeStores,
21220
21497
  normalizeAnthropicBaseUrl,
21221
21498
  normalizeOpenAICompatibleBaseUrl,
21222
21499
  buildPiModel,
@@ -21309,4 +21586,4 @@ export {
21309
21586
  hashImportBody,
21310
21587
  ImportIdempotencyCache
21311
21588
  };
21312
- //# sourceMappingURL=chunk-LNU4RW5G.mjs.map
21589
+ //# sourceMappingURL=chunk-AQPDZAWB.mjs.map