chatlab-cli 0.36.2 → 0.37.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.
- package/dist/{chat-command-SEV6N3AR.mjs → chat-command-YAFRLHMQ.mjs} +15 -16
- package/dist/{chat-command-SEV6N3AR.mjs.map → chat-command-YAFRLHMQ.mjs.map} +1 -1
- package/dist/{chunk-6EZPD3XJ.mjs → chunk-2PY4XCH3.mjs} +6 -3
- package/dist/chunk-2PY4XCH3.mjs.map +1 -0
- package/dist/{chunk-HWIXK7SU.mjs → chunk-73TJ2S7W.mjs} +1643 -3
- package/dist/chunk-73TJ2S7W.mjs.map +1 -0
- package/dist/{chunk-YUH3H3QW.mjs → chunk-ARIBCH47.mjs} +96 -5
- package/dist/chunk-ARIBCH47.mjs.map +1 -0
- package/dist/{chunk-WRBFV2GX.mjs → chunk-FPVRSXQ4.mjs} +49 -55
- package/dist/chunk-FPVRSXQ4.mjs.map +1 -0
- package/dist/{chunk-VREVDZ4Q.mjs → chunk-GKCIRKOH.mjs} +1934 -35
- package/dist/chunk-GKCIRKOH.mjs.map +1 -0
- package/dist/{chunk-TJ3NOBVI.mjs → chunk-JWJREWVN.mjs} +119 -288
- package/dist/chunk-JWJREWVN.mjs.map +1 -0
- package/dist/{chunk-TWH422HC.mjs → chunk-MMTEGUQA.mjs} +3 -3
- package/dist/{chunk-VUYWUHWW.mjs → chunk-N4SB6QZV.mjs} +3 -3
- package/dist/chunk-N4SB6QZV.mjs.map +1 -0
- package/dist/{chunk-TRGR3KKJ.mjs → chunk-OTYNMDDL.mjs} +2643 -682
- package/dist/chunk-OTYNMDDL.mjs.map +1 -0
- package/dist/{chunk-CH5YAZ4Q.mjs → chunk-PDQV4RUA.mjs} +2 -2
- package/dist/{chunk-WJQOXOMR.mjs → chunk-UP6JIV3S.mjs} +285 -41
- package/dist/chunk-UP6JIV3S.mjs.map +1 -0
- package/dist/chunk-WDLO7OLX.mjs +101 -0
- package/dist/chunk-WDLO7OLX.mjs.map +1 -0
- package/dist/cli.mjs +9 -9
- package/dist/contacts-worker.mjs +2 -2
- package/dist/global-insight-worker.mjs +1 -1
- package/dist/{http-237XYNRT.mjs → http-LPFPVTBC.mjs} +12 -12
- package/dist/{import-R2E2TFAK.mjs → import-T4WX2ZJL.mjs} +7 -7
- package/dist/index.mjs +12 -12
- package/dist/{mcp-BED4AARV.mjs → mcp-SMF3NWIN.mjs} +8 -8
- package/dist/people-relationships-worker.mjs +2 -2
- package/dist/semantic-index-worker.mjs +1 -1
- package/dist/{src-AYMALWU5.mjs → src-EGLLMTAU.mjs} +44 -14
- package/dist/{stream-import-RXNSOOYH.mjs → stream-import-2BXJTTCE.mjs} +7 -7
- package/dist/{worker-runtime-JGAZU7MC.mjs → worker-runtime-RDLSPXFT.mjs} +5 -5
- package/dist-cli-web/assets/AISettingsTab-CEhrXlYR.js +1 -0
- package/dist-cli-web/assets/{AboutTab-DCM1-glA.js → AboutTab-Bbu0SsMM.js} +1 -1
- package/dist-cli-web/assets/ActionToolsPanel-DDNXXH8v.css +1 -0
- package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-IB1yZxCE.js +75 -0
- package/dist-cli-web/assets/{Alert-D-egBWcb.js → Alert-510jTN3l.js} +1 -1
- package/dist-cli-web/assets/ApiSettingsTab-Bi1GRMN4.js +2 -0
- package/dist-cli-web/assets/BatchManageTab-DhnqMCSM.js +1 -0
- package/dist-cli-web/assets/{BufferResource-BE2_2bZK.js → BufferResource-CKZxnACp.js} +1 -1
- package/dist-cli-web/assets/{CanvasRenderer-DDKdvEYQ.js → CanvasRenderer-BeK5POBy.js} +1 -1
- package/dist-cli-web/assets/CardDecoration-RuBA3N2j.js +1 -0
- package/dist-cli-web/assets/ChatRecordDrawer-BY_-lIEj.js +1 -0
- package/dist-cli-web/assets/{ChatRecordWorkspace-DqUI4QiU.js → ChatRecordWorkspace-7prKd-Tq.js} +1 -1
- package/dist-cli-web/assets/Checkbox-_eH64bb0.js +1 -0
- package/dist-cli-web/assets/DataDirCleanupNotice-BHaXwlfr.js +1 -0
- package/dist-cli-web/assets/{DatePicker.vue_vue_type_script_setup_true_lang-sPDoPzZ9.js → DatePicker.vue_vue_type_script_setup_true_lang-D9Ul_GYz.js} +1 -1
- package/dist-cli-web/assets/{DebugToolsPanel-BIHA6jw4.js → DebugToolsPanel-ByKItGrS.js} +1 -1
- package/dist-cli-web/assets/EChart.vue_vue_type_script_setup_true_lang-DubnuUXI.js +1 -0
- package/dist-cli-web/assets/EChartBar.vue_vue_type_script_setup_true_lang-DKNCZuFm.js +1 -0
- package/dist-cli-web/assets/EChartLine.vue_vue_type_script_setup_true_lang-Fl7xAOSR.js +1 -0
- package/dist-cli-web/assets/EChartWordcloud-C6XM4KOX.js +1 -0
- package/dist-cli-web/assets/FileDropZone.vue_vue_type_script_setup_true_lang-DYDdRSNE.js +1 -0
- package/dist-cli-web/assets/{Filter-kO4dIDJ2.js → Filter-CByj_rCR.js} +1 -1
- package/dist-cli-web/assets/GlobalTaskBar-BdELjzrs.js +1 -0
- package/dist-cli-web/assets/GlobalTaskBar-haLzezqY.css +1 -0
- package/dist-cli-web/assets/GroupRelationshipGalaxyView-CnzPEgUS.js +1 -0
- package/dist-cli-web/assets/Label-DCIcxkhU.js +1 -0
- package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-BBcCt_Qm.js +1 -0
- package/dist-cli-web/assets/MessageExportModal-DNU16Bs8.js +1 -0
- package/dist-cli-web/assets/PageHeader-CKeZjNNR.js +1 -0
- package/dist-cli-web/assets/PageHeader-CPLgiKT8.css +1 -0
- package/dist-cli-web/assets/{PageTabs.vue_vue_type_script_setup_true_lang-Cgungbjl.js → PageTabs.vue_vue_type_script_setup_true_lang-C8M3wNNH.js} +1 -1
- package/dist-cli-web/assets/{RelationshipGalaxyCanvas-Cod1JX9m.js → RelationshipGalaxyCanvas-CDhljGyJ.js} +3 -3
- package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-CkefzeFJ.js → RelationshipGalaxyThreeCanvas-BP8fsvvf.js} +1 -1
- package/dist-cli-web/assets/{RenderTargetSystem-SuQ7cbze.js → RenderTargetSystem-D76QrDT_.js} +1 -1
- package/dist-cli-web/assets/{RovingFocusItem-psoFYxrC.js → RovingFocusItem-BJMUQ5DJ.js} +1 -1
- package/dist-cli-web/assets/{ScreenCaptureModal-D4r9ixKh.js → ScreenCaptureModal-DkX4z-M1.js} +1 -1
- package/dist-cli-web/assets/SectionTabs.vue_vue_type_script_setup_true_lang-qFhJO5B0.js +1 -0
- package/dist-cli-web/assets/Select-B5oJc5y1.js +1 -0
- package/dist-cli-web/assets/SettingsModal-BO-QKQ2h.js +2 -0
- package/dist-cli-web/assets/{StorageTab-DhSJ-0Ir.js → StorageTab-CPPXXaRm.js} +1 -1
- package/dist-cli-web/assets/Switch-CDk3LZ4_.js +1 -0
- package/dist-cli-web/assets/Tabs-BMq9wODW.js +1 -0
- package/dist-cli-web/assets/{Tabs-Bnm_Q7yW.js → Tabs-DqWTBV8x.js} +1 -1
- package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-D2outEOV.js +1 -0
- package/dist-cli-web/assets/{TimeSelect.vue_vue_type_script_setup_true_lang-Cjz8Wpg-.js → TimeSelect.vue_vue_type_script_setup_true_lang-BcflxP_U.js} +1 -1
- package/dist-cli-web/assets/{VisuallyHiddenInput-Bo_XwaLu.js → VisuallyHiddenInput-BAFpkxMI.js} +1 -1
- package/dist-cli-web/assets/{WebGLRenderer-D1x9WZRj.js → WebGLRenderer-DjCPvR_O.js} +1 -1
- package/dist-cli-web/assets/{WebGPURenderer-37Nw-tLm.js → WebGPURenderer-C2B_u03z.js} +1 -1
- package/dist-cli-web/assets/aiChat--CR4g2M7.js +8 -0
- package/dist-cli-web/assets/base-F6s0vTrr.js +1 -0
- package/dist-cli-web/assets/{browserAll-CWrz_-4U.js → browserAll-DiUAZg4L.js} +1 -1
- package/dist-cli-web/assets/chatMessages-BGRbSlvD.css +1 -0
- package/dist-cli-web/assets/chatMessages-Bd33SSXI.js +147 -0
- package/dist-cli-web/assets/cli-web-DoibEdj2.js +1 -0
- package/dist-cli-web/assets/{fetch-CkXgOxcu.js → fetch-4nh1LaVF.js} +3 -3
- package/dist-cli-web/assets/fetch-7lUvXbhU.js +1 -0
- package/dist-cli-web/assets/{fetch-CFpLXWTe.js → fetch-BEaW1j5H.js} +1 -1
- package/dist-cli-web/assets/{fetch-CgA1peJD.js → fetch-B_YFABxI.js} +1 -1
- package/dist-cli-web/assets/{fetch-BGmCCyzz.js → fetch-BeX69kpS.js} +1 -1
- package/dist-cli-web/assets/{fetch-Du-wU0OO.js → fetch-CRocA-4C.js} +1 -1
- package/dist-cli-web/assets/{fetch-DPSWt6yX.js → fetch-CXM-cjDS.js} +1 -1
- package/dist-cli-web/assets/{fetch-qd2FgBWQ.js → fetch-CXqeAbnY.js} +1 -1
- package/dist-cli-web/assets/{fetch-6pcVxB9h.js → fetch-CxVcHNid.js} +1 -1
- package/dist-cli-web/assets/fetch-DBpDMy29.js +1 -0
- package/dist-cli-web/assets/{fetch-Gu4qQjUB.js → fetch-DKBBS9Op.js} +1 -1
- package/dist-cli-web/assets/{fetch-DZ6OF-xW.js → fetch-epFXmPIQ.js} +1 -1
- package/dist-cli-web/assets/{host-D8zlA62K.js → host-C4rU8pKA.js} +1 -1
- package/dist-cli-web/assets/{host-CWsAWKgy.js → host-CtSqR_Zm.js} +1 -1
- package/dist-cli-web/assets/{index-Dy0fRMDf.js → index-4DVZ2g0M.js} +10 -10
- package/dist-cli-web/assets/index-B1oj9KDv.js +135 -0
- package/dist-cli-web/assets/index-BFH1j_R9.js +1 -0
- package/dist-cli-web/assets/index-BZpR015e.css +1 -0
- package/dist-cli-web/assets/index-BgelKQTW.css +1 -0
- package/dist-cli-web/assets/{index-v6DlL4ue.js → index-BvMAw3Zd.js} +1 -1
- package/dist-cli-web/assets/index-C1A1FwpO.css +1 -0
- package/dist-cli-web/assets/index-CIIyg_o9.css +1 -0
- package/dist-cli-web/assets/index-CaYtkONV.js +64 -0
- package/dist-cli-web/assets/index-CuCQ1d9f.js +1 -0
- package/dist-cli-web/assets/index-D3Hyjv4e.js +137 -0
- package/dist-cli-web/assets/index-D8cHMqV-.js +2 -0
- package/dist-cli-web/assets/index-DlHuILT_.js +1 -0
- package/dist-cli-web/assets/index-R4LqAnx9.js +2 -0
- package/dist-cli-web/assets/index-_m1mf1jm.js +1 -0
- package/dist-cli-web/assets/{index-a7CT8DQm.js → index-ekvsNmfe.js} +1 -1
- package/dist-cli-web/assets/index-mXe1LssU.js +1 -0
- package/dist-cli-web/assets/index-mbLXPIyC.js +1 -0
- package/dist-cli-web/assets/{people-page-header-Czt9e1jv.js → people-page-header-BbYSSpsO.js} +1 -1
- package/dist-cli-web/assets/{prompt-DGazz143.js → prompt-C-ofJ7t_.js} +1 -1
- package/dist-cli-web/assets/{semantic-index-config-builder-BkFHzIUy.js → semantic-index-config-builder-D3TYVYC-.js} +1 -1
- package/dist-cli-web/assets/service-BYOUkk7V.js +1 -0
- package/dist-cli-web/assets/{service-CAp0YJoS.js → service-CppETV4a.js} +1 -1
- package/dist-cli-web/assets/service-tEx-5LUL.js +1 -0
- package/dist-cli-web/assets/useFormControl-CrWi_6Hn.js +1 -0
- package/dist-cli-web/assets/useTable-Clk7ecq-.js +1 -0
- package/dist-cli-web/assets/{useToast-YAHDkKDN.js → useToast-B5LaA6c-.js} +1 -1
- package/dist-cli-web/assets/{vendor-echarts-hTLhTs52.js → vendor-echarts-C9ugTmum.js} +1 -1
- package/dist-cli-web/assets/{vendor-echarts-wordcloud-B8CF_NgP.js → vendor-echarts-wordcloud-DNiU8Psq.js} +1 -1
- package/dist-cli-web/assets/{webworkerAll-CvSp2Y8j.js → webworkerAll-CyA4b4LT.js} +1 -1
- package/dist-cli-web/index.html +2 -2
- package/package.json +3 -3
- package/dist/chunk-6E46U4H2.mjs +0 -41
- package/dist/chunk-6E46U4H2.mjs.map +0 -1
- package/dist/chunk-6EZPD3XJ.mjs.map +0 -1
- package/dist/chunk-HWIXK7SU.mjs.map +0 -1
- package/dist/chunk-TJ3NOBVI.mjs.map +0 -1
- package/dist/chunk-TRGR3KKJ.mjs.map +0 -1
- package/dist/chunk-VREVDZ4Q.mjs.map +0 -1
- package/dist/chunk-VUYWUHWW.mjs.map +0 -1
- package/dist/chunk-WJQOXOMR.mjs.map +0 -1
- package/dist/chunk-WRBFV2GX.mjs.map +0 -1
- package/dist/chunk-YUH3H3QW.mjs.map +0 -1
- package/dist-cli-web/assets/AISettingsTab-VgI7PiJd.js +0 -1
- package/dist-cli-web/assets/ActionToolsPanel-BTGgwXVD.css +0 -1
- package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-a7RMjjve.js +0 -112
- package/dist-cli-web/assets/ApiSettingsTab-Co3LWf1f.js +0 -2
- package/dist-cli-web/assets/BatchManageTab-2LrckmQm.js +0 -1
- package/dist-cli-web/assets/ChatRecordDrawer-DRhztqLG.js +0 -1
- package/dist-cli-web/assets/Checkbox-EfpQmv4A.js +0 -1
- package/dist-cli-web/assets/DataDirCleanupNotice-BYIiR58J.js +0 -1
- package/dist-cli-web/assets/EChart.vue_vue_type_script_setup_true_lang-DryMSoCz.js +0 -1
- package/dist-cli-web/assets/EChartBar.vue_vue_type_script_setup_true_lang-BdFW-hvt.js +0 -1
- package/dist-cli-web/assets/EChartLine.vue_vue_type_script_setup_true_lang-C69Kdw9X.js +0 -1
- package/dist-cli-web/assets/EChartWordcloud-DM7UEHdK.js +0 -1
- package/dist-cli-web/assets/GlobalTaskBar-BZS_h0zC.js +0 -1
- package/dist-cli-web/assets/GlobalTaskBar-COW_uBX6.css +0 -1
- package/dist-cli-web/assets/GroupRelationshipGalaxyView--WIaHx_F.js +0 -1
- package/dist-cli-web/assets/Label-CIuDXyeM.js +0 -1
- package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-B_7VHxTx.js +0 -1
- package/dist-cli-web/assets/MessageExportModal-BQsIpyxv.js +0 -1
- package/dist-cli-web/assets/PageHeader-DxhQHF_0.js +0 -1
- package/dist-cli-web/assets/PageHeader-XIXMTomn.css +0 -1
- package/dist-cli-web/assets/SectionTabs.vue_vue_type_script_setup_true_lang-v_7aRBgM.js +0 -1
- package/dist-cli-web/assets/Select-CxurgslB.js +0 -1
- package/dist-cli-web/assets/SettingsModal-DBZucuf6.js +0 -2
- package/dist-cli-web/assets/Switch-3D81G6uV.js +0 -1
- package/dist-cli-web/assets/Tabs-DB603EEu.js +0 -1
- package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-BDTdkH5i.js +0 -1
- package/dist-cli-web/assets/aiChat-BCzYQeZq.js +0 -8
- package/dist-cli-web/assets/cli-web-C-8N0CJh.js +0 -1
- package/dist-cli-web/assets/fetch-B69OkB6B.js +0 -1
- package/dist-cli-web/assets/fetch-z9M9FcIC.js +0 -1
- package/dist-cli-web/assets/index-BGd4ZfhO.js +0 -2
- package/dist-cli-web/assets/index-BYZF1ngN.js +0 -1
- package/dist-cli-web/assets/index-BcuwlCin.css +0 -1
- package/dist-cli-web/assets/index-BnaLj2dx.css +0 -1
- package/dist-cli-web/assets/index-Bq5q3kSU.js +0 -36
- package/dist-cli-web/assets/index-CPR3jdC2.js +0 -137
- package/dist-cli-web/assets/index-D3AjvaOX.js +0 -1
- package/dist-cli-web/assets/index-DAitScUA.js +0 -1
- package/dist-cli-web/assets/index-DBSMWAJ0.js +0 -1
- package/dist-cli-web/assets/index-DTn9htw-.js +0 -64
- package/dist-cli-web/assets/index-DV_rWxcH.js +0 -1
- package/dist-cli-web/assets/index-DxhAGqYM.css +0 -1
- package/dist-cli-web/assets/index-I0mIcDpE.css +0 -1
- package/dist-cli-web/assets/index-onwNShRw.js +0 -2
- package/dist-cli-web/assets/service-BMqwB74f.js +0 -1
- package/dist-cli-web/assets/service-CUQ8iHdS.js +0 -1
- package/dist-cli-web/assets/useFormControl-DcSMUc2d.js +0 -1
- package/dist-cli-web/assets/useTable-Clmq3sge.js +0 -1
- /package/dist/{chunk-TWH422HC.mjs.map → chunk-MMTEGUQA.mjs.map} +0 -0
- /package/dist/{chunk-CH5YAZ4Q.mjs.map → chunk-PDQV4RUA.mjs.map} +0 -0
- /package/dist/{http-237XYNRT.mjs.map → http-LPFPVTBC.mjs.map} +0 -0
- /package/dist/{import-R2E2TFAK.mjs.map → import-T4WX2ZJL.mjs.map} +0 -0
- /package/dist/{mcp-BED4AARV.mjs.map → mcp-SMF3NWIN.mjs.map} +0 -0
- /package/dist/{src-AYMALWU5.mjs.map → src-EGLLMTAU.mjs.map} +0 -0
- /package/dist/{stream-import-RXNSOOYH.mjs.map → stream-import-2BXJTTCE.mjs.map} +0 -0
- /package/dist/{worker-runtime-JGAZU7MC.mjs.map → worker-runtime-RDLSPXFT.mjs.map} +0 -0
|
@@ -3947,7 +3947,21 @@ var PUNCTUATION_REGEX = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;
|
|
|
3947
3947
|
var URL_REGEX = /https?:\/\/[^\s]+/g;
|
|
3948
3948
|
var MENTION_REGEX = /@[^\s@]+/g;
|
|
3949
3949
|
var PURE_NUMBER_REGEX = /^\d+$/;
|
|
3950
|
-
var
|
|
3950
|
+
var MEDIA_PLACEHOLDER_NAME_PATTERN = "(?:\u56FE\u7247|\u89C6\u9891|\u8BED\u97F3|\u6587\u4EF6|\u52A8\u753B\u8868\u60C5|\u8868\u60C5|\u94FE\u63A5|\u4F4D\u7F6E|\u5730\u7406\u4F4D\u7F6E|\u540D\u7247|\u7EA2\u5305|\u8F6C\u8D26|\u97F3\u4E50|Image|Photo|Video|Voice|Audio|File|Sticker|Link|Location)";
|
|
3951
|
+
var MEDIA_PLACEHOLDER_DURATION_PATTERN = "(?:\\s+\\d+(?:\\.\\d+)?\\s*(?:\u79D2|s))?";
|
|
3952
|
+
var SYSTEM_PLACEHOLDER_REGEX = new RegExp(
|
|
3953
|
+
`\\[${MEDIA_PLACEHOLDER_NAME_PATTERN}${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]`,
|
|
3954
|
+
"gi"
|
|
3955
|
+
);
|
|
3956
|
+
var MEDIA_PLACEHOLDER_ONLY_REGEX = new RegExp(
|
|
3957
|
+
`^\\[${MEDIA_PLACEHOLDER_NAME_PATTERN}${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]$`,
|
|
3958
|
+
"i"
|
|
3959
|
+
);
|
|
3960
|
+
var VOICE_TRANSCRIPTION_PREFIX_REGEX = new RegExp(
|
|
3961
|
+
`^\\s*\\[(?:\u8BED\u97F3|Voice|Audio)${MEDIA_PLACEHOLDER_DURATION_PATTERN}\\]\\s*`,
|
|
3962
|
+
"i"
|
|
3963
|
+
);
|
|
3964
|
+
var SYSTEM_MESSAGE_PREFIX_REGEX = /^\s*(?:\[(?:系统(?:消息)?|分享内容|system(?:\s+message)?)\]|【(?:系统(?:消息)?|分享内容|system(?:\s+message)?)】)\s*/iu;
|
|
3951
3965
|
var BRACKET_EMOJI_PLACEHOLDER_REGEX = /(?:\[|【)([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Letter}\p{Number}_-]{1,16})(?:\]|】)/gu;
|
|
3952
3966
|
var CJK_TEXT_REGEX = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
3953
3967
|
var BRACKET_EMOJI_MAP = {
|
|
@@ -4040,6 +4054,18 @@ function cleanText(text) {
|
|
|
4040
4054
|
return CJK_TEXT_REGEX.test(name) ? " " : match;
|
|
4041
4055
|
}).replace(EMOJI_REGEX, " ").replace(EMOJI_VARIATION_SELECTOR_REGEX, " ").replace(PUNCTUATION_REGEX, " ").replace(/\s+/g, " ").trim();
|
|
4042
4056
|
}
|
|
4057
|
+
function stripMediaPlaceholders(text) {
|
|
4058
|
+
return text.replace(SYSTEM_PLACEHOLDER_REGEX, " ");
|
|
4059
|
+
}
|
|
4060
|
+
function stripVoiceTranscriptionPrefix(text) {
|
|
4061
|
+
return text.replace(VOICE_TRANSCRIPTION_PREFIX_REGEX, "").trim();
|
|
4062
|
+
}
|
|
4063
|
+
function isSystemMessageContent(text) {
|
|
4064
|
+
return SYSTEM_MESSAGE_PREFIX_REGEX.test(text);
|
|
4065
|
+
}
|
|
4066
|
+
function isMediaPlaceholderContent(text) {
|
|
4067
|
+
return MEDIA_PLACEHOLDER_ONLY_REGEX.test(text.trim());
|
|
4068
|
+
}
|
|
4043
4069
|
function isValidWord(word, locale, minLength, enableStopwords, isStopwordFn) {
|
|
4044
4070
|
if (!word || word.trim().length === 0) return false;
|
|
4045
4071
|
if (PURE_NUMBER_REGEX.test(word)) return false;
|
|
@@ -4118,6 +4144,14 @@ function buildMsgConditions(options) {
|
|
|
4118
4144
|
if (options?.senderId != null) {
|
|
4119
4145
|
conds.push("msg.sender_id = ?");
|
|
4120
4146
|
params.push(options.senderId);
|
|
4147
|
+
} else if (options?.senderIds) {
|
|
4148
|
+
const senderIds = [...new Set(options.senderIds)];
|
|
4149
|
+
if (senderIds.length === 0) {
|
|
4150
|
+
conds.push("1 = 0");
|
|
4151
|
+
} else {
|
|
4152
|
+
conds.push(`msg.sender_id IN (${senderIds.map(() => "?").join(", ")})`);
|
|
4153
|
+
params.push(...senderIds);
|
|
4154
|
+
}
|
|
4121
4155
|
}
|
|
4122
4156
|
if (options?.memberId != null) {
|
|
4123
4157
|
conds.push("msg.sender_id = ?");
|
|
@@ -4332,6 +4366,18 @@ function getLaughAnalysis(db, filter, keywords) {
|
|
|
4332
4366
|
};
|
|
4333
4367
|
}
|
|
4334
4368
|
var DEFAULT_CLUSTER_OPTIONS = { lookAhead: 3, decaySeconds: 120, topEdges: 100 };
|
|
4369
|
+
var MAX_CLUSTER_LOOK_AHEAD = 10;
|
|
4370
|
+
var DEFAULT_SELECTED_PROXIMITY_MAX_GAP_SECONDS = 1800;
|
|
4371
|
+
var CO_OCCURRENCE_MESSAGE_BATCH_SIZE = 2e4;
|
|
4372
|
+
function resolveClusterOptions(options) {
|
|
4373
|
+
const requestedLookAhead = options?.lookAhead ?? DEFAULT_CLUSTER_OPTIONS.lookAhead;
|
|
4374
|
+
const lookAhead = Number.isFinite(requestedLookAhead) ? Math.min(MAX_CLUSTER_LOOK_AHEAD, Math.max(0, Math.ceil(requestedLookAhead))) : DEFAULT_CLUSTER_OPTIONS.lookAhead;
|
|
4375
|
+
return {
|
|
4376
|
+
lookAhead,
|
|
4377
|
+
decaySeconds: options?.decaySeconds ?? DEFAULT_CLUSTER_OPTIONS.decaySeconds,
|
|
4378
|
+
topEdges: options?.topEdges ?? DEFAULT_CLUSTER_OPTIONS.topEdges
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4335
4381
|
function roundNum(value, digits = 4) {
|
|
4336
4382
|
const factor = 10 ** digits;
|
|
4337
4383
|
return Math.round(value * factor) / factor;
|
|
@@ -4340,44 +4386,222 @@ function clusterPairKey(aId, bId) {
|
|
|
4340
4386
|
return aId < bId ? `${aId}-${bId}` : `${bId}-${aId}`;
|
|
4341
4387
|
}
|
|
4342
4388
|
function accumulateCoOccurrencePairs(messages, options) {
|
|
4343
|
-
const opts =
|
|
4344
|
-
const
|
|
4345
|
-
const
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
const anchor = messages[i];
|
|
4389
|
+
const opts = resolveClusterOptions(options);
|
|
4390
|
+
const pairStatsBySource = /* @__PURE__ */ new Map();
|
|
4391
|
+
const pairs = [];
|
|
4392
|
+
for (let index = 0; index < messages.length - 1; index++) {
|
|
4393
|
+
const anchor = messages[index];
|
|
4349
4394
|
const seenPartners = /* @__PURE__ */ new Set();
|
|
4350
4395
|
let partnersFound = 0;
|
|
4351
|
-
for (let
|
|
4352
|
-
const candidate = messages[
|
|
4396
|
+
for (let nextIndex = index + 1; nextIndex < messages.length && partnersFound < opts.lookAhead; nextIndex++) {
|
|
4397
|
+
const candidate = messages[nextIndex];
|
|
4353
4398
|
if (candidate.senderId === anchor.senderId || seenPartners.has(candidate.senderId)) continue;
|
|
4354
4399
|
seenPartners.add(candidate.senderId);
|
|
4355
4400
|
partnersFound++;
|
|
4356
|
-
const
|
|
4357
|
-
const decayWeight = Math.exp(-deltaSeconds / opts.decaySeconds);
|
|
4401
|
+
const decayWeight = Math.exp(-(candidate.ts - anchor.ts) / opts.decaySeconds);
|
|
4358
4402
|
const positionWeight = 1 - (partnersFound - 1) * 0.2;
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4403
|
+
applyCoOccurrencePair(
|
|
4404
|
+
anchor.senderId,
|
|
4405
|
+
candidate.senderId,
|
|
4406
|
+
candidate.ts,
|
|
4407
|
+
decayWeight * positionWeight,
|
|
4408
|
+
pairStatsBySource,
|
|
4409
|
+
pairs
|
|
4410
|
+
);
|
|
4364
4411
|
}
|
|
4365
4412
|
}
|
|
4413
|
+
return pairs;
|
|
4414
|
+
}
|
|
4415
|
+
function accumulateCoOccurrencePairBatches(batches, options) {
|
|
4416
|
+
const senderChunks = [];
|
|
4417
|
+
const timestampChunks = [];
|
|
4418
|
+
let messageCount = 0;
|
|
4419
|
+
for (const batch of batches) {
|
|
4420
|
+
if (batch.length === 0) continue;
|
|
4421
|
+
const senderIds2 = new Float64Array(batch.length);
|
|
4422
|
+
const timestamps2 = new Float64Array(batch.length);
|
|
4423
|
+
for (let index = 0; index < batch.length; index++) {
|
|
4424
|
+
senderIds2[index] = batch[index].senderId;
|
|
4425
|
+
timestamps2[index] = batch[index].ts;
|
|
4426
|
+
}
|
|
4427
|
+
senderChunks.push(senderIds2);
|
|
4428
|
+
timestampChunks.push(timestamps2);
|
|
4429
|
+
messageCount += batch.length;
|
|
4430
|
+
}
|
|
4431
|
+
const senderIds = mergeNumberChunks(senderChunks, messageCount);
|
|
4432
|
+
const timestamps = mergeNumberChunks(timestampChunks, messageCount);
|
|
4433
|
+
senderChunks.length = 0;
|
|
4434
|
+
timestampChunks.length = 0;
|
|
4435
|
+
return accumulateCoOccurrenceColumns(senderIds, timestamps, options);
|
|
4436
|
+
}
|
|
4437
|
+
function mergeNumberChunks(chunks, totalLength) {
|
|
4438
|
+
if (chunks.length === 1) return chunks[0];
|
|
4439
|
+
const merged = new Float64Array(totalLength);
|
|
4440
|
+
let offset = 0;
|
|
4441
|
+
for (const chunk of chunks) {
|
|
4442
|
+
merged.set(chunk, offset);
|
|
4443
|
+
offset += chunk.length;
|
|
4444
|
+
}
|
|
4445
|
+
return merged;
|
|
4446
|
+
}
|
|
4447
|
+
function accumulateCoOccurrenceColumns(senderIds, timestamps, options) {
|
|
4448
|
+
const opts = resolveClusterOptions(options);
|
|
4449
|
+
const lookAhead = opts.lookAhead;
|
|
4450
|
+
const candidateIndexes = buildDistinctSpeakerLookAhead(senderIds.length, (index) => senderIds[index], lookAhead);
|
|
4451
|
+
const pairStatsBySource = /* @__PURE__ */ new Map();
|
|
4366
4452
|
const pairs = [];
|
|
4367
|
-
for (
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4453
|
+
for (let index = 0; index < senderIds.length - 1; index++) {
|
|
4454
|
+
for (let partnerIndex = 0; partnerIndex < lookAhead; partnerIndex++) {
|
|
4455
|
+
const candidateIndex = candidateIndexes[index * lookAhead + partnerIndex];
|
|
4456
|
+
if (candidateIndex < 0) break;
|
|
4457
|
+
const decayWeight = Math.exp(-(timestamps[candidateIndex] - timestamps[index]) / opts.decaySeconds);
|
|
4458
|
+
const positionWeight = 1 - partnerIndex * 0.2;
|
|
4459
|
+
applyCoOccurrencePair(
|
|
4460
|
+
senderIds[index],
|
|
4461
|
+
senderIds[candidateIndex],
|
|
4462
|
+
timestamps[candidateIndex],
|
|
4463
|
+
decayWeight * positionWeight,
|
|
4464
|
+
pairStatsBySource,
|
|
4465
|
+
pairs
|
|
4466
|
+
);
|
|
4467
|
+
}
|
|
4376
4468
|
}
|
|
4377
4469
|
return pairs;
|
|
4378
4470
|
}
|
|
4471
|
+
function applyCoOccurrencePair(leftSenderId, rightSenderId, timestamp, weight, pairStatsBySource, pairs) {
|
|
4472
|
+
const sourceId = Math.min(leftSenderId, rightSenderId);
|
|
4473
|
+
const targetId = Math.max(leftSenderId, rightSenderId);
|
|
4474
|
+
let pairStatsByTarget = pairStatsBySource.get(sourceId);
|
|
4475
|
+
if (!pairStatsByTarget) {
|
|
4476
|
+
pairStatsByTarget = /* @__PURE__ */ new Map();
|
|
4477
|
+
pairStatsBySource.set(sourceId, pairStatsByTarget);
|
|
4478
|
+
}
|
|
4479
|
+
let pair = pairStatsByTarget.get(targetId);
|
|
4480
|
+
if (!pair) {
|
|
4481
|
+
pair = { sourceId, targetId, rawScore: 0, coOccurrenceCount: 0, lastOccurrenceTs: 0 };
|
|
4482
|
+
pairStatsByTarget.set(targetId, pair);
|
|
4483
|
+
pairs.push(pair);
|
|
4484
|
+
}
|
|
4485
|
+
pair.rawScore += weight;
|
|
4486
|
+
pair.coOccurrenceCount++;
|
|
4487
|
+
pair.lastOccurrenceTs = Math.max(pair.lastOccurrenceTs, timestamp);
|
|
4488
|
+
}
|
|
4489
|
+
function accumulateSelectedCoOccurrencePairs(messages, selectedPairs, options) {
|
|
4490
|
+
const opts = resolveClusterOptions(options);
|
|
4491
|
+
const lookAhead = opts.lookAhead;
|
|
4492
|
+
const maxAnchorsPerPair = Math.max(0, Math.floor(options?.maxAnchorsPerPair ?? 2));
|
|
4493
|
+
const maxGapSeconds = Math.max(0, options?.maxGapSeconds ?? DEFAULT_SELECTED_PROXIMITY_MAX_GAP_SECONDS);
|
|
4494
|
+
const selectedKeys = new Set(selectedPairs.map(([left, right]) => clusterPairKey(left, right)));
|
|
4495
|
+
const candidateIndexes = buildDistinctSpeakerLookAhead(
|
|
4496
|
+
messages.length,
|
|
4497
|
+
(index) => messages[index].senderId,
|
|
4498
|
+
lookAhead
|
|
4499
|
+
);
|
|
4500
|
+
const stats = /* @__PURE__ */ new Map();
|
|
4501
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
4502
|
+
const anchor = messages[i];
|
|
4503
|
+
for (let partnerIndex = 0; partnerIndex < lookAhead; partnerIndex++) {
|
|
4504
|
+
const candidateIndex = candidateIndexes[i * lookAhead + partnerIndex];
|
|
4505
|
+
if (candidateIndex < 0) break;
|
|
4506
|
+
const candidate = messages[candidateIndex];
|
|
4507
|
+
const deltaSeconds = candidate.ts - anchor.ts;
|
|
4508
|
+
if (deltaSeconds > maxGapSeconds) break;
|
|
4509
|
+
const key = clusterPairKey(anchor.senderId, candidate.senderId);
|
|
4510
|
+
if (!selectedKeys.has(key)) continue;
|
|
4511
|
+
const decayWeight = Math.exp(-deltaSeconds / opts.decaySeconds);
|
|
4512
|
+
const positionWeight = 1 - partnerIndex * 0.2;
|
|
4513
|
+
const weight = decayWeight * positionWeight;
|
|
4514
|
+
const sourceId = Math.min(anchor.senderId, candidate.senderId);
|
|
4515
|
+
const targetId = Math.max(anchor.senderId, candidate.senderId);
|
|
4516
|
+
const current = stats.get(key) ?? {
|
|
4517
|
+
sourceId,
|
|
4518
|
+
targetId,
|
|
4519
|
+
rawScore: 0,
|
|
4520
|
+
coOccurrenceCount: 0,
|
|
4521
|
+
lastOccurrenceTs: 0,
|
|
4522
|
+
anchors: []
|
|
4523
|
+
};
|
|
4524
|
+
current.rawScore += weight;
|
|
4525
|
+
current.coOccurrenceCount++;
|
|
4526
|
+
current.lastOccurrenceTs = Math.max(current.lastOccurrenceTs, candidate.ts);
|
|
4527
|
+
retainBestSelectedAnchor(
|
|
4528
|
+
current.anchors,
|
|
4529
|
+
{
|
|
4530
|
+
messageId: candidate.messageId,
|
|
4531
|
+
relatedMessageId: anchor.messageId,
|
|
4532
|
+
fromMemberId: anchor.senderId,
|
|
4533
|
+
toMemberId: candidate.senderId,
|
|
4534
|
+
timestamp: candidate.ts,
|
|
4535
|
+
weight
|
|
4536
|
+
},
|
|
4537
|
+
maxAnchorsPerPair
|
|
4538
|
+
);
|
|
4539
|
+
stats.set(key, current);
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
return [...stats.values()].map((item) => ({
|
|
4543
|
+
sourceId: item.sourceId,
|
|
4544
|
+
targetId: item.targetId,
|
|
4545
|
+
rawScore: item.rawScore,
|
|
4546
|
+
coOccurrenceCount: item.coOccurrenceCount,
|
|
4547
|
+
lastOccurrenceTs: item.lastOccurrenceTs,
|
|
4548
|
+
anchors: item.anchors.sort((left, right) => right.weight - left.weight || right.timestamp - left.timestamp).slice(0, maxAnchorsPerPair)
|
|
4549
|
+
}));
|
|
4550
|
+
}
|
|
4551
|
+
function buildDistinctSpeakerLookAhead(messageCount, senderIdAt, lookAhead) {
|
|
4552
|
+
const candidateIndexes = new Int32Array(messageCount * lookAhead);
|
|
4553
|
+
candidateIndexes.fill(-1);
|
|
4554
|
+
if (lookAhead === 0 || messageCount === 0) return candidateIndexes;
|
|
4555
|
+
const previousIndexes = new Int32Array(messageCount);
|
|
4556
|
+
const nextIndexes = new Int32Array(messageCount);
|
|
4557
|
+
previousIndexes.fill(-1);
|
|
4558
|
+
nextIndexes.fill(-1);
|
|
4559
|
+
const activeIndexBySender = /* @__PURE__ */ new Map();
|
|
4560
|
+
let headIndex = -1;
|
|
4561
|
+
for (let index = messageCount - 1; index >= 0; index--) {
|
|
4562
|
+
const senderId = senderIdAt(index);
|
|
4563
|
+
let candidateIndex = headIndex;
|
|
4564
|
+
let found = 0;
|
|
4565
|
+
while (candidateIndex >= 0 && found < lookAhead) {
|
|
4566
|
+
if (senderIdAt(candidateIndex) !== senderId) {
|
|
4567
|
+
candidateIndexes[index * lookAhead + found] = candidateIndex;
|
|
4568
|
+
found++;
|
|
4569
|
+
}
|
|
4570
|
+
candidateIndex = nextIndexes[candidateIndex];
|
|
4571
|
+
}
|
|
4572
|
+
const previousOccurrence = activeIndexBySender.get(senderId);
|
|
4573
|
+
if (previousOccurrence !== void 0) {
|
|
4574
|
+
const previousIndex = previousIndexes[previousOccurrence];
|
|
4575
|
+
const nextIndex = nextIndexes[previousOccurrence];
|
|
4576
|
+
if (previousIndex >= 0) nextIndexes[previousIndex] = nextIndex;
|
|
4577
|
+
else headIndex = nextIndex;
|
|
4578
|
+
if (nextIndex >= 0) previousIndexes[nextIndex] = previousIndex;
|
|
4579
|
+
}
|
|
4580
|
+
previousIndexes[index] = -1;
|
|
4581
|
+
nextIndexes[index] = headIndex;
|
|
4582
|
+
if (headIndex >= 0) previousIndexes[headIndex] = index;
|
|
4583
|
+
headIndex = index;
|
|
4584
|
+
activeIndexBySender.set(senderId, index);
|
|
4585
|
+
}
|
|
4586
|
+
return candidateIndexes;
|
|
4587
|
+
}
|
|
4588
|
+
function retainBestSelectedAnchor(anchors, candidate, limit) {
|
|
4589
|
+
if (limit === 0) return;
|
|
4590
|
+
if (anchors.length < limit) {
|
|
4591
|
+
anchors.push(candidate);
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
let worstIndex = 0;
|
|
4595
|
+
for (let index = 1; index < anchors.length; index++) {
|
|
4596
|
+
if (compareSelectedAnchorQuality(anchors[index], anchors[worstIndex]) < 0) worstIndex = index;
|
|
4597
|
+
}
|
|
4598
|
+
if (compareSelectedAnchorQuality(candidate, anchors[worstIndex]) > 0) anchors[worstIndex] = candidate;
|
|
4599
|
+
}
|
|
4600
|
+
function compareSelectedAnchorQuality(left, right) {
|
|
4601
|
+
return left.weight - right.weight || left.timestamp - right.timestamp;
|
|
4602
|
+
}
|
|
4379
4603
|
function getClusterGraph(db, filter, options) {
|
|
4380
|
-
const opts =
|
|
4604
|
+
const opts = resolveClusterOptions(options);
|
|
4381
4605
|
const emptyResult2 = {
|
|
4382
4606
|
nodes: [],
|
|
4383
4607
|
links: [],
|
|
@@ -4394,28 +4618,26 @@ function getClusterGraph(db, filter, options) {
|
|
|
4394
4618
|
const memberInfo = /* @__PURE__ */ new Map();
|
|
4395
4619
|
for (const m of members)
|
|
4396
4620
|
memberInfo.set(m.id, { name: m.name, platformId: m.platformId, messageCount: m.messageCount });
|
|
4397
|
-
const
|
|
4398
|
-
let
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
)
|
|
4408
|
-
|
|
4621
|
+
const memberMsgCount = /* @__PURE__ */ new Map();
|
|
4622
|
+
let totalMessages = 0;
|
|
4623
|
+
const trackedMessageBatches = (function* () {
|
|
4624
|
+
for (const batch of queryClusterMessageBatches(db, filter)) {
|
|
4625
|
+
for (const message of batch) {
|
|
4626
|
+
memberMsgCount.set(message.senderId, (memberMsgCount.get(message.senderId) || 0) + 1);
|
|
4627
|
+
totalMessages++;
|
|
4628
|
+
}
|
|
4629
|
+
yield batch;
|
|
4630
|
+
}
|
|
4631
|
+
})();
|
|
4632
|
+
const coOccurrencePairs = accumulateCoOccurrencePairBatches(trackedMessageBatches, opts);
|
|
4633
|
+
if (totalMessages < 2)
|
|
4409
4634
|
return {
|
|
4410
4635
|
...emptyResult2,
|
|
4411
|
-
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages
|
|
4636
|
+
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages }
|
|
4412
4637
|
};
|
|
4413
|
-
const memberMsgCount = /* @__PURE__ */ new Map();
|
|
4414
|
-
for (const msg of messages) memberMsgCount.set(msg.senderId, (memberMsgCount.get(msg.senderId) || 0) + 1);
|
|
4415
|
-
const totalMessages = messages.length;
|
|
4416
4638
|
const lookAheadFactor = opts.lookAhead * 0.8;
|
|
4417
4639
|
const rawEdges = [];
|
|
4418
|
-
for (const pair of
|
|
4640
|
+
for (const pair of coOccurrencePairs) {
|
|
4419
4641
|
const aId = pair.sourceId;
|
|
4420
4642
|
const bId = pair.targetId;
|
|
4421
4643
|
const aMsgCount = memberMsgCount.get(aId) || 0;
|
|
@@ -4448,7 +4670,7 @@ function getClusterGraph(db, filter, options) {
|
|
|
4448
4670
|
if (keptEdges.length === 0)
|
|
4449
4671
|
return {
|
|
4450
4672
|
...emptyResult2,
|
|
4451
|
-
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages
|
|
4673
|
+
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages }
|
|
4452
4674
|
};
|
|
4453
4675
|
const involvedIds = /* @__PURE__ */ new Set();
|
|
4454
4676
|
for (const edge of keptEdges) {
|
|
@@ -4508,13 +4730,44 @@ function getClusterGraph(db, filter, options) {
|
|
|
4508
4730
|
communities: [],
|
|
4509
4731
|
stats: {
|
|
4510
4732
|
totalMembers: members.length,
|
|
4511
|
-
totalMessages
|
|
4733
|
+
totalMessages,
|
|
4512
4734
|
involvedMembers: involvedIds.size,
|
|
4513
4735
|
edgeCount: keptEdges.length,
|
|
4514
4736
|
communityCount: 0
|
|
4515
4737
|
}
|
|
4516
4738
|
};
|
|
4517
4739
|
}
|
|
4740
|
+
function* queryClusterMessageBatches(db, filter) {
|
|
4741
|
+
const { clause, params } = buildTimeFilter(filter, "msg");
|
|
4742
|
+
const systemMemberCondition = "COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'";
|
|
4743
|
+
const firstWhere = appendSocialConditions(clause, [systemMemberCondition]);
|
|
4744
|
+
const nextWhere = appendSocialConditions(clause, [systemMemberCondition, "(msg.ts, msg.id) > (?, ?)"]);
|
|
4745
|
+
const firstPage = db.prepare(
|
|
4746
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
4747
|
+
FROM message msg JOIN member m ON msg.sender_id = m.id
|
|
4748
|
+
${firstWhere}
|
|
4749
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
4750
|
+
LIMIT ?`
|
|
4751
|
+
);
|
|
4752
|
+
const nextPage = db.prepare(
|
|
4753
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
4754
|
+
FROM message msg JOIN member m ON msg.sender_id = m.id
|
|
4755
|
+
${nextWhere}
|
|
4756
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
4757
|
+
LIMIT ?`
|
|
4758
|
+
);
|
|
4759
|
+
let rows = firstPage.all(...params, CO_OCCURRENCE_MESSAGE_BATCH_SIZE);
|
|
4760
|
+
while (rows.length > 0) {
|
|
4761
|
+
yield rows;
|
|
4762
|
+
if (rows.length < CO_OCCURRENCE_MESSAGE_BATCH_SIZE) break;
|
|
4763
|
+
const last = rows[rows.length - 1];
|
|
4764
|
+
rows = nextPage.all(...params, last.ts, last.id, CO_OCCURRENCE_MESSAGE_BATCH_SIZE);
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
function appendSocialConditions(clause, conditions) {
|
|
4768
|
+
const existing = clause.trim().replace(/^WHERE\s+/i, "");
|
|
4769
|
+
return `WHERE ${[existing, ...conditions].filter(Boolean).join(" AND ")}`;
|
|
4770
|
+
}
|
|
4518
4771
|
|
|
4519
4772
|
// ../../packages/core/src/query/contact-queries.ts
|
|
4520
4773
|
var SYSTEM_MESSAGE_TYPES = [80, 81];
|
|
@@ -4526,6 +4779,10 @@ function isValidContactPlatformId(platformId) {
|
|
|
4526
4779
|
function resolveOwnerMember(db) {
|
|
4527
4780
|
const meta = db.prepare("SELECT owner_id FROM meta LIMIT 1").get();
|
|
4528
4781
|
if (!isValidContactPlatformId(meta?.owner_id)) return null;
|
|
4782
|
+
return resolveContactMember(db, meta.owner_id);
|
|
4783
|
+
}
|
|
4784
|
+
function resolveContactMember(db, platformId) {
|
|
4785
|
+
if (!isValidContactPlatformId(platformId)) return null;
|
|
4529
4786
|
const aliasesSelect = hasColumn(db, "member", "aliases") ? "aliases" : "NULL as aliases";
|
|
4530
4787
|
const row = db.prepare(
|
|
4531
4788
|
`SELECT
|
|
@@ -4537,7 +4794,7 @@ function resolveOwnerMember(db) {
|
|
|
4537
4794
|
FROM member m
|
|
4538
4795
|
WHERE platform_id = ? AND ${nonSystemContactMemberCondition(db, "m")}
|
|
4539
4796
|
LIMIT 1`
|
|
4540
|
-
).get(
|
|
4797
|
+
).get(platformId);
|
|
4541
4798
|
return row ? mapContactMemberRow(row) : null;
|
|
4542
4799
|
}
|
|
4543
4800
|
function getNonSystemMembersForContacts(db) {
|
|
@@ -4564,6 +4821,261 @@ function getLatestContactMessageTs(db) {
|
|
|
4564
4821
|
).get();
|
|
4565
4822
|
return row?.ts ?? null;
|
|
4566
4823
|
}
|
|
4824
|
+
function getParticipantSessionFacts(db, memberId, options = {}) {
|
|
4825
|
+
const timeFilter = createBoundedMessageTimeFilter("msg", options);
|
|
4826
|
+
const row = db.prepare(
|
|
4827
|
+
`SELECT
|
|
4828
|
+
COUNT(*) as sessionMessageCount,
|
|
4829
|
+
COALESCE(SUM(CASE WHEN msg.sender_id = ? THEN 1 ELSE 0 END), 0) as ownMessageCount,
|
|
4830
|
+
MIN(CASE WHEN msg.sender_id = ? THEN msg.ts END) as firstOwnMessageTs,
|
|
4831
|
+
MAX(CASE WHEN msg.sender_id = ? THEN msg.ts END) as lastOwnMessageTs,
|
|
4832
|
+
COUNT(DISTINCT CASE
|
|
4833
|
+
WHEN msg.sender_id = ? THEN strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')
|
|
4834
|
+
END) as activeDays
|
|
4835
|
+
FROM message msg
|
|
4836
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4837
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}${timeFilter.sql}`
|
|
4838
|
+
).get(memberId, memberId, memberId, memberId, ...timeFilter.params);
|
|
4839
|
+
const dataRange = db.prepare(
|
|
4840
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
4841
|
+
FROM message msg
|
|
4842
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4843
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}`
|
|
4844
|
+
).get();
|
|
4845
|
+
return {
|
|
4846
|
+
memberId,
|
|
4847
|
+
ownMessageCount: row?.ownMessageCount ?? 0,
|
|
4848
|
+
sessionMessageCount: row?.sessionMessageCount ?? 0,
|
|
4849
|
+
firstOwnMessageTs: row?.firstOwnMessageTs ?? null,
|
|
4850
|
+
lastOwnMessageTs: row?.lastOwnMessageTs ?? null,
|
|
4851
|
+
activeDays: row?.activeDays ?? 0,
|
|
4852
|
+
memberCount: getNonSystemMembersForContacts(db).length,
|
|
4853
|
+
sessionFirstMessageTs: dataRange?.firstMessageTs ?? null,
|
|
4854
|
+
sessionLastMessageTs: dataRange?.lastMessageTs ?? null
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
function getParticipantSetInteractionFacts(db, rawMemberIds, options = {}) {
|
|
4858
|
+
const memberIds = [...new Set(rawMemberIds)];
|
|
4859
|
+
if (memberIds.length < 2) throw new Error("At least two distinct member IDs are required");
|
|
4860
|
+
const placeholders = memberIds.map(() => "?").join(", ");
|
|
4861
|
+
const timeFilter = createBoundedMessageTimeFilter("msg", options);
|
|
4862
|
+
const activityRows = db.prepare(
|
|
4863
|
+
`SELECT
|
|
4864
|
+
msg.sender_id as memberId,
|
|
4865
|
+
COUNT(*) as messageCount,
|
|
4866
|
+
MIN(msg.ts) as firstMessageTs,
|
|
4867
|
+
MAX(msg.ts) as lastMessageTs,
|
|
4868
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays
|
|
4869
|
+
FROM message msg
|
|
4870
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4871
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}
|
|
4872
|
+
AND msg.sender_id IN (${placeholders})${timeFilter.sql}
|
|
4873
|
+
GROUP BY msg.sender_id`
|
|
4874
|
+
).all(...memberIds, ...timeFilter.params);
|
|
4875
|
+
const activityByMemberId = new Map(activityRows.map((row) => [row.memberId, row]));
|
|
4876
|
+
const participants = memberIds.map((memberId) => {
|
|
4877
|
+
const row = activityByMemberId.get(memberId);
|
|
4878
|
+
return {
|
|
4879
|
+
memberId,
|
|
4880
|
+
messageCount: row?.messageCount ?? 0,
|
|
4881
|
+
firstMessageTs: row?.firstMessageTs ?? null,
|
|
4882
|
+
lastMessageTs: row?.lastMessageTs ?? null,
|
|
4883
|
+
activeDays: row?.activeDays ?? 0
|
|
4884
|
+
};
|
|
4885
|
+
});
|
|
4886
|
+
const activeDayRows = db.prepare(
|
|
4887
|
+
`SELECT
|
|
4888
|
+
msg.sender_id as memberId,
|
|
4889
|
+
strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as activeDay
|
|
4890
|
+
FROM message msg
|
|
4891
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4892
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}
|
|
4893
|
+
AND msg.sender_id IN (${placeholders})${timeFilter.sql}
|
|
4894
|
+
GROUP BY msg.sender_id, activeDay`
|
|
4895
|
+
).all(...memberIds, ...timeFilter.params);
|
|
4896
|
+
const activeDaysByMemberId = new Map(memberIds.map((memberId) => [memberId, /* @__PURE__ */ new Set()]));
|
|
4897
|
+
for (const row of activeDayRows) activeDaysByMemberId.get(row.memberId)?.add(row.activeDay);
|
|
4898
|
+
const pairMap = /* @__PURE__ */ new Map();
|
|
4899
|
+
for (let sourceIndex = 0; sourceIndex < memberIds.length - 1; sourceIndex++) {
|
|
4900
|
+
for (let targetIndex = sourceIndex + 1; targetIndex < memberIds.length; targetIndex++) {
|
|
4901
|
+
const sourceMemberId = memberIds[sourceIndex];
|
|
4902
|
+
const targetMemberId = memberIds[targetIndex];
|
|
4903
|
+
pairMap.set(contactPairKey(sourceMemberId, targetMemberId), {
|
|
4904
|
+
sourceMemberId,
|
|
4905
|
+
targetMemberId,
|
|
4906
|
+
directReplyCount: 0,
|
|
4907
|
+
repliesFromSourceToTarget: 0,
|
|
4908
|
+
repliesFromTargetToSource: 0,
|
|
4909
|
+
lastDirectReplyTs: null,
|
|
4910
|
+
coOccurrenceCount: 0,
|
|
4911
|
+
coOccurrenceRawScore: 0,
|
|
4912
|
+
lastProximityTs: null,
|
|
4913
|
+
coActiveDays: countSetIntersection(
|
|
4914
|
+
activeDaysByMemberId.get(sourceMemberId) ?? /* @__PURE__ */ new Set(),
|
|
4915
|
+
activeDaysByMemberId.get(targetMemberId) ?? /* @__PURE__ */ new Set()
|
|
4916
|
+
),
|
|
4917
|
+
anchors: [],
|
|
4918
|
+
anchorsTruncated: false
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
const replyStatsRows = db.prepare(
|
|
4923
|
+
`SELECT
|
|
4924
|
+
msg.sender_id as replySenderId,
|
|
4925
|
+
target.sender_id as targetSenderId,
|
|
4926
|
+
COUNT(*) as directReplyCount,
|
|
4927
|
+
MAX(msg.ts) as lastDirectReplyTs
|
|
4928
|
+
FROM message msg
|
|
4929
|
+
JOIN message target ON msg.reply_to_message_id = target.platform_message_id
|
|
4930
|
+
JOIN member sender ON msg.sender_id = sender.id
|
|
4931
|
+
JOIN member targetMember ON target.sender_id = targetMember.id
|
|
4932
|
+
WHERE msg.reply_to_message_id IS NOT NULL
|
|
4933
|
+
AND ${nonSystemMessageCondition(db, "msg", "sender")}
|
|
4934
|
+
AND ${nonSystemMessageCondition(db, "target", "targetMember")}
|
|
4935
|
+
AND msg.sender_id IN (${placeholders})
|
|
4936
|
+
AND +target.sender_id IN (${placeholders})
|
|
4937
|
+
AND msg.sender_id <> target.sender_id${timeFilter.sql}
|
|
4938
|
+
GROUP BY msg.sender_id, target.sender_id`
|
|
4939
|
+
).all(...memberIds, ...memberIds, ...timeFilter.params);
|
|
4940
|
+
const maxAnchorsPerPair = Math.max(0, Math.floor(options.maxAnchorsPerPair ?? 4));
|
|
4941
|
+
const replyDirectionCountsByPair = /* @__PURE__ */ new Map();
|
|
4942
|
+
for (const row of replyStatsRows) {
|
|
4943
|
+
const pair = pairMap.get(contactPairKey(row.replySenderId, row.targetSenderId));
|
|
4944
|
+
if (!pair) continue;
|
|
4945
|
+
pair.directReplyCount += row.directReplyCount;
|
|
4946
|
+
if (row.replySenderId === pair.sourceMemberId) pair.repliesFromSourceToTarget += row.directReplyCount;
|
|
4947
|
+
else pair.repliesFromTargetToSource += row.directReplyCount;
|
|
4948
|
+
if (row.lastDirectReplyTs !== null) {
|
|
4949
|
+
pair.lastDirectReplyTs = Math.max(pair.lastDirectReplyTs ?? 0, row.lastDirectReplyTs);
|
|
4950
|
+
}
|
|
4951
|
+
const pairKey = contactPairKey(row.replySenderId, row.targetSenderId);
|
|
4952
|
+
replyDirectionCountsByPair.set(pairKey, (replyDirectionCountsByPair.get(pairKey) ?? 0) + 1);
|
|
4953
|
+
}
|
|
4954
|
+
if (maxAnchorsPerPair > 0 && replyStatsRows.length > 0) {
|
|
4955
|
+
const replyAnchorStatement = db.prepare(
|
|
4956
|
+
`SELECT
|
|
4957
|
+
msg.id as messageId,
|
|
4958
|
+
target.id as relatedMessageId,
|
|
4959
|
+
msg.sender_id as replySenderId,
|
|
4960
|
+
target.sender_id as targetSenderId,
|
|
4961
|
+
msg.ts as replyTs
|
|
4962
|
+
FROM message msg
|
|
4963
|
+
JOIN message target ON msg.reply_to_message_id = target.platform_message_id
|
|
4964
|
+
JOIN member sender ON msg.sender_id = sender.id
|
|
4965
|
+
JOIN member targetMember ON target.sender_id = targetMember.id
|
|
4966
|
+
WHERE msg.reply_to_message_id IS NOT NULL
|
|
4967
|
+
AND ${nonSystemMessageCondition(db, "msg", "sender")}
|
|
4968
|
+
AND ${nonSystemMessageCondition(db, "target", "targetMember")}
|
|
4969
|
+
AND msg.sender_id = ?
|
|
4970
|
+
AND +target.sender_id = ?${timeFilter.sql}
|
|
4971
|
+
ORDER BY msg.ts DESC, msg.id DESC
|
|
4972
|
+
LIMIT 1`
|
|
4973
|
+
);
|
|
4974
|
+
const anchorsByPair = /* @__PURE__ */ new Map();
|
|
4975
|
+
for (const row of replyStatsRows) {
|
|
4976
|
+
const anchor = replyAnchorStatement.get(row.replySenderId, row.targetSenderId, ...timeFilter.params);
|
|
4977
|
+
if (!anchor) continue;
|
|
4978
|
+
const pairKey = contactPairKey(anchor.replySenderId, anchor.targetSenderId);
|
|
4979
|
+
const pairAnchors = anchorsByPair.get(pairKey) ?? [];
|
|
4980
|
+
pairAnchors.push({
|
|
4981
|
+
messageId: anchor.messageId,
|
|
4982
|
+
relatedMessageId: anchor.relatedMessageId,
|
|
4983
|
+
timestamp: anchor.replyTs,
|
|
4984
|
+
signal: "direct_reply",
|
|
4985
|
+
fromMemberId: anchor.replySenderId,
|
|
4986
|
+
toMemberId: anchor.targetSenderId
|
|
4987
|
+
});
|
|
4988
|
+
anchorsByPair.set(pairKey, pairAnchors);
|
|
4989
|
+
}
|
|
4990
|
+
for (const [pairKey, anchors] of anchorsByPair) {
|
|
4991
|
+
const pair = pairMap.get(pairKey);
|
|
4992
|
+
if (!pair) continue;
|
|
4993
|
+
anchors.sort((left, right) => right.timestamp - left.timestamp || right.messageId - left.messageId);
|
|
4994
|
+
pair.anchors.push(...anchors.slice(0, maxAnchorsPerPair));
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
for (const [pairKey, directionCount] of replyDirectionCountsByPair) {
|
|
4998
|
+
const pair = pairMap.get(pairKey);
|
|
4999
|
+
if (pair && directionCount > maxAnchorsPerPair) pair.anchorsTruncated = true;
|
|
5000
|
+
}
|
|
5001
|
+
const maxProximityMessages = Math.max(0, Math.floor(options.maxProximityMessages ?? 2e5));
|
|
5002
|
+
let proximityStatus = "complete";
|
|
5003
|
+
if (maxProximityMessages === 0) {
|
|
5004
|
+
proximityStatus = "skipped_budget";
|
|
5005
|
+
for (const pair of pairMap.values()) {
|
|
5006
|
+
pair.coOccurrenceCount = null;
|
|
5007
|
+
pair.coOccurrenceRawScore = null;
|
|
5008
|
+
}
|
|
5009
|
+
} else {
|
|
5010
|
+
const proximityRows = db.prepare(
|
|
5011
|
+
`SELECT msg.id as messageId, msg.sender_id as senderId, msg.ts as ts
|
|
5012
|
+
FROM message msg
|
|
5013
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5014
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}${timeFilter.sql}
|
|
5015
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
5016
|
+
LIMIT ?`
|
|
5017
|
+
).all(...timeFilter.params, maxProximityMessages + 1);
|
|
5018
|
+
if (proximityRows.length > maxProximityMessages) {
|
|
5019
|
+
proximityRows.length = maxProximityMessages;
|
|
5020
|
+
proximityStatus = "partial";
|
|
5021
|
+
}
|
|
5022
|
+
const selectedPairFacts = accumulateSelectedCoOccurrencePairs(
|
|
5023
|
+
proximityRows,
|
|
5024
|
+
[...pairMap.values()].map((pair) => [pair.sourceMemberId, pair.targetMemberId]),
|
|
5025
|
+
// 多保留一个候选,用于区分“命中上限”和“确实省略了证据”。
|
|
5026
|
+
{ maxAnchorsPerPair: maxAnchorsPerPair + 1 }
|
|
5027
|
+
);
|
|
5028
|
+
for (const selected of selectedPairFacts) {
|
|
5029
|
+
const pair = pairMap.get(contactPairKey(selected.sourceId, selected.targetId));
|
|
5030
|
+
if (!pair) continue;
|
|
5031
|
+
pair.coOccurrenceCount = selected.coOccurrenceCount;
|
|
5032
|
+
pair.coOccurrenceRawScore = selected.rawScore;
|
|
5033
|
+
pair.lastProximityTs = selected.lastOccurrenceTs;
|
|
5034
|
+
for (const anchor of selected.anchors) {
|
|
5035
|
+
if (pair.anchors.length >= maxAnchorsPerPair) {
|
|
5036
|
+
pair.anchorsTruncated = true;
|
|
5037
|
+
break;
|
|
5038
|
+
}
|
|
5039
|
+
pair.anchors.push({
|
|
5040
|
+
messageId: anchor.messageId,
|
|
5041
|
+
relatedMessageId: anchor.relatedMessageId,
|
|
5042
|
+
timestamp: anchor.timestamp,
|
|
5043
|
+
signal: "proximity",
|
|
5044
|
+
fromMemberId: anchor.fromMemberId,
|
|
5045
|
+
toMemberId: anchor.toMemberId
|
|
5046
|
+
});
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
if (proximityStatus === "partial") {
|
|
5050
|
+
for (const pair of pairMap.values()) {
|
|
5051
|
+
if (pair.coOccurrenceCount === 0) {
|
|
5052
|
+
pair.coOccurrenceCount = null;
|
|
5053
|
+
pair.coOccurrenceRawScore = null;
|
|
5054
|
+
pair.lastProximityTs = null;
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
const activeDaySets = memberIds.map((memberId) => activeDaysByMemberId.get(memberId) ?? /* @__PURE__ */ new Set());
|
|
5060
|
+
const firstMessageTimestamps = participants.map((participant) => participant.firstMessageTs);
|
|
5061
|
+
const lastMessageTimestamps = participants.map((participant) => participant.lastMessageTs);
|
|
5062
|
+
const dataRange = db.prepare(
|
|
5063
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
5064
|
+
FROM message msg
|
|
5065
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5066
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}`
|
|
5067
|
+
).get();
|
|
5068
|
+
return {
|
|
5069
|
+
participants,
|
|
5070
|
+
overlapRange: calculateOverlapRange(firstMessageTimestamps, lastMessageTimestamps),
|
|
5071
|
+
allParticipantsCoActiveDays: countMultipleSetIntersection(activeDaySets),
|
|
5072
|
+
pairs: [...pairMap.values()],
|
|
5073
|
+
proximityStatus,
|
|
5074
|
+
memberCount: getNonSystemMembersForContacts(db).length,
|
|
5075
|
+
sessionFirstMessageTs: dataRange?.firstMessageTs ?? null,
|
|
5076
|
+
sessionLastMessageTs: dataRange?.lastMessageTs ?? null
|
|
5077
|
+
};
|
|
5078
|
+
}
|
|
4567
5079
|
function getPrivateContactFacts(db, ownerMemberId, options = {}) {
|
|
4568
5080
|
const candidates = getNonSystemMembersForContacts(db).filter((member) => member.id !== ownerMemberId);
|
|
4569
5081
|
if (candidates.length === 0) return { type: "missing" };
|
|
@@ -4799,9 +5311,52 @@ function getGroupRelationshipGraphFacts(db, ownerMemberId, options = {}) {
|
|
|
4799
5311
|
function createMessageTimeFilter(alias, startTs) {
|
|
4800
5312
|
return typeof startTs === "number" ? { sql: ` AND ${alias}.ts >= ?`, params: [startTs] } : { sql: "", params: [] };
|
|
4801
5313
|
}
|
|
5314
|
+
function createBoundedMessageTimeFilter(alias, options) {
|
|
5315
|
+
const clauses = [];
|
|
5316
|
+
const params = [];
|
|
5317
|
+
if (typeof options.startTs === "number") {
|
|
5318
|
+
clauses.push(`${alias}.ts >= ?`);
|
|
5319
|
+
params.push(options.startTs);
|
|
5320
|
+
}
|
|
5321
|
+
if (typeof options.endTs === "number") {
|
|
5322
|
+
clauses.push(`${alias}.ts <= ?`);
|
|
5323
|
+
params.push(options.endTs);
|
|
5324
|
+
}
|
|
5325
|
+
return {
|
|
5326
|
+
sql: clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "",
|
|
5327
|
+
params
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
4802
5330
|
function createReplyTimeFilter(startTs) {
|
|
4803
5331
|
return typeof startTs === "number" ? { sql: " AND msg.ts >= ? AND target.ts >= ?", params: [startTs, startTs] } : { sql: "", params: [] };
|
|
4804
5332
|
}
|
|
5333
|
+
function contactPairKey(left, right) {
|
|
5334
|
+
return left < right ? `${left}:${right}` : `${right}:${left}`;
|
|
5335
|
+
}
|
|
5336
|
+
function countSetIntersection(left, right) {
|
|
5337
|
+
const smaller = left.size <= right.size ? left : right;
|
|
5338
|
+
const larger = smaller === left ? right : left;
|
|
5339
|
+
let count = 0;
|
|
5340
|
+
for (const value of smaller) {
|
|
5341
|
+
if (larger.has(value)) count++;
|
|
5342
|
+
}
|
|
5343
|
+
return count;
|
|
5344
|
+
}
|
|
5345
|
+
function countMultipleSetIntersection(sets) {
|
|
5346
|
+
if (sets.length === 0) return 0;
|
|
5347
|
+
return sets.slice(1).reduce((intersection, current) => {
|
|
5348
|
+
for (const value of intersection) {
|
|
5349
|
+
if (!current.has(value)) intersection.delete(value);
|
|
5350
|
+
}
|
|
5351
|
+
return intersection;
|
|
5352
|
+
}, new Set(sets[0])).size;
|
|
5353
|
+
}
|
|
5354
|
+
function calculateOverlapRange(firstTimestamps, lastTimestamps) {
|
|
5355
|
+
if (firstTimestamps.some((value) => value === null) || lastTimestamps.some((value) => value === null)) return null;
|
|
5356
|
+
const startTs = Math.max(...firstTimestamps);
|
|
5357
|
+
const endTs = Math.min(...lastTimestamps);
|
|
5358
|
+
return startTs <= endTs ? { startTs, endTs } : null;
|
|
5359
|
+
}
|
|
4805
5360
|
function mapContactMemberRow(row) {
|
|
4806
5361
|
return {
|
|
4807
5362
|
id: row.id,
|
|
@@ -4938,6 +5493,83 @@ function buildContactKey(platform, platformId, sessionId) {
|
|
|
4938
5493
|
return sessionId?.trim() ? `${normalizedPlatform}:${sessionId.trim()}:${normalizedPlatformId}` : `${normalizedPlatform}:${normalizedPlatformId}`;
|
|
4939
5494
|
}
|
|
4940
5495
|
|
|
5496
|
+
// ../../packages/core/src/query/cross-chat-statistics.ts
|
|
5497
|
+
function getCrossChatSessionActivityFacts(db, options = {}) {
|
|
5498
|
+
const timeFilter = createTimeFilter("msg", options);
|
|
5499
|
+
const messageCondition = nonSystemMessageCondition(db, "msg", "m");
|
|
5500
|
+
const summary = db.prepare(
|
|
5501
|
+
`SELECT
|
|
5502
|
+
COUNT(*) as totalMessages,
|
|
5503
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays,
|
|
5504
|
+
COUNT(DISTINCT msg.sender_id) as activeMembers,
|
|
5505
|
+
MIN(msg.ts) as firstMessageTs,
|
|
5506
|
+
MAX(msg.ts) as lastMessageTs
|
|
5507
|
+
FROM message msg
|
|
5508
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5509
|
+
WHERE ${messageCondition}${timeFilter.sql}`
|
|
5510
|
+
).get(...timeFilter.params);
|
|
5511
|
+
const memberRows = db.prepare(
|
|
5512
|
+
`SELECT
|
|
5513
|
+
msg.sender_id as memberId,
|
|
5514
|
+
m.platform_id as platformId,
|
|
5515
|
+
COALESCE(m.group_nickname, m.account_name, m.platform_id) as memberName,
|
|
5516
|
+
COUNT(*) as messageCount,
|
|
5517
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays,
|
|
5518
|
+
GROUP_CONCAT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDayKeys,
|
|
5519
|
+
MIN(msg.ts) as firstMessageTs,
|
|
5520
|
+
MAX(msg.ts) as lastMessageTs
|
|
5521
|
+
FROM message msg
|
|
5522
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5523
|
+
WHERE ${messageCondition}${timeFilter.sql}
|
|
5524
|
+
GROUP BY msg.sender_id, m.platform_id, m.group_nickname, m.account_name
|
|
5525
|
+
ORDER BY msg.sender_id`
|
|
5526
|
+
).all(...timeFilter.params);
|
|
5527
|
+
const members = memberRows.map((row) => ({
|
|
5528
|
+
...row,
|
|
5529
|
+
activeDayKeys: row.activeDayKeys ? row.activeDayKeys.split(",").sort() : []
|
|
5530
|
+
}));
|
|
5531
|
+
const dataRange = db.prepare(
|
|
5532
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
5533
|
+
FROM message msg
|
|
5534
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5535
|
+
WHERE ${messageCondition}`
|
|
5536
|
+
).get();
|
|
5537
|
+
const activeDayKeys = db.prepare(
|
|
5538
|
+
`SELECT DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as activeDay
|
|
5539
|
+
FROM message msg
|
|
5540
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5541
|
+
WHERE ${messageCondition}${timeFilter.sql}
|
|
5542
|
+
ORDER BY activeDay`
|
|
5543
|
+
).all(...timeFilter.params).map((row) => row.activeDay);
|
|
5544
|
+
return {
|
|
5545
|
+
totalMessages: summary?.totalMessages ?? 0,
|
|
5546
|
+
activeDays: summary?.activeDays ?? 0,
|
|
5547
|
+
activeMembers: summary?.activeMembers ?? 0,
|
|
5548
|
+
firstMessageTs: summary?.firstMessageTs ?? null,
|
|
5549
|
+
lastMessageTs: summary?.lastMessageTs ?? null,
|
|
5550
|
+
dataEarliestMessageTs: dataRange?.firstMessageTs ?? null,
|
|
5551
|
+
dataLatestMessageTs: dataRange?.lastMessageTs ?? null,
|
|
5552
|
+
activeDayKeys,
|
|
5553
|
+
members
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5556
|
+
function createTimeFilter(alias, options) {
|
|
5557
|
+
const clauses = [];
|
|
5558
|
+
const params = [];
|
|
5559
|
+
if (typeof options.startTs === "number") {
|
|
5560
|
+
clauses.push(`${alias}.ts >= ?`);
|
|
5561
|
+
params.push(options.startTs);
|
|
5562
|
+
}
|
|
5563
|
+
if (typeof options.endTs === "number") {
|
|
5564
|
+
clauses.push(`${alias}.ts <= ?`);
|
|
5565
|
+
params.push(options.endTs);
|
|
5566
|
+
}
|
|
5567
|
+
return {
|
|
5568
|
+
sql: clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "",
|
|
5569
|
+
params
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
|
|
4941
5573
|
// ../../packages/core/src/query/global-insight.ts
|
|
4942
5574
|
var SYSTEM_MESSAGE_TYPES2 = [80 /* SYSTEM */, 81 /* RECALL */];
|
|
4943
5575
|
function getAnnualSummarySessionFacts(db, sessionId, range) {
|
|
@@ -5726,6 +6358,7 @@ function searchMessagesByKeywords(db, keywords, options) {
|
|
|
5726
6358
|
startTs: options?.startTs,
|
|
5727
6359
|
endTs: options?.endTs,
|
|
5728
6360
|
senderId: options?.senderId,
|
|
6361
|
+
senderIds: options?.senderIds,
|
|
5729
6362
|
keywords: cleaned.length > 0 ? cleaned : void 0,
|
|
5730
6363
|
matchMode: options?.matchMode,
|
|
5731
6364
|
excludeKeywords: options?.excludeKeywords,
|
|
@@ -5748,6 +6381,33 @@ function searchMessagesByKeywords(db, keywords, options) {
|
|
|
5748
6381
|
});
|
|
5749
6382
|
return { messages, hasMore, total: countRow.total };
|
|
5750
6383
|
}
|
|
6384
|
+
function getRecentMessages(db, options) {
|
|
6385
|
+
const limit = options?.limit ?? 50;
|
|
6386
|
+
const rows = db.prepare(
|
|
6387
|
+
`SELECT
|
|
6388
|
+
msg.id as id,
|
|
6389
|
+
m.id as senderId,
|
|
6390
|
+
COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
|
|
6391
|
+
m.platform_id as senderPlatformId,
|
|
6392
|
+
msg.content as content,
|
|
6393
|
+
msg.ts as timestamp,
|
|
6394
|
+
msg.type as type
|
|
6395
|
+
FROM message msg
|
|
6396
|
+
JOIN member m ON msg.sender_id = m.id
|
|
6397
|
+
WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'
|
|
6398
|
+
ORDER BY msg.ts DESC
|
|
6399
|
+
LIMIT ?`
|
|
6400
|
+
).all(limit);
|
|
6401
|
+
return rows.map((row) => ({
|
|
6402
|
+
id: Number(row.id),
|
|
6403
|
+
senderId: Number(row.senderId),
|
|
6404
|
+
senderName: String(row.senderName || ""),
|
|
6405
|
+
senderPlatformId: String(row.senderPlatformId || ""),
|
|
6406
|
+
content: row.content != null ? String(row.content) : "",
|
|
6407
|
+
timestamp: Number(row.timestamp),
|
|
6408
|
+
type: Number(row.type)
|
|
6409
|
+
}));
|
|
6410
|
+
}
|
|
5751
6411
|
function getMembers(db) {
|
|
5752
6412
|
return db.prepare(
|
|
5753
6413
|
`SELECT
|
|
@@ -6185,17 +6845,27 @@ var SYSTEM_PLACEHOLDER_CONTENTS = /* @__PURE__ */ new Set([
|
|
|
6185
6845
|
"[Location]"
|
|
6186
6846
|
]);
|
|
6187
6847
|
function isSystemPlaceholderContent(content) {
|
|
6188
|
-
|
|
6848
|
+
const trimmed = content.trim();
|
|
6849
|
+
return SYSTEM_PLACEHOLDER_CONTENTS.has(trimmed) || isMediaPlaceholderContent(trimmed);
|
|
6189
6850
|
}
|
|
6190
6851
|
|
|
6191
6852
|
// ../../packages/core/src/query/advanced/repeat.ts
|
|
6853
|
+
var VOICE_TRANSCRIPTION_SQL = `
|
|
6854
|
+
TRIM(msg.content) LIKE '[\u8BED\u97F3] %' OR
|
|
6855
|
+
TRIM(msg.content) LIKE '[\u8BED\u97F3 %] %' OR
|
|
6856
|
+
TRIM(msg.content) LIKE '[Voice] %' OR
|
|
6857
|
+
TRIM(msg.content) LIKE '[Voice %] %' OR
|
|
6858
|
+
TRIM(msg.content) LIKE '[Audio] %' OR
|
|
6859
|
+
TRIM(msg.content) LIKE '[Audio %] %'
|
|
6860
|
+
`;
|
|
6861
|
+
var CATCHPHRASE_MESSAGE_TYPES = [0 /* TEXT */, 5 /* EMOJI */].join(", ");
|
|
6192
6862
|
function getCatchphraseAnalysis(db, filter) {
|
|
6193
6863
|
const { clause, params } = buildTimeFilter(filter);
|
|
6194
6864
|
let whereClause = clause;
|
|
6195
6865
|
if (whereClause.includes("WHERE")) {
|
|
6196
|
-
whereClause +=
|
|
6866
|
+
whereClause += ` AND COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' AND msg.type IN (${CATCHPHRASE_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2`;
|
|
6197
6867
|
} else {
|
|
6198
|
-
whereClause =
|
|
6868
|
+
whereClause = ` WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' AND msg.type IN (${CATCHPHRASE_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2`;
|
|
6199
6869
|
}
|
|
6200
6870
|
const rows = db.prepare(
|
|
6201
6871
|
`
|
|
@@ -6209,13 +6879,15 @@ function getCatchphraseAnalysis(db, filter) {
|
|
|
6209
6879
|
JOIN member m ON msg.sender_id = m.id
|
|
6210
6880
|
${whereClause}
|
|
6211
6881
|
GROUP BY m.id, TRIM(msg.content)
|
|
6212
|
-
HAVING COUNT(*) >= 2
|
|
6882
|
+
HAVING COUNT(*) >= 2 OR (${VOICE_TRANSCRIPTION_SQL})
|
|
6213
6883
|
ORDER BY m.id, count DESC
|
|
6214
6884
|
`
|
|
6215
6885
|
).all(...params);
|
|
6216
6886
|
const memberMap = /* @__PURE__ */ new Map();
|
|
6887
|
+
const phraseMaps = /* @__PURE__ */ new Map();
|
|
6217
6888
|
for (const row of rows) {
|
|
6218
|
-
|
|
6889
|
+
const content = stripVoiceTranscriptionPrefix(row.content);
|
|
6890
|
+
if (!content || isSystemMessageContent(content) || isSystemPlaceholderContent(content)) continue;
|
|
6219
6891
|
if (!memberMap.has(row.memberId)) {
|
|
6220
6892
|
memberMap.set(row.memberId, {
|
|
6221
6893
|
memberId: row.memberId,
|
|
@@ -6224,17 +6896,24 @@ function getCatchphraseAnalysis(db, filter) {
|
|
|
6224
6896
|
catchphrases: []
|
|
6225
6897
|
});
|
|
6226
6898
|
}
|
|
6227
|
-
|
|
6228
|
-
if (
|
|
6229
|
-
|
|
6899
|
+
let phrases = phraseMaps.get(row.memberId);
|
|
6900
|
+
if (!phrases) {
|
|
6901
|
+
phrases = /* @__PURE__ */ new Map();
|
|
6902
|
+
phraseMaps.set(row.memberId, phrases);
|
|
6230
6903
|
}
|
|
6904
|
+
const existing = phrases.get(content);
|
|
6905
|
+
if (existing) existing.count += Number(row.count);
|
|
6906
|
+
else phrases.set(content, { content, count: Number(row.count) });
|
|
6231
6907
|
}
|
|
6232
6908
|
const members = Array.from(memberMap.values());
|
|
6909
|
+
for (const member of members) {
|
|
6910
|
+
member.catchphrases = [...phraseMaps.get(member.memberId)?.values() ?? []].filter((item) => item.count >= 2).sort((a, b) => b.count - a.count || a.content.localeCompare(b.content)).slice(0, 100);
|
|
6911
|
+
}
|
|
6233
6912
|
members.sort((a, b) => {
|
|
6234
6913
|
const countDifference = (b.catchphrases[0]?.count ?? 0) - (a.catchphrases[0]?.count ?? 0);
|
|
6235
6914
|
return countDifference || a.memberId - b.memberId;
|
|
6236
6915
|
});
|
|
6237
|
-
return { members };
|
|
6916
|
+
return { members: members.filter((member) => member.catchphrases.length > 0) };
|
|
6238
6917
|
}
|
|
6239
6918
|
|
|
6240
6919
|
// ../../packages/core/src/query/advanced/group-relationship-galaxy.ts
|
|
@@ -6243,6 +6922,7 @@ var REPLY_WEIGHT = 3;
|
|
|
6243
6922
|
var MENTION_WEIGHT = 2;
|
|
6244
6923
|
var CO_OCCURRENCE_COUNT_WEIGHT = 0.05;
|
|
6245
6924
|
var MIN_EDGE_WEIGHT = 1e-6;
|
|
6925
|
+
var MESSAGE_BATCH_SIZE = 2e4;
|
|
6246
6926
|
var DEFAULT_LIMITS = {
|
|
6247
6927
|
nodeLimit: 400,
|
|
6248
6928
|
edgeLimit: 1200,
|
|
@@ -6264,14 +6944,16 @@ function getGroupRelationshipGalaxy(db, filter, options = {}) {
|
|
|
6264
6944
|
const limits = normalizeLimits(options);
|
|
6265
6945
|
const allMembers = getNonSystemMembersForContacts(db);
|
|
6266
6946
|
const memberById = new Map(allMembers.map((member) => [member.id, member]));
|
|
6267
|
-
const
|
|
6268
|
-
const
|
|
6947
|
+
const messageStats = /* @__PURE__ */ new Map();
|
|
6948
|
+
const messageBatches = filterRelationshipMessageBatches(db, filter, memberById, messageStats);
|
|
6949
|
+
const coOccurrencePairs = accumulateCoOccurrencePairBatches(messageBatches);
|
|
6950
|
+
const activeMembers = buildActiveMembers(allMembers, messageStats);
|
|
6269
6951
|
const activeMemberById = new Map(activeMembers.map((member) => [member.id, member]));
|
|
6270
6952
|
if (activeMembers.length < 2) {
|
|
6271
6953
|
return emptyResult(allMembers.length, activeMembers.length);
|
|
6272
6954
|
}
|
|
6273
6955
|
const edgeByPair = /* @__PURE__ */ new Map();
|
|
6274
|
-
for (const pair of
|
|
6956
|
+
for (const pair of coOccurrencePairs) {
|
|
6275
6957
|
if (!activeMemberById.has(pair.sourceId) || !activeMemberById.has(pair.targetId)) continue;
|
|
6276
6958
|
const edge = ensureEdge(edgeByPair, activeMemberById, pair.sourceId, pair.targetId);
|
|
6277
6959
|
edge.coOccurrenceCount += pair.coOccurrenceCount;
|
|
@@ -6389,19 +7071,44 @@ function getGroupRelationshipGalaxy(db, filter, options = {}) {
|
|
|
6389
7071
|
algorithmVersion: GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION
|
|
6390
7072
|
};
|
|
6391
7073
|
}
|
|
6392
|
-
function
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
7074
|
+
function* filterRelationshipMessageBatches(db, filter, memberById, messageStats) {
|
|
7075
|
+
for (const batch of queryRelationshipMessageBatches(db, filter)) {
|
|
7076
|
+
const filtered = [];
|
|
7077
|
+
for (const message of batch) {
|
|
7078
|
+
if (!memberById.has(message.senderId)) continue;
|
|
7079
|
+
const stats = messageStats.get(message.senderId) ?? { count: 0, lastTs: 0 };
|
|
7080
|
+
stats.count++;
|
|
7081
|
+
stats.lastTs = Math.max(stats.lastTs, message.ts);
|
|
7082
|
+
messageStats.set(message.senderId, stats);
|
|
7083
|
+
filtered.push(message);
|
|
7084
|
+
}
|
|
7085
|
+
yield filtered;
|
|
7086
|
+
}
|
|
6396
7087
|
}
|
|
6397
|
-
function
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
7088
|
+
function* queryRelationshipMessageBatches(db, filter) {
|
|
7089
|
+
const { clause, params } = buildTimeFilter(filter, "msg");
|
|
7090
|
+
const conditions = ["msg.type NOT IN (80, 81)"];
|
|
7091
|
+
const firstPage = db.prepare(
|
|
7092
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
7093
|
+
FROM message msg ${appendConditions(clause, conditions)}
|
|
7094
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
7095
|
+
LIMIT ?`
|
|
7096
|
+
);
|
|
7097
|
+
const nextPage = db.prepare(
|
|
7098
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
7099
|
+
FROM message msg ${appendConditions(clause, [...conditions, "(msg.ts, msg.id) > (?, ?)"])}
|
|
7100
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
7101
|
+
LIMIT ?`
|
|
7102
|
+
);
|
|
7103
|
+
let rows = firstPage.all(...params, MESSAGE_BATCH_SIZE);
|
|
7104
|
+
while (rows.length > 0) {
|
|
7105
|
+
yield rows;
|
|
7106
|
+
if (rows.length < MESSAGE_BATCH_SIZE) break;
|
|
7107
|
+
const last = rows[rows.length - 1];
|
|
7108
|
+
rows = nextPage.all(...params, last.ts, last.id, MESSAGE_BATCH_SIZE);
|
|
6404
7109
|
}
|
|
7110
|
+
}
|
|
7111
|
+
function buildActiveMembers(allMembers, messageStats) {
|
|
6405
7112
|
const active = allMembers.filter((member) => messageStats.has(member.id)).map((member) => ({
|
|
6406
7113
|
...member,
|
|
6407
7114
|
key: memberKey(member.id),
|
|
@@ -6900,8 +7607,8 @@ function getJourneyStats(db, filter) {
|
|
|
6900
7607
|
const indexAvailable = hasSessionIndex(db) && hasTable(db, "message_context");
|
|
6901
7608
|
const segmentRows = indexAvailable ? querySegmentRows(db, effectiveFilter) : [];
|
|
6902
7609
|
const segmentCountByMonth = /* @__PURE__ */ new Map();
|
|
6903
|
-
for (const
|
|
6904
|
-
segmentCountByMonth.set(
|
|
7610
|
+
for (const segment2 of segmentRows) {
|
|
7611
|
+
segmentCountByMonth.set(segment2.startMonth, (segmentCountByMonth.get(segment2.startMonth) ?? 0) + 1);
|
|
6905
7612
|
}
|
|
6906
7613
|
const firstRow = monthlyRows[0];
|
|
6907
7614
|
const lastRow = monthlyRows[monthlyRows.length - 1];
|
|
@@ -7097,8 +7804,9 @@ var RE_MENTION = /@[^\s@]+/g;
|
|
|
7097
7804
|
var RE_EMOJI = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu;
|
|
7098
7805
|
var RE_PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~,。!?、;:""''()【】《》…—~·\s]/g;
|
|
7099
7806
|
var RE_PURE_NUMBER = /^\d+$/;
|
|
7807
|
+
var LANGUAGE_ANALYSIS_MESSAGE_TYPES = [0 /* TEXT */, 5 /* EMOJI */].join(", ");
|
|
7100
7808
|
function cleanTextForNlp(text) {
|
|
7101
|
-
return text.replace(RE_URL, " ").replace(RE_MENTION, " ").replace(RE_EMOJI, " ").replace(RE_PUNCTUATION, " ").replace(/\s+/g, " ").trim();
|
|
7809
|
+
return stripMediaPlaceholders(text).replace(RE_URL, " ").replace(RE_MENTION, " ").replace(RE_EMOJI, " ").replace(RE_PUNCTUATION, " ").replace(/\s+/g, " ").trim();
|
|
7102
7810
|
}
|
|
7103
7811
|
function countMatches(text, regex) {
|
|
7104
7812
|
regex.lastIndex = 0;
|
|
@@ -7119,7 +7827,7 @@ function getLanguagePreferenceAnalysis(db, params) {
|
|
|
7119
7827
|
const { locale, timeFilter, nlpProvider } = params;
|
|
7120
7828
|
const { clause, params: filterParams } = buildTimeFilter(timeFilter);
|
|
7121
7829
|
let whereClause = clause;
|
|
7122
|
-
const textFilter =
|
|
7830
|
+
const textFilter = ` COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F' AND msg.type IN (${LANGUAGE_ANALYSIS_MESSAGE_TYPES}) AND msg.content IS NOT NULL AND LENGTH(TRIM(msg.content)) >= 2`;
|
|
7123
7831
|
if (whereClause.includes("WHERE")) {
|
|
7124
7832
|
whereClause += " AND " + textFilter;
|
|
7125
7833
|
} else {
|
|
@@ -7132,13 +7840,14 @@ function getLanguagePreferenceAnalysis(db, params) {
|
|
|
7132
7840
|
if (rows.length === 0) return { members: [], sharedWords: [], similarityScore: 0 };
|
|
7133
7841
|
const memberMessages = /* @__PURE__ */ new Map();
|
|
7134
7842
|
for (const row of rows) {
|
|
7135
|
-
|
|
7843
|
+
const content = stripVoiceTranscriptionPrefix(row.content);
|
|
7844
|
+
if (!content || isSystemMessageContent(content) || isSystemPlaceholderContent(content)) continue;
|
|
7136
7845
|
let entry = memberMessages.get(row.memberId);
|
|
7137
7846
|
if (!entry) {
|
|
7138
7847
|
entry = { name: row.name, messages: [] };
|
|
7139
7848
|
memberMessages.set(row.memberId, entry);
|
|
7140
7849
|
}
|
|
7141
|
-
entry.messages.push(
|
|
7850
|
+
entry.messages.push(content);
|
|
7142
7851
|
}
|
|
7143
7852
|
const isChinese2 = locale.startsWith("zh");
|
|
7144
7853
|
const minWordLength = isChinese2 ? 2 : 3;
|
|
@@ -9001,8 +9710,8 @@ var AssistantManager = class {
|
|
|
9001
9710
|
}
|
|
9002
9711
|
// ==================== Init ====================
|
|
9003
9712
|
init() {
|
|
9004
|
-
const { fs:
|
|
9005
|
-
|
|
9713
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9714
|
+
fs20.ensureDir(assistantsDir);
|
|
9006
9715
|
const { generalCreated, generalUpdated } = this.syncGeneralAssistants();
|
|
9007
9716
|
this.loadAll();
|
|
9008
9717
|
this.initialized = true;
|
|
@@ -9014,25 +9723,25 @@ var AssistantManager = class {
|
|
|
9014
9723
|
return { total: this.cache.size, generalCreated, generalUpdated };
|
|
9015
9724
|
}
|
|
9016
9725
|
syncGeneralAssistants() {
|
|
9017
|
-
const { fs:
|
|
9726
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9018
9727
|
let generalCreated = false;
|
|
9019
9728
|
let generalUpdated = false;
|
|
9020
9729
|
for (const id of this.generalIds) {
|
|
9021
9730
|
const config = this.getBuiltinConfig(id);
|
|
9022
9731
|
if (!config) continue;
|
|
9023
|
-
const filePath =
|
|
9024
|
-
if (!
|
|
9025
|
-
|
|
9732
|
+
const filePath = fs20.joinPath(assistantsDir, `${id}.md`);
|
|
9733
|
+
if (!fs20.fileExists(filePath)) {
|
|
9734
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9026
9735
|
generalCreated = true;
|
|
9027
9736
|
continue;
|
|
9028
9737
|
}
|
|
9029
9738
|
try {
|
|
9030
|
-
const existing = parseAssistantFile(
|
|
9739
|
+
const existing = parseAssistantFile(fs20.readFile(filePath), filePath);
|
|
9031
9740
|
if (!existing) continue;
|
|
9032
9741
|
if (existing.builtinId !== id) {
|
|
9033
9742
|
try {
|
|
9034
9743
|
const backupId = this.backupAssistantOccupyingReservedId(existing);
|
|
9035
|
-
|
|
9744
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9036
9745
|
generalCreated = true;
|
|
9037
9746
|
this.deps.logger?.info("AssistantManager", "Backed up assistant occupying reserved default ID", {
|
|
9038
9747
|
reservedId: id,
|
|
@@ -9048,7 +9757,7 @@ var AssistantManager = class {
|
|
|
9048
9757
|
continue;
|
|
9049
9758
|
}
|
|
9050
9759
|
if (!this.shouldUpgradeBuiltin(existing, config)) continue;
|
|
9051
|
-
|
|
9760
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config, existing.id)));
|
|
9052
9761
|
generalUpdated = true;
|
|
9053
9762
|
this.deps.logger?.info("AssistantManager", "Updated unmodified default assistant template", {
|
|
9054
9763
|
id,
|
|
@@ -9064,14 +9773,14 @@ var AssistantManager = class {
|
|
|
9064
9773
|
return { generalCreated, generalUpdated };
|
|
9065
9774
|
}
|
|
9066
9775
|
backupAssistantOccupyingReservedId(config) {
|
|
9067
|
-
const { fs:
|
|
9776
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9068
9777
|
const baseId = this.deps.generateId?.() || `custom_${Date.now().toString(36)}`;
|
|
9069
9778
|
let backupId = baseId;
|
|
9070
9779
|
let suffix = 2;
|
|
9071
|
-
while (this.generalIds.includes(backupId) ||
|
|
9780
|
+
while (this.generalIds.includes(backupId) || fs20.fileExists(fs20.joinPath(assistantsDir, `${backupId}.md`))) {
|
|
9072
9781
|
backupId = `${baseId}_${suffix++}`;
|
|
9073
9782
|
}
|
|
9074
|
-
|
|
9783
|
+
fs20.writeFile(fs20.joinPath(assistantsDir, `${backupId}.md`), serializeAssistant({ ...config, id: backupId }));
|
|
9075
9784
|
return backupId;
|
|
9076
9785
|
}
|
|
9077
9786
|
getConfigDigest(config) {
|
|
@@ -9108,13 +9817,13 @@ var AssistantManager = class {
|
|
|
9108
9817
|
return { isUnmodified, isOutdated };
|
|
9109
9818
|
}
|
|
9110
9819
|
loadAll() {
|
|
9111
|
-
const { fs:
|
|
9820
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9112
9821
|
this.cache.clear();
|
|
9113
|
-
const files =
|
|
9822
|
+
const files = fs20.listFiles(assistantsDir, ".md");
|
|
9114
9823
|
for (const file of files) {
|
|
9115
9824
|
try {
|
|
9116
|
-
const filePath =
|
|
9117
|
-
const content =
|
|
9825
|
+
const filePath = fs20.joinPath(assistantsDir, file);
|
|
9826
|
+
const content = fs20.readFile(filePath);
|
|
9118
9827
|
const config = parseAssistantFile(content, filePath);
|
|
9119
9828
|
if (config) {
|
|
9120
9829
|
this.cache.set(config.id, config);
|
|
@@ -9156,31 +9865,10 @@ var AssistantManager = class {
|
|
|
9156
9865
|
this.ensureInitialized();
|
|
9157
9866
|
return this.cache.has(id);
|
|
9158
9867
|
}
|
|
9159
|
-
getBuiltinCatalog() {
|
|
9160
|
-
this.ensureInitialized();
|
|
9161
|
-
return [];
|
|
9162
|
-
}
|
|
9163
9868
|
isGeneralAssistant(id) {
|
|
9164
9869
|
return this.generalIds.includes(id);
|
|
9165
9870
|
}
|
|
9166
9871
|
// ==================== Import ====================
|
|
9167
|
-
importAssistant(builtinId) {
|
|
9168
|
-
this.ensureInitialized();
|
|
9169
|
-
const builtinConfig = this.getBuiltinConfig(builtinId);
|
|
9170
|
-
if (!builtinConfig) return { success: false, error: `Builtin assistant not found: ${builtinId}` };
|
|
9171
|
-
const existing = this.findByBuiltinId(builtinId);
|
|
9172
|
-
if (existing) return { success: false, error: `Assistant already imported: ${builtinId}` };
|
|
9173
|
-
return this.saveToDisk(this.withBuiltinTracking(builtinConfig));
|
|
9174
|
-
}
|
|
9175
|
-
reimportAssistant(id) {
|
|
9176
|
-
this.ensureInitialized();
|
|
9177
|
-
const existing = this.cache.get(id);
|
|
9178
|
-
if (!existing) return { success: false, error: `Assistant not found: ${id}` };
|
|
9179
|
-
if (!existing.builtinId) return { success: false, error: "Only imported builtin assistants can be reimported" };
|
|
9180
|
-
const builtinConfig = this.getBuiltinConfig(existing.builtinId);
|
|
9181
|
-
if (!builtinConfig) return { success: false, error: `Builtin template not found: ${existing.builtinId}` };
|
|
9182
|
-
return this.saveToDisk(this.withBuiltinTracking(builtinConfig, existing.id));
|
|
9183
|
-
}
|
|
9184
9872
|
importAssistantFromMd(rawMd) {
|
|
9185
9873
|
this.ensureInitialized();
|
|
9186
9874
|
const config = parseAssistantFile(rawMd, "cloud_import.md");
|
|
@@ -9274,9 +9962,6 @@ var AssistantManager = class {
|
|
|
9274
9962
|
return { success: true, backupId };
|
|
9275
9963
|
}
|
|
9276
9964
|
// ==================== Internal ====================
|
|
9277
|
-
findByBuiltinId(builtinId) {
|
|
9278
|
-
return Array.from(this.cache.values()).find((c) => c.builtinId === builtinId);
|
|
9279
|
-
}
|
|
9280
9965
|
saveToDisk(config) {
|
|
9281
9966
|
try {
|
|
9282
9967
|
const filePath = this.deps.fs.joinPath(this.deps.assistantsDir, `${config.id}.md`);
|
|
@@ -9368,6 +10053,7 @@ var AIChatManager = class {
|
|
|
9368
10053
|
CREATE TABLE IF NOT EXISTS ai_chat (
|
|
9369
10054
|
id TEXT PRIMARY KEY,
|
|
9370
10055
|
session_id TEXT NOT NULL,
|
|
10056
|
+
kind TEXT NOT NULL DEFAULT 'session',
|
|
9371
10057
|
title TEXT,
|
|
9372
10058
|
assistant_id TEXT DEFAULT '${DEFAULT_GENERAL_ID}',
|
|
9373
10059
|
active_message_id TEXT,
|
|
@@ -9389,6 +10075,7 @@ var AIChatManager = class {
|
|
|
9389
10075
|
branch_index INTEGER DEFAULT 0,
|
|
9390
10076
|
debug_context TEXT,
|
|
9391
10077
|
token_usage TEXT,
|
|
10078
|
+
entity_refs TEXT,
|
|
9392
10079
|
FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE
|
|
9393
10080
|
);
|
|
9394
10081
|
|
|
@@ -9424,9 +10111,19 @@ var AIChatManager = class {
|
|
|
9424
10111
|
if (!messageColumns.includes("branch_index")) {
|
|
9425
10112
|
db.exec(`ALTER TABLE ${tableName} ADD COLUMN branch_index INTEGER DEFAULT 0`);
|
|
9426
10113
|
}
|
|
10114
|
+
if (!messageColumns.includes("entity_refs")) {
|
|
10115
|
+
db.exec(`ALTER TABLE ${tableName} ADD COLUMN entity_refs TEXT`);
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
ensureChatMigrationColumns(db) {
|
|
10119
|
+
const chatColumns = this.getTableColumns(db, "ai_chat");
|
|
10120
|
+
if (!chatColumns.includes("kind")) {
|
|
10121
|
+
db.exec(`ALTER TABLE ai_chat ADD COLUMN kind TEXT NOT NULL DEFAULT 'session'`);
|
|
10122
|
+
}
|
|
9427
10123
|
}
|
|
9428
10124
|
migrateDatabase(db) {
|
|
9429
10125
|
try {
|
|
10126
|
+
this.ensureChatMigrationColumns(db);
|
|
9430
10127
|
const hasLegacyConversationTable = this.tableExists(db, "ai_conversation");
|
|
9431
10128
|
if (hasLegacyConversationTable) {
|
|
9432
10129
|
const convColumns = this.getTableColumns(db, "ai_conversation");
|
|
@@ -9468,15 +10165,16 @@ var AIChatManager = class {
|
|
|
9468
10165
|
branch_index INTEGER DEFAULT 0,
|
|
9469
10166
|
debug_context TEXT,
|
|
9470
10167
|
token_usage TEXT,
|
|
10168
|
+
entity_refs TEXT,
|
|
9471
10169
|
FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE
|
|
9472
10170
|
);
|
|
9473
10171
|
|
|
9474
10172
|
INSERT INTO ai_message (
|
|
9475
10173
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9476
|
-
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage
|
|
10174
|
+
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage, entity_refs
|
|
9477
10175
|
)
|
|
9478
10176
|
SELECT id, conversation_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9479
|
-
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage
|
|
10177
|
+
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage, entity_refs
|
|
9480
10178
|
FROM ai_message_legacy;
|
|
9481
10179
|
|
|
9482
10180
|
DROP TABLE ai_message_legacy;
|
|
@@ -9561,7 +10259,8 @@ var AIChatManager = class {
|
|
|
9561
10259
|
dataKeywords: row.dataKeywords ? JSON.parse(row.dataKeywords) : void 0,
|
|
9562
10260
|
dataMessageCount: row.dataMessageCount ?? void 0,
|
|
9563
10261
|
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0,
|
|
9564
|
-
tokenUsage: row.tokenUsage ? JSON.parse(row.tokenUsage) : void 0
|
|
10262
|
+
tokenUsage: row.tokenUsage ? JSON.parse(row.tokenUsage) : void 0,
|
|
10263
|
+
entityRefs: row.entityRefs ? JSON.parse(row.entityRefs) : void 0
|
|
9565
10264
|
};
|
|
9566
10265
|
}
|
|
9567
10266
|
getMessageRow(messageId) {
|
|
@@ -9570,7 +10269,7 @@ var AIChatManager = class {
|
|
|
9570
10269
|
`SELECT id, ai_chat_id as aiChatId, role, content, timestamp,
|
|
9571
10270
|
parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,
|
|
9572
10271
|
data_keywords as dataKeywords, data_message_count as dataMessageCount,
|
|
9573
|
-
content_blocks as contentBlocks, token_usage as tokenUsage
|
|
10272
|
+
content_blocks as contentBlocks, token_usage as tokenUsage, entity_refs as entityRefs
|
|
9574
10273
|
FROM ai_message WHERE id = ?`
|
|
9575
10274
|
).get(messageId);
|
|
9576
10275
|
return row ?? null;
|
|
@@ -9594,7 +10293,7 @@ var AIChatManager = class {
|
|
|
9594
10293
|
`SELECT id, ai_chat_id as aiChatId, role, content, timestamp,
|
|
9595
10294
|
parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,
|
|
9596
10295
|
data_keywords as dataKeywords, data_message_count as dataMessageCount,
|
|
9597
|
-
content_blocks as contentBlocks, token_usage as tokenUsage
|
|
10296
|
+
content_blocks as contentBlocks, token_usage as tokenUsage, entity_refs as entityRefs
|
|
9598
10297
|
FROM ai_message WHERE ai_chat_id = ? ORDER BY timestamp ASC, id ASC`
|
|
9599
10298
|
).all(aiChatId);
|
|
9600
10299
|
}
|
|
@@ -9604,16 +10303,16 @@ var AIChatManager = class {
|
|
|
9604
10303
|
if (allRows.length === 0) return [];
|
|
9605
10304
|
const rowMap = new Map(allRows.map((row) => [row.id, row]));
|
|
9606
10305
|
let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
|
|
9607
|
-
const
|
|
10306
|
+
const path31 = [];
|
|
9608
10307
|
const seen = /* @__PURE__ */ new Set();
|
|
9609
10308
|
while (currentId && !seen.has(currentId)) {
|
|
9610
10309
|
seen.add(currentId);
|
|
9611
10310
|
const row = rowMap.get(currentId);
|
|
9612
10311
|
if (!row) break;
|
|
9613
|
-
|
|
10312
|
+
path31.push(row);
|
|
9614
10313
|
currentId = row.parentId;
|
|
9615
10314
|
}
|
|
9616
|
-
return
|
|
10315
|
+
return path31.length > 0 ? path31.reverse() : allRows;
|
|
9617
10316
|
}
|
|
9618
10317
|
// ==================== 生命周期 ====================
|
|
9619
10318
|
close() {
|
|
@@ -9670,20 +10369,35 @@ var AIChatManager = class {
|
|
|
9670
10369
|
}
|
|
9671
10370
|
// ==================== 对话管理 ====================
|
|
9672
10371
|
createAIChat(sessionId, title, assistantId) {
|
|
10372
|
+
return this.createChat("session", sessionId, title, assistantId);
|
|
10373
|
+
}
|
|
10374
|
+
createGlobalAIChat(title, assistantId) {
|
|
10375
|
+
return this.createChat("global", "", title, assistantId);
|
|
10376
|
+
}
|
|
10377
|
+
createChat(kind, sessionId, title, assistantId) {
|
|
9673
10378
|
const db = this.getDb();
|
|
9674
10379
|
const now = Math.floor(Date.now() / 1e3);
|
|
9675
10380
|
const id = this.generateId("conv");
|
|
9676
10381
|
db.prepare(
|
|
9677
|
-
`INSERT INTO ai_chat (id, session_id, title, assistant_id, created_at, updated_at)
|
|
9678
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
9679
|
-
).run(id, sessionId, title || null, assistantId, now, now);
|
|
9680
|
-
return {
|
|
10382
|
+
`INSERT INTO ai_chat (id, session_id, kind, title, assistant_id, created_at, updated_at)
|
|
10383
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
10384
|
+
).run(id, sessionId, kind, title || null, assistantId, now, now);
|
|
10385
|
+
return {
|
|
10386
|
+
id,
|
|
10387
|
+
sessionId,
|
|
10388
|
+
kind,
|
|
10389
|
+
title: title || null,
|
|
10390
|
+
assistantId,
|
|
10391
|
+
activeMessageId: null,
|
|
10392
|
+
createdAt: now,
|
|
10393
|
+
updatedAt: now
|
|
10394
|
+
};
|
|
9681
10395
|
}
|
|
9682
10396
|
getAIChatCountsBySession() {
|
|
9683
10397
|
const result = /* @__PURE__ */ new Map();
|
|
9684
10398
|
try {
|
|
9685
10399
|
const db = this.getDb();
|
|
9686
|
-
const rows = db.prepare("SELECT session_id, COUNT(*) as count FROM ai_chat GROUP BY session_id").all();
|
|
10400
|
+
const rows = db.prepare("SELECT session_id, COUNT(*) as count FROM ai_chat WHERE kind = 'session' GROUP BY session_id").all();
|
|
9687
10401
|
for (const row of rows) {
|
|
9688
10402
|
result.set(row.session_id, row.count);
|
|
9689
10403
|
}
|
|
@@ -9694,16 +10408,24 @@ var AIChatManager = class {
|
|
|
9694
10408
|
getAIChats(sessionId) {
|
|
9695
10409
|
const db = this.getDb();
|
|
9696
10410
|
return db.prepare(
|
|
9697
|
-
`SELECT id, session_id as sessionId, title, assistant_id as assistantId,
|
|
10411
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
9698
10412
|
active_message_id as activeMessageId,
|
|
9699
10413
|
created_at as createdAt, updated_at as updatedAt
|
|
9700
|
-
FROM ai_chat WHERE session_id = ? ORDER BY updated_at DESC`
|
|
10414
|
+
FROM ai_chat WHERE kind = 'session' AND session_id = ? ORDER BY updated_at DESC`
|
|
9701
10415
|
).all(sessionId);
|
|
9702
10416
|
}
|
|
9703
|
-
|
|
10417
|
+
getGlobalAIChats() {
|
|
10418
|
+
return this.getDb().prepare(
|
|
10419
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
10420
|
+
active_message_id as activeMessageId,
|
|
10421
|
+
created_at as createdAt, updated_at as updatedAt
|
|
10422
|
+
FROM ai_chat WHERE kind = 'global' ORDER BY updated_at DESC`
|
|
10423
|
+
).all();
|
|
10424
|
+
}
|
|
10425
|
+
getAIChat(aiChatId) {
|
|
9704
10426
|
const db = this.getDb();
|
|
9705
10427
|
const row = db.prepare(
|
|
9706
|
-
`SELECT id, session_id as sessionId, title, assistant_id as assistantId,
|
|
10428
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
9707
10429
|
active_message_id as activeMessageId,
|
|
9708
10430
|
created_at as createdAt, updated_at as updatedAt
|
|
9709
10431
|
FROM ai_chat WHERE id = ?`
|
|
@@ -9723,13 +10445,79 @@ var AIChatManager = class {
|
|
|
9723
10445
|
return result.changes > 0;
|
|
9724
10446
|
}
|
|
9725
10447
|
// ==================== 消息管理 ====================
|
|
9726
|
-
addMessage(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage) {
|
|
10448
|
+
addMessage(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage, entityRefs) {
|
|
10449
|
+
return this.addMessageWithEntityRefs(
|
|
10450
|
+
aiChatId,
|
|
10451
|
+
role,
|
|
10452
|
+
content,
|
|
10453
|
+
dataKeywords,
|
|
10454
|
+
dataMessageCount,
|
|
10455
|
+
contentBlocks,
|
|
10456
|
+
tokenUsage,
|
|
10457
|
+
role === "user" ? entityRefs : void 0
|
|
10458
|
+
);
|
|
10459
|
+
}
|
|
10460
|
+
addMessagePair(aiChatId, userMessage, assistantMessage) {
|
|
10461
|
+
const db = this.getDb();
|
|
10462
|
+
return db.transaction(() => ({
|
|
10463
|
+
userMessage: this.addMessage(
|
|
10464
|
+
aiChatId,
|
|
10465
|
+
"user",
|
|
10466
|
+
userMessage.content,
|
|
10467
|
+
void 0,
|
|
10468
|
+
void 0,
|
|
10469
|
+
void 0,
|
|
10470
|
+
void 0,
|
|
10471
|
+
userMessage.entityRefs
|
|
10472
|
+
),
|
|
10473
|
+
assistantMessage: this.addMessage(
|
|
10474
|
+
aiChatId,
|
|
10475
|
+
"assistant",
|
|
10476
|
+
assistantMessage.content,
|
|
10477
|
+
void 0,
|
|
10478
|
+
void 0,
|
|
10479
|
+
assistantMessage.contentBlocks,
|
|
10480
|
+
assistantMessage.tokenUsage
|
|
10481
|
+
)
|
|
10482
|
+
}))();
|
|
10483
|
+
}
|
|
10484
|
+
/** Atomically replaces the latest user/assistant round on the active path. */
|
|
10485
|
+
replaceLatestMessageRound(aiChatId, input) {
|
|
10486
|
+
const db = this.getDb();
|
|
10487
|
+
return db.transaction(() => {
|
|
10488
|
+
const activePath = this.getActivePathRows(aiChatId);
|
|
10489
|
+
const latestUserIndex = activePath.findLastIndex((message) => message.role === "user");
|
|
10490
|
+
const userMessage = activePath[latestUserIndex];
|
|
10491
|
+
if (!userMessage || userMessage.id !== input.userMessageId) {
|
|
10492
|
+
throw new Error("Only the latest user message can be edited");
|
|
10493
|
+
}
|
|
10494
|
+
const followingMessages = activePath.slice(latestUserIndex + 1);
|
|
10495
|
+
if (followingMessages.length > 1 || followingMessages.length === 1 && (followingMessages[0]?.role !== "assistant" || followingMessages[0].parentId !== input.userMessageId)) {
|
|
10496
|
+
throw new Error("Latest user message is not a replaceable round");
|
|
10497
|
+
}
|
|
10498
|
+
const oldAssistantMessage = followingMessages[0];
|
|
10499
|
+
this.updateMessageContent(input.userMessageId, input.userContent);
|
|
10500
|
+
if (oldAssistantMessage) {
|
|
10501
|
+
this.deleteAndRelinkMessage(aiChatId, oldAssistantMessage.id);
|
|
10502
|
+
}
|
|
10503
|
+
return this.insertMessageAfter(
|
|
10504
|
+
aiChatId,
|
|
10505
|
+
input.userMessageId,
|
|
10506
|
+
"assistant",
|
|
10507
|
+
input.assistantMessage.content,
|
|
10508
|
+
input.assistantMessage.contentBlocks,
|
|
10509
|
+
input.assistantMessage.tokenUsage
|
|
10510
|
+
);
|
|
10511
|
+
})();
|
|
10512
|
+
}
|
|
10513
|
+
addMessageWithEntityRefs(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage, entityRefs) {
|
|
9727
10514
|
const db = this.getDb();
|
|
9728
10515
|
const now = Math.floor(Date.now() / 1e3);
|
|
9729
10516
|
const id = this.generateId("msg");
|
|
9730
10517
|
const parentId = this.getActiveMessageId(aiChatId);
|
|
9731
10518
|
const siblingGroupId = id;
|
|
9732
10519
|
const branchIndex = 0;
|
|
10520
|
+
const persistedEntityRefs = entityRefs?.length ? entityRefs : void 0;
|
|
9733
10521
|
const pendingDebug = role === "assistant" ? this.pendingDebugContextMap.get(aiChatId) : void 0;
|
|
9734
10522
|
if (pendingDebug) {
|
|
9735
10523
|
this.pendingDebugContextMap.delete(aiChatId);
|
|
@@ -9737,9 +10525,9 @@ var AIChatManager = class {
|
|
|
9737
10525
|
db.prepare(
|
|
9738
10526
|
`INSERT INTO ai_message (
|
|
9739
10527
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9740
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10528
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9741
10529
|
)
|
|
9742
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
10530
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
9743
10531
|
).run(
|
|
9744
10532
|
id,
|
|
9745
10533
|
aiChatId,
|
|
@@ -9753,7 +10541,8 @@ var AIChatManager = class {
|
|
|
9753
10541
|
pendingDebug ?? null,
|
|
9754
10542
|
parentId,
|
|
9755
10543
|
siblingGroupId,
|
|
9756
|
-
branchIndex
|
|
10544
|
+
branchIndex,
|
|
10545
|
+
persistedEntityRefs?.length ? JSON.stringify(persistedEntityRefs) : null
|
|
9757
10546
|
);
|
|
9758
10547
|
db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(id, now, aiChatId);
|
|
9759
10548
|
return {
|
|
@@ -9766,12 +10555,17 @@ var AIChatManager = class {
|
|
|
9766
10555
|
dataKeywords,
|
|
9767
10556
|
dataMessageCount,
|
|
9768
10557
|
contentBlocks,
|
|
9769
|
-
tokenUsage
|
|
10558
|
+
tokenUsage,
|
|
10559
|
+
entityRefs: persistedEntityRefs?.length ? persistedEntityRefs : void 0
|
|
9770
10560
|
};
|
|
9771
10561
|
}
|
|
9772
10562
|
getMessages(aiChatId) {
|
|
9773
10563
|
return this.getActivePathRows(aiChatId).map((row) => this.parseMessageRow(row));
|
|
9774
10564
|
}
|
|
10565
|
+
getMessage(messageId) {
|
|
10566
|
+
const row = this.getMessageRow(messageId);
|
|
10567
|
+
return row ? this.parseMessageRow(row) : null;
|
|
10568
|
+
}
|
|
9775
10569
|
deleteMessage(messageId) {
|
|
9776
10570
|
const db = this.getDb();
|
|
9777
10571
|
const result = db.prepare("DELETE FROM ai_message WHERE id = ?").run(messageId);
|
|
@@ -9811,9 +10605,9 @@ var AIChatManager = class {
|
|
|
9811
10605
|
const newConvId = this.generateId("conv");
|
|
9812
10606
|
const forkTitle = title || `${source.title || "Untitled"} (fork)`;
|
|
9813
10607
|
db.prepare(
|
|
9814
|
-
`INSERT INTO ai_chat (id, session_id, title, assistant_id, active_message_id, created_at, updated_at)
|
|
9815
|
-
VALUES (?, ?, ?, ?, NULL, ?, ?)`
|
|
9816
|
-
).run(newConvId, source.sessionId, forkTitle, source.assistantId, now, now);
|
|
10608
|
+
`INSERT INTO ai_chat (id, session_id, kind, title, assistant_id, active_message_id, created_at, updated_at)
|
|
10609
|
+
VALUES (?, ?, ?, ?, ?, NULL, ?, ?)`
|
|
10610
|
+
).run(newConvId, source.sessionId, source.kind, forkTitle, source.assistantId, now, now);
|
|
9817
10611
|
const idMap = /* @__PURE__ */ new Map();
|
|
9818
10612
|
let lastNewId = null;
|
|
9819
10613
|
for (const row of messagesToCopy) {
|
|
@@ -9823,9 +10617,9 @@ var AIChatManager = class {
|
|
|
9823
10617
|
db.prepare(
|
|
9824
10618
|
`INSERT INTO ai_message (
|
|
9825
10619
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9826
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10620
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9827
10621
|
)
|
|
9828
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, 0)`
|
|
10622
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, 0, ?)`
|
|
9829
10623
|
).run(
|
|
9830
10624
|
newMsgId,
|
|
9831
10625
|
newConvId,
|
|
@@ -9837,7 +10631,8 @@ var AIChatManager = class {
|
|
|
9837
10631
|
row.contentBlocks,
|
|
9838
10632
|
row.tokenUsage,
|
|
9839
10633
|
newParentId,
|
|
9840
|
-
newMsgId
|
|
10634
|
+
newMsgId,
|
|
10635
|
+
row.role === "user" || row.role === "summary" ? row.entityRefs : null
|
|
9841
10636
|
);
|
|
9842
10637
|
lastNewId = newMsgId;
|
|
9843
10638
|
}
|
|
@@ -9847,6 +10642,7 @@ var AIChatManager = class {
|
|
|
9847
10642
|
return {
|
|
9848
10643
|
id: newConvId,
|
|
9849
10644
|
sessionId: source.sessionId,
|
|
10645
|
+
kind: source.kind,
|
|
9850
10646
|
title: forkTitle,
|
|
9851
10647
|
assistantId: source.assistantId,
|
|
9852
10648
|
activeMessageId: lastNewId,
|
|
@@ -9854,9 +10650,15 @@ var AIChatManager = class {
|
|
|
9854
10650
|
updatedAt: now
|
|
9855
10651
|
};
|
|
9856
10652
|
}
|
|
9857
|
-
|
|
10653
|
+
/** Replace message text and user entity references atomically; omitted references clear the previous value. */
|
|
10654
|
+
updateMessageContent(messageId, newContent, entityRefs) {
|
|
9858
10655
|
const db = this.getDb();
|
|
9859
|
-
const
|
|
10656
|
+
const serializedEntityRefs = entityRefs?.length ? JSON.stringify(entityRefs) : null;
|
|
10657
|
+
const result = db.prepare(
|
|
10658
|
+
`UPDATE ai_message
|
|
10659
|
+
SET content = ?, entity_refs = CASE WHEN role = 'user' THEN ? ELSE NULL END
|
|
10660
|
+
WHERE id = ?`
|
|
10661
|
+
).run(newContent, serializedEntityRefs, messageId);
|
|
9860
10662
|
if (result.changes === 0) throw new Error("Message not found");
|
|
9861
10663
|
}
|
|
9862
10664
|
deleteAndRelinkMessage(aiChatId, messageId) {
|
|
@@ -9881,10 +10683,11 @@ var AIChatManager = class {
|
|
|
9881
10683
|
}
|
|
9882
10684
|
db.prepare("DELETE FROM ai_message WHERE id = ?").run(messageId);
|
|
9883
10685
|
}
|
|
9884
|
-
insertMessageAfter(aiChatId, afterMessageId, role, content, contentBlocks, tokenUsage) {
|
|
10686
|
+
insertMessageAfter(aiChatId, afterMessageId, role, content, contentBlocks, tokenUsage, entityRefs) {
|
|
9885
10687
|
const db = this.getDb();
|
|
9886
10688
|
const now = Math.floor(Date.now() / 1e3);
|
|
9887
10689
|
const id = this.generateId("msg");
|
|
10690
|
+
const persistedEntityRefs = role === "user" ? entityRefs : void 0;
|
|
9888
10691
|
const pendingDebug = role === "assistant" ? this.pendingDebugContextMap.get(aiChatId) : void 0;
|
|
9889
10692
|
if (pendingDebug) {
|
|
9890
10693
|
this.pendingDebugContextMap.delete(aiChatId);
|
|
@@ -9893,9 +10696,9 @@ var AIChatManager = class {
|
|
|
9893
10696
|
db.prepare(
|
|
9894
10697
|
`INSERT INTO ai_message (
|
|
9895
10698
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9896
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10699
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9897
10700
|
)
|
|
9898
|
-
VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, 0)`
|
|
10701
|
+
VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, 0, ?)`
|
|
9899
10702
|
).run(
|
|
9900
10703
|
id,
|
|
9901
10704
|
aiChatId,
|
|
@@ -9906,7 +10709,8 @@ var AIChatManager = class {
|
|
|
9906
10709
|
tokenUsage ? JSON.stringify(tokenUsage) : null,
|
|
9907
10710
|
pendingDebug ?? null,
|
|
9908
10711
|
afterMessageId,
|
|
9909
|
-
id
|
|
10712
|
+
id,
|
|
10713
|
+
persistedEntityRefs?.length ? JSON.stringify(persistedEntityRefs) : null
|
|
9910
10714
|
);
|
|
9911
10715
|
if (childRow) {
|
|
9912
10716
|
db.prepare("UPDATE ai_message SET parent_id = ? WHERE id = ?").run(id, childRow.id);
|
|
@@ -9922,7 +10726,8 @@ var AIChatManager = class {
|
|
|
9922
10726
|
timestamp: now,
|
|
9923
10727
|
parentId: afterMessageId,
|
|
9924
10728
|
contentBlocks,
|
|
9925
|
-
tokenUsage
|
|
10729
|
+
tokenUsage,
|
|
10730
|
+
entityRefs: persistedEntityRefs?.length ? persistedEntityRefs : void 0
|
|
9926
10731
|
};
|
|
9927
10732
|
}
|
|
9928
10733
|
getAIChatTokenUsage(aiChatId) {
|
|
@@ -9986,11 +10791,26 @@ var AIChatManager = class {
|
|
|
9986
10791
|
}
|
|
9987
10792
|
const contextMessages = bufferBoundary ? validMessages.filter((m) => m.role !== "summary" && m.timestamp >= bufferBoundary) : [];
|
|
9988
10793
|
result = [
|
|
9989
|
-
{
|
|
9990
|
-
|
|
10794
|
+
{
|
|
10795
|
+
role: "summary",
|
|
10796
|
+
content: summaryMsg.content,
|
|
10797
|
+
contentBlocks: summaryMsg.contentBlocks,
|
|
10798
|
+
entityRefs: summaryMsg.entityRefs
|
|
10799
|
+
},
|
|
10800
|
+
...contextMessages.map((m) => ({
|
|
10801
|
+
role: m.role,
|
|
10802
|
+
content: m.content,
|
|
10803
|
+
contentBlocks: m.contentBlocks,
|
|
10804
|
+
entityRefs: m.role === "user" ? m.entityRefs : void 0
|
|
10805
|
+
}))
|
|
9991
10806
|
];
|
|
9992
10807
|
} else {
|
|
9993
|
-
result = validMessages.map((m) => ({
|
|
10808
|
+
result = validMessages.map((m) => ({
|
|
10809
|
+
role: m.role,
|
|
10810
|
+
content: m.content,
|
|
10811
|
+
contentBlocks: m.contentBlocks,
|
|
10812
|
+
entityRefs: m.role === "user" ? m.entityRefs : void 0
|
|
10813
|
+
}));
|
|
9994
10814
|
}
|
|
9995
10815
|
if (maxMessages && result.length > maxMessages) {
|
|
9996
10816
|
if (result.length > 0 && result[0].role === "summary") {
|
|
@@ -10003,7 +10823,7 @@ var AIChatManager = class {
|
|
|
10003
10823
|
return result;
|
|
10004
10824
|
}
|
|
10005
10825
|
// ==================== Summary / 压缩专用 ====================
|
|
10006
|
-
addSummaryMessage(aiChatId, content, meta) {
|
|
10826
|
+
addSummaryMessage(aiChatId, content, meta, entityRefs) {
|
|
10007
10827
|
const contentBlocks = [
|
|
10008
10828
|
{
|
|
10009
10829
|
type: "summary_meta",
|
|
@@ -10011,7 +10831,16 @@ var AIChatManager = class {
|
|
|
10011
10831
|
compressedMessageCount: meta.compressedMessageCount
|
|
10012
10832
|
}
|
|
10013
10833
|
];
|
|
10014
|
-
return this.
|
|
10834
|
+
return this.addMessageWithEntityRefs(
|
|
10835
|
+
aiChatId,
|
|
10836
|
+
"summary",
|
|
10837
|
+
content,
|
|
10838
|
+
void 0,
|
|
10839
|
+
void 0,
|
|
10840
|
+
contentBlocks,
|
|
10841
|
+
void 0,
|
|
10842
|
+
entityRefs
|
|
10843
|
+
);
|
|
10015
10844
|
}
|
|
10016
10845
|
getLatestSummary(aiChatId) {
|
|
10017
10846
|
const row = [...this.getActivePathRows(aiChatId)].reverse().find((message) => message.role === "summary");
|
|
@@ -10028,7 +10857,8 @@ var AIChatManager = class {
|
|
|
10028
10857
|
role: row.role,
|
|
10029
10858
|
content: row.content,
|
|
10030
10859
|
timestamp: row.timestamp,
|
|
10031
|
-
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0
|
|
10860
|
+
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0,
|
|
10861
|
+
entityRefs: row.role === "user" && row.entityRefs ? JSON.parse(row.entityRefs) : void 0
|
|
10032
10862
|
};
|
|
10033
10863
|
}
|
|
10034
10864
|
getMessageCountAfterSummary(aiChatId) {
|
|
@@ -10046,6 +10876,747 @@ var AIChatManager = class {
|
|
|
10046
10876
|
}
|
|
10047
10877
|
};
|
|
10048
10878
|
|
|
10879
|
+
// ../../packages/node-runtime/src/nlp/segmenter.ts
|
|
10880
|
+
import * as fs9 from "fs";
|
|
10881
|
+
import * as path16 from "path";
|
|
10882
|
+
import { createRequire } from "module";
|
|
10883
|
+
var _nlpDir = null;
|
|
10884
|
+
var jiebaInstances = /* @__PURE__ */ new Map();
|
|
10885
|
+
var require2 = createRequire(import.meta.url);
|
|
10886
|
+
function initNlpDir(nlpDir) {
|
|
10887
|
+
_nlpDir = nlpDir;
|
|
10888
|
+
}
|
|
10889
|
+
function getNlpDir() {
|
|
10890
|
+
return _nlpDir;
|
|
10891
|
+
}
|
|
10892
|
+
function existsDictOnDisk(dictId) {
|
|
10893
|
+
if (!_nlpDir) return false;
|
|
10894
|
+
return fs9.existsSync(path16.join(_nlpDir, `${dictId}.dict`));
|
|
10895
|
+
}
|
|
10896
|
+
function tryLoadDictFromDisk(dictId) {
|
|
10897
|
+
if (!_nlpDir) return null;
|
|
10898
|
+
const dictPath = path16.join(_nlpDir, `${dictId}.dict`);
|
|
10899
|
+
if (!fs9.existsSync(dictPath)) return null;
|
|
10900
|
+
try {
|
|
10901
|
+
return fs9.readFileSync(dictPath);
|
|
10902
|
+
} catch {
|
|
10903
|
+
return null;
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
function getJieba(dictType = "default") {
|
|
10907
|
+
const effectiveType = dictType === "default" ? "zh-CN" : dictType;
|
|
10908
|
+
const cached = jiebaInstances.get(effectiveType);
|
|
10909
|
+
if (cached) {
|
|
10910
|
+
if (cached.fromDiskDict === existsDictOnDisk(effectiveType)) return cached.instance;
|
|
10911
|
+
jiebaInstances.delete(effectiveType);
|
|
10912
|
+
console.error(`[NLP] jieba cache invalidated (dict ${cached.fromDiskDict ? "removed" : "added"}): ${effectiveType}`);
|
|
10913
|
+
}
|
|
10914
|
+
try {
|
|
10915
|
+
const { Jieba } = require2("@node-rs/jieba");
|
|
10916
|
+
let instance2;
|
|
10917
|
+
const diskDict = tryLoadDictFromDisk(effectiveType);
|
|
10918
|
+
if (diskDict) {
|
|
10919
|
+
instance2 = Jieba.withDict(diskDict);
|
|
10920
|
+
console.error(`[NLP] jieba dict loaded: ${effectiveType} (${diskDict.length} bytes)`);
|
|
10921
|
+
} else {
|
|
10922
|
+
instance2 = new Jieba();
|
|
10923
|
+
console.warn(`[NLP] jieba dict missing: ${effectiveType}, fallback to built-in tokenizer`);
|
|
10924
|
+
}
|
|
10925
|
+
jiebaInstances.set(effectiveType, { instance: instance2, fromDiskDict: !!diskDict });
|
|
10926
|
+
return instance2;
|
|
10927
|
+
} catch (error) {
|
|
10928
|
+
console.error(`[NLP] Failed to load jieba module (dict=${effectiveType}):`, error);
|
|
10929
|
+
throw new Error(`jieba \u6A21\u5757\u52A0\u8F7D\u5931\u8D25 (${effectiveType})`);
|
|
10930
|
+
}
|
|
10931
|
+
}
|
|
10932
|
+
function clearJiebaInstance(dictType) {
|
|
10933
|
+
jiebaInstances.delete(dictType);
|
|
10934
|
+
console.error(`[NLP] jieba instance cleared: ${dictType}`);
|
|
10935
|
+
}
|
|
10936
|
+
function segmentChinese(text, options = {}) {
|
|
10937
|
+
const { posFilterMode = "meaningful", customPosTags, dictType = "default" } = options;
|
|
10938
|
+
const cleaned = cleanText(text);
|
|
10939
|
+
if (!cleaned) return [];
|
|
10940
|
+
try {
|
|
10941
|
+
const jieba = getJieba(dictType);
|
|
10942
|
+
if (posFilterMode === "all") return jieba.cut(cleaned, false);
|
|
10943
|
+
const tagged = jieba.tag(cleaned);
|
|
10944
|
+
const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
|
|
10945
|
+
return tagged.filter((item) => allowedTags.has(item.tag)).map((item) => item.word);
|
|
10946
|
+
} catch (error) {
|
|
10947
|
+
console.error("[NLP] Chinese segmentation failed:", error);
|
|
10948
|
+
try {
|
|
10949
|
+
return getJieba("default").cut(cleanText(text), false);
|
|
10950
|
+
} catch {
|
|
10951
|
+
return cleaned.split("");
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
}
|
|
10955
|
+
function segmentEnglish(text) {
|
|
10956
|
+
const cleaned = cleanText(text);
|
|
10957
|
+
if (!cleaned) return [];
|
|
10958
|
+
try {
|
|
10959
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
|
|
10960
|
+
return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
|
|
10961
|
+
} catch {
|
|
10962
|
+
return cleaned.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
function segmentJapanese(text) {
|
|
10966
|
+
const cleaned = cleanText(text);
|
|
10967
|
+
if (!cleaned) return [];
|
|
10968
|
+
try {
|
|
10969
|
+
const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
|
|
10970
|
+
return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment);
|
|
10971
|
+
} catch {
|
|
10972
|
+
return cleaned.split("").filter((ch) => ch.trim().length > 0);
|
|
10973
|
+
}
|
|
10974
|
+
}
|
|
10975
|
+
function segment(text, locale, options = {}) {
|
|
10976
|
+
const {
|
|
10977
|
+
minLength,
|
|
10978
|
+
posFilterMode = "meaningful",
|
|
10979
|
+
customPosTags,
|
|
10980
|
+
enableStopwords = true,
|
|
10981
|
+
dictType = "default"
|
|
10982
|
+
} = options;
|
|
10983
|
+
const isChinese2 = locale.startsWith("zh");
|
|
10984
|
+
const isJapanese = locale === "ja-JP";
|
|
10985
|
+
const defaultMinLength = isChinese2 || isJapanese ? 2 : 3;
|
|
10986
|
+
const effectiveMinLength = minLength ?? defaultMinLength;
|
|
10987
|
+
let words;
|
|
10988
|
+
if (isChinese2) {
|
|
10989
|
+
words = segmentChinese(text, { posFilterMode, customPosTags, dictType });
|
|
10990
|
+
} else if (isJapanese) {
|
|
10991
|
+
words = segmentJapanese(text);
|
|
10992
|
+
} else {
|
|
10993
|
+
words = segmentEnglish(text);
|
|
10994
|
+
}
|
|
10995
|
+
return words.filter((word) => isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword));
|
|
10996
|
+
}
|
|
10997
|
+
function batchSegmentWithFrequency(texts, locale, options = {}) {
|
|
10998
|
+
const {
|
|
10999
|
+
minLength,
|
|
11000
|
+
minCount = 2,
|
|
11001
|
+
topN = 100,
|
|
11002
|
+
posFilterMode,
|
|
11003
|
+
customPosTags,
|
|
11004
|
+
enableStopwords,
|
|
11005
|
+
dictType,
|
|
11006
|
+
excludeWords
|
|
11007
|
+
} = options;
|
|
11008
|
+
const wordFrequency = /* @__PURE__ */ new Map();
|
|
11009
|
+
const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
|
|
11010
|
+
for (const text of texts) {
|
|
11011
|
+
const words = segment(text, locale, { minLength, posFilterMode, customPosTags, enableStopwords, dictType });
|
|
11012
|
+
for (const word of words) {
|
|
11013
|
+
if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
|
|
11014
|
+
wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
11018
|
+
let totalWords = 0;
|
|
11019
|
+
for (const [word, count] of wordFrequency) {
|
|
11020
|
+
if (count >= minCount) {
|
|
11021
|
+
filtered.set(word, count);
|
|
11022
|
+
totalWords += count;
|
|
11023
|
+
}
|
|
11024
|
+
}
|
|
11025
|
+
const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
|
|
11026
|
+
return { words: new Map(sorted), uniqueWords: filtered.size, totalWords };
|
|
11027
|
+
}
|
|
11028
|
+
function batchSegmentChineseWithStats(texts, locale, options = {}) {
|
|
11029
|
+
const {
|
|
11030
|
+
minLength,
|
|
11031
|
+
minCount = 2,
|
|
11032
|
+
topN = 100,
|
|
11033
|
+
posFilterMode = "meaningful",
|
|
11034
|
+
customPosTags,
|
|
11035
|
+
enableStopwords = true,
|
|
11036
|
+
dictType = "default",
|
|
11037
|
+
excludeWords
|
|
11038
|
+
} = options;
|
|
11039
|
+
const effectiveMinLength = minLength ?? 2;
|
|
11040
|
+
const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
|
|
11041
|
+
const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
|
|
11042
|
+
const wordFrequency = /* @__PURE__ */ new Map();
|
|
11043
|
+
const posTagStats = /* @__PURE__ */ new Map();
|
|
11044
|
+
try {
|
|
11045
|
+
const jieba = getJieba(dictType);
|
|
11046
|
+
for (const text of texts) {
|
|
11047
|
+
const cleaned = cleanText(text);
|
|
11048
|
+
if (!cleaned) continue;
|
|
11049
|
+
for (const { tag, word } of jieba.tag(cleaned)) {
|
|
11050
|
+
if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue;
|
|
11051
|
+
posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1);
|
|
11052
|
+
if (!allowedTags.has(tag)) continue;
|
|
11053
|
+
if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
|
|
11054
|
+
wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
|
|
11055
|
+
}
|
|
11056
|
+
}
|
|
11057
|
+
} catch (error) {
|
|
11058
|
+
console.error("[NLP] Chinese single-pass segmentation failed:", error);
|
|
11059
|
+
}
|
|
11060
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
11061
|
+
let totalWords = 0;
|
|
11062
|
+
for (const [word, count] of wordFrequency) {
|
|
11063
|
+
if (count >= minCount) {
|
|
11064
|
+
filtered.set(word, count);
|
|
11065
|
+
totalWords += count;
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
|
|
11069
|
+
return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats };
|
|
11070
|
+
}
|
|
11071
|
+
function collectPosTagStats(texts, minWordLength = 2, enableStopwords = true, dictType = "default") {
|
|
11072
|
+
const posStats = /* @__PURE__ */ new Map();
|
|
11073
|
+
try {
|
|
11074
|
+
const jieba = getJieba(dictType);
|
|
11075
|
+
for (const text of texts) {
|
|
11076
|
+
const cleaned = cleanText(text);
|
|
11077
|
+
if (!cleaned) continue;
|
|
11078
|
+
const tagged = jieba.tag(cleaned);
|
|
11079
|
+
for (const item of tagged) {
|
|
11080
|
+
if (!isValidWord(item.word, "zh-CN", minWordLength, enableStopwords, isStopword)) continue;
|
|
11081
|
+
posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1);
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
} catch (error) {
|
|
11085
|
+
console.error("[NLP] Failed to collect POS stats:", error);
|
|
11086
|
+
}
|
|
11087
|
+
return posStats;
|
|
11088
|
+
}
|
|
11089
|
+
function getPosTagDefinitions() {
|
|
11090
|
+
return POS_TAG_DEFINITIONS;
|
|
11091
|
+
}
|
|
11092
|
+
|
|
11093
|
+
// ../../packages/node-runtime/src/ai/memory.ts
|
|
11094
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11095
|
+
import * as fs10 from "fs";
|
|
11096
|
+
import * as path17 from "path";
|
|
11097
|
+
import Database3 from "better-sqlite3";
|
|
11098
|
+
var AI_MEMORY_CONTENT_MAX_CHARS = 2e3;
|
|
11099
|
+
var AI_MEMORY_SCHEMA_VERSION = 2;
|
|
11100
|
+
var AI_MEMORY_PROMPT_MAX_ENTRIES = 20;
|
|
11101
|
+
var AI_MEMORY_PROMPT_MAX_CONTENT_CHARS = 4e3;
|
|
11102
|
+
var AI_MEMORY_PROMPT_BASE_USER_ENTRIES = 5;
|
|
11103
|
+
var AI_MEMORY_SUBSTRING_MIN_CHARS = 3;
|
|
11104
|
+
var NUMERIC_MEMORY_TOKEN_REGEX = new RegExp("\\p{Number}+", "gu");
|
|
11105
|
+
var SYMBOL_SUFFIX_MEMORY_TOKEN_REGEX = /[\p{Letter}\p{Number}]+(?:\+\+|#)/gu;
|
|
11106
|
+
function rankMemoryEntries(entries, options) {
|
|
11107
|
+
const recentEntries = [...entries].sort(compareRecentMemoryEntries);
|
|
11108
|
+
const query = normalizeSearchText(options.query);
|
|
11109
|
+
if (!query) {
|
|
11110
|
+
return { entries: recentEntries, retrievalMode: "recent_fallback", matchedCount: 0 };
|
|
11111
|
+
}
|
|
11112
|
+
const locale = normalizeMemoryLocale(options.locale);
|
|
11113
|
+
const queryTokens = tokenizeMemoryText(options.query, locale);
|
|
11114
|
+
const scored = recentEntries.map((entry) => ({
|
|
11115
|
+
entry,
|
|
11116
|
+
score: scoreMemoryEntry(entry.content, query, queryTokens, locale)
|
|
11117
|
+
}));
|
|
11118
|
+
const matchedCount = scored.filter((item) => item.score > 0).length;
|
|
11119
|
+
if (matchedCount === 0) {
|
|
11120
|
+
return { entries: recentEntries, retrievalMode: "recent_fallback", matchedCount: 0 };
|
|
11121
|
+
}
|
|
11122
|
+
scored.sort((left, right) => {
|
|
11123
|
+
if (left.score !== right.score) return right.score - left.score;
|
|
11124
|
+
if (left.entry.sourceType !== right.entry.sourceType) return left.entry.sourceType === "user" ? -1 : 1;
|
|
11125
|
+
return compareRecentMemoryEntries(left.entry, right.entry);
|
|
11126
|
+
});
|
|
11127
|
+
return {
|
|
11128
|
+
entries: scored.map((item) => item.entry),
|
|
11129
|
+
retrievalMode: "relevance",
|
|
11130
|
+
matchedCount
|
|
11131
|
+
};
|
|
11132
|
+
}
|
|
11133
|
+
function buildGlobalMemoryPrompt(entries, locale = "zh-CN", query = "") {
|
|
11134
|
+
const ranking = rankMemoryEntries(entries, { query, locale });
|
|
11135
|
+
const rankedEntries = ranking.entries;
|
|
11136
|
+
const recentUserEntries = [...entries].filter((entry) => entry.sourceType === "user").sort(compareRecentMemoryEntries).slice(0, AI_MEMORY_PROMPT_BASE_USER_ENTRIES);
|
|
11137
|
+
const prioritizedEntries = [];
|
|
11138
|
+
const prioritizedIds = /* @__PURE__ */ new Set();
|
|
11139
|
+
const topRelevantEntries = ranking.retrievalMode === "relevance" ? rankedEntries.slice(0, 1) : [];
|
|
11140
|
+
for (const entry of [...topRelevantEntries, ...recentUserEntries, ...rankedEntries]) {
|
|
11141
|
+
if (prioritizedIds.has(entry.id)) continue;
|
|
11142
|
+
prioritizedIds.add(entry.id);
|
|
11143
|
+
prioritizedEntries.push(entry);
|
|
11144
|
+
}
|
|
11145
|
+
const selected = [];
|
|
11146
|
+
let contentChars = 0;
|
|
11147
|
+
for (const entry of prioritizedEntries) {
|
|
11148
|
+
if (selected.length >= AI_MEMORY_PROMPT_MAX_ENTRIES) break;
|
|
11149
|
+
if (contentChars + entry.content.length > AI_MEMORY_PROMPT_MAX_CONTENT_CHARS) continue;
|
|
11150
|
+
selected.push(entry);
|
|
11151
|
+
contentChars += entry.content.length;
|
|
11152
|
+
}
|
|
11153
|
+
if (selected.length === 0) return "";
|
|
11154
|
+
const lines = selected.map((entry) => `- [id=${entry.id}; source=${entry.sourceType}] ${entry.content}`);
|
|
11155
|
+
if (selected.length < prioritizedEntries.length) {
|
|
11156
|
+
lines.push(
|
|
11157
|
+
locale.startsWith("zh") ? "- \u90E8\u5206\u5168\u5C40\u8BB0\u5FC6\u672A\u6CE8\u5165\uFF1B\u9700\u8981\u65F6\u8C03\u7528 memory_read \u8BFB\u53D6\u3002" : "- Some global memories were not injected; call memory_read when needed."
|
|
11158
|
+
);
|
|
11159
|
+
}
|
|
11160
|
+
return lines.join("\n");
|
|
11161
|
+
}
|
|
11162
|
+
function buildEntityMemoryPrompt(entityRefs, loadEntries, locale = "zh-CN", query = "") {
|
|
11163
|
+
const buckets = [];
|
|
11164
|
+
const seenScopes = /* @__PURE__ */ new Set();
|
|
11165
|
+
for (const ref of entityRefs ?? []) {
|
|
11166
|
+
const scope = ref.type === "contact" ? { scopeType: "contact", scopeId: ref.contactKey } : ref.sessionType === "group" ? { scopeType: "group", scopeId: ref.sessionId } : null;
|
|
11167
|
+
if (!scope?.scopeId) continue;
|
|
11168
|
+
const scopeKey = `${scope.scopeType}:${scope.scopeId}`;
|
|
11169
|
+
if (seenScopes.has(scopeKey)) continue;
|
|
11170
|
+
seenScopes.add(scopeKey);
|
|
11171
|
+
buckets.push({
|
|
11172
|
+
scope,
|
|
11173
|
+
displayName: ref.displayName,
|
|
11174
|
+
entries: rankMemoryEntries(loadEntries(scope), { query, locale }).entries
|
|
11175
|
+
});
|
|
11176
|
+
}
|
|
11177
|
+
const candidates = [];
|
|
11178
|
+
const maxBucketSize = Math.max(0, ...buckets.map((bucket) => bucket.entries.length));
|
|
11179
|
+
for (let index = 0; index < maxBucketSize; index++) {
|
|
11180
|
+
for (const bucket of buckets) {
|
|
11181
|
+
const entry = bucket.entries[index];
|
|
11182
|
+
if (entry) candidates.push({ bucket, entry });
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
const selected = [];
|
|
11186
|
+
let contentChars = 0;
|
|
11187
|
+
for (const candidate of candidates) {
|
|
11188
|
+
if (selected.length >= AI_MEMORY_PROMPT_MAX_ENTRIES) break;
|
|
11189
|
+
if (contentChars + candidate.entry.content.length > AI_MEMORY_PROMPT_MAX_CONTENT_CHARS) continue;
|
|
11190
|
+
selected.push(candidate);
|
|
11191
|
+
contentChars += candidate.entry.content.length;
|
|
11192
|
+
}
|
|
11193
|
+
if (selected.length === 0) return "";
|
|
11194
|
+
const lines = selected.map(({ bucket, entry }) => {
|
|
11195
|
+
return `- [scope=${bucket.scope.scopeType}; scope_id=${JSON.stringify(bucket.scope.scopeId)}; display_name=${JSON.stringify(bucket.displayName)}; id=${entry.id}; source=${entry.sourceType}] ${entry.content}`;
|
|
11196
|
+
});
|
|
11197
|
+
if (selected.length < candidates.length) {
|
|
11198
|
+
lines.push(
|
|
11199
|
+
locale.startsWith("zh") ? "- \u90E8\u5206\u5F53\u524D\u5B9E\u4F53\u8BB0\u5FC6\u672A\u6CE8\u5165\uFF1B\u9700\u8981\u65F6\u8C03\u7528 memory_read \u8BFB\u53D6\u3002" : "- Some memories for the current entities were not injected; call memory_read when needed."
|
|
11200
|
+
);
|
|
11201
|
+
}
|
|
11202
|
+
return lines.join("\n");
|
|
11203
|
+
}
|
|
11204
|
+
var AIMemoryService = class {
|
|
11205
|
+
db = null;
|
|
11206
|
+
dbPath;
|
|
11207
|
+
nativeBinding;
|
|
11208
|
+
now;
|
|
11209
|
+
idFactory;
|
|
11210
|
+
constructor(aiDataDir, options = {}) {
|
|
11211
|
+
this.dbPath = path17.join(aiDataDir, "memory.db");
|
|
11212
|
+
this.nativeBinding = options.nativeBinding;
|
|
11213
|
+
this.now = options.now ?? Date.now;
|
|
11214
|
+
this.idFactory = options.idFactory ?? (() => `memory_${randomUUID3()}`);
|
|
11215
|
+
}
|
|
11216
|
+
list(scope) {
|
|
11217
|
+
const db = this.getDb();
|
|
11218
|
+
if (!scope) {
|
|
11219
|
+
return db.prepare(
|
|
11220
|
+
`SELECT id,
|
|
11221
|
+
scope_type AS scopeType,
|
|
11222
|
+
scope_id AS scopeId,
|
|
11223
|
+
content,
|
|
11224
|
+
source_type AS sourceType,
|
|
11225
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11226
|
+
source_message_id AS sourceMessageId,
|
|
11227
|
+
created_at AS createdAt,
|
|
11228
|
+
updated_at AS updatedAt
|
|
11229
|
+
FROM ai_memory
|
|
11230
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11231
|
+
).all();
|
|
11232
|
+
}
|
|
11233
|
+
const normalized = normalizeScope(scope);
|
|
11234
|
+
if (normalized.scopeType === "global" || normalized.scopeType === "self") {
|
|
11235
|
+
return db.prepare(
|
|
11236
|
+
`SELECT id,
|
|
11237
|
+
scope_type AS scopeType,
|
|
11238
|
+
scope_id AS scopeId,
|
|
11239
|
+
content,
|
|
11240
|
+
source_type AS sourceType,
|
|
11241
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11242
|
+
source_message_id AS sourceMessageId,
|
|
11243
|
+
created_at AS createdAt,
|
|
11244
|
+
updated_at AS updatedAt
|
|
11245
|
+
FROM ai_memory
|
|
11246
|
+
WHERE scope_type = ? AND scope_id IS NULL
|
|
11247
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11248
|
+
).all(normalized.scopeType);
|
|
11249
|
+
}
|
|
11250
|
+
return db.prepare(
|
|
11251
|
+
`SELECT id,
|
|
11252
|
+
scope_type AS scopeType,
|
|
11253
|
+
scope_id AS scopeId,
|
|
11254
|
+
content,
|
|
11255
|
+
source_type AS sourceType,
|
|
11256
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11257
|
+
source_message_id AS sourceMessageId,
|
|
11258
|
+
created_at AS createdAt,
|
|
11259
|
+
updated_at AS updatedAt
|
|
11260
|
+
FROM ai_memory
|
|
11261
|
+
WHERE scope_type = ? AND scope_id = ?
|
|
11262
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11263
|
+
).all(normalized.scopeType, normalized.scopeId);
|
|
11264
|
+
}
|
|
11265
|
+
search(scope, query, locale = "zh-CN") {
|
|
11266
|
+
const startedAt = Date.now();
|
|
11267
|
+
const result = rankMemoryEntries(this.list(scope), { query, locale });
|
|
11268
|
+
appLogger.debug("ai-memory", "AI memory relevance search completed", {
|
|
11269
|
+
candidateCount: result.entries.length,
|
|
11270
|
+
matchedCount: result.matchedCount,
|
|
11271
|
+
retrievalMode: result.retrievalMode,
|
|
11272
|
+
durationMs: Date.now() - startedAt
|
|
11273
|
+
});
|
|
11274
|
+
return result;
|
|
11275
|
+
}
|
|
11276
|
+
get(id) {
|
|
11277
|
+
const row = this.getDb().prepare(
|
|
11278
|
+
`SELECT id,
|
|
11279
|
+
scope_type AS scopeType,
|
|
11280
|
+
scope_id AS scopeId,
|
|
11281
|
+
content,
|
|
11282
|
+
source_type AS sourceType,
|
|
11283
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11284
|
+
source_message_id AS sourceMessageId,
|
|
11285
|
+
created_at AS createdAt,
|
|
11286
|
+
updated_at AS updatedAt
|
|
11287
|
+
FROM ai_memory
|
|
11288
|
+
WHERE id = ?`
|
|
11289
|
+
).get(id);
|
|
11290
|
+
return row ?? null;
|
|
11291
|
+
}
|
|
11292
|
+
create(input) {
|
|
11293
|
+
return this.runWrite("create", () => {
|
|
11294
|
+
const scope = normalizeScope(input);
|
|
11295
|
+
const content = normalizeContent(input.content);
|
|
11296
|
+
const sourceType = normalizeSourceType(input.sourceType);
|
|
11297
|
+
const id = this.idFactory();
|
|
11298
|
+
const now = this.now();
|
|
11299
|
+
this.getDb().prepare(
|
|
11300
|
+
`INSERT INTO ai_memory (
|
|
11301
|
+
id, scope_type, scope_id, content, source_type,
|
|
11302
|
+
source_ai_chat_id, source_message_id, created_at, updated_at
|
|
11303
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
11304
|
+
).run(
|
|
11305
|
+
id,
|
|
11306
|
+
scope.scopeType,
|
|
11307
|
+
scope.scopeId,
|
|
11308
|
+
content,
|
|
11309
|
+
sourceType,
|
|
11310
|
+
normalizeOptionalId(input.sourceAIChatId),
|
|
11311
|
+
normalizeOptionalId(input.sourceMessageId),
|
|
11312
|
+
now,
|
|
11313
|
+
now
|
|
11314
|
+
);
|
|
11315
|
+
return this.requireEntry(id);
|
|
11316
|
+
});
|
|
11317
|
+
}
|
|
11318
|
+
update(id, input) {
|
|
11319
|
+
return this.runWrite("update", () => {
|
|
11320
|
+
const content = normalizeContent(input.content);
|
|
11321
|
+
const sourceType = normalizeSourceType(input.sourceType);
|
|
11322
|
+
const result = this.getDb().prepare(
|
|
11323
|
+
`UPDATE ai_memory
|
|
11324
|
+
SET content = ?,
|
|
11325
|
+
source_type = ?,
|
|
11326
|
+
source_ai_chat_id = ?,
|
|
11327
|
+
source_message_id = ?,
|
|
11328
|
+
updated_at = ?
|
|
11329
|
+
WHERE id = ?`
|
|
11330
|
+
).run(
|
|
11331
|
+
content,
|
|
11332
|
+
sourceType,
|
|
11333
|
+
normalizeOptionalId(input.sourceAIChatId),
|
|
11334
|
+
normalizeOptionalId(input.sourceMessageId),
|
|
11335
|
+
this.now(),
|
|
11336
|
+
id
|
|
11337
|
+
);
|
|
11338
|
+
if (result.changes === 0) throw new Error(`AI memory not found: ${id}`);
|
|
11339
|
+
return this.requireEntry(id);
|
|
11340
|
+
});
|
|
11341
|
+
}
|
|
11342
|
+
linkSourceMessages(links) {
|
|
11343
|
+
if (links.length === 0) return [];
|
|
11344
|
+
return this.runWrite("link sources", () => {
|
|
11345
|
+
const db = this.getDb();
|
|
11346
|
+
const update = db.prepare(
|
|
11347
|
+
`UPDATE ai_memory
|
|
11348
|
+
SET source_message_id = ?
|
|
11349
|
+
WHERE id = ?
|
|
11350
|
+
AND source_ai_chat_id = ?
|
|
11351
|
+
AND (source_message_id IS NULL OR source_message_id = ?)`
|
|
11352
|
+
);
|
|
11353
|
+
return db.transaction(() => {
|
|
11354
|
+
const linkedIds = [];
|
|
11355
|
+
for (const link of links) {
|
|
11356
|
+
const result = update.run(link.sourceMessageId, link.id, link.sourceAIChatId, link.sourceMessageId);
|
|
11357
|
+
if (result.changes === 0) throw new Error("AI memory source changed before it could be linked");
|
|
11358
|
+
linkedIds.push(link.id);
|
|
11359
|
+
}
|
|
11360
|
+
return linkedIds;
|
|
11361
|
+
})();
|
|
11362
|
+
});
|
|
11363
|
+
}
|
|
11364
|
+
forget(id) {
|
|
11365
|
+
return this.runWrite("forget", () => this.getDb().prepare("DELETE FROM ai_memory WHERE id = ?").run(id).changes > 0);
|
|
11366
|
+
}
|
|
11367
|
+
clear(scope) {
|
|
11368
|
+
return this.runWrite("clear", () => {
|
|
11369
|
+
const db = this.getDb();
|
|
11370
|
+
if (!scope) return db.prepare("DELETE FROM ai_memory").run().changes;
|
|
11371
|
+
const normalized = normalizeScope(scope);
|
|
11372
|
+
if (normalized.scopeType === "global" || normalized.scopeType === "self") {
|
|
11373
|
+
return db.prepare("DELETE FROM ai_memory WHERE scope_type = ? AND scope_id IS NULL").run(normalized.scopeType).changes;
|
|
11374
|
+
}
|
|
11375
|
+
return db.prepare("DELETE FROM ai_memory WHERE scope_type = ? AND scope_id = ?").run(normalized.scopeType, normalized.scopeId).changes;
|
|
11376
|
+
});
|
|
11377
|
+
}
|
|
11378
|
+
getSchemaVersion() {
|
|
11379
|
+
return this.getDb().pragma("user_version", { simple: true });
|
|
11380
|
+
}
|
|
11381
|
+
close() {
|
|
11382
|
+
if (!this.db) return;
|
|
11383
|
+
this.db.close();
|
|
11384
|
+
this.db = null;
|
|
11385
|
+
appLogger.debug("ai-memory", "AI memory database closed");
|
|
11386
|
+
}
|
|
11387
|
+
getDb() {
|
|
11388
|
+
if (this.db) return this.db;
|
|
11389
|
+
fs10.mkdirSync(path17.dirname(this.dbPath), { recursive: true });
|
|
11390
|
+
const db = this.nativeBinding ? new Database3(this.dbPath, { nativeBinding: this.nativeBinding }) : new Database3(this.dbPath);
|
|
11391
|
+
try {
|
|
11392
|
+
db.pragma("journal_mode = WAL");
|
|
11393
|
+
db.pragma("busy_timeout = 5000");
|
|
11394
|
+
migrateMemorySchema(db);
|
|
11395
|
+
} catch (error) {
|
|
11396
|
+
db.close();
|
|
11397
|
+
throw error;
|
|
11398
|
+
}
|
|
11399
|
+
this.db = db;
|
|
11400
|
+
appLogger.info("ai-memory", "AI memory database initialized", { schemaVersion: AI_MEMORY_SCHEMA_VERSION });
|
|
11401
|
+
return db;
|
|
11402
|
+
}
|
|
11403
|
+
requireEntry(id) {
|
|
11404
|
+
const entry = this.get(id);
|
|
11405
|
+
if (!entry) throw new Error(`AI memory not found after write: ${id}`);
|
|
11406
|
+
return entry;
|
|
11407
|
+
}
|
|
11408
|
+
runWrite(operation, write) {
|
|
11409
|
+
try {
|
|
11410
|
+
return write();
|
|
11411
|
+
} catch (error) {
|
|
11412
|
+
appLogger.error("ai-memory", `AI memory ${operation} failed`, error);
|
|
11413
|
+
throw error;
|
|
11414
|
+
}
|
|
11415
|
+
}
|
|
11416
|
+
};
|
|
11417
|
+
function compareRecentMemoryEntries(left, right) {
|
|
11418
|
+
return right.updatedAt - left.updatedAt || left.id.localeCompare(right.id);
|
|
11419
|
+
}
|
|
11420
|
+
function normalizeMemoryLocale(locale) {
|
|
11421
|
+
if (locale?.startsWith("zh-TW")) return "zh-TW";
|
|
11422
|
+
if (locale?.startsWith("zh")) return "zh-CN";
|
|
11423
|
+
if (locale?.startsWith("ja")) return "ja-JP";
|
|
11424
|
+
return "en-US";
|
|
11425
|
+
}
|
|
11426
|
+
function normalizeSearchText(value) {
|
|
11427
|
+
return value.normalize("NFKC").toLocaleLowerCase().replace(/[\p{P}\p{S}\s]+/gu, "");
|
|
11428
|
+
}
|
|
11429
|
+
function tokenizeMemoryText(value, locale) {
|
|
11430
|
+
const words = locale.startsWith("zh") ? segmentChineseMemoryText(value, locale) : segment(value, locale, { posFilterMode: "all", enableStopwords: true });
|
|
11431
|
+
const tokens = new Set(words.map(normalizeSearchText).filter((token) => token.length >= 2));
|
|
11432
|
+
for (const token of extractNumericMemoryTokens(value)) tokens.add(token);
|
|
11433
|
+
for (const token of extractSymbolSuffixMemoryTokens(value)) tokens.add(token);
|
|
11434
|
+
if (locale.startsWith("zh")) {
|
|
11435
|
+
for (const token of extractCjkBigrams(value)) tokens.add(token);
|
|
11436
|
+
}
|
|
11437
|
+
return tokens;
|
|
11438
|
+
}
|
|
11439
|
+
function extractNumericMemoryTokens(value) {
|
|
11440
|
+
return value.normalize("NFKC").match(NUMERIC_MEMORY_TOKEN_REGEX) ?? [];
|
|
11441
|
+
}
|
|
11442
|
+
function extractSymbolSuffixMemoryTokens(value) {
|
|
11443
|
+
return value.normalize("NFKC").toLocaleLowerCase().match(SYMBOL_SUFFIX_MEMORY_TOKEN_REGEX) ?? [];
|
|
11444
|
+
}
|
|
11445
|
+
function segmentChineseMemoryText(value, locale) {
|
|
11446
|
+
try {
|
|
11447
|
+
const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
|
|
11448
|
+
return [...segmenter.segment(value)].filter((item) => item.isWordLike).map((item) => item.segment);
|
|
11449
|
+
} catch {
|
|
11450
|
+
return value.match(/[\p{Letter}\p{Number}]+/gu) ?? [];
|
|
11451
|
+
}
|
|
11452
|
+
}
|
|
11453
|
+
function extractCjkBigrams(value) {
|
|
11454
|
+
const runs = value.normalize("NFKC").match(/[\p{Script=Han}]+/gu) ?? [];
|
|
11455
|
+
return runs.flatMap(
|
|
11456
|
+
(run) => Array.from({ length: Math.max(0, run.length - 1) }, (_, index) => run.slice(index, index + 2))
|
|
11457
|
+
);
|
|
11458
|
+
}
|
|
11459
|
+
function scoreMemoryEntry(content, normalizedQuery, queryTokens, locale) {
|
|
11460
|
+
const normalizedContent = normalizeSearchText(content);
|
|
11461
|
+
let score = 0;
|
|
11462
|
+
const matchesFullQuery = normalizedQuery.length >= AI_MEMORY_SUBSTRING_MIN_CHARS && normalizedContent.includes(normalizedQuery);
|
|
11463
|
+
const isMeaningfulReverseMatch = normalizedContent.length >= AI_MEMORY_SUBSTRING_MIN_CHARS && normalizedQuery.includes(normalizedContent);
|
|
11464
|
+
if (matchesFullQuery || isMeaningfulReverseMatch) score += 3;
|
|
11465
|
+
if (queryTokens.size === 0) return score;
|
|
11466
|
+
const contentTokens = tokenizeMemoryText(content, locale);
|
|
11467
|
+
if (contentTokens.size === 0) return score;
|
|
11468
|
+
let overlap = 0;
|
|
11469
|
+
for (const token of queryTokens) {
|
|
11470
|
+
if (contentTokens.has(token)) overlap += 1;
|
|
11471
|
+
}
|
|
11472
|
+
if (overlap === 0) return score;
|
|
11473
|
+
return score + overlap / queryTokens.size * 2 + overlap / contentTokens.size;
|
|
11474
|
+
}
|
|
11475
|
+
function normalizeScope(scope) {
|
|
11476
|
+
const scopeType = scope.scopeType;
|
|
11477
|
+
if (scopeType !== "global" && scopeType !== "self" && scopeType !== "contact" && scopeType !== "group") {
|
|
11478
|
+
throw new Error(`Unsupported AI memory scope type: ${String(scopeType)}`);
|
|
11479
|
+
}
|
|
11480
|
+
const scopeId = normalizeOptionalId(scope.scopeId);
|
|
11481
|
+
if (scopeType === "global" || scopeType === "self") {
|
|
11482
|
+
if (scopeId !== null) throw new Error(`${scopeType} AI memory scopeId must be null`);
|
|
11483
|
+
return { scopeType, scopeId: null };
|
|
11484
|
+
}
|
|
11485
|
+
if (!scopeId) throw new Error(`${scopeType} AI memory scopeId is required`);
|
|
11486
|
+
return { scopeType, scopeId };
|
|
11487
|
+
}
|
|
11488
|
+
function normalizeSourceType(sourceType) {
|
|
11489
|
+
if (sourceType !== "user" && sourceType !== "ai") {
|
|
11490
|
+
throw new Error(`Unsupported AI memory source type: ${String(sourceType)}`);
|
|
11491
|
+
}
|
|
11492
|
+
return sourceType;
|
|
11493
|
+
}
|
|
11494
|
+
function normalizeContent(content) {
|
|
11495
|
+
const normalized = typeof content === "string" ? content.trim() : "";
|
|
11496
|
+
if (!normalized || normalized.length > AI_MEMORY_CONTENT_MAX_CHARS) {
|
|
11497
|
+
throw new Error(`AI memory content must be between 1 and ${AI_MEMORY_CONTENT_MAX_CHARS} characters`);
|
|
11498
|
+
}
|
|
11499
|
+
return normalized;
|
|
11500
|
+
}
|
|
11501
|
+
function normalizeOptionalId(value) {
|
|
11502
|
+
if (typeof value !== "string") return null;
|
|
11503
|
+
const normalized = value.trim();
|
|
11504
|
+
return normalized || null;
|
|
11505
|
+
}
|
|
11506
|
+
function migrateMemorySchema(db) {
|
|
11507
|
+
const version = db.pragma("user_version", { simple: true });
|
|
11508
|
+
if (version > AI_MEMORY_SCHEMA_VERSION) {
|
|
11509
|
+
throw new Error(`Unsupported AI memory schema version: ${version}`);
|
|
11510
|
+
}
|
|
11511
|
+
if (version === AI_MEMORY_SCHEMA_VERSION) return;
|
|
11512
|
+
if (version !== 0) {
|
|
11513
|
+
throw new Error(`Unsupported AI memory schema version: ${version}`);
|
|
11514
|
+
}
|
|
11515
|
+
const migrate = db.transaction(() => {
|
|
11516
|
+
db.exec(`
|
|
11517
|
+
CREATE TABLE ai_memory (
|
|
11518
|
+
id TEXT PRIMARY KEY,
|
|
11519
|
+
scope_type TEXT NOT NULL CHECK (scope_type IN ('global', 'self', 'contact', 'group')),
|
|
11520
|
+
scope_id TEXT,
|
|
11521
|
+
content TEXT NOT NULL,
|
|
11522
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('user', 'ai')),
|
|
11523
|
+
source_ai_chat_id TEXT,
|
|
11524
|
+
source_message_id TEXT,
|
|
11525
|
+
created_at INTEGER NOT NULL,
|
|
11526
|
+
updated_at INTEGER NOT NULL,
|
|
11527
|
+
CHECK (
|
|
11528
|
+
(scope_type IN ('global', 'self') AND scope_id IS NULL) OR
|
|
11529
|
+
(scope_type IN ('contact', 'group') AND scope_id IS NOT NULL)
|
|
11530
|
+
)
|
|
11531
|
+
);
|
|
11532
|
+
`);
|
|
11533
|
+
db.exec(`
|
|
11534
|
+
CREATE INDEX idx_ai_memory_scope_updated
|
|
11535
|
+
ON ai_memory(scope_type, scope_id, updated_at DESC, id ASC);
|
|
11536
|
+
`);
|
|
11537
|
+
db.pragma(`user_version = ${AI_MEMORY_SCHEMA_VERSION}`);
|
|
11538
|
+
});
|
|
11539
|
+
migrate();
|
|
11540
|
+
appLogger.info("ai-memory", "AI memory database schema migrated", {
|
|
11541
|
+
fromVersion: version,
|
|
11542
|
+
toVersion: AI_MEMORY_SCHEMA_VERSION
|
|
11543
|
+
});
|
|
11544
|
+
}
|
|
11545
|
+
|
|
11546
|
+
// ../../packages/node-runtime/src/ai/memory-provenance.ts
|
|
11547
|
+
function listAIMemoriesWithSourceStatus(memoryService, aiChatManager, scope) {
|
|
11548
|
+
return memoryService.list(scope).map((entry) => ({
|
|
11549
|
+
...entry,
|
|
11550
|
+
sourceStatus: resolveAIMemorySourceStatus(entry, aiChatManager)
|
|
11551
|
+
}));
|
|
11552
|
+
}
|
|
11553
|
+
function linkAIMemorySources(memoryService, aiChatManager, input, options) {
|
|
11554
|
+
const aiChatId = requireId(input.aiChatId, "aiChatId");
|
|
11555
|
+
const conversation = aiChatManager.getAIChat(aiChatId);
|
|
11556
|
+
if (!conversation || conversation.kind !== "global") {
|
|
11557
|
+
throw new Error("Global AI conversation not found");
|
|
11558
|
+
}
|
|
11559
|
+
const userMessage = requireSourceMessage(aiChatManager, input.userMessageId, aiChatId, "user");
|
|
11560
|
+
const assistantMessage = requireSourceMessage(aiChatManager, input.assistantMessageId, aiChatId, "assistant");
|
|
11561
|
+
if (assistantMessage.parentId !== userMessage.id) {
|
|
11562
|
+
throw new Error("Assistant source message must directly reply to the user source message");
|
|
11563
|
+
}
|
|
11564
|
+
if (options && options.expectedParentMessageId !== void 0 && userMessage.parentId !== options.expectedParentMessageId) {
|
|
11565
|
+
throw new Error("User source message does not belong to the requested agent turn");
|
|
11566
|
+
}
|
|
11567
|
+
const memoryIds = normalizeMemoryIds(input.memoryIds);
|
|
11568
|
+
const memories = memoryIds.map((id) => ({ id, entry: memoryService.get(id) }));
|
|
11569
|
+
for (const { entry } of memories) {
|
|
11570
|
+
if (entry && entry.sourceAIChatId !== aiChatId) {
|
|
11571
|
+
throw new Error("AI memory does not belong to the requested conversation");
|
|
11572
|
+
}
|
|
11573
|
+
if (entry?.sourceMessageId) {
|
|
11574
|
+
const expectedSourceMessageId = entry.sourceType === "user" ? userMessage.id : assistantMessage.id;
|
|
11575
|
+
if (entry.sourceMessageId !== expectedSourceMessageId) {
|
|
11576
|
+
throw new Error("AI memory is already linked to another source message");
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11580
|
+
const skippedMemoryIds = [];
|
|
11581
|
+
const links = [];
|
|
11582
|
+
for (const { id, entry } of memories) {
|
|
11583
|
+
if (!entry) {
|
|
11584
|
+
skippedMemoryIds.push(id);
|
|
11585
|
+
continue;
|
|
11586
|
+
}
|
|
11587
|
+
const sourceMessage = entry.sourceType === "user" ? userMessage : assistantMessage;
|
|
11588
|
+
links.push({ id, sourceAIChatId: aiChatId, sourceMessageId: sourceMessage.id });
|
|
11589
|
+
}
|
|
11590
|
+
const linkedMemoryIds = memoryService.linkSourceMessages(links);
|
|
11591
|
+
return { linkedMemoryIds, skippedMemoryIds };
|
|
11592
|
+
}
|
|
11593
|
+
function resolveAIMemorySourceStatus(entry, aiChatManager) {
|
|
11594
|
+
if (!entry.sourceAIChatId) return "none";
|
|
11595
|
+
const conversation = aiChatManager.getAIChat(entry.sourceAIChatId);
|
|
11596
|
+
if (!conversation || conversation.kind !== "global") return "unavailable";
|
|
11597
|
+
if (!entry.sourceMessageId) return "conversation";
|
|
11598
|
+
const message = aiChatManager.getMessage(entry.sourceMessageId);
|
|
11599
|
+
const expectedRole = entry.sourceType === "user" ? "user" : "assistant";
|
|
11600
|
+
return message?.aiChatId === entry.sourceAIChatId && message.role === expectedRole ? "message" : "unavailable";
|
|
11601
|
+
}
|
|
11602
|
+
function requireSourceMessage(aiChatManager, messageId, aiChatId, role) {
|
|
11603
|
+
const normalizedMessageId = requireId(messageId, `${role}MessageId`);
|
|
11604
|
+
const message = aiChatManager.getMessage(normalizedMessageId);
|
|
11605
|
+
if (!message || message.aiChatId !== aiChatId || message.role !== role) {
|
|
11606
|
+
throw new Error(`${role} source message does not belong to the requested conversation`);
|
|
11607
|
+
}
|
|
11608
|
+
return message;
|
|
11609
|
+
}
|
|
11610
|
+
function normalizeMemoryIds(memoryIds) {
|
|
11611
|
+
if (!Array.isArray(memoryIds)) throw new Error("memoryIds must be an array");
|
|
11612
|
+
return [...new Set(memoryIds.map((id) => requireId(id, "memoryId")))];
|
|
11613
|
+
}
|
|
11614
|
+
function requireId(value, field) {
|
|
11615
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
11616
|
+
if (!normalized) throw new Error(`${field} is required`);
|
|
11617
|
+
return normalized;
|
|
11618
|
+
}
|
|
11619
|
+
|
|
10049
11620
|
// ../../packages/node-runtime/src/ai/tokenizer.ts
|
|
10050
11621
|
import { Tiktoken } from "js-tiktoken/lite";
|
|
10051
11622
|
var encoder = null;
|
|
@@ -10183,21 +11754,21 @@ var SkillManagerCore = class {
|
|
|
10183
11754
|
}
|
|
10184
11755
|
// ==================== Init ====================
|
|
10185
11756
|
init() {
|
|
10186
|
-
const { fs:
|
|
10187
|
-
|
|
11757
|
+
const { fs: fs20, skillsDir } = this.deps;
|
|
11758
|
+
fs20.ensureDir(skillsDir);
|
|
10188
11759
|
this.loadAll();
|
|
10189
11760
|
this.initialized = true;
|
|
10190
11761
|
this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
|
|
10191
11762
|
return { total: this.cache.size };
|
|
10192
11763
|
}
|
|
10193
11764
|
loadAll() {
|
|
10194
|
-
const { fs:
|
|
11765
|
+
const { fs: fs20, skillsDir } = this.deps;
|
|
10195
11766
|
this.cache.clear();
|
|
10196
|
-
const files =
|
|
11767
|
+
const files = fs20.listFiles(skillsDir, ".md");
|
|
10197
11768
|
for (const file of files) {
|
|
10198
11769
|
try {
|
|
10199
|
-
const filePath =
|
|
10200
|
-
const content =
|
|
11770
|
+
const filePath = fs20.joinPath(skillsDir, file);
|
|
11771
|
+
const content = fs20.readFile(filePath);
|
|
10201
11772
|
const def = parseSkillFile(content, filePath);
|
|
10202
11773
|
if (def) {
|
|
10203
11774
|
this.cache.set(def.id, def);
|
|
@@ -10349,8 +11920,8 @@ var SkillManagerCore = class {
|
|
|
10349
11920
|
};
|
|
10350
11921
|
|
|
10351
11922
|
// ../../packages/node-runtime/src/ai/skill-manager.ts
|
|
10352
|
-
import * as
|
|
10353
|
-
import * as
|
|
11923
|
+
import * as fs11 from "fs";
|
|
11924
|
+
import * as path18 from "path";
|
|
10354
11925
|
var SKILLS_DIR_NAME = "skills";
|
|
10355
11926
|
var defaultLogger2 = {
|
|
10356
11927
|
info: () => {
|
|
@@ -10360,26 +11931,26 @@ var defaultLogger2 = {
|
|
|
10360
11931
|
};
|
|
10361
11932
|
var nodeFs = {
|
|
10362
11933
|
ensureDir(dir) {
|
|
10363
|
-
|
|
11934
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
10364
11935
|
},
|
|
10365
11936
|
listFiles(dir, ext) {
|
|
10366
|
-
if (!
|
|
10367
|
-
return
|
|
11937
|
+
if (!fs11.existsSync(dir)) return [];
|
|
11938
|
+
return fs11.readdirSync(dir).filter((file) => file.endsWith(ext));
|
|
10368
11939
|
},
|
|
10369
11940
|
readFile(filePath) {
|
|
10370
|
-
return
|
|
11941
|
+
return fs11.readFileSync(filePath, "utf-8");
|
|
10371
11942
|
},
|
|
10372
11943
|
writeFile(filePath, content) {
|
|
10373
|
-
|
|
11944
|
+
fs11.writeFileSync(filePath, content, "utf-8");
|
|
10374
11945
|
},
|
|
10375
11946
|
deleteFile(filePath) {
|
|
10376
|
-
|
|
11947
|
+
fs11.unlinkSync(filePath);
|
|
10377
11948
|
},
|
|
10378
11949
|
fileExists(filePath) {
|
|
10379
|
-
return
|
|
11950
|
+
return fs11.existsSync(filePath);
|
|
10380
11951
|
},
|
|
10381
11952
|
joinPath(...parts) {
|
|
10382
|
-
return
|
|
11953
|
+
return path18.join(...parts);
|
|
10383
11954
|
}
|
|
10384
11955
|
};
|
|
10385
11956
|
var SkillManager = class {
|
|
@@ -10389,7 +11960,7 @@ var SkillManager = class {
|
|
|
10389
11960
|
this.logger = logger ?? defaultLogger2;
|
|
10390
11961
|
this.core = new SkillManagerCore({
|
|
10391
11962
|
fs: nodeFs,
|
|
10392
|
-
skillsDir:
|
|
11963
|
+
skillsDir: path18.join(aiDataDir, SKILLS_DIR_NAME),
|
|
10393
11964
|
builtinRawSkills: [],
|
|
10394
11965
|
logger: {
|
|
10395
11966
|
info: () => {
|
|
@@ -10694,6 +12265,12 @@ var DEFAULT_CONTEXT_COMPRESSION_CONFIG = Object.freeze({
|
|
|
10694
12265
|
});
|
|
10695
12266
|
|
|
10696
12267
|
// ../../packages/node-runtime/src/ai/agent/history.ts
|
|
12268
|
+
function appendEntityRefsForModel(text, entityRefs) {
|
|
12269
|
+
if (!entityRefs?.length) return text;
|
|
12270
|
+
return `${text}
|
|
12271
|
+
|
|
12272
|
+
<chatlab_entity_refs>${JSON.stringify(entityRefs)}</chatlab_entity_refs>`;
|
|
12273
|
+
}
|
|
10697
12274
|
function createEmptyPiUsage() {
|
|
10698
12275
|
return {
|
|
10699
12276
|
input: 0,
|
|
@@ -10767,7 +12344,7 @@ function toPiHistoryMessages(messages, options) {
|
|
|
10767
12344
|
if (msg.role === "user") {
|
|
10768
12345
|
out.push({
|
|
10769
12346
|
role: "user",
|
|
10770
|
-
content: [{ type: "text", text: msg.content || "" }],
|
|
12347
|
+
content: [{ type: "text", text: appendEntityRefsForModel(msg.content || "", msg.entityRefs) }],
|
|
10771
12348
|
timestamp: Date.now()
|
|
10772
12349
|
});
|
|
10773
12350
|
continue;
|
|
@@ -10776,7 +12353,18 @@ function toPiHistoryMessages(messages, options) {
|
|
|
10776
12353
|
if (replayable) {
|
|
10777
12354
|
replayAssistantBlocks(msg.contentBlocks, out, options);
|
|
10778
12355
|
} else {
|
|
10779
|
-
out.push(
|
|
12356
|
+
out.push(
|
|
12357
|
+
makeAssistantMessage(
|
|
12358
|
+
[
|
|
12359
|
+
{
|
|
12360
|
+
type: "text",
|
|
12361
|
+
text: appendEntityRefsForModel(msg.content || "", msg.role === "summary" ? msg.entityRefs : void 0)
|
|
12362
|
+
}
|
|
12363
|
+
],
|
|
12364
|
+
"stop",
|
|
12365
|
+
options?.modelInfo
|
|
12366
|
+
)
|
|
12367
|
+
);
|
|
10780
12368
|
}
|
|
10781
12369
|
}
|
|
10782
12370
|
return out;
|
|
@@ -10825,8 +12413,9 @@ var defaultLogger3 = {
|
|
|
10825
12413
|
error: () => {
|
|
10826
12414
|
}
|
|
10827
12415
|
};
|
|
10828
|
-
async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger = defaultLogger3) {
|
|
12416
|
+
async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger = defaultLogger3, options = {}) {
|
|
10829
12417
|
try {
|
|
12418
|
+
throwIfAborted(options.signal);
|
|
10830
12419
|
const contextWindow = llmAdapter.contextWindow || DEFAULT_CONTEXT_WINDOW;
|
|
10831
12420
|
const thresholdTokens = Math.floor(contextWindow * (config.tokenThresholdPercent / 100) * 0.95);
|
|
10832
12421
|
const summary = convManager.getLatestSummary(aiChatId);
|
|
@@ -10842,10 +12431,16 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10842
12431
|
}
|
|
10843
12432
|
const historyForTokenCount = [];
|
|
10844
12433
|
if (summary) {
|
|
10845
|
-
historyForTokenCount.push({
|
|
12434
|
+
historyForTokenCount.push({
|
|
12435
|
+
role: "assistant",
|
|
12436
|
+
content: appendEntityRefsForModel(summary.content, summary.entityRefs)
|
|
12437
|
+
});
|
|
10846
12438
|
}
|
|
10847
12439
|
for (const msg of messages) {
|
|
10848
|
-
historyForTokenCount.push({
|
|
12440
|
+
historyForTokenCount.push({
|
|
12441
|
+
role: msg.role,
|
|
12442
|
+
content: appendEntityRefsForModel(msg.content, msg.entityRefs)
|
|
12443
|
+
});
|
|
10849
12444
|
for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
|
|
10850
12445
|
historyForTokenCount.push({ role: "tool", content: toolText });
|
|
10851
12446
|
}
|
|
@@ -10870,19 +12465,32 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10870
12465
|
const targetTokens = Math.min(Math.floor(contextWindow * 0.1), 16384);
|
|
10871
12466
|
const template = isProgressive ? PROGRESSIVE_COMPRESSION_PROMPT : INITIAL_COMPRESSION_PROMPT;
|
|
10872
12467
|
const prompt = template.replace("{maxTokens}", String(targetTokens)).replace("{messages}", compressInput);
|
|
10873
|
-
let summaryText = await llmAdapter.compress(prompt, targetTokens);
|
|
12468
|
+
let summaryText = await llmAdapter.compress(prompt, targetTokens, options.signal);
|
|
12469
|
+
throwIfAborted(options.signal);
|
|
10874
12470
|
if (!summaryText) {
|
|
10875
12471
|
logger.warn("Compression", "LLM compression failed, falling back to truncation");
|
|
10876
12472
|
summaryText = forceTruncate(compressInput, targetTokens);
|
|
10877
12473
|
}
|
|
10878
12474
|
const bufferBoundary = bufferMessages.length > 0 ? bufferMessages[0].timestamp : messagesToCompress[messagesToCompress.length - 1].timestamp + 1;
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
12475
|
+
const summaryEntityRefs = mergeEntityRefs(
|
|
12476
|
+
summary?.entityRefs,
|
|
12477
|
+
...messagesToCompress.map((message) => message.entityRefs)
|
|
12478
|
+
);
|
|
12479
|
+
throwIfAborted(options.signal);
|
|
12480
|
+
convManager.addSummaryMessage(
|
|
12481
|
+
aiChatId,
|
|
12482
|
+
summaryText,
|
|
12483
|
+
{
|
|
12484
|
+
bufferBoundaryTimestamp: bufferBoundary,
|
|
12485
|
+
compressedMessageCount: messagesToCompress.length
|
|
12486
|
+
},
|
|
12487
|
+
summaryEntityRefs
|
|
12488
|
+
);
|
|
12489
|
+
const afterTokenCount = [
|
|
12490
|
+
{ role: "assistant", content: appendEntityRefsForModel(summaryText, summaryEntityRefs) }
|
|
12491
|
+
];
|
|
10884
12492
|
for (const m of bufferMessages) {
|
|
10885
|
-
afterTokenCount.push({ role: m.role, content: m.content });
|
|
12493
|
+
afterTokenCount.push({ role: m.role, content: appendEntityRefsForModel(m.content, m.entityRefs) });
|
|
10886
12494
|
for (const toolText of replayedToolResultTexts(m.contentBlocks)) {
|
|
10887
12495
|
afterTokenCount.push({ role: "tool", content: toolText });
|
|
10888
12496
|
}
|
|
@@ -10910,10 +12518,19 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10910
12518
|
summaryContent: summaryText
|
|
10911
12519
|
};
|
|
10912
12520
|
} catch (error) {
|
|
12521
|
+
if (options.signal?.aborted) {
|
|
12522
|
+
return { compressed: false, reason: "error", error: "Compression aborted" };
|
|
12523
|
+
}
|
|
10913
12524
|
logger.error("Compression", "Compression failed", { error: String(error) });
|
|
10914
12525
|
return { compressed: false, reason: "error", error: String(error) };
|
|
10915
12526
|
}
|
|
10916
12527
|
}
|
|
12528
|
+
function throwIfAborted(signal) {
|
|
12529
|
+
if (!signal?.aborted) return;
|
|
12530
|
+
const error = new Error("Compression aborted");
|
|
12531
|
+
error.name = "AbortError";
|
|
12532
|
+
throw error;
|
|
12533
|
+
}
|
|
10917
12534
|
async function manualCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger) {
|
|
10918
12535
|
const messageCount = convManager.getMessageCountAfterSummary(aiChatId);
|
|
10919
12536
|
if (messageCount < 5) {
|
|
@@ -10927,7 +12544,7 @@ function replayedToolResultTexts(blocks) {
|
|
|
10927
12544
|
return blocks.filter(isReplayableToolBlock).map((block) => truncateToolResultText(block.tool.result));
|
|
10928
12545
|
}
|
|
10929
12546
|
function countMessageTokensWithTools(msg) {
|
|
10930
|
-
let tokens = countTokens(msg.content) + 4;
|
|
12547
|
+
let tokens = countTokens(appendEntityRefsForModel(msg.content, msg.entityRefs)) + 4;
|
|
10931
12548
|
for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
|
|
10932
12549
|
tokens += countTokens(toolText) + 4;
|
|
10933
12550
|
}
|
|
@@ -10947,6 +12564,10 @@ function splitMessagesForCompression(messages, bufferTokenBudget) {
|
|
|
10947
12564
|
splitIndex = 0;
|
|
10948
12565
|
}
|
|
10949
12566
|
}
|
|
12567
|
+
if (splitIndex > 0) {
|
|
12568
|
+
const boundaryTimestamp = messages[Math.min(splitIndex, messages.length - 1)].timestamp;
|
|
12569
|
+
while (splitIndex > 0 && messages[splitIndex - 1].timestamp === boundaryTimestamp) splitIndex--;
|
|
12570
|
+
}
|
|
10950
12571
|
return {
|
|
10951
12572
|
bufferMessages: messages.slice(splitIndex),
|
|
10952
12573
|
messagesToCompress: messages.slice(0, splitIndex)
|
|
@@ -10955,14 +12576,16 @@ function splitMessagesForCompression(messages, bufferTokenBudget) {
|
|
|
10955
12576
|
function buildCompressionInput(messagesToCompress, existingSummary) {
|
|
10956
12577
|
const parts = [];
|
|
10957
12578
|
if (existingSummary) {
|
|
10958
|
-
parts.push(
|
|
10959
|
-
|
|
10960
|
-
|
|
12579
|
+
parts.push(
|
|
12580
|
+
`[PREVIOUS SUMMARY \u2014 MUST PRESERVE]
|
|
12581
|
+
${appendEntityRefsForModel(existingSummary.content, existingSummary.entityRefs)}
|
|
12582
|
+
`
|
|
12583
|
+
);
|
|
10961
12584
|
parts.push(`[NEW MESSAGES \u2014 SUMMARIZE AND MERGE]`);
|
|
10962
12585
|
}
|
|
10963
12586
|
for (const msg of messagesToCompress) {
|
|
10964
12587
|
const roleLabel = msg.role === "user" ? "User" : "Assistant";
|
|
10965
|
-
parts.push(`${roleLabel}: ${msg.content}`);
|
|
12588
|
+
parts.push(`${roleLabel}: ${appendEntityRefsForModel(msg.content, msg.entityRefs)}`);
|
|
10966
12589
|
for (const block of (msg.contentBlocks ?? []).filter(isReplayableToolBlock)) {
|
|
10967
12590
|
parts.push(`[Tool result: ${block.tool.name}]
|
|
10968
12591
|
${truncateToolResultText(block.tool.result)}`);
|
|
@@ -10970,6 +12593,16 @@ ${truncateToolResultText(block.tool.result)}`);
|
|
|
10970
12593
|
}
|
|
10971
12594
|
return parts.join("\n\n");
|
|
10972
12595
|
}
|
|
12596
|
+
function mergeEntityRefs(...groups) {
|
|
12597
|
+
const refs = /* @__PURE__ */ new Map();
|
|
12598
|
+
for (const group of groups) {
|
|
12599
|
+
for (const ref of group ?? []) {
|
|
12600
|
+
const key = ref.type === "contact" ? `contact:${ref.contactKey}` : `session:${ref.sessionId}`;
|
|
12601
|
+
refs.set(key, ref);
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
return refs.size > 0 ? [...refs.values()] : void 0;
|
|
12605
|
+
}
|
|
10973
12606
|
function forceTruncate(input, targetTokens) {
|
|
10974
12607
|
const lines = input.split("\n");
|
|
10975
12608
|
const result = [];
|
|
@@ -11016,7 +12649,7 @@ function createCompressionLlmAdapter(options) {
|
|
|
11016
12649
|
const contextWindow = options.contextWindow ?? piModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW2;
|
|
11017
12650
|
return {
|
|
11018
12651
|
contextWindow,
|
|
11019
|
-
compress: async (prompt, maxTokens) => {
|
|
12652
|
+
compress: async (prompt, maxTokens, signal) => {
|
|
11020
12653
|
onCompressing?.();
|
|
11021
12654
|
try {
|
|
11022
12655
|
const result = await completeSimple(
|
|
@@ -11025,11 +12658,12 @@ function createCompressionLlmAdapter(options) {
|
|
|
11025
12658
|
systemPrompt: void 0,
|
|
11026
12659
|
messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }]
|
|
11027
12660
|
},
|
|
11028
|
-
{ apiKey, maxTokens }
|
|
12661
|
+
{ apiKey, maxTokens, signal }
|
|
11029
12662
|
);
|
|
11030
12663
|
const text = result.content.filter((item) => item.type === "text").map((item) => item.text).join("");
|
|
11031
12664
|
return text || null;
|
|
11032
12665
|
} catch (error) {
|
|
12666
|
+
if (signal?.aborted) return null;
|
|
11033
12667
|
onError?.(error);
|
|
11034
12668
|
return null;
|
|
11035
12669
|
}
|
|
@@ -11822,7 +13456,8 @@ async function runAgentCore(options) {
|
|
|
11822
13456
|
return clampThinkingLevel(piModel, level);
|
|
11823
13457
|
})();
|
|
11824
13458
|
const finalThinkingLevel = resolvedThinkingLevel ?? (piModel.reasoning ? void 0 : "off");
|
|
11825
|
-
let hasReachedToolRoundLimit =
|
|
13459
|
+
let hasReachedToolRoundLimit = maxToolRounds <= 0;
|
|
13460
|
+
let hasCompletedFinalAnswerTurn = false;
|
|
11826
13461
|
const thinkingFormat = piModel.compat?.thinkingFormat;
|
|
11827
13462
|
const replayOptions = piModel.reasoning && thinkingFormat === "deepseek" ? {
|
|
11828
13463
|
modelInfo: { api: piModel.api, provider: piModel.provider, id: piModel.id },
|
|
@@ -11845,10 +13480,11 @@ async function runAgentCore(options) {
|
|
|
11845
13480
|
onConvertToLlm?.(filtered);
|
|
11846
13481
|
return filtered;
|
|
11847
13482
|
},
|
|
11848
|
-
|
|
13483
|
+
afterToolCall: async ({ result }) => result.isError === true ? { isError: true } : void 0,
|
|
13484
|
+
shouldStopAfterTurn: async () => hasCompletedFinalAnswerTurn,
|
|
13485
|
+
prepareNextTurnWithContext: ({ context }) => hasReachedToolRoundLimit ? {
|
|
11849
13486
|
context: {
|
|
11850
|
-
|
|
11851
|
-
messages: coreAgent.state.messages,
|
|
13487
|
+
...context,
|
|
11852
13488
|
tools: []
|
|
11853
13489
|
}
|
|
11854
13490
|
} : void 0,
|
|
@@ -11926,9 +13562,10 @@ async function runAgentCore(options) {
|
|
|
11926
13562
|
});
|
|
11927
13563
|
} else if (event.type === "turn_end") {
|
|
11928
13564
|
const hadToolCalls = event.toolResults.length > 0;
|
|
11929
|
-
|
|
13565
|
+
const isFinalAnswerTurn = hasReachedToolRoundLimit;
|
|
13566
|
+
if (hadToolCalls && !hasReachedToolRoundLimit) {
|
|
11930
13567
|
toolRounds += 1;
|
|
11931
|
-
if (
|
|
13568
|
+
if (maxToolRounds > 0 && toolRounds >= maxToolRounds) {
|
|
11932
13569
|
hasReachedToolRoundLimit = true;
|
|
11933
13570
|
coreAgent.state.tools = [];
|
|
11934
13571
|
coreAgent.steer({
|
|
@@ -11938,6 +13575,7 @@ async function runAgentCore(options) {
|
|
|
11938
13575
|
});
|
|
11939
13576
|
}
|
|
11940
13577
|
}
|
|
13578
|
+
if (isFinalAnswerTurn) hasCompletedFinalAnswerTurn = true;
|
|
11941
13579
|
onEvent({ type: "turn_end", round: toolRounds, hadToolCalls });
|
|
11942
13580
|
} else if (event.type === "message_end") {
|
|
11943
13581
|
if (event.message.role === "assistant") {
|
|
@@ -12579,288 +14217,6 @@ function buildSemanticSearchGuidance(locale) {
|
|
|
12579
14217
|
].join("");
|
|
12580
14218
|
}
|
|
12581
14219
|
|
|
12582
|
-
// ../../packages/node-runtime/src/ai/agent/event-handler.ts
|
|
12583
|
-
function estimateTokensFromText(text) {
|
|
12584
|
-
if (!text) return 0;
|
|
12585
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
12586
|
-
if (!normalized) return 0;
|
|
12587
|
-
const cjkCount = (normalized.match(/[\u3400-\u9fff\uf900-\ufaff]/g) || []).length;
|
|
12588
|
-
const latinCount = normalized.length - cjkCount;
|
|
12589
|
-
return Math.max(1, Math.ceil(cjkCount * 1.15 + latinCount / 4));
|
|
12590
|
-
}
|
|
12591
|
-
function extractMessageText(message) {
|
|
12592
|
-
if (message.role === "user") {
|
|
12593
|
-
if (typeof message.content === "string") return message.content;
|
|
12594
|
-
return message.content.map((item) => {
|
|
12595
|
-
if (item.type === "text") return item.text;
|
|
12596
|
-
if (item.type === "image") return "[image]";
|
|
12597
|
-
return "";
|
|
12598
|
-
}).join("\n");
|
|
12599
|
-
}
|
|
12600
|
-
if (message.role === "assistant") {
|
|
12601
|
-
return message.content.map((item) => {
|
|
12602
|
-
if (item.type === "text") return item.text;
|
|
12603
|
-
if (item.type === "thinking") return item.thinking;
|
|
12604
|
-
if (item.type === "toolCall") return `${item.name} ${JSON.stringify(item.arguments || {})}`;
|
|
12605
|
-
return "";
|
|
12606
|
-
}).join("\n");
|
|
12607
|
-
}
|
|
12608
|
-
if (message.role === "toolResult") {
|
|
12609
|
-
return message.content.map((item) => {
|
|
12610
|
-
if (item.type === "text") return item.text;
|
|
12611
|
-
return "[binary]";
|
|
12612
|
-
}).join("\n");
|
|
12613
|
-
}
|
|
12614
|
-
return "";
|
|
12615
|
-
}
|
|
12616
|
-
function estimateContextTokens(systemPrompt, messages, pendingUserMessage) {
|
|
12617
|
-
let tokens = estimateTokensFromText(systemPrompt);
|
|
12618
|
-
for (const message of messages) {
|
|
12619
|
-
if (message.role === "toolResult") continue;
|
|
12620
|
-
tokens += estimateTokensFromText(extractMessageText(message));
|
|
12621
|
-
}
|
|
12622
|
-
if (pendingUserMessage) {
|
|
12623
|
-
tokens += estimateTokensFromText(pendingUserMessage);
|
|
12624
|
-
}
|
|
12625
|
-
return tokens;
|
|
12626
|
-
}
|
|
12627
|
-
var AgentEventHandler = class {
|
|
12628
|
-
toolsUsed = [];
|
|
12629
|
-
toolRounds = 0;
|
|
12630
|
-
totalUsage = {
|
|
12631
|
-
promptTokens: 0,
|
|
12632
|
-
completionTokens: 0,
|
|
12633
|
-
totalTokens: 0,
|
|
12634
|
-
cacheReadTokens: 0,
|
|
12635
|
-
cacheWriteTokens: 0
|
|
12636
|
-
};
|
|
12637
|
-
lastStatusAt = 0;
|
|
12638
|
-
onChunk;
|
|
12639
|
-
context;
|
|
12640
|
-
systemPrompt;
|
|
12641
|
-
activeTools = /* @__PURE__ */ new Map();
|
|
12642
|
-
constructor(config) {
|
|
12643
|
-
this.onChunk = config.onChunk;
|
|
12644
|
-
this.context = config.context;
|
|
12645
|
-
this.systemPrompt = config.systemPrompt;
|
|
12646
|
-
}
|
|
12647
|
-
handleCoreEvent(event, messages) {
|
|
12648
|
-
switch (event.type) {
|
|
12649
|
-
case "content":
|
|
12650
|
-
this.onChunk({ type: "content", content: event.content });
|
|
12651
|
-
this.emitStatus("responding", messages);
|
|
12652
|
-
break;
|
|
12653
|
-
case "thinking_start":
|
|
12654
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12655
|
-
break;
|
|
12656
|
-
case "thinking_delta":
|
|
12657
|
-
this.onChunk({ type: "think", content: event.content, thinkTag: "thinking" });
|
|
12658
|
-
this.emitStatus("thinking", messages);
|
|
12659
|
-
break;
|
|
12660
|
-
case "thinking_end":
|
|
12661
|
-
this.onChunk({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: event.durationMs });
|
|
12662
|
-
this.emitStatus("responding", messages, { force: true });
|
|
12663
|
-
break;
|
|
12664
|
-
case "tool_start": {
|
|
12665
|
-
const params = this.normalizeToolParams(event.toolName, event.toolParams);
|
|
12666
|
-
this.toolsUsed.push(event.toolName);
|
|
12667
|
-
this.activeTools.set(event.toolCallId, event.toolName);
|
|
12668
|
-
this.onChunk({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params });
|
|
12669
|
-
this.emitStatus("tool_running", messages, { currentTool: event.toolName, force: true });
|
|
12670
|
-
break;
|
|
12671
|
-
}
|
|
12672
|
-
case "tool_update":
|
|
12673
|
-
this.onChunk({
|
|
12674
|
-
type: "tool_update",
|
|
12675
|
-
toolCallId: event.toolCallId,
|
|
12676
|
-
toolName: event.toolName,
|
|
12677
|
-
toolProgress: event.progress
|
|
12678
|
-
});
|
|
12679
|
-
break;
|
|
12680
|
-
case "tool_end":
|
|
12681
|
-
this.activeTools.delete(event.toolCallId);
|
|
12682
|
-
this.onChunk({
|
|
12683
|
-
type: "tool_result",
|
|
12684
|
-
toolCallId: event.toolCallId,
|
|
12685
|
-
toolName: event.toolName,
|
|
12686
|
-
toolResult: event.toolResult,
|
|
12687
|
-
toolIsError: event.isError
|
|
12688
|
-
});
|
|
12689
|
-
if (this.activeTools.size > 0) {
|
|
12690
|
-
const currentTool = Array.from(this.activeTools.values()).at(-1);
|
|
12691
|
-
this.emitStatus("tool_running", messages, { currentTool, force: true });
|
|
12692
|
-
} else {
|
|
12693
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12694
|
-
}
|
|
12695
|
-
break;
|
|
12696
|
-
case "turn_end":
|
|
12697
|
-
this.activeTools.clear();
|
|
12698
|
-
this.toolRounds = event.round;
|
|
12699
|
-
this.onChunk({ type: "turn_end", hadToolCalls: event.hadToolCalls });
|
|
12700
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12701
|
-
break;
|
|
12702
|
-
case "usage_update":
|
|
12703
|
-
this.totalUsage = { ...event.usage };
|
|
12704
|
-
this.emitStatus("responding", messages, { force: true });
|
|
12705
|
-
break;
|
|
12706
|
-
}
|
|
12707
|
-
}
|
|
12708
|
-
cloneUsage() {
|
|
12709
|
-
return {
|
|
12710
|
-
promptTokens: this.totalUsage.promptTokens,
|
|
12711
|
-
completionTokens: this.totalUsage.completionTokens,
|
|
12712
|
-
totalTokens: this.totalUsage.totalTokens,
|
|
12713
|
-
cacheReadTokens: this.totalUsage.cacheReadTokens,
|
|
12714
|
-
cacheWriteTokens: this.totalUsage.cacheWriteTokens
|
|
12715
|
-
};
|
|
12716
|
-
}
|
|
12717
|
-
emitStatus(phase, messages, options) {
|
|
12718
|
-
const now = Date.now();
|
|
12719
|
-
if (!options?.force && now - this.lastStatusAt < 240) return;
|
|
12720
|
-
this.lastStatusAt = now;
|
|
12721
|
-
const contextTokens = estimateContextTokens(this.systemPrompt, messages, options?.pendingUserMessage);
|
|
12722
|
-
const status = {
|
|
12723
|
-
phase,
|
|
12724
|
-
round: this.toolRounds,
|
|
12725
|
-
toolsUsed: this.toolsUsed.length,
|
|
12726
|
-
currentTool: options?.currentTool,
|
|
12727
|
-
contextTokens,
|
|
12728
|
-
totalUsage: this.cloneUsage(),
|
|
12729
|
-
updatedAt: now
|
|
12730
|
-
};
|
|
12731
|
-
this.onChunk({ type: "status", status });
|
|
12732
|
-
}
|
|
12733
|
-
normalizeToolParams(toolName, params) {
|
|
12734
|
-
const normalized = { ...params };
|
|
12735
|
-
const toolsWithLimit = ["search_messages", "get_recent_messages", "get_conversation_between"];
|
|
12736
|
-
if (this.context.maxMessagesLimit && toolsWithLimit.includes(toolName)) {
|
|
12737
|
-
normalized.limit = this.context.maxMessagesLimit;
|
|
12738
|
-
}
|
|
12739
|
-
if (this.context.timeFilter && (toolName === "search_messages" || toolName === "get_recent_messages")) {
|
|
12740
|
-
normalized._timeFilter = this.context.timeFilter;
|
|
12741
|
-
}
|
|
12742
|
-
return normalized;
|
|
12743
|
-
}
|
|
12744
|
-
};
|
|
12745
|
-
|
|
12746
|
-
// ../../packages/node-runtime/src/ai/agent/prompt-builder.ts
|
|
12747
|
-
function agentT(t2, key, locale, options) {
|
|
12748
|
-
return t2(key, { lng: locale, ...options });
|
|
12749
|
-
}
|
|
12750
|
-
function formatNullableTimestamp2(timestamp) {
|
|
12751
|
-
return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
|
|
12752
|
-
}
|
|
12753
|
-
function formatSharePercent2(share) {
|
|
12754
|
-
if (!Number.isFinite(share)) return "0%";
|
|
12755
|
-
const rounded = Math.round(share * 10) / 10;
|
|
12756
|
-
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
|
|
12757
|
-
}
|
|
12758
|
-
function normalizeInlineText(text) {
|
|
12759
|
-
return text.replace(/\s+/g, " ").trim();
|
|
12760
|
-
}
|
|
12761
|
-
function formatDataSnapshotContext(t2, dataSnapshot, locale) {
|
|
12762
|
-
const memberHints = dataSnapshot.activeMemberHints ?? [];
|
|
12763
|
-
const memberHintTitle = memberHints.length === 0 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsUnavailable", locale) : dataSnapshot.totalMembers <= 10 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsAll", locale) : agentT(t2, "ai.agent.dataSnapshotMemberHintsTop", locale);
|
|
12764
|
-
const memberHintLines = memberHints.length > 0 ? memberHints.map((member, index) => {
|
|
12765
|
-
return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent2(member.share)}`;
|
|
12766
|
-
}).join("\n") : agentT(t2, "ai.agent.dataSnapshotMemberHintsEmpty", locale);
|
|
12767
|
-
return agentT(t2, "ai.agent.dataSnapshotContext", locale, {
|
|
12768
|
-
name: dataSnapshot.name,
|
|
12769
|
-
platform: dataSnapshot.platform,
|
|
12770
|
-
type: dataSnapshot.type,
|
|
12771
|
-
totalMessages: dataSnapshot.totalMessages,
|
|
12772
|
-
totalMembers: dataSnapshot.totalMembers,
|
|
12773
|
-
firstMessageTs: formatNullableTimestamp2(dataSnapshot.firstMessageTs),
|
|
12774
|
-
firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
|
12775
|
-
lastMessageTs: formatNullableTimestamp2(dataSnapshot.lastMessageTs),
|
|
12776
|
-
lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
|
12777
|
-
segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
|
|
12778
|
-
memberHintTitle,
|
|
12779
|
-
memberHintLines,
|
|
12780
|
-
usageRules: agentT(t2, "ai.agent.dataSnapshotUsageRules", locale)
|
|
12781
|
-
});
|
|
12782
|
-
}
|
|
12783
|
-
function getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot) {
|
|
12784
|
-
const now = /* @__PURE__ */ new Date();
|
|
12785
|
-
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
12786
|
-
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
12787
|
-
year: "numeric",
|
|
12788
|
-
month: "long",
|
|
12789
|
-
day: "numeric",
|
|
12790
|
-
weekday: "long"
|
|
12791
|
-
});
|
|
12792
|
-
const isPrivate = chatType === "private";
|
|
12793
|
-
const chatContext = agentT(t2, `ai.agent.chatContext.${chatType}`, locale);
|
|
12794
|
-
const ownerNote = ownerInfo ? agentT(t2, "ai.agent.ownerNote", locale, {
|
|
12795
|
-
displayName: ownerInfo.displayName,
|
|
12796
|
-
platformId: ownerInfo.platformId,
|
|
12797
|
-
chatContext
|
|
12798
|
-
}) : "";
|
|
12799
|
-
const memberNote = isPrivate ? agentT(t2, "ai.agent.memberNotePrivate", locale) : agentT(t2, "ai.agent.memberNoteGroup", locale);
|
|
12800
|
-
const mentionedMembersNote = mentionedMembers && mentionedMembers.length > 0 ? `${agentT(t2, "ai.agent.mentionedMembersNote", locale)}
|
|
12801
|
-
${mentionedMembers.map((member) => {
|
|
12802
|
-
const aliasPart = member.aliases.length > 0 ? ` | aliases=${member.aliases.join(",")}` : "";
|
|
12803
|
-
return `- member_id=${member.memberId} | mention=${member.mentionText} | display_name=${member.displayName} | platform_id=${member.platformId}${aliasPart}`;
|
|
12804
|
-
}).join("\n")}
|
|
12805
|
-
` : "";
|
|
12806
|
-
const year = now.getFullYear();
|
|
12807
|
-
const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t2, dataSnapshot, locale)}
|
|
12808
|
-
` : "";
|
|
12809
|
-
return `${agentT(t2, "ai.agent.currentDateIs", locale)} ${currentDate}\u3002
|
|
12810
|
-
${ownerNote}
|
|
12811
|
-
${mentionedMembersNote}
|
|
12812
|
-
${dataSnapshotNote}
|
|
12813
|
-
${memberNote}
|
|
12814
|
-
${agentT(t2, "ai.agent.timeParamsIntro", locale)}
|
|
12815
|
-
${agentT(t2, "ai.agent.defaultYearNote", locale, { year })}
|
|
12816
|
-
${agentT(t2, "ai.agent.evidencePolicy", locale)}
|
|
12817
|
-
|
|
12818
|
-
${agentT(t2, "ai.agent.responseInstruction", locale)}`;
|
|
12819
|
-
}
|
|
12820
|
-
function getFallbackRoleDefinition(t2, chatType, locale) {
|
|
12821
|
-
return agentT(t2, `ai.agent.fallbackRoleDefinition.${chatType}`, locale);
|
|
12822
|
-
}
|
|
12823
|
-
function formatTimestamp(timestamp, locale) {
|
|
12824
|
-
if (!timestamp) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
12825
|
-
const date = new Date(timestamp * 1e3);
|
|
12826
|
-
if (Number.isNaN(date.getTime())) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
12827
|
-
const dateLocale = locale.startsWith("zh") ? "zh-CN" : locale;
|
|
12828
|
-
return date.toLocaleString(dateLocale, {
|
|
12829
|
-
year: "numeric",
|
|
12830
|
-
month: "2-digit",
|
|
12831
|
-
day: "2-digit",
|
|
12832
|
-
hour: "2-digit",
|
|
12833
|
-
minute: "2-digit"
|
|
12834
|
-
});
|
|
12835
|
-
}
|
|
12836
|
-
function buildSystemPrompt(options) {
|
|
12837
|
-
const {
|
|
12838
|
-
t: t2,
|
|
12839
|
-
chatType = "group",
|
|
12840
|
-
assistantSystemPrompt,
|
|
12841
|
-
ownerInfo,
|
|
12842
|
-
locale = "zh-CN",
|
|
12843
|
-
skillCtx,
|
|
12844
|
-
mentionedMembers,
|
|
12845
|
-
dataSnapshot
|
|
12846
|
-
} = options;
|
|
12847
|
-
const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t2, chatType, locale);
|
|
12848
|
-
const lockedSection = getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot);
|
|
12849
|
-
let skillSection = "";
|
|
12850
|
-
if (skillCtx?.skillDef) {
|
|
12851
|
-
skillSection = `
|
|
12852
|
-
## ${agentT(t2, "ai.agent.currentTask", locale)}\uFF1A${skillCtx.skillDef.name}
|
|
12853
|
-
${agentT(t2, "ai.agent.skillPriorityNote", locale)}
|
|
12854
|
-
` + skillCtx.skillDef.prompt;
|
|
12855
|
-
} else if (skillCtx?.skillMenu) {
|
|
12856
|
-
skillSection = `
|
|
12857
|
-
${skillCtx.skillMenu}`;
|
|
12858
|
-
}
|
|
12859
|
-
return `${systemPrompt}${skillSection}
|
|
12860
|
-
|
|
12861
|
-
${lockedSection}`;
|
|
12862
|
-
}
|
|
12863
|
-
|
|
12864
14220
|
// ../../packages/node-runtime/src/ai/i18n/locales/zh-CN.ts
|
|
12865
14221
|
var zh_CN_default = {
|
|
12866
14222
|
ai: {
|
|
@@ -14274,67 +15630,638 @@ var zh_TW_default = {
|
|
|
14274
15630
|
- \u5728\u8B49\u64DA\u5141\u8A31\u6642\u7D66\u51FA\u660E\u78BA\u5224\u65B7\uFF1B\u7121\u6CD5\u78BA\u8A8D\u7684\u90E8\u5206\u8981\u5766\u7387\u8AAA\u660E\u3002
|
|
14275
15631
|
- \u53EF\u4EE5\u6709\u4E00\u9EDE\u81EA\u7136\u7684\u5E7D\u9ED8\u611F\uFF0C\u4F46\u4E0D\u8981\u5F37\u884C\u73A9\u6897\u3001\u5806\u8868\u60C5\u6216\u72A7\u7272\u6E96\u78BA\u6027\u3002
|
|
14276
15632
|
|
|
14277
|
-
## \u5206\u6790\u908A\u754C
|
|
14278
|
-
- \u5340\u5206\u80FD\u5920\u78BA\u8A8D\u7684\u4E8B\u5BE6\u3001\u57FA\u65BC\u8B49\u64DA\u7684\u63A8\u8AD6\uFF0C\u4EE5\u53CA\u66AB\u6642\u7121\u6CD5\u5224\u65B7\u7684\u90E8\u5206\u3002
|
|
14279
|
-
- \u6D89\u53CA\u6027\u683C\u3001\u60C5\u7DD2\u3001\u95DC\u4FC2\u548C\u52D5\u6A5F\u6642\u5C24\u5176\u8B39\u614E\uFF0C\u4E0D\u7DE8\u9020\u5FC3\u7406\u6D3B\u52D5\uFF0C\u4E5F\u4E0D\u8F15\u6613\u7D66\u4EBA\u8CBC\u6A19\u7C64\u3002
|
|
14280
|
-
- \u9069\u91CF\u5F15\u7528\u6709\u4EE3\u8868\u6027\u7684\u539F\u8A71\u548C\u8CC7\u6599\uFF0C\u4E0D\u8981\u5927\u6BB5\u5806\u780C\u804A\u5929\u8A18\u9304\u3002
|
|
14281
|
-
- \u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\uFF0C\u4E0D\u8AAA\u6559\u3001\u4E0D\u64C5\u81EA\u7D66\u4EBA\u751F\u5EFA\u8B70\uFF0C\u4E5F\u4E0D\u628A\u6BCF\u6B21\u56DE\u7B54\u90FD\u5BEB\u6210\u7E3D\u7D50\u5831\u544A\u3002`
|
|
15633
|
+
## \u5206\u6790\u908A\u754C
|
|
15634
|
+
- \u5340\u5206\u80FD\u5920\u78BA\u8A8D\u7684\u4E8B\u5BE6\u3001\u57FA\u65BC\u8B49\u64DA\u7684\u63A8\u8AD6\uFF0C\u4EE5\u53CA\u66AB\u6642\u7121\u6CD5\u5224\u65B7\u7684\u90E8\u5206\u3002
|
|
15635
|
+
- \u6D89\u53CA\u6027\u683C\u3001\u60C5\u7DD2\u3001\u95DC\u4FC2\u548C\u52D5\u6A5F\u6642\u5C24\u5176\u8B39\u614E\uFF0C\u4E0D\u7DE8\u9020\u5FC3\u7406\u6D3B\u52D5\uFF0C\u4E5F\u4E0D\u8F15\u6613\u7D66\u4EBA\u8CBC\u6A19\u7C64\u3002
|
|
15636
|
+
- \u9069\u91CF\u5F15\u7528\u6709\u4EE3\u8868\u6027\u7684\u539F\u8A71\u548C\u8CC7\u6599\uFF0C\u4E0D\u8981\u5927\u6BB5\u5806\u780C\u804A\u5929\u8A18\u9304\u3002
|
|
15637
|
+
- \u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\uFF0C\u4E0D\u8AAA\u6559\u3001\u4E0D\u64C5\u81EA\u7D66\u4EBA\u751F\u5EFA\u8B70\uFF0C\u4E5F\u4E0D\u628A\u6BCF\u6B21\u56DE\u7B54\u90FD\u5BEB\u6210\u7E3D\u7D50\u5831\u544A\u3002`
|
|
15638
|
+
}
|
|
15639
|
+
}
|
|
15640
|
+
},
|
|
15641
|
+
llm: {
|
|
15642
|
+
notConfigured: "LLM \u670D\u52D9\u5C1A\u672A\u8A2D\u5B9A\uFF0C\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u586B\u5165 API Key",
|
|
15643
|
+
maxConfigs: "\u6700\u591A\u53EA\u80FD\u65B0\u589E {{count}} \u7D44\u8A2D\u5B9A",
|
|
15644
|
+
configNotFound: "\u627E\u4E0D\u5230\u8A2D\u5B9A",
|
|
15645
|
+
noActiveConfig: "\u6C92\u6709\u555F\u7528\u4E2D\u7684\u8A2D\u5B9A",
|
|
15646
|
+
callFailed: "LLM \u547C\u53EB\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u6A21\u578B\u8A2D\u5B9A\u662F\u5426\u6B63\u78BA",
|
|
15647
|
+
genericProviderName: "API \u670D\u52D9",
|
|
15648
|
+
rawErrorLabel: "\u539F\u59CB\u932F\u8AA4"
|
|
15649
|
+
},
|
|
15650
|
+
summary: {
|
|
15651
|
+
segmentNotFound: "\u6703\u8A71\u4E0D\u5B58\u5728\u6216\u8CC7\u6599\u5EAB\u958B\u555F\u5931\u6557",
|
|
15652
|
+
tooFewMessages: "\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
15653
|
+
tooFewValidMessages: "\u6709\u6548\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
15654
|
+
segmentNotExist: "\u627E\u4E0D\u5230\u6703\u8A71",
|
|
15655
|
+
messagesTooFew: "\u8A0A\u606F\u592A\u5C11",
|
|
15656
|
+
validMessagesTooFew: "\u6709\u6548\u8A0A\u606F\u592A\u5C11",
|
|
15657
|
+
systemPromptDirect: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u7528\u7C21\u6F54\u7684\u8A9E\u8A00\u7E3D\u7D50\u5C0D\u8A71\u5167\u5BB9\u3002",
|
|
15658
|
+
systemPromptMerge: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u5C07\u591A\u500B\u6458\u8981\u5408\u4F75\u6210\u4E00\u500B\u9023\u8CAB\u7684\u7E3D\u7D50\u3002"
|
|
15659
|
+
}
|
|
15660
|
+
};
|
|
15661
|
+
|
|
15662
|
+
// ../../packages/node-runtime/src/ai/i18n/index.ts
|
|
15663
|
+
var localeMap = {
|
|
15664
|
+
"zh-CN": zh_CN_default,
|
|
15665
|
+
"en-US": en_US_default,
|
|
15666
|
+
"ja-JP": ja_JP_default,
|
|
15667
|
+
"zh-TW": zh_TW_default
|
|
15668
|
+
};
|
|
15669
|
+
function getNestedValue(obj, path31) {
|
|
15670
|
+
let current = obj;
|
|
15671
|
+
for (const segment2 of path31.split(".")) {
|
|
15672
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
15673
|
+
current = current[segment2];
|
|
15674
|
+
}
|
|
15675
|
+
return current;
|
|
15676
|
+
}
|
|
15677
|
+
function interpolate(template, vars) {
|
|
15678
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
15679
|
+
const val = vars[key];
|
|
15680
|
+
return val != null ? String(val) : "";
|
|
15681
|
+
});
|
|
15682
|
+
}
|
|
15683
|
+
function createAiTranslate(defaultLocale = "zh-CN") {
|
|
15684
|
+
return (key, options) => {
|
|
15685
|
+
const lng = options?.lng || defaultLocale;
|
|
15686
|
+
const locale = localeMap[lng] ? lng : "en-US";
|
|
15687
|
+
const value = getNestedValue(localeMap[locale], key) ?? getNestedValue(localeMap["en-US"], key);
|
|
15688
|
+
if (typeof value !== "string") return key;
|
|
15689
|
+
if (!options) return value;
|
|
15690
|
+
return interpolate(value, options);
|
|
15691
|
+
};
|
|
15692
|
+
}
|
|
15693
|
+
var aiLocales = { "zh-CN": zh_CN_default, "en-US": en_US_default, "ja-JP": ja_JP_default, "zh-TW": zh_TW_default };
|
|
15694
|
+
|
|
15695
|
+
// ../../packages/node-runtime/src/ai/agent/event-handler.ts
|
|
15696
|
+
function estimateTokensFromText(text) {
|
|
15697
|
+
if (!text) return 0;
|
|
15698
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
15699
|
+
if (!normalized) return 0;
|
|
15700
|
+
const cjkCount = (normalized.match(/[\u3400-\u9fff\uf900-\ufaff]/g) || []).length;
|
|
15701
|
+
const latinCount = normalized.length - cjkCount;
|
|
15702
|
+
return Math.max(1, Math.ceil(cjkCount * 1.15 + latinCount / 4));
|
|
15703
|
+
}
|
|
15704
|
+
function extractMessageText(message) {
|
|
15705
|
+
if (message.role === "user") {
|
|
15706
|
+
if (typeof message.content === "string") return message.content;
|
|
15707
|
+
return message.content.map((item) => {
|
|
15708
|
+
if (item.type === "text") return item.text;
|
|
15709
|
+
if (item.type === "image") return "[image]";
|
|
15710
|
+
return "";
|
|
15711
|
+
}).join("\n");
|
|
15712
|
+
}
|
|
15713
|
+
if (message.role === "assistant") {
|
|
15714
|
+
return message.content.map((item) => {
|
|
15715
|
+
if (item.type === "text") return item.text;
|
|
15716
|
+
if (item.type === "thinking") return item.thinking;
|
|
15717
|
+
if (item.type === "toolCall") return `${item.name} ${JSON.stringify(item.arguments || {})}`;
|
|
15718
|
+
return "";
|
|
15719
|
+
}).join("\n");
|
|
15720
|
+
}
|
|
15721
|
+
if (message.role === "toolResult") {
|
|
15722
|
+
return message.content.map((item) => {
|
|
15723
|
+
if (item.type === "text") return item.text;
|
|
15724
|
+
return "[binary]";
|
|
15725
|
+
}).join("\n");
|
|
15726
|
+
}
|
|
15727
|
+
return "";
|
|
15728
|
+
}
|
|
15729
|
+
function estimateContextTokens(systemPrompt, messages, pendingUserMessage) {
|
|
15730
|
+
let tokens = estimateTokensFromText(systemPrompt);
|
|
15731
|
+
for (const message of messages) {
|
|
15732
|
+
if (message.role === "toolResult") continue;
|
|
15733
|
+
tokens += estimateTokensFromText(extractMessageText(message));
|
|
15734
|
+
}
|
|
15735
|
+
if (pendingUserMessage) {
|
|
15736
|
+
tokens += estimateTokensFromText(pendingUserMessage);
|
|
15737
|
+
}
|
|
15738
|
+
return tokens;
|
|
15739
|
+
}
|
|
15740
|
+
var AgentEventHandler = class {
|
|
15741
|
+
toolsUsed = [];
|
|
15742
|
+
toolRounds = 0;
|
|
15743
|
+
totalUsage = {
|
|
15744
|
+
promptTokens: 0,
|
|
15745
|
+
completionTokens: 0,
|
|
15746
|
+
totalTokens: 0,
|
|
15747
|
+
cacheReadTokens: 0,
|
|
15748
|
+
cacheWriteTokens: 0
|
|
15749
|
+
};
|
|
15750
|
+
lastStatusAt = 0;
|
|
15751
|
+
onChunk;
|
|
15752
|
+
context;
|
|
15753
|
+
systemPrompt;
|
|
15754
|
+
activeTools = /* @__PURE__ */ new Map();
|
|
15755
|
+
constructor(config) {
|
|
15756
|
+
this.onChunk = config.onChunk;
|
|
15757
|
+
this.context = config.context;
|
|
15758
|
+
this.systemPrompt = config.systemPrompt;
|
|
15759
|
+
}
|
|
15760
|
+
handleCoreEvent(event, messages) {
|
|
15761
|
+
switch (event.type) {
|
|
15762
|
+
case "content":
|
|
15763
|
+
this.onChunk({ type: "content", content: event.content });
|
|
15764
|
+
this.emitStatus("responding", messages);
|
|
15765
|
+
break;
|
|
15766
|
+
case "thinking_start":
|
|
15767
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15768
|
+
break;
|
|
15769
|
+
case "thinking_delta":
|
|
15770
|
+
this.onChunk({ type: "think", content: event.content, thinkTag: "thinking" });
|
|
15771
|
+
this.emitStatus("thinking", messages);
|
|
15772
|
+
break;
|
|
15773
|
+
case "thinking_end":
|
|
15774
|
+
this.onChunk({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: event.durationMs });
|
|
15775
|
+
this.emitStatus("responding", messages, { force: true });
|
|
15776
|
+
break;
|
|
15777
|
+
case "tool_start": {
|
|
15778
|
+
const params = this.normalizeToolParams(event.toolName, event.toolParams);
|
|
15779
|
+
this.toolsUsed.push(event.toolName);
|
|
15780
|
+
this.activeTools.set(event.toolCallId, event.toolName);
|
|
15781
|
+
this.onChunk({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params });
|
|
15782
|
+
this.emitStatus("tool_running", messages, { currentTool: event.toolName, force: true });
|
|
15783
|
+
break;
|
|
15784
|
+
}
|
|
15785
|
+
case "tool_update":
|
|
15786
|
+
this.onChunk({
|
|
15787
|
+
type: "tool_update",
|
|
15788
|
+
toolCallId: event.toolCallId,
|
|
15789
|
+
toolName: event.toolName,
|
|
15790
|
+
toolProgress: event.progress
|
|
15791
|
+
});
|
|
15792
|
+
break;
|
|
15793
|
+
case "tool_end":
|
|
15794
|
+
this.activeTools.delete(event.toolCallId);
|
|
15795
|
+
this.onChunk({
|
|
15796
|
+
type: "tool_result",
|
|
15797
|
+
toolCallId: event.toolCallId,
|
|
15798
|
+
toolName: event.toolName,
|
|
15799
|
+
toolResult: event.toolResult,
|
|
15800
|
+
toolIsError: event.isError
|
|
15801
|
+
});
|
|
15802
|
+
if (this.activeTools.size > 0) {
|
|
15803
|
+
const currentTool = Array.from(this.activeTools.values()).at(-1);
|
|
15804
|
+
this.emitStatus("tool_running", messages, { currentTool, force: true });
|
|
15805
|
+
} else {
|
|
15806
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15807
|
+
}
|
|
15808
|
+
break;
|
|
15809
|
+
case "turn_end":
|
|
15810
|
+
this.activeTools.clear();
|
|
15811
|
+
this.toolRounds = event.round;
|
|
15812
|
+
this.onChunk({ type: "turn_end", hadToolCalls: event.hadToolCalls });
|
|
15813
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15814
|
+
break;
|
|
15815
|
+
case "usage_update":
|
|
15816
|
+
this.totalUsage = { ...event.usage };
|
|
15817
|
+
this.emitStatus("responding", messages, { force: true });
|
|
15818
|
+
break;
|
|
15819
|
+
}
|
|
15820
|
+
}
|
|
15821
|
+
cloneUsage() {
|
|
15822
|
+
return {
|
|
15823
|
+
promptTokens: this.totalUsage.promptTokens,
|
|
15824
|
+
completionTokens: this.totalUsage.completionTokens,
|
|
15825
|
+
totalTokens: this.totalUsage.totalTokens,
|
|
15826
|
+
cacheReadTokens: this.totalUsage.cacheReadTokens,
|
|
15827
|
+
cacheWriteTokens: this.totalUsage.cacheWriteTokens
|
|
15828
|
+
};
|
|
15829
|
+
}
|
|
15830
|
+
emitStatus(phase, messages, options) {
|
|
15831
|
+
const now = Date.now();
|
|
15832
|
+
if (!options?.force && now - this.lastStatusAt < 240) return;
|
|
15833
|
+
this.lastStatusAt = now;
|
|
15834
|
+
const contextTokens = estimateContextTokens(this.systemPrompt, messages, options?.pendingUserMessage);
|
|
15835
|
+
const status = {
|
|
15836
|
+
phase,
|
|
15837
|
+
round: this.toolRounds,
|
|
15838
|
+
toolsUsed: this.toolsUsed.length,
|
|
15839
|
+
currentTool: options?.currentTool,
|
|
15840
|
+
contextTokens,
|
|
15841
|
+
totalUsage: this.cloneUsage(),
|
|
15842
|
+
updatedAt: now
|
|
15843
|
+
};
|
|
15844
|
+
this.onChunk({ type: "status", status });
|
|
15845
|
+
}
|
|
15846
|
+
normalizeToolParams(toolName, params) {
|
|
15847
|
+
const normalized = { ...params };
|
|
15848
|
+
const toolsWithLimit = ["search_messages", "get_recent_messages", "get_conversation_between"];
|
|
15849
|
+
if (this.context.maxMessagesLimit && toolsWithLimit.includes(toolName)) {
|
|
15850
|
+
normalized.limit = this.context.maxMessagesLimit;
|
|
15851
|
+
}
|
|
15852
|
+
if (this.context.timeFilter && (toolName === "search_messages" || toolName === "get_recent_messages")) {
|
|
15853
|
+
normalized._timeFilter = this.context.timeFilter;
|
|
15854
|
+
}
|
|
15855
|
+
return normalized;
|
|
15856
|
+
}
|
|
15857
|
+
};
|
|
15858
|
+
|
|
15859
|
+
// ../../packages/node-runtime/src/ai/cross-chat-agent.ts
|
|
15860
|
+
var CROSS_CHAT_MAX_TOOL_RESULT_TOKENS = 128e3;
|
|
15861
|
+
function resolveCrossChatToolResultTokenBudget(contextWindow) {
|
|
15862
|
+
const maxToolResultPercent = DEFAULT_CONTEXT_COMPRESSION_CONFIG.maxToolResultPercent ?? 50;
|
|
15863
|
+
const proportionalBudget = Math.floor(contextWindow * (maxToolResultPercent / 100));
|
|
15864
|
+
return Math.min(proportionalBudget, CROSS_CHAT_MAX_TOOL_RESULT_TOKENS);
|
|
15865
|
+
}
|
|
15866
|
+
async function runCrossChatAgent(options) {
|
|
15867
|
+
const {
|
|
15868
|
+
userMessage,
|
|
15869
|
+
entityRefs,
|
|
15870
|
+
aiChatId,
|
|
15871
|
+
historyLeafMessageId,
|
|
15872
|
+
locale = "zh-CN",
|
|
15873
|
+
piModel,
|
|
15874
|
+
apiKey,
|
|
15875
|
+
tools,
|
|
15876
|
+
aiChatManager,
|
|
15877
|
+
memoryService,
|
|
15878
|
+
onEvent,
|
|
15879
|
+
abortSignal,
|
|
15880
|
+
thinkingLevel,
|
|
15881
|
+
logger
|
|
15882
|
+
} = options;
|
|
15883
|
+
await initTokenizer();
|
|
15884
|
+
logger?.info("CrossChatAgent", "Cross-chat agent execution started", {
|
|
15885
|
+
aiChatId,
|
|
15886
|
+
entityRefCount: entityRefs?.length ?? 0,
|
|
15887
|
+
toolCount: tools.length
|
|
15888
|
+
});
|
|
15889
|
+
const globalMemoryPrompt = buildGlobalMemoryPrompt(
|
|
15890
|
+
memoryService.list({ scopeType: "global", scopeId: null }),
|
|
15891
|
+
locale,
|
|
15892
|
+
userMessage
|
|
15893
|
+
);
|
|
15894
|
+
const entityMemoryPrompt = buildEntityMemoryPrompt(
|
|
15895
|
+
entityRefs,
|
|
15896
|
+
(scope) => memoryService.list(scope),
|
|
15897
|
+
locale,
|
|
15898
|
+
userMessage
|
|
15899
|
+
);
|
|
15900
|
+
const systemPrompt = buildCrossChatSystemPrompt(locale, /* @__PURE__ */ new Date(), globalMemoryPrompt, entityMemoryPrompt);
|
|
15901
|
+
const handler = new AgentEventHandler({ onChunk: onEvent, context: {}, systemPrompt });
|
|
15902
|
+
let cachedMessages = [];
|
|
15903
|
+
const finishAborted = () => {
|
|
15904
|
+
handler.emitStatus("aborted", cachedMessages, { force: true });
|
|
15905
|
+
onEvent({ type: "done", isFinished: true, usage: handler.cloneUsage() });
|
|
15906
|
+
};
|
|
15907
|
+
if (abortSignal?.aborted) {
|
|
15908
|
+
finishAborted();
|
|
15909
|
+
return;
|
|
15910
|
+
}
|
|
15911
|
+
if (historyLeafMessageId === void 0) {
|
|
15912
|
+
const compressionResult = await checkAndCompress(
|
|
15913
|
+
aiChatId,
|
|
15914
|
+
DEFAULT_CONTEXT_COMPRESSION_CONFIG,
|
|
15915
|
+
systemPrompt,
|
|
15916
|
+
createCompressionLlmAdapter({
|
|
15917
|
+
piModel,
|
|
15918
|
+
apiKey,
|
|
15919
|
+
onCompressing: () => handler.emitStatus("compressing", [])
|
|
15920
|
+
}),
|
|
15921
|
+
aiChatManager,
|
|
15922
|
+
logger,
|
|
15923
|
+
{ signal: abortSignal }
|
|
15924
|
+
);
|
|
15925
|
+
if (compressionResult.compressed) {
|
|
15926
|
+
onEvent({
|
|
15927
|
+
type: "compression_done",
|
|
15928
|
+
compressionResult: {
|
|
15929
|
+
summaryContent: compressionResult.summaryContent ?? "",
|
|
15930
|
+
tokensBefore: compressionResult.tokensBefore ?? 0,
|
|
15931
|
+
tokensAfter: compressionResult.tokensAfter ?? 0,
|
|
15932
|
+
timestamp: Date.now()
|
|
15933
|
+
}
|
|
15934
|
+
});
|
|
15935
|
+
}
|
|
15936
|
+
}
|
|
15937
|
+
if (abortSignal?.aborted) {
|
|
15938
|
+
finishAborted();
|
|
15939
|
+
return;
|
|
15940
|
+
}
|
|
15941
|
+
const history = aiChatManager.getHistoryForAgent(aiChatId, void 0, historyLeafMessageId);
|
|
15942
|
+
const modelUserMessage = appendEntityRefsForModel(userMessage, entityRefs);
|
|
15943
|
+
handler.emitStatus("preparing", [], { pendingUserMessage: modelUserMessage, force: true });
|
|
15944
|
+
try {
|
|
15945
|
+
const routeInput = {
|
|
15946
|
+
userMessage: modelUserMessage,
|
|
15947
|
+
chatType: "group",
|
|
15948
|
+
locale,
|
|
15949
|
+
availableTools: tools.map((tool) => tool.name),
|
|
15950
|
+
availableCapabilities: []
|
|
15951
|
+
};
|
|
15952
|
+
const routeDecision = await decideRequestRoute(routeInput, {
|
|
15953
|
+
llmRouter: createLlmRouteDecider({ piModel, apiKey, abortSignal })
|
|
15954
|
+
});
|
|
15955
|
+
onEvent({ type: "route", routeDecision });
|
|
15956
|
+
let effectiveSystemPrompt = systemPrompt;
|
|
15957
|
+
if (routeDecision.route === "planned_execution") {
|
|
15958
|
+
const planner = createAnalysisPlanner({
|
|
15959
|
+
piModel,
|
|
15960
|
+
apiKey,
|
|
15961
|
+
onPlanDelta: (delta) => onEvent({ type: "plan_delta", planDelta: delta }),
|
|
15962
|
+
onThinkingDelta: (delta) => onEvent({ type: "think", content: delta, thinkTag: "thinking" }),
|
|
15963
|
+
onThinkingEnd: (durationMs) => onEvent({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: durationMs }),
|
|
15964
|
+
onValidationDelta: (delta) => onEvent({ type: "think", content: delta, thinkTag: "plan_validation" }),
|
|
15965
|
+
onValidationEnd: (durationMs) => onEvent({ type: "think", content: "", thinkTag: "plan_validation", thinkDurationMs: durationMs })
|
|
15966
|
+
});
|
|
15967
|
+
const plan = await planner(routeInput, abortSignal);
|
|
15968
|
+
if (plan) {
|
|
15969
|
+
onEvent({ type: "plan", plan: createPlanContentBlock(plan) });
|
|
15970
|
+
effectiveSystemPrompt = `${systemPrompt}
|
|
15971
|
+
|
|
15972
|
+
${buildPlanGuidance(plan)}`;
|
|
15973
|
+
} else {
|
|
15974
|
+
onEvent({ type: "plan_skipped" });
|
|
14282
15975
|
}
|
|
14283
15976
|
}
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
15977
|
+
const result = await runAgentCore({
|
|
15978
|
+
piModel,
|
|
15979
|
+
apiKey,
|
|
15980
|
+
systemPrompt: effectiveSystemPrompt,
|
|
15981
|
+
tools,
|
|
15982
|
+
history,
|
|
15983
|
+
userMessage: modelUserMessage,
|
|
15984
|
+
maxToolRounds: DEFAULT_MAX_TOOL_ROUNDS,
|
|
15985
|
+
abortSignal,
|
|
15986
|
+
providerSessionId: aiChatId,
|
|
15987
|
+
steerMessage: createAiTranslate(locale)("ai.agent.answerWithoutTools"),
|
|
15988
|
+
thinkingLevel,
|
|
15989
|
+
onConvertToLlm: (messages) => {
|
|
15990
|
+
cachedMessages = messages;
|
|
15991
|
+
},
|
|
15992
|
+
onEvent: (event) => handler.handleCoreEvent(event, cachedMessages),
|
|
15993
|
+
onDebugContext: (messages) => {
|
|
15994
|
+
try {
|
|
15995
|
+
aiChatManager.setPendingDebugContext(aiChatId, JSON.stringify(messages, null, 2));
|
|
15996
|
+
} catch {
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
});
|
|
16000
|
+
if (abortSignal?.aborted) {
|
|
16001
|
+
finishAborted();
|
|
16002
|
+
return;
|
|
16003
|
+
}
|
|
16004
|
+
if (result.error) {
|
|
16005
|
+
onEvent({ type: "error", error: { name: "AgentError", message: formatAIError(result.error) } });
|
|
16006
|
+
}
|
|
16007
|
+
handler.emitStatus("completed", cachedMessages, { force: true });
|
|
16008
|
+
logger?.info("CrossChatAgent", "Cross-chat agent execution completed", {
|
|
16009
|
+
aiChatId,
|
|
16010
|
+
toolRounds: result.toolRounds,
|
|
16011
|
+
toolsUsed: result.toolsUsed.length
|
|
16012
|
+
});
|
|
16013
|
+
onEvent({ type: "done", isFinished: true, usage: result.usage });
|
|
16014
|
+
} catch (error) {
|
|
16015
|
+
if (abortSignal?.aborted) {
|
|
16016
|
+
finishAborted();
|
|
16017
|
+
return;
|
|
16018
|
+
}
|
|
16019
|
+
logger?.error("CrossChatAgent", "Cross-chat agent execution failed", error);
|
|
16020
|
+
handler.emitStatus("error", cachedMessages, { force: true });
|
|
16021
|
+
onEvent({ type: "error", error: { name: "AgentError", message: formatAIError(error) } });
|
|
16022
|
+
onEvent({ type: "done", isFinished: true, usage: handler.cloneUsage() });
|
|
14303
16023
|
}
|
|
14304
|
-
}
|
|
16024
|
+
}
|
|
16025
|
+
function buildCrossChatSystemPrompt(locale = "zh-CN", now = /* @__PURE__ */ new Date(), globalMemoryPrompt = "", entityMemoryPrompt = "") {
|
|
16026
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
16027
|
+
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
16028
|
+
year: "numeric",
|
|
16029
|
+
month: "long",
|
|
16030
|
+
day: "numeric",
|
|
16031
|
+
weekday: "long"
|
|
16032
|
+
});
|
|
16033
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
|
|
16034
|
+
if (locale.startsWith("zh")) {
|
|
16035
|
+
return `\u4F60\u662F ChatLab \u7684\u8DE8\u5BF9\u8BDD\u5206\u6790\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u6309\u7528\u6237\u5F53\u524D\u95EE\u9898\uFF0C\u5728\u5176\u672C\u5730\u804A\u5929\u6570\u636E\u5E93\u4E2D\u6309\u9700\u68C0\u7D22\u591A\u4E2A\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u3002
|
|
14305
16036
|
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
16037
|
+
\u6570\u636E\u4E0E\u8303\u56F4\u89C4\u5219\uFF1A
|
|
16038
|
+
- \u5F53\u524D\u65E5\u671F\u662F ${currentDate}\uFF08\u65F6\u533A\uFF1A${timeZone}\uFF09\u3002\u7528\u6237\u7684\u76F8\u5BF9\u65F6\u95F4\u8868\u8FBE\u4EE5\u771F\u5B9E\u5F53\u524D\u65F6\u95F4\u4E3A\u57FA\u51C6\uFF1B\u6570\u636E\u5E93\u622A\u6B62\u65F6\u95F4\u53EA\u8BF4\u660E\u5DF2\u5BFC\u5165\u6570\u636E\u7684\u8986\u76D6\u8303\u56F4\u3002
|
|
16039
|
+
- \u7528\u6237\u5DF2\u6388\u6743\u4F60\u67E5\u8BE2\u5168\u90E8\u672C\u5730\u804A\u5929\u6570\u636E\uFF0C\u4F46\u53EA\u80FD\u4E3A\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u6309\u9700\u8C03\u7528\u5DE5\u5177\uFF0C\u7981\u6B62\u65E0\u76EE\u7684\u904D\u5386\u3002
|
|
16040
|
+
- <chatlab_entity_refs> \u662F\u754C\u9762\u9009\u62E9\u5668\u5199\u5165\u7684\u7A33\u5B9A\u5B9E\u4F53\u5F15\u7528\uFF0C\u6D89\u53CA\u8FD9\u4E9B\u5B9E\u4F53\u65F6\u76F4\u63A5\u8C03\u7528 resolve_chat_entities\u3002\u7528\u6237\u53EA\u8F93\u5165\u8054\u7CFB\u4EBA\u540D\u79F0\u65F6\uFF0C\u4E5F\u8981\u7528 resolve_chat_entities \u67E5\u8BE2\u8054\u7CFB\u4EBA\u76EE\u5F55\uFF1A\u552F\u4E00\u5019\u9009\u81EA\u52A8\u7EE7\u7EED\uFF0C\u591A\u4E2A\u5019\u9009\u5FC5\u987B\u505C\u4E0B\u6765\u8BF7\u7528\u6237\u786E\u8BA4\uFF0C\u6CA1\u6709\u5019\u9009\u65F6\u518D\u63D0\u793A\u8865\u5145\u4FE1\u606F\uFF1B\u7981\u6B62\u5728\u591A\u4E2A\u5019\u9009\u4E2D\u81EA\u884C\u731C\u6D4B\u3002
|
|
16041
|
+
- \u5F53\u524D\u7528\u6237\u6D88\u606F\u4E2D\u7684 <chatlab_entity_refs> \u662F\u672C\u8F6E\u6700\u65B0\u9009\u62E9\u3002\u7528\u6237\u8BF4\u201C\u5F53\u524D\u5BF9\u8C61\u201D\u201C\u6240\u9009\u5B9E\u4F53\u201D\u7B49\u6307\u4EE3\u65F6\uFF0C\u53EA\u4F7F\u7528\u672C\u8F6E\u5F15\u7528\uFF0C\u4E0D\u5F97\u590D\u7528\u5386\u53F2\u6D88\u606F\u4E2D\u7684\u65E7\u5B9E\u4F53\uFF1B\u672C\u8F6E\u540C\u65F6\u9009\u62E9\u591A\u4E2A\u5B9E\u4F53\u65F6\u53EF\u4EE5\u6309\u95EE\u9898\u4F7F\u7528\u5176\u4E2D\u4EFB\u610F\u4E00\u4E2A\u3002
|
|
16042
|
+
- \u5BF9\u8BDD\u5386\u53F2\u4E2D\u7684\u5B9E\u4F53\u5F15\u7528\u53EA\u5E2E\u52A9\u7406\u89E3\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u6784\u6210\u6C38\u4E45\u9501\u5B9A\u8303\u56F4\u3002\u6BCF\u4E00\u8F6E\u6839\u636E\u7528\u6237\u8BED\u4E49\u51B3\u5B9A\u7EE7\u7EED\u539F\u5BF9\u8C61\u3001\u5207\u6362\u5BF9\u8C61\u6216\u6267\u884C\u5168\u5C40\u53D1\u73B0\u3002
|
|
16043
|
+
- \u8054\u7CFB\u4EBA\u9ED8\u8BA4\u8986\u76D6\u5176\u5B9E\u9645\u53C2\u4E0E\u7684\u79C1\u804A\u548C\u7FA4\u804A\u3002\u591A\u4E2A\u5BF9\u8C61\u65E2\u53EF\u80FD\u662F\u4EA4\u96C6\u3001\u5E76\u96C6\uFF0C\u4E5F\u53EF\u80FD\u9700\u8981\u5206\u522B\u68C0\u7D22\u6BD4\u8F83\uFF1B\u4EE5\u7528\u6237\u8BED\u4E49\u4E3A\u51C6\uFF0C\u4E0D\u8981\u673A\u68B0\u5957\u7528\u4E00\u79CD\u96C6\u5408\u89C4\u5219\u3002
|
|
16044
|
+
- \u5F53\u7528\u6237\u95EE\u201C\u6211\u548C\u67D0\u4EBA\u6700\u8FD1\u804A\u4E86\u4EC0\u4E48\u201D\u6216\u660E\u786E\u8981\u6C42\u56DE\u987E\u4E24\u4EBA\u7684\u79C1\u804A\u5185\u5BB9\u65F6\uFF0C\u53EA\u4F7F\u7528 resolve_chat_entities \u8FD4\u56DE\u7684\u5BF9\u5E94\u79C1\u804A session\uFF0C\u4E0D\u8981\u6DF7\u5165\u8BE5\u8054\u7CFB\u4EBA\u6240\u5728\u7684\u7FA4\u804A\u3002\u7528\u6237\u6CA1\u6709\u7ED9\u51FA\u660E\u786E\u65E5\u671F\u6216\u5929\u6570\u65F6\uFF0C\u8FD9\u91CC\u7684\u201C\u6700\u8FD1\u201D\u6307\u5DF2\u5BFC\u5165\u79C1\u804A\u4E2D\u6700\u65B0\u4E00\u6BB5\u53EF\u7528\u5185\u5BB9\uFF1A\u89E3\u6790\u5230\u552F\u4E00\u79C1\u804A\u540E\u76F4\u63A5\u8C03\u7528 read_recent_session\uFF0C\u4E0D\u8981\u5148\u7528 search_messages_globally \u586B\u6EE1\u8DE8\u5BF9\u8BDD\u8BC1\u636E\u9884\u7B97\u3002\u53EA\u6709\u660E\u786E\u8BF4\u201C\u6700\u8FD1 N \u5929\u201D\u3001\u7ED9\u51FA\u65E5\u671F\u8303\u56F4\u3001\u8981\u6C42\u66F4\u65E9\u5185\u5BB9\u6216\u6DF1\u5EA6\u8C03\u67E5\u65F6\u624D\u6539\u7528 search_messages_globally\u3002\u6CA1\u6709\u5BF9\u5E94\u79C1\u804A\u65F6\u5148\u660E\u786E\u8BF4\u660E\uFF0C\u518D\u8BE2\u95EE\u662F\u5426\u6539\u67E5\u5171\u540C\u7FA4\u3002
|
|
16045
|
+
- \u7528\u6237\u8BE2\u95EE\u67D0\u4E2A\u8054\u7CFB\u4EBA\u51FA\u73B0\u5728\u54EA\u4E9B\u5DF2\u5BFC\u5165\u4F1A\u8BDD\u3001\u5206\u522B\u8BF4\u4E86\u591A\u5C11\u6216\u6700\u8FD1\u5728\u54EA\u91CC\u6D3B\u8DC3\u65F6\uFF0C\u5148\u7528 inspect_contact_sessions \u76D8\u70B9\u6765\u6E90\u3002\u4E0D\u8981\u4EC5\u56E0\u4E3A\u6D88\u606F\u4E2D\u51FA\u73B0\u4E86 @\u8054\u7CFB\u4EBA\u5C31\u673A\u68B0\u8C03\u7528\uFF1B\u666E\u901A\u4E8B\u5B9E\u9898\u4ECD\u6309\u95EE\u9898\u4F7F\u7528\u641C\u7D22\u4E0E\u4E0A\u4E0B\u6587\u5DE5\u5177\u3002
|
|
16046
|
+
- \u7528\u6237\u8BE2\u95EE 2\u20145 \u4EBA\u7684\u5173\u7CFB\u3001\u76F8\u8BC6\u80CC\u666F\u3001\u5171\u540C\u5708\u5B50\u6216\u4E92\u52A8\u53D8\u5316\u65F6\uFF0C\u5148\u7528 inspect_shared_interactions \u627E\u5168\u5458\u5171\u540C\u4F1A\u8BDD\u3001\u9010\u4EBA\u6D3B\u8DC3\u3001\u76F4\u63A5\u56DE\u590D\u3001\u76F8\u90BB\u53D1\u8A00\u548C\u6D88\u606F\u951A\u70B9\uFF1B\u4E0D\u8981\u4E3A\u4E86\u591A\u4EBA\u95EE\u9898\u673A\u68B0\u5730\u5148\u5BF9\u6BCF\u4E2A\u4EBA\u8C03\u7528 inspect_contact_sessions\u3002
|
|
16047
|
+
- \u7528\u6237\u8BE2\u95EE\u201C\u6211\u548C\u8C01\u79C1\u804A\u6700\u591A\u201D\u201C\u79C1\u804A\u9891\u7387\u6392\u884C\u201D\u6216\u201C\u548C\u54EA\u4E9B\u8054\u7CFB\u4EBA\u804A\u5929\u7684\u6D3B\u8DC3\u5929\u6570\u6700\u591A\u201D\u65F6\uFF0C\u76F4\u63A5\u8C03\u7528 rank_private_contacts\uFF0C\u5E76\u6309\u7528\u6237\u7ED9\u51FA\u7684\u81EA\u7136\u5E74\u3001\u65E5\u671F\u6216 recent_days \u7EDF\u8BA1\u3002\u4E0D\u8981\u628A\u201C\u6211\u201D\u89E3\u6790\u6210\u8054\u7CFB\u4EBA\uFF0C\u4E5F\u4E0D\u8981\u7528 search_messages_globally \u7684\u5173\u952E\u8BCD\u547D\u4E2D\u91CF\u3001\u62BD\u6837\u6D88\u606F\u6216 Contacts \u7EFC\u5408\u5206\u6570\u4EE3\u66FF\u5168\u5C40\u6392\u884C\u3002
|
|
16048
|
+
- \u7528\u6237\u8BE2\u95EE\u201C\u6211\u5728\u54EA\u4E9B\u7FA4\u6700\u6D3B\u8DC3\u201D\u65F6\u8C03\u7528 rank_group_sessions(mode=owner_activity)\uFF1B\u8BE2\u95EE\u201C\u54EA\u4E9B\u7FA4\u6700\u70ED\u95F9\u3001\u603B\u6D88\u606F\u6700\u591A\u201D\u65F6\u8C03\u7528 mode=total_activity\u3002\u4E24\u79CD\u53E3\u5F84\u4E0D\u80FD\u4E92\u6362\uFF1B\u53E5\u5B50\u4ECD\u65E0\u6CD5\u5224\u65AD\u662F\u5728\u95EE\u672C\u4EBA\u8FD8\u662F\u7FA4\u6574\u4F53\u65F6\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u786E\u8BA4\u3002
|
|
16049
|
+
- \u7528\u6237\u8BE2\u95EE\u81EA\u7136\u5E74\u6216\u6700\u8FD1 365 \u5929\u5185\u672C\u4EBA\u603B\u53D1\u8A00\u91CF\u3001\u6D3B\u8DC3\u5929\u6570\u3001\u79C1\u804A\u8054\u7CFB\u4EBA\u6570\u3001\u6708\u5EA6/\u6BCF\u65E5\u8D8B\u52BF\u3001\u6D88\u606F\u7C7B\u578B\u6216\u6587\u672C\u957F\u5EA6\u65F6\uFF0C\u76F4\u63A5\u8C03\u7528 get_global_activity_summary\u3002\u5B83\u4E0D\u63D0\u4F9B\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A\u6392\u884C\uFF1B\u7528\u6237\u8BE2\u95EE\u6700\u8FD1 30 \u5929\u7B49\u5176\u4ED6\u603B\u4F53\u7A97\u53E3\u65F6\uFF0C\u660E\u786E\u8BF4\u660E\u9996\u7248\u53EA\u652F\u6301\u81EA\u7136\u5E74\u548C\u6700\u8FD1 365 \u5929\uFF0C\u4E0D\u8981\u7528\u5173\u952E\u8BCD\u641C\u7D22\u4F30\u7B97\u603B\u91CF\u3002
|
|
16050
|
+
- \u4E09\u4EBA\u53CA\u4EE5\u4E0A\u7684 inspect_shared_interactions \u7ED3\u679C\u662F\u201C\u6240\u6709\u53C2\u4E0E\u8005\u90FD\u51FA\u73B0\u201D\u7684\u4E25\u683C\u4EA4\u96C6\uFF1B\u5982\u679C\u7528\u6237\u8981\u6BD4\u8F83\u591A\u7EC4\u4E24\u4E24\u5173\u7CFB\uFF0C\u5E94\u6309\u6210\u5458\u5BF9\u5206\u522B\u8C03\u67E5\uFF0C\u4E0D\u8981\u628A\u53EA\u542B\u90E8\u5206\u4EBA\u7684\u4F1A\u8BDD\u6DF7\u8FDB\u5168\u5458\u5171\u540C\u6765\u6E90\u3002
|
|
16051
|
+
- \u53EA\u6709\u7528\u6237\u660E\u786E\u8868\u8FBE\u201C\u5FD8\u4E86\u548C\u8C01\u804A\u8FC7\u201D\u201C\u5728\u6240\u6709\u804A\u5929\u91CC\u627E\u201D\u7B49\u5168\u5C40\u53D1\u73B0\u610F\u56FE\u65F6\uFF0C\u624D\u5141\u8BB8\u4E0D\u5E26 scopes \u8C03\u7528 search_messages_globally\u3002\u5168\u5C40\u53D1\u73B0\u5FC5\u987B\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u5173\u952E\u8BCD\uFF1B\u5DF2\u7ECF\u9650\u5B9A scopes \u65F6\uFF0C\u53EF\u4EE5\u4E0D\u63D0\u4F9B\u5173\u952E\u8BCD\u6765\u62BD\u53D6\u8FD1\u671F\u6D88\u606F\u6837\u672C\u3002
|
|
16052
|
+
- \u9664\u4E0A\u8FF0\u53CC\u4EBA\u79C1\u804A\u5185\u5BB9\u56DE\u987E\u5916\uFF0C\u7528\u6237\u4F7F\u7528\u201C\u6700\u8FD1\u201D\u201C\u8FD1\u671F\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u4F46\u6CA1\u6709\u7ED9\u51FA\u5177\u4F53\u8303\u56F4\u65F6\uFF0C\u7B2C\u4E00\u6B21\u652F\u6301\u65F6\u95F4\u8303\u56F4\u7684\u641C\u7D22\u6216\u7ED3\u6784\u8C03\u67E5\u5DE5\u5177\u8C03\u7528\u5FC5\u987B\u4F20 recent_days=30\uFF0C\u5E76\u5728\u56DE\u7B54\u4E2D\u8BF4\u660E\u6309\u6700\u8FD1 30 \u5929\u7EDF\u8BA1\uFF1B\u7981\u6B62\u6839\u636E\u6570\u636E\u5E93\u622A\u6B62\u65F6\u95F4\u6216 lastMessageTs \u624B\u5DE5\u8BA1\u7B97 start_time\uFF0C\u4E5F\u7981\u6B62\u5148\u626B\u63CF\u5168\u90E8\u5386\u53F2\u518D\u4E8B\u540E\u4E3B\u89C2\u622A\u53D6\u3002\u53EA\u6709 30 \u5929\u5185\u6CA1\u6709\u7ED3\u679C\u65F6\uFF0C\u624D\u80FD\u8BE2\u95EE\u7528\u6237\u662F\u5426\u6269\u5927\u8303\u56F4\u3002
|
|
16053
|
+
- \u7528\u6237\u628A\u81EA\u5DF1\u4F5C\u4E3A\u4E8B\u4EF6\u4E3B\u4F53\uFF08\u4F8B\u5982\u201C\u6211\u6700\u8FD1\u8DDF\u591A\u5C11\u4EBA\u804A\u8FC7\u6211\u4E70\u623F\u201D\uFF09\u65F6\uFF0C\u7B2C\u4E00\u6B21\u641C\u7D22\u5FC5\u987B\u4F20 sender=owner\uFF0C\u628A\u672C\u4EBA\u53D1\u8A00\u4F5C\u4E3A\u68C0\u7D22\u79CD\u5B50\uFF1B\u4E0D\u8981\u5148\u641C\u7D22\u6240\u6709\u4EBA\u7684\u540C\u7C7B\u8BDD\u9898\u3002\u641C\u7D22\u5DE5\u5177\u4F1A\u81EA\u52A8\u9644\u5E26\u5468\u8FB9\u6D88\u606F\u4EE5\u8BC6\u522B\u771F\u6B63\u53C2\u4E0E\u5BF9\u8BDD\u7684\u4EBA\uFF1B\u53EA\u6709\u9700\u8981\u66F4\u6DF1\u539F\u6587\u65F6\u624D\u7EE7\u7EED\u4F7F\u7528 get_cross_chat_message_context\uFF0C\u4E0A\u4E0B\u6587\u4E0D\u9650\u5236\u4E3A\u672C\u4EBA\u53D1\u8A00\u3002
|
|
16054
|
+
|
|
16055
|
+
\u5DE5\u5177\u4E0E\u7ED3\u8BBA\u89C4\u5219\uFF1A
|
|
16056
|
+
- \u4F60\u53EA\u6709 resolve_chat_entities\u3001read_recent_session\u3001rank_private_contacts\u3001rank_group_sessions\u3001get_global_activity_summary\u3001inspect_contact_sessions\u3001inspect_shared_interactions\u3001search_messages_globally\u3001get_cross_chat_message_context\u3001get_cross_chat_overview\u3001memory_read\u3001memory_write\u3001memory_forget \u5341\u4E09\u4E2A\u5DE5\u5177\u3002\u4E0D\u8981\u58F0\u79F0\u53EF\u4EE5\u4F7F\u7528\u5355\u4F1A\u8BDD SQL\u3001\u8BED\u4E49\u7D22\u5F15\u3001\u6280\u80FD\u3001\u56FE\u8868\u6216\u70ED\u529B\u56FE\u3002
|
|
16057
|
+
- read_recent_session \u662F\u5355\u4E2A\u5DF2\u89E3\u6790\u4F1A\u8BDD\u7684\u8F7B\u91CF\u8FD1\u671F\u56DE\u987E\uFF1A\u5B83\u5185\u90E8\u8BFB\u53D6\u6700\u65B0\u4E00\u5C0F\u6BB5\u539F\u6587\u548C\u6700\u591A 5 \u4E2A\u5DF2\u6709\u6BB5\u843D\u6458\u8981\uFF0C\u6A21\u578B\u4E0D\u80FD\u6307\u5B9A\u6D88\u606F\u9884\u7B97\u3002\u4E0D\u8981\u4EC5\u56E0\u4E3A selection.hasEarlierMessages=true \u5C31\u81EA\u52A8\u6269\u5927\u641C\u7D22\uFF1B\u5148\u56DE\u7B54\u7528\u6237\u5F53\u524D\u7684\u666E\u901A\u8FD1\u671F\u56DE\u987E\uFF0C\u53EA\u6709\u7528\u6237\u8981\u6C42\u5177\u4F53\u65F6\u95F4\u3001\u5173\u952E\u8BCD\u3001\u66F4\u65E9\u5185\u5BB9\u6216\u6DF1\u6316\u65F6\u518D\u4F7F\u7528 search_messages_globally\u3002
|
|
16058
|
+
- inspect_contact_sessions \u8FD4\u56DE\u7684\u662F\u5DF2\u5BFC\u5165\u4F1A\u8BDD\u4E2D\u7684\u672C\u4EBA\u53D1\u8A00\u548C\u6210\u5458\u8868\u7ED3\u6784\u4E8B\u5B9E\uFF0C\u4E0D\u8FD4\u56DE\u804A\u5929\u6B63\u6587\uFF0C\u4E5F\u4E0D\u80FD\u8BC1\u660E\u73B0\u5B9E\u4E16\u754C\u4E2D\u7684\u5168\u90E8\u7FA4\u804A\u3002roster_only \u53EA\u8BF4\u660E\u5BFC\u5165\u6210\u5458\u8868\u8BB0\u5F55\u8FC7\u6B64\u4EBA\uFF1B\u5B58\u5728 nextCursor \u6216 coverage \u4E0D\u5B8C\u6574\u65F6\u4E0D\u80FD\u8868\u8FF0\u6210\u5168\u91CF\u7ED3\u679C\u3002
|
|
16059
|
+
- inspect_shared_interactions \u7684\u7FA4\u540D\u79F0\u3001\u6210\u5458\u7ED3\u6784\u3001\u5171\u540C\u6D3B\u8DC3\u548C\u76F8\u90BB\u53D1\u8A00\u53EA\u662F\u8C03\u67E5\u5BFC\u822A\u4FE1\u53F7\uFF0C\u4E0D\u80FD\u76F4\u63A5\u8BC1\u660E\u540C\u4E8B\u3001\u540C\u5B66\u3001\u4EB2\u5C5E\u3001\u670B\u53CB\u6216\u4EB2\u5BC6\u5173\u7CFB\u3002\u4F18\u5148\u5BF9 direct_reply \u548C proximity \u951A\u70B9\u8C03\u7528 get_cross_chat_message_context \u8BFB\u53D6\u539F\u6587\uFF1B\u56DE\u590D\u76EE\u6807\u53EF\u80FD\u7531 relatedMessageId \u6307\u5411\u8F83\u8FDC\u6D88\u606F\uFF0C\u5FC5\u8981\u65F6\u5206\u522B\u5C55\u5F00\u4E24\u4E2A\u6D88\u606F ID\u3002
|
|
16060
|
+
- rank_private_contacts \u8FD4\u56DE\u5B8C\u6574\u5019\u9009\u626B\u63CF\u4E0B\u7684\u786E\u5B9A\u6027\u79C1\u804A\u8BA1\u6570\u3001\u53CC\u65B9\u6D88\u606F\u5206\u9879\u3001\u6D3B\u8DC3\u5929\u6570\u548C\u8EAB\u4EFD coverage\u3002\u53EA\u6709 coverage.complete=true \u65F6\u624D\u80FD\u79F0\u4E3A\u5168\u5C40\u7EDD\u5BF9\u6392\u884C\uFF1B\u7F3A\u5C11\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u3001\u79C1\u804A\u5BF9\u8C61\u6B67\u4E49\u3001\u4F1A\u8BDD\u5931\u8D25\u6216 time_budget \u622A\u65AD\u90FD\u5FC5\u987B\u660E\u786E\u62AB\u9732\u3002\u8BE5\u5DE5\u5177\u4E0D\u8FD4\u56DE\u804A\u5929\u6B63\u6587\uFF0C\u7528\u6237\u7EE7\u7EED\u8FFD\u95EE\u201C\u4E3A\u4EC0\u4E48\u201D\u65F6\u518D\u89E3\u6790\u5BF9\u5E94\u8054\u7CFB\u4EBA\u5E76\u8BFB\u53D6\u5FC5\u8981\u539F\u6587\u3002
|
|
16061
|
+
- rank_group_sessions \u7684 owner_activity \u53EA\u6309\u672C\u4EBA\u6D88\u606F\u91CF\u6392\u5E8F\uFF0C\u7F3A\u5C11\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u7684\u7FA4\u662F coverage \u7F3A\u53E3\uFF1Btotal_activity \u6309\u7FA4\u603B\u6D88\u606F\u91CF\u6392\u5E8F\uFF0C\u4E0D\u8981\u6C42 owner \u53EF\u89E3\u6790\u3002\u4E24\u4E2A mode \u90FD\u9ED8\u8BA4\u6392\u9664\u7528\u6237\u6807\u8BB0\u201C\u5F53\u524D\u5BF9\u8BDD\u4E2D\u6CA1\u6709\u6211\u201D\u7684\u4F1A\u8BDD\uFF0C\u5E76\u4E14\u53EA\u6709 coverage.complete=true \u65F6\u624D\u80FD\u79F0\u4E3A\u5B8C\u6574\u5168\u5C40\u6392\u884C\u3002
|
|
16062
|
+
- get_global_activity_summary \u590D\u7528\u672C\u5730\u5E74\u5EA6\u6982\u89C8\u5FEB\u7167\uFF0C\u4E0D\u8BFB\u53D6\u804A\u5929\u6B63\u6587\u3002dataState=fresh \u53EF\u76F4\u63A5\u4F7F\u7528\uFF1Bstale \u53EF\u4F7F\u7528\u4F46\u5FC5\u987B\u62AB\u9732 cache.computedAt\uFF0C\u5E76\u6839\u636E task.status \u8BF4\u660E\u540E\u53F0\u6B63\u5728\u66F4\u65B0\u6216\u5237\u65B0\u5931\u8D25\uFF1Bpreparing \u8868\u793A\u9996\u6B21\u7EDF\u8BA1\u4ECD\u5728\u540E\u53F0\u751F\u6210\uFF0Cfailed \u8868\u793A\u751F\u6210\u5931\u8D25\uFF0C\u4E24\u8005\u90FD\u4E0D\u662F\u96F6\u6570\u636E\u3002\u4E0D\u8981\u5728\u540C\u4E00\u6B21\u56DE\u7B54\u4E2D\u8F6E\u8BE2 preparing \u72B6\u6001\u3002
|
|
16063
|
+
- \u6CA1\u6709\u76F4\u63A5\u56DE\u590D\u6216\u539F\u6587\u8BC1\u636E\u65F6\uFF0C\u53EA\u80FD\u786E\u8BA4\u5171\u540C\u6765\u6E90\u6216\u540C\u573A\u6D3B\u8DC3\uFF0C\u5E76\u660E\u786E\u5177\u4F53\u5173\u7CFB\u4ECD\u4E0D\u786E\u5B9A\u3002\u53EA\u6709 coverage \u5B8C\u6574\u4E14\u65F6\u95F4\u8303\u56F4\u660E\u786E\uFF0C\u624D\u5141\u8BB8\u7ED9\u51FA\u201C\u6CA1\u6709\u53D1\u751F\u8FC7\u201D\u6216\u201C\u6CA1\u6709\u4E92\u52A8\u201D\u7684\u5F3A\u8D1F\u9762\u5224\u65AD\uFF1Bpartial/skipped_budget \u4E0D\u662F\u96F6\u3002
|
|
16064
|
+
- search_messages_globally \u63D0\u4F9B\u5173\u952E\u8BCD\u65F6\u662F\u5B57\u9762 LIKE \u68C0\u7D22\uFF0C\u4E0D\u662F\u8BED\u4E49\u641C\u7D22\u3002\u7528\u6237\u95EE\u201C\u804A\u8FC7\u4EC0\u4E48\u201D\u7B49\u5F00\u653E\u95EE\u9898\u65F6\uFF0C\u5148\u9650\u5B9A\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A scopes\uFF0C\u518D\u65E0\u5173\u952E\u8BCD\u62BD\u53D6\u6307\u5B9A\u65F6\u95F4\u8303\u56F4\u5185\u7684\u6D88\u606F\uFF1B\u5DE5\u5177\u81EA\u884C\u63A7\u5236\u8BC1\u636E\u91CF\uFF0C\u4F18\u5148\u8FD4\u56DE\u79C1\u804A\u8BC1\u636E\uFF0C\u5269\u4F59\u5BB9\u91CF\u6309\u76EE\u6807\u4EBA\u7269\u5728\u7FA4\u804A\u4E2D\u7684\u5339\u914D\u6D3B\u8DC3\u5EA6\u5206\u914D\uFF0C\u5E76\u81EA\u52A8\u9644\u5E26\u547D\u4E2D\u6D88\u606F\u5468\u56F4\u7684\u4E0A\u4E0B\u6587\u3002\u4E0D\u8981\u4F20\u5165\u6D88\u606F\u6570\u91CF\u3001\u4F1A\u8BDD\u6570\u91CF\u6216\u6267\u884C\u65F6\u957F\u9884\u7B97\uFF0C\u4E5F\u4E0D\u8981\u5BF9\u5168\u90E8\u4F1A\u8BDD\u505A\u65E0\u5173\u952E\u8BCD\u626B\u63CF\u3002
|
|
16065
|
+
- \u6BD4\u8F83\u5DF2\u7ECF\u89E3\u6790\u51FA\u7684\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A\u65F6\uFF0C\u7528 get_cross_chat_overview \u4F20\u5165\u4E0E\u95EE\u9898\u4E00\u81F4\u7684\u660E\u786E\u65F6\u95F4\u8303\u56F4\uFF0C\u8BFB\u53D6\u7CBE\u786E\u6D88\u606F\u6570\u3001\u6D3B\u8DC3\u5929\u6570\u3001\u6210\u5458\u6D3B\u52A8\u4E0E owner \u8986\u76D6\uFF1B\u5B83\u4E0D\u8D1F\u8D23\u53D1\u73B0\u6216\u5168\u5C40\u6392\u540D\u3002\u9700\u8981\u89E3\u91CA\u5DEE\u5F02\u65F6\uFF0C\u518D\u68C0\u7D22\u6709\u6765\u6E90\u7684\u539F\u6587\u8BC1\u636E\uFF1B\u9700\u8981\u7406\u89E3\u547D\u4E2D\u6D88\u606F\u65F6\uFF0C\u7528 session_id + message_id \u83B7\u53D6\u4E0A\u4E0B\u6587\u3002
|
|
16066
|
+
- \u5DE5\u5177\u8FD4\u56DE coverage \u548C truncated\u3002\u8986\u76D6\u4E0D\u5B8C\u6574\u6216\u88AB\u622A\u65AD\u65F6\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u62BD\u6837\u8303\u56F4\uFF0C\u7981\u6B62\u628A\u6837\u672C\u8868\u8FF0\u6210\u5168\u91CF\u7ED3\u8BBA\u3002
|
|
16067
|
+
- \u6D89\u53CA\u5355\u4E2A\u8054\u7CFB\u4EBA\u7684\u9650\u65F6\u641C\u7D22\u8FD4\u56DE 0 \u6761\u65F6\uFF0C\u4E0D\u8981\u53EA\u8BF4\u201C\u4EC0\u4E48\u90FD\u6CA1\u6709\u201D\u3002\u5FC5\u987B\u4ECE resolve_chat_entities \u8FD4\u56DE\u7684\u79C1\u804A session \u5143\u6570\u636E\u4E2D\u8BF4\u660E\u6700\u8FD1\u4E00\u6761\u5DF2\u5BFC\u5165\u79C1\u804A\u7684\u5177\u4F53\u65F6\u95F4\uFF0C\u4EE5\u53CA\u5B83\u662F\u5426\u843D\u5728\u67E5\u8BE2\u7A97\u53E3\u4E4B\u5916\uFF1B\u6CA1\u6709\u79C1\u804A session \u6216\u6CA1\u6709\u53EF\u7528\u65F6\u95F4\u65F6\u4E5F\u8981\u660E\u786E\u8BF4\u660E\u3002\u4E4B\u540E\u518D\u8BE2\u95EE\u7528\u6237\u662F\u5426\u6269\u5927\u8303\u56F4\u3002
|
|
16068
|
+
- sender=owner \u65F6\u8FD8\u8981\u68C0\u67E5 coverage.ownerResolution\uFF1B\u5B58\u5728\u672A\u8BBE\u7F6E\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u7684\u4F1A\u8BDD\u65F6\uFF0C\u5FC5\u987B\u8BF4\u660E\u5BF9\u5E94\u8986\u76D6\u7F3A\u53E3\uFF0C\u7981\u6B62\u901A\u8FC7\u6635\u79F0\u731C\u6D4B\u672C\u4EBA\u3002
|
|
16069
|
+
- \u5F15\u7528\u8BC1\u636E\u65F6\u8BF4\u660E\u6765\u6E90\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u6CC4\u9732\u5DE5\u5177\u672A\u8FD4\u56DE\u7684\u4FE1\u606F\uFF0C\u4E5F\u4E0D\u8981\u7F16\u9020\u8054\u7CFB\u4EBA\u3001\u7FA4\u804A\u6216\u6D88\u606F\u3002
|
|
16070
|
+
- \u5982\u679C\u95EE\u9898\u4E0D\u9700\u8981\u804A\u5929\u6570\u636E\uFF0C\u76F4\u63A5\u56DE\u7B54\uFF1B\u5982\u679C\u73B0\u6709\u5341\u4E09\u4E2A\u5DE5\u5177\u4E0D\u8DB3\uFF0C\u5766\u8BDA\u8BF4\u660E\u5F53\u524D\u80FD\u529B\u8FB9\u754C\u3002
|
|
16071
|
+
|
|
16072
|
+
\u957F\u671F\u8BB0\u5FC6\u89C4\u5219\uFF1A
|
|
16073
|
+
- \u53EA\u4FDD\u5B58\u8DE8 AI \u5BF9\u8BDD\u4ECD\u6709\u957F\u671F\u4EF7\u503C\u7684\u7528\u6237\u504F\u597D\u3001\u8EAB\u4EFD\u80CC\u666F\u3001\u660E\u786E\u7EA0\u6B63\uFF0C\u6216\u5DF2\u6709\u804A\u5929\u8BC1\u636E\u652F\u6301\u4E14\u4F1A\u5F71\u54CD\u540E\u7EED\u8C03\u67E5\u65B9\u5411\u7684\u7A33\u5B9A\u7ED3\u8BBA\u3002\u4E0D\u8981\u4FDD\u5B58\u672C\u8F6E\u4E34\u65F6\u8981\u6C42\u3001\u666E\u901A\u7EDF\u8BA1\u3001\u804A\u5929\u539F\u6587\u3001\u5DE5\u5177\u8FC7\u7A0B\u3001\u666E\u901A\u56DE\u7B54\u6216\u672A\u7ECF\u8BC1\u636E\u652F\u6301\u7684\u731C\u6D4B\u3002
|
|
16074
|
+
- \u5168\u5C40\u7528\u6237\u504F\u597D\u4F7F\u7528 scope_type=global\u3002\u5173\u4E8E\u7528\u6237\u672C\u4EBA\u7684\u7A33\u5B9A\u8EAB\u4EFD\u3001\u80CC\u666F\u3001\u957F\u671F\u76EE\u6807\u6216\u751F\u6D3B\u72B6\u6001\u4F7F\u7528 scope_type=self\uFF1B\u4E0E\u67D0\u4E2A\u7279\u5B9A\u8054\u7CFB\u4EBA\u7684\u5173\u7CFB\u4ECD\u5C5E\u4E8E\u8BE5 contact \u4F5C\u7528\u57DF\u3002
|
|
16075
|
+
- \u5173\u4E8E\u7528\u6237\u672C\u4EBA\u7684 self \u8BB0\u5FC6\u4E0D\u4F1A\u81EA\u52A8\u6CE8\u5165\u3002\u53EA\u6709\u5F53\u524D\u95EE\u9898\u4F9D\u8D56\u8FD9\u7C7B\u80CC\u666F\u4E14\u73B0\u6709\u5BF9\u8BDD\u4FE1\u606F\u4E0D\u8DB3\u65F6\uFF0C\u624D\u8C03\u7528 memory_read(scope_type=self, query=\u5F53\u524D\u95EE\u9898\u6216\u4E3B\u9898) \u6309\u9700\u8BFB\u53D6\uFF1B\u5199\u5165\u524D\u4E5F\u5148\u8BFB\u53D6\u540C\u4E00\u4F5C\u7528\u57DF\uFF0C\u907F\u514D\u91CD\u590D\u3002
|
|
16076
|
+
- \u672C\u8F6E\u9009\u62E9\u7684\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\u5DF2\u6309\u5F53\u524D\u7A33\u5B9A ID \u81EA\u52A8\u6CE8\u5165\uFF1B\u4F18\u5148\u4F7F\u7528\u8FD9\u4E9B\u8BB0\u5FC6\uFF0C\u4E0D\u5F97\u590D\u7528\u5386\u53F2\u6D88\u606F\u4E2D\u65E7\u5B9E\u4F53\u7684\u8BB0\u5FC6\u3002\u5F53\u524D\u5B9E\u4F53\u6CA1\u6709\u5DF2\u6CE8\u5165\u8BB0\u5FC6\u3001\u9700\u8981\u66F4\u591A\u8BB0\u5FC6\u6216\u5B9E\u4F53\u7531\u6587\u672C\u4E34\u65F6\u89E3\u6790\u5F97\u5230\u65F6\uFF0C\u518D\u8C03\u7528 memory_read\u3002
|
|
16077
|
+
- \u8C03\u7528 memory_read \u67E5\u627E\u4E0E\u672C\u8F6E\u95EE\u9898\u6709\u5173\u7684\u8BB0\u5FC6\u65F6\u4F20\u5165 query\uFF1B\u76F8\u5173\u6027\u53EA\u8C03\u6574\u6388\u6743\u4F5C\u7528\u57DF\u5185\u7684\u987A\u5E8F\uFF0C\u6CA1\u6709\u5339\u914D\u65F6\u5DE5\u5177\u4F1A\u7A33\u5B9A\u56DE\u9000\u5230\u6700\u8FD1\u66F4\u65B0\u987A\u5E8F\u3002
|
|
16078
|
+
- \u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\u5FC5\u987B\u4F7F\u7528\u7A33\u5B9A contactKey \u6216 group sessionId\u3002\u5199\u5165\u524D\u5148\u8C03\u7528 memory_read \u8BFB\u53D6\u540C\u4E00\u4F5C\u7528\u57DF\uFF0C\u5DF2\u6709\u76F8\u540C\u542B\u4E49\u65F6\u4E0D\u8981\u91CD\u590D\u521B\u5EFA\u3002
|
|
16079
|
+
- \u7528\u6237\u660E\u786E\u63D0\u4F9B\u3001\u8981\u6C42\u8BB0\u4F4F\u6216\u7EA0\u6B63\u7684\u5185\u5BB9\u4F7F\u7528 source_type=user\uFF1B\u4F60\u6839\u636E\u804A\u5929\u6570\u636E\u5F62\u6210\u7684\u7ED3\u8BBA\u4F7F\u7528 source_type=ai\uFF0C\u5E76\u5199\u6E05\u5FC5\u8981\u7684\u89C2\u5BDF\u65F6\u95F4\u548C\u4E0D\u786E\u5B9A\u6027\u3002
|
|
16080
|
+
- source_type=ai \u7684\u8BB0\u5FC6\u53EA\u662F\u8C03\u67E5\u7EBF\u7D22\uFF0C\u540E\u7EED\u4F7F\u7528\u65F6\u5FC5\u987B\u91CD\u65B0\u67E5\u8BE2\u5F53\u524D\u804A\u5929\u6570\u636E\u548C\u539F\u59CB\u804A\u5929\u8BC1\u636E\uFF0C\u4E0D\u80FD\u628A\u65E7\u7ED3\u8BBA\u5F53\u4F5C\u5F53\u524D\u4E8B\u5B9E\u3002
|
|
16081
|
+
- \u7528\u6237\u7EA0\u6B63\u65E7\u5185\u5BB9\u65F6\u6309 memory ID \u8C03\u7528 memory_write \u7CBE\u786E\u66F4\u65B0\uFF1B\u786E\u8BA4\u5931\u6548\u6216\u7528\u6237\u8981\u6C42\u5FD8\u8BB0\u65F6\u8C03\u7528 memory_forget\u3002\u6CA1\u6709\u771F\u6B63\u957F\u671F\u4EF7\u503C\u65F6\u4E0D\u8981\u8C03\u7528 memory_write\u3002
|
|
16082
|
+
${globalMemoryPrompt ? `
|
|
16083
|
+
\u5F53\u524D\u5DF2\u6CE8\u5165\u7684\u5168\u5C40\u7528\u6237\u504F\u597D\uFF1A
|
|
16084
|
+
${globalMemoryPrompt}` : ""}
|
|
16085
|
+
${entityMemoryPrompt ? `
|
|
16086
|
+
\u5F53\u524D\u5DF2\u6CE8\u5165\u7684\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\uFF1A
|
|
16087
|
+
${entityMemoryPrompt}` : ""}
|
|
16088
|
+
|
|
16089
|
+
\u4F60\u53EF\u4EE5\u4F7F\u7528\u5DE5\u5177\u3002\u5982\u679C\u9700\u8981\u4F60\u6CA1\u6709\u7684\u4FE1\u606F\uFF0C\u8BF7\u8C03\u7528\u63D0\u4F9B\u7684\u51FD\u6570\u3002`;
|
|
14318
16090
|
}
|
|
14319
|
-
return current
|
|
16091
|
+
return `You are ChatLab's cross-chat analysis assistant. You may query multiple contacts and group chats from the user's local chat databases as needed for the current question.
|
|
16092
|
+
|
|
16093
|
+
Data and scope rules:
|
|
16094
|
+
- The current date is ${currentDate} (time zone: ${timeZone}). Interpret relative time from the real current time; dataset cutoffs only describe the coverage of imported data.
|
|
16095
|
+
- The user authorizes access to all local chat data, but you must query only what is needed for the current question and never crawl without purpose.
|
|
16096
|
+
- <chatlab_entity_refs> contains stable references selected in the UI and should be passed directly to resolve_chat_entities. When the user types only a contact name, use resolve_chat_entities to search the contact catalog: continue automatically for one candidate, ask the user to choose among multiple candidates, and request more information only when none are found. Never guess among ambiguous candidates.
|
|
16097
|
+
- <chatlab_entity_refs> in the current user message is the latest selection for this turn. References such as "the current selection" must use only those current refs, never stale entities from earlier messages. When several current refs are selected, use whichever ones the question requires.
|
|
16098
|
+
- Entity references in history provide conversational context, not a permanently locked scope. Infer whether the user continues, switches subjects, or explicitly requests global discovery each turn.
|
|
16099
|
+
- A contact normally covers the private and group sessions they actually participate in. Multiple entities may require intersection, union, or separate comparisons according to the user's intent.
|
|
16100
|
+
- When the user asks \u201Cwhat have this person and I been talking about recently\u201D or clearly requests a dyadic private-chat recap, use only the resolved direct private session and do not mix in groups containing that contact. Without an explicit calendar range or duration, \u201Crecent\u201D means the latest available imported private conversation: after resolving one direct private session, call read_recent_session instead of filling the cross-chat search evidence budget. Use search_messages_globally only when the user gives dates or a duration, asks for earlier content, or requests deeper investigation. If no direct private session exists, say so before asking whether to search shared groups.
|
|
16101
|
+
- When the user asks which imported sessions contain one contact, how much that person spoke in each session, or where they were active, use inspect_contact_sessions first. Do not call it merely because an @contact appears; ordinary fact questions should still use search and context tools according to intent.
|
|
16102
|
+
- For relationship, shared-background, shared-circle, or interaction-change questions about two to five people, call inspect_shared_interactions first to find sessions containing everyone, per-person activity, replies, proximity signals, and message anchors. Do not mechanically call inspect_contact_sessions once per person first.
|
|
16103
|
+
- When the user asks who they privately chatted with most, requests a private-chat frequency ranking, or asks which contacts span the most private-chat active days, call rank_private_contacts directly with the requested calendar year, explicit dates, or recent_days. Never resolve the user as a contact, and never substitute keyword hits, sampled search evidence, or Contacts scores for the deterministic ranking.
|
|
16104
|
+
- Call rank_group_sessions with mode=owner_activity for \u201Cwhich groups was I most active in\u201D and mode=total_activity for \u201Cwhich groups were busiest or had the most messages.\u201D Never swap the two metrics. If the wording still does not establish whether the subject is the user or the whole group, ask one concise clarification.
|
|
16105
|
+
- For the user's total sent messages, active days, direct-contact count, monthly or daily trends, message types, or text-length distribution in a calendar year or the rolling latest 365 days, call get_global_activity_summary directly. It does not rank contacts or groups. For unsupported overall windows such as the last 30 days, state that this first version supports only calendar years and the latest 365 days; never estimate totals from keyword-search hits.
|
|
16106
|
+
- For three or more people, inspect_shared_interactions returns the strict intersection containing every participant. If the user wants several pairwise comparisons, inspect each pair separately rather than mixing sessions that contain only part of the cohort into the all-participant result.
|
|
16107
|
+
- Call search_messages_globally without scopes only for explicit global discovery such as "I forgot who I discussed this with". Global discovery always requires at least one keyword; scoped searches may omit keywords to sample recent messages.
|
|
16108
|
+
- Except for the dyadic private-chat recap above, when the user says "recent" or "recently" without a specific range, the first time-ranged search or structural-inspection call must pass recent_days=30 and the answer must state that it covers the last 30 days. Never calculate start_time from a dataset cutoff or lastMessageTs, and never scan all history first and apply a subjective cutoff afterward. Ask before widening only when the 30-day search finds nothing.
|
|
16109
|
+
- When the user is the subject of the event, such as "who did I discuss my home purchase with", the first search must pass sender=owner and treat the owner's messages as discovery seeds. Search automatically includes surrounding messages to identify actual participants; call get_cross_chat_message_context only when deeper source text is needed. Surrounding context is not owner-only.
|
|
16110
|
+
|
|
16111
|
+
Tool and conclusion rules:
|
|
16112
|
+
- You only have resolve_chat_entities, read_recent_session, rank_private_contacts, rank_group_sessions, get_global_activity_summary, inspect_contact_sessions, inspect_shared_interactions, search_messages_globally, get_cross_chat_message_context, get_cross_chat_overview, memory_read, memory_write, and memory_forget. Do not claim access to session SQL, semantic search, skills, charts, or heatmaps.
|
|
16113
|
+
- read_recent_session is the lightweight recent-recap path for one resolved session. It internally reads a small newest message slice and up to five existing segment summaries; the model cannot set message budgets. Do not expand merely because selection.hasEarlierMessages is true. Answer the ordinary recent recap first, then use search_messages_globally only for explicit dates, keywords, earlier content, or deeper investigation.
|
|
16114
|
+
- inspect_contact_sessions returns the contact's own activity and imported member-roster facts, not message text or every real-world chat. roster_only only means that the imported member table records the person. Never describe a paged or incomplete result as exhaustive.
|
|
16115
|
+
- Group names, member structure, co-active days, and proximity from inspect_shared_interactions are investigation signals, not proof of colleague, classmate, family, friend, or intimate relationships. Expand direct_reply and proximity anchors with get_cross_chat_message_context; a distant reply target may require separately opening relatedMessageId.
|
|
16116
|
+
- rank_private_contacts returns deterministic private-chat counts, owner/contact splits, active days, and identity coverage after scanning all eligible candidates. Describe it as a global absolute ranking only when coverage.complete=true. Disclose missing or unresolved owners, ambiguous private contacts, failed sessions, and time-budget truncation. It returns no message text; resolve the relevant contact and read only the needed evidence if the user asks why.
|
|
16117
|
+
- rank_group_sessions owner_activity ranks only by the user's own message count and treats missing or unresolved owners as coverage gaps. total_activity ranks by total group messages and does not require owner resolution. Both modes exclude sessions marked as not containing the user, and only coverage.complete=true supports an exhaustive global-ranking claim.
|
|
16118
|
+
- get_global_activity_summary reuses the local annual-insight snapshot and never reads message text. fresh data is ready; stale data is usable only with cache.computedAt disclosed, while task.status says whether refresh is running or failed. preparing means the first snapshot is still being built, and failed means generation failed; neither state means zero activity. Never poll a preparing result within the same answer.
|
|
16119
|
+
- Without direct replies or source text, state only shared-source or co-activity facts and keep the relationship uncertain. Strong negative claims require complete coverage and an explicit time range. partial or skipped_budget never means zero.
|
|
16120
|
+
- Search with keywords is literal LIKE matching, not semantic retrieval. For open-ended questions such as "what did we discuss", first resolve contact or session scopes, then sample messages inside the requested time range without keywords. The tool controls evidence volume, prioritizes private-chat evidence, allocates remaining capacity to groups by the target person's matching activity, and automatically includes surrounding context. Do not pass message-count, session-count, or execution-time budgets, and never scan every session without keywords.
|
|
16121
|
+
- For comparisons across already resolved contacts or sessions, call get_cross_chat_overview with the exact requested time range and use its deterministic message counts, active days, member activity, and owner coverage. It does not discover or rank sessions. Retrieve source-backed message evidence only when the difference needs explanation, and expand a hit with session_id plus message_id when context is needed.
|
|
16122
|
+
- Respect coverage and truncated fields. State incomplete coverage or sampling explicitly and never present a sample as exhaustive.
|
|
16123
|
+
- When a time-bounded search about one contact returns zero messages, do not merely say that nothing was found. From the direct private session metadata returned by resolve_chat_entities, state the latest imported private-chat timestamp as an exact date and whether it falls outside the requested window. Explicitly say when no private session or timestamp is available, then ask before widening the range.
|
|
16124
|
+
- For sender=owner, inspect coverage.ownerResolution and disclose sessions where the owner is missing or unresolved. Never guess the owner from display names.
|
|
16125
|
+
- Name the source session when citing evidence. Never invent people, sessions, or messages.
|
|
16126
|
+
- Answer directly when no chat data is needed. If the thirteen tools are insufficient, explain the current limitation honestly.
|
|
16127
|
+
|
|
16128
|
+
Long-term memory rules:
|
|
16129
|
+
- Save only preferences, identity/background facts, explicit corrections, or evidence-backed stable conclusions that remain useful across AI conversations. Never save temporary instructions, ordinary statistics, transcripts, tool traces, ordinary answers, or unsupported guesses.
|
|
16130
|
+
- Use scope_type=global for global user preferences. Store stable facts about the user's identity, background, long-term goals, or life situation with scope_type=self; a relationship with one specific contact still belongs to that contact scope.
|
|
16131
|
+
- Self memory is not automatically injected. Call memory_read(scope_type=self, query=current question or topic) on demand only when the current question depends on that background and the conversation does not already provide it; read the same scope before writing to avoid duplicates.
|
|
16132
|
+
- Memories keyed by the current stable IDs are automatically injected for the selected contacts and groups. Prefer those memories and never reuse memories for stale entities from history. If the current entity has no injected memory, you need more entries, or the entity was resolved from plain text during this turn, call memory_read.
|
|
16133
|
+
- Pass query when memory_read should find memories related to the current question. Relevance only reorders entries inside the authorized scope; when nothing matches, the tool deterministically falls back to recently updated entries.
|
|
16134
|
+
- Entity memory must use a stable contactKey or group sessionId. Call memory_read for the same scope before writing, and do not create a duplicate with the same meaning.
|
|
16135
|
+
- Use source_type=user only for facts the user explicitly provides, asks to remember, or corrects. Use source_type=ai for conclusions derived from chat data, including the observation period and uncertainty.
|
|
16136
|
+
- Treat source_type=ai memory only as an investigation lead. Re-query current chat data and original chat evidence before using it as a fact.
|
|
16137
|
+
- Update a corrected memory by stable memory ID with memory_write. Call memory_forget when the user asks to forget it or confirms it is invalid. Do not call memory_write when nothing has genuine long-term value.
|
|
16138
|
+
${globalMemoryPrompt ? `
|
|
16139
|
+
Injected global user preferences:
|
|
16140
|
+
${globalMemoryPrompt}` : ""}
|
|
16141
|
+
${entityMemoryPrompt ? `
|
|
16142
|
+
Injected memories for the current contacts and groups:
|
|
16143
|
+
${entityMemoryPrompt}` : ""}
|
|
16144
|
+
|
|
16145
|
+
You have access to tools. If you need information you don't have, use the provided functions.`;
|
|
14320
16146
|
}
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
16147
|
+
|
|
16148
|
+
// ../../packages/node-runtime/src/ai/agent/prompt-builder.ts
|
|
16149
|
+
function agentT(t2, key, locale, options) {
|
|
16150
|
+
return t2(key, { lng: locale, ...options });
|
|
16151
|
+
}
|
|
16152
|
+
function formatNullableTimestamp2(timestamp) {
|
|
16153
|
+
return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
|
|
16154
|
+
}
|
|
16155
|
+
function formatSharePercent2(share) {
|
|
16156
|
+
if (!Number.isFinite(share)) return "0%";
|
|
16157
|
+
const rounded = Math.round(share * 10) / 10;
|
|
16158
|
+
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
|
|
16159
|
+
}
|
|
16160
|
+
function normalizeInlineText(text) {
|
|
16161
|
+
return text.replace(/\s+/g, " ").trim();
|
|
16162
|
+
}
|
|
16163
|
+
function formatDataSnapshotContext(t2, dataSnapshot, locale) {
|
|
16164
|
+
const memberHints = dataSnapshot.activeMemberHints ?? [];
|
|
16165
|
+
const memberHintTitle = memberHints.length === 0 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsUnavailable", locale) : dataSnapshot.totalMembers <= 10 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsAll", locale) : agentT(t2, "ai.agent.dataSnapshotMemberHintsTop", locale);
|
|
16166
|
+
const memberHintLines = memberHints.length > 0 ? memberHints.map((member, index) => {
|
|
16167
|
+
return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent2(member.share)}`;
|
|
16168
|
+
}).join("\n") : agentT(t2, "ai.agent.dataSnapshotMemberHintsEmpty", locale);
|
|
16169
|
+
return agentT(t2, "ai.agent.dataSnapshotContext", locale, {
|
|
16170
|
+
name: dataSnapshot.name,
|
|
16171
|
+
platform: dataSnapshot.platform,
|
|
16172
|
+
type: dataSnapshot.type,
|
|
16173
|
+
totalMessages: dataSnapshot.totalMessages,
|
|
16174
|
+
totalMembers: dataSnapshot.totalMembers,
|
|
16175
|
+
firstMessageTs: formatNullableTimestamp2(dataSnapshot.firstMessageTs),
|
|
16176
|
+
firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
|
16177
|
+
lastMessageTs: formatNullableTimestamp2(dataSnapshot.lastMessageTs),
|
|
16178
|
+
lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
|
16179
|
+
segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
|
|
16180
|
+
memberHintTitle,
|
|
16181
|
+
memberHintLines,
|
|
16182
|
+
usageRules: agentT(t2, "ai.agent.dataSnapshotUsageRules", locale)
|
|
14325
16183
|
});
|
|
14326
16184
|
}
|
|
14327
|
-
function
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
};
|
|
16185
|
+
function getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot) {
|
|
16186
|
+
const now = /* @__PURE__ */ new Date();
|
|
16187
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
16188
|
+
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
16189
|
+
year: "numeric",
|
|
16190
|
+
month: "long",
|
|
16191
|
+
day: "numeric",
|
|
16192
|
+
weekday: "long"
|
|
16193
|
+
});
|
|
16194
|
+
const isPrivate = chatType === "private";
|
|
16195
|
+
const chatContext = agentT(t2, `ai.agent.chatContext.${chatType}`, locale);
|
|
16196
|
+
const ownerNote = ownerInfo ? agentT(t2, "ai.agent.ownerNote", locale, {
|
|
16197
|
+
displayName: ownerInfo.displayName,
|
|
16198
|
+
platformId: ownerInfo.platformId,
|
|
16199
|
+
chatContext
|
|
16200
|
+
}) : "";
|
|
16201
|
+
const memberNote = isPrivate ? agentT(t2, "ai.agent.memberNotePrivate", locale) : agentT(t2, "ai.agent.memberNoteGroup", locale);
|
|
16202
|
+
const mentionedMembersNote = mentionedMembers && mentionedMembers.length > 0 ? `${agentT(t2, "ai.agent.mentionedMembersNote", locale)}
|
|
16203
|
+
${mentionedMembers.map((member) => {
|
|
16204
|
+
const aliasPart = member.aliases.length > 0 ? ` | aliases=${member.aliases.join(",")}` : "";
|
|
16205
|
+
return `- member_id=${member.memberId} | mention=${member.mentionText} | display_name=${member.displayName} | platform_id=${member.platformId}${aliasPart}`;
|
|
16206
|
+
}).join("\n")}
|
|
16207
|
+
` : "";
|
|
16208
|
+
const year = now.getFullYear();
|
|
16209
|
+
const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t2, dataSnapshot, locale)}
|
|
16210
|
+
` : "";
|
|
16211
|
+
return `${agentT(t2, "ai.agent.currentDateIs", locale)} ${currentDate}\u3002
|
|
16212
|
+
${ownerNote}
|
|
16213
|
+
${mentionedMembersNote}
|
|
16214
|
+
${dataSnapshotNote}
|
|
16215
|
+
${memberNote}
|
|
16216
|
+
${agentT(t2, "ai.agent.timeParamsIntro", locale)}
|
|
16217
|
+
${agentT(t2, "ai.agent.defaultYearNote", locale, { year })}
|
|
16218
|
+
${agentT(t2, "ai.agent.evidencePolicy", locale)}
|
|
16219
|
+
|
|
16220
|
+
${agentT(t2, "ai.agent.responseInstruction", locale)}`;
|
|
16221
|
+
}
|
|
16222
|
+
function getFallbackRoleDefinition(t2, chatType, locale) {
|
|
16223
|
+
return agentT(t2, `ai.agent.fallbackRoleDefinition.${chatType}`, locale);
|
|
16224
|
+
}
|
|
16225
|
+
function formatTimestamp(timestamp, locale) {
|
|
16226
|
+
if (!timestamp) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
16227
|
+
const date = new Date(timestamp * 1e3);
|
|
16228
|
+
if (Number.isNaN(date.getTime())) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
16229
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : locale;
|
|
16230
|
+
return date.toLocaleString(dateLocale, {
|
|
16231
|
+
year: "numeric",
|
|
16232
|
+
month: "2-digit",
|
|
16233
|
+
day: "2-digit",
|
|
16234
|
+
hour: "2-digit",
|
|
16235
|
+
minute: "2-digit"
|
|
16236
|
+
});
|
|
16237
|
+
}
|
|
16238
|
+
function buildSystemPrompt(options) {
|
|
16239
|
+
const {
|
|
16240
|
+
t: t2,
|
|
16241
|
+
chatType = "group",
|
|
16242
|
+
assistantSystemPrompt,
|
|
16243
|
+
ownerInfo,
|
|
16244
|
+
locale = "zh-CN",
|
|
16245
|
+
skillCtx,
|
|
16246
|
+
mentionedMembers,
|
|
16247
|
+
dataSnapshot
|
|
16248
|
+
} = options;
|
|
16249
|
+
const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t2, chatType, locale);
|
|
16250
|
+
const lockedSection = getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot);
|
|
16251
|
+
let skillSection = "";
|
|
16252
|
+
if (skillCtx?.skillDef) {
|
|
16253
|
+
skillSection = `
|
|
16254
|
+
## ${agentT(t2, "ai.agent.currentTask", locale)}\uFF1A${skillCtx.skillDef.name}
|
|
16255
|
+
${agentT(t2, "ai.agent.skillPriorityNote", locale)}
|
|
16256
|
+
` + skillCtx.skillDef.prompt;
|
|
16257
|
+
} else if (skillCtx?.skillMenu) {
|
|
16258
|
+
skillSection = `
|
|
16259
|
+
${skillCtx.skillMenu}`;
|
|
16260
|
+
}
|
|
16261
|
+
return `${systemPrompt}${skillSection}
|
|
16262
|
+
|
|
16263
|
+
${lockedSection}`;
|
|
14336
16264
|
}
|
|
14337
|
-
var aiLocales = { "zh-CN": zh_CN_default, "en-US": en_US_default, "ja-JP": ja_JP_default, "zh-TW": zh_TW_default };
|
|
14338
16265
|
|
|
14339
16266
|
// ../../packages/node-runtime/src/ai/summary/index.ts
|
|
14340
16267
|
var MIN_MESSAGE_COUNT = 3;
|
|
@@ -14590,8 +16517,8 @@ async function generateSessionSummary(deps, segmentId, options = {}) {
|
|
|
14590
16517
|
const segments = splitIntoSegments(validMessages, MAX_CONTENT_PER_CALL);
|
|
14591
16518
|
log?.info("Summary", `Long session segmented: ${segments.length} segments`);
|
|
14592
16519
|
const subSummaries = [];
|
|
14593
|
-
for (const
|
|
14594
|
-
const segContent = formatMessages(
|
|
16520
|
+
for (const segment2 of segments) {
|
|
16521
|
+
const segContent = formatMessages(segment2);
|
|
14595
16522
|
const sub = await deps.llmComplete(
|
|
14596
16523
|
deps.t("summary.systemPromptDirect"),
|
|
14597
16524
|
buildSubSummaryPrompt(segContent, locale, strategy),
|
|
@@ -14894,10 +16821,10 @@ var LLMConfigStore = class {
|
|
|
14894
16821
|
};
|
|
14895
16822
|
|
|
14896
16823
|
// ../../packages/node-runtime/src/ai/file-config-storage.ts
|
|
14897
|
-
import * as
|
|
16824
|
+
import * as path19 from "path";
|
|
14898
16825
|
function createFileConfigStorage(baseDir) {
|
|
14899
16826
|
const heldLocks = /* @__PURE__ */ new Set();
|
|
14900
|
-
const getFilePath = (key) =>
|
|
16827
|
+
const getFilePath = (key) => path19.join(baseDir, `${key}.json`);
|
|
14901
16828
|
return {
|
|
14902
16829
|
readJson(key) {
|
|
14903
16830
|
return readJsonFile(getFilePath(key));
|
|
@@ -14935,6 +16862,9 @@ function getAuthProfile2(config) {
|
|
|
14935
16862
|
function getNamedAuthProfile(config) {
|
|
14936
16863
|
return config.name?.toLowerCase().replace(/\s+/g, "-") || config.provider;
|
|
14937
16864
|
}
|
|
16865
|
+
function getConfigAuthProfile(config) {
|
|
16866
|
+
return `${getNamedAuthProfile(config)}-${config.id}`;
|
|
16867
|
+
}
|
|
14938
16868
|
function restoreLegacyAuthProfileReferences(configs, authProfiles) {
|
|
14939
16869
|
const usedProfiles = new Set(configs.map(getAuthProfile2).filter((name) => Boolean(name)));
|
|
14940
16870
|
for (const config of configs) {
|
|
@@ -14969,7 +16899,7 @@ function createAuthProfileLlmConfigStore(storage, deps = {}) {
|
|
|
14969
16899
|
},
|
|
14970
16900
|
resolveApiKey: resolvedDeps.resolveApiKey,
|
|
14971
16901
|
onApiKeyCreated: (config, apiKey) => {
|
|
14972
|
-
const profileName =
|
|
16902
|
+
const profileName = getConfigAuthProfile(config);
|
|
14973
16903
|
resolvedDeps.writeAuthProfile(profileName, { type: "api_key", provider: config.provider, key: apiKey });
|
|
14974
16904
|
return profileName;
|
|
14975
16905
|
},
|
|
@@ -15405,29 +17335,29 @@ async function runSimpleLlmStream(options) {
|
|
|
15405
17335
|
import { Type } from "@earendil-works/pi-ai";
|
|
15406
17336
|
|
|
15407
17337
|
// ../../packages/node-runtime/src/migrations/electron-data-migration.ts
|
|
15408
|
-
import * as
|
|
17338
|
+
import * as fs12 from "fs";
|
|
15409
17339
|
import * as os3 from "os";
|
|
15410
|
-
import * as
|
|
17340
|
+
import * as path20 from "path";
|
|
15411
17341
|
var SYSTEM_SUBDIRS = ["ai", "settings", "cache", "logs", "nlp"];
|
|
15412
17342
|
var STORAGE_CONFIG_FILE = "storage.json";
|
|
15413
17343
|
function getElectronUserDataDir(options = {}) {
|
|
15414
17344
|
const home = options.homeDir ?? os3.homedir();
|
|
15415
17345
|
switch (options.platform ?? process.platform) {
|
|
15416
17346
|
case "darwin":
|
|
15417
|
-
return
|
|
17347
|
+
return path20.join(home, "Library", "Application Support", "ChatLab");
|
|
15418
17348
|
case "win32": {
|
|
15419
|
-
const appData = options.appDataDir ?? process.env.APPDATA ??
|
|
15420
|
-
return
|
|
17349
|
+
const appData = options.appDataDir ?? process.env.APPDATA ?? path20.join(home, "AppData", "Roaming");
|
|
17350
|
+
return path20.join(appData, "ChatLab");
|
|
15421
17351
|
}
|
|
15422
17352
|
default:
|
|
15423
|
-
return
|
|
17353
|
+
return path20.join(home, ".config", "ChatLab");
|
|
15424
17354
|
}
|
|
15425
17355
|
}
|
|
15426
17356
|
function readElectronStorageConfig(electronUserData) {
|
|
15427
|
-
const configPath =
|
|
15428
|
-
if (!
|
|
17357
|
+
const configPath = path20.join(electronUserData, STORAGE_CONFIG_FILE);
|
|
17358
|
+
if (!fs12.existsSync(configPath)) return {};
|
|
15429
17359
|
try {
|
|
15430
|
-
return JSON.parse(
|
|
17360
|
+
return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
15431
17361
|
} catch {
|
|
15432
17362
|
return {};
|
|
15433
17363
|
}
|
|
@@ -15435,45 +17365,45 @@ function readElectronStorageConfig(electronUserData) {
|
|
|
15435
17365
|
function resolveElectronDataDir() {
|
|
15436
17366
|
const electronUserData = getElectronUserDataDir();
|
|
15437
17367
|
const storageConfig = readElectronStorageConfig(electronUserData);
|
|
15438
|
-
if (storageConfig.dataDir &&
|
|
17368
|
+
if (storageConfig.dataDir && path20.isAbsolute(storageConfig.dataDir)) {
|
|
15439
17369
|
return storageConfig.dataDir;
|
|
15440
17370
|
}
|
|
15441
|
-
return
|
|
17371
|
+
return path20.join(electronUserData, "data");
|
|
15442
17372
|
}
|
|
15443
17373
|
function hasDatabases(dir) {
|
|
15444
|
-
const dbDir =
|
|
15445
|
-
if (!
|
|
17374
|
+
const dbDir = path20.join(dir, "databases");
|
|
17375
|
+
if (!fs12.existsSync(dbDir)) return false;
|
|
15446
17376
|
try {
|
|
15447
|
-
return
|
|
17377
|
+
return fs12.readdirSync(dbDir).some((f) => f.endsWith(".db"));
|
|
15448
17378
|
} catch {
|
|
15449
17379
|
return false;
|
|
15450
17380
|
}
|
|
15451
17381
|
}
|
|
15452
17382
|
function ensureDir2(dirPath) {
|
|
15453
|
-
if (!
|
|
15454
|
-
|
|
17383
|
+
if (!fs12.existsSync(dirPath)) {
|
|
17384
|
+
fs12.mkdirSync(dirPath, { recursive: true });
|
|
15455
17385
|
}
|
|
15456
17386
|
}
|
|
15457
17387
|
function copyDirMerge(src, dest) {
|
|
15458
17388
|
const stats = { copied: 0, skipped: 0 };
|
|
15459
|
-
if (!
|
|
17389
|
+
if (!fs12.existsSync(src)) return stats;
|
|
15460
17390
|
ensureDir2(dest);
|
|
15461
17391
|
let entries;
|
|
15462
17392
|
try {
|
|
15463
|
-
entries =
|
|
17393
|
+
entries = fs12.readdirSync(src, { withFileTypes: true });
|
|
15464
17394
|
} catch {
|
|
15465
17395
|
return stats;
|
|
15466
17396
|
}
|
|
15467
17397
|
for (const entry of entries) {
|
|
15468
|
-
const srcPath =
|
|
15469
|
-
const destPath =
|
|
17398
|
+
const srcPath = path20.join(src, entry.name);
|
|
17399
|
+
const destPath = path20.join(dest, entry.name);
|
|
15470
17400
|
try {
|
|
15471
17401
|
if (entry.isDirectory()) {
|
|
15472
17402
|
const sub = copyDirMerge(srcPath, destPath);
|
|
15473
17403
|
stats.copied += sub.copied;
|
|
15474
17404
|
stats.skipped += sub.skipped;
|
|
15475
|
-
} else if (!
|
|
15476
|
-
|
|
17405
|
+
} else if (!fs12.existsSync(destPath)) {
|
|
17406
|
+
fs12.copyFileSync(srcPath, destPath);
|
|
15477
17407
|
stats.copied++;
|
|
15478
17408
|
} else {
|
|
15479
17409
|
stats.skipped++;
|
|
@@ -15486,22 +17416,22 @@ function copyDirMerge(src, dest) {
|
|
|
15486
17416
|
function writeMigrationLog(logDir, message) {
|
|
15487
17417
|
try {
|
|
15488
17418
|
ensureDir2(logDir);
|
|
15489
|
-
const logPath =
|
|
17419
|
+
const logPath = path20.join(logDir, "app.log");
|
|
15490
17420
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
15491
|
-
|
|
17421
|
+
fs12.appendFileSync(logPath, `[${ts}] [MIGRATION] ${message}
|
|
15492
17422
|
`, "utf-8");
|
|
15493
17423
|
} catch {
|
|
15494
17424
|
}
|
|
15495
17425
|
}
|
|
15496
17426
|
function wasElectronUsed() {
|
|
15497
17427
|
const electronUserData = getElectronUserDataDir();
|
|
15498
|
-
if (!
|
|
15499
|
-
return
|
|
17428
|
+
if (!fs12.existsSync(electronUserData)) return false;
|
|
17429
|
+
return fs12.existsSync(path20.join(electronUserData, "Preferences")) || fs12.existsSync(path20.join(electronUserData, "Local Storage")) || fs12.existsSync(path20.join(electronUserData, "Session Storage")) || fs12.existsSync(path20.join(electronUserData, "data"));
|
|
15500
17430
|
}
|
|
15501
17431
|
function verifyCliDataPath(databaseDir) {
|
|
15502
|
-
if (
|
|
17432
|
+
if (fs12.existsSync(databaseDir)) {
|
|
15503
17433
|
try {
|
|
15504
|
-
if (
|
|
17434
|
+
if (fs12.readdirSync(databaseDir).some((f) => f.endsWith(".db"))) return true;
|
|
15505
17435
|
} catch {
|
|
15506
17436
|
}
|
|
15507
17437
|
}
|
|
@@ -15519,9 +17449,9 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15519
17449
|
let totalSkipped = 0;
|
|
15520
17450
|
const movedDirs = [];
|
|
15521
17451
|
for (const subDir of SYSTEM_SUBDIRS) {
|
|
15522
|
-
const srcDir =
|
|
15523
|
-
const destDir =
|
|
15524
|
-
if (!
|
|
17452
|
+
const srcDir = path20.join(electronDataDir, subDir);
|
|
17453
|
+
const destDir = path20.join(systemDir, subDir);
|
|
17454
|
+
if (!fs12.existsSync(srcDir)) continue;
|
|
15525
17455
|
const { copied, skipped } = copyDirMerge(srcDir, destDir);
|
|
15526
17456
|
if (copied > 0 || skipped > 0) {
|
|
15527
17457
|
movedDirs.push(subDir);
|
|
@@ -15531,13 +17461,13 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15531
17461
|
}
|
|
15532
17462
|
}
|
|
15533
17463
|
for (const subDir of movedDirs) {
|
|
15534
|
-
const srcDir =
|
|
17464
|
+
const srcDir = path20.join(electronDataDir, subDir);
|
|
15535
17465
|
try {
|
|
15536
|
-
|
|
17466
|
+
fs12.rmSync(srcDir, { recursive: true, force: true });
|
|
15537
17467
|
} catch {
|
|
15538
17468
|
}
|
|
15539
17469
|
}
|
|
15540
|
-
const logsDir =
|
|
17470
|
+
const logsDir = path20.join(systemDir, "logs");
|
|
15541
17471
|
const summary = `CLI first-run: detected Electron data at ${electronDataDir}, pointed user_data_dir there, copied ${totalCopied} system files (${movedDirs.join(", ") || "none"})`;
|
|
15542
17472
|
writeMigrationLog(logsDir, summary);
|
|
15543
17473
|
console.log(`[Migration] ${summary}`);
|
|
@@ -15551,7 +17481,7 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15551
17481
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
15552
17482
|
console.error("[Migration] Electron data migration failed:", errorMsg);
|
|
15553
17483
|
try {
|
|
15554
|
-
writeMigrationLog(
|
|
17484
|
+
writeMigrationLog(path20.join(systemDir, "logs"), `Electron migration failed: ${errorMsg}`);
|
|
15555
17485
|
} catch {
|
|
15556
17486
|
}
|
|
15557
17487
|
return { migrated: false, electronDetected: true, error: errorMsg };
|
|
@@ -15559,26 +17489,26 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15559
17489
|
}
|
|
15560
17490
|
|
|
15561
17491
|
// ../../packages/node-runtime/src/cache/session-cache.ts
|
|
15562
|
-
import * as
|
|
15563
|
-
import * as
|
|
17492
|
+
import * as fs13 from "fs";
|
|
17493
|
+
import * as path21 from "path";
|
|
15564
17494
|
function getCachePath(sessionId, cacheDir) {
|
|
15565
|
-
return
|
|
17495
|
+
return path21.join(cacheDir, `${sessionId}.cache.json`);
|
|
15566
17496
|
}
|
|
15567
17497
|
function readCacheFile(cachePath) {
|
|
15568
17498
|
try {
|
|
15569
|
-
if (!
|
|
15570
|
-
return JSON.parse(
|
|
17499
|
+
if (!fs13.existsSync(cachePath)) return null;
|
|
17500
|
+
return JSON.parse(fs13.readFileSync(cachePath, "utf-8"));
|
|
15571
17501
|
} catch {
|
|
15572
17502
|
return null;
|
|
15573
17503
|
}
|
|
15574
17504
|
}
|
|
15575
17505
|
function writeCacheFile(cachePath, content) {
|
|
15576
17506
|
try {
|
|
15577
|
-
const dir =
|
|
15578
|
-
if (!
|
|
15579
|
-
|
|
17507
|
+
const dir = path21.dirname(cachePath);
|
|
17508
|
+
if (!fs13.existsSync(dir)) {
|
|
17509
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
15580
17510
|
}
|
|
15581
|
-
|
|
17511
|
+
fs13.writeFileSync(cachePath, JSON.stringify(content), "utf-8");
|
|
15582
17512
|
} catch {
|
|
15583
17513
|
}
|
|
15584
17514
|
}
|
|
@@ -15598,7 +17528,7 @@ function invalidateCache(sessionId, cacheDir, key) {
|
|
|
15598
17528
|
const cachePath = getCachePath(sessionId, cacheDir);
|
|
15599
17529
|
try {
|
|
15600
17530
|
if (!key) {
|
|
15601
|
-
if (
|
|
17531
|
+
if (fs13.existsSync(cachePath)) fs13.unlinkSync(cachePath);
|
|
15602
17532
|
} else {
|
|
15603
17533
|
const file = readCacheFile(cachePath);
|
|
15604
17534
|
if (file && file[key]) {
|
|
@@ -15612,7 +17542,7 @@ function invalidateCache(sessionId, cacheDir, key) {
|
|
|
15612
17542
|
function deleteSessionCache(sessionId, cacheDir) {
|
|
15613
17543
|
const cachePath = getCachePath(sessionId, cacheDir);
|
|
15614
17544
|
try {
|
|
15615
|
-
if (
|
|
17545
|
+
if (fs13.existsSync(cachePath)) fs13.unlinkSync(cachePath);
|
|
15616
17546
|
} catch {
|
|
15617
17547
|
}
|
|
15618
17548
|
}
|
|
@@ -15666,8 +17596,8 @@ function computeAndSetMembersCache(db, sessionId, cacheDir) {
|
|
|
15666
17596
|
}
|
|
15667
17597
|
|
|
15668
17598
|
// ../../packages/node-runtime/src/data-dir-compat.ts
|
|
15669
|
-
import
|
|
15670
|
-
import
|
|
17599
|
+
import fs14 from "fs";
|
|
17600
|
+
import path22 from "path";
|
|
15671
17601
|
var DataDirCompatibilityError = class extends Error {
|
|
15672
17602
|
code;
|
|
15673
17603
|
userDataDir;
|
|
@@ -15687,10 +17617,10 @@ var DataDirCompatibilityError = class extends Error {
|
|
|
15687
17617
|
};
|
|
15688
17618
|
function readDataDirCompatibilityMeta(userDataDir) {
|
|
15689
17619
|
const metaPath = getMetaPath(userDataDir);
|
|
15690
|
-
if (!
|
|
17620
|
+
if (!fs14.existsSync(metaPath)) return null;
|
|
15691
17621
|
let parsed;
|
|
15692
17622
|
try {
|
|
15693
|
-
parsed = JSON.parse(
|
|
17623
|
+
parsed = JSON.parse(fs14.readFileSync(metaPath, "utf-8"));
|
|
15694
17624
|
} catch (error) {
|
|
15695
17625
|
throw createInvalidMetaError(userDataDir, metaPath, "Invalid data directory compatibility meta JSON.", error);
|
|
15696
17626
|
}
|
|
@@ -15778,7 +17708,7 @@ function raiseDataDirMinRuntimeVersion(pathProvider, input) {
|
|
|
15778
17708
|
return nextMeta;
|
|
15779
17709
|
}
|
|
15780
17710
|
function getMetaPath(userDataDir) {
|
|
15781
|
-
return
|
|
17711
|
+
return path22.join(userDataDir, ".chatlab-meta.json");
|
|
15782
17712
|
}
|
|
15783
17713
|
function validateDataDirCompatibilityMeta(value, userDataDir, metaPath) {
|
|
15784
17714
|
if (!isRecord3(value)) {
|
|
@@ -15849,15 +17779,15 @@ function mergeReasons(existing, next) {
|
|
|
15849
17779
|
return [.../* @__PURE__ */ new Set([...existing, next])];
|
|
15850
17780
|
}
|
|
15851
17781
|
function writeMetaAtomic(userDataDir, metaPath, meta) {
|
|
15852
|
-
|
|
15853
|
-
const tempPath =
|
|
17782
|
+
fs14.mkdirSync(userDataDir, { recursive: true });
|
|
17783
|
+
const tempPath = path22.join(userDataDir, `.chatlab-meta.json.${process.pid}.${Date.now()}.tmp`);
|
|
15854
17784
|
try {
|
|
15855
|
-
|
|
17785
|
+
fs14.writeFileSync(tempPath, `${JSON.stringify(meta, null, 2)}
|
|
15856
17786
|
`, "utf-8");
|
|
15857
|
-
|
|
17787
|
+
fs14.renameSync(tempPath, metaPath);
|
|
15858
17788
|
} catch (error) {
|
|
15859
17789
|
try {
|
|
15860
|
-
if (
|
|
17790
|
+
if (fs14.existsSync(tempPath)) fs14.unlinkSync(tempPath);
|
|
15861
17791
|
} catch {
|
|
15862
17792
|
}
|
|
15863
17793
|
throw error;
|
|
@@ -16186,51 +18116,51 @@ function getChatDbMigrations() {
|
|
|
16186
18116
|
}
|
|
16187
18117
|
|
|
16188
18118
|
// ../../packages/node-runtime/src/services/contacts/paths.ts
|
|
16189
|
-
import
|
|
18119
|
+
import path23 from "path";
|
|
16190
18120
|
var CONTACTS_DIR_NAME = "contacts";
|
|
16191
18121
|
var CONTACTS_FACTS_DIR_NAME = "facts";
|
|
16192
18122
|
function getContactsDir(userDataDir) {
|
|
16193
|
-
return
|
|
18123
|
+
return path23.join(userDataDir, CONTACTS_DIR_NAME);
|
|
16194
18124
|
}
|
|
16195
18125
|
function getContactsFactsCacheDir(userDataDir) {
|
|
16196
|
-
return
|
|
18126
|
+
return path23.join(getContactsDir(userDataDir), CONTACTS_FACTS_DIR_NAME);
|
|
16197
18127
|
}
|
|
16198
18128
|
|
|
16199
18129
|
// ../../packages/node-runtime/src/services/global-insight/paths.ts
|
|
16200
|
-
import
|
|
18130
|
+
import path24 from "path";
|
|
16201
18131
|
function getGlobalInsightDir(userDataDir) {
|
|
16202
|
-
return
|
|
18132
|
+
return path24.join(userDataDir, "insight", "annual-summary");
|
|
16203
18133
|
}
|
|
16204
18134
|
function getGlobalInsightFactsCacheDir(userDataDir) {
|
|
16205
|
-
return
|
|
18135
|
+
return path24.join(getGlobalInsightDir(userDataDir), "facts");
|
|
16206
18136
|
}
|
|
16207
18137
|
function getTimeInvestmentDir(userDataDir) {
|
|
16208
|
-
return
|
|
18138
|
+
return path24.join(userDataDir, "insight", "time-investment");
|
|
16209
18139
|
}
|
|
16210
18140
|
function getTimeInvestmentFactsCacheDir(userDataDir) {
|
|
16211
|
-
return
|
|
18141
|
+
return path24.join(getTimeInvestmentDir(userDataDir), "facts");
|
|
16212
18142
|
}
|
|
16213
18143
|
|
|
16214
18144
|
// ../../packages/node-runtime/src/services/people/relationships/paths.ts
|
|
16215
|
-
import
|
|
18145
|
+
import path25 from "path";
|
|
16216
18146
|
var PEOPLE_DIR_NAME = "people";
|
|
16217
18147
|
var RELATIONSHIPS_DIR_NAME = "relationships";
|
|
16218
18148
|
var RELATIONSHIPS_FACTS_DIR_NAME = "facts";
|
|
16219
18149
|
function getPeopleRelationshipsDir(userDataDir) {
|
|
16220
|
-
return
|
|
18150
|
+
return path25.join(userDataDir, PEOPLE_DIR_NAME, RELATIONSHIPS_DIR_NAME);
|
|
16221
18151
|
}
|
|
16222
18152
|
function getPeopleRelationshipsFactsCacheDir(userDataDir) {
|
|
16223
|
-
return
|
|
18153
|
+
return path25.join(getPeopleRelationshipsDir(userDataDir), RELATIONSHIPS_FACTS_DIR_NAME);
|
|
16224
18154
|
}
|
|
16225
18155
|
|
|
16226
18156
|
// ../../packages/node-runtime/src/services/topics/paths.ts
|
|
16227
|
-
import
|
|
18157
|
+
import path26 from "path";
|
|
16228
18158
|
var CHAT_TOPICS_DB_FILENAME = "topics.db";
|
|
16229
18159
|
function getChatTopicsDir(userDataDir) {
|
|
16230
|
-
return
|
|
18160
|
+
return path26.join(userDataDir, "insight");
|
|
16231
18161
|
}
|
|
16232
18162
|
function getChatTopicsDbPath(userDataDir) {
|
|
16233
|
-
return
|
|
18163
|
+
return path26.join(getChatTopicsDir(userDataDir), CHAT_TOPICS_DB_FILENAME);
|
|
16234
18164
|
}
|
|
16235
18165
|
|
|
16236
18166
|
// ../../packages/node-runtime/src/services/topics/model-client.ts
|
|
@@ -16418,13 +18348,13 @@ function getZonedDateTimeParts(timestampMs, timezone) {
|
|
|
16418
18348
|
}
|
|
16419
18349
|
|
|
16420
18350
|
// ../../packages/node-runtime/src/services/topics/store.ts
|
|
16421
|
-
import
|
|
16422
|
-
import
|
|
18351
|
+
import fs15 from "fs";
|
|
18352
|
+
import path27 from "path";
|
|
16423
18353
|
var CHAT_TOPICS_SCHEMA_VERSION = 3;
|
|
16424
18354
|
var ChatTopicStore = class {
|
|
16425
18355
|
db;
|
|
16426
18356
|
constructor(dbPath, options = {}) {
|
|
16427
|
-
|
|
18357
|
+
fs15.mkdirSync(path27.dirname(dbPath), { recursive: true });
|
|
16428
18358
|
this.db = openBetterSqliteDatabase(dbPath, { nativeBinding: options.nativeBinding });
|
|
16429
18359
|
this.db.pragma("foreign_keys = ON");
|
|
16430
18360
|
this.initialize();
|
|
@@ -17020,7 +18950,7 @@ var ChatTopicStore = class {
|
|
|
17020
18950
|
};
|
|
17021
18951
|
function deleteSessionChatTopics(userDataDir, sessionId, options = {}) {
|
|
17022
18952
|
const dbPath = getChatTopicsDbPath(userDataDir);
|
|
17023
|
-
if (!
|
|
18953
|
+
if (!fs15.existsSync(dbPath)) return false;
|
|
17024
18954
|
const store = new ChatTopicStore(dbPath, options);
|
|
17025
18955
|
try {
|
|
17026
18956
|
return store.deleteSession(sessionId);
|
|
@@ -17030,7 +18960,7 @@ function deleteSessionChatTopics(userDataDir, sessionId, options = {}) {
|
|
|
17030
18960
|
}
|
|
17031
18961
|
function assertSessionChatTopicsIdle(userDataDir, sessionId, options = {}) {
|
|
17032
18962
|
const dbPath = getChatTopicsDbPath(userDataDir);
|
|
17033
|
-
if (!
|
|
18963
|
+
if (!fs15.existsSync(dbPath)) return;
|
|
17034
18964
|
const store = new ChatTopicStore(dbPath, options);
|
|
17035
18965
|
try {
|
|
17036
18966
|
const timestamp = Date.now();
|
|
@@ -17098,7 +19028,7 @@ var ChatTopicWorkCoordinator = class {
|
|
|
17098
19028
|
var chatTopicWorkCoordinator = new ChatTopicWorkCoordinator();
|
|
17099
19029
|
|
|
17100
19030
|
// ../../packages/node-runtime/src/services/topics/service.ts
|
|
17101
|
-
import { randomUUID as
|
|
19031
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
17102
19032
|
|
|
17103
19033
|
// ../../packages/node-runtime/src/services/topics/ledger.ts
|
|
17104
19034
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -17736,8 +19666,8 @@ var CHAT_TOPICS_MIN_RUNTIME_VERSION = "0.35.1";
|
|
|
17736
19666
|
var CHAT_TOPICS_DATA_COMPATIBILITY_VERSION = 2;
|
|
17737
19667
|
function createChatTopicService(deps) {
|
|
17738
19668
|
const now = deps.now ?? Date.now;
|
|
17739
|
-
const generateId = deps.generateId ??
|
|
17740
|
-
const runtimeId = deps.runtimeId ??
|
|
19669
|
+
const generateId = deps.generateId ?? randomUUID4;
|
|
19670
|
+
const runtimeId = deps.runtimeId ?? randomUUID4();
|
|
17741
19671
|
const store = new ChatTopicStore(getChatTopicsDbPath(deps.pathProvider.getUserDataDir()), {
|
|
17742
19672
|
nativeBinding: deps.nativeBinding
|
|
17743
19673
|
});
|
|
@@ -18351,12 +20281,12 @@ function validationErrorMessage(error) {
|
|
|
18351
20281
|
}
|
|
18352
20282
|
|
|
18353
20283
|
// ../../packages/node-runtime/src/database-manager.ts
|
|
18354
|
-
import * as
|
|
18355
|
-
import * as
|
|
20284
|
+
import * as fs18 from "fs";
|
|
20285
|
+
import * as path30 from "path";
|
|
18356
20286
|
|
|
18357
20287
|
// ../../packages/node-runtime/src/services/global-insight/snapshot.ts
|
|
18358
|
-
import
|
|
18359
|
-
import
|
|
20288
|
+
import fs16 from "fs";
|
|
20289
|
+
import path28 from "path";
|
|
18360
20290
|
|
|
18361
20291
|
// ../../packages/node-runtime/src/services/global-insight/time-range.ts
|
|
18362
20292
|
function normalizeAnnualSummaryRange(input, nowInput = /* @__PURE__ */ new Date()) {
|
|
@@ -18407,17 +20337,17 @@ function toAnnualSummaryLocalDate(range) {
|
|
|
18407
20337
|
// ../../packages/node-runtime/src/services/global-insight/snapshot.ts
|
|
18408
20338
|
var SNAPSHOT_TMP_PREFIX = "annual-summary.tmp-";
|
|
18409
20339
|
function getAnnualSummarySnapshotPath(snapshotDir, range) {
|
|
18410
|
-
return
|
|
20340
|
+
return path28.join(snapshotDir, `annual-summary-${toAnnualSummaryRangeKey(range)}.json`);
|
|
18411
20341
|
}
|
|
18412
20342
|
function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
|
|
18413
20343
|
const snapshotPath = getAnnualSummarySnapshotPath(snapshotDir, range);
|
|
18414
|
-
if (!
|
|
20344
|
+
if (!fs16.existsSync(snapshotPath)) return null;
|
|
18415
20345
|
try {
|
|
18416
|
-
return JSON.parse(
|
|
20346
|
+
return JSON.parse(fs16.readFileSync(snapshotPath, "utf-8"));
|
|
18417
20347
|
} catch (error) {
|
|
18418
|
-
const backupPath =
|
|
20348
|
+
const backupPath = path28.join(snapshotDir, `annual-summary.corrupt-${options.now?.() ?? Date.now()}.json`);
|
|
18419
20349
|
try {
|
|
18420
|
-
|
|
20350
|
+
fs16.renameSync(snapshotPath, backupPath);
|
|
18421
20351
|
} catch (renameError) {
|
|
18422
20352
|
appLogger.warn("global-insight", "failed to backup corrupt annual summary snapshot", renameError);
|
|
18423
20353
|
}
|
|
@@ -18426,28 +20356,28 @@ function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
|
|
|
18426
20356
|
}
|
|
18427
20357
|
}
|
|
18428
20358
|
function writeAnnualSummarySnapshot(snapshotDir, snapshot) {
|
|
18429
|
-
|
|
18430
|
-
const tmpPath =
|
|
18431
|
-
|
|
18432
|
-
|
|
20359
|
+
fs16.mkdirSync(snapshotDir, { recursive: true });
|
|
20360
|
+
const tmpPath = path28.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
20361
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20362
|
+
fs16.renameSync(tmpPath, getAnnualSummarySnapshotPath(snapshotDir, snapshot.range));
|
|
18433
20363
|
}
|
|
18434
20364
|
function cleanupAnnualSummarySnapshotTempFiles(snapshotDir) {
|
|
18435
|
-
if (!
|
|
18436
|
-
for (const name of
|
|
20365
|
+
if (!fs16.existsSync(snapshotDir)) return;
|
|
20366
|
+
for (const name of fs16.readdirSync(snapshotDir)) {
|
|
18437
20367
|
if (!name.startsWith(SNAPSHOT_TMP_PREFIX)) continue;
|
|
18438
20368
|
try {
|
|
18439
|
-
|
|
20369
|
+
fs16.rmSync(path28.join(snapshotDir, name), { force: true });
|
|
18440
20370
|
} catch (error) {
|
|
18441
20371
|
appLogger.warn("global-insight", "failed to remove annual summary snapshot temp file", error);
|
|
18442
20372
|
}
|
|
18443
20373
|
}
|
|
18444
20374
|
}
|
|
18445
20375
|
function deleteAnnualSummarySnapshots(snapshotDir) {
|
|
18446
|
-
if (!
|
|
18447
|
-
for (const name of
|
|
20376
|
+
if (!fs16.existsSync(snapshotDir)) return;
|
|
20377
|
+
for (const name of fs16.readdirSync(snapshotDir)) {
|
|
18448
20378
|
if (!name.startsWith("annual-summary-") && !name.startsWith("annual-summary.")) continue;
|
|
18449
20379
|
try {
|
|
18450
|
-
|
|
20380
|
+
fs16.rmSync(path28.join(snapshotDir, name), { force: true });
|
|
18451
20381
|
} catch (error) {
|
|
18452
20382
|
appLogger.warn("global-insight", "failed to remove annual summary snapshot", error);
|
|
18453
20383
|
}
|
|
@@ -18455,21 +20385,21 @@ function deleteAnnualSummarySnapshots(snapshotDir) {
|
|
|
18455
20385
|
}
|
|
18456
20386
|
|
|
18457
20387
|
// ../../packages/node-runtime/src/services/global-insight/time-investment-snapshot.ts
|
|
18458
|
-
import
|
|
18459
|
-
import
|
|
20388
|
+
import fs17 from "fs";
|
|
20389
|
+
import path29 from "path";
|
|
18460
20390
|
var SNAPSHOT_TMP_PREFIX2 = "time-investment.tmp-";
|
|
18461
20391
|
function getTimeInvestmentSnapshotPath(snapshotDir, range) {
|
|
18462
|
-
return
|
|
20392
|
+
return path29.join(snapshotDir, `time-investment-${toAnnualSummaryRangeKey(range)}.json`);
|
|
18463
20393
|
}
|
|
18464
20394
|
function readTimeInvestmentSnapshot(snapshotDir, range, options = {}) {
|
|
18465
20395
|
const snapshotPath = getTimeInvestmentSnapshotPath(snapshotDir, range);
|
|
18466
|
-
if (!
|
|
20396
|
+
if (!fs17.existsSync(snapshotPath)) return null;
|
|
18467
20397
|
try {
|
|
18468
|
-
return JSON.parse(
|
|
20398
|
+
return JSON.parse(fs17.readFileSync(snapshotPath, "utf-8"));
|
|
18469
20399
|
} catch (error) {
|
|
18470
|
-
const backupPath =
|
|
20400
|
+
const backupPath = path29.join(snapshotDir, `time-investment.corrupt-${options.now?.() ?? Date.now()}.json`);
|
|
18471
20401
|
try {
|
|
18472
|
-
|
|
20402
|
+
fs17.renameSync(snapshotPath, backupPath);
|
|
18473
20403
|
} catch (renameError) {
|
|
18474
20404
|
appLogger.warn("global-insight", "failed to backup corrupt time investment snapshot", renameError);
|
|
18475
20405
|
}
|
|
@@ -18478,28 +20408,28 @@ function readTimeInvestmentSnapshot(snapshotDir, range, options = {}) {
|
|
|
18478
20408
|
}
|
|
18479
20409
|
}
|
|
18480
20410
|
function writeTimeInvestmentSnapshot(snapshotDir, snapshot) {
|
|
18481
|
-
|
|
18482
|
-
const tmpPath =
|
|
18483
|
-
|
|
18484
|
-
|
|
20411
|
+
fs17.mkdirSync(snapshotDir, { recursive: true });
|
|
20412
|
+
const tmpPath = path29.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX2}${process.pid}-${Date.now()}`);
|
|
20413
|
+
fs17.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20414
|
+
fs17.renameSync(tmpPath, getTimeInvestmentSnapshotPath(snapshotDir, snapshot.range));
|
|
18485
20415
|
}
|
|
18486
20416
|
function cleanupTimeInvestmentSnapshotTempFiles(snapshotDir) {
|
|
18487
|
-
if (!
|
|
18488
|
-
for (const name of
|
|
20417
|
+
if (!fs17.existsSync(snapshotDir)) return;
|
|
20418
|
+
for (const name of fs17.readdirSync(snapshotDir)) {
|
|
18489
20419
|
if (!name.startsWith(SNAPSHOT_TMP_PREFIX2)) continue;
|
|
18490
20420
|
try {
|
|
18491
|
-
|
|
20421
|
+
fs17.rmSync(path29.join(snapshotDir, name), { force: true });
|
|
18492
20422
|
} catch (error) {
|
|
18493
20423
|
appLogger.warn("global-insight", "failed to remove time investment snapshot temp file", error);
|
|
18494
20424
|
}
|
|
18495
20425
|
}
|
|
18496
20426
|
}
|
|
18497
20427
|
function deleteTimeInvestmentSnapshots(snapshotDir) {
|
|
18498
|
-
if (!
|
|
18499
|
-
for (const name of
|
|
20428
|
+
if (!fs17.existsSync(snapshotDir)) return;
|
|
20429
|
+
for (const name of fs17.readdirSync(snapshotDir)) {
|
|
18500
20430
|
if (!name.startsWith("time-investment-") && !name.startsWith("time-investment.")) continue;
|
|
18501
20431
|
try {
|
|
18502
|
-
|
|
20432
|
+
fs17.rmSync(path29.join(snapshotDir, name), { force: true });
|
|
18503
20433
|
} catch (error) {
|
|
18504
20434
|
appLogger.warn("global-insight", "failed to remove time investment snapshot", error);
|
|
18505
20435
|
}
|
|
@@ -18508,8 +20438,8 @@ function deleteTimeInvestmentSnapshots(snapshotDir) {
|
|
|
18508
20438
|
|
|
18509
20439
|
// ../../packages/node-runtime/src/database-manager.ts
|
|
18510
20440
|
function listDatabaseCandidateIds(databaseDir) {
|
|
18511
|
-
if (!
|
|
18512
|
-
return
|
|
20441
|
+
if (!fs18.existsSync(databaseDir)) return [];
|
|
20442
|
+
return fs18.readdirSync(databaseDir).filter((fileName) => fileName.endsWith(".db") && !fileName.startsWith("._")).map((fileName) => fileName.slice(0, -3));
|
|
18513
20443
|
}
|
|
18514
20444
|
var DatabaseManager = class {
|
|
18515
20445
|
constructor(pathProvider, options = {}) {
|
|
@@ -18533,7 +20463,7 @@ var DatabaseManager = class {
|
|
|
18533
20463
|
return this.cache.get(sessionId);
|
|
18534
20464
|
}
|
|
18535
20465
|
const dbPath = this.getDbPath(sessionId);
|
|
18536
|
-
if (!
|
|
20466
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
18537
20467
|
this.migrateIfNeeded(dbPath);
|
|
18538
20468
|
this.assertCompatible();
|
|
18539
20469
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
@@ -18579,7 +20509,7 @@ var DatabaseManager = class {
|
|
|
18579
20509
|
this.cache.delete(sessionId);
|
|
18580
20510
|
}
|
|
18581
20511
|
const dbPath = this.getDbPath(sessionId);
|
|
18582
|
-
if (!
|
|
20512
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
18583
20513
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
18584
20514
|
readonly: false,
|
|
18585
20515
|
nativeBinding: this.nativeBinding
|
|
@@ -18617,14 +20547,20 @@ var DatabaseManager = class {
|
|
|
18617
20547
|
* 列举数据库目录下的所有聊天会话 ID
|
|
18618
20548
|
*/
|
|
18619
20549
|
listSessionIds() {
|
|
18620
|
-
this.
|
|
18621
|
-
const dbDir = this.pathProvider.getDatabaseDir();
|
|
18622
|
-
return listDatabaseCandidateIds(dbDir).filter((id) => {
|
|
20550
|
+
return this.listSessionCandidateIds().filter((id) => {
|
|
18623
20551
|
const db = this.open(id);
|
|
18624
20552
|
if (!db) return false;
|
|
18625
20553
|
return isChatSessionDb(db);
|
|
18626
20554
|
});
|
|
18627
20555
|
}
|
|
20556
|
+
/**
|
|
20557
|
+
* 仅列出数据库文件候选,不同步打开所有数据库。
|
|
20558
|
+
* 需要逐库检查、取消和时间预算的调用方应使用此入口后自行验证。
|
|
20559
|
+
*/
|
|
20560
|
+
listSessionCandidateIds() {
|
|
20561
|
+
this.assertCompatible();
|
|
20562
|
+
return listDatabaseCandidateIds(this.pathProvider.getDatabaseDir());
|
|
20563
|
+
}
|
|
18628
20564
|
/**
|
|
18629
20565
|
* 只读列举聊天会话 ID,不触发数据库迁移或兼容门禁提升。
|
|
18630
20566
|
*/
|
|
@@ -18647,17 +20583,17 @@ var DatabaseManager = class {
|
|
|
18647
20583
|
* 获取数据库文件路径
|
|
18648
20584
|
*/
|
|
18649
20585
|
getDbPath(sessionId) {
|
|
18650
|
-
return
|
|
20586
|
+
return path30.join(this.pathProvider.getDatabaseDir(), `${sessionId}.db`);
|
|
18651
20587
|
}
|
|
18652
20588
|
openRawSessionDatabase(sessionId, options = {}) {
|
|
18653
20589
|
this.assertCompatible();
|
|
18654
20590
|
const dbPath = this.getDbPath(sessionId);
|
|
18655
|
-
if (!options.create && !
|
|
20591
|
+
if (!options.create && !fs18.existsSync(dbPath)) {
|
|
18656
20592
|
throw new Error(`Session database not found: ${sessionId}`);
|
|
18657
20593
|
}
|
|
18658
20594
|
if (!options.readonly) {
|
|
18659
20595
|
this.close(sessionId);
|
|
18660
|
-
|
|
20596
|
+
fs18.mkdirSync(path30.dirname(dbPath), { recursive: true });
|
|
18661
20597
|
}
|
|
18662
20598
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
18663
20599
|
readonly: options.readonly ?? false,
|
|
@@ -18676,19 +20612,19 @@ var DatabaseManager = class {
|
|
|
18676
20612
|
assertSessionChatTopicsIdle(this.pathProvider.getUserDataDir(), sessionId, { nativeBinding: this.nativeBinding });
|
|
18677
20613
|
this.close(sessionId);
|
|
18678
20614
|
const dbPath = this.getDbPath(sessionId);
|
|
18679
|
-
const existed = ["", "-wal", "-shm"].some((suffix) =>
|
|
18680
|
-
if (
|
|
20615
|
+
const existed = ["", "-wal", "-shm"].some((suffix) => fs18.existsSync(dbPath + suffix));
|
|
20616
|
+
if (fs18.existsSync(dbPath)) fs18.unlinkSync(dbPath);
|
|
18681
20617
|
for (const suffix of ["-wal", "-shm"]) {
|
|
18682
20618
|
try {
|
|
18683
20619
|
const filePath = dbPath + suffix;
|
|
18684
|
-
if (
|
|
20620
|
+
if (fs18.existsSync(filePath)) fs18.unlinkSync(filePath);
|
|
18685
20621
|
} catch {
|
|
18686
20622
|
}
|
|
18687
20623
|
}
|
|
18688
20624
|
deleteSessionChatTopics(this.pathProvider.getUserDataDir(), sessionId, { nativeBinding: this.nativeBinding });
|
|
18689
20625
|
const cacheDir = this.pathProvider.getCacheDir();
|
|
18690
20626
|
deleteSessionCache(sessionId, cacheDir);
|
|
18691
|
-
deleteSessionCache(sessionId,
|
|
20627
|
+
deleteSessionCache(sessionId, path30.join(cacheDir, "query"));
|
|
18692
20628
|
deleteSessionCache(sessionId, getContactsFactsCacheDir(this.pathProvider.getUserDataDir()));
|
|
18693
20629
|
deleteSessionCache(sessionId, getGlobalInsightFactsCacheDir(this.pathProvider.getUserDataDir()));
|
|
18694
20630
|
deleteAnnualSummarySnapshots(getGlobalInsightDir(this.pathProvider.getUserDataDir()));
|
|
@@ -18731,12 +20667,12 @@ function shouldRepairCompatibilityGate(schemaVersion) {
|
|
|
18731
20667
|
}
|
|
18732
20668
|
|
|
18733
20669
|
// ../../packages/node-runtime/src/cache/analytics-cache.ts
|
|
18734
|
-
import * as
|
|
20670
|
+
import * as fs19 from "fs";
|
|
18735
20671
|
function getDbFileVersion(dbPath) {
|
|
18736
20672
|
const parts = [];
|
|
18737
20673
|
for (const p of [dbPath, `${dbPath}-wal`]) {
|
|
18738
20674
|
try {
|
|
18739
|
-
const st =
|
|
20675
|
+
const st = fs19.statSync(p);
|
|
18740
20676
|
parts.push(`${Math.floor(st.mtimeMs)}:${st.size}`);
|
|
18741
20677
|
} catch {
|
|
18742
20678
|
parts.push("-");
|
|
@@ -18810,6 +20746,7 @@ var StaticPathProvider = class {
|
|
|
18810
20746
|
function createDatabaseManagerAdapter(dbManager) {
|
|
18811
20747
|
return {
|
|
18812
20748
|
listSessionIds: () => dbManager.listSessionIds(),
|
|
20749
|
+
listSessionCandidateIds: () => dbManager.listSessionCandidateIds(),
|
|
18813
20750
|
openReadonly: (sessionId) => dbManager.open(sessionId),
|
|
18814
20751
|
openWritable: (sessionId) => dbManager.openWritable(sessionId),
|
|
18815
20752
|
closeSession: (sessionId) => dbManager.close(sessionId),
|
|
@@ -18887,17 +20824,30 @@ export {
|
|
|
18887
20824
|
generateSessionIndex,
|
|
18888
20825
|
generateIncrementalSessionIndex,
|
|
18889
20826
|
getPrivateChatMemberAvatar,
|
|
20827
|
+
CHATLAB_FORMAT_VERSION,
|
|
20828
|
+
CHATLAB_SUPPORTED_FORMAT_VERSIONS,
|
|
20829
|
+
isSupportedChatLabFormatVersion,
|
|
20830
|
+
ANNUAL_SUMMARY_PLUGIN_ID,
|
|
20831
|
+
TIME_INVESTMENT_PLUGIN_ID,
|
|
20832
|
+
NAVIGATION_LAYOUT_SCHEMA_VERSION,
|
|
20833
|
+
MessageType,
|
|
20834
|
+
KNOWN_PLATFORMS,
|
|
20835
|
+
getDefaultGeneralAssistantId,
|
|
20836
|
+
CONTACTS_TIME_RANGE_PRESETS,
|
|
18890
20837
|
POS_TAG_DEFINITIONS,
|
|
18891
20838
|
MEANINGFUL_POS_TAGS,
|
|
18892
20839
|
isStopword,
|
|
18893
|
-
|
|
18894
|
-
isValidWord,
|
|
20840
|
+
isSystemMessageContent,
|
|
18895
20841
|
buildExcludeKeywordsConditions,
|
|
18896
20842
|
getMentionAnalysis,
|
|
18897
20843
|
getLaughAnalysis,
|
|
18898
20844
|
getClusterGraph,
|
|
18899
20845
|
resolveOwnerMember,
|
|
20846
|
+
resolveContactMember,
|
|
20847
|
+
getNonSystemMembersForContacts,
|
|
18900
20848
|
getLatestContactMessageTs,
|
|
20849
|
+
getParticipantSessionFacts,
|
|
20850
|
+
getParticipantSetInteractionFacts,
|
|
18901
20851
|
getPrivateContactFacts,
|
|
18902
20852
|
getGroupContactFacts,
|
|
18903
20853
|
getGroupRelationshipGraphFacts,
|
|
@@ -18906,16 +20856,7 @@ export {
|
|
|
18906
20856
|
matchOwnerProfile,
|
|
18907
20857
|
shouldScopeContactToSession,
|
|
18908
20858
|
buildContactKey,
|
|
18909
|
-
|
|
18910
|
-
CHATLAB_SUPPORTED_FORMAT_VERSIONS,
|
|
18911
|
-
isSupportedChatLabFormatVersion,
|
|
18912
|
-
ANNUAL_SUMMARY_PLUGIN_ID,
|
|
18913
|
-
TIME_INVESTMENT_PLUGIN_ID,
|
|
18914
|
-
NAVIGATION_LAYOUT_SCHEMA_VERSION,
|
|
18915
|
-
MessageType,
|
|
18916
|
-
KNOWN_PLATFORMS,
|
|
18917
|
-
getDefaultGeneralAssistantId,
|
|
18918
|
-
CONTACTS_TIME_RANGE_PRESETS,
|
|
20859
|
+
getCrossChatSessionActivityFacts,
|
|
18919
20860
|
getAnnualSummarySessionFacts,
|
|
18920
20861
|
aggregateAnnualSummaryFacts,
|
|
18921
20862
|
getTimeInvestmentSessionFacts,
|
|
@@ -18926,6 +20867,7 @@ export {
|
|
|
18926
20867
|
queryMessages,
|
|
18927
20868
|
searchMessagesLike,
|
|
18928
20869
|
searchMessagesByKeywords,
|
|
20870
|
+
getRecentMessages,
|
|
18929
20871
|
getMembers,
|
|
18930
20872
|
executeReadonlySql,
|
|
18931
20873
|
executeSql,
|
|
@@ -18988,6 +20930,21 @@ export {
|
|
|
18988
20930
|
parseSkillFile,
|
|
18989
20931
|
extractSkillId,
|
|
18990
20932
|
AIChatManager,
|
|
20933
|
+
initNlpDir,
|
|
20934
|
+
getNlpDir,
|
|
20935
|
+
getJieba,
|
|
20936
|
+
clearJiebaInstance,
|
|
20937
|
+
segment,
|
|
20938
|
+
batchSegmentWithFrequency,
|
|
20939
|
+
batchSegmentChineseWithStats,
|
|
20940
|
+
collectPosTagStats,
|
|
20941
|
+
getPosTagDefinitions,
|
|
20942
|
+
AI_MEMORY_CONTENT_MAX_CHARS,
|
|
20943
|
+
buildGlobalMemoryPrompt,
|
|
20944
|
+
AIMemoryService,
|
|
20945
|
+
listAIMemoriesWithSourceStatus,
|
|
20946
|
+
linkAIMemorySources,
|
|
20947
|
+
resolveAIMemorySourceStatus,
|
|
18991
20948
|
initTokenizer,
|
|
18992
20949
|
countTokens,
|
|
18993
20950
|
countMessagesTokens,
|
|
@@ -19042,11 +20999,15 @@ export {
|
|
|
19042
20999
|
createPlanContentBlock,
|
|
19043
21000
|
buildPlanGuidance,
|
|
19044
21001
|
buildSemanticSearchGuidance,
|
|
21002
|
+
createAiTranslate,
|
|
21003
|
+
aiLocales,
|
|
19045
21004
|
estimateTokensFromText,
|
|
19046
21005
|
AgentEventHandler,
|
|
21006
|
+
CROSS_CHAT_MAX_TOOL_RESULT_TOKENS,
|
|
21007
|
+
resolveCrossChatToolResultTokenBudget,
|
|
21008
|
+
runCrossChatAgent,
|
|
21009
|
+
buildCrossChatSystemPrompt,
|
|
19047
21010
|
buildSystemPrompt,
|
|
19048
|
-
createAiTranslate,
|
|
19049
|
-
aiLocales,
|
|
19050
21011
|
isValidMessage,
|
|
19051
21012
|
filterValidMessages,
|
|
19052
21013
|
splitIntoSegments,
|
|
@@ -19125,4 +21086,4 @@ export {
|
|
|
19125
21086
|
StaticPathProvider,
|
|
19126
21087
|
createDatabaseManagerAdapter
|
|
19127
21088
|
};
|
|
19128
|
-
//# sourceMappingURL=chunk-
|
|
21089
|
+
//# sourceMappingURL=chunk-OTYNMDDL.mjs.map
|