chatlab-cli 0.32.0 → 0.33.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 (153) hide show
  1. package/README.md +2 -1
  2. package/dist/{chat-command-3KUWKS53.mjs → chat-command-IHRNO7J4.mjs} +8 -8
  3. package/dist/{chunk-3J7WTUEE.mjs → chunk-4V6UP7EW.mjs} +2 -2
  4. package/dist/{chunk-YVNZ73O5.mjs → chunk-C3B7RXES.mjs} +3919 -3773
  5. package/dist/chunk-C3B7RXES.mjs.map +1 -0
  6. package/dist/{chunk-27DWKA3L.mjs → chunk-HGV4MUIW.mjs} +4 -4
  7. package/dist/{chunk-LNU4RW5G.mjs → chunk-KWJ5FZ74.mjs} +1182 -1042
  8. package/dist/chunk-KWJ5FZ74.mjs.map +1 -0
  9. package/dist/{chunk-DV4U34MR.mjs → chunk-NHTOSDYI.mjs} +2 -2
  10. package/dist/{chunk-CANPJOIE.mjs → chunk-NSWFHMBZ.mjs} +112 -16
  11. package/dist/chunk-NSWFHMBZ.mjs.map +1 -0
  12. package/dist/{chunk-UVCH2ICD.mjs → chunk-RHA534GU.mjs} +3 -3
  13. package/dist/{chunk-23AIEWCD.mjs → chunk-S7YPBKQ3.mjs} +2 -2
  14. package/dist/{chunk-BLOBEOCG.mjs → chunk-U4F6BNGZ.mjs} +2 -2
  15. package/dist/{chunk-3IGBPOJM.mjs → chunk-ZERQIKI4.mjs} +5 -4
  16. package/dist/chunk-ZERQIKI4.mjs.map +1 -0
  17. package/dist/{chunk-PZM3FHVZ.mjs → chunk-ZTMZ7S7T.mjs} +16 -16
  18. package/dist/chunk-ZTMZ7S7T.mjs.map +1 -0
  19. package/dist/cli.mjs +8 -8
  20. package/dist/contacts-worker.mjs +2 -2
  21. package/dist/global-insight-worker.mjs +1 -1
  22. package/dist/{http-YR26GF7X.mjs → http-Y2WC2UK4.mjs} +11 -11
  23. package/dist/{import-Q3DUACU4.mjs → import-4HF2UNQI.mjs} +7 -7
  24. package/dist/index.mjs +11 -11
  25. package/dist/{mcp-HTVZSGTI.mjs → mcp-DJPDRFTW.mjs} +8 -8
  26. package/dist/people-relationships-worker.mjs +2 -2
  27. package/dist/semantic-index-worker.mjs +1 -1
  28. package/dist/{src-HJL7YZZJ.mjs → src-R7MZLKXQ.mjs} +9 -9
  29. package/dist/{stream-import-MXETSEPW.mjs → stream-import-DX57ANUL.mjs} +7 -7
  30. package/dist/{worker-runtime-DZQDL2E2.mjs → worker-runtime-QMN7OKOZ.mjs} +5 -5
  31. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-hwRVh048.js +110 -0
  32. package/{dist-web/assets/BufferResource-BXfwfGam.js → dist-cli-web/assets/BufferResource-DwfMBlAa.js} +1 -1
  33. package/dist-cli-web/assets/CanvasRenderer-q312LRsp.js +1 -0
  34. package/dist-cli-web/assets/DataDirCleanupNotice--ee1dY0d.js +1 -0
  35. package/dist-cli-web/assets/EChartWordcloud-BaMgUl0Y.js +1 -0
  36. package/{dist-web/assets/Filter-Irxx0f6j.js → dist-cli-web/assets/Filter-BOb5cLBx.js} +1 -1
  37. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-DK2z0Jrh.js +1 -0
  38. package/dist-cli-web/assets/MessageExportModal-CFbovWh1.js +1 -0
  39. package/dist-cli-web/assets/PageHeader-B3-W9mvh.js +1 -0
  40. package/{dist-web/assets/RelationshipGalaxyCanvas-ByDU6ZS_.js → dist-cli-web/assets/RelationshipGalaxyCanvas-GEpsKHxV.js} +4 -4
  41. package/{dist-web/assets/RelationshipGalaxyThreeCanvas-DgAlyKjc.js → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-DHOU66Gj.js} +2 -2
  42. package/{dist-web/assets/RenderTargetSystem-Cx5U5HAM.js → dist-cli-web/assets/RenderTargetSystem-PEnnW2yb.js} +1 -1
  43. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-sNw3hP4c.js +1 -0
  44. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-BtUvMehe.js +1 -0
  45. package/{dist-web/assets/WebGLRenderer-B3Fnp1WX.js → dist-cli-web/assets/WebGLRenderer-BjP_1do7.js} +2 -2
  46. package/{dist-web/assets/WebGPURenderer-DZ0X9N86.js → dist-cli-web/assets/WebGPURenderer-ldVv92u7.js} +2 -2
  47. package/dist-cli-web/assets/annual-summary-time-range-BHClzNvd.js +1 -0
  48. package/dist-cli-web/assets/base-DBJ95yXf.js +1 -0
  49. package/{dist-web/assets/browserAll-BqC9ELNO.js → dist-cli-web/assets/browserAll-CPoMOLKh.js} +1 -1
  50. package/dist-cli-web/assets/cli-web-B5VYO-Dh.js +1 -0
  51. package/{dist-web/assets/dateFormat-CX9atL2g.js → dist-cli-web/assets/dateFormat-Cf2xuaE-.js} +1 -1
  52. package/dist-cli-web/assets/fetch-80O3EH4a.js +1 -0
  53. package/dist-cli-web/assets/fetch-B1km2u3N.js +1 -0
  54. package/dist-cli-web/assets/fetch-BFDKzf1P.js +1 -0
  55. package/dist-cli-web/assets/fetch-BuWxo6oK.js +1 -0
  56. package/{dist-web/assets/fetch-C3KuMfIU.js → dist-cli-web/assets/fetch-C0HHT1Yn.js} +3 -3
  57. package/dist-cli-web/assets/fetch-CXIdj2-_.js +1 -0
  58. package/{dist-web/assets/fetch-CUT9JdXT.js → dist-cli-web/assets/fetch-CtyVktRW.js} +3 -3
  59. package/{dist-web/assets/fetch-th_egarZ.js → dist-cli-web/assets/fetch-DGi_9my2.js} +2 -2
  60. package/dist-cli-web/assets/fetch-Dr41uuXL.js +1 -0
  61. package/dist-cli-web/assets/fetch-J67jyI2k.js +1 -0
  62. package/dist-cli-web/assets/index-5UdyJtVU.js +1 -0
  63. package/dist-cli-web/assets/index-7jm3YI9u.js +53 -0
  64. package/dist-cli-web/assets/index-B7R74Yp7.js +1 -0
  65. package/dist-cli-web/assets/index-BGiTw98l.js +1 -0
  66. package/dist-cli-web/assets/index-Ba21yU86.js +1 -0
  67. package/{dist-web/assets/index-Bm_U0hSr.js → dist-cli-web/assets/index-Bf3gFaid.js} +4 -4
  68. package/dist-cli-web/assets/index-ByQSR8Bj.js +2 -0
  69. package/dist-cli-web/assets/index-CPZYydbc.js +2 -0
  70. package/dist-cli-web/assets/index-D8Q3U-6e.js +1 -0
  71. package/dist-cli-web/assets/index-DGtDxRCd.js +173 -0
  72. package/{dist-web/assets/index-DBb03wVF.css → dist-cli-web/assets/index-DTxnpQiv.css} +1 -1
  73. package/dist-cli-web/assets/index-DmrrdYXH.css +1 -0
  74. package/dist-cli-web/assets/index-DtlTo3nt.js +1 -0
  75. package/dist-cli-web/assets/index-DzQT-qXF.js +1 -0
  76. package/dist-cli-web/assets/index-gpTDI3sq.js +64 -0
  77. package/{dist-web/assets/index-D8Uq3_Dc.css → dist-cli-web/assets/index-lwhCMqhd.css} +1 -1
  78. package/dist-cli-web/assets/index-qsbWn8VP.css +1 -0
  79. package/dist-cli-web/assets/people-page-header-BvQy1GQj.js +1 -0
  80. package/{dist-web/assets/webworkerAll-a_fveCLR.js → dist-cli-web/assets/webworkerAll-DgKQKbNn.js} +1 -1
  81. package/{dist-web → dist-cli-web}/index.html +2 -4
  82. package/package.json +4 -4
  83. package/dist/chunk-3IGBPOJM.mjs.map +0 -1
  84. package/dist/chunk-CANPJOIE.mjs.map +0 -1
  85. package/dist/chunk-LNU4RW5G.mjs.map +0 -1
  86. package/dist/chunk-PZM3FHVZ.mjs.map +0 -1
  87. package/dist/chunk-YVNZ73O5.mjs.map +0 -1
  88. package/dist-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-DfQa9E2W.js +0 -110
  89. package/dist-web/assets/CanvasRenderer-Bvpsbxds.js +0 -1
  90. package/dist-web/assets/DataDirCleanupNotice-C0sCmNL2.js +0 -1
  91. package/dist-web/assets/EChartWordcloud-C5hLT1vS.js +0 -1
  92. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-8s6XRB3S.js +0 -1
  93. package/dist-web/assets/MessageExportModal-tTI358HU.js +0 -1
  94. package/dist-web/assets/PageHeader-D5QQI9io.js +0 -1
  95. package/dist-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-CpNjFhpm.js +0 -1
  96. package/dist-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-DtCKfbaz.js +0 -1
  97. package/dist-web/assets/annual-summary-time-range-CvG74N2l.js +0 -1
  98. package/dist-web/assets/base-DdQRTliS.js +0 -1
  99. package/dist-web/assets/electron-ChQkD5ht.js +0 -1
  100. package/dist-web/assets/electron-DGxVMsvC.js +0 -1
  101. package/dist-web/assets/electron-hcWiB7Gg.js +0 -1
  102. package/dist-web/assets/fetch-3Ifr5YJN.js +0 -1
  103. package/dist-web/assets/fetch-74o_kRZf.js +0 -1
  104. package/dist-web/assets/fetch-BKFznq4l.js +0 -1
  105. package/dist-web/assets/fetch-BPj6IlT-.js +0 -1
  106. package/dist-web/assets/fetch-CoYaXr5K.js +0 -1
  107. package/dist-web/assets/fetch-Dcg6Q6Ei.js +0 -1
  108. package/dist-web/assets/fetch-DxWfYzkB.js +0 -1
  109. package/dist-web/assets/index-B3riRaJA.js +0 -1
  110. package/dist-web/assets/index-BKFGuwJu.css +0 -1
  111. package/dist-web/assets/index-BRDdozcm.js +0 -173
  112. package/dist-web/assets/index-Bg-OiHMa.js +0 -1
  113. package/dist-web/assets/index-BhewGKey.js +0 -2
  114. package/dist-web/assets/index-BpeJ40uK.js +0 -1
  115. package/dist-web/assets/index-C6bwt1H4.js +0 -1
  116. package/dist-web/assets/index-CEJqRkis.js +0 -2
  117. package/dist-web/assets/index-CKLCaqdT.js +0 -1
  118. package/dist-web/assets/index-Ds5PiVyB.js +0 -64
  119. package/dist-web/assets/index-GXMxhYc9.js +0 -1
  120. package/dist-web/assets/index-JyB6Cr8r.js +0 -25
  121. package/dist-web/assets/index-UJl03IYE.css +0 -1
  122. package/dist-web/assets/index-tx-Oal36.js +0 -1
  123. package/dist-web/assets/people-page-header-DGkK0AMO.js +0 -1
  124. package/dist-web/assets/types-CZnACqaB.js +0 -1
  125. package/dist-web/assets/vendor-nuxt-ui-DMdbdSFy.js +0 -15
  126. package/dist-web/assets/vendor-reka-ui-8_x7iKfi.js +0 -15
  127. package/dist-web/assets/web-aZcioccr.js +0 -1
  128. /package/dist/{chat-command-3KUWKS53.mjs.map → chat-command-IHRNO7J4.mjs.map} +0 -0
  129. /package/dist/{chunk-3J7WTUEE.mjs.map → chunk-4V6UP7EW.mjs.map} +0 -0
  130. /package/dist/{chunk-27DWKA3L.mjs.map → chunk-HGV4MUIW.mjs.map} +0 -0
  131. /package/dist/{chunk-DV4U34MR.mjs.map → chunk-NHTOSDYI.mjs.map} +0 -0
  132. /package/dist/{chunk-UVCH2ICD.mjs.map → chunk-RHA534GU.mjs.map} +0 -0
  133. /package/dist/{chunk-23AIEWCD.mjs.map → chunk-S7YPBKQ3.mjs.map} +0 -0
  134. /package/dist/{chunk-BLOBEOCG.mjs.map → chunk-U4F6BNGZ.mjs.map} +0 -0
  135. /package/dist/{http-YR26GF7X.mjs.map → http-Y2WC2UK4.mjs.map} +0 -0
  136. /package/dist/{import-Q3DUACU4.mjs.map → import-4HF2UNQI.mjs.map} +0 -0
  137. /package/dist/{mcp-HTVZSGTI.mjs.map → mcp-DJPDRFTW.mjs.map} +0 -0
  138. /package/dist/{src-HJL7YZZJ.mjs.map → src-R7MZLKXQ.mjs.map} +0 -0
  139. /package/dist/{stream-import-MXETSEPW.mjs.map → stream-import-DX57ANUL.mjs.map} +0 -0
  140. /package/dist/{worker-runtime-DZQDL2E2.mjs.map → worker-runtime-QMN7OKOZ.mjs.map} +0 -0
  141. /package/{dist-web → dist-cli-web}/assets/ActionToolsPanel-Dp-ojXjO.css +0 -0
  142. /package/{dist-web → dist-cli-web}/assets/PageHeader-XIXMTomn.css +0 -0
  143. /package/{dist-web → dist-cli-web}/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css +0 -0
  144. /package/{dist-web → dist-cli-web}/assets/index-BfO_LpDv.css +0 -0
  145. /package/{dist-web → dist-cli-web}/assets/index-ByUaiVQK.css +0 -0
  146. /package/{dist-web → dist-cli-web}/assets/index-ZsZwPLWy.css +0 -0
  147. /package/{dist-web → dist-cli-web}/assets/logo-DXIuG_WE.svg +0 -0
  148. /package/{dist-web → dist-cli-web}/assets/relationship-galaxy-viewport-Cwfa51ER.js +0 -0
  149. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-B-i_56Av.js +0 -0
  150. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-wordcloud-Ca8n6qS6.js +0 -0
  151. /package/{dist-web → dist-cli-web}/assets/vendor-snapdom--nZvo8Sg.js +0 -0
  152. /package/{dist-web → dist-cli-web}/assets/vendor-zrender-5DcYjSZk.js +0 -0
  153. /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-4V6UP7EW.mjs";
13
13
  import {
14
14
  PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
15
15
  buildPeopleRelationshipsNeighborhoodGraph,
@@ -18,7 +18,7 @@ import {
18
18
  computePeopleRelationshipsSnapshot,
19
19
  normalizePeopleRelationshipsTimeRangePreset,
20
20
  resolvePeopleRelationshipsTimeRange
21
- } from "./chunk-23AIEWCD.mjs";
21
+ } from "./chunk-S7YPBKQ3.mjs";
22
22
  import {
23
23
  ANNUAL_SUMMARY_ALGORITHM_VERSION
24
24
  } from "./chunk-YAQLYL52.mjs";
