chatlab-cli 0.34.2 → 0.35.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 (176) hide show
  1. package/dist/{chat-command-6IOH2QRF.mjs → chat-command-24RYGJAS.mjs} +8 -8
  2. package/dist/{chunk-INBJ7SND.mjs → chunk-22YJOBA3.mjs} +589 -513
  3. package/dist/chunk-22YJOBA3.mjs.map +1 -0
  4. package/dist/{chunk-4LW6443F.mjs → chunk-4GWWKHWA.mjs} +833 -886
  5. package/dist/chunk-4GWWKHWA.mjs.map +1 -0
  6. package/dist/{chunk-MENKFEPC.mjs → chunk-7IGBPZMI.mjs} +56 -48
  7. package/dist/chunk-7IGBPZMI.mjs.map +1 -0
  8. package/dist/{chunk-72RWABYJ.mjs → chunk-BRZ6YIBR.mjs} +3 -3
  9. package/dist/chunk-BRZ6YIBR.mjs.map +1 -0
  10. package/dist/{chunk-2XCNTG4O.mjs → chunk-DC7IUDNV.mjs} +677 -69
  11. package/dist/chunk-DC7IUDNV.mjs.map +1 -0
  12. package/dist/{chunk-H6D4DZOC.mjs → chunk-DEFEU567.mjs} +12 -18
  13. package/dist/chunk-DEFEU567.mjs.map +1 -0
  14. package/dist/{chunk-4RLZFGO5.mjs → chunk-GQMCDWZQ.mjs} +2 -2
  15. package/dist/{chunk-52P5ZRBD.mjs → chunk-KIMJXKZO.mjs} +2 -2
  16. package/dist/{chunk-652KEAX3.mjs → chunk-KXDJ7N6N.mjs} +2 -2
  17. package/dist/{chunk-U6MVJSXX.mjs → chunk-QKEARRFH.mjs} +15 -9
  18. package/dist/chunk-QKEARRFH.mjs.map +1 -0
  19. package/dist/{chunk-UMBEHBIZ.mjs → chunk-U36TQMAF.mjs} +2 -2
  20. package/dist/{chunk-JX6MFS6F.mjs → chunk-ZBPPLXM4.mjs} +15 -15
  21. package/dist/cli.mjs +9 -9
  22. package/dist/contacts-worker.mjs +2 -2
  23. package/dist/global-insight-worker.mjs +1 -1
  24. package/dist/{http-HSK46XEU.mjs → http-3K4YMWDE.mjs} +12 -12
  25. package/dist/{import-DF2WD2HE.mjs → import-D4LV565A.mjs} +7 -7
  26. package/dist/index.mjs +12 -12
  27. package/dist/{mcp-A2RND7KN.mjs → mcp-KL22FKQ2.mjs} +8 -8
  28. package/dist/people-relationships-worker.mjs +2 -2
  29. package/dist/semantic-index-worker.mjs +1 -1
  30. package/dist/{src-SHFHW2QB.mjs → src-COIB6Z4P.mjs} +24 -38
  31. package/dist/{stream-import-GZ2QDE3Z.mjs → stream-import-FSXCFG4M.mjs} +7 -7
  32. package/dist/{worker-runtime-OWN56GG5.mjs → worker-runtime-DPS2LURI.mjs} +5 -5
  33. package/dist-cli-web/assets/AISettingsTab-rQC2ocFH.js +1 -0
  34. package/dist-cli-web/assets/AboutTab-DMPziKjj.js +1 -0
  35. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-CnRLt_1j.js +110 -0
  36. package/dist-cli-web/assets/Alert--bTQXRcZ.js +1 -0
  37. package/dist-cli-web/assets/ApiSettingsTab-CtXl4I28.js +2 -0
  38. package/dist-cli-web/assets/BatchManageTab-1KaCPweN.js +1 -0
  39. package/dist-cli-web/assets/{BufferResource-BdKXeRZS.js → BufferResource-BXSJADxx.js} +1 -1
  40. package/dist-cli-web/assets/CanvasRenderer-Cq--8UQ_.js +1 -0
  41. package/dist-cli-web/assets/ChatRecordDrawer-BC0F7ok1.js +11 -0
  42. package/dist-cli-web/assets/ChatRecordDrawer-Tt6UduRC.css +1 -0
  43. package/dist-cli-web/assets/Checkbox-CwajNPGF.js +1 -0
  44. package/dist-cli-web/assets/DataDirCleanupNotice-D0Oruka2.js +1 -0
  45. package/dist-cli-web/assets/DatePicker.vue_vue_type_script_setup_true_lang-C4CsoIlq.js +1 -0
  46. package/dist-cli-web/assets/DebugToolsPanel-DVDwS-wz.js +1 -0
  47. package/dist-cli-web/assets/EChartWordcloud-KV0MdsDE.js +1 -0
  48. package/dist-cli-web/assets/{Filter-B2eEAPxI.js → Filter-DVaqcFmI.js} +1 -1
  49. package/dist-cli-web/assets/GlobalTaskBar-COW_uBX6.css +1 -0
  50. package/dist-cli-web/assets/GlobalTaskBar-DpPMX0jx.js +1 -0
  51. package/dist-cli-web/assets/GroupRelationshipGalaxyView-CLnO3phW.js +1 -0
  52. package/dist-cli-web/assets/Label-Bt2U4lGy.js +1 -0
  53. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-Cnshyn0f.js +1 -0
  54. package/dist-cli-web/assets/MessageExportModal-BozaR9UZ.js +1 -0
  55. package/dist-cli-web/assets/PageHeader-CEe0xTl3.js +1 -0
  56. package/dist-cli-web/assets/PageTabs.vue_vue_type_script_setup_true_lang-D2DZrrnv.js +1 -0
  57. package/dist-cli-web/assets/{RelationshipGalaxyCanvas-CwQ63TPi.js → RelationshipGalaxyCanvas-6EamagxG.js} +3 -3
  58. package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-BLqISQe5.js → RelationshipGalaxyThreeCanvas-B6GB4PWS.js} +166 -166
  59. package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-CMuibfiA.css → RelationshipGalaxyThreeCanvas-TYGOUDiv.css} +1 -1
  60. package/dist-cli-web/assets/{RenderTargetSystem-eC_VrDAr.js → RenderTargetSystem-mYuj4uBS.js} +1 -1
  61. package/dist-cli-web/assets/RovingFocusItem-B2y6BG1a.js +1 -0
  62. package/dist-cli-web/assets/ScreenCaptureModal-BvAtSNNR.js +1 -0
  63. package/dist-cli-web/assets/SectionTabs.vue_vue_type_script_setup_true_lang-ueEF4zHE.js +1 -0
  64. package/dist-cli-web/assets/Select-Cm4I8u9n.js +1 -0
  65. package/dist-cli-web/assets/SettingsModal-CjVax3I6.css +1 -0
  66. package/dist-cli-web/assets/SettingsModal-Cwshzj7i.js +2 -0
  67. package/dist-cli-web/assets/StorageTab-D7HzH16Y.js +1 -0
  68. package/dist-cli-web/assets/Switch-BVw4e_19.js +1 -0
  69. package/dist-cli-web/assets/Tabs-Dqwkh3Ea.js +1 -0
  70. package/dist-cli-web/assets/Tabs-OrJkANIA.js +1 -0
  71. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-DPIvbq75.js +1 -0
  72. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-dEdo6CqM.js +1 -0
  73. package/dist-cli-web/assets/VisuallyHiddenInput-BOdkR_GC.js +1 -0
  74. package/dist-cli-web/assets/{WebGLRenderer-CRp_HC84.js → WebGLRenderer-BB8ukBI_.js} +2 -2
  75. package/dist-cli-web/assets/{WebGPURenderer-WIUnEhpi.js → WebGPURenderer-DDOzF1Kc.js} +2 -2
  76. package/dist-cli-web/assets/aiChat-CzFSFuOJ.js +8 -0
  77. package/dist-cli-web/assets/annual-summary-time-range-CmrAqcuj.js +1 -0
  78. package/dist-cli-web/assets/base-CawUl6ET.js +1 -0
  79. package/dist-cli-web/assets/{browserAll-B29Ox2in.js → browserAll-BRqlnNxq.js} +2 -2
  80. package/dist-cli-web/assets/chatlabSiteLocale-CrCNb8QP.js +1 -0
  81. package/dist-cli-web/assets/clamp-jFYhBC76.js +1 -0
  82. package/dist-cli-web/assets/cli-web-ClAJ6Tao.js +1 -0
  83. package/dist-cli-web/assets/{dateFormat-T7kMS5IP.js → dateFormat-DXuDxIye.js} +1 -1
  84. package/dist-cli-web/assets/fetch-3_xX8Yi8.js +1 -0
  85. package/dist-cli-web/assets/{fetch-DsHf-61a.js → fetch-Bcy2T8Fe.js} +1 -1
  86. package/dist-cli-web/assets/{fetch-DvrVGAzR.js → fetch-CGu53cxT.js} +1 -1
  87. package/dist-cli-web/assets/{fetch-BBgZQob_.js → fetch-CP1afnT0.js} +1 -1
  88. package/dist-cli-web/assets/{fetch-D9sn99zX.js → fetch-DWAIk_6_.js} +1 -1
  89. package/dist-cli-web/assets/{fetch-BKBlpBl4.js → fetch-DijSOvop.js} +1 -1
  90. package/dist-cli-web/assets/{fetch-CYaGyohV.js → fetch-Dy38lkAa.js} +1 -1
  91. package/dist-cli-web/assets/{fetch-L6PKD8-N.js → fetch-DzKlTaLz.js} +1 -1
  92. package/dist-cli-web/assets/{fetch-D8hJZa8o.js → fetch-LY-yerw1.js} +1 -1
  93. package/dist-cli-web/assets/{fetch-BLi6Jsjg.js → fetch-q480XYZQ.js} +1 -1
  94. package/dist-cli-web/assets/index-0dp58uL4.css +1 -0
  95. package/dist-cli-web/assets/index-1X7zdLgJ.css +1 -0
  96. package/dist-cli-web/assets/index-4enwdBcq.css +1 -0
  97. package/dist-cli-web/assets/index-AudHHbMs.js +1 -0
  98. package/dist-cli-web/assets/{index-0qcENyvH.js → index-BRV_D8dv.js} +3 -3
  99. package/dist-cli-web/assets/index-CF4DTbvD.js +1 -0
  100. package/dist-cli-web/assets/index-CMLoht5R.js +1 -0
  101. package/dist-cli-web/assets/index-CMPHZH6B.css +1 -0
  102. package/dist-cli-web/assets/index-CNzv7aBW.js +1 -0
  103. package/dist-cli-web/assets/index-CXUTiy-V.js +1 -0
  104. package/dist-cli-web/assets/index-D5m0P4ss.js +36 -0
  105. package/dist-cli-web/assets/index-Di6FmCdk.js +64 -0
  106. package/dist-cli-web/assets/index-DkOrt7_X.js +2 -0
  107. package/dist-cli-web/assets/index-Dlb6fC68.js +1 -0
  108. package/dist-cli-web/assets/index-IA7YUjQX.js +2 -0
  109. package/dist-cli-web/assets/index-kzU381y1.js +1 -0
  110. package/dist-cli-web/assets/index-z8Vogc-b.js +137 -0
  111. package/dist-cli-web/assets/lazy-overlay-visibility-Bd9ZFOgR.js +1 -0
  112. package/dist-cli-web/assets/member-select-utils-BXZ3oHct.js +1 -0
  113. package/dist-cli-web/assets/people-page-header-Bh3KXD4g.js +1 -0
  114. package/dist-cli-web/assets/prompt-DcAcgQiB.js +1 -0
  115. package/dist-cli-web/assets/relationship-galaxy-connections-Cg4G5yYE.js +1 -0
  116. package/dist-cli-web/assets/semantic-index-config-builder-CK6v64Y1.js +1 -0
  117. package/dist-cli-web/assets/service-YZnJyEMq.js +1 -0
  118. package/dist-cli-web/assets/useFormControl-B-aXrJZN.js +1 -0
  119. package/dist-cli-web/assets/useTable-DPEMaGby.js +1 -0
  120. package/dist-cli-web/assets/useToast-B36MzFFy.js +1 -0
  121. package/dist-cli-web/assets/vendor-echarts-DfAMQxdr.js +11 -0
  122. package/dist-cli-web/assets/{vendor-echarts-wordcloud-Ca8n6qS6.js → vendor-echarts-wordcloud-BsaEJTPY.js} +1 -1
  123. package/dist-cli-web/assets/vendor-zrender-PFmZe1EY.js +8 -0
  124. package/dist-cli-web/assets/{webworkerAll-B2qvP2qR.js → webworkerAll-Dwqmy_YO.js} +1 -1
  125. package/dist-cli-web/index.html +2 -2
  126. package/package.json +1 -1
  127. package/dist/chunk-2XCNTG4O.mjs.map +0 -1
  128. package/dist/chunk-4LW6443F.mjs.map +0 -1
  129. package/dist/chunk-72RWABYJ.mjs.map +0 -1
  130. package/dist/chunk-H6D4DZOC.mjs.map +0 -1
  131. package/dist/chunk-INBJ7SND.mjs.map +0 -1
  132. package/dist/chunk-MENKFEPC.mjs.map +0 -1
  133. package/dist/chunk-U6MVJSXX.mjs.map +0 -1
  134. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-B06LYYS9.js +0 -110
  135. package/dist-cli-web/assets/CanvasRenderer-DDRdIyZ8.js +0 -1
  136. package/dist-cli-web/assets/DataDirCleanupNotice-CP8P1lsL.js +0 -1
  137. package/dist-cli-web/assets/EChartWordcloud-CqknPGui.js +0 -1
  138. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-BFaXTz1c.js +0 -1
  139. package/dist-cli-web/assets/MessageExportModal-CZge6qub.js +0 -1
  140. package/dist-cli-web/assets/PageHeader-CxKbcLDc.js +0 -1
  141. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-DVmpDz05.js +0 -1
  142. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-CCH1UQGc.js +0 -1
  143. package/dist-cli-web/assets/annual-summary-time-range-R3pLPEuN.js +0 -1
  144. package/dist-cli-web/assets/base-CLwWn3Rs.js +0 -1
  145. package/dist-cli-web/assets/cli-web-CVLb5Pkq.js +0 -1
  146. package/dist-cli-web/assets/fetch-Cm2WR2Cv.js +0 -1
  147. package/dist-cli-web/assets/index-AkF_KqVI.js +0 -1
  148. package/dist-cli-web/assets/index-BVOFyB9E.js +0 -137
  149. package/dist-cli-web/assets/index-BkdOH41D.js +0 -1
  150. package/dist-cli-web/assets/index-C4TuoOLM.js +0 -2
  151. package/dist-cli-web/assets/index-C6Mk-Xqt.js +0 -1
  152. package/dist-cli-web/assets/index-CkgUsaeX.js +0 -1
  153. package/dist-cli-web/assets/index-CuAy1Xmt.js +0 -64
  154. package/dist-cli-web/assets/index-D_0qeKKc.js +0 -53
  155. package/dist-cli-web/assets/index-DeR0Vukh.js +0 -1
  156. package/dist-cli-web/assets/index-DrjoMv9s.css +0 -1
  157. package/dist-cli-web/assets/index-Dx-XvfIK.js +0 -2
  158. package/dist-cli-web/assets/index-Jhwca9kg.css +0 -1
  159. package/dist-cli-web/assets/index-_DrviTnP.js +0 -1
  160. package/dist-cli-web/assets/index-vtF05fh7.css +0 -1
  161. package/dist-cli-web/assets/index-xN0sjOLK.js +0 -1
  162. package/dist-cli-web/assets/people-page-header-CCNJOrmK.js +0 -1
  163. package/dist-cli-web/assets/vendor-echarts-B-i_56Av.js +0 -11
  164. package/dist-cli-web/assets/vendor-zrender-5DcYjSZk.js +0 -8
  165. /package/dist/{chat-command-6IOH2QRF.mjs.map → chat-command-24RYGJAS.mjs.map} +0 -0
  166. /package/dist/{chunk-4RLZFGO5.mjs.map → chunk-GQMCDWZQ.mjs.map} +0 -0
  167. /package/dist/{chunk-52P5ZRBD.mjs.map → chunk-KIMJXKZO.mjs.map} +0 -0
  168. /package/dist/{chunk-652KEAX3.mjs.map → chunk-KXDJ7N6N.mjs.map} +0 -0
  169. /package/dist/{chunk-UMBEHBIZ.mjs.map → chunk-U36TQMAF.mjs.map} +0 -0
  170. /package/dist/{chunk-JX6MFS6F.mjs.map → chunk-ZBPPLXM4.mjs.map} +0 -0
  171. /package/dist/{http-HSK46XEU.mjs.map → http-3K4YMWDE.mjs.map} +0 -0
  172. /package/dist/{import-DF2WD2HE.mjs.map → import-D4LV565A.mjs.map} +0 -0
  173. /package/dist/{mcp-A2RND7KN.mjs.map → mcp-KL22FKQ2.mjs.map} +0 -0
  174. /package/dist/{src-SHFHW2QB.mjs.map → src-COIB6Z4P.mjs.map} +0 -0
  175. /package/dist/{stream-import-GZ2QDE3Z.mjs.map → stream-import-FSXCFG4M.mjs.map} +0 -0
  176. /package/dist/{worker-runtime-OWN56GG5.mjs.map → worker-runtime-DPS2LURI.mjs.map} +0 -0
@@ -40,46 +40,49 @@ import {
40
40
  composeChunkId,
41
41
  computeChunkerConfigHash,
42
42
  computeDbPathHash,
43
- createChatDbFtsSearcher,
44
43
  createChatDbMessageRangeReader,
45
44
  createChatDbMessageSource,
46
45
  createChatLabTempDir,
47
46
  createEmbedder,
48
47
  createLocalEmbeddingRuntimeManager,
48
+ createMessageDedupState,
49
49
  createSemanticIndexService,
50
50
  defaultSemanticIndexConfig,
51
51
  deriveParentId,
52
52
  estimateTokens,
53
- extractFtsKeywords,
54
53
  formatEvidenceMessages,
54
+ generateFallbackMessageKey,
55
55
  getChatLabTempScopeDir,
56
56
  getLocalProfileByModelId,
57
57
  getLocalProfilesForLocale,
58
- hybridSearch,
59
58
  isKeylessSemanticIndexApiBaseUrl,
60
59
  isSemanticIndexConfigured,
61
60
  isSemanticVoid,
62
61
  parseParentBounds,
63
62
  persistSemanticIndexConfig,
64
63
  rebaseChatLabDemoDocuments,
65
- reciprocalRankFusion,
64
+ registerMessageAndCheckDuplicate,
66
65
  removeChatLabTempDir,
67
66
  resolveModelId,
68
67
  resolveSemanticIndexApiKeySet,
69
68
  runWarmup,
69
+ semanticSearch,
70
70
  vecTableName,
71
71
  vecTableSchema
72
- } from "./chunk-4LW6443F.mjs";
72
+ } from "./chunk-4GWWKHWA.mjs";
73
73
  import {
74
74
  CHATLAB_FORMAT_VERSION,
75
75
  MEANINGFUL_POS_TAGS,
76
+ POS_TAG_DEFINITIONS,
76
77
  StaticPathProvider,
77
78
  appLogger,
79
+ buildExcludeKeywordsConditions,
80
+ cleanText,
78
81
  detectConflictsInMessages,
79
- generateMessageKey,
80
82
  getCollidingPlatformIds,
81
- getJieba,
83
+ getCollidingPlatformIdsFromMessages,
82
84
  isStopword,
85
+ isValidWord,
83
86
  loadConfig,
84
87
  mergeMembers2 as mergeMembers,
85
88
  migrateFromElectronIfNeeded,
@@ -90,7 +93,7 @@ import {
90
93
  withFileLock,
91
94
  writeConfigField,
92
95
  writeJsonFileAtomically
93
- } from "./chunk-INBJ7SND.mjs";
96
+ } from "./chunk-22YJOBA3.mjs";
94
97
  import {
95
98
  __export
96
99
  } from "./chunk-VSOWNKPA.mjs";
@@ -556,6 +559,220 @@ function expandHome(filePath) {
556
559
  return filePath;
557
560
  }
558
561
 
562
+ // ../../packages/node-runtime/src/nlp/segmenter.ts
563
+ import * as fs3 from "fs";
564
+ import * as path3 from "path";
565
+ import { createRequire } from "module";
566
+ var _nlpDir = null;
567
+ var jiebaInstances = /* @__PURE__ */ new Map();
568
+ var require2 = createRequire(import.meta.url);
569
+ function initNlpDir(nlpDir) {
570
+ _nlpDir = nlpDir;
571
+ }
572
+ function getNlpDir() {
573
+ return _nlpDir;
574
+ }
575
+ function existsDictOnDisk(dictId) {
576
+ if (!_nlpDir) return false;
577
+ return fs3.existsSync(path3.join(_nlpDir, `${dictId}.dict`));
578
+ }
579
+ function tryLoadDictFromDisk(dictId) {
580
+ if (!_nlpDir) return null;
581
+ const dictPath = path3.join(_nlpDir, `${dictId}.dict`);
582
+ if (!fs3.existsSync(dictPath)) return null;
583
+ try {
584
+ return fs3.readFileSync(dictPath);
585
+ } catch {
586
+ return null;
587
+ }
588
+ }
589
+ function getJieba(dictType = "default") {
590
+ const effectiveType = dictType === "default" ? "zh-CN" : dictType;
591
+ const cached = jiebaInstances.get(effectiveType);
592
+ if (cached) {
593
+ if (cached.fromDiskDict === existsDictOnDisk(effectiveType)) return cached.instance;
594
+ jiebaInstances.delete(effectiveType);
595
+ console.error(`[NLP] jieba cache invalidated (dict ${cached.fromDiskDict ? "removed" : "added"}): ${effectiveType}`);
596
+ }
597
+ try {
598
+ const { Jieba } = require2("@node-rs/jieba");
599
+ let instance;
600
+ const diskDict = tryLoadDictFromDisk(effectiveType);
601
+ if (diskDict) {
602
+ instance = Jieba.withDict(diskDict);
603
+ console.error(`[NLP] jieba dict loaded: ${effectiveType} (${diskDict.length} bytes)`);
604
+ } else {
605
+ instance = new Jieba();
606
+ console.warn(`[NLP] jieba dict missing: ${effectiveType}, fallback to built-in tokenizer`);
607
+ }
608
+ jiebaInstances.set(effectiveType, { instance, fromDiskDict: !!diskDict });
609
+ return instance;
610
+ } catch (error) {
611
+ console.error(`[NLP] Failed to load jieba module (dict=${effectiveType}):`, error);
612
+ throw new Error(`jieba \u6A21\u5757\u52A0\u8F7D\u5931\u8D25 (${effectiveType})`);
613
+ }
614
+ }
615
+ function clearJiebaInstance(dictType) {
616
+ jiebaInstances.delete(dictType);
617
+ console.error(`[NLP] jieba instance cleared: ${dictType}`);
618
+ }
619
+ function segmentChinese(text, options = {}) {
620
+ const { posFilterMode = "meaningful", customPosTags, dictType = "default" } = options;
621
+ const cleaned = cleanText(text);
622
+ if (!cleaned) return [];
623
+ try {
624
+ const jieba = getJieba(dictType);
625
+ if (posFilterMode === "all") return jieba.cut(cleaned, false);
626
+ const tagged = jieba.tag(cleaned);
627
+ const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
628
+ return tagged.filter((item) => allowedTags.has(item.tag)).map((item) => item.word);
629
+ } catch (error) {
630
+ console.error("[NLP] Chinese segmentation failed:", error);
631
+ try {
632
+ return getJieba("default").cut(cleanText(text), false);
633
+ } catch {
634
+ return cleaned.split("");
635
+ }
636
+ }
637
+ }
638
+ function segmentEnglish(text) {
639
+ const cleaned = cleanText(text);
640
+ if (!cleaned) return [];
641
+ try {
642
+ const segmenter = new Intl.Segmenter("en", { granularity: "word" });
643
+ return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
644
+ } catch {
645
+ return cleaned.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
646
+ }
647
+ }
648
+ function segmentJapanese(text) {
649
+ const cleaned = cleanText(text);
650
+ if (!cleaned) return [];
651
+ try {
652
+ const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
653
+ return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment);
654
+ } catch {
655
+ return cleaned.split("").filter((ch) => ch.trim().length > 0);
656
+ }
657
+ }
658
+ function segment(text, locale, options = {}) {
659
+ const {
660
+ minLength,
661
+ posFilterMode = "meaningful",
662
+ customPosTags,
663
+ enableStopwords = true,
664
+ dictType = "default"
665
+ } = options;
666
+ const isChinese = locale.startsWith("zh");
667
+ const isJapanese = locale === "ja-JP";
668
+ const defaultMinLength = isChinese || isJapanese ? 2 : 3;
669
+ const effectiveMinLength = minLength ?? defaultMinLength;
670
+ let words;
671
+ if (isChinese) {
672
+ words = segmentChinese(text, { posFilterMode, customPosTags, dictType });
673
+ } else if (isJapanese) {
674
+ words = segmentJapanese(text);
675
+ } else {
676
+ words = segmentEnglish(text);
677
+ }
678
+ return words.filter((word) => isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword));
679
+ }
680
+ function batchSegmentWithFrequency(texts, locale, options = {}) {
681
+ const {
682
+ minLength,
683
+ minCount = 2,
684
+ topN = 100,
685
+ posFilterMode,
686
+ customPosTags,
687
+ enableStopwords,
688
+ dictType,
689
+ excludeWords
690
+ } = options;
691
+ const wordFrequency = /* @__PURE__ */ new Map();
692
+ const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
693
+ for (const text of texts) {
694
+ const words = segment(text, locale, { minLength, posFilterMode, customPosTags, enableStopwords, dictType });
695
+ for (const word of words) {
696
+ if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
697
+ wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
698
+ }
699
+ }
700
+ const filtered = /* @__PURE__ */ new Map();
701
+ let totalWords = 0;
702
+ for (const [word, count] of wordFrequency) {
703
+ if (count >= minCount) {
704
+ filtered.set(word, count);
705
+ totalWords += count;
706
+ }
707
+ }
708
+ const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
709
+ return { words: new Map(sorted), uniqueWords: filtered.size, totalWords };
710
+ }
711
+ function batchSegmentChineseWithStats(texts, locale, options = {}) {
712
+ const {
713
+ minLength,
714
+ minCount = 2,
715
+ topN = 100,
716
+ posFilterMode = "meaningful",
717
+ customPosTags,
718
+ enableStopwords = true,
719
+ dictType = "default",
720
+ excludeWords
721
+ } = options;
722
+ const effectiveMinLength = minLength ?? 2;
723
+ const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
724
+ const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
725
+ const wordFrequency = /* @__PURE__ */ new Map();
726
+ const posTagStats = /* @__PURE__ */ new Map();
727
+ try {
728
+ const jieba = getJieba(dictType);
729
+ for (const text of texts) {
730
+ const cleaned = cleanText(text);
731
+ if (!cleaned) continue;
732
+ for (const { tag, word } of jieba.tag(cleaned)) {
733
+ if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue;
734
+ posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1);
735
+ if (!allowedTags.has(tag)) continue;
736
+ if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
737
+ wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
738
+ }
739
+ }
740
+ } catch (error) {
741
+ console.error("[NLP] Chinese single-pass segmentation failed:", error);
742
+ }
743
+ const filtered = /* @__PURE__ */ new Map();
744
+ let totalWords = 0;
745
+ for (const [word, count] of wordFrequency) {
746
+ if (count >= minCount) {
747
+ filtered.set(word, count);
748
+ totalWords += count;
749
+ }
750
+ }
751
+ const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
752
+ return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats };
753
+ }
754
+ function collectPosTagStats(texts, minWordLength = 2, enableStopwords = true, dictType = "default") {
755
+ const posStats = /* @__PURE__ */ new Map();
756
+ try {
757
+ const jieba = getJieba(dictType);
758
+ for (const text of texts) {
759
+ const cleaned = cleanText(text);
760
+ if (!cleaned) continue;
761
+ const tagged = jieba.tag(cleaned);
762
+ for (const item of tagged) {
763
+ if (!isValidWord(item.word, "zh-CN", minWordLength, enableStopwords, isStopword)) continue;
764
+ posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1);
765
+ }
766
+ }
767
+ } catch (error) {
768
+ console.error("[NLP] Failed to collect POS stats:", error);
769
+ }
770
+ return posStats;
771
+ }
772
+ function getPosTagDefinitions() {
773
+ return POS_TAG_DEFINITIONS;
774
+ }
775
+
559
776
  // ../../packages/node-runtime/src/jieba-nlp-provider.ts
560
777
  function createJiebaNlpProvider(dictType = "default") {
561
778
  return {
@@ -567,9 +784,233 @@ function createJiebaNlpProvider(dictType = "default") {
567
784
  };
568
785
  }
569
786
 
787
+ // ../../packages/node-runtime/src/nlp/word-frequency.ts
788
+ function buildMessageQuery(timeFilter, memberId, excludeKeywords) {
789
+ const conditions = [];
790
+ const params = [];
791
+ if (timeFilter?.startTs !== void 0) {
792
+ conditions.push("msg.ts >= ?");
793
+ params.push(timeFilter.startTs);
794
+ }
795
+ if (timeFilter?.endTs !== void 0) {
796
+ conditions.push("msg.ts <= ?");
797
+ params.push(timeFilter.endTs);
798
+ }
799
+ if (memberId !== void 0 && memberId !== null) {
800
+ conditions.push("msg.sender_id = ?");
801
+ params.push(memberId);
802
+ }
803
+ if (excludeKeywords && excludeKeywords.length > 0) {
804
+ const exclude = buildExcludeKeywordsConditions(excludeKeywords);
805
+ conditions.push(...exclude.conditions);
806
+ params.push(...exclude.params);
807
+ }
808
+ conditions.push("COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'");
809
+ conditions.push("msg.type = 0");
810
+ conditions.push("msg.content IS NOT NULL");
811
+ conditions.push("TRIM(msg.content) != ''");
812
+ return {
813
+ clause: ` WHERE ${conditions.join(" AND ")}`,
814
+ params
815
+ };
816
+ }
817
+ function computeWordFrequency(db, params) {
818
+ const {
819
+ locale,
820
+ timeFilter,
821
+ memberId,
822
+ topN = 100,
823
+ minWordLength,
824
+ minCount = 2,
825
+ posFilterMode = "meaningful",
826
+ customPosTags,
827
+ enableStopwords = true,
828
+ dictType = "default",
829
+ excludeWords,
830
+ excludeKeywords
831
+ } = params;
832
+ const { clause, params: filterParams } = buildMessageQuery(timeFilter, memberId, excludeKeywords);
833
+ const messages = db.prepare(`SELECT msg.content FROM message msg JOIN member m ON msg.sender_id = m.id${clause}`).all(...filterParams);
834
+ if (messages.length === 0) {
835
+ return { words: [], totalWords: 0, totalMessages: 0, uniqueWords: 0 };
836
+ }
837
+ const texts = messages.map((m) => m.content);
838
+ const segmentOptions = {
839
+ minLength: minWordLength,
840
+ minCount,
841
+ topN,
842
+ posFilterMode,
843
+ customPosTags,
844
+ enableStopwords,
845
+ dictType,
846
+ excludeWords
847
+ };
848
+ let posTagStats;
849
+ let result;
850
+ if (locale.startsWith("zh") && posFilterMode !== "all") {
851
+ const combined = batchSegmentChineseWithStats(texts, locale, segmentOptions);
852
+ result = { words: combined.words, uniqueWords: combined.uniqueWords, totalWords: combined.totalWords };
853
+ posTagStats = [...combined.posTagStats.entries()].map(([tag, count]) => ({ tag, count }));
854
+ } else {
855
+ if (locale.startsWith("zh")) {
856
+ const posStatsMap = collectPosTagStats(texts, minWordLength ?? 2, enableStopwords, dictType);
857
+ posTagStats = [...posStatsMap.entries()].map(([tag, count]) => ({ tag, count }));
858
+ }
859
+ result = batchSegmentWithFrequency(texts, locale, segmentOptions);
860
+ }
861
+ let topNTotalWords = 0;
862
+ for (const count of result.words.values()) topNTotalWords += count;
863
+ const words = [...result.words.entries()].map(([word, count]) => ({
864
+ word,
865
+ count,
866
+ percentage: topNTotalWords > 0 ? Math.round(count / topNTotalWords * 1e4) / 100 : 0
867
+ }));
868
+ return {
869
+ words,
870
+ totalWords: result.totalWords,
871
+ totalMessages: messages.length,
872
+ uniqueWords: result.uniqueWords,
873
+ posTagStats
874
+ };
875
+ }
876
+ function segmentText(text, locale, minLength) {
877
+ return segment(text, locale, { minLength });
878
+ }
879
+
880
+ // ../../packages/node-runtime/src/nlp/dict-manager.ts
881
+ import * as fs4 from "fs";
882
+ import * as path4 from "path";
883
+ import { createHash } from "crypto";
884
+ var DICT_DOWNLOAD_URL_BASE = "https://chatlab.fun/assets/nlp";
885
+ var DICT_SHA256 = {
886
+ "zh-CN": "139519822fe8ab9e10d9d07e68ea0451045380aedaf54ecc51e2a28c6b42a13f",
887
+ "zh-TW": "a63ec7e388f16f1b486dcd948a9f1a3b492be5d9b6bdab786a95e59966786dfd"
888
+ };
889
+ var AVAILABLE_DICTS = [
890
+ { id: "zh-CN", label: "\u7B80\u4F53\u4E2D\u6587", locale: "zh-CN" },
891
+ { id: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", locale: "zh-TW" }
892
+ ];
893
+ function getDictFilePath(nlpDir, dictId) {
894
+ return path4.join(nlpDir, `${dictId}.dict`);
895
+ }
896
+ function isDictDownloaded(nlpDir, dictId) {
897
+ return fs4.existsSync(getDictFilePath(nlpDir, dictId));
898
+ }
899
+ function getDictList(nlpDir) {
900
+ return AVAILABLE_DICTS.map((d) => {
901
+ const filePath = getDictFilePath(nlpDir, d.id);
902
+ const downloaded = fs4.existsSync(filePath);
903
+ let fileSize;
904
+ if (downloaded) {
905
+ try {
906
+ fileSize = fs4.statSync(filePath).size;
907
+ } catch {
908
+ }
909
+ }
910
+ return { ...d, downloaded, fileSize };
911
+ });
912
+ }
913
+ function loadDictBuffer(nlpDir, dictId) {
914
+ const filePath = getDictFilePath(nlpDir, dictId);
915
+ if (!fs4.existsSync(filePath)) return null;
916
+ try {
917
+ return fs4.readFileSync(filePath);
918
+ } catch (error) {
919
+ console.error(`[NLP DictManager] Failed to read dict file: ${filePath}`, error);
920
+ return null;
921
+ }
922
+ }
923
+ async function downloadDict(nlpDir, dictId, onProgress) {
924
+ if (!AVAILABLE_DICTS.find((d) => d.id === dictId)) {
925
+ return { success: false, error: `Unknown dict: ${dictId}` };
926
+ }
927
+ fs4.mkdirSync(nlpDir, { recursive: true });
928
+ const url = `${DICT_DOWNLOAD_URL_BASE}/${dictId}.dict`;
929
+ const filePath = getDictFilePath(nlpDir, dictId);
930
+ const tmpPath = filePath + ".tmp";
931
+ try {
932
+ const response = await fetch(url, { signal: AbortSignal.timeout(12e4) });
933
+ if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
934
+ const total = Number(response.headers.get("content-length") || 0);
935
+ let buffer;
936
+ if (!response.body) {
937
+ buffer = Buffer.from(await response.arrayBuffer());
938
+ } else {
939
+ const reader = response.body.getReader();
940
+ const chunks = [];
941
+ let loaded = 0;
942
+ while (true) {
943
+ const { done, value } = await reader.read();
944
+ if (done) break;
945
+ const chunk = Buffer.from(value);
946
+ chunks.push(chunk);
947
+ loaded += chunk.length;
948
+ if (total > 0 && onProgress) onProgress(Math.round(loaded / total * 100));
949
+ }
950
+ buffer = Buffer.concat(chunks);
951
+ }
952
+ const MIN_DICT_SIZE = 1e6;
953
+ if (buffer.length < MIN_DICT_SIZE) {
954
+ return { success: false, error: `Downloaded file is invalid (${buffer.length} bytes)` };
955
+ }
956
+ const head = buffer.subarray(0, 50).toString("utf-8").trim();
957
+ if (head.startsWith("<!") || head.startsWith("<html")) {
958
+ return { success: false, error: "Downloaded file is HTML, not a dictionary file" };
959
+ }
960
+ const expectedSha256 = DICT_SHA256[dictId];
961
+ if (!expectedSha256) {
962
+ return { success: false, error: `Missing SHA256 checksum for dict: ${dictId}` };
963
+ }
964
+ const actualSha256 = createHash("sha256").update(buffer).digest("hex");
965
+ if (actualSha256 !== expectedSha256) {
966
+ return { success: false, error: "Dictionary integrity check failed (SHA256 mismatch)" };
967
+ }
968
+ fs4.writeFileSync(tmpPath, buffer);
969
+ if (fs4.existsSync(filePath)) fs4.unlinkSync(filePath);
970
+ fs4.renameSync(tmpPath, filePath);
971
+ clearJiebaInstance(dictId);
972
+ console.log(`[NLP DictManager] Dict downloaded: ${dictId} (${fs4.statSync(filePath).size} bytes)`);
973
+ return { success: true };
974
+ } catch (error) {
975
+ if (fs4.existsSync(tmpPath)) {
976
+ try {
977
+ fs4.unlinkSync(tmpPath);
978
+ } catch {
979
+ }
980
+ }
981
+ const msg = error instanceof Error ? error.message : String(error);
982
+ console.error(`[NLP DictManager] Download failed for ${dictId}:`, msg);
983
+ return { success: false, error: msg };
984
+ }
985
+ }
986
+ function deleteDict(nlpDir, dictId) {
987
+ const filePath = getDictFilePath(nlpDir, dictId);
988
+ if (!fs4.existsSync(filePath)) return { success: true };
989
+ try {
990
+ fs4.unlinkSync(filePath);
991
+ clearJiebaInstance(dictId);
992
+ console.log(`[NLP DictManager] Dict deleted: ${dictId}`);
993
+ return { success: true };
994
+ } catch (error) {
995
+ const msg = error instanceof Error ? error.message : String(error);
996
+ console.error(`[NLP DictManager] Delete failed for ${dictId}:`, msg);
997
+ return { success: false, error: msg };
998
+ }
999
+ }
1000
+ async function ensureDefaultDict(nlpDir) {
1001
+ if (isDictDownloaded(nlpDir, "zh-CN")) return;
1002
+ console.log("[NLP DictManager] zh-CN dict not found, starting background download...");
1003
+ const result = await downloadDict(nlpDir, "zh-CN");
1004
+ if (result.success) {
1005
+ console.log("[NLP DictManager] zh-CN dict auto-downloaded successfully");
1006
+ } else {
1007
+ console.warn("[NLP DictManager] zh-CN dict auto-download failed:", result.error);
1008
+ }
1009
+ }
1010
+
570
1011
  // ../../packages/node-runtime/src/preferences.ts
571
- import * as fs3 from "fs";
572
- import * as path3 from "path";
1012
+ import * as fs5 from "fs";
1013
+ import * as path5 from "path";
573
1014
  var DEFAULTS = {
574
1015
  pinnedSessionIds: [],
575
1016
  assistantUpgradeSkippedVersions: {},
@@ -592,13 +1033,7 @@ var DEFAULTS = {
592
1033
  enableAutoSkill: true,
593
1034
  chartAutoMode: "suggest",
594
1035
  searchContextBefore: 2,
595
- searchContextAfter: 2,
596
- contextCompression: {
597
- enabled: true,
598
- tokenThresholdPercent: 75,
599
- bufferSizePercent: 20,
600
- maxToolResultPercent: 50
601
- }
1036
+ searchContextAfter: 2
602
1037
  },
603
1038
  customKeywordTemplates: [],
604
1039
  deletedPresetTemplateIds: [],
@@ -616,13 +1051,13 @@ var PreferencesManager = class {
616
1051
  filePath;
617
1052
  cache = null;
618
1053
  constructor(systemDir) {
619
- this.filePath = path3.join(systemDir, "preferences.json");
1054
+ this.filePath = path5.join(systemDir, "preferences.json");
620
1055
  }
621
1056
  load() {
622
1057
  if (this.cache) return this.cache;
623
1058
  try {
624
- if (fs3.existsSync(this.filePath)) {
625
- const raw = fs3.readFileSync(this.filePath, "utf-8");
1059
+ if (fs5.existsSync(this.filePath)) {
1060
+ const raw = fs5.readFileSync(this.filePath, "utf-8");
626
1061
  const parsed = JSON.parse(raw);
627
1062
  const migrated = this.migrateLegacyDesensitizeRules(parsed, raw);
628
1063
  this.cache = this.mergeDefaults(migrated.preferences);
@@ -668,14 +1103,7 @@ var PreferencesManager = class {
668
1103
  pinnedSessionIds: partial.pinnedSessionIds ?? DEFAULTS.pinnedSessionIds,
669
1104
  assistantUpgradeSkippedVersions: this.normalizePositiveIntegerMap(partial.assistantUpgradeSkippedVersions),
670
1105
  aiPreprocessConfig: partial.aiPreprocessConfig ? this.normalizeAiPreprocessConfig(partial.aiPreprocessConfig) : { ...DEFAULTS.aiPreprocessConfig },
671
- aiGlobalSettings: partial.aiGlobalSettings ? {
672
- ...DEFAULTS.aiGlobalSettings,
673
- ...partial.aiGlobalSettings,
674
- contextCompression: {
675
- ...DEFAULTS.aiGlobalSettings.contextCompression,
676
- ...partial.aiGlobalSettings.contextCompression ?? {}
677
- }
678
- } : { ...DEFAULTS.aiGlobalSettings },
1106
+ aiGlobalSettings: this.normalizeAiGlobalSettings(partial.aiGlobalSettings),
679
1107
  customKeywordTemplates: partial.customKeywordTemplates ?? DEFAULTS.customKeywordTemplates,
680
1108
  deletedPresetTemplateIds: partial.deletedPresetTemplateIds ?? DEFAULTS.deletedPresetTemplateIds,
681
1109
  wordFilter: partial.wordFilter ? { ...DEFAULTS.wordFilter, ...partial.wordFilter } : { ...DEFAULTS.wordFilter },
@@ -685,6 +1113,11 @@ var PreferencesManager = class {
685
1113
  ownerPromptDismissedSessionIds: partial.ownerPromptDismissedSessionIds ?? DEFAULTS.ownerPromptDismissedSessionIds
686
1114
  };
687
1115
  }
1116
+ normalizeAiGlobalSettings(partial) {
1117
+ const normalized = { ...partial ?? {} };
1118
+ delete normalized.contextCompression;
1119
+ return { ...DEFAULTS.aiGlobalSettings, ...normalized };
1120
+ }
688
1121
  normalizeAiPreprocessConfig(partial) {
689
1122
  const defaults = DEFAULTS.aiPreprocessConfig;
690
1123
  const rules = Array.isArray(partial?.desensitizeRules) ? partial.desensitizeRules : [];
@@ -748,21 +1181,21 @@ var PreferencesManager = class {
748
1181
  };
749
1182
  }
750
1183
  backupLegacyPreferences(rawContent) {
751
- const backupDir = path3.join(path3.dirname(this.filePath), "backups");
752
- if (!fs3.existsSync(backupDir)) {
753
- fs3.mkdirSync(backupDir, { recursive: true });
1184
+ const backupDir = path5.join(path5.dirname(this.filePath), "backups");
1185
+ if (!fs5.existsSync(backupDir)) {
1186
+ fs5.mkdirSync(backupDir, { recursive: true });
754
1187
  }
755
1188
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
756
- const backupPath = path3.join(backupDir, `preferences-pre-desensitize-groups-${timestamp}.json`);
757
- fs3.writeFileSync(backupPath, rawContent, "utf-8");
1189
+ const backupPath = path5.join(backupDir, `preferences-pre-desensitize-groups-${timestamp}.json`);
1190
+ fs5.writeFileSync(backupPath, rawContent, "utf-8");
758
1191
  console.info(`[Preferences] Backed up legacy desensitize rules to ${backupPath}`);
759
1192
  }
760
1193
  writePreferences(preferences) {
761
- const dir = path3.dirname(this.filePath);
762
- if (!fs3.existsSync(dir)) {
763
- fs3.mkdirSync(dir, { recursive: true });
1194
+ const dir = path5.dirname(this.filePath);
1195
+ if (!fs5.existsSync(dir)) {
1196
+ fs5.mkdirSync(dir, { recursive: true });
764
1197
  }
765
- fs3.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
1198
+ fs5.writeFileSync(this.filePath, JSON.stringify(preferences, null, 2), "utf-8");
766
1199
  }
767
1200
  /**
768
1201
  * Deep merge: arrays are replaced (not concatenated), objects are merged.
@@ -782,26 +1215,146 @@ var PreferencesManager = class {
782
1215
  };
783
1216
 
784
1217
  // ../../packages/node-runtime/src/merger/index.ts
1218
+ function createMessageScope(meta, members, sourceIndex) {
1219
+ const platform = meta?.platform || "unknown";
1220
+ const type = meta?.type || "unknown";
1221
+ if (meta?.groupId) return JSON.stringify([platform, type, "group", meta.groupId]);
1222
+ if (type === "group") return JSON.stringify([platform, type, "source", sourceIndex]);
1223
+ const memberIds = [...new Set(members.map((member) => member.platformId))].sort();
1224
+ if (memberIds.length > 0) {
1225
+ if (type === "private") {
1226
+ if (memberIds.length >= 2) return JSON.stringify([platform, type, "members", memberIds]);
1227
+ } else {
1228
+ return JSON.stringify([platform, type, "name-members", meta?.name || "", memberIds]);
1229
+ }
1230
+ }
1231
+ return JSON.stringify([platform, type, "source", sourceIndex]);
1232
+ }
1233
+ function createMessageBridgeFamily(meta) {
1234
+ if (!meta || meta.groupId || meta.type !== "group" && meta.type !== "private") return void 0;
1235
+ return JSON.stringify([meta.platform || "unknown", meta.type]);
1236
+ }
1237
+ function createMessageIdNormalizer(scopes) {
1238
+ const uniqueScopes = [...new Set(scopes)];
1239
+ const scopeIndexes = new Map(uniqueScopes.map((scope, index) => [scope, index]));
1240
+ const namespaceMessageIds = uniqueScopes.length > 1;
1241
+ return (id, scope) => {
1242
+ if (!id || !namespaceMessageIds) return id;
1243
+ return `__chatlab_message_scope__${scopeIndexes.get(scope) ?? 0}__${encodeURIComponent(id)}`;
1244
+ };
1245
+ }
1246
+ function createMessageScopeResolver(contexts) {
1247
+ const contextBySource = /* @__PURE__ */ new Map();
1248
+ const parents = /* @__PURE__ */ new Map();
1249
+ const firstSourceBySignature = /* @__PURE__ */ new Map();
1250
+ for (const context of contexts) {
1251
+ contextBySource.set(context.sourceIndex, context);
1252
+ parents.set(context.sourceIndex, context.sourceIndex);
1253
+ }
1254
+ const find = (sourceIndex) => {
1255
+ const parent = parents.get(sourceIndex) ?? sourceIndex;
1256
+ if (parent === sourceIndex) return sourceIndex;
1257
+ const root = find(parent);
1258
+ parents.set(sourceIndex, root);
1259
+ return root;
1260
+ };
1261
+ const union = (firstSourceIndex, secondSourceIndex) => {
1262
+ const firstRoot = find(firstSourceIndex);
1263
+ const secondRoot = find(secondSourceIndex);
1264
+ if (firstRoot === secondRoot) return;
1265
+ const root = Math.min(firstRoot, secondRoot);
1266
+ parents.set(firstRoot === root ? secondRoot : firstRoot, root);
1267
+ };
1268
+ return {
1269
+ addMessage(message, context) {
1270
+ if (!context.messageBridgeFamily || !message.platformMessageId) return;
1271
+ const fallbackKey = generateFallbackMessageKey({
1272
+ timestamp: message.timestamp,
1273
+ senderPlatformId: message.senderPlatformId,
1274
+ type: message.type,
1275
+ content: message.content ?? null,
1276
+ replyToMessageId: message.replyToMessageId
1277
+ });
1278
+ const signature = JSON.stringify([context.messageBridgeFamily, message.platformMessageId, fallbackKey]);
1279
+ const firstSourceIndex = firstSourceBySignature.get(signature);
1280
+ if (firstSourceIndex === void 0) {
1281
+ firstSourceBySignature.set(signature, context.sourceIndex);
1282
+ return;
1283
+ }
1284
+ union(firstSourceIndex, context.sourceIndex);
1285
+ },
1286
+ complete() {
1287
+ firstSourceBySignature.clear();
1288
+ },
1289
+ resolve(sourceIndex) {
1290
+ const rootContext = contextBySource.get(find(sourceIndex));
1291
+ return rootContext?.messageScope ?? JSON.stringify(["unknown", "source", sourceIndex]);
1292
+ }
1293
+ };
1294
+ }
785
1295
  function checkConflictsFromSources(dataSources) {
786
- const allMessages = [];
787
- for (const { source, filename } of dataSources) {
1296
+ const preparedSources = dataSources.map(({ source, filename }, sourceIndex) => {
788
1297
  const meta = source.getMeta();
789
- const platform = meta?.platform || "unknown";
1298
+ const members = source.getMembers();
1299
+ return {
1300
+ source,
1301
+ filename,
1302
+ sourceIndex,
1303
+ platform: meta?.platform || "unknown",
1304
+ messageScope: createMessageScope(meta, members, sourceIndex),
1305
+ messageBridgeFamily: createMessageBridgeFamily(meta)
1306
+ };
1307
+ });
1308
+ const allMessages = [];
1309
+ for (const { source, filename, sourceIndex, platform, messageScope, messageBridgeFamily } of preparedSources) {
790
1310
  source.streamMessages(1e4, (messages) => {
791
1311
  for (const msg of messages) {
792
- allMessages.push({ msg, source: filename, platform });
1312
+ allMessages.push({ msg, source: filename, sourceIndex, platform, messageScope, messageBridgeFamily });
793
1313
  }
794
1314
  });
795
1315
  }
796
- return detectConflictsInMessages(allMessages);
1316
+ const result = detectConflictsInMessages(allMessages);
1317
+ const collidingIds = getCollidingPlatformIdsFromMessages(allMessages);
1318
+ const scopeResolver = createMessageScopeResolver(preparedSources);
1319
+ for (const message of allMessages) scopeResolver.addMessage(message.msg, message);
1320
+ scopeResolver.complete();
1321
+ const normalizeMessageId = createMessageIdNormalizer(
1322
+ preparedSources.map(({ sourceIndex }) => scopeResolver.resolve(sourceIndex))
1323
+ );
1324
+ const dedupState = createMessageDedupState();
1325
+ let totalMessages = 0;
1326
+ for (const { msg, sourceIndex, platform } of allMessages) {
1327
+ const messageScope = scopeResolver.resolve(sourceIndex);
1328
+ const senderPlatformId = normalizePlatformId(msg.senderPlatformId, platform, collidingIds);
1329
+ const duplicate = registerMessageAndCheckDuplicate(
1330
+ {
1331
+ platformMessageId: normalizeMessageId(msg.platformMessageId, messageScope),
1332
+ timestamp: msg.timestamp,
1333
+ senderPlatformId,
1334
+ type: msg.type,
1335
+ content: msg.content ?? null,
1336
+ replyToMessageId: normalizeMessageId(msg.replyToMessageId, messageScope)
1337
+ },
1338
+ dedupState
1339
+ );
1340
+ if (!duplicate) totalMessages++;
1341
+ }
1342
+ return { ...result, totalMessages };
797
1343
  }
798
1344
  function buildMergedOutput(dataSources, outputName) {
799
- const metas = dataSources.map(({ source, filename }) => ({
800
- meta: source.getMeta(),
801
- members: source.getMembers(),
802
- filename,
803
- source
804
- }));
1345
+ const metas = dataSources.map(({ source, filename }, sourceIndex) => {
1346
+ const meta = source.getMeta();
1347
+ const members = source.getMembers();
1348
+ return {
1349
+ meta,
1350
+ members,
1351
+ filename,
1352
+ source,
1353
+ sourceIndex,
1354
+ messageScope: createMessageScope(meta, members, sourceIndex),
1355
+ messageBridgeFamily: createMessageBridgeFamily(meta)
1356
+ };
1357
+ });
805
1358
  const collidingIds = getCollidingPlatformIds(
806
1359
  metas.map(({ meta, members }) => ({
807
1360
  platform: meta?.platform || "unknown",
@@ -815,24 +1368,63 @@ function buildMergedOutput(dataSources, outputName) {
815
1368
  })),
816
1369
  collidingIds
817
1370
  );
818
- const seenKeys = /* @__PURE__ */ new Set();
1371
+ const uniquePlatforms = [...new Set(metas.map(({ meta }) => meta?.platform || "unknown"))];
1372
+ const scopeResolver = createMessageScopeResolver(metas);
1373
+ for (const context of metas) {
1374
+ if (!context.messageBridgeFamily) continue;
1375
+ context.source.streamMessages(1e4, (messages) => {
1376
+ for (const message of messages) scopeResolver.addMessage(message, context);
1377
+ });
1378
+ }
1379
+ scopeResolver.complete();
1380
+ const normalizeMessageId = createMessageIdNormalizer(
1381
+ metas.map(({ sourceIndex }) => scopeResolver.resolve(sourceIndex))
1382
+ );
1383
+ const dedupState = createMessageDedupState();
1384
+ const fallbackOnlyMessageIndexes = /* @__PURE__ */ new Map();
819
1385
  const mergedMessages = [];
820
- for (const { source, meta } of metas) {
1386
+ for (const { source, meta, sourceIndex } of metas) {
821
1387
  const platform2 = meta?.platform || "unknown";
1388
+ const messageScope = scopeResolver.resolve(sourceIndex);
822
1389
  source.streamMessages(1e4, (messages) => {
823
1390
  for (const msg of messages) {
824
1391
  const nid = normalizePlatformId(msg.senderPlatformId, platform2, collidingIds);
825
- const key = generateMessageKey(msg.timestamp, nid, msg.content ?? null);
826
- if (seenKeys.has(key)) continue;
827
- seenKeys.add(key);
1392
+ const platformMessageId = normalizeMessageId(msg.platformMessageId, messageScope);
1393
+ const replyToMessageId = normalizeMessageId(msg.replyToMessageId, messageScope);
1394
+ const dedupMessage = {
1395
+ platformMessageId,
1396
+ timestamp: msg.timestamp,
1397
+ senderPlatformId: nid,
1398
+ type: msg.type,
1399
+ content: msg.content ?? null,
1400
+ replyToMessageId
1401
+ };
1402
+ const fallbackKey = generateFallbackMessageKey(dedupMessage);
1403
+ const shouldBackfillId = Boolean(
1404
+ platformMessageId && !dedupState.platformMessageIds.has(platformMessageId) && dedupState.fallbackOnlyKeys.has(fallbackKey)
1405
+ );
1406
+ if (registerMessageAndCheckDuplicate(dedupMessage, dedupState)) {
1407
+ if (shouldBackfillId) {
1408
+ const retainedIndex2 = fallbackOnlyMessageIndexes.get(fallbackKey);
1409
+ if (retainedIndex2 !== void 0) {
1410
+ mergedMessages[retainedIndex2].platformMessageId = platformMessageId;
1411
+ fallbackOnlyMessageIndexes.delete(fallbackKey);
1412
+ }
1413
+ }
1414
+ continue;
1415
+ }
1416
+ const retainedIndex = mergedMessages.length;
828
1417
  mergedMessages.push({
1418
+ platformMessageId,
829
1419
  sender: nid,
830
1420
  accountName: msg.senderAccountName,
831
1421
  groupNickname: msg.senderGroupNickname,
832
1422
  timestamp: msg.timestamp,
833
1423
  type: msg.type,
834
- content: msg.content
1424
+ content: msg.content,
1425
+ replyToMessageId
835
1426
  });
1427
+ if (!platformMessageId) fallbackOnlyMessageIndexes.set(fallbackKey, retainedIndex);
836
1428
  }
837
1429
  });
838
1430
  }
@@ -842,11 +1434,13 @@ function buildMergedOutput(dataSources, outputName) {
842
1434
  platform: source.getMeta()?.platform || "unknown",
843
1435
  messageCount: source.getMessageCount()
844
1436
  }));
845
- const uniquePlatforms = [...new Set(metas.map(({ meta }) => meta?.platform || "unknown"))];
846
1437
  const platform = uniquePlatforms.length === 1 ? uniquePlatforms[0] : "mixed";
847
1438
  const groupIds = new Set(metas.map(({ meta }) => meta?.groupId).filter(Boolean));
848
1439
  const groupId = groupIds.size === 1 ? metas.find(({ meta }) => meta?.groupId)?.meta?.groupId : void 0;
849
1440
  const groupAvatar = groupId ? metas.filter(({ meta }) => meta?.groupId === groupId).pop()?.meta?.groupAvatar : void 0;
1441
+ const ownerIds = new Set(metas.map(({ meta }) => meta?.ownerId).filter((id) => Boolean(id)));
1442
+ const ownerCandidate = uniquePlatforms.length === 1 && ownerIds.size === 1 ? [...ownerIds][0] : void 0;
1443
+ const ownerId = ownerCandidate && memberMap.has(ownerCandidate) ? ownerCandidate : void 0;
850
1444
  const chatLabData = {
851
1445
  chatlab: {
852
1446
  version: CHATLAB_FORMAT_VERSION,
@@ -860,7 +1454,8 @@ function buildMergedOutput(dataSources, outputName) {
860
1454
  type: metas[0]?.meta?.type || "group",
861
1455
  sources,
862
1456
  groupId,
863
- groupAvatar
1457
+ groupAvatar,
1458
+ ownerId
864
1459
  },
865
1460
  members: Array.from(memberMap.values()),
866
1461
  messages: mergedMessages
@@ -882,7 +1477,7 @@ function* serializeChatLabToJsonl(data) {
882
1477
  }
883
1478
 
884
1479
  // ../../packages/node-runtime/src/services/analytics.ts
885
- import * as path4 from "path";
1480
+ import * as path6 from "path";
886
1481
  import { randomUUID as randomUUID2 } from "crypto";
887
1482
  var DEFAULT_DATA = {
888
1483
  lastReportDate: null,
@@ -994,7 +1589,7 @@ var AnalyticsService = class {
994
1589
  // Daily-active deduplication is intentionally process-local unless concurrent runtimes become supported.
995
1590
  dailyActivePromise = null;
996
1591
  constructor(systemDir, options) {
997
- this.dataPath = path4.join(systemDir, "analytics.json");
1592
+ this.dataPath = path6.join(systemDir, "analytics.json");
998
1593
  this.options = {
999
1594
  ...options,
1000
1595
  umami: options.umami ?? getUmamiConfigFromEnv()
@@ -1156,8 +1751,8 @@ var AnalyticsService = class {
1156
1751
  };
1157
1752
 
1158
1753
  // ../../packages/node-runtime/src/services/demo-import.ts
1159
- import * as fs4 from "fs";
1160
- import * as path5 from "path";
1754
+ import * as fs6 from "fs";
1755
+ import * as path7 from "path";
1161
1756
  var DEMO_BASE_URL = "https://chatlab.fun/assets/demo";
1162
1757
  var DEMO_FILES = ["demo-group.json", "demo-private-B-wukong.json"];
1163
1758
  async function importDemoSessions(options) {
@@ -1189,8 +1784,8 @@ async function importDemoSessions(options) {
1189
1784
  }
1190
1785
  const rebased = rebaseChatLabDemoDocuments(downloaded, options.now?.() ?? /* @__PURE__ */ new Date(), options.targetTimeZone);
1191
1786
  const localPaths = rebased.documents.map((document, index) => {
1192
- const localPath = path5.join(createdTempDir, DEMO_FILES[index]);
1193
- fs4.writeFileSync(localPath, document, "utf8");
1787
+ const localPath = path7.join(createdTempDir, DEMO_FILES[index]);
1788
+ fs6.writeFileSync(localPath, document, "utf8");
1194
1789
  return localPath;
1195
1790
  });
1196
1791
  appLogger.info("demo-import", "Demo timeline prepared", {
@@ -1274,7 +1869,6 @@ __export(semantic_index_exports, {
1274
1869
  composeChunkId: () => composeChunkId,
1275
1870
  computeChunkerConfigHash: () => computeChunkerConfigHash,
1276
1871
  computeDbPathHash: () => computeDbPathHash,
1277
- createChatDbFtsSearcher: () => createChatDbFtsSearcher,
1278
1872
  createChatDbMessageRangeReader: () => createChatDbMessageRangeReader,
1279
1873
  createChatDbMessageSource: () => createChatDbMessageSource,
1280
1874
  createEmbedder: () => createEmbedder,
@@ -1287,24 +1881,22 @@ __export(semantic_index_exports, {
1287
1881
  deriveParentId: () => deriveParentId,
1288
1882
  embedding: () => embedding_exports,
1289
1883
  estimateTokens: () => estimateTokens,
1290
- extractFtsKeywords: () => extractFtsKeywords,
1291
1884
  formatEvidenceMessages: () => formatEvidenceMessages,
1292
- hybridSearch: () => hybridSearch,
1293
1885
  isSemanticIndexConfigured: () => isSemanticIndexConfigured,
1294
1886
  isSemanticVoid: () => isSemanticVoid,
1295
1887
  parseParentBounds: () => parseParentBounds,
1296
1888
  persistSemanticIndexConfig: () => persistSemanticIndexConfig,
1297
- reciprocalRankFusion: () => reciprocalRankFusion,
1298
1889
  resolveModelId: () => resolveModelId,
1299
1890
  resolveSemanticIndexApiKeySet: () => resolveSemanticIndexApiKeySet,
1300
1891
  runWarmup: () => runWarmup,
1892
+ semanticSearch: () => semanticSearch,
1301
1893
  snapshotPathProvider: () => snapshotPathProvider,
1302
1894
  vecTableName: () => vecTableName,
1303
1895
  vecTableSchema: () => vecTableSchema
1304
1896
  });
1305
1897
 
1306
1898
  // ../../packages/node-runtime/src/semantic-index/worker-client.ts
1307
- import path6 from "path";
1899
+ import path8 from "path";
1308
1900
 
1309
1901
  // ../../packages/node-runtime/src/semantic-index/worker-thread-transport.ts
1310
1902
  import { Worker } from "worker_threads";
@@ -1686,7 +2278,7 @@ function createSemanticIndexWorkerClient(options) {
1686
2278
  }
1687
2279
  function createSemanticIndexWorkerRuntimeClient(options) {
1688
2280
  const configStore = new SemanticIndexConfigStore(
1689
- path6.join(options.pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE)
2281
+ path8.join(options.pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE)
1690
2282
  );
1691
2283
  return createSemanticIndexWorkerClient({
1692
2284
  configStore,
@@ -1741,7 +2333,23 @@ export {
1741
2333
  NodePathProvider,
1742
2334
  getDefaultNodeUserDataDir,
1743
2335
  getSystemLogsDir,
2336
+ initNlpDir,
2337
+ getNlpDir,
2338
+ getJieba,
2339
+ clearJiebaInstance,
2340
+ segment,
2341
+ batchSegmentWithFrequency,
2342
+ collectPosTagStats,
2343
+ getPosTagDefinitions,
1744
2344
  createJiebaNlpProvider,
2345
+ computeWordFrequency,
2346
+ segmentText,
2347
+ isDictDownloaded,
2348
+ getDictList,
2349
+ loadDictBuffer,
2350
+ downloadDict,
2351
+ deleteDict,
2352
+ ensureDefaultDict,
1745
2353
  PreferencesManager,
1746
2354
  checkConflictsFromSources,
1747
2355
  buildMergedOutput,
@@ -1751,4 +2359,4 @@ export {
1751
2359
  createSemanticIndexWorkerRuntimeClient,
1752
2360
  semantic_index_exports
1753
2361
  };
1754
- //# sourceMappingURL=chunk-2XCNTG4O.mjs.map
2362
+ //# sourceMappingURL=chunk-DC7IUDNV.mjs.map