chatlab-cli 0.31.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/{chat-command-CHV7RLBH.mjs → chat-command-MOETAGU5.mjs} +8 -8
  2. package/dist/{chunk-DBBL74RK.mjs → chunk-2IXPWPLR.mjs} +164 -82
  3. package/dist/chunk-2IXPWPLR.mjs.map +1 -0
  4. package/dist/{chunk-YPQ72HMX.mjs → chunk-3VMKS7PU.mjs} +44 -5
  5. package/dist/chunk-3VMKS7PU.mjs.map +1 -0
  6. package/dist/{chunk-SNCXU2QL.mjs → chunk-7HBVGVOB.mjs} +2 -2
  7. package/dist/{chunk-UWWFOEGF.mjs → chunk-DQ45LDKZ.mjs} +19 -2
  8. package/dist/chunk-DQ45LDKZ.mjs.map +1 -0
  9. package/dist/{chunk-WQCY45U2.mjs → chunk-E6W2RURS.mjs} +3 -3
  10. package/dist/{chunk-T64GA5HP.mjs → chunk-KCPGEILH.mjs} +388 -185
  11. package/dist/chunk-KCPGEILH.mjs.map +1 -0
  12. package/dist/{chunk-RDCUGC24.mjs → chunk-LJXSUN25.mjs} +2 -2
  13. package/dist/{chunk-EXD2SXWL.mjs → chunk-N5AGDT7I.mjs} +40 -7
  14. package/dist/chunk-N5AGDT7I.mjs.map +1 -0
  15. package/dist/{chunk-JH5RAJT2.mjs → chunk-SZEBCNBB.mjs} +121 -56
  16. package/dist/chunk-SZEBCNBB.mjs.map +1 -0
  17. package/dist/{chunk-RD335EMA.mjs → chunk-VOVLPSG6.mjs} +29 -52
  18. package/dist/chunk-VOVLPSG6.mjs.map +1 -0
  19. package/dist/{chunk-DA5T64RY.mjs → chunk-ZVWUR7ET.mjs} +2 -2
  20. package/dist/cli.mjs +9 -7
  21. package/dist/contacts-worker.mjs +2 -2
  22. package/dist/global-insight-worker.mjs +1 -1
  23. package/dist/{http-3B5ARN7A.mjs → http-M6NHWJZD.mjs} +11 -11
  24. package/dist/{import-2DZGNSEX.mjs → import-SEYHFXS5.mjs} +9 -7
  25. package/dist/index.mjs +12 -12
  26. package/dist/{mcp-44I6FUUQ.mjs → mcp-MIPFYRLV.mjs} +8 -8
  27. package/dist/people-relationships-worker.mjs +2 -2
  28. package/dist/semantic-index-worker.mjs +1 -1
  29. package/dist/{src-R3QX6L3L.mjs → src-7OD6RD5V.mjs} +18 -6
  30. package/dist/{stream-import-VUEL2TTU.mjs → stream-import-AWEHGQJY.mjs} +9 -7
  31. package/dist/{worker-runtime-GJAOIIZW.mjs → worker-runtime-TGYMASIE.mjs} +5 -5
  32. package/dist-web/assets/{ActionToolsPanel.vue_vue_type_script_setup_true_lang-CdJT0t2f.js → ActionToolsPanel.vue_vue_type_script_setup_true_lang-CkDGVRXe.js} +4 -4
  33. package/dist-web/assets/{BufferResource-BPGkshPa.js → BufferResource-CHI62pIA.js} +1 -1
  34. package/dist-web/assets/{CanvasRenderer-DTKkZhq4.js → CanvasRenderer-OZRoiF0j.js} +1 -1
  35. package/dist-web/assets/{Filter-CHXKzS2l.js → Filter-DNb5VErW.js} +1 -1
  36. package/dist-web/assets/{MessageExportModal-h-zl0dx1.js → MessageExportModal-CqP-w7HP.js} +1 -1
  37. package/dist-web/assets/{PageHeader-CK0hTsdx.js → PageHeader-Ck8ULyZH.js} +1 -1
  38. package/dist-web/assets/{RelationshipGalaxyCanvas-Dwdy5flh.js → RelationshipGalaxyCanvas-ioRNObb5.js} +3 -3
  39. package/dist-web/assets/{RelationshipGalaxyThreeCanvas-CtY4TGen.js → RelationshipGalaxyThreeCanvas-L1Y43p1A.js} +1 -1
  40. package/dist-web/assets/{RenderTargetSystem-BE56mKRG.js → RenderTargetSystem-fJ7D7Chv.js} +1 -1
  41. package/dist-web/assets/{TimeSelect.vue_vue_type_script_setup_true_lang-27hMyAUE.js → TimeSelect.vue_vue_type_script_setup_true_lang-7SEaCxDL.js} +1 -1
  42. package/dist-web/assets/{WebGLRenderer-Dm18dxOf.js → WebGLRenderer-BD3ZUtxH.js} +1 -1
  43. package/dist-web/assets/{WebGPURenderer-BF6TF-is.js → WebGPURenderer-DZXYsT6D.js} +1 -1
  44. package/dist-web/assets/{base-CFPyD9Gy.js → base-DTElkJcC.js} +1 -1
  45. package/dist-web/assets/{browserAll-Cn_CfkXl.js → browserAll-ChT6N6a9.js} +1 -1
  46. package/dist-web/assets/{dateFormat-BcqNKTQ1.js → dateFormat-D9WibwOu.js} +1 -1
  47. package/dist-web/assets/{electron-B6xOabTq.js → electron-CIzyGxH_.js} +1 -1
  48. package/dist-web/assets/{fetch-C_EfVFJi.js → fetch-Avtnh8cJ.js} +1 -1
  49. package/dist-web/assets/{fetch-DL_u25Ui.js → fetch-BFnrBahy.js} +1 -1
  50. package/dist-web/assets/fetch-C9rx2isC.js +31 -0
  51. package/dist-web/assets/{fetch-BPkgnY0R.js → fetch-Ccq_0ecb.js} +1 -1
  52. package/dist-web/assets/{fetch-DMLq3xPG.js → fetch-CuiQUcO_.js} +1 -1
  53. package/dist-web/assets/{fetch-BRXmDI_2.js → fetch-CyKILLnB.js} +1 -1
  54. package/dist-web/assets/fetch-Dd6t3p1r.js +6 -0
  55. package/dist-web/assets/{fetch-DylpNKCR.js → fetch-DnGx-1_t.js} +1 -1
  56. package/dist-web/assets/{fetch-CEhUu1Yu.js → fetch-qC7om4QG.js} +1 -1
  57. package/dist-web/assets/{fetch-etgwVKIw.js → fetch-wSSfDBdV.js} +1 -1
  58. package/dist-web/assets/index-BH9jI5kV.js +173 -0
  59. package/dist-web/assets/index-BWtNqcYK.js +22 -0
  60. package/dist-web/assets/{index-FoTy2Cce.js → index-Bkq7-GBp.js} +1 -1
  61. package/dist-web/assets/index-Bqr-LkS1.css +1 -0
  62. package/dist-web/assets/{index-aMIgqj43.js → index-C6qtsaP7.js} +1 -1
  63. package/dist-web/assets/{index-zZVsV5HB.js → index-ChFGuu70.js} +1 -1
  64. package/dist-web/assets/{index-C3TnydSt.js → index-CiS20iAR.js} +1 -1
  65. package/dist-web/assets/{index-BS6loLHW.js → index-CqD6u57u.js} +1 -1
  66. package/dist-web/assets/{index-CAWO10k1.js → index-CzDG8LVr.js} +2 -2
  67. package/dist-web/assets/{index-BrTlAl8b.js → index-D0Euk-vQ.js} +1 -1
  68. package/dist-web/assets/{index-B5S9FnfP.js → index-D26BWhHz.js} +1 -1
  69. package/dist-web/assets/{index-0x1QPzKZ.js → index-DXHYzIIj.js} +1 -1
  70. package/dist-web/assets/{index-Chm17wNe.js → index-Dq8u7gMb.js} +2 -2
  71. package/dist-web/assets/{index-Em6upenM.js → index-u_UH6rDY.js} +3 -3
  72. package/dist-web/assets/{web-BUZ-dZy0.js → web-BDBdpdK7.js} +1 -1
  73. package/dist-web/assets/{webworkerAll-BKGP--A4.js → webworkerAll-C3dAOQj4.js} +1 -1
  74. package/dist-web/index.html +2 -2
  75. package/package.json +1 -1
  76. package/dist/chunk-DBBL74RK.mjs.map +0 -1
  77. package/dist/chunk-EXD2SXWL.mjs.map +0 -1
  78. package/dist/chunk-JH5RAJT2.mjs.map +0 -1
  79. package/dist/chunk-RD335EMA.mjs.map +0 -1
  80. package/dist/chunk-T64GA5HP.mjs.map +0 -1
  81. package/dist/chunk-UWWFOEGF.mjs.map +0 -1
  82. package/dist/chunk-YPQ72HMX.mjs.map +0 -1
  83. package/dist-web/assets/fetch-BUm1isWe.js +0 -5
  84. package/dist-web/assets/fetch-CBOJwwJp.js +0 -17
  85. package/dist-web/assets/index-DAr_2DlV.js +0 -173
  86. package/dist-web/assets/index-YADL4XIp.js +0 -22
  87. package/dist-web/assets/index-_6lck7Bv.css +0 -1
  88. /package/dist/{chat-command-CHV7RLBH.mjs.map → chat-command-MOETAGU5.mjs.map} +0 -0
  89. /package/dist/{chunk-SNCXU2QL.mjs.map → chunk-7HBVGVOB.mjs.map} +0 -0
  90. /package/dist/{chunk-WQCY45U2.mjs.map → chunk-E6W2RURS.mjs.map} +0 -0
  91. /package/dist/{chunk-RDCUGC24.mjs.map → chunk-LJXSUN25.mjs.map} +0 -0
  92. /package/dist/{chunk-DA5T64RY.mjs.map → chunk-ZVWUR7ET.mjs.map} +0 -0
  93. /package/dist/{http-3B5ARN7A.mjs.map → http-M6NHWJZD.mjs.map} +0 -0
  94. /package/dist/{import-2DZGNSEX.mjs.map → import-SEYHFXS5.mjs.map} +0 -0
  95. /package/dist/{mcp-44I6FUUQ.mjs.map → mcp-MIPFYRLV.mjs.map} +0 -0
  96. /package/dist/{src-R3QX6L3L.mjs.map → src-7OD6RD5V.mjs.map} +0 -0
  97. /package/dist/{stream-import-VUEL2TTU.mjs.map → stream-import-AWEHGQJY.mjs.map} +0 -0
  98. /package/dist/{worker-runtime-GJAOIIZW.mjs.map → worker-runtime-TGYMASIE.mjs.map} +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  createEmptyContactsDiagnostics,
10
10
  normalizeContactsTimeRangePreset,
11
11
  resolveContactsTimeRange
12
- } from "./chunk-SNCXU2QL.mjs";
12
+ } from "./chunk-7HBVGVOB.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-RDCUGC24.mjs";
21
+ } from "./chunk-LJXSUN25.mjs";
22
22
  import {
23
23
  ANNUAL_SUMMARY_ALGORITHM_VERSION
24
24
  } from "./chunk-YAQLYL52.mjs";
@@ -93,7 +93,7 @@ import {
93
93
  updateSessionOwnerId,
94
94
  writeAnnualSummarySnapshot,
95
95
  writeAuthProfile
96
- } from "./chunk-UWWFOEGF.mjs";
96
+ } from "./chunk-DQ45LDKZ.mjs";
97
97
  import {
98
98
  __commonJS,
99
99
  __export,
@@ -3881,6 +3881,9 @@ function extractNameFromFilePath3(filePath) {
3881
3881
  const basename13 = path5.basename(filePath);
3882
3882
  return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3883
3883
  }
3884
+ function isPlaceholderSenderId(id) {
3885
+ return id === "0" || id === "\u672A\u77E5";
3886
+ }
3884
3887
  function countSenders(content) {
3885
3888
  const sendersStart = content.indexOf('"senders"');
3886
3889
  if (sendersStart === -1) return 0;
@@ -3895,8 +3898,19 @@ function countSenders(content) {
3895
3898
  }
3896
3899
  if (depth !== 0) return 0;
3897
3900
  const sendersContent = content.slice(arrayStart + 1, i - 1);
3898
- const uidMatches = sendersContent.match(/"uid"\s*:/g);
3899
- return uidMatches ? uidMatches.length : 0;
3901
+ const uidMatches = sendersContent.matchAll(/"uid"\s*:\s*"([^"]*)"/g);
3902
+ let count = 0;
3903
+ for (const match of uidMatches) {
3904
+ const uid = match[1];
3905
+ if (uid && !isPlaceholderSenderId(uid)) count++;
3906
+ }
3907
+ return count;
3908
+ }
3909
+ function resolveChatType(chatInfoType, headContent) {
3910
+ if (chatInfoType === "group") return "group" /* GROUP */;
3911
+ const sendersCount = countSenders(headContent);
3912
+ if (chatInfoType === "private") return sendersCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
3913
+ return sendersCount > 2 ? "group" /* GROUP */ : sendersCount > 0 ? "private" /* PRIVATE */ : "group" /* GROUP */;
3900
3914
  }
3901
3915
  function convertMessageType(messageType, content, isRecalled) {
3902
3916
  if (isRecalled) return 81 /* RECALL */;
@@ -3950,8 +3964,7 @@ async function* parseV4(options2) {
3950
3964
  }
3951
3965
  } catch {
3952
3966
  }
3953
- const sendersCount = countSenders(headContent);
3954
- const chatType = sendersCount > 2 ? "group" /* GROUP */ : sendersCount > 0 ? "private" /* PRIVATE */ : "group" /* GROUP */;
3967
+ const chatType = resolveChatType(chatInfo.type, headContent);
3955
3968
  const meta = {
3956
3969
  name: chatInfo.name || extractNameFromFilePath3(filePath),
3957
3970
  platform: KNOWN_PLATFORMS.QQ,
@@ -3970,7 +3983,7 @@ async function* parseV4(options2) {
3970
3983
  const pipeline2 = chain3([readStream, parser4(), pick3({ filter: /^messages\.\d+$/ }), streamValues3()]);
3971
3984
  pipeline2.on("data", ({ value }) => {
3972
3985
  const platformId = value.sender.uin || value.sender.uid || value.rawMessage?.senderUin || value.rawMessage?.senderUid;
3973
- if (!platformId) {
3986
+ if (!platformId || isPlaceholderSenderId(platformId)) {
3974
3987
  skippedMessages++;
3975
3988
  return;
3976
3989
  }
@@ -3989,9 +4002,11 @@ async function* parseV4(options2) {
3989
4002
  skippedMessages++;
3990
4003
  return;
3991
4004
  }
3992
- const type = value.isSystemMessage ? 80 /* SYSTEM */ : convertMessageType(value.messageType, value.content, value.isRecalled);
4005
+ const isSystem = value.system ?? value.isSystemMessage;
4006
+ const isRecalled = value.recalled ?? value.isRecalled;
4007
+ const type = isSystem ? 80 /* SYSTEM */ : convertMessageType(value.messageType, value.content, isRecalled);
3993
4008
  let textContent = value.content?.text || "";
3994
- if (value.isRecalled) textContent = "[\u5DF2\u64A4\u56DE] " + textContent;
4009
+ if (isRecalled) textContent = "[\u5DF2\u64A4\u56DE] " + textContent;
3995
4010
  messageBatch.push({
3996
4011
  platformMessageId: value.messageId,
3997
4012
  // 消息的平台原始 ID
@@ -7890,8 +7905,8 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
7890
7905
  }
7891
7906
  return { success: true, sessionId, diagnostics };
7892
7907
  }
7893
- async function analyzeNewImport(filePath, onProgress) {
7894
- const formatFeature = detectFormat(filePath);
7908
+ async function analyzeNewImport(filePath, onProgress, options2) {
7909
+ const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
7895
7910
  if (!formatFeature) {
7896
7911
  return {
7897
7912
  totalMessages: 0,
@@ -7908,30 +7923,35 @@ async function analyzeNewImport(filePath, onProgress) {
7908
7923
  let totalMessages = 0;
7909
7924
  let newMessageCount = 0;
7910
7925
  let duplicateCount = 0;
7911
- await streamParseFile(filePath, {
7912
- onMeta: (parsedMeta) => {
7913
- meta = { name: parsedMeta.name, platform: parsedMeta.platform, type: parsedMeta.type };
7914
- },
7915
- onMembers: (members) => {
7916
- for (const m of members) memberSet.add(m.platformId);
7917
- },
7918
- onProgress: (progress) => {
7919
- onProgress(progress);
7920
- },
7921
- onMessageBatch: (batch) => {
7922
- for (const msg of batch) {
7923
- totalMessages++;
7924
- if (!memberSet.has(msg.senderPlatformId)) memberSet.add(msg.senderPlatformId);
7925
- const prepared = prepareMessageForCreate(msg);
7926
- if ("skipCounter" in prepared) continue;
7927
- if (registerMessageAndCheckDuplicate(prepared.message, dedupState)) {
7928
- duplicateCount++;
7929
- } else {
7930
- newMessageCount++;
7926
+ await streamParseFile(
7927
+ filePath,
7928
+ {
7929
+ formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
7930
+ onMeta: (parsedMeta) => {
7931
+ meta = { name: parsedMeta.name, platform: parsedMeta.platform, type: parsedMeta.type };
7932
+ },
7933
+ onMembers: (members) => {
7934
+ for (const m of members) memberSet.add(m.platformId);
7935
+ },
7936
+ onProgress: (progress) => {
7937
+ onProgress(progress);
7938
+ },
7939
+ onMessageBatch: (batch) => {
7940
+ for (const msg of batch) {
7941
+ totalMessages++;
7942
+ if (!memberSet.has(msg.senderPlatformId)) memberSet.add(msg.senderPlatformId);
7943
+ const prepared = prepareMessageForCreate(msg);
7944
+ if ("skipCounter" in prepared) continue;
7945
+ if (registerMessageAndCheckDuplicate(prepared.message, dedupState)) {
7946
+ duplicateCount++;
7947
+ } else {
7948
+ newMessageCount++;
7949
+ }
7931
7950
  }
7932
7951
  }
7933
- }
7934
- });
7952
+ },
7953
+ options2?.formatId
7954
+ );
7935
7955
  return { totalMessages, newMessageCount, duplicateCount, totalMembers: memberSet.size, meta };
7936
7956
  }
7937
7957
  async function streamParseFileInfo(filePath, deps) {
@@ -8128,8 +8148,8 @@ function normalizeImportTimestamp(timestamp) {
8128
8148
  const value = typeof timestamp === "string" && timestamp.trim() !== "" ? Number(timestamp) : timestamp;
8129
8149
  return typeof value === "number" && value > 0 && Number.isFinite(value) ? value : null;
8130
8150
  }
8131
- async function analyzeIncrementalImport(sessionId, filePath, deps) {
8132
- const formatFeature = detectFormat(filePath);
8151
+ async function analyzeIncrementalImport(sessionId, filePath, deps, options2) {
8152
+ const formatFeature = options2?.formatId ? getFormatFeatureById(options2.formatId) : detectFormat(filePath);
8133
8153
  if (!formatFeature) {
8134
8154
  return { error: "error.unrecognized_format", newMessageCount: 0, duplicateCount: 0, totalInFile: 0 };
8135
8155
  }
@@ -8144,27 +8164,32 @@ async function analyzeIncrementalImport(sessionId, filePath, deps) {
8144
8164
  let totalInFile = 0;
8145
8165
  let newMessageCount = 0;
8146
8166
  let duplicateCount = 0;
8147
- await streamParseFile(filePath, {
8148
- onMeta: () => {
8149
- },
8150
- onMembers: () => {
8151
- },
8152
- onProgress: (progress) => {
8153
- deps.onProgress(progress);
8154
- },
8155
- onMessageBatch: (batch) => {
8156
- for (const msg of batch) {
8157
- totalInFile++;
8158
- const timestamp = normalizeImportTimestamp(msg.timestamp);
8159
- if (timestamp === null) continue;
8160
- if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
8161
- duplicateCount++;
8162
- } else {
8163
- newMessageCount++;
8167
+ await streamParseFile(
8168
+ filePath,
8169
+ {
8170
+ formatOptions: options2?.chatIndex === void 0 ? void 0 : { chatIndex: options2.chatIndex },
8171
+ onMeta: () => {
8172
+ },
8173
+ onMembers: () => {
8174
+ },
8175
+ onProgress: (progress) => {
8176
+ deps.onProgress(progress);
8177
+ },
8178
+ onMessageBatch: (batch) => {
8179
+ for (const msg of batch) {
8180
+ totalInFile++;
8181
+ const timestamp = normalizeImportTimestamp(msg.timestamp);
8182
+ if (timestamp === null) continue;
8183
+ if (isDuplicate({ ...msg, timestamp }, existingPlatformMsgIds, existingKeys, existingFallbackOnlyKeys)) {
8184
+ duplicateCount++;
8185
+ } else {
8186
+ newMessageCount++;
8187
+ }
8164
8188
  }
8165
8189
  }
8166
- }
8167
- });
8190
+ },
8191
+ options2?.formatId
8192
+ );
8168
8193
  return { newMessageCount, duplicateCount, totalInFile };
8169
8194
  }
8170
8195
  async function incrementalImport(sessionId, filePath, deps, options2) {
@@ -8418,6 +8443,12 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
8418
8443
  }
8419
8444
  }
8420
8445
 
8446
+ // ../../packages/node-runtime/src/import/session-id.ts
8447
+ var SAFE_IMPORT_SESSION_ID_RE = /^[A-Za-z0-9_@-][A-Za-z0-9._@-]{0,127}$/;
8448
+ function isValidImportSessionId(sessionId) {
8449
+ return SAFE_IMPORT_SESSION_ID_RE.test(sessionId) && !sessionId.includes("..");
8450
+ }
8451
+
8421
8452
  // ../../packages/node-runtime/src/import/auto-import-matcher.ts
8422
8453
  var MATCH_WINDOW_SIZE = 5;
8423
8454
  function buildPrivateIdentity(ownerId, memberIds) {
@@ -8528,6 +8559,20 @@ async function resolveAutoImportTarget(filePath, deps, formatOptions) {
8528
8559
  }
8529
8560
 
8530
8561
  // ../../packages/node-runtime/src/import/auto-importer.ts
8562
+ async function planAutoImport(filePath, deps, options2) {
8563
+ if (options2.explicitSessionId) {
8564
+ if (!isValidImportSessionId(options2.explicitSessionId)) {
8565
+ throw new Error("sessionId contains invalid characters");
8566
+ }
8567
+ return deps.sessionExists(options2.explicitSessionId) ? { action: "incremental", sessionId: options2.explicitSessionId } : { action: "create", sessionId: options2.explicitSessionId };
8568
+ }
8569
+ const decision = await (deps.resolveTarget ?? resolveAutoImportTarget)(
8570
+ filePath,
8571
+ deps,
8572
+ options2.formatOptions
8573
+ );
8574
+ return decision.action === "incremental" ? { action: "incremental", sessionId: decision.sessionId, matchedBy: decision.matchedBy } : { action: "create", reason: decision.reason };
8575
+ }
8531
8576
  function mapCreateResult(result, createReason) {
8532
8577
  if (!result.success || !result.sessionId) {
8533
8578
  return { success: false, error: result.error, diagnostics: result.diagnostics };
@@ -8539,6 +8584,11 @@ function mapCreateResult(result, createReason) {
8539
8584
  ...createReason ? { createReason } : {},
8540
8585
  newMessageCount: result.diagnostics?.messagesWritten ?? 0,
8541
8586
  duplicateCount: result.diagnostics?.duplicateCount ?? 0,
8587
+ batch: result.diagnostics ? {
8588
+ receivedCount: result.diagnostics.messagesReceived,
8589
+ writtenCount: result.diagnostics.messagesWritten,
8590
+ duplicateCount: result.diagnostics.duplicateCount
8591
+ } : void 0,
8542
8592
  diagnostics: result.diagnostics
8543
8593
  };
8544
8594
  }
@@ -8550,61 +8600,41 @@ function mapIncrementalResult(sessionId, result, matchedBy) {
8550
8600
  importMode: "incremental",
8551
8601
  ...matchedBy ? { matchedBy } : {},
8552
8602
  newMessageCount: result.newMessageCount,
8553
- duplicateCount: result.batch?.duplicateCount ?? 0
8603
+ duplicateCount: result.batch?.duplicateCount ?? 0,
8604
+ batch: result.batch,
8605
+ session: result.session,
8606
+ updates: result.updates
8554
8607
  };
8555
8608
  }
8556
8609
  async function autoImportFile(filePath, deps, options2 = {}) {
8557
8610
  try {
8558
- if (options2.explicitSessionId) {
8559
- if (deps.sessionExists(options2.explicitSessionId)) {
8560
- const result3 = mapIncrementalResult(
8561
- options2.explicitSessionId,
8562
- await deps.appendSession(options2.explicitSessionId, filePath, options2.formatOptions)
8563
- );
8564
- appLogger.info("import", "Explicit incremental import completed", {
8565
- sessionId: options2.explicitSessionId,
8566
- success: result3.success,
8567
- newMessageCount: result3.newMessageCount,
8568
- duplicateCount: result3.duplicateCount
8569
- });
8570
- return result3;
8571
- }
8572
- const result2 = mapCreateResult(
8573
- await deps.createSession(filePath, options2.formatOptions, options2.explicitSessionId)
8574
- );
8575
- appLogger.info("import", "Explicit session import completed", {
8576
- sessionId: result2.sessionId,
8577
- success: result2.success,
8578
- importMode: result2.importMode
8611
+ if (!options2.explicitSessionId) {
8612
+ appLogger.info("import", "Automatic session matching started", {
8613
+ candidateCount: deps.listSessionIds().length
8579
8614
  });
8580
- return result2;
8581
8615
  }
8582
- appLogger.info("import", "Automatic session matching started", {
8583
- candidateCount: deps.listSessionIds().length
8584
- });
8585
- const decision = await (deps.resolveTarget ?? resolveAutoImportTarget)(
8586
- filePath,
8587
- deps,
8588
- options2.formatOptions
8589
- );
8590
- if (decision.action === "incremental") {
8616
+ const plan = await planAutoImport(filePath, deps, options2);
8617
+ if (plan.action === "incremental") {
8591
8618
  const result2 = mapIncrementalResult(
8592
- decision.sessionId,
8593
- await deps.appendSession(decision.sessionId, filePath, options2.formatOptions),
8594
- decision.matchedBy
8619
+ plan.sessionId,
8620
+ await deps.appendSession(plan.sessionId, filePath, options2.formatOptions),
8621
+ plan.matchedBy
8595
8622
  );
8596
- appLogger.info("import", "Automatic incremental import completed", {
8597
- sessionId: decision.sessionId,
8598
- matchedBy: decision.matchedBy,
8623
+ appLogger.info("import", "Incremental import completed", {
8624
+ sessionId: plan.sessionId,
8625
+ matchedBy: plan.matchedBy,
8599
8626
  success: result2.success,
8600
8627
  newMessageCount: result2.newMessageCount,
8601
8628
  duplicateCount: result2.duplicateCount
8602
8629
  });
8603
8630
  return result2;
8604
8631
  }
8605
- const result = mapCreateResult(await deps.createSession(filePath, options2.formatOptions), decision.reason);
8606
- appLogger.info("import", "Automatic import created a new session", {
8607
- reason: decision.reason,
8632
+ const result = mapCreateResult(
8633
+ await deps.createSession(filePath, options2.formatOptions, plan.sessionId),
8634
+ plan.reason
8635
+ );
8636
+ appLogger.info("import", "Import created a new session", {
8637
+ reason: plan.reason,
8608
8638
  sessionId: result.sessionId,
8609
8639
  success: result.success
8610
8640
  });
@@ -8614,6 +8644,53 @@ async function autoImportFile(filePath, deps, options2 = {}) {
8614
8644
  return { success: false, error: error instanceof Error ? error.message : String(error) };
8615
8645
  }
8616
8646
  }
8647
+ async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
8648
+ try {
8649
+ const plan = await planAutoImport(filePath, deps, options2);
8650
+ if (plan.action === "incremental") {
8651
+ const analysis2 = await deps.analyzeAppendSession(plan.sessionId, filePath, options2.formatOptions);
8652
+ if (analysis2.error) return { success: false, error: analysis2.error };
8653
+ const result2 = {
8654
+ success: true,
8655
+ importMode: "incremental",
8656
+ sessionId: plan.sessionId,
8657
+ ...plan.matchedBy ? { matchedBy: plan.matchedBy } : {},
8658
+ totalMessageCount: analysis2.totalInFile,
8659
+ newMessageCount: analysis2.newMessageCount,
8660
+ duplicateCount: analysis2.duplicateCount
8661
+ };
8662
+ appLogger.info("import", "Incremental import analysis completed", {
8663
+ sessionId: plan.sessionId,
8664
+ matchedBy: plan.matchedBy,
8665
+ newMessageCount: analysis2.newMessageCount,
8666
+ duplicateCount: analysis2.duplicateCount
8667
+ });
8668
+ return result2;
8669
+ }
8670
+ const analysis = await deps.analyzeCreateSession(filePath, options2.formatOptions);
8671
+ if (analysis.error) return { success: false, error: analysis.error };
8672
+ const result = {
8673
+ success: true,
8674
+ importMode: "created",
8675
+ ...plan.sessionId ? { sessionId: plan.sessionId } : {},
8676
+ ...plan.reason ? { createReason: plan.reason } : {},
8677
+ totalMessageCount: analysis.totalMessages,
8678
+ newMessageCount: analysis.newMessageCount,
8679
+ duplicateCount: analysis.duplicateCount,
8680
+ totalMemberCount: analysis.totalMembers,
8681
+ meta: analysis.meta
8682
+ };
8683
+ appLogger.info("import", "New session import analysis completed", {
8684
+ reason: plan.reason,
8685
+ messageCount: analysis.totalMessages,
8686
+ memberCount: analysis.totalMembers
8687
+ });
8688
+ return result;
8689
+ } catch (error) {
8690
+ appLogger.error("import", "Automatic import analysis failed", error);
8691
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
8692
+ }
8693
+ }
8617
8694
 
8618
8695
  // ../../packages/node-runtime/src/import/import-lock.ts
8619
8696
  import * as fs21 from "fs";
@@ -20085,17 +20162,41 @@ var MergeSessionCache = class {
20085
20162
  import * as fs34 from "fs";
20086
20163
  var SYSTEM_SENDER_ID = "SYSTEM";
20087
20164
  var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
20165
+ function isRecord3(value) {
20166
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20167
+ }
20168
+ function validateOptionalString(value, field) {
20169
+ return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
20170
+ }
20088
20171
  function validatePayload(payload, isNew) {
20172
+ if (!isRecord3(payload)) return "payload must be an object";
20089
20173
  const messages = payload.messages;
20090
20174
  if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
20175
+ if (payload.chatlab !== void 0) {
20176
+ if (!isRecord3(payload.chatlab)) return "chatlab must be an object";
20177
+ if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
20178
+ return "chatlab.version must be a string";
20179
+ if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
20180
+ return "chatlab.exportedAt must be a number";
20181
+ const generatorError = validateOptionalString(payload.chatlab.generator, "chatlab.generator");
20182
+ if (generatorError) return generatorError;
20183
+ }
20091
20184
  if (isNew) {
20092
20185
  if (!payload.chatlab) return "chatlab is required for new sessions";
20093
20186
  if (!payload.meta) return "meta is required for new sessions";
20094
- const m = payload.meta;
20095
- if (!m.name) return "meta.name is required";
20096
- if (!m.platform) return "meta.platform is required";
20097
- if (!m.type) return "meta.type is required";
20098
20187
  }
20188
+ if (payload.meta !== void 0) {
20189
+ if (!isRecord3(payload.meta)) return "meta must be an object";
20190
+ const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
20191
+ for (const field of metaFields) {
20192
+ const error = validateOptionalString(payload.meta[field], `meta.${field}`);
20193
+ if (error) return error;
20194
+ }
20195
+ if (isNew && !payload.meta.name) return "meta.name is required";
20196
+ if (isNew && !payload.meta.platform) return "meta.platform is required";
20197
+ if (isNew && !payload.meta.type) return "meta.type is required";
20198
+ }
20199
+ if (payload.options !== void 0 && !isRecord3(payload.options)) return "options must be an object";
20099
20200
  const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
20100
20201
  if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
20101
20202
  return `options.metaUpdateMode must be 'patch' or 'none'`;
@@ -20106,16 +20207,35 @@ function validatePayload(payload, isNew) {
20106
20207
  if (payload.members !== void 0 && !Array.isArray(payload.members)) return "members must be an array";
20107
20208
  if (payload.members) {
20108
20209
  for (let i = 0; i < payload.members.length; i++) {
20109
- if (typeof payload.members[i].platformId !== "string" || payload.members[i].platformId.length === 0)
20210
+ const member = payload.members[i];
20211
+ if (!isRecord3(member)) return `members[${i}] must be an object`;
20212
+ if (typeof member.platformId !== "string" || member.platformId.length === 0)
20110
20213
  return `members[${i}].platformId must be a string`;
20214
+ for (const field of ["accountName", "groupNickname", "avatar"]) {
20215
+ const error = validateOptionalString(member[field], `members[${i}].${field}`);
20216
+ if (error) return error;
20217
+ }
20218
+ if (member.roles !== void 0) {
20219
+ if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
20220
+ for (let j = 0; j < member.roles.length; j++) {
20221
+ const role = member.roles[j];
20222
+ if (!isRecord3(role) || typeof role.id !== "string" || role.id.length === 0)
20223
+ return `members[${i}].roles[${j}].id must be a string`;
20224
+ }
20225
+ }
20111
20226
  }
20112
20227
  }
20113
20228
  for (let i = 0; i < messages.length; i++) {
20114
20229
  const msg = messages[i];
20230
+ if (!isRecord3(msg)) return `messages[${i}] must be an object`;
20115
20231
  if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
20116
- if (typeof msg.timestamp !== "number" || msg.timestamp <= 0)
20232
+ if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
20117
20233
  return `messages[${i}].timestamp must be a positive number`;
20118
- if (typeof msg.type !== "number") return `messages[${i}].type must be a number`;
20234
+ if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
20235
+ for (const field of ["accountName", "groupNickname"]) {
20236
+ const error = validateOptionalString(msg[field], `messages[${i}].${field}`);
20237
+ if (error) return error;
20238
+ }
20119
20239
  if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
20120
20240
  return `messages[${i}].content must be a string or null`;
20121
20241
  if (msg.platformMessageId !== void 0 && typeof msg.platformMessageId !== "string")
@@ -20136,47 +20256,79 @@ function normalizeSenderAccountName(sender, accountName) {
20136
20256
  function normalizeSenderGroupNickname(sender, groupNickname) {
20137
20257
  return sender === SYSTEM_SENDER_ID ? SYSTEM_MEMBER_NAME : groupNickname;
20138
20258
  }
20139
- function writeMessages(db, messages, existingPmids, existingKeys) {
20259
+ function isCountedMember(platformId) {
20260
+ return platformId !== SYSTEM_SENDER_ID;
20261
+ }
20262
+ function registerPushMessageAndCheckDuplicate(message, dedupState) {
20263
+ return registerMessageAndCheckDuplicate(
20264
+ {
20265
+ platformMessageId: message.platformMessageId,
20266
+ timestamp: message.timestamp,
20267
+ senderPlatformId: message.sender,
20268
+ type: message.type,
20269
+ content: message.content ?? null,
20270
+ replyToMessageId: message.replyToMessageId
20271
+ },
20272
+ dedupState
20273
+ );
20274
+ }
20275
+ function createExistingMessageDedupState(db) {
20276
+ const existingPmids = /* @__PURE__ */ new Set();
20277
+ const existingKeys = /* @__PURE__ */ new Set();
20278
+ const existingFallbackOnlyKeys = /* @__PURE__ */ new Set();
20279
+ const existingMessages = db.prepare(
20280
+ `SELECT msg.ts, m.platform_id, msg.type, msg.content, msg.reply_to_message_id, msg.platform_message_id
20281
+ FROM message msg
20282
+ JOIN member m ON msg.sender_id = m.id`
20283
+ ).all();
20284
+ for (const message of existingMessages) {
20285
+ if (message.platform_message_id) existingPmids.add(message.platform_message_id);
20286
+ const key = generateFallbackMessageKey({
20287
+ timestamp: message.ts,
20288
+ senderPlatformId: message.platform_id,
20289
+ type: message.type,
20290
+ content: message.content,
20291
+ replyToMessageId: message.reply_to_message_id ?? void 0
20292
+ });
20293
+ existingKeys.add(key);
20294
+ if (!message.platform_message_id) existingFallbackOnlyKeys.add(key);
20295
+ }
20296
+ return createMessageDedupState(existingPmids, existingKeys, existingFallbackOnlyKeys);
20297
+ }
20298
+ function writeMessages(db, messages, dedupState) {
20140
20299
  const insertMsg = db.prepare(
20141
20300
  `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)
20142
20301
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
20143
20302
  );
20144
20303
  const getMemberId = db.prepare("SELECT id FROM member WHERE platform_id = ?");
20145
- const insertMinimalMember = db.prepare("INSERT OR IGNORE INTO member (platform_id, account_name) VALUES (?, ?)");
20304
+ const insertMinimalMember = db.prepare(
20305
+ "INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname) VALUES (?, ?, ?)"
20306
+ );
20146
20307
  const memberIdCache = /* @__PURE__ */ new Map();
20147
20308
  let writtenCount = 0;
20148
20309
  let duplicateCount = 0;
20310
+ let membersAdded = 0;
20149
20311
  let minWrittenTs = Infinity;
20150
20312
  const ftsEntries = [];
20151
20313
  const sorted = [...messages].sort((a, b) => a.timestamp - b.timestamp);
20152
20314
  db.transaction(() => {
20153
20315
  for (const msg of sorted) {
20154
- const key = generateMessageKey(msg.timestamp, msg.sender, msg.content ?? null);
20155
- if (msg.platformMessageId) {
20156
- if (existingPmids.has(msg.platformMessageId)) {
20157
- duplicateCount++;
20158
- continue;
20159
- }
20160
- if (existingKeys.has(key)) {
20161
- duplicateCount++;
20162
- continue;
20163
- }
20164
- existingPmids.add(msg.platformMessageId);
20165
- existingKeys.add(key);
20166
- } else {
20167
- if (existingKeys.has(key)) {
20168
- duplicateCount++;
20169
- continue;
20170
- }
20171
- existingKeys.add(key);
20316
+ if (registerPushMessageAndCheckDuplicate(msg, dedupState)) {
20317
+ duplicateCount++;
20318
+ continue;
20172
20319
  }
20173
20320
  let memberId = memberIdCache.get(msg.sender);
20174
20321
  if (!memberId) {
20175
- insertMinimalMember.run(msg.sender, normalizeSenderAccountName(msg.sender, msg.accountName) || null);
20322
+ const insertResult = insertMinimalMember.run(
20323
+ msg.sender,
20324
+ normalizeSenderAccountName(msg.sender, msg.accountName) || null,
20325
+ normalizeSenderGroupNickname(msg.sender, msg.groupNickname) || null
20326
+ );
20176
20327
  const row = getMemberId.get(msg.sender);
20177
20328
  if (row) {
20178
20329
  memberId = row.id;
20179
20330
  memberIdCache.set(msg.sender, memberId);
20331
+ if (isCountedMember(msg.sender)) membersAdded += insertResult.changes;
20180
20332
  }
20181
20333
  }
20182
20334
  if (!memberId) continue;
@@ -20195,7 +20347,7 @@ function writeMessages(db, messages, existingPmids, existingKeys) {
20195
20347
  writtenCount++;
20196
20348
  }
20197
20349
  });
20198
- return { writtenCount, duplicateCount, minWrittenTs, ftsEntries };
20350
+ return { writtenCount, duplicateCount, membersAdded, minWrittenTs, ftsEntries };
20199
20351
  }
20200
20352
  function fullImport(db, meta, members, messages) {
20201
20353
  db.exec(CHAT_DB_SCHEMA);
@@ -20212,29 +20364,13 @@ function fullImport(db, meta, members, messages) {
20212
20364
  }
20213
20365
  }
20214
20366
  const allMembers = extraMembers.length > 0 ? [...members, ...extraMembers] : members;
20215
- const pmids = /* @__PURE__ */ new Set();
20216
- const hashKeys = /* @__PURE__ */ new Set();
20367
+ const dedupState = createMessageDedupState();
20217
20368
  let duplicateCount = 0;
20218
20369
  const dedupedMessages = [];
20219
20370
  for (const msg of messages) {
20220
- const key = generateMessageKey(msg.timestamp, msg.sender, msg.content ?? null);
20221
- if (msg.platformMessageId) {
20222
- if (pmids.has(msg.platformMessageId)) {
20223
- duplicateCount++;
20224
- continue;
20225
- }
20226
- if (hashKeys.has(key)) {
20227
- duplicateCount++;
20228
- continue;
20229
- }
20230
- pmids.add(msg.platformMessageId);
20231
- hashKeys.add(key);
20232
- } else {
20233
- if (hashKeys.has(key)) {
20234
- duplicateCount++;
20235
- continue;
20236
- }
20237
- hashKeys.add(key);
20371
+ if (registerPushMessageAndCheckDuplicate(msg, dedupState)) {
20372
+ duplicateCount++;
20373
+ continue;
20238
20374
  }
20239
20375
  dedupedMessages.push(msg);
20240
20376
  }
@@ -20262,15 +20398,12 @@ function fullImport(db, meta, members, messages) {
20262
20398
  generateSessionIndex(db);
20263
20399
  } catch {
20264
20400
  }
20265
- return { writtenCount: stats.messageCount, duplicateCount, membersAdded: members.length };
20401
+ return { writtenCount: stats.messageCount, duplicateCount };
20266
20402
  }
20267
- function incrementalImport2(db, payload) {
20403
+ function writeIncrementalImport(db, payload) {
20268
20404
  const metaUpdateMode = payload.options?.metaUpdateMode ?? "patch";
20269
20405
  const memberUpdateMode = payload.options?.memberUpdateMode ?? "upsert";
20270
- const existingPmids = /* @__PURE__ */ new Set();
20271
- const existingKeys = /* @__PURE__ */ new Set();
20272
- db.prepare("SELECT platform_message_id FROM message WHERE platform_message_id IS NOT NULL").all().forEach((r) => existingPmids.add(r.platform_message_id));
20273
- db.prepare(`SELECT msg.ts, m.platform_id, msg.content FROM message msg JOIN member m ON msg.sender_id = m.id`).all().forEach((r) => existingKeys.add(generateMessageKey(r.ts, r.platform_id, r.content)));
20406
+ const dedupState = createExistingMessageDedupState(db);
20274
20407
  let metaUpdated = false;
20275
20408
  let membersAdded = 0;
20276
20409
  let membersUpdated = 0;
@@ -20307,22 +20440,23 @@ function incrementalImport2(db, payload) {
20307
20440
  m.roles ? JSON.stringify(m.roles) : "[]"
20308
20441
  );
20309
20442
  if (!existed) {
20310
- membersAdded++;
20443
+ if (isCountedMember(m.platformId)) membersAdded++;
20311
20444
  const row = getMemberId.get(m.platformId);
20312
20445
  if (row) existingMemberIds.add(m.platformId);
20313
- } else {
20446
+ } else if (isCountedMember(m.platformId)) {
20314
20447
  membersUpdated++;
20315
20448
  }
20316
20449
  }
20317
20450
  });
20318
20451
  }
20319
20452
  const preWriteMaxTs = db.prepare("SELECT MAX(ts) as max_ts FROM message").get()?.max_ts ?? 0;
20320
- const { writtenCount, duplicateCount, minWrittenTs, ftsEntries } = writeMessages(
20321
- db,
20322
- payload.messages,
20323
- existingPmids,
20324
- existingKeys
20325
- );
20453
+ const {
20454
+ writtenCount,
20455
+ duplicateCount,
20456
+ membersAdded: autoCreatedMembersAdded,
20457
+ minWrittenTs,
20458
+ ftsEntries
20459
+ } = writeMessages(db, payload.messages, dedupState);
20326
20460
  if (ftsEntries.length > 0) {
20327
20461
  try {
20328
20462
  insertFtsEntries(db, ftsEntries);
@@ -20342,18 +20476,40 @@ function incrementalImport2(db, payload) {
20342
20476
  if (!metaUpdated) {
20343
20477
  db.prepare("UPDATE meta SET imported_at = ?").run(Math.floor(Date.now() / 1e3));
20344
20478
  }
20345
- return { writtenCount, duplicateCount, metaUpdated, membersAdded, membersUpdated };
20479
+ return {
20480
+ writtenCount,
20481
+ duplicateCount,
20482
+ metaUpdated,
20483
+ membersAdded: membersAdded + autoCreatedMembersAdded,
20484
+ membersUpdated
20485
+ };
20486
+ }
20487
+ function incrementalImport2(db, payload) {
20488
+ return db.transaction(() => writeIncrementalImport(db, payload));
20346
20489
  }
20347
- var SAFE_SESSION_ID_RE = /^[a-zA-Z0-9_@-][a-zA-Z0-9_@.-]*$/;
20348
20490
  async function pushImport(dbManager, sessionId, payload) {
20349
- if (!SAFE_SESSION_ID_RE.test(sessionId) || sessionId.includes("..")) {
20350
- return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
20351
- }
20352
20491
  try {
20353
- return await withDataDirImportLock(
20354
- dbManager.getUserDataDir(),
20355
- () => pushImportUnlocked(dbManager, sessionId, payload)
20356
- );
20492
+ return await withDataDirImportLock(dbManager.getUserDataDir(), async () => {
20493
+ const outcome = await executePushImportUnlocked(
20494
+ {
20495
+ getDbPath: (id) => dbManager.getDbPath(id),
20496
+ openDatabase: (id, options2) => dbManager.openRawSessionDatabase(id, options2),
20497
+ deleteDatabase: (id) => {
20498
+ dbManager.deleteSessionDatabaseFiles(id);
20499
+ }
20500
+ },
20501
+ sessionId,
20502
+ payload
20503
+ );
20504
+ if (!outcome.ok) return outcome;
20505
+ try {
20506
+ dbManager.raiseCurrentChatDbCompatibilityGate();
20507
+ } catch (error) {
20508
+ if (outcome.result.created) dbManager.deleteSessionDatabaseFiles(sessionId);
20509
+ throw error;
20510
+ }
20511
+ return outcome;
20512
+ });
20357
20513
  } catch (error) {
20358
20514
  if (error instanceof ImportInProgressError) {
20359
20515
  return {
@@ -20365,8 +20521,11 @@ async function pushImport(dbManager, sessionId, payload) {
20365
20521
  throw error;
20366
20522
  }
20367
20523
  }
20368
- async function pushImportUnlocked(dbManager, sessionId, payload) {
20369
- const dbPath = dbManager.getDbPath(sessionId);
20524
+ async function executePushImportUnlocked(deps, sessionId, payload) {
20525
+ if (!isValidImportSessionId(sessionId)) {
20526
+ return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
20527
+ }
20528
+ const dbPath = deps.getDbPath(sessionId);
20370
20529
  const isNew = !fs34.existsSync(dbPath);
20371
20530
  const validationError = validatePayload(payload, isNew);
20372
20531
  if (validationError) {
@@ -20374,17 +20533,12 @@ async function pushImportUnlocked(dbManager, sessionId, payload) {
20374
20533
  }
20375
20534
  try {
20376
20535
  if (isNew) {
20377
- const db2 = dbManager.openRawSessionDatabase(sessionId, { create: true });
20536
+ const db2 = deps.openDatabase(sessionId, { create: true });
20378
20537
  try {
20379
- const { writtenCount, duplicateCount, membersAdded } = fullImport(
20380
- db2,
20381
- payload.meta,
20382
- payload.members ?? [],
20383
- payload.messages
20384
- );
20538
+ const { writtenCount, duplicateCount } = fullImport(db2, payload.meta, payload.members ?? [], payload.messages);
20385
20539
  const session = queryStats(db2);
20386
- dbManager.raiseCurrentChatDbCompatibilityGate();
20387
- return {
20540
+ const membersAdded = session.memberCount;
20541
+ const outcome = {
20388
20542
  ok: true,
20389
20543
  result: {
20390
20544
  sessionId,
@@ -20394,16 +20548,17 @@ async function pushImportUnlocked(dbManager, sessionId, payload) {
20394
20548
  updates: { metaUpdated: true, membersAdded, membersUpdated: 0 }
20395
20549
  }
20396
20550
  };
20551
+ appLogger.info("push-import", "Push import completed", outcome.result);
20552
+ return outcome;
20397
20553
  } finally {
20398
20554
  db2.close();
20399
20555
  }
20400
20556
  }
20401
- const db = dbManager.openRawSessionDatabase(sessionId, { readonly: false });
20557
+ const db = deps.openDatabase(sessionId, { readonly: false });
20402
20558
  try {
20403
20559
  const { writtenCount, duplicateCount, metaUpdated, membersAdded, membersUpdated } = incrementalImport2(db, payload);
20404
20560
  const session = queryStats(db);
20405
- dbManager.raiseCurrentChatDbCompatibilityGate();
20406
- return {
20561
+ const outcome = {
20407
20562
  ok: true,
20408
20563
  result: {
20409
20564
  sessionId,
@@ -20413,6 +20568,8 @@ async function pushImportUnlocked(dbManager, sessionId, payload) {
20413
20568
  updates: { metaUpdated, membersAdded, membersUpdated }
20414
20569
  }
20415
20570
  };
20571
+ appLogger.info("push-import", "Push import completed", outcome.result);
20572
+ return outcome;
20416
20573
  } finally {
20417
20574
  db.close();
20418
20575
  }
@@ -20420,14 +20577,54 @@ async function pushImportUnlocked(dbManager, sessionId, payload) {
20420
20577
  if (err instanceof DataDirCompatibilityError) throw err;
20421
20578
  if (isNew) {
20422
20579
  try {
20423
- dbManager.deleteSessionDatabaseFiles(sessionId);
20580
+ deps.deleteDatabase(sessionId);
20424
20581
  } catch {
20425
20582
  }
20426
20583
  }
20584
+ appLogger.error("push-import", "Push import failed", err);
20427
20585
  return { ok: false, reason: "import_failed", message: err instanceof Error ? err.message : String(err) };
20428
20586
  }
20429
20587
  }
20430
20588
 
20589
+ // ../../packages/node-runtime/src/services/import-idempotency.ts
20590
+ import { createHash as createHash4 } from "crypto";
20591
+ var DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS = 60 * 60 * 1e3;
20592
+ function hashImportBody(body) {
20593
+ return createHash4("sha256").update(JSON.stringify(body ?? "")).digest("hex");
20594
+ }
20595
+ var ImportIdempotencyCache = class {
20596
+ constructor(ttlMs = DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS) {
20597
+ this.ttlMs = ttlMs;
20598
+ }
20599
+ ttlMs;
20600
+ entries = /* @__PURE__ */ new Map();
20601
+ start(key, bodyHash, now = Date.now()) {
20602
+ this.prune(now);
20603
+ const entry = this.entries.get(key);
20604
+ if (!entry) {
20605
+ this.entries.set(key, { bodyHash, status: "pending", timestamp: now });
20606
+ return { status: "started" };
20607
+ }
20608
+ if (entry.bodyHash !== bodyHash) return { status: "conflict" };
20609
+ if (entry.status === "pending") return { status: "pending" };
20610
+ return { status: "success", response: entry.response };
20611
+ }
20612
+ success(key, response) {
20613
+ const entry = this.entries.get(key);
20614
+ if (!entry) return;
20615
+ entry.status = "success";
20616
+ entry.response = response;
20617
+ }
20618
+ fail(key) {
20619
+ this.entries.delete(key);
20620
+ }
20621
+ prune(now) {
20622
+ for (const [key, entry] of this.entries) {
20623
+ if (now - entry.timestamp > this.ttlMs) this.entries.delete(key);
20624
+ }
20625
+ }
20626
+ };
20627
+
20431
20628
  export {
20432
20629
  writeParseResultToDb,
20433
20630
  detectFormat,
@@ -20466,8 +20663,10 @@ export {
20466
20663
  streamParseFileInfo,
20467
20664
  analyzeIncrementalImport,
20468
20665
  incrementalImport,
20666
+ isValidImportSessionId,
20469
20667
  resolveAutoImportTarget,
20470
20668
  autoImportFile,
20669
+ analyzeAutoImportFile,
20471
20670
  IMPORT_LOCK_FILENAME,
20472
20671
  IMPORT_IN_PROGRESS_ERROR_KEY,
20473
20672
  ImportInProgressError,
@@ -20636,6 +20835,10 @@ export {
20636
20835
  createSemanticIndexService,
20637
20836
  SemanticIndexService,
20638
20837
  MergeSessionCache,
20639
- pushImport
20838
+ pushImport,
20839
+ executePushImportUnlocked,
20840
+ DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS,
20841
+ hashImportBody,
20842
+ ImportIdempotencyCache
20640
20843
  };
20641
- //# sourceMappingURL=chunk-T64GA5HP.mjs.map
20844
+ //# sourceMappingURL=chunk-KCPGEILH.mjs.map