@@ -27,6 +27,8 @@ import {
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,
@@ -76,6 +79,7 @@ import {
76
79
  isChatSessionDb,
77
80
  isNameMatchPlatform,
78
81
  isReasoningModel,
82
+ isSupportedChatLabFormatVersion,
79
83
  matchOwnerProfile,
80
84
  mergeConfirmedNames,
81
85
  mergeMembers,
@@ -97,8 +101,9 @@ import {
97
101
  updateMemberAliases,
98
102
  updateSessionOwnerId,
99
103
  writeAnnualSummarySnapshot,
100
- writeAuthProfile
101
- } from "./chunk-YVNZ73O5.mjs";
104
+ writeAuthProfile,
105
+ writeParseResultToDb
106
+ } from "./chunk-C3B7RXES.mjs";
102
107
  import {
103
108
  __commonJS,
104
109
  __export,
@@ -714,8 +719,8 @@ var require_FilterBase = __commonJS({
714
719
  var FilterBase = class _FilterBase extends Transform {
715
720
  static stringFilter(string, separator) {
716
721
  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;
722
+ const path30 = stack.join(separator);
723
+ 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
724
  };
720
725
  }
721
726
  static regExpFilter(regExp, separator) {
@@ -1057,9 +1062,9 @@ var require_stream_chain = __commonJS({
1057
1062
  var require_withParser = __commonJS({
1058
1063
  "../../node_modules/stream-json/utils/withParser.js"(exports, module) {
1059
1064
  "use strict";
1060
- var { chain: chain10 } = require_stream_chain();
1065
+ var { chain: chain9 } = require_stream_chain();
1061
1066
  var Parser = require_Parser();
1062
- var withParser = (fn, options2) => chain10([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1067
+ var withParser = (fn, options2) => chain9([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1063
1068
  module.exports = withParser;
1064
1069
  }
1065
1070
  });
@@ -1137,7 +1142,7 @@ var require_Assembler = __commonJS({
1137
1142
  this.current = new Ctr();
1138
1143
  this.key = null;
1139
1144
  };
1140
- var Assembler = class _Assembler extends EventEmitter {
1145
+ var Assembler2 = class _Assembler extends EventEmitter {
1141
1146
  static connectTo(stream, options2) {
1142
1147
  return new _Assembler(options2).connectTo(stream);
1143
1148
  }
@@ -1169,12 +1174,12 @@ var require_Assembler = __commonJS({
1169
1174
  return (this.stack.length >> 1) + (this.done ? 0 : 1);
1170
1175
  }
1171
1176
  get path() {
1172
- const path33 = [];
1177
+ const path30 = [];
1173
1178
  for (let i = 0; i < this.stack.length; i += 2) {
1174
1179
  const key = this.stack[i + 1];
1175
- path33.push(key === null ? this.stack[i].length : key);
1180
+ path30.push(key === null ? this.stack[i].length : key);
1176
1181
  }
1177
- return path33;
1182
+ return path30;
1178
1183
  }
1179
1184
  dropToLevel(level) {
1180
1185
  if (level < this.depth) {
@@ -1256,11 +1261,11 @@ var require_Assembler = __commonJS({
1256
1261
  }
1257
1262
  }
1258
1263
  };
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;
1264
+ Assembler2.prototype.stringValue = Assembler2.prototype._saveValue;
1265
+ Assembler2.prototype.startObject = startObject(Object);
1266
+ Assembler2.prototype.startArray = startObject(Array);
1267
+ Assembler2.prototype.endArray = Assembler2.prototype.endObject;
1268
+ module.exports = Assembler2;
1264
1269
  }
1265
1270
  });
1266
1271
 
@@ -1269,7 +1274,7 @@ var require_StreamBase = __commonJS({
1269
1274
  "../../node_modules/stream-json/streamers/StreamBase.js"(exports, module) {
1270
1275
  "use strict";
1271
1276
  var { Transform } = __require("stream");
1272
- var Assembler = require_Assembler();
1277
+ var Assembler2 = require_Assembler();
1273
1278
  var Counter = class {
1274
1279
  constructor(initialDepth) {
1275
1280
  this.depth = initialDepth;
@@ -1298,7 +1303,7 @@ var require_StreamBase = __commonJS({
1298
1303
  this._filter = this._transform;
1299
1304
  }
1300
1305
  this._transform = this._wait || this._filter;
1301
- this._assembler = new Assembler(options2);
1306
+ this._assembler = new Assembler2(options2);
1302
1307
  }
1303
1308
  _transform(chunk, encoding, callback) {
1304
1309
  if (this._assembler[chunk.name]) {
@@ -1912,9 +1917,9 @@ var require_yauzl = __commonJS({
1912
1917
  exports.Entry = Entry;
1913
1918
  exports.LocalFileHeader = LocalFileHeader;
1914
1919
  exports.RandomAccessReader = RandomAccessReader;
1915
- function openPromise2(path33, options2) {
1920
+ function openPromise2(path30, options2) {
1916
1921
  return new Promise((resolve4, reject) => {
1917
- open(path33, { ...options2, lazyEntries: true }, function(err, zipfile) {
1922
+ open(path30, { ...options2, lazyEntries: true }, function(err, zipfile) {
1918
1923
  if (err) return reject(err);
1919
1924
  resolve4(zipfile);
1920
1925
  });
@@ -1944,7 +1949,7 @@ var require_yauzl = __commonJS({
1944
1949
  });
1945
1950
  });
1946
1951
  }
1947
- function open(path33, options2, callback) {
1952
+ function open(path30, options2, callback) {
1948
1953
  if (typeof options2 === "function") {
1949
1954
  callback = options2;
1950
1955
  options2 = null;
@@ -1956,7 +1961,7 @@ var require_yauzl = __commonJS({
1956
1961
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
1957
1962
  if (options2.strictFileNames == null) options2.strictFileNames = false;
1958
1963
  if (callback == null) callback = defaultCallback;
1959
- fs37.open(path33, "r", function(err, fd) {
1964
+ fs37.open(path30, "r", function(err, fd) {
1960
1965
  if (err) return callback(err);
1961
1966
  fromFd(fd, options2, function(err2, zipfile) {
1962
1967
  if (err2) fs37.close(fd, defaultCallback);
@@ -2810,122 +2815,9 @@ function removeChatLabTempDir(dirPath, scope, options2 = {}) {
2810
2815
  fs.rmSync(resolvedDir, { recursive: true, force: true });
2811
2816
  }
2812
2817
 
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
2818
  // ../../packages/parser/src/index.ts
2927
2819
  import * as fs18 from "fs";
2928
- import * as path14 from "path";
2820
+ import * as path11 from "path";
2929
2821
 
2930
2822
  // ../../packages/parser/src/sniffer.ts
2931
2823
  import * as fs2 from "fs";
@@ -3315,8 +3207,8 @@ var { pick } = import_Pick.default;
3315
3207
  var { streamValues } = import_StreamValues.default;
3316
3208
  var { chain } = import_stream_chain.default;
3317
3209
  function extractNameFromFilePath(filePath) {
3318
- const basename13 = path3.basename(filePath);
3319
- const name = basename13.replace(/\.json$/i, "");
3210
+ const basename10 = path3.basename(filePath);
3211
+ const name = basename10.replace(/\.json$/i, "");
3320
3212
  return name || "\u672A\u77E5\u7FA4\u804A";
3321
3213
  }
3322
3214
  var feature = {
@@ -3486,8 +3378,8 @@ import * as fs5 from "fs";
3486
3378
  import * as readline from "readline";
3487
3379
  import * as path4 from "path";
3488
3380
  function extractNameFromFilePath2(filePath) {
3489
- const basename13 = path4.basename(filePath);
3490
- const name = basename13.replace(/\.jsonl$/i, "");
3381
+ const basename10 = path4.basename(filePath);
3382
+ const name = basename10.replace(/\.jsonl$/i, "");
3491
3383
  return name || "\u672A\u77E5\u7FA4\u804A";
3492
3384
  }
3493
3385
  function parseLine(line) {
@@ -3509,8 +3401,10 @@ var feature2 = {
3509
3401
  // 低优先级,让其他格式先匹配
3510
3402
  extensions: [".jsonl"],
3511
3403
  signatures: {
3512
- // 第一行必须是 header 类型,包含 chatlab 信息
3513
- head: [/^\s*\{.*"_type"\s*:\s*"header".*"chatlab"/]
3404
+ // 跳过格式允许的空行和注释后,分别匹配首条数据中的 header 类型和 chatlab 对象。
3405
+ head: [
3406
+ /^\uFEFF?(?:[ \t]*(?:#[^\r\n]*)?\r?\n)*[ \t]*\{(?=[^\r\n]*"_type"\s*:\s*"header")(?=[^\r\n]*"chatlab"\s*:)[^\r\n]*\}/
3407
+ ]
3514
3408
  }
3515
3409
  };
3516
3410
  async function* parseChatLabJsonl(options2) {
@@ -3561,7 +3455,9 @@ async function* parseChatLabJsonl(options2) {
3561
3455
  platformId: parsed.platformId,
3562
3456
  accountName: parsed.accountName,
3563
3457
  groupNickname: parsed.groupNickname,
3564
- avatar: parsed.avatar
3458
+ aliases: parsed.aliases,
3459
+ avatar: parsed.avatar,
3460
+ roles: parsed.roles
3565
3461
  };
3566
3462
  members.push(member);
3567
3463
  memberMap.set(parsed.platformId, member);
@@ -3960,8 +3856,8 @@ function extractJsonObject2(content, key) {
3960
3856
  return null;
3961
3857
  }
3962
3858
  function extractNameFromFilePath3(filePath) {
3963
- const basename13 = path6.basename(filePath);
3964
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3859
+ const basename10 = path6.basename(filePath);
3860
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3965
3861
  }
3966
3862
  function isPlaceholderSenderId(id) {
3967
3863
  return id === "0" || id === "\u672A\u77E5";
@@ -4561,8 +4457,8 @@ var { pick: pick4 } = import_Pick4.default;
4561
4457
  var { streamValues: streamValues4 } = import_StreamValues4.default;
4562
4458
  var { chain: chain4 } = import_stream_chain4.default;
4563
4459
  function extractNameFromFilePath4(filePath) {
4564
- const basename13 = path8.basename(filePath);
4565
- const name = basename13.replace(/\.json$/i, "");
4460
+ const basename10 = path8.basename(filePath);
4461
+ const name = basename10.replace(/\.json$/i, "");
4566
4462
  return name || "\u672A\u77E5\u804A\u5929";
4567
4463
  }
4568
4464
  var feature5 = {
@@ -5473,13 +5369,16 @@ var module_8 = {
5473
5369
  var telegram_native_default = module_8;
5474
5370
 
5475
5371
  // ../../packages/parser/src/formats/telegram-native-single.ts
5476
- var import_stream_chain7 = __toESM(require_stream_chain(), 1);
5372
+ var import_Assembler = __toESM(require_Assembler(), 1);
5477
5373
  var import_stream_json7 = __toESM(require_stream_json(), 1);
5478
- var import_StreamValues7 = __toESM(require_StreamValues(), 1);
5479
5374
  import * as fs12 from "fs";
5480
- var { chain: chain7 } = import_stream_chain7.default;
5375
+
5376
+ // ../../packages/parser/src/browser/telegram.ts
5377
+ 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)"/;
5378
+ var TELEGRAM_SINGLE_HEAD_SIGNATURES = [/^\s*\{\s*(?:\r?\n\s*)?"name"\s*:/];
5379
+
5380
+ // ../../packages/parser/src/formats/telegram-native-single.ts
5481
5381
  var { parser: parser8 } = import_stream_json7.default;
5482
- var { streamValues: streamValues7 } = import_StreamValues7.default;
5483
5382
  var feature9 = {
5484
5383
  id: "telegram-native-single",
5485
5384
  name: "Telegram \u5355\u804A\u5929\u5BFC\u51FA (JSON)",
@@ -5487,98 +5386,97 @@ var feature9 = {
5487
5386
  priority: 23,
5488
5387
  extensions: [".json"],
5489
5388
  signatures: {
5490
- // Telegram 单聊天导出特征:文件以 "name" 作为第一个 JSON 键
5491
- // 这与全量导出(以 "about" 开头)有效区分,避免嵌套字段误匹配
5492
- head: [/^\s*\{\s*\r?\n\s*"name"\s*:/],
5389
+ head: TELEGRAM_SINGLE_HEAD_SIGNATURES,
5493
5390
  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
- }
5391
+ fieldPatterns: { telegramChatType: TELEGRAM_SINGLE_CHAT_TYPE_PATTERN }
5498
5392
  }
5499
5393
  };
5500
5394
  async function* parseTelegramSingle(options2) {
5501
5395
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5502
5396
  const totalBytes = getFileSize(filePath);
5503
- let messagesProcessed = 0;
5504
5397
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5505
5398
  yield { type: "progress", data: initialProgress };
5506
5399
  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 };
5400
+ onLog?.("info", `Starting Telegram single-chat JSON parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5401
+ let messagesProcessed = 0;
5537
5402
  const memberMap = /* @__PURE__ */ new Map();
5538
5403
  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
- });
5404
+ try {
5405
+ const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5406
+ const tokenStream = readStream.pipe(parser8());
5407
+ let depth = 0;
5408
+ let rootKey = null;
5409
+ let chatName = null;
5410
+ let telegramType = null;
5411
+ let chatId = null;
5412
+ let messagesArrayDepth = null;
5413
+ let messageAssembler = null;
5414
+ let meta = null;
5415
+ for await (const rawToken of tokenStream) {
5416
+ const token = rawToken;
5417
+ if (messageAssembler) {
5418
+ messageAssembler.consume(token);
5419
+ if (messageAssembler.done) {
5420
+ const message = messageAssembler.current;
5421
+ messageAssembler = null;
5422
+ const parsedMessage = parseTelegramMessage(message, memberMap);
5423
+ if (parsedMessage) {
5424
+ messageBatch.push(parsedMessage);
5425
+ messagesProcessed++;
5426
+ }
5427
+ if (messageBatch.length >= batchSize) {
5428
+ yield { type: "messages", data: messageBatch.splice(0) };
5429
+ const progress = createProgress(
5430
+ "parsing",
5431
+ readStream.bytesRead,
5432
+ totalBytes,
5433
+ messagesProcessed,
5434
+ `Processed ${messagesProcessed} messages`
5435
+ );
5436
+ yield { type: "progress", data: progress };
5437
+ onProgress?.(progress);
5438
+ }
5439
+ }
5440
+ } else if (token.name === "keyValue" && depth === 1) {
5441
+ rootKey = token.value ?? null;
5442
+ } else if (token.name === "stringValue" && depth === 1) {
5443
+ if (rootKey === "name") chatName = token.value ?? "";
5444
+ if (rootKey === "type") telegramType = token.value ?? "";
5445
+ } else if (token.name === "numberValue" && depth === 1 && rootKey === "id") {
5446
+ const parsedId = Number(token.value);
5447
+ if (Number.isFinite(parsedId)) chatId = parsedId;
5448
+ }
5449
+ if (token.name === "startArray" && depth === 1 && rootKey === "messages") {
5450
+ if (chatName === null || telegramType === null || chatId === null) {
5451
+ throw new Error("Invalid Telegram single-chat JSON export");
5452
+ }
5453
+ const chatType = mapChatType(telegramType);
5454
+ meta = {
5455
+ name: chatName || `Telegram Chat ${chatId}`,
5456
+ platform: KNOWN_PLATFORMS.TELEGRAM,
5457
+ type: chatType,
5458
+ groupId: chatType === "group" /* GROUP */ ? String(chatId) : void 0
5459
+ };
5460
+ messagesArrayDepth = depth + 1;
5461
+ yield { type: "meta", data: meta };
5462
+ } else if (token.name === "startObject" && messagesArrayDepth !== null && depth === messagesArrayDepth) {
5463
+ messageAssembler = new import_Assembler.default();
5464
+ messageAssembler.consume(token);
5465
+ }
5466
+ if (token.name === "startObject" || token.name === "startArray") {
5467
+ depth++;
5468
+ } else if (token.name === "endObject" || token.name === "endArray") {
5469
+ depth--;
5470
+ }
5555
5471
  }
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);
5472
+ if (!meta || messagesArrayDepth === null || messageAssembler) {
5473
+ throw new Error("Invalid Telegram single-chat JSON export");
5581
5474
  }
5475
+ } catch (error) {
5476
+ const parseError = error instanceof Error ? error : new Error(String(error));
5477
+ onLog?.("error", "Telegram single-chat JSON parsing failed");
5478
+ yield { type: "error", data: parseError };
5479
+ return;
5582
5480
  }
5583
5481
  yield { type: "members", data: Array.from(memberMap.values()) };
5584
5482
  if (messageBatch.length > 0) {
@@ -5587,77 +5485,63 @@ async function* parseTelegramSingle(options2) {
5587
5485
  const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5588
5486
  yield { type: "progress", data: doneProgress };
5589
5487
  onProgress?.(doneProgress);
5590
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5488
+ onLog?.(
5489
+ "info",
5490
+ `Telegram single-chat JSON parsing completed: ${messagesProcessed} messages, ${memberMap.size} members`
5491
+ );
5591
5492
  yield {
5592
5493
  type: "done",
5593
5494
  data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5594
5495
  };
5595
5496
  }
5596
- var parser_8 = {
5597
- feature: feature9,
5598
- parse: parseTelegramSingle
5599
- };
5600
- var module_9 = {
5601
- feature: feature9,
5602
- parser: parser_8
5603
- };
5497
+ function parseTelegramMessage(message, memberMap) {
5498
+ const isService = message.type === "service";
5499
+ const senderPlatformId = extractPlatformId(isService ? message.actor_id : message.from_id);
5500
+ const senderName = isService ? message.actor || "System" : message.from || senderPlatformId;
5501
+ if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
5502
+ memberMap.set(senderPlatformId, {
5503
+ platformId: senderPlatformId,
5504
+ accountName: senderName
5505
+ });
5506
+ }
5507
+ const timestamp = Number.parseInt(message.date_unixtime, 10);
5508
+ if (Number.isNaN(timestamp)) return null;
5509
+ return {
5510
+ platformMessageId: String(message.id),
5511
+ senderPlatformId,
5512
+ senderAccountName: senderName,
5513
+ timestamp,
5514
+ type: detectMessageType(message),
5515
+ content: buildContent(message),
5516
+ replyToMessageId: message.reply_to_message_id ? String(message.reply_to_message_id) : void 0
5517
+ };
5518
+ }
5519
+ var parser_8 = { feature: feature9, parse: parseTelegramSingle };
5520
+ var module_9 = { feature: feature9, parser: parser_8 };
5604
5521
  var telegram_native_single_default = module_9;
5605
5522
 
5606
5523
  // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5607
5524
  import * as fs13 from "fs";
5608
- import * as path9 from "path";
5609
5525
  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;
5526
+
5527
+ // ../../packages/parser/src/browser/whatsapp.ts
5528
+ var WHATSAPP_HEAD_SIGNATURES = [
5529
+ /消息和通话已进行端到端加密/,
5530
+ /訊息與通話已受端對端加密保護/,
5531
+ /Messages and calls are end-to-end encrypted/i,
5532
+ /你发送给自己的消息已进行端到端加密/,
5533
+ /\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,
5534
+ /\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
5535
+ ];
5536
+ var WHATSAPP_FILENAME_SIGNATURES = [
5537
+ /^与.+的\s*WhatsApp\s*聊天\.txt$/i,
5538
+ /^與.+的\s*WhatsApp\s*對話\.txt$/i,
5539
+ /WhatsApp/i
5540
+ ];
5541
+ 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
5542
  var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
5658
5543
  var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
5659
5544
  var SYSTEM_MESSAGE_PATTERNS = [
5660
- // 简体中文系统消息
5661
5545
  /消息和通话已进行端到端加密/,
5662
5546
  /创建了此群组/,
5663
5547
  /加入群组/,
@@ -5669,7 +5553,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5669
5553
  /这条消息已删除/,
5670
5554
  /限时消息功能/,
5671
5555
  /正在等待此消息/,
5672
- // 繁体中文系统消息
5673
5556
  /訊息與通話已受端對端加密保護/,
5674
5557
  /建立了此群組/,
5675
5558
  /加入了群組/,
@@ -5679,7 +5562,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5679
5562
  /已變更本群組/,
5680
5563
  /此訊息已刪除/,
5681
5564
  /限時訊息/,
5682
- // 英文系统消息
5683
5565
  /Messages and calls are end-to-end encrypted/i,
5684
5566
  /created this group/i,
5685
5567
  /joined the group/i,
@@ -5690,21 +5572,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5690
5572
  /This message was deleted/i,
5691
5573
  /disappearing messages/i
5692
5574
  ];
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
5575
  var AMPM_MARKERS = [
5709
5576
  [/\bPM\b/i, true],
5710
5577
  [/\bP\.M\.(?!\w)/i, true],
@@ -5717,191 +5584,226 @@ var AMPM_MARKERS = [
5717
5584
  [/午前/, false],
5718
5585
  [/오전/, false]
5719
5586
  ];
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;
5587
+ var WhatsAppTextAccumulator = class {
5588
+ constructor(fileName) {
5589
+ this.fileName = fileName;
5590
+ this.fileChatName = extractNameFromFileName(fileName);
5591
+ }
5592
+ fileName;
5593
+ fileChatName;
5594
+ memberMap = /* @__PURE__ */ new Map();
5595
+ messages = [];
5596
+ currentMessage = null;
5597
+ skippedLines = 0;
5598
+ get messageCount() {
5599
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5600
+ }
5601
+ pushLine(line) {
5602
+ const cleanedLine = cleanLine(line);
5603
+ const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
5604
+ if (v1Match) {
5605
+ this.saveCurrentMessage();
5606
+ this.currentMessage = createPendingMessage(parseFlexibleTimestamp(v1Match[1]) ?? 0, v1Match[2]);
5607
+ return;
5728
5608
  }
5609
+ const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
5610
+ if (v2Match) {
5611
+ const timestamp = parseFlexibleTimestamp(v2Match[1]);
5612
+ if (timestamp !== null) {
5613
+ this.saveCurrentMessage();
5614
+ this.currentMessage = createPendingMessage(timestamp, v2Match[2]);
5615
+ return;
5616
+ }
5617
+ }
5618
+ if (this.currentMessage && cleanedLine) {
5619
+ this.currentMessage.contentLines.push(cleanedLine);
5620
+ } else if (cleanedLine) {
5621
+ this.skippedLines += 1;
5622
+ }
5623
+ }
5624
+ finish() {
5625
+ this.saveCurrentMessage();
5626
+ const chatType = this.memberMap.size > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
5627
+ const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(this.fileName, this.fileChatName) ? inferPrivateChatName(this.memberMap, this.fileChatName) : this.fileChatName;
5628
+ return {
5629
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.WHATSAPP, type: chatType },
5630
+ members: Array.from(this.memberMap.values(), (member) => ({
5631
+ platformId: member.platformId,
5632
+ accountName: member.nickname
5633
+ })),
5634
+ messages: this.messages,
5635
+ skippedLines: this.skippedLines
5636
+ };
5637
+ }
5638
+ saveCurrentMessage() {
5639
+ if (!this.currentMessage) return;
5640
+ const content = this.currentMessage.contentLines.join("\n").trim();
5641
+ const senderPlatformId = this.currentMessage.sender ?? "system";
5642
+ const senderName = this.currentMessage.sender ?? "\u7CFB\u7EDF\u6D88\u606F";
5643
+ this.messages.push({
5644
+ senderPlatformId,
5645
+ senderAccountName: senderName,
5646
+ timestamp: this.currentMessage.timestamp,
5647
+ type: detectMessageType2(content),
5648
+ content: content || null
5649
+ });
5650
+ if (this.currentMessage.sender) {
5651
+ this.memberMap.set(senderPlatformId, { platformId: senderPlatformId, nickname: senderName });
5652
+ }
5653
+ this.currentMessage = null;
5654
+ }
5655
+ };
5656
+ function createPendingMessage(timestamp, restContent) {
5657
+ const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5658
+ if (senderMatch && !isSystemMessage(restContent)) {
5659
+ return { timestamp, sender: senderMatch[1].trim(), contentLines: [senderMatch[2]] };
5729
5660
  }
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})?)$/);
5661
+ return { timestamp, sender: null, contentLines: [restContent] };
5662
+ }
5663
+ function extractNameFromFileName(fileName) {
5664
+ const basename10 = getBasename(fileName);
5665
+ const simplifiedChinese = basename10.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5666
+ if (simplifiedChinese) return simplifiedChinese[1].trim();
5667
+ const traditionalChinese = basename10.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
5668
+ if (traditionalChinese) return traditionalChinese[1].trim();
5669
+ return basename10.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
5670
+ }
5671
+ function shouldInferPrivateChatName(fileName, fallbackName) {
5672
+ const basename10 = getBasename(fileName);
5673
+ return /^_chat\.txt$/i.test(basename10) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
5674
+ }
5675
+ function inferPrivateChatName(memberMap, fallbackName) {
5676
+ return Array.from(memberMap.values()).find((member) => member.platformId !== "system")?.nickname ?? fallbackName;
5677
+ }
5678
+ function getBasename(fileName) {
5679
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5680
+ }
5681
+ function cleanLine(line) {
5682
+ return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
5683
+ }
5684
+ function isSystemMessage(content) {
5685
+ return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
5686
+ }
5687
+ function detectMessageType2(content) {
5688
+ const trimmed = content.trim();
5689
+ 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") {
5690
+ return 1 /* IMAGE */;
5691
+ }
5692
+ if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A")) {
5693
+ return 4 /* FILE */;
5694
+ }
5695
+ if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
5696
+ 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")) {
5697
+ return 81 /* RECALL */;
5698
+ }
5699
+ if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
5700
+ return 0 /* TEXT */;
5701
+ }
5702
+ function parseFlexibleTimestamp(raw) {
5703
+ let value = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
5704
+ let isPm = null;
5705
+ for (const [pattern, pm] of AMPM_MARKERS) {
5706
+ if (!pattern.test(value)) continue;
5707
+ isPm = pm;
5708
+ value = value.replace(pattern, "").trim();
5709
+ break;
5710
+ }
5711
+ value = value.replace(/,/g, " ").replace(/\s+/g, " ").trim();
5712
+ const match = value.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
5732
5713
  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;
5714
+ const dateParts = match[1].split("/").map((part) => Number.parseInt(part, 10));
5715
+ const timeParts = match[2].split(":").map((part) => Number.parseInt(part, 10));
5716
+ let year;
5717
+ let month;
5718
+ let day;
5737
5719
  if (dateParts[0] > 31) {
5738
- year = dateParts[0];
5739
- month = dateParts[1];
5740
- day = dateParts[2];
5720
+ ;
5721
+ [year, month, day] = dateParts;
5741
5722
  } else if (dateParts[2] > 31) {
5742
- day = dateParts[0];
5743
- month = dateParts[1];
5744
- year = dateParts[2];
5723
+ ;
5724
+ [day, month, year] = dateParts;
5745
5725
  } else {
5746
- month = dateParts[0];
5747
- day = dateParts[1];
5726
+ ;
5727
+ [month, day] = dateParts;
5748
5728
  year = 2e3 + dateParts[2];
5749
5729
  }
5750
5730
  let hour = timeParts[0];
5751
5731
  const minute = timeParts[1];
5752
5732
  const second = timeParts[2] ?? 0;
5753
- if (isPM === true && hour !== 12) hour += 12;
5754
- if (isPM === false && hour === 12) hour = 0;
5733
+ if (isPm === true && hour !== 12) hour += 12;
5734
+ if (isPm === false && hour === 12) hour = 0;
5755
5735
  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;
5736
+ const timestamp = Math.floor(date.getTime() / 1e3);
5737
+ return Number.isNaN(timestamp) ? null : timestamp;
5761
5738
  }
5739
+
5740
+ // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5741
+ var feature10 = {
5742
+ id: "whatsapp-native-txt",
5743
+ name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
5744
+ platform: KNOWN_PLATFORMS.WHATSAPP,
5745
+ priority: 25,
5746
+ extensions: [".txt"],
5747
+ signatures: {
5748
+ head: WHATSAPP_HEAD_SIGNATURES,
5749
+ filename: WHATSAPP_FILENAME_SIGNATURES
5750
+ }
5751
+ };
5762
5752
  async function* parseWhatsApp(options2) {
5763
5753
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5764
5754
  const totalBytes = getFileSize(filePath);
5755
+ const accumulator = new WhatsAppTextAccumulator(filePath);
5765
5756
  let bytesRead = 0;
5766
- let messagesProcessed = 0;
5767
- let skippedLines = 0;
5757
+ let lastReportedMessages = 0;
5768
5758
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5769
5759
  yield { type: "progress", data: initialProgress };
5770
5760
  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
- };
5761
+ onLog?.("info", `Starting WhatsApp TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5798
5762
  const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
5799
- const rl = readline3.createInterface({
5800
- input: fileStream,
5801
- crlfDelay: Infinity
5802
- });
5763
+ const lines = readline3.createInterface({ input: fileStream, crlfDelay: Infinity });
5803
5764
  fileStream.on("data", (chunk) => {
5804
5765
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
5805
5766
  });
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++;
5767
+ for await (const line of lines) {
5768
+ accumulator.pushLine(line);
5769
+ if (accumulator.messageCount >= lastReportedMessages + 500) {
5770
+ lastReportedMessages = accumulator.messageCount;
5771
+ onProgress?.(
5772
+ createProgress(
5773
+ "parsing",
5774
+ bytesRead,
5775
+ totalBytes,
5776
+ accumulator.messageCount,
5777
+ `Processed ${accumulator.messageCount} messages`
5778
+ )
5779
+ );
5865
5780
  }
5866
5781
  }
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 };
5782
+ const result = accumulator.finish();
5783
+ yield { type: "meta", data: result.meta };
5784
+ yield { type: "members", data: result.members };
5785
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5786
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5886
5787
  }
5887
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5788
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
5888
5789
  yield { type: "progress", data: doneProgress };
5889
5790
  onProgress?.(doneProgress);
5890
5791
  const typeCounts = /* @__PURE__ */ new Map();
5891
- for (const msg of messages) {
5892
- typeCounts.set(msg.type, (typeCounts.get(msg.type) || 0) + 1);
5792
+ for (const message of result.messages) {
5793
+ typeCounts.set(message.type, (typeCounts.get(message.type) ?? 0) + 1);
5893
5794
  }
5894
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5895
5795
  onLog?.(
5896
5796
  "info",
5897
- `\u6D88\u606F\u7C7B\u578B\u7EDF\u8BA1: ${Array.from(typeCounts.entries()).map(([type, count]) => `${type}=${count}`).join(", ")}`
5797
+ `WhatsApp TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members, type ${result.meta.type}`
5898
5798
  );
5899
- if (skippedLines > 0) {
5900
- onLog?.("info", `\u8DF3\u8FC7 ${skippedLines} \u884C\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9`);
5901
- }
5799
+ onLog?.(
5800
+ "info",
5801
+ `Message types: ${Array.from(typeCounts.entries(), ([type, count]) => `${type}=${count}`).join(", ")}`
5802
+ );
5803
+ if (result.skippedLines > 0) onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines`);
5902
5804
  yield {
5903
5805
  type: "done",
5904
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5806
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
5905
5807
  };
5906
5808
  }
5907
5809
  var parser_9 = {
@@ -5911,32 +5813,110 @@ var parser_9 = {
5911
5813
  var module_10 = {
5912
5814
  feature: feature10,
5913
5815
  parser: parser_9
5914
- // TXT 格式不需要预处理器
5915
5816
  };
5916
5817
  var whatsapp_native_txt_default = module_10;
5917
5818
 
5918
5819
  // ../../packages/parser/src/formats/qq-native-txt.ts
5919
5820
  import * as fs14 from "fs";
5920
- import * as path10 from "path";
5921
5821
  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
- };
5822
+
5823
+ // ../../packages/parser/src/browser/qq.ts
5824
+ var QQ_HEAD_SIGNATURES = [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/];
5938
5825
  var MESSAGE_HEADER_REGEX = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.+?)(?:\(([^)]+)\)|<([^>]+)>)?$/;
5939
5826
  var GROUP_NAME_REGEX = /^消息对象:(.+)$/;
5827
+ var QqTextAccumulator = class {
5828
+ fallbackName;
5829
+ memberMap = /* @__PURE__ */ new Map();
5830
+ lastValidNickname = /* @__PURE__ */ new Map();
5831
+ messages = [];
5832
+ groupName = "\u672A\u77E5\u7FA4\u804A";
5833
+ currentMessage = null;
5834
+ skippedLines = 0;
5835
+ constructor(fileName) {
5836
+ this.fallbackName = extractNameFromFileName2(fileName);
5837
+ }
5838
+ get messageCount() {
5839
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5840
+ }
5841
+ pushLine(line) {
5842
+ const groupMatch = line.match(GROUP_NAME_REGEX);
5843
+ if (groupMatch) {
5844
+ this.groupName = groupMatch[1].trim();
5845
+ return;
5846
+ }
5847
+ const headerMatch = line.match(MESSAGE_HEADER_REGEX);
5848
+ if (headerMatch) {
5849
+ this.saveCurrentMessage();
5850
+ const platformId = headerMatch[3] || headerMatch[4] || cleanNickname(headerMatch[2].trim());
5851
+ let nickname = cleanNickname(headerMatch[2].trim());
5852
+ if (nickname === platformId && headerMatch[3]) {
5853
+ nickname = this.lastValidNickname.get(platformId) ?? nickname;
5854
+ } else if (headerMatch[3] || headerMatch[4]) {
5855
+ this.lastValidNickname.set(platformId, nickname);
5856
+ }
5857
+ this.currentMessage = {
5858
+ timestamp: parseLocalTime(headerMatch[1]),
5859
+ platformId,
5860
+ nickname,
5861
+ contentLines: []
5862
+ };
5863
+ return;
5864
+ }
5865
+ if (this.currentMessage) {
5866
+ if (line.startsWith("=====") || line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) return;
5867
+ this.currentMessage.contentLines.push(line);
5868
+ return;
5869
+ }
5870
+ const trimmed = line.trim();
5871
+ if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
5872
+ this.skippedLines += 1;
5873
+ }
5874
+ }
5875
+ finish() {
5876
+ this.saveCurrentMessage();
5877
+ return {
5878
+ meta: {
5879
+ name: this.groupName === "\u672A\u77E5\u7FA4\u804A" ? this.fallbackName : this.groupName,
5880
+ platform: KNOWN_PLATFORMS.QQ,
5881
+ type: "group" /* GROUP */
5882
+ },
5883
+ members: Array.from(this.memberMap.values(), (member) => ({
5884
+ platformId: member.platformId,
5885
+ accountName: member.nickname
5886
+ })),
5887
+ messages: this.messages,
5888
+ skippedLines: this.skippedLines
5889
+ };
5890
+ }
5891
+ saveCurrentMessage() {
5892
+ if (!this.currentMessage) return;
5893
+ const content = this.currentMessage.contentLines.join("\n").trim();
5894
+ this.messages.push({
5895
+ senderPlatformId: this.currentMessage.platformId,
5896
+ senderAccountName: this.currentMessage.nickname,
5897
+ timestamp: this.currentMessage.timestamp,
5898
+ type: detectMessageType3(content),
5899
+ content: content || null
5900
+ });
5901
+ this.memberMap.set(this.currentMessage.platformId, {
5902
+ platformId: this.currentMessage.platformId,
5903
+ nickname: this.currentMessage.nickname
5904
+ });
5905
+ this.currentMessage = null;
5906
+ }
5907
+ };
5908
+ function extractNameFromFileName2(fileName) {
5909
+ return getBasename2(fileName).replace(/\.txt$/i, "") || "\u672A\u77E5\u7FA4\u804A";
5910
+ }
5911
+ function getBasename2(fileName) {
5912
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5913
+ }
5914
+ function cleanNickname(nickname) {
5915
+ return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
5916
+ }
5917
+ function parseLocalTime(timeText) {
5918
+ return Math.floor(new Date(timeText.replace(" ", "T")).getTime() / 1e3);
5919
+ }
5940
5920
  function detectMessageType3(content) {
5941
5921
  const trimmed = content.trim();
5942
5922
  if (trimmed === "[\u56FE\u7247]") return 1 /* IMAGE */;
@@ -5949,7 +5929,9 @@ function detectMessageType3(content) {
5949
5929
  if (trimmed === "[\u7EA2\u5305]" || trimmed.includes("\u53D1\u51FA\u4E86\u7EA2\u5305")) return 20 /* RED_PACKET */;
5950
5930
  if (trimmed === "[\u8F6C\u8D26]" || trimmed.includes("\u5411\u4F60\u8F6C\u8D26")) return 21 /* TRANSFER */;
5951
5931
  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 */;
5932
+ if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) {
5933
+ return 23 /* CALL */;
5934
+ }
5953
5935
  if (trimmed === "[\u5206\u4EAB]" || trimmed === "[\u97F3\u4E50]" || trimmed === "[\u5C0F\u7A0B\u5E8F]") return 24 /* SHARE */;
5954
5936
  if (trimmed.startsWith("[\u56DE\u590D]")) return 25 /* REPLY */;
5955
5937
  if (trimmed === "[\u8F6C\u53D1]" || trimmed === "[\u804A\u5929\u8BB0\u5F55]") return 26 /* FORWARD */;
@@ -5960,150 +5942,64 @@ function detectMessageType3(content) {
5960
5942
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) return 99 /* OTHER */;
5961
5943
  return 0 /* TEXT */;
5962
5944
  }
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) {
5945
+
5946
+ // ../../packages/parser/src/formats/qq-native-txt.ts
5947
+ var feature11 = {
5948
+ id: "qq-native-txt",
5949
+ name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
5950
+ platform: KNOWN_PLATFORMS.QQ,
5951
+ priority: 30,
5952
+ extensions: [".txt"],
5953
+ signatures: { head: QQ_HEAD_SIGNATURES }
5954
+ };
5955
+ async function* parseQq(options2) {
5972
5956
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5973
5957
  const totalBytes = getFileSize(filePath);
5974
5958
  let bytesRead = 0;
5975
- let messagesProcessed = 0;
5976
- let skippedLines = 0;
5977
5959
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5978
5960
  yield { type: "progress", data: initialProgress };
5979
5961
  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
- };
5962
+ onLog?.("info", `Starting QQ TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5963
+ const accumulator = new QqTextAccumulator(filePath);
6005
5964
  const fileStream = fs14.createReadStream(filePath, { encoding: "utf-8" });
6006
- const rl = readline4.createInterface({
6007
- input: fileStream,
6008
- crlfDelay: Infinity
6009
- });
5965
+ const lines = readline4.createInterface({ input: fileStream, crlfDelay: Infinity });
6010
5966
  fileStream.on("data", (chunk) => {
6011
5967
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6012
5968
  });
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, "");
5969
+ let lastReportedCount = -1;
5970
+ for await (const line of lines) {
5971
+ accumulator.pushLine(line);
5972
+ const messageCount = accumulator.messageCount;
5973
+ if (messageCount > 0 && messageCount % 1e3 === 0 && messageCount !== lastReportedCount) {
5974
+ lastReportedCount = messageCount;
5975
+ onProgress?.(createProgress("parsing", bytesRead, totalBytes, messageCount, `Processed ${messageCount} messages`));
5976
+ }
5977
+ }
5978
+ const result = accumulator.finish();
5979
+ yield { type: "meta", data: result.meta };
5980
+ yield { type: "members", data: result.members };
5981
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5982
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5983
+ }
5984
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6082
5985
  yield { type: "progress", data: doneProgress };
6083
5986
  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`);
5987
+ onLog?.("info", `QQ TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
5988
+ if (result.skippedLines > 0) {
5989
+ onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines while parsing QQ TXT`);
6087
5990
  }
6088
5991
  yield {
6089
5992
  type: "done",
6090
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5993
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6091
5994
  };
6092
5995
  }
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
- };
5996
+ var parser_10 = { feature: feature11, parse: parseQq };
5997
+ var module_11 = { feature: feature11, parser: parser_10 };
6102
5998
  var qq_native_txt_default = module_11;
6103
5999
 
6104
6000
  // ../../packages/parser/src/formats/instagram-native.ts
6105
6001
  import * as fs15 from "fs";
6106
- import * as path11 from "path";
6002
+ import * as path9 from "path";
6107
6003
  var feature12 = {
6108
6004
  id: "instagram-native",
6109
6005
  name: "Instagram \u5B98\u65B9\u5BFC\u51FA",
@@ -6128,9 +6024,9 @@ function decodeInstagramText(text) {
6128
6024
  return text;
6129
6025
  }
6130
6026
  }
6131
- function extractNameFromFilePath7(filePath) {
6132
- const basename13 = path11.basename(filePath);
6133
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6027
+ function extractNameFromFilePath5(filePath) {
6028
+ const basename10 = path9.basename(filePath);
6029
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6134
6030
  }
6135
6031
  function isSystemMessage2(content) {
6136
6032
  const systemPatterns = [
@@ -6216,7 +6112,7 @@ async function* parseInstagram(options2) {
6216
6112
  ownerId = createMsg ? decodeInstagramText(createMsg.sender_name) : void 0;
6217
6113
  }
6218
6114
  const meta = {
6219
- name: decodeInstagramText(data.title) || extractNameFromFilePath7(filePath),
6115
+ name: decodeInstagramText(data.title) || extractNameFromFilePath5(filePath),
6220
6116
  platform: KNOWN_PLATFORMS.INSTAGRAM,
6221
6117
  type: chatType,
6222
6118
  ownerId
@@ -6289,16 +6185,16 @@ var module_12 = {
6289
6185
  var instagram_native_default = module_12;
6290
6186
 
6291
6187
  // ../../packages/parser/src/formats/google-chat-takeout.ts
6292
- var import_stream_chain8 = __toESM(require_stream_chain(), 1);
6188
+ var import_stream_chain7 = __toESM(require_stream_chain(), 1);
6293
6189
  var import_stream_json8 = __toESM(require_stream_json(), 1);
6294
6190
  var import_Pick7 = __toESM(require_Pick(), 1);
6295
- var import_StreamValues8 = __toESM(require_StreamValues(), 1);
6191
+ var import_StreamValues7 = __toESM(require_StreamValues(), 1);
6296
6192
  import * as fs16 from "fs";
6297
- import * as path12 from "path";
6298
- var { chain: chain8 } = import_stream_chain8.default;
6193
+ import * as path10 from "path";
6194
+ var { chain: chain7 } = import_stream_chain7.default;
6299
6195
  var { parser: parser9 } = import_stream_json8.default;
6300
6196
  var { pick: pick7 } = import_Pick7.default;
6301
- var { streamValues: streamValues8 } = import_StreamValues8.default;
6197
+ var { streamValues: streamValues7 } = import_StreamValues7.default;
6302
6198
  var ENGLISH_MONTHS = /* @__PURE__ */ new Map([
6303
6199
  ["january", 0],
6304
6200
  ["february", 1],
@@ -6380,9 +6276,9 @@ function readJson(filePath) {
6380
6276
  return JSON.parse(fs16.readFileSync(filePath, "utf8"));
6381
6277
  }
6382
6278
  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)) {
6279
+ const resolved = path10.resolve(manifestDir, relativePath);
6280
+ const relative2 = path10.relative(manifestDir, resolved);
6281
+ if (relative2.startsWith("..") || path10.isAbsolute(relative2)) {
6386
6282
  throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`);
6387
6283
  }
6388
6284
  return resolved;
@@ -6395,7 +6291,7 @@ function readManifest2(filePath) {
6395
6291
  if (manifest.chatType !== "private" && manifest.chatType !== "group") {
6396
6292
  throw new Error("Invalid Google Chat chat type");
6397
6293
  }
6398
- const manifestDir = path12.dirname(filePath);
6294
+ const manifestDir = path10.dirname(filePath);
6399
6295
  return {
6400
6296
  manifest,
6401
6297
  userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),
@@ -6479,7 +6375,7 @@ async function* parseGoogleChat(options2) {
6479
6375
  readStream.on("data", (chunk) => {
6480
6376
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6481
6377
  });
6482
- const pipeline2 = chain8([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues8()]);
6378
+ const pipeline2 = chain7([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues7()]);
6483
6379
  const messageBatch = [];
6484
6380
  try {
6485
6381
  for await (const item of pipeline2) {
@@ -6527,418 +6423,270 @@ var google_chat_takeout_default = module_13;
6527
6423
 
6528
6424
  // ../../packages/parser/src/formats/line-native-txt.ts
6529
6425
  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
- }
6426
+
6427
+ // ../../packages/parser/src/browser/line.ts
6428
+ var LINE_HEAD_SIGNATURES = [
6429
+ /^\[LINE\] /m,
6430
+ /^(?:\[LINE\] )?Chat history (?:with|in) /m,
6431
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
6432
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
6433
+ /^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
6434
+ /^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
6435
+ /^\d{4}\/\d{1,2}\/\d{1,2}周/m
6436
+ ];
6437
+ var LINE_FILENAME_SIGNATURES = [/\[LINE\]/i];
6581
6438
  var DATE_PATTERNS = [
6582
- // 2025.12.10 Wednesday
6583
6439
  /^(\d{4})\.(\d{2})\.(\d{2})\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)?/,
6584
- // 2026/1/30周五 or 2026/1/30(金)
6585
6440
  /^(\d{4})\/(\d{1,2})\/(\d{1,2})/,
6586
- // Fri, 1/30/2026
6587
6441
  /^[A-Za-z]+,\s*(\d{1,2})\/(\d{1,2})\/(\d{4})/
6588
- ];
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(.+)$/;
6442
+ ];
6443
+ var TAB_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
6444
+ var SPACE_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
6445
+ var SYSTEM_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
6611
6446
  var SPECIAL_MESSAGE_TYPES = {
6612
- // 图片 / Photo
6613
6447
  "[Photo]": 1 /* IMAGE */,
6614
- // EN
6615
6448
  "[\u7167\u7247]": 1 /* IMAGE */,
6616
- // ZH-CN / ZH-TW
6617
6449
  "[\u5199\u771F]": 1 /* IMAGE */,
6618
- // JA
6619
6450
  Photos: 1 /* IMAGE */,
6620
- // EN (fallback)
6621
- // 语音 / Voice
6622
6451
  "[Voice message]": 2 /* VOICE */,
6623
- // EN
6624
6452
  "[\u8BED\u97F3\u4FE1\u606F]": 2 /* VOICE */,
6625
- // ZH-CN
6626
6453
  "[\u8A9E\u97F3\u8A0A\u606F]": 2 /* VOICE */,
6627
- // ZH-TW
6628
6454
  "[\u30DC\u30A4\u30B9\u30E1\u30C3\u30BB\u30FC\u30B8]": 2 /* VOICE */,
6629
- // JA
6630
6455
  Audio: 2 /* VOICE */,
6631
- // EN (fallback)
6632
- // 视频 / Video
6633
6456
  "[Video]": 3 /* VIDEO */,
6634
- // EN
6635
6457
  "[\u89C6\u9891]": 3 /* VIDEO */,
6636
- // ZH-CN
6637
6458
  "[\u5F71\u7247]": 3 /* VIDEO */,
6638
- // ZH-TW
6639
6459
  "[\u52D5\u753B]": 3 /* VIDEO */,
6640
- // JA
6641
6460
  Videos: 3 /* VIDEO */,
6642
- // EN (fallback)
6643
- // 文件 / File
6644
6461
  "[File]": 4 /* FILE */,
6645
- // EN
6646
6462
  "[\u6587\u4EF6]": 4 /* FILE */,
6647
- // ZH-CN
6648
6463
  "[\u6A94\u6848]": 4 /* FILE */,
6649
- // ZH-TW
6650
6464
  "[\u30D5\u30A1\u30A4\u30EB]": 4 /* FILE */,
6651
- // JA
6652
- // 贴纸 / Sticker
6653
6465
  "[Sticker]": 5 /* EMOJI */,
6654
- // EN
6655
6466
  "[\u8D34\u56FE]": 5 /* EMOJI */,
6656
- // ZH-CN
6657
6467
  "[\u8CBC\u5716]": 5 /* EMOJI */,
6658
- // ZH-TW
6659
6468
  "[\u30B9\u30BF\u30F3\u30D7]": 5 /* EMOJI */,
6660
- // JA
6661
6469
  Stickers: 5 /* EMOJI */,
6662
- // EN (fallback)
6663
- // 位置 / Location
6664
6470
  "[Location]": 8 /* LOCATION */,
6665
- // EN
6666
6471
  "[\u4F4D\u7F6E]": 8 /* LOCATION */,
6667
- // ZH-CN / ZH-TW
6668
6472
  "[\u4F4D\u7F6E\u60C5\u5831]": 8 /* LOCATION */,
6669
- // JA
6670
- // 记事本 / Notes
6671
6473
  "[Notes]": 0 /* TEXT */,
6672
- // EN
6673
6474
  "[\u8BB0\u4E8B\u672C]": 0 /* TEXT */,
6674
- // ZH-CN
6675
6475
  "[\u8A18\u4E8B\u672C]": 0 /* TEXT */,
6676
- // ZH-TW
6677
6476
  "[\u30CE\u30FC\u30C8]": 0 /* TEXT */
6678
- // JA
6679
6477
  };
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");
6478
+ async function parseLineText(content, fileName, options2 = {}) {
6739
6479
  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
6480
  const memberMap = /* @__PURE__ */ new Map();
6745
6481
  const messages = [];
6482
+ const header = lines.length > 0 ? extractNameFromHeader(lines[0].trim()) : null;
6483
+ const chatName = header?.name ?? extractNameFromFileName3(fileName);
6484
+ let currentDate = null;
6746
6485
  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) {
6486
+ let quotedMultiline = false;
6487
+ let lineIndex = header ? 3 : 0;
6488
+ let useTabSeparator = Boolean(header);
6489
+ const yieldEvery = Math.max(1, options2.yieldEvery ?? 1e3);
6490
+ if (!header) {
6761
6491
  for (const line of lines) {
6762
- if (PRIVATE_MSG_PATTERN.test(line)) {
6492
+ if (TAB_MESSAGE_PATTERN.test(line)) {
6763
6493
  useTabSeparator = true;
6764
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230 Tab \u5206\u9694\u683C\u5F0F`);
6765
6494
  break;
6766
6495
  }
6767
- if (GROUP_MSG_PATTERN.test(line)) {
6496
+ if (SPACE_MESSAGE_PATTERN.test(line)) {
6768
6497
  useTabSeparator = false;
6769
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u7A7A\u683C\u5206\u9694\u683C\u5F0F`);
6770
6498
  break;
6771
6499
  }
6772
6500
  }
6773
6501
  }
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);
6801
- }
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);
6826
- } 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
- });
6837
- }
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...`
6856
- );
6857
- onProgress?.(progress);
6858
- }
6502
+ for (; lineIndex < lines.length; lineIndex += 1) {
6503
+ options2.checkCancelled?.();
6504
+ const line = lines[lineIndex];
6505
+ const date = parseDateLine(line);
6506
+ if (date) {
6507
+ currentDate = date;
6859
6508
  } 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];
6509
+ const messagePattern = useTabSeparator ? TAB_MESSAGE_PATTERN : SPACE_MESSAGE_PATTERN;
6510
+ const messageMatch = line.match(messagePattern);
6511
+ if (messageMatch) {
6512
+ const [, timeText, sender, rawContent] = messageMatch;
6513
+ let messageContent = rawContent.trim();
6514
+ quotedMultiline = false;
6515
+ if (messageContent.startsWith('"')) {
6516
+ messageContent = messageContent.slice(1);
6517
+ quotedMultiline = !messageContent.endsWith('"');
6518
+ if (!quotedMultiline) messageContent = messageContent.slice(0, -1);
6881
6519
  }
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);
6520
+ if (!memberMap.has(sender)) {
6521
+ memberMap.set(sender, { platformId: sender, accountName: sender });
6891
6522
  }
6892
6523
  lastMessage = {
6893
- senderPlatformId: "system",
6894
- senderAccountName: "\u7CFB\u7D71",
6895
- timestamp,
6896
- type: 80 /* SYSTEM */,
6897
- content: content2 || null
6524
+ senderPlatformId: sender,
6525
+ senderAccountName: sender,
6526
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6527
+ type: detectMessageType6(messageContent),
6528
+ content: messageContent || null
6898
6529
  };
6899
6530
  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;
6907
- }
6908
- if (lastMessage.content) {
6909
- lastMessage.content += "\n" + appendLine;
6910
- } else {
6911
- lastMessage.content = appendLine;
6531
+ } else {
6532
+ const systemMatch = line.match(SYSTEM_MESSAGE_PATTERN);
6533
+ if (systemMatch) {
6534
+ const [, timeText, rawContent] = systemMatch;
6535
+ const messageContent = rawContent.trim();
6536
+ quotedMultiline = false;
6537
+ lastMessage = {
6538
+ senderPlatformId: "system",
6539
+ senderAccountName: "\u7CFB\u7D71",
6540
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6541
+ type: 80 /* SYSTEM */,
6542
+ content: messageContent || null
6543
+ };
6544
+ messages.push(lastMessage);
6545
+ } else if (line.trim() && lastMessage) {
6546
+ let continuation = line;
6547
+ if (quotedMultiline && continuation.endsWith('"')) {
6548
+ continuation = continuation.slice(0, -1);
6549
+ quotedMultiline = false;
6550
+ }
6551
+ lastMessage.content = lastMessage.content ? `${lastMessage.content}
6552
+ ${continuation}` : continuation;
6912
6553
  }
6913
6554
  }
6914
6555
  }
6556
+ if (lineIndex > 0 && lineIndex % yieldEvery === 0) {
6557
+ options2.onProgress?.({ progress: lineIndex / lines.length, messagesProcessed: messages.length });
6558
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
6559
+ options2.checkCancelled?.();
6560
+ }
6915
6561
  }
6562
+ options2.checkCancelled?.();
6916
6563
  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
6564
+ const chatType = header ? header.isGroup ? "group" /* GROUP */ : "private" /* PRIVATE */ : memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
6565
+ const result = {
6566
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.LINE, type: chatType },
6567
+ members: Array.from(memberMap.values()),
6568
+ messages
6922
6569
  };
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 };
6570
+ options2.onProgress?.({ progress: 1, messagesProcessed: messages.length });
6571
+ return result;
6572
+ }
6573
+ function extractNameFromFileName3(fileName) {
6574
+ const basename10 = getBasename3(fileName).replace(/\.txt$/, "");
6575
+ return basename10.replace(/^\[LINE\]\s*/i, "").trim() || "\u672A\u77E5\u804A\u5929";
6576
+ }
6577
+ function extractNameFromHeader(header) {
6578
+ const englishPrivate = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
6579
+ if (englishPrivate) return { name: englishPrivate[1].trim(), isGroup: false };
6580
+ const englishGroup = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
6581
+ if (englishGroup) return { name: englishGroup[1].trim(), isGroup: true };
6582
+ const japanesePrivate = header.match(/^\[LINE\] (.+)とのトーク履歴/);
6583
+ if (japanesePrivate) return { name: japanesePrivate[1].trim(), isGroup: false };
6584
+ const japaneseGroup = header.match(/^\[LINE\] (.+)のトーク履歴/);
6585
+ if (japaneseGroup) return { name: japaneseGroup[1].trim(), isGroup: true };
6586
+ const simplifiedPrivate = header.match(/^\[LINE\] 与(.+)的聊天记录/);
6587
+ if (simplifiedPrivate) return { name: simplifiedPrivate[1].trim(), isGroup: false };
6588
+ const simplifiedGroup = header.match(/^\[LINE\] (.+)的聊天记录/);
6589
+ if (simplifiedGroup) return { name: simplifiedGroup[1].trim(), isGroup: true };
6590
+ const traditionalPrivate = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
6591
+ if (traditionalPrivate) return { name: traditionalPrivate[1].trim(), isGroup: false };
6592
+ const traditionalGroup = header.match(/^\[LINE\] (.+)的聊天記錄/);
6593
+ return traditionalGroup ? { name: traditionalGroup[1].trim(), isGroup: true } : null;
6594
+ }
6595
+ function getBasename3(fileName) {
6596
+ return fileName.split(/[\\/]/).pop() ?? fileName;
6597
+ }
6598
+ function parseDateLine(line) {
6599
+ for (let index = 0; index < DATE_PATTERNS.length; index += 1) {
6600
+ const match = line.match(DATE_PATTERNS[index]);
6601
+ if (!match) continue;
6602
+ if (index < 2) {
6603
+ return new Date(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10) - 1, Number.parseInt(match[3], 10));
6604
+ }
6605
+ return new Date(Number.parseInt(match[3], 10), Number.parseInt(match[1], 10) - 1, Number.parseInt(match[2], 10));
6929
6606
  }
6930
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
6607
+ return null;
6608
+ }
6609
+ function buildTimestamp(currentDate, timeText, now) {
6610
+ const { hours, minutes } = parseTime(timeText);
6611
+ const value = currentDate ? new Date(currentDate) : now?.() ?? /* @__PURE__ */ new Date();
6612
+ value.setHours(hours, minutes, 0, 0);
6613
+ return Math.floor(value.getTime() / 1e3);
6614
+ }
6615
+ function parseTime(timeText) {
6616
+ const prefix = timeText.match(/^(上午|下午|午前|午後)/)?.[1];
6617
+ const cleanTime = timeText.replace(/^(上午|下午|午前|午後)/, "");
6618
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6619
+ if (!match) {
6620
+ const [hours2, minutes2] = timeText.split(":").map(Number);
6621
+ return { hours: hours2, minutes: minutes2 };
6622
+ }
6623
+ let hours = Number.parseInt(match[1], 10);
6624
+ const minutes = Number.parseInt(match[2], 10);
6625
+ const suffix = match[3]?.toLowerCase();
6626
+ if (suffix === "pm" && hours < 12) hours += 12;
6627
+ if (suffix === "am" && hours === 12) hours = 0;
6628
+ if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6629
+ if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6630
+ return { hours, minutes };
6631
+ }
6632
+ function detectMessageType6(content) {
6633
+ for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
6634
+ if (content === pattern || content.startsWith(pattern)) return type;
6635
+ }
6636
+ if (content.startsWith("[null]") && content.includes("maps.google.com")) return 8 /* LOCATION */;
6637
+ 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")) {
6638
+ return 80 /* SYSTEM */;
6639
+ }
6640
+ return /^https?:\/\//.test(content) ? 7 /* LINK */ : 0 /* TEXT */;
6641
+ }
6642
+
6643
+ // ../../packages/parser/src/formats/line-native-txt.ts
6644
+ var feature14 = {
6645
+ id: "line-native-txt",
6646
+ name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
6647
+ platform: KNOWN_PLATFORMS.LINE,
6648
+ priority: 35,
6649
+ extensions: [".txt"],
6650
+ signatures: {
6651
+ head: LINE_HEAD_SIGNATURES,
6652
+ filename: LINE_FILENAME_SIGNATURES
6653
+ }
6654
+ };
6655
+ async function* parseLine2(options2) {
6656
+ const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
6657
+ const totalBytes = getFileSize(filePath);
6658
+ const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
6659
+ yield { type: "progress", data: initialProgress };
6660
+ onProgress?.(initialProgress);
6661
+ onLog?.("info", `Starting LINE TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
6662
+ const result = await parseLineText(fs17.readFileSync(filePath, "utf-8"), filePath, {
6663
+ onProgress: (progress) => onProgress?.(
6664
+ createProgress(
6665
+ "parsing",
6666
+ Math.round(progress.progress * totalBytes),
6667
+ totalBytes,
6668
+ progress.messagesProcessed,
6669
+ `Processed ${progress.messagesProcessed} messages`
6670
+ )
6671
+ )
6672
+ });
6673
+ yield { type: "meta", data: result.meta };
6674
+ yield { type: "members", data: result.members };
6675
+ for (let index = 0; index < result.messages.length; index += batchSize) {
6676
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
6677
+ }
6678
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6931
6679
  yield { type: "progress", data: doneProgress };
6932
6680
  onProgress?.(doneProgress);
6933
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberCount} \u4E2A\u6210\u5458`);
6681
+ onLog?.("info", `LINE TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
6934
6682
  yield {
6935
6683
  type: "done",
6936
- data: { messageCount: messagesProcessed, memberCount }
6684
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6937
6685
  };
6938
6686
  }
6939
6687
  var parser_13 = {
6940
6688
  feature: feature14,
6941
- parse: parseLINE
6689
+ parse: parseLine2
6942
6690
  };
6943
6691
  var module_14 = {
6944
6692
  feature: feature14,
@@ -6978,6 +6726,398 @@ var formats = [
6978
6726
  // 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
6979
6727
  ];
6980
6728
 
6729
+ // ../../packages/parser/src/chatlab-validator.ts
6730
+ import { createReadStream as createReadStream13 } from "fs";
6731
+ import { readFile, stat } from "fs/promises";
6732
+ import { extname as extname2 } from "path";
6733
+ import { createInterface as createInterface5 } from "readline";
6734
+ var MAX_REPORTED_ISSUES = 100;
6735
+ var MAX_UNIX_SECONDS = 1e11;
6736
+ var KNOWN_MESSAGE_TYPES = new Set(
6737
+ Object.values(MessageType).filter((value) => typeof value === "number")
6738
+ );
6739
+ function createState(format, fileSize) {
6740
+ return {
6741
+ format,
6742
+ fileSize,
6743
+ errorCount: 0,
6744
+ warningCount: 0,
6745
+ issues: [],
6746
+ stats: {
6747
+ headerCount: 0,
6748
+ memberCount: 0,
6749
+ messageCount: 0,
6750
+ uniqueSenderCount: 0,
6751
+ commentLineCount: 0,
6752
+ blankLineCount: 0
6753
+ },
6754
+ memberIds: /* @__PURE__ */ new Set(),
6755
+ ownerIds: /* @__PURE__ */ new Set(),
6756
+ senderIds: /* @__PURE__ */ new Set(),
6757
+ messageIds: /* @__PURE__ */ new Set(),
6758
+ replyTargetIds: /* @__PURE__ */ new Set()
6759
+ };
6760
+ }
6761
+ function addIssue(state, severity, code, message, location = {}) {
6762
+ if (severity === "error") state.errorCount++;
6763
+ else state.warningCount++;
6764
+ if (state.issues.length < MAX_REPORTED_ISSUES) {
6765
+ state.issues.push({ severity, code, message, ...location });
6766
+ }
6767
+ }
6768
+ function isRecord(value) {
6769
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6770
+ }
6771
+ function hasOwn(value, key) {
6772
+ return Object.prototype.hasOwnProperty.call(value, key);
6773
+ }
6774
+ function childLocation(location, path30) {
6775
+ return { ...location, path: path30 };
6776
+ }
6777
+ function validateRequiredString(state, value, path30, location) {
6778
+ if (typeof value === "string" && value.trim().length > 0) return true;
6779
+ addIssue(state, "error", "INVALID_REQUIRED_STRING", "Expected a non-empty string.", childLocation(location, path30));
6780
+ return false;
6781
+ }
6782
+ function validateOptionalString(state, object, key, path30, location) {
6783
+ if (!hasOwn(object, key)) return;
6784
+ if (typeof object[key] !== "string") {
6785
+ addIssue(state, "error", "INVALID_OPTIONAL_STRING", "Expected a string.", childLocation(location, path30));
6786
+ }
6787
+ }
6788
+ function validateUnixSeconds(state, value, path30, location) {
6789
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value < MAX_UNIX_SECONDS) {
6790
+ return true;
6791
+ }
6792
+ addIssue(
6793
+ state,
6794
+ "error",
6795
+ "INVALID_UNIX_SECONDS",
6796
+ "Expected a non-negative integer Unix timestamp in seconds, not milliseconds.",
6797
+ childLocation(location, path30)
6798
+ );
6799
+ return false;
6800
+ }
6801
+ function validateHeader(state, chatlab, meta, location, basePath = "") {
6802
+ state.stats.headerCount++;
6803
+ if (!isRecord(chatlab)) {
6804
+ addIssue(state, "error", "INVALID_CHATLAB_HEADER", "Expected a chatlab object.", {
6805
+ ...location,
6806
+ path: `${basePath}chatlab`
6807
+ });
6808
+ } else {
6809
+ const versionPath = `${basePath}chatlab.version`;
6810
+ if (typeof chatlab.version !== "string") {
6811
+ addIssue(state, "error", "INVALID_VERSION", "Expected a string format version.", {
6812
+ ...location,
6813
+ path: versionPath
6814
+ });
6815
+ } else {
6816
+ state.version = chatlab.version;
6817
+ if (!isSupportedChatLabFormatVersion(chatlab.version)) {
6818
+ addIssue(
6819
+ state,
6820
+ "error",
6821
+ "UNSUPPORTED_VERSION",
6822
+ `Supported ChatLab format versions: ${CHATLAB_SUPPORTED_FORMAT_VERSIONS.join(", ")}.`,
6823
+ {
6824
+ ...location,
6825
+ path: versionPath
6826
+ }
6827
+ );
6828
+ }
6829
+ }
6830
+ validateUnixSeconds(state, chatlab.exportedAt, `${basePath}chatlab.exportedAt`, location);
6831
+ validateOptionalString(state, chatlab, "generator", `${basePath}chatlab.generator`, location);
6832
+ validateOptionalString(state, chatlab, "description", `${basePath}chatlab.description`, location);
6833
+ }
6834
+ if (!isRecord(meta)) {
6835
+ addIssue(state, "error", "INVALID_META", "Expected a meta object.", {
6836
+ ...location,
6837
+ path: `${basePath}meta`
6838
+ });
6839
+ return;
6840
+ }
6841
+ validateRequiredString(state, meta.name, `${basePath}meta.name`, location);
6842
+ validateRequiredString(state, meta.platform, `${basePath}meta.platform`, location);
6843
+ if (meta.type !== "group" && meta.type !== "private") {
6844
+ addIssue(state, "error", "INVALID_CHAT_TYPE", 'Expected chat type "group" or "private".', {
6845
+ ...location,
6846
+ path: `${basePath}meta.type`
6847
+ });
6848
+ }
6849
+ validateOptionalString(state, meta, "groupId", `${basePath}meta.groupId`, location);
6850
+ validateOptionalString(state, meta, "groupAvatar", `${basePath}meta.groupAvatar`, location);
6851
+ if (hasOwn(meta, "ownerId")) {
6852
+ if (validateRequiredString(state, meta.ownerId, `${basePath}meta.ownerId`, location)) {
6853
+ state.ownerIds.add(meta.ownerId);
6854
+ }
6855
+ }
6856
+ validateOptionalString(state, meta, "sourceSessionId", `${basePath}meta.sourceSessionId`, location);
6857
+ }
6858
+ function validateMember(state, value, path30, location) {
6859
+ state.stats.memberCount++;
6860
+ if (!isRecord(value)) {
6861
+ addIssue(state, "error", "INVALID_MEMBER", "Expected a member object.", childLocation(location, path30));
6862
+ return;
6863
+ }
6864
+ if (validateRequiredString(state, value.platformId, `${path30}.platformId`, location)) {
6865
+ if (state.memberIds.has(value.platformId)) {
6866
+ addIssue(state, "error", "DUPLICATE_MEMBER_ID", "Member platformId must be unique.", {
6867
+ ...location,
6868
+ path: `${path30}.platformId`
6869
+ });
6870
+ } else {
6871
+ state.memberIds.add(value.platformId);
6872
+ }
6873
+ }
6874
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6875
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6876
+ validateOptionalString(state, value, "avatar", `${path30}.avatar`, location);
6877
+ if (hasOwn(value, "aliases")) {
6878
+ if (!Array.isArray(value.aliases) || value.aliases.some((alias) => typeof alias !== "string")) {
6879
+ addIssue(state, "error", "INVALID_ALIASES", "Expected aliases to be an array of strings.", {
6880
+ ...location,
6881
+ path: `${path30}.aliases`
6882
+ });
6883
+ }
6884
+ }
6885
+ if (hasOwn(value, "roles")) {
6886
+ if (!Array.isArray(value.roles)) {
6887
+ addIssue(state, "error", "INVALID_ROLES", "Expected roles to be an array.", {
6888
+ ...location,
6889
+ path: `${path30}.roles`
6890
+ });
6891
+ } else {
6892
+ value.roles.forEach((role, index) => {
6893
+ const rolePath = `${path30}.roles[${index}]`;
6894
+ if (!isRecord(role)) {
6895
+ addIssue(state, "error", "INVALID_ROLE", "Expected a role object.", {
6896
+ ...location,
6897
+ path: rolePath
6898
+ });
6899
+ return;
6900
+ }
6901
+ validateRequiredString(state, role.id, `${rolePath}.id`, location);
6902
+ validateOptionalString(state, role, "name", `${rolePath}.name`, location);
6903
+ });
6904
+ }
6905
+ }
6906
+ }
6907
+ function validateMessage(state, value, path30, location) {
6908
+ state.stats.messageCount++;
6909
+ if (!isRecord(value)) {
6910
+ addIssue(state, "error", "INVALID_MESSAGE", "Expected a message object.", childLocation(location, path30));
6911
+ return;
6912
+ }
6913
+ if (validateRequiredString(state, value.sender, `${path30}.sender`, location)) {
6914
+ state.senderIds.add(value.sender);
6915
+ }
6916
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6917
+ if (validateUnixSeconds(state, value.timestamp, `${path30}.timestamp`, location)) {
6918
+ if (state.lastTimestamp !== void 0 && value.timestamp < state.lastTimestamp) {
6919
+ addIssue(
6920
+ state,
6921
+ "warning",
6922
+ "OUT_OF_ORDER_TIMESTAMP",
6923
+ "Messages should be ordered by timestamp ascending.",
6924
+ childLocation(location, `${path30}.timestamp`)
6925
+ );
6926
+ }
6927
+ state.lastTimestamp = value.timestamp;
6928
+ }
6929
+ if (typeof value.type !== "number" || !Number.isInteger(value.type) || !KNOWN_MESSAGE_TYPES.has(value.type)) {
6930
+ addIssue(state, "error", "INVALID_MESSAGE_TYPE", "Expected a supported numeric ChatLab message type.", {
6931
+ ...location,
6932
+ path: `${path30}.type`
6933
+ });
6934
+ }
6935
+ if (!hasOwn(value, "content") || typeof value.content !== "string" && value.content !== null) {
6936
+ addIssue(state, "error", "INVALID_MESSAGE_CONTENT", "Expected content to be a string or null.", {
6937
+ ...location,
6938
+ path: `${path30}.content`
6939
+ });
6940
+ }
6941
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6942
+ if (hasOwn(value, "platformMessageId")) {
6943
+ if (validateRequiredString(state, value.platformMessageId, `${path30}.platformMessageId`, location)) {
6944
+ if (state.messageIds.has(value.platformMessageId)) {
6945
+ addIssue(state, "error", "DUPLICATE_MESSAGE_ID", "platformMessageId must be unique.", {
6946
+ ...location,
6947
+ path: `${path30}.platformMessageId`
6948
+ });
6949
+ } else {
6950
+ state.messageIds.add(value.platformMessageId);
6951
+ }
6952
+ }
6953
+ }
6954
+ if (hasOwn(value, "replyToMessageId")) {
6955
+ if (validateRequiredString(state, value.replyToMessageId, `${path30}.replyToMessageId`, location)) {
6956
+ state.replyTargetIds.add(value.replyToMessageId);
6957
+ }
6958
+ }
6959
+ }
6960
+ function finalizeState(state, requireExplicitMembers, enforceRequiredSections = true) {
6961
+ if (enforceRequiredSections) {
6962
+ if (state.stats.headerCount === 0) {
6963
+ addIssue(state, "error", "MISSING_HEADER", "A ChatLab header is required.");
6964
+ }
6965
+ if (state.stats.messageCount === 0) {
6966
+ addIssue(state, "error", "MISSING_MESSAGES", "At least one message is required.");
6967
+ }
6968
+ if (requireExplicitMembers && state.stats.memberCount === 0) {
6969
+ addIssue(state, "error", "MISSING_MEMBERS", "The JSON format requires a members array with sender identities.");
6970
+ }
6971
+ }
6972
+ const ownerMemberIds = state.memberIds.size > 0 ? state.memberIds : requireExplicitMembers ? void 0 : state.senderIds;
6973
+ if (ownerMemberIds) {
6974
+ for (const ownerId of state.ownerIds) {
6975
+ if (!ownerMemberIds.has(ownerId)) {
6976
+ addIssue(state, "error", "UNKNOWN_OWNER", "meta.ownerId must reference a member platformId.");
6977
+ }
6978
+ }
6979
+ }
6980
+ if (state.memberIds.size > 0) {
6981
+ for (const senderId of state.senderIds) {
6982
+ if (!state.memberIds.has(senderId)) {
6983
+ addIssue(
6984
+ state,
6985
+ "error",
6986
+ "UNKNOWN_MESSAGE_SENDER",
6987
+ "Every message sender must reference an explicit member platformId."
6988
+ );
6989
+ }
6990
+ }
6991
+ }
6992
+ for (const replyTargetId of state.replyTargetIds) {
6993
+ if (!state.messageIds.has(replyTargetId)) {
6994
+ addIssue(
6995
+ state,
6996
+ "warning",
6997
+ "UNKNOWN_REPLY_TARGET",
6998
+ "A replyToMessageId does not match any platformMessageId in this file."
6999
+ );
7000
+ }
7001
+ }
7002
+ state.stats.uniqueSenderCount = state.senderIds.size;
7003
+ const totalIssueCount = state.errorCount + state.warningCount;
7004
+ return {
7005
+ valid: state.errorCount === 0,
7006
+ format: state.format,
7007
+ version: state.version,
7008
+ fileSize: state.fileSize,
7009
+ errorCount: state.errorCount,
7010
+ warningCount: state.warningCount,
7011
+ issues: state.issues,
7012
+ truncatedIssueCount: Math.max(0, totalIssueCount - state.issues.length),
7013
+ stats: state.stats
7014
+ };
7015
+ }
7016
+ async function validateJson(filePath, fileSize) {
7017
+ const state = createState("json", fileSize);
7018
+ let root;
7019
+ try {
7020
+ root = JSON.parse(await readFile(filePath, "utf8"));
7021
+ } catch {
7022
+ addIssue(state, "error", "INVALID_JSON", "The file is not valid JSON.");
7023
+ return finalizeState(state, true, false);
7024
+ }
7025
+ if (!isRecord(root)) {
7026
+ addIssue(state, "error", "INVALID_ROOT", "Expected a top-level JSON object.");
7027
+ return finalizeState(state, true, false);
7028
+ }
7029
+ validateHeader(state, root.chatlab, root.meta, {});
7030
+ if (!Array.isArray(root.members)) {
7031
+ addIssue(state, "error", "INVALID_MEMBERS", "Expected members to be an array.", { path: "members" });
7032
+ } else {
7033
+ root.members.forEach((member, index) => validateMember(state, member, `members[${index}]`, {}));
7034
+ }
7035
+ if (!Array.isArray(root.messages)) {
7036
+ addIssue(state, "error", "INVALID_MESSAGES", "Expected messages to be an array.", { path: "messages" });
7037
+ } else {
7038
+ root.messages.forEach((message, index) => validateMessage(state, message, `messages[${index}]`, {}));
7039
+ }
7040
+ return finalizeState(state, true);
7041
+ }
7042
+ async function validateJsonl(filePath, fileSize) {
7043
+ const state = createState("jsonl", fileSize);
7044
+ const input = createReadStream13(filePath, { encoding: "utf8" });
7045
+ const lines = createInterface5({ input, crlfDelay: Infinity });
7046
+ let lineNumber = 0;
7047
+ let dataLineCount = 0;
7048
+ let sawMessage = false;
7049
+ for await (const line of lines) {
7050
+ lineNumber++;
7051
+ const trimmed = line.trim();
7052
+ if (!trimmed) {
7053
+ state.stats.blankLineCount++;
7054
+ continue;
7055
+ }
7056
+ if (trimmed.startsWith("#")) {
7057
+ state.stats.commentLineCount++;
7058
+ continue;
7059
+ }
7060
+ dataLineCount++;
7061
+ let value;
7062
+ try {
7063
+ value = JSON.parse(trimmed);
7064
+ } catch {
7065
+ addIssue(state, "error", "INVALID_JSONL_LINE", "The line is not valid JSON.", { line: lineNumber });
7066
+ continue;
7067
+ }
7068
+ if (!isRecord(value)) {
7069
+ addIssue(state, "error", "INVALID_JSONL_VALUE", "Expected a JSON object on every data line.", {
7070
+ line: lineNumber
7071
+ });
7072
+ continue;
7073
+ }
7074
+ if (dataLineCount === 1 && value._type !== "header") {
7075
+ addIssue(state, "error", "HEADER_NOT_FIRST", "The first data line must be a header.", {
7076
+ line: lineNumber,
7077
+ path: "_type"
7078
+ });
7079
+ }
7080
+ switch (value._type) {
7081
+ case "header":
7082
+ if (state.stats.headerCount > 0) {
7083
+ addIssue(state, "error", "DUPLICATE_HEADER", "Only one header line is allowed.", {
7084
+ line: lineNumber,
7085
+ path: "_type"
7086
+ });
7087
+ }
7088
+ validateHeader(state, value.chatlab, value.meta, { line: lineNumber });
7089
+ break;
7090
+ case "member":
7091
+ if (sawMessage) {
7092
+ addIssue(state, "error", "MEMBER_AFTER_MESSAGE", "Member lines must appear before message lines.", {
7093
+ line: lineNumber
7094
+ });
7095
+ }
7096
+ validateMember(state, value, `line[${lineNumber}]`, { line: lineNumber });
7097
+ break;
7098
+ case "message":
7099
+ sawMessage = true;
7100
+ validateMessage(state, value, `line[${lineNumber}]`, { line: lineNumber });
7101
+ break;
7102
+ default:
7103
+ addIssue(state, "error", "UNKNOWN_LINE_TYPE", "Expected _type to be header, member, or message.", {
7104
+ line: lineNumber,
7105
+ path: "_type"
7106
+ });
7107
+ }
7108
+ }
7109
+ return finalizeState(state, false);
7110
+ }
7111
+ async function validateChatLabFile(filePath) {
7112
+ const fileStat = await stat(filePath);
7113
+ const extension = extname2(filePath).toLowerCase();
7114
+ if (extension === ".json") return validateJson(filePath, fileStat.size);
7115
+ if (extension === ".jsonl") return validateJsonl(filePath, fileStat.size);
7116
+ const state = createState("unknown", fileStat.size);
7117
+ addIssue(state, "error", "UNSUPPORTED_EXTENSION", "Expected a .json or .jsonl file.");
7118
+ return finalizeState(state, false, false);
7119
+ }
7120
+
6981
7121
  // ../../packages/parser/src/index.ts
6982
7122
  var sniffer = createSniffer();
6983
7123
  sniffer.registerAll(formats);
@@ -7051,15 +7191,15 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
7051
7191
  return null;
7052
7192
  }
7053
7193
  for (const entry of entries) {
7054
- const full = path14.join(current, entry.name);
7055
- if (entry.isFile() && supportedExtensions.has(path14.extname(entry.name).toLowerCase())) {
7194
+ const full = path11.join(current, entry.name);
7195
+ if (entry.isFile() && supportedExtensions.has(path11.extname(entry.name).toLowerCase())) {
7056
7196
  const format = detectFormat(full);
7057
7197
  if (format) return full;
7058
7198
  }
7059
7199
  }
7060
7200
  for (const entry of entries) {
7061
7201
  if (entry.isDirectory()) {
7062
- const found = walk(path14.join(current, entry.name), depth + 1);
7202
+ const found = walk(path11.join(current, entry.name), depth + 1);
7063
7203
  if (found) return found;
7064
7204
  }
7065
7205
  }
@@ -7109,7 +7249,7 @@ function logNativeParserStatus() {
7109
7249
 
7110
7250
  // ../../packages/node-runtime/src/import/perf-logger.ts
7111
7251
  import * as fs19 from "fs";
7112
- import * as path15 from "path";
7252
+ import * as path12 from "path";
7113
7253
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7114
7254
  LogLevel2["ERROR"] = "ERROR";
7115
7255
  LogLevel2["INFO"] = "INFO";
@@ -7124,7 +7264,7 @@ function initPerfLog(sessionId, logDir) {
7124
7264
  if (!fs19.existsSync(logDir)) {
7125
7265
  fs19.mkdirSync(logDir, { recursive: true });
7126
7266
  }
7127
- currentLogFile = path15.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7267
+ currentLogFile = path12.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7128
7268
  fs19.writeFileSync(currentLogFile, `=== Import Log ===
7129
7269
  Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
7130
7270
 
@@ -7301,7 +7441,7 @@ function segmentText(text, locale, minLength) {
7301
7441
 
7302
7442
  // ../../packages/node-runtime/src/nlp/dict-manager.ts
7303
7443
  import * as fs20 from "fs";
7304
- import * as path16 from "path";
7444
+ import * as path13 from "path";
7305
7445
  import { createHash } from "crypto";
7306
7446
  var DICT_DOWNLOAD_URL_BASE = "https://chatlab.fun/assets/nlp";
7307
7447
  var DICT_SHA256 = {
@@ -7313,7 +7453,7 @@ var AVAILABLE_DICTS = [
7313
7453
  { id: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", locale: "zh-TW" }
7314
7454
  ];
7315
7455
  function getDictFilePath(nlpDir, dictId) {
7316
- return path16.join(nlpDir, `${dictId}.dict`);
7456
+ return path13.join(nlpDir, `${dictId}.dict`);
7317
7457
  }
7318
7458
  function isDictDownloaded(nlpDir, dictId) {
7319
7459
  return fs20.existsSync(getDictFilePath(nlpDir, dictId));
@@ -8779,7 +8919,7 @@ async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
8779
8919
 
8780
8920
  // ../../packages/node-runtime/src/import/import-lock.ts
8781
8921
  import * as fs22 from "fs";
8782
- import * as path17 from "path";
8922
+ import * as path14 from "path";
8783
8923
  import { randomUUID } from "crypto";
8784
8924
  var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
8785
8925
  var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
@@ -8848,7 +8988,7 @@ function releaseLock(lockPath, fd, owner) {
8848
8988
  }
8849
8989
  async function withDataDirImportLock(userDataDir, task) {
8850
8990
  fs22.mkdirSync(userDataDir, { recursive: true });
8851
- const lockPath = path17.join(userDataDir, IMPORT_LOCK_FILENAME);
8991
+ const lockPath = path14.join(userDataDir, IMPORT_LOCK_FILENAME);
8852
8992
  let fd = null;
8853
8993
  for (let attempt = 0; attempt < 2; attempt++) {
8854
8994
  try {
@@ -8893,7 +9033,7 @@ var ArchiveImportError = class extends Error {
8893
9033
  // ../../packages/node-runtime/src/import/archive/archive-reader.ts
8894
9034
  var import_yauzl = __toESM(require_yauzl(), 1);
8895
9035
  import * as fs23 from "fs";
8896
- import * as path18 from "path";
9036
+ import * as path15 from "path";
8897
9037
  import { pipeline } from "stream/promises";
8898
9038
  var DEFAULT_MAX_ENTRIES = 1e5;
8899
9039
  var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
@@ -9012,7 +9152,7 @@ var ZipArchiveReader = class {
9012
9152
  await this.visitEntries(async (entry, openStream) => {
9013
9153
  const destination = targets.get(entry.name);
9014
9154
  if (!destination || entry.isDirectory) return;
9015
- fs23.mkdirSync(path18.dirname(destination), { recursive: true });
9155
+ fs23.mkdirSync(path15.dirname(destination), { recursive: true });
9016
9156
  await pipeline(await openStream(), fs23.createWriteStream(destination, { flags: "wx" }));
9017
9157
  remaining.delete(entry.name);
9018
9158
  });
@@ -9026,16 +9166,16 @@ var ZipArchiveReader = class {
9026
9166
  };
9027
9167
 
9028
9168
  // ../../packages/node-runtime/src/import/archive/google-chat-resolver.ts
9029
- var import_stream_chain9 = __toESM(require_stream_chain(), 1);
9169
+ var import_stream_chain8 = __toESM(require_stream_chain(), 1);
9030
9170
  var import_stream_json9 = __toESM(require_stream_json(), 1);
9031
9171
  var import_Pick8 = __toESM(require_Pick(), 1);
9032
- var import_StreamValues9 = __toESM(require_StreamValues(), 1);
9172
+ var import_StreamValues8 = __toESM(require_StreamValues(), 1);
9033
9173
  import * as fs24 from "fs";
9034
- import * as path19 from "path";
9035
- var { chain: chain9 } = import_stream_chain9.default;
9174
+ import * as path16 from "path";
9175
+ var { chain: chain8 } = import_stream_chain8.default;
9036
9176
  var { parser: parser10 } = import_stream_json9.default;
9037
9177
  var { pick: pick8 } = import_Pick8.default;
9038
- var { streamValues: streamValues9 } = import_StreamValues9.default;
9178
+ var { streamValues: streamValues8 } = import_StreamValues8.default;
9039
9179
  var GROUP_FILE = /^Takeout\/Google Chat\/Groups\/((DM|Space) [^/]+)\/(group_info|messages)\.json$/;
9040
9180
  var USER_INFO_FILE = /^Takeout\/Google Chat\/Users\/[^/]+\/user_info\.json$/;
9041
9181
  var TAKEOUT_ROOT = /^Takeout\/([^/]+)\//;
@@ -9058,7 +9198,7 @@ async function readSmallJson(stream, maxBytes = MAX_METADATA_BYTES) {
9058
9198
  }
9059
9199
  }
9060
9200
  async function countMessages(stream) {
9061
- const pipeline2 = chain9([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues9()]);
9201
+ const pipeline2 = chain8([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues8()]);
9062
9202
  let count = 0;
9063
9203
  try {
9064
9204
  for await (const _item of pipeline2) count++;
@@ -9163,12 +9303,12 @@ var GoogleChatTakeoutResolver = class {
9163
9303
  fs24.mkdirSync(targetDir, { recursive: true });
9164
9304
  await reader.pipeEntries(
9165
9305
  /* @__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")]
9306
+ [userInfoEntry, path16.join(targetDir, "user_info.json")],
9307
+ [groupInfoEntry, path16.join(targetDir, "group_info.json")],
9308
+ [messagesEntry, path16.join(targetDir, "messages.json")]
9169
9309
  ])
9170
9310
  );
9171
- const manifestPath = path19.join(targetDir, "google-chat-import.json");
9311
+ const manifestPath = path16.join(targetDir, "google-chat-import.json");
9172
9312
  fs24.writeFileSync(
9173
9313
  manifestPath,
9174
9314
  JSON.stringify(
@@ -9193,7 +9333,7 @@ var GoogleChatTakeoutResolver = class {
9193
9333
 
9194
9334
  // ../../packages/node-runtime/src/import/archive/source-manager.ts
9195
9335
  import * as fs25 from "fs";
9196
- import * as path20 from "path";
9336
+ import * as path17 from "path";
9197
9337
  import { randomUUID as randomUUID2 } from "crypto";
9198
9338
  var DEFAULT_TTL_MS = 30 * 60 * 1e3;
9199
9339
  var ArchiveImportSourceManager = class {
@@ -9265,7 +9405,7 @@ var ArchiveImportSourceManager = class {
9265
9405
  `Archive conversation was not found: ${chatId}`
9266
9406
  );
9267
9407
  }
9268
- const materializedDir = fs25.mkdtempSync(path20.join(this.tempRoot, `materialized-${sourceId}-`));
9408
+ const materializedDir = fs25.mkdtempSync(path17.join(this.tempRoot, `materialized-${sourceId}-`));
9269
9409
  try {
9270
9410
  const reader = new ZipArchiveReader(record.archivePath);
9271
9411
  const materialized = await record.resolver.materialize(reader, chat, materializedDir);
@@ -9386,12 +9526,12 @@ ${rawErrorLabel}: ${details}`;
9386
9526
  }
9387
9527
 
9388
9528
  // ../../packages/node-runtime/src/ai/assistant-parser.ts
9389
- import * as path21 from "path";
9529
+ import * as path18 from "path";
9390
9530
  import matter from "gray-matter";
9391
9531
  function parseAssistantFile(content, filePath) {
9392
9532
  try {
9393
9533
  const { data: fm, content: body } = matter(content);
9394
- const id = fm.id ?? path21.basename(filePath, ".md");
9534
+ const id = fm.id ?? path18.basename(filePath, ".md");
9395
9535
  const name = fm.name;
9396
9536
  if (!name) return null;
9397
9537
  return {
@@ -9901,12 +10041,12 @@ var AssistantManager = class {
9901
10041
  };
9902
10042
 
9903
10043
  // ../../packages/node-runtime/src/ai/skill-parser.ts
9904
- import * as path22 from "path";
10044
+ import * as path19 from "path";
9905
10045
  import matter2 from "gray-matter";
9906
10046
  function parseSkillFile(content, filePath) {
9907
10047
  try {
9908
10048
  const { data: fm, content: prompt } = matter2(content);
9909
- const id = fm.id ?? path22.basename(filePath, ".md");
10049
+ const id = fm.id ?? path19.basename(filePath, ".md");
9910
10050
  const name = fm.name;
9911
10051
  if (!name) return null;
9912
10052
  return {
@@ -9925,7 +10065,7 @@ function parseSkillFile(content, filePath) {
9925
10065
  function extractSkillId(content, filePath) {
9926
10066
  try {
9927
10067
  const { data: fm } = matter2(content);
9928
- return fm.id ?? path22.basename(filePath, ".md");
10068
+ return fm.id ?? path19.basename(filePath, ".md");
9929
10069
  } catch {
9930
10070
  return null;
9931
10071
  }
@@ -9945,7 +10085,7 @@ function validateChatScope(raw) {
9945
10085
  // ../../packages/node-runtime/src/ai/chats.ts
9946
10086
  import Database from "better-sqlite3";
9947
10087
  import * as fs26 from "fs";
9948
- import * as path23 from "path";
10088
+ import * as path20 from "path";
9949
10089
  var DEFAULT_GENERAL_ID = DEFAULT_GENERAL_ASSISTANT_ID;
9950
10090
  var defaultLogger = {
9951
10091
  warn(_category, message, extra) {
@@ -9971,7 +10111,7 @@ var AIChatManager = class {
9971
10111
  getDb() {
9972
10112
  if (this.db) return this.db;
9973
10113
  this.ensureDir(this.aiDataDir);
9974
- const dbPath = path23.join(this.aiDataDir, "conversations.db");
10114
+ const dbPath = path20.join(this.aiDataDir, "conversations.db");
9975
10115
  this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
9976
10116
  this.db.pragma("journal_mode = WAL");
9977
10117
  this.db.exec(`
@@ -10214,16 +10354,16 @@ var AIChatManager = class {
10214
10354
  if (allRows.length === 0) return [];
10215
10355
  const rowMap = new Map(allRows.map((row) => [row.id, row]));
10216
10356
  let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
10217
- const path33 = [];
10357
+ const path30 = [];
10218
10358
  const seen = /* @__PURE__ */ new Set();
10219
10359
  while (currentId && !seen.has(currentId)) {
10220
10360
  seen.add(currentId);
10221
10361
  const row = rowMap.get(currentId);
10222
10362
  if (!row) break;
10223
- path33.push(row);
10363
+ path30.push(row);
10224
10364
  currentId = row.parentId;
10225
10365
  }
10226
- return path33.length > 0 ? path33.reverse() : allRows;
10366
+ return path30.length > 0 ? path30.reverse() : allRows;
10227
10367
  }
10228
10368
  // ==================== 生命周期 ====================
10229
10369
  close() {
@@ -10960,7 +11100,7 @@ var SkillManagerCore = class {
10960
11100
 
10961
11101
  // ../../packages/node-runtime/src/ai/skill-manager.ts
10962
11102
  import * as fs27 from "fs";
10963
- import * as path24 from "path";
11103
+ import * as path21 from "path";
10964
11104
  var SKILLS_DIR_NAME = "skills";
10965
11105
  var defaultLogger2 = {
10966
11106
  info: () => {
@@ -10989,7 +11129,7 @@ var nodeFs = {
10989
11129
  return fs27.existsSync(filePath);
10990
11130
  },
10991
11131
  joinPath(...parts) {
10992
- return path24.join(...parts);
11132
+ return path21.join(...parts);
10993
11133
  }
10994
11134
  };
10995
11135
  var SkillManager = class {
@@ -10999,7 +11139,7 @@ var SkillManager = class {
10999
11139
  this.logger = logger ?? defaultLogger2;
11000
11140
  this.core = new SkillManagerCore({
11001
11141
  fs: nodeFs,
11002
- skillsDir: path24.join(aiDataDir, SKILLS_DIR_NAME),
11142
+ skillsDir: path21.join(aiDataDir, SKILLS_DIR_NAME),
11003
11143
  builtinRawSkills: [],
11004
11144
  logger: {
11005
11145
  info: () => {
@@ -12861,7 +13001,7 @@ var PLAN_INTENTS = [
12861
13001
  "mixed"
12862
13002
  ];
12863
13003
  var STREAM_MARKER_HOLDBACK_CHARS = Math.max("</draft>".length, "</plan>".length, "<json>".length) - 1;
12864
- function isRecord(value) {
13004
+ function isRecord2(value) {
12865
13005
  return typeof value === "object" && value !== null && !Array.isArray(value);
12866
13006
  }
12867
13007
  function isIntent(value) {
@@ -12883,10 +13023,10 @@ function extractJsonObject4(text) {
12883
13023
  function parsePlanJson(rawText, availableTools) {
12884
13024
  try {
12885
13025
  const parsed = JSON.parse(extractJsonObject4(rawText));
12886
- if (!isRecord(parsed)) return null;
13026
+ if (!isRecord2(parsed)) return null;
12887
13027
  const title = normalizeString(parsed.title);
12888
13028
  if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null;
12889
- const steps = parsed.steps.filter(isRecord).map((step) => {
13029
+ const steps = parsed.steps.filter(isRecord2).map((step) => {
12890
13030
  const goal = normalizeString(step.goal);
12891
13031
  const evidenceNeeded = normalizeString(step.evidenceNeeded);
12892
13032
  if (!goal || !evidenceNeeded) return null;
@@ -14865,9 +15005,9 @@ var localeMap = {
14865
15005
  "ja-JP": ja_JP_default,
14866
15006
  "zh-TW": zh_TW_default
14867
15007
  };
14868
- function getNestedValue(obj, path33) {
15008
+ function getNestedValue(obj, path30) {
14869
15009
  let current = obj;
14870
- for (const segment2 of path33.split(".")) {
15010
+ for (const segment2 of path30.split(".")) {
14871
15011
  if (current == null || typeof current !== "object") return void 0;
14872
15012
  current = current[segment2];
14873
15013
  }
@@ -15382,19 +15522,19 @@ var LLMConfigStore = class {
15382
15522
 
15383
15523
  // ../../packages/node-runtime/src/ai/file-config-storage.ts
15384
15524
  import * as fs28 from "fs";
15385
- import * as path25 from "path";
15525
+ import * as path22 from "path";
15386
15526
  function createFileConfigStorage(baseDir) {
15387
15527
  return {
15388
15528
  readJson(key) {
15389
15529
  try {
15390
- return JSON.parse(fs28.readFileSync(path25.join(baseDir, `${key}.json`), "utf-8"));
15530
+ return JSON.parse(fs28.readFileSync(path22.join(baseDir, `${key}.json`), "utf-8"));
15391
15531
  } catch {
15392
15532
  return null;
15393
15533
  }
15394
15534
  },
15395
15535
  writeJson(key, data) {
15396
15536
  fs28.mkdirSync(baseDir, { recursive: true });
15397
- fs28.writeFileSync(path25.join(baseDir, `${key}.json`), JSON.stringify(data, null, 2), "utf-8");
15537
+ fs28.writeFileSync(path22.join(baseDir, `${key}.json`), JSON.stringify(data, null, 2), "utf-8");
15398
15538
  }
15399
15539
  };
15400
15540
  }
@@ -16133,7 +16273,7 @@ function exportAsJson(db, messages, sessionName, sourceSessionId) {
16133
16273
  ).all();
16134
16274
  const data = {
16135
16275
  chatlab: {
16136
- version: "0.0.2",
16276
+ version: CHATLAB_FORMAT_VERSION,
16137
16277
  exportedAt: Math.floor(Date.now() / 1e3),
16138
16278
  generator: "ChatLab"
16139
16279
  },
@@ -16243,7 +16383,7 @@ function exportWithFormat(params, openDatabase) {
16243
16383
 
16244
16384
  // ../../packages/node-runtime/src/merger/temp-db.ts
16245
16385
  import * as fs29 from "fs";
16246
- import * as path26 from "path";
16386
+ import * as path23 from "path";
16247
16387
  var TEMP_DB_SCHEMA = `
16248
16388
  CREATE TABLE IF NOT EXISTS meta (
16249
16389
  name TEXT NOT NULL,
@@ -16458,7 +16598,7 @@ function exportSessionToJson(db) {
16458
16598
  ).all();
16459
16599
  return {
16460
16600
  chatlab: {
16461
- version: "0.0.1",
16601
+ version: CHATLAB_FORMAT_VERSION,
16462
16602
  exportedAt: Math.floor(Date.now() / 1e3),
16463
16603
  generator: "ChatLab Export",
16464
16604
  description: `Exported from session: ${meta.name}`
@@ -16502,7 +16642,7 @@ function cleanupTempDatabases(tempDir) {
16502
16642
  const files = fs29.readdirSync(tempDir);
16503
16643
  for (const file of files) {
16504
16644
  if (file.startsWith("merge_") && file.endsWith(".db")) {
16505
- deleteTempDatabase(path26.join(tempDir, file));
16645
+ deleteTempDatabase(path23.join(tempDir, file));
16506
16646
  }
16507
16647
  }
16508
16648
  } catch {
@@ -16888,7 +17028,7 @@ function clearSessionOwner(adapter, sessionId) {
16888
17028
 
16889
17029
  // ../../packages/node-runtime/src/services/contacts/overrides.ts
16890
17030
  import fs30 from "fs";
16891
- import path27 from "path";
17031
+ import path24 from "path";
16892
17032
  var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
16893
17033
  var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
16894
17034
  var CONTACT_OVERRIDES_VERSION = 1;
@@ -16899,7 +17039,7 @@ function createEmptyContactOverridesFile() {
16899
17039
  };
16900
17040
  }
16901
17041
  function getContactOverridesPath(snapshotDir) {
16902
- return path27.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
17042
+ return path24.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
16903
17043
  }
16904
17044
  function readContactOverrides(snapshotDir) {
16905
17045
  const filePath = getContactOverridesPath(snapshotDir);
@@ -16913,16 +17053,16 @@ function readContactOverrides(snapshotDir) {
16913
17053
  }
16914
17054
  function writeContactOverrides(snapshotDir, overrides) {
16915
17055
  if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
16916
- const tmpPath = path27.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
17056
+ const tmpPath = path24.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
16917
17057
  fs30.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
16918
17058
  fs30.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
16919
17059
  }
16920
17060
  function normalizeContactOverrides(value) {
16921
- const input = isRecord2(value) ? value : {};
16922
- const manualFriendsInput = isRecord2(input.manualFriends) ? input.manualFriends : {};
17061
+ const input = isRecord3(value) ? value : {};
17062
+ const manualFriendsInput = isRecord3(input.manualFriends) ? input.manualFriends : {};
16923
17063
  const manualFriends = {};
16924
17064
  for (const [key, entry] of Object.entries(manualFriendsInput)) {
16925
- if (!key || !isRecord2(entry)) continue;
17065
+ if (!key || !isRecord3(entry)) continue;
16926
17066
  const createdAt = Number.isFinite(entry.createdAt) ? Number(entry.createdAt) : Date.now();
16927
17067
  const updatedAt = Number.isFinite(entry.updatedAt) ? Number(entry.updatedAt) : createdAt;
16928
17068
  manualFriends[key] = { key, createdAt, updatedAt };
@@ -16932,7 +17072,7 @@ function normalizeContactOverrides(value) {
16932
17072
  manualFriends
16933
17073
  };
16934
17074
  }
16935
- function isRecord2(value) {
17075
+ function isRecord3(value) {
16936
17076
  return typeof value === "object" && value !== null && !Array.isArray(value);
16937
17077
  }
16938
17078
 
@@ -16951,11 +17091,11 @@ function buildContactsSignature(adapter, timeRangePreset) {
16951
17091
 
16952
17092
  // ../../packages/node-runtime/src/services/contacts/snapshot.ts
16953
17093
  import fs31 from "fs";
16954
- import path28 from "path";
17094
+ import path25 from "path";
16955
17095
  var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
16956
17096
  function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
16957
17097
  const preset = normalizeContactsTimeRangePreset(timeRangePreset);
16958
- return path28.join(snapshotDir, `contacts-snapshot-${preset}.json`);
17098
+ return path25.join(snapshotDir, `contacts-snapshot-${preset}.json`);
16959
17099
  }
16960
17100
  function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16961
17101
  const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
@@ -16964,7 +17104,7 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16964
17104
  return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
16965
17105
  } catch (error) {
16966
17106
  const ts = options2.now?.() ?? Date.now();
16967
- const backupPath = path28.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
17107
+ const backupPath = path25.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
16968
17108
  try {
16969
17109
  fs31.renameSync(snapshotPath, backupPath);
16970
17110
  } catch (renameError) {
@@ -16976,7 +17116,7 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16976
17116
  }
16977
17117
  function writeContactsSnapshot(snapshotDir, snapshot) {
16978
17118
  if (!fs31.existsSync(snapshotDir)) fs31.mkdirSync(snapshotDir, { recursive: true });
16979
- const tmpPath = path28.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17119
+ const tmpPath = path25.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
16980
17120
  fs31.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
16981
17121
  fs31.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
16982
17122
  }
@@ -16985,7 +17125,7 @@ function cleanupContactsSnapshotTempFiles(snapshotDir) {
16985
17125
  for (const name of fs31.readdirSync(snapshotDir)) {
16986
17126
  if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
16987
17127
  try {
16988
- fs31.rmSync(path28.join(snapshotDir, name), { force: true });
17128
+ fs31.rmSync(path25.join(snapshotDir, name), { force: true });
16989
17129
  } catch (error) {
16990
17130
  appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
16991
17131
  }
@@ -17484,11 +17624,11 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
17484
17624
 
17485
17625
  // ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
17486
17626
  import fs32 from "fs";
17487
- import path29 from "path";
17627
+ import path26 from "path";
17488
17628
  var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
17489
17629
  function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
17490
17630
  const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
17491
- return path29.join(snapshotDir, `graph-snapshot-${preset}.json`);
17631
+ return path26.join(snapshotDir, `graph-snapshot-${preset}.json`);
17492
17632
  }
17493
17633
  function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
17494
17634
  const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
@@ -17497,7 +17637,7 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
17497
17637
  return JSON.parse(fs32.readFileSync(snapshotPath, "utf-8"));
17498
17638
  } catch (error) {
17499
17639
  const ts = options2.now?.() ?? Date.now();
17500
- const backupPath = path29.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17640
+ const backupPath = path26.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17501
17641
  try {
17502
17642
  fs32.renameSync(snapshotPath, backupPath);
17503
17643
  } catch (renameError) {
@@ -17509,7 +17649,7 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
17509
17649
  }
17510
17650
  function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
17511
17651
  if (!fs32.existsSync(snapshotDir)) fs32.mkdirSync(snapshotDir, { recursive: true });
17512
- const tmpPath = path29.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17652
+ const tmpPath = path26.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17513
17653
  fs32.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
17514
17654
  fs32.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
17515
17655
  }
@@ -17518,7 +17658,7 @@ function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
17518
17658
  for (const name of fs32.readdirSync(snapshotDir)) {
17519
17659
  if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
17520
17660
  try {
17521
- fs32.rmSync(path29.join(snapshotDir, name), { force: true });
17661
+ fs32.rmSync(path26.join(snapshotDir, name), { force: true });
17522
17662
  } catch (error) {
17523
17663
  appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
17524
17664
  }
@@ -18342,7 +18482,7 @@ __export(global_insight_exports, {
18342
18482
 
18343
18483
  // ../../packages/node-runtime/src/semantic-index/config.ts
18344
18484
  import fs34 from "fs";
18345
- import path30 from "path";
18485
+ import path27 from "path";
18346
18486
  var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
18347
18487
  var SEMANTIC_INDEX_CONFIG_VERSION = 1;
18348
18488
  var SEARCH_MAX_RESULTS_DEFAULT = 10;
@@ -18441,7 +18581,7 @@ var SemanticIndexConfigStore = class {
18441
18581
  }
18442
18582
  set(config) {
18443
18583
  const normalized = normalizeConfig(config);
18444
- const dir = path30.dirname(this.filePath);
18584
+ const dir = path27.dirname(this.filePath);
18445
18585
  if (!fs34.existsSync(dir)) fs34.mkdirSync(dir, { recursive: true });
18446
18586
  fs34.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
18447
18587
  return normalized;
@@ -18452,7 +18592,7 @@ var SemanticIndexConfigStore = class {
18452
18592
  };
18453
18593
 
18454
18594
  // ../../packages/node-runtime/src/semantic-index/service.ts
18455
- import path31 from "path";
18595
+ import path28 from "path";
18456
18596
 
18457
18597
  // ../../packages/node-runtime/src/semantic-index/chunker-config.ts
18458
18598
  import { createHash as createHash2 } from "crypto";
@@ -19983,16 +20123,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
19983
20123
  return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
19984
20124
  }
19985
20125
  function sessionIdFromDbPath(dbPath) {
19986
- return path31.basename(dbPath, ".db");
20126
+ return path28.basename(dbPath, ".db");
19987
20127
  }
19988
20128
  var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
19989
20129
  var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
19990
20130
  function createSemanticIndexService(params) {
19991
20131
  const { pathProvider } = params;
19992
20132
  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"),
20133
+ vectorDbPath: path28.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
20134
+ configPath: path28.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
20135
+ modelsCacheDir: path28.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
19996
20136
  modelDownloadProxyUrl: params.modelDownloadProxyUrl,
19997
20137
  sessionAdapter: params.sessionAdapter,
19998
20138
  nativeBinding: params.nativeBinding,
@@ -20543,7 +20683,7 @@ ${text}`;
20543
20683
 
20544
20684
  // ../../packages/node-runtime/src/services/merge-cache.ts
20545
20685
  import * as fs35 from "fs";
20546
- import * as path32 from "path";
20686
+ import * as path29 from "path";
20547
20687
  import * as crypto from "crypto";
20548
20688
  var MergeSessionCache = class {
20549
20689
  cache = /* @__PURE__ */ new Map();
@@ -20560,7 +20700,7 @@ var MergeSessionCache = class {
20560
20700
  const ts = Date.now();
20561
20701
  const rand = Math.random().toString(36).slice(2, 8);
20562
20702
  const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
20563
- return path32.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20703
+ return path29.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20564
20704
  }
20565
20705
  /** Store a parsed file's temp DB and return a handle for subsequent requests. */
20566
20706
  store(filename, tempDbPath) {
@@ -20619,23 +20759,23 @@ var MergeSessionCache = class {
20619
20759
  import * as fs36 from "fs";
20620
20760
  var SYSTEM_SENDER_ID = "SYSTEM";
20621
20761
  var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
20622
- function isRecord3(value) {
20762
+ function isRecord4(value) {
20623
20763
  return typeof value === "object" && value !== null && !Array.isArray(value);
20624
20764
  }
20625
- function validateOptionalString(value, field) {
20765
+ function validateOptionalString2(value, field) {
20626
20766
  return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
20627
20767
  }
20628
20768
  function validatePayload(payload, isNew) {
20629
- if (!isRecord3(payload)) return "payload must be an object";
20769
+ if (!isRecord4(payload)) return "payload must be an object";
20630
20770
  const messages = payload.messages;
20631
20771
  if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
20632
20772
  if (payload.chatlab !== void 0) {
20633
- if (!isRecord3(payload.chatlab)) return "chatlab must be an object";
20773
+ if (!isRecord4(payload.chatlab)) return "chatlab must be an object";
20634
20774
  if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
20635
20775
  return "chatlab.version must be a string";
20636
20776
  if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
20637
20777
  return "chatlab.exportedAt must be a number";
20638
- const generatorError = validateOptionalString(payload.chatlab.generator, "chatlab.generator");
20778
+ const generatorError = validateOptionalString2(payload.chatlab.generator, "chatlab.generator");
20639
20779
  if (generatorError) return generatorError;
20640
20780
  }
20641
20781
  if (isNew) {
@@ -20643,17 +20783,17 @@ function validatePayload(payload, isNew) {
20643
20783
  if (!payload.meta) return "meta is required for new sessions";
20644
20784
  }
20645
20785
  if (payload.meta !== void 0) {
20646
- if (!isRecord3(payload.meta)) return "meta must be an object";
20786
+ if (!isRecord4(payload.meta)) return "meta must be an object";
20647
20787
  const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
20648
20788
  for (const field of metaFields) {
20649
- const error = validateOptionalString(payload.meta[field], `meta.${field}`);
20789
+ const error = validateOptionalString2(payload.meta[field], `meta.${field}`);
20650
20790
  if (error) return error;
20651
20791
  }
20652
20792
  if (isNew && !payload.meta.name) return "meta.name is required";
20653
20793
  if (isNew && !payload.meta.platform) return "meta.platform is required";
20654
20794
  if (isNew && !payload.meta.type) return "meta.type is required";
20655
20795
  }
20656
- if (payload.options !== void 0 && !isRecord3(payload.options)) return "options must be an object";
20796
+ if (payload.options !== void 0 && !isRecord4(payload.options)) return "options must be an object";
20657
20797
  const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
20658
20798
  if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
20659
20799
  return `options.metaUpdateMode must be 'patch' or 'none'`;
@@ -20665,18 +20805,18 @@ function validatePayload(payload, isNew) {
20665
20805
  if (payload.members) {
20666
20806
  for (let i = 0; i < payload.members.length; i++) {
20667
20807
  const member = payload.members[i];
20668
- if (!isRecord3(member)) return `members[${i}] must be an object`;
20808
+ if (!isRecord4(member)) return `members[${i}] must be an object`;
20669
20809
  if (typeof member.platformId !== "string" || member.platformId.length === 0)
20670
20810
  return `members[${i}].platformId must be a string`;
20671
20811
  for (const field of ["accountName", "groupNickname", "avatar"]) {
20672
- const error = validateOptionalString(member[field], `members[${i}].${field}`);
20812
+ const error = validateOptionalString2(member[field], `members[${i}].${field}`);
20673
20813
  if (error) return error;
20674
20814
  }
20675
20815
  if (member.roles !== void 0) {
20676
20816
  if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
20677
20817
  for (let j = 0; j < member.roles.length; j++) {
20678
20818
  const role = member.roles[j];
20679
- if (!isRecord3(role) || typeof role.id !== "string" || role.id.length === 0)
20819
+ if (!isRecord4(role) || typeof role.id !== "string" || role.id.length === 0)
20680
20820
  return `members[${i}].roles[${j}].id must be a string`;
20681
20821
  }
20682
20822
  }
@@ -20684,13 +20824,13 @@ function validatePayload(payload, isNew) {
20684
20824
  }
20685
20825
  for (let i = 0; i < messages.length; i++) {
20686
20826
  const msg = messages[i];
20687
- if (!isRecord3(msg)) return `messages[${i}] must be an object`;
20827
+ if (!isRecord4(msg)) return `messages[${i}] must be an object`;
20688
20828
  if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
20689
20829
  if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
20690
20830
  return `messages[${i}].timestamp must be a positive number`;
20691
20831
  if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
20692
20832
  for (const field of ["accountName", "groupNickname"]) {
20693
- const error = validateOptionalString(msg[field], `messages[${i}].${field}`);
20833
+ const error = validateOptionalString2(msg[field], `messages[${i}].${field}`);
20694
20834
  if (error) return error;
20695
20835
  }
20696
20836
  if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
@@ -21089,7 +21229,7 @@ export {
21089
21229
  getChatLabTempScopeDir,
21090
21230
  createChatLabTempDir,
21091
21231
  removeChatLabTempDir,
21092
- writeParseResultToDb,
21232
+ validateChatLabFile,
21093
21233
  detectFormat,
21094
21234
  detectAllFormats,
21095
21235
  getSupportedFormats,
@@ -21309,4 +21449,4 @@ export {
21309
21449
  hashImportBody,
21310
21450
  ImportIdempotencyCache
21311
21451
  };
21312
- //# sourceMappingURL=chunk-LNU4RW5G.mjs.map
21452
+ //# sourceMappingURL=chunk-KWJ5FZ74.mjs.map