chatlab-cli 0.36.2 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-command-SEV6N3AR.mjs → chat-command-WBAJBKIE.mjs} +15 -16
- package/dist/{chat-command-SEV6N3AR.mjs.map → chat-command-WBAJBKIE.mjs.map} +1 -1
- package/dist/chunk-2TV7M4CC.mjs +101 -0
- package/dist/chunk-2TV7M4CC.mjs.map +1 -0
- package/dist/{chunk-TRGR3KKJ.mjs → chunk-4J4MA3RC.mjs} +2605 -672
- package/dist/chunk-4J4MA3RC.mjs.map +1 -0
- package/dist/{chunk-TJ3NOBVI.mjs → chunk-5YOHLJFB.mjs} +98 -284
- package/dist/chunk-5YOHLJFB.mjs.map +1 -0
- package/dist/{chunk-WRBFV2GX.mjs → chunk-CR2MU7LN.mjs} +49 -55
- package/dist/chunk-CR2MU7LN.mjs.map +1 -0
- package/dist/{chunk-WJQOXOMR.mjs → chunk-DKIYHA3O.mjs} +281 -30
- package/dist/chunk-DKIYHA3O.mjs.map +1 -0
- package/dist/{chunk-VREVDZ4Q.mjs → chunk-DQR2YS3I.mjs} +1930 -33
- package/dist/chunk-DQR2YS3I.mjs.map +1 -0
- package/dist/{chunk-VUYWUHWW.mjs → chunk-EJBBVLUN.mjs} +3 -3
- package/dist/chunk-EJBBVLUN.mjs.map +1 -0
- package/dist/{chunk-HWIXK7SU.mjs → chunk-FM3GXNEO.mjs} +1643 -3
- package/dist/chunk-FM3GXNEO.mjs.map +1 -0
- package/dist/{chunk-YUH3H3QW.mjs → chunk-MSMDLDPB.mjs} +96 -5
- package/dist/chunk-MSMDLDPB.mjs.map +1 -0
- package/dist/{chunk-6EZPD3XJ.mjs → chunk-N2WGG3MS.mjs} +2 -2
- package/dist/{chunk-TWH422HC.mjs → chunk-RXMSZIYD.mjs} +3 -3
- package/dist/{chunk-CH5YAZ4Q.mjs → chunk-SQWN4GTQ.mjs} +2 -2
- 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-X6OEOOSH.mjs} +12 -12
- package/dist/{import-R2E2TFAK.mjs → import-WBJ6YIQ7.mjs} +7 -7
- package/dist/index.mjs +12 -12
- package/dist/{mcp-BED4AARV.mjs → mcp-FR6OKWKL.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-WE5HNN2O.mjs} +44 -14
- package/dist/{stream-import-RXNSOOYH.mjs → stream-import-YYQVZDGQ.mjs} +7 -7
- package/dist/{worker-runtime-JGAZU7MC.mjs → worker-runtime-YGB5ZDE4.mjs} +5 -5
- package/dist-cli-web/assets/AISettingsTab-Co2sM1iY.js +1 -0
- package/dist-cli-web/assets/{AboutTab-DCM1-glA.js → AboutTab-DQ8r6mYE.js} +1 -1
- package/dist-cli-web/assets/ActionToolsPanel-CmYpw8Mt.css +1 -0
- package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang--RPlmfIu.js +75 -0
- package/dist-cli-web/assets/{Alert-D-egBWcb.js → Alert-DU1tLAAg.js} +1 -1
- package/dist-cli-web/assets/ApiSettingsTab-D5g5b79L.js +2 -0
- package/dist-cli-web/assets/BatchManageTab-BFUyVaGU.js +1 -0
- package/dist-cli-web/assets/{BufferResource-BE2_2bZK.js → BufferResource-C-RfYwQW.js} +1 -1
- package/dist-cli-web/assets/{CanvasRenderer-DDKdvEYQ.js → CanvasRenderer-Dz7JAMrR.js} +1 -1
- package/dist-cli-web/assets/CardDecoration-D28DI1G9.js +1 -0
- package/dist-cli-web/assets/ChatRecordDrawer-Cph819yY.js +1 -0
- package/dist-cli-web/assets/{ChatRecordWorkspace-DqUI4QiU.js → ChatRecordWorkspace-BAfERdPc.js} +1 -1
- package/dist-cli-web/assets/Checkbox-B8uqt13j.js +1 -0
- package/dist-cli-web/assets/DataDirCleanupNotice-3sGBE5vU.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-CQuub8-f.js} +1 -1
- package/dist-cli-web/assets/{DebugToolsPanel-BIHA6jw4.js → DebugToolsPanel-Cwkr0BOY.js} +1 -1
- package/dist-cli-web/assets/EChart.vue_vue_type_script_setup_true_lang-BuLujQCF.js +1 -0
- package/dist-cli-web/assets/EChartBar.vue_vue_type_script_setup_true_lang-DuA5xbcC.js +1 -0
- package/dist-cli-web/assets/EChartLine.vue_vue_type_script_setup_true_lang-CvmMEfYq.js +1 -0
- package/dist-cli-web/assets/EChartWordcloud-CHVtyPR7.js +1 -0
- package/dist-cli-web/assets/FileDropZone.vue_vue_type_script_setup_true_lang-BKPy9Qv-.js +1 -0
- package/dist-cli-web/assets/{Filter-kO4dIDJ2.js → Filter-CMfRHIoK.js} +1 -1
- package/dist-cli-web/assets/GlobalTaskBar-Jz6MASCu.js +1 -0
- package/dist-cli-web/assets/GlobalTaskBar-haLzezqY.css +1 -0
- package/dist-cli-web/assets/GroupRelationshipGalaxyView-Ch4obfEq.js +1 -0
- package/dist-cli-web/assets/Label-CuryuymO.js +1 -0
- package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-CoeQsaiL.js +1 -0
- package/dist-cli-web/assets/MessageExportModal-JqGZ-iLr.js +1 -0
- package/dist-cli-web/assets/PageHeader-CPLgiKT8.css +1 -0
- package/dist-cli-web/assets/PageHeader-D4up77TB.js +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-DYQRFk9s.js} +1 -1
- package/dist-cli-web/assets/{RelationshipGalaxyCanvas-Cod1JX9m.js → RelationshipGalaxyCanvas-Cn7Eta5U.js} +3 -3
- package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-CkefzeFJ.js → RelationshipGalaxyThreeCanvas-LlaX57z6.js} +1 -1
- package/dist-cli-web/assets/{RenderTargetSystem-SuQ7cbze.js → RenderTargetSystem-JhA2PUFi.js} +1 -1
- package/dist-cli-web/assets/{RovingFocusItem-psoFYxrC.js → RovingFocusItem-DHaiTb4j.js} +1 -1
- package/dist-cli-web/assets/{ScreenCaptureModal-D4r9ixKh.js → ScreenCaptureModal-Bxhyt5g1.js} +1 -1
- package/dist-cli-web/assets/SectionTabs.vue_vue_type_script_setup_true_lang-BlAje3Tf.js +1 -0
- package/dist-cli-web/assets/Select-Beeqv0wU.js +1 -0
- package/dist-cli-web/assets/SettingsModal-DV8yDBJs.js +2 -0
- package/dist-cli-web/assets/{StorageTab-DhSJ-0Ir.js → StorageTab-B7GHadia.js} +1 -1
- package/dist-cli-web/assets/Switch-u4zlvUMa.js +1 -0
- package/dist-cli-web/assets/{Tabs-Bnm_Q7yW.js → Tabs-B7-Koq3x.js} +1 -1
- package/dist-cli-web/assets/Tabs-RWKHwtfM.js +1 -0
- package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-ndsa3-aT.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-DKa1flT-.js} +1 -1
- package/dist-cli-web/assets/{VisuallyHiddenInput-Bo_XwaLu.js → VisuallyHiddenInput-DGDqcvUy.js} +1 -1
- package/dist-cli-web/assets/{WebGLRenderer-D1x9WZRj.js → WebGLRenderer-ifvgA1fe.js} +1 -1
- package/dist-cli-web/assets/{WebGPURenderer-37Nw-tLm.js → WebGPURenderer-DwzmuWF0.js} +1 -1
- package/dist-cli-web/assets/aiChat-BY4C3UQe.js +8 -0
- package/dist-cli-web/assets/base-BWDX-164.js +1 -0
- package/dist-cli-web/assets/{browserAll-CWrz_-4U.js → browserAll-CSIIETeE.js} +1 -1
- package/dist-cli-web/assets/chatMessages--r0obAVw.css +1 -0
- package/dist-cli-web/assets/chatMessages-Zywjea1n.js +147 -0
- package/dist-cli-web/assets/cli-web-BRY49NWe.js +1 -0
- package/dist-cli-web/assets/{fetch-Du-wU0OO.js → fetch-Bf4cqeM3.js} +1 -1
- package/dist-cli-web/assets/{fetch-DPSWt6yX.js → fetch-BiXab0IF.js} +1 -1
- package/dist-cli-web/assets/{fetch-B69OkB6B.js → fetch-C8CcoTWY.js} +1 -1
- package/dist-cli-web/assets/{fetch-qd2FgBWQ.js → fetch-Co2vLmiE.js} +1 -1
- package/dist-cli-web/assets/{fetch-BGmCCyzz.js → fetch-D3nLtFBi.js} +1 -1
- package/dist-cli-web/assets/{fetch-CFpLXWTe.js → fetch-DNSDHSqQ.js} +1 -1
- package/dist-cli-web/assets/{fetch-DZ6OF-xW.js → fetch-DeHqF6yL.js} +1 -1
- package/dist-cli-web/assets/{fetch-CkXgOxcu.js → fetch-jx1VdM9j.js} +3 -3
- package/dist-cli-web/assets/{fetch-Gu4qQjUB.js → fetch-mYSeL4Ki.js} +1 -1
- package/dist-cli-web/assets/fetch-qBaUXqFt.js +1 -0
- package/dist-cli-web/assets/{fetch-6pcVxB9h.js → fetch-r6mhEGrP.js} +1 -1
- package/dist-cli-web/assets/{fetch-CgA1peJD.js → fetch-wzv71PYy.js} +1 -1
- package/dist-cli-web/assets/{host-CWsAWKgy.js → host-1emQf9_4.js} +1 -1
- package/dist-cli-web/assets/{host-D8zlA62K.js → host-jXJHyyOX.js} +1 -1
- package/dist-cli-web/assets/index-2dJCozDq.js +64 -0
- package/dist-cli-web/assets/index-6ejRbH3h.js +2 -0
- package/dist-cli-web/assets/index-B4mJkX_d.js +1 -0
- package/dist-cli-web/assets/index-B7biznha.js +1 -0
- package/dist-cli-web/assets/index-BOcFyPCa.js +1 -0
- package/dist-cli-web/assets/index-BQ463TS7.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-C1A1FwpO.css +1 -0
- package/dist-cli-web/assets/index-CIIyg_o9.css +1 -0
- package/dist-cli-web/assets/{index-a7CT8DQm.js → index-CYc_VLgK.js} +1 -1
- package/dist-cli-web/assets/index-ClHMs0-a.js +2 -0
- package/dist-cli-web/assets/{index-Dy0fRMDf.js → index-Cw0IhKZ6.js} +10 -10
- package/dist-cli-web/assets/{index-v6DlL4ue.js → index-DOqbUvhA.js} +1 -1
- package/dist-cli-web/assets/index-DnpnIyle.js +135 -0
- package/dist-cli-web/assets/index-LV9LfFNu.js +1 -0
- package/dist-cli-web/assets/index-kePiJ3ca.js +137 -0
- package/dist-cli-web/assets/index-slDUxib2.js +1 -0
- package/dist-cli-web/assets/{people-page-header-Czt9e1jv.js → people-page-header-Cx5r9Bk2.js} +1 -1
- package/dist-cli-web/assets/{prompt-DGazz143.js → prompt-BggEDxxi.js} +1 -1
- package/dist-cli-web/assets/{semantic-index-config-builder-BkFHzIUy.js → semantic-index-config-builder-1sO0aEc1.js} +1 -1
- package/dist-cli-web/assets/{service-CAp0YJoS.js → service-Bvq1fRkC.js} +1 -1
- package/dist-cli-web/assets/service-DkPheDt8.js +1 -0
- package/dist-cli-web/assets/service-DvI8O_7K.js +1 -0
- package/dist-cli-web/assets/useFormControl-B0aShtLp.js +1 -0
- package/dist-cli-web/assets/useTable-DC7oxmBl.js +1 -0
- package/dist-cli-web/assets/{useToast-YAHDkKDN.js → useToast-CCPBE-Sy.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-FCJk6reG.js} +1 -1
- package/dist-cli-web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-6E46U4H2.mjs +0 -41
- package/dist/chunk-6E46U4H2.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-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-6EZPD3XJ.mjs.map → chunk-N2WGG3MS.mjs.map} +0 -0
- /package/dist/{chunk-TWH422HC.mjs.map → chunk-RXMSZIYD.mjs.map} +0 -0
- /package/dist/{chunk-CH5YAZ4Q.mjs.map → chunk-SQWN4GTQ.mjs.map} +0 -0
- /package/dist/{http-237XYNRT.mjs.map → http-X6OEOOSH.mjs.map} +0 -0
- /package/dist/{import-R2E2TFAK.mjs.map → import-WBJ6YIQ7.mjs.map} +0 -0
- /package/dist/{mcp-BED4AARV.mjs.map → mcp-FR6OKWKL.mjs.map} +0 -0
- /package/dist/{src-AYMALWU5.mjs.map → src-WE5HNN2O.mjs.map} +0 -0
- /package/dist/{stream-import-RXNSOOYH.mjs.map → stream-import-YYQVZDGQ.mjs.map} +0 -0
- /package/dist/{worker-runtime-JGAZU7MC.mjs.map → worker-runtime-YGB5ZDE4.mjs.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createEmptyContactsDiagnostics,
|
|
10
10
|
normalizeContactsTimeRangePreset,
|
|
11
11
|
resolveContactsTimeRange
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-EJBBVLUN.mjs";
|
|
13
13
|
import {
|
|
14
14
|
PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
|
|
15
15
|
buildPeopleRelationshipsNeighborhoodGraph,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
computePeopleRelationshipsSnapshot,
|
|
19
19
|
normalizePeopleRelationshipsTimeRangePreset,
|
|
20
20
|
resolvePeopleRelationshipsTimeRange
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-N2WGG3MS.mjs";
|
|
22
22
|
import {
|
|
23
23
|
ANNUAL_SUMMARY_ALGORITHM_VERSION,
|
|
24
24
|
TIME_INVESTMENT_ALGORITHM_VERSION,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
NAVIGATION_LAYOUT_SCHEMA_VERSION,
|
|
38
38
|
appLogger,
|
|
39
39
|
applyPreprocessingPipeline,
|
|
40
|
+
buildContactKey,
|
|
40
41
|
buildSessionInfo,
|
|
41
42
|
checkSessionsCanGenerateSummary,
|
|
42
43
|
cleanupAnnualSummarySnapshotTempFiles,
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
deleteMember,
|
|
48
49
|
deleteMembers,
|
|
49
50
|
deleteTimeInvestmentSnapshots,
|
|
51
|
+
desensitizeText,
|
|
50
52
|
generateIncrementalSessionIndex,
|
|
51
53
|
generateMessageKey,
|
|
52
54
|
generateSessionIndex,
|
|
@@ -55,15 +57,22 @@ import {
|
|
|
55
57
|
getChatSessionList,
|
|
56
58
|
getContactsDir,
|
|
57
59
|
getContactsFactsCacheDir,
|
|
60
|
+
getCrossChatSessionActivityFacts,
|
|
58
61
|
getDbFileVersion,
|
|
59
62
|
getGlobalInsightDir,
|
|
60
63
|
getGlobalInsightFactsCacheDir,
|
|
61
64
|
getMemberNameHistory,
|
|
62
65
|
getMembersPaginated,
|
|
63
66
|
getMembersWithAliases,
|
|
67
|
+
getNonSystemMembersForContacts,
|
|
68
|
+
getParticipantSessionFacts,
|
|
69
|
+
getParticipantSetInteractionFacts,
|
|
64
70
|
getPeopleRelationshipsDir,
|
|
65
71
|
getPeopleRelationshipsFactsCacheDir,
|
|
66
72
|
getPrivateChatMemberAvatar,
|
|
73
|
+
getRecentMessages,
|
|
74
|
+
getSearchMessageContext,
|
|
75
|
+
getSegmentSummaries,
|
|
67
76
|
getSegmentSummary,
|
|
68
77
|
getSessionIndexStats,
|
|
69
78
|
getSessionInfo,
|
|
@@ -80,16 +89,22 @@ import {
|
|
|
80
89
|
isSupportedChatLabFormatVersion,
|
|
81
90
|
loadSegmentMessages,
|
|
82
91
|
matchOwnerProfile,
|
|
92
|
+
matchesBlacklist,
|
|
83
93
|
mergeConfirmedNames,
|
|
84
94
|
mergeMembers,
|
|
85
95
|
normalizeAnnualSummaryRange,
|
|
86
96
|
normalizeSessionGapThreshold,
|
|
87
97
|
openBetterSqliteDatabase,
|
|
98
|
+
preprocessMessages,
|
|
88
99
|
readAnnualSummarySnapshot,
|
|
89
100
|
readTimeInvestmentSnapshot,
|
|
90
101
|
renameSession,
|
|
91
102
|
resolveApiKey,
|
|
103
|
+
resolveContactMember,
|
|
104
|
+
resolveOwnerMember,
|
|
92
105
|
saveSegmentSummary,
|
|
106
|
+
searchMessagesByKeywords,
|
|
107
|
+
shouldScopeContactToSession,
|
|
93
108
|
snapshotPathProvider,
|
|
94
109
|
toAnnualSummaryLocalDate,
|
|
95
110
|
toAnnualSummaryRangeKey,
|
|
@@ -99,7 +114,7 @@ import {
|
|
|
99
114
|
writeAuthProfile,
|
|
100
115
|
writeParseResultToDb,
|
|
101
116
|
writeTimeInvestmentSnapshot
|
|
102
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-4J4MA3RC.mjs";
|
|
103
118
|
import {
|
|
104
119
|
__commonJS,
|
|
105
120
|
__export,
|
|
@@ -4778,6 +4793,11 @@ var feature5 = {
|
|
|
4778
4793
|
requiredFields: ["weflow", "session"]
|
|
4779
4794
|
}
|
|
4780
4795
|
};
|
|
4796
|
+
function normalizePlatformMessageId(value) {
|
|
4797
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
4798
|
+
const id = String(value).trim();
|
|
4799
|
+
return id && id !== "0" ? id : void 0;
|
|
4800
|
+
}
|
|
4781
4801
|
function convertMessageType3(typeStr) {
|
|
4782
4802
|
switch (typeStr) {
|
|
4783
4803
|
case "\u6587\u672C\u6D88\u606F":
|
|
@@ -5080,9 +5100,9 @@ async function* parseWeFlow(options2) {
|
|
|
5080
5100
|
const rawContent = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
5081
5101
|
content = rawContent.trim() || null;
|
|
5082
5102
|
}
|
|
5103
|
+
const platformMessageId = normalizePlatformMessageId(msg.platformMessageId);
|
|
5083
5104
|
return {
|
|
5084
|
-
platformMessageId
|
|
5085
|
-
// 消息的平台原始 ID(用于回复关联查询)
|
|
5105
|
+
platformMessageId,
|
|
5086
5106
|
senderPlatformId: platformId,
|
|
5087
5107
|
senderAccountName: accountName,
|
|
5088
5108
|
// WeFlow 格式没有单独的群昵称字段
|
|
@@ -7931,22 +7951,56 @@ function generateFallbackMessageKey(message) {
|
|
|
7931
7951
|
const contentKey = generateMessageKey(message.timestamp, message.senderPlatformId, message.content);
|
|
7932
7952
|
return JSON.stringify([contentKey, message.type, message.replyToMessageId || null]);
|
|
7933
7953
|
}
|
|
7934
|
-
function createMessageDedupState(platformMessageIds = [], fallbackKeys = [], fallbackOnlyKeys = []) {
|
|
7954
|
+
function createMessageDedupState(platformMessageIds = [], fallbackKeys = [], fallbackOnlyKeys = [], options2 = {}) {
|
|
7955
|
+
const fallbackKeySet = new Set(fallbackKeys);
|
|
7956
|
+
const fallbackOnlyKeySet = new Set(fallbackOnlyKeys);
|
|
7957
|
+
const fallbackMultiplicityBalances = /* @__PURE__ */ new Map();
|
|
7958
|
+
if (options2.preserveFallbackMultiplicity) {
|
|
7959
|
+
for (const key of fallbackKeySet) fallbackMultiplicityBalances.set(key, -1);
|
|
7960
|
+
for (const key of fallbackOnlyKeySet) fallbackMultiplicityBalances.set(key, 1);
|
|
7961
|
+
}
|
|
7935
7962
|
return {
|
|
7936
7963
|
platformMessageIds: new Set(platformMessageIds),
|
|
7937
|
-
fallbackKeys:
|
|
7938
|
-
fallbackOnlyKeys:
|
|
7964
|
+
fallbackKeys: fallbackKeySet,
|
|
7965
|
+
fallbackOnlyKeys: fallbackOnlyKeySet,
|
|
7966
|
+
preserveFallbackMultiplicity: options2.preserveFallbackMultiplicity ?? false,
|
|
7967
|
+
fallbackMultiplicityBalances
|
|
7939
7968
|
};
|
|
7940
7969
|
}
|
|
7970
|
+
function setFallbackMultiplicityBalance(state, key, balance) {
|
|
7971
|
+
if (balance === 0) state.fallbackMultiplicityBalances.delete(key);
|
|
7972
|
+
else state.fallbackMultiplicityBalances.set(key, balance);
|
|
7973
|
+
}
|
|
7974
|
+
function hasUnmatchedFallbackOnlyMessage(state, fallbackKey) {
|
|
7975
|
+
return state.preserveFallbackMultiplicity ? (state.fallbackMultiplicityBalances.get(fallbackKey) ?? 0) > 0 : state.fallbackOnlyKeys.has(fallbackKey);
|
|
7976
|
+
}
|
|
7941
7977
|
function registerMessageAndCheckDuplicate(message, state) {
|
|
7942
7978
|
const key = generateFallbackMessageKey(message);
|
|
7943
7979
|
if (message.platformMessageId) {
|
|
7944
7980
|
if (state.platformMessageIds.has(message.platformMessageId)) return true;
|
|
7945
7981
|
state.platformMessageIds.add(message.platformMessageId);
|
|
7982
|
+
if (state.preserveFallbackMultiplicity) {
|
|
7983
|
+
const balance = state.fallbackMultiplicityBalances.get(key) ?? 0;
|
|
7984
|
+
if (balance > 0) {
|
|
7985
|
+
setFallbackMultiplicityBalance(state, key, balance - 1);
|
|
7986
|
+
return true;
|
|
7987
|
+
}
|
|
7988
|
+
setFallbackMultiplicityBalance(state, key, balance - 1);
|
|
7989
|
+
return false;
|
|
7990
|
+
}
|
|
7946
7991
|
if (state.fallbackOnlyKeys.delete(key)) return true;
|
|
7947
7992
|
state.fallbackKeys.add(key);
|
|
7948
7993
|
return false;
|
|
7949
7994
|
}
|
|
7995
|
+
if (state.preserveFallbackMultiplicity) {
|
|
7996
|
+
const balance = state.fallbackMultiplicityBalances.get(key) ?? 0;
|
|
7997
|
+
if (balance < 0) {
|
|
7998
|
+
setFallbackMultiplicityBalance(state, key, balance + 1);
|
|
7999
|
+
return true;
|
|
8000
|
+
}
|
|
8001
|
+
setFallbackMultiplicityBalance(state, key, balance + 1);
|
|
8002
|
+
return false;
|
|
8003
|
+
}
|
|
7950
8004
|
if (state.fallbackKeys.has(key)) return true;
|
|
7951
8005
|
state.fallbackKeys.add(key);
|
|
7952
8006
|
state.fallbackOnlyKeys.add(key);
|
|
@@ -7954,6 +8008,9 @@ function registerMessageAndCheckDuplicate(message, state) {
|
|
|
7954
8008
|
}
|
|
7955
8009
|
|
|
7956
8010
|
// ../../packages/node-runtime/src/import/streaming-importer.ts
|
|
8011
|
+
function shouldPreserveFallbackMultiplicity(formatId) {
|
|
8012
|
+
return formatId === PARSER_FORMAT_IDS.WEFLOW || formatId === PARSER_FORMAT_IDS.ECHOTRACE;
|
|
8013
|
+
}
|
|
7957
8014
|
var BATCH_COMMIT_SIZE = 5e4;
|
|
7958
8015
|
var CHECKPOINT_INTERVAL = 2e5;
|
|
7959
8016
|
var SYSTEM_SENDER_ID2 = "SYSTEM";
|
|
@@ -8251,7 +8308,9 @@ async function streamImportSingle(filePath, deps, formatFeature, formatOptions,
|
|
|
8251
8308
|
skippedInvalidTimestamp: 0,
|
|
8252
8309
|
skippedNoType: 0
|
|
8253
8310
|
};
|
|
8254
|
-
const dedupState = createMessageDedupState(
|
|
8311
|
+
const dedupState = createMessageDedupState([], [], [], {
|
|
8312
|
+
preserveFallbackMultiplicity: shouldPreserveFallbackMultiplicity(formatFeature.id)
|
|
8313
|
+
});
|
|
8255
8314
|
logger?.info("Starting streamParseFile...");
|
|
8256
8315
|
const parsePipelineStartedAt = now();
|
|
8257
8316
|
let parserTimingFinished = false;
|
|
@@ -8591,7 +8650,9 @@ async function analyzeNewImport(filePath, onProgress, options2) {
|
|
|
8591
8650
|
}
|
|
8592
8651
|
let meta = null;
|
|
8593
8652
|
const memberSet = /* @__PURE__ */ new Set();
|
|
8594
|
-
const dedupState = createMessageDedupState(
|
|
8653
|
+
const dedupState = createMessageDedupState([], [], [], {
|
|
8654
|
+
preserveFallbackMultiplicity: shouldPreserveFallbackMultiplicity(formatFeature.id)
|
|
8655
|
+
});
|
|
8595
8656
|
let totalMessages = 0;
|
|
8596
8657
|
let newMessageCount = 0;
|
|
8597
8658
|
let duplicateCount = 0;
|
|
@@ -8816,8 +8877,9 @@ function createExistingMessageLookup(db) {
|
|
|
8816
8877
|
db,
|
|
8817
8878
|
maxMessageId,
|
|
8818
8879
|
hasFallbackOnlyMessages,
|
|
8819
|
-
|
|
8880
|
+
consumedFallbackOnlyOccurrenceCounts: /* @__PURE__ */ new Map(),
|
|
8820
8881
|
bridgedPlatformMessageIds: /* @__PURE__ */ new Set(),
|
|
8882
|
+
consumedFallbackOccurrenceCounts: /* @__PURE__ */ new Map(),
|
|
8821
8883
|
candidateStatement: db.prepare(
|
|
8822
8884
|
`SELECT msg.type, msg.content, msg.reply_to_message_id, msg.platform_message_id
|
|
8823
8885
|
FROM member m
|
|
@@ -8868,28 +8930,36 @@ function candidateFallbackKey(message, candidate) {
|
|
|
8868
8930
|
replyToMessageId: candidate.reply_to_message_id ?? void 0
|
|
8869
8931
|
});
|
|
8870
8932
|
}
|
|
8871
|
-
function isExistingMessageDuplicate(lookup, message, existingBatchPlatformIds, candidateCache) {
|
|
8933
|
+
function isExistingMessageDuplicate(lookup, message, existingBatchPlatformIds, candidateCache, preserveFallbackMultiplicity) {
|
|
8872
8934
|
if (message.platformMessageId && (existingBatchPlatformIds.has(message.platformMessageId) || lookup.bridgedPlatformMessageIds.has(message.platformMessageId))) {
|
|
8873
8935
|
return true;
|
|
8874
8936
|
}
|
|
8875
8937
|
const fallbackKey = generateFallbackMessageKey(message);
|
|
8876
8938
|
if (message.platformMessageId) {
|
|
8877
|
-
if (!lookup.hasFallbackOnlyMessages
|
|
8878
|
-
const
|
|
8939
|
+
if (!lookup.hasFallbackOnlyMessages) return false;
|
|
8940
|
+
const matchingFallbackOnlyCount = getExistingCandidates(lookup, message, candidateCache).filter(
|
|
8879
8941
|
(candidate) => !candidate.platform_message_id && candidateFallbackKey(message, candidate) === fallbackKey
|
|
8880
|
-
);
|
|
8942
|
+
).length;
|
|
8943
|
+
const consumedCount2 = lookup.consumedFallbackOnlyOccurrenceCounts.get(fallbackKey) ?? 0;
|
|
8944
|
+
const matchesFallbackOnly = preserveFallbackMultiplicity ? consumedCount2 < matchingFallbackOnlyCount : consumedCount2 === 0 && matchingFallbackOnlyCount > 0;
|
|
8881
8945
|
if (matchesFallbackOnly) {
|
|
8882
|
-
lookup.
|
|
8946
|
+
lookup.consumedFallbackOnlyOccurrenceCounts.set(fallbackKey, consumedCount2 + 1);
|
|
8883
8947
|
lookup.bridgedPlatformMessageIds.add(message.platformMessageId);
|
|
8884
8948
|
}
|
|
8885
8949
|
return matchesFallbackOnly;
|
|
8886
8950
|
}
|
|
8887
|
-
|
|
8951
|
+
const matchingCount = getExistingCandidates(lookup, message, candidateCache).filter(
|
|
8888
8952
|
(candidate) => candidateFallbackKey(message, candidate) === fallbackKey
|
|
8889
|
-
);
|
|
8953
|
+
).length;
|
|
8954
|
+
if (!preserveFallbackMultiplicity) return matchingCount > 0;
|
|
8955
|
+
const consumedCount = lookup.consumedFallbackOccurrenceCounts.get(fallbackKey) ?? 0;
|
|
8956
|
+
if (consumedCount >= matchingCount) return false;
|
|
8957
|
+
lookup.consumedFallbackOccurrenceCounts.set(fallbackKey, consumedCount + 1);
|
|
8958
|
+
return true;
|
|
8890
8959
|
}
|
|
8891
|
-
function isDuplicate(lookup, incomingState, message, existingBatchPlatformIds, candidateCache) {
|
|
8892
|
-
if (isExistingMessageDuplicate(lookup, message, existingBatchPlatformIds, candidateCache))
|
|
8960
|
+
function isDuplicate(lookup, incomingState, message, existingBatchPlatformIds, candidateCache, preserveFallbackMultiplicity) {
|
|
8961
|
+
if (isExistingMessageDuplicate(lookup, message, existingBatchPlatformIds, candidateCache, preserveFallbackMultiplicity))
|
|
8962
|
+
return true;
|
|
8893
8963
|
return registerMessageAndCheckDuplicate(message, incomingState);
|
|
8894
8964
|
}
|
|
8895
8965
|
function normalizeImportTimestamp(timestamp) {
|
|
@@ -8912,9 +8982,10 @@ async function analyzeIncrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8912
8982
|
let duplicateCount = 0;
|
|
8913
8983
|
let platform = formatFeature.platform;
|
|
8914
8984
|
const mapPlatformId = createPlatformIdMapper(options2?.senderPlatformIdMappings);
|
|
8985
|
+
const preserveFallbackMultiplicity = shouldPreserveFallbackMultiplicity(formatFeature.id);
|
|
8915
8986
|
try {
|
|
8916
8987
|
const existingLookup = createExistingMessageLookup(db);
|
|
8917
|
-
const incomingState = createMessageDedupState();
|
|
8988
|
+
const incomingState = createMessageDedupState([], [], [], { preserveFallbackMultiplicity });
|
|
8918
8989
|
await streamParseFile(
|
|
8919
8990
|
filePath,
|
|
8920
8991
|
{
|
|
@@ -8946,7 +9017,8 @@ async function analyzeIncrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8946
9017
|
incomingState,
|
|
8947
9018
|
{ ...msg, timestamp },
|
|
8948
9019
|
existingBatchPlatformIds,
|
|
8949
|
-
candidateCache
|
|
9020
|
+
candidateCache,
|
|
9021
|
+
preserveFallbackMultiplicity
|
|
8950
9022
|
)) {
|
|
8951
9023
|
duplicateCount++;
|
|
8952
9024
|
} else {
|
|
@@ -8976,6 +9048,7 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8976
9048
|
const metaUpdateMode = options2?.metaUpdateMode ?? "patch";
|
|
8977
9049
|
const memberUpdateMode = options2?.memberUpdateMode ?? "upsert";
|
|
8978
9050
|
const mapPlatformId = createPlatformIdMapper(options2?.senderPlatformIdMappings);
|
|
9051
|
+
const preserveFallbackMultiplicity = shouldPreserveFallbackMultiplicity(formatFeature.id);
|
|
8979
9052
|
try {
|
|
8980
9053
|
let trackError2 = function(index, reason, detail) {
|
|
8981
9054
|
errorCount++;
|
|
@@ -8986,7 +9059,7 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
8986
9059
|
};
|
|
8987
9060
|
var trackError = trackError2;
|
|
8988
9061
|
const existingLookup = createExistingMessageLookup(db);
|
|
8989
|
-
const incomingState = createMessageDedupState();
|
|
9062
|
+
const incomingState = createMessageDedupState([], [], [], { preserveFallbackMultiplicity });
|
|
8990
9063
|
const memberIdMap = /* @__PURE__ */ new Map();
|
|
8991
9064
|
const existingMembers = db.prepare("SELECT id, platform_id FROM member").all();
|
|
8992
9065
|
for (const m of existingMembers) {
|
|
@@ -9105,7 +9178,8 @@ async function incrementalImport(sessionId, filePath, deps, options2) {
|
|
|
9105
9178
|
incomingState,
|
|
9106
9179
|
{ ...msg, timestamp },
|
|
9107
9180
|
existingBatchPlatformIds,
|
|
9108
|
-
candidateCache
|
|
9181
|
+
candidateCache,
|
|
9182
|
+
preserveFallbackMultiplicity
|
|
9109
9183
|
)) {
|
|
9110
9184
|
duplicateCount++;
|
|
9111
9185
|
continue;
|
|
@@ -11693,7 +11767,8 @@ function buildContactsSignature(adapter, timeRangePreset) {
|
|
|
11693
11767
|
`algorithm:${CONTACTS_ALGORITHM_VERSION}`,
|
|
11694
11768
|
`range:${normalizeContactsTimeRangePreset(timeRangePreset)}`
|
|
11695
11769
|
];
|
|
11696
|
-
|
|
11770
|
+
const sessionIds = adapter.listSessionCandidateIds?.() ?? adapter.listSessionIds();
|
|
11771
|
+
for (const sessionId of [...sessionIds].sort()) {
|
|
11697
11772
|
const dbPath = adapter.getDbPath(sessionId);
|
|
11698
11773
|
parts.push(`${sessionId}:${getDbFileVersion(dbPath)}`);
|
|
11699
11774
|
}
|
|
@@ -11941,6 +12016,7 @@ var DefaultContactsService = class {
|
|
|
11941
12016
|
}
|
|
11942
12017
|
ensureTaskStarted(signature, timeRangePreset) {
|
|
11943
12018
|
if (this.inFlight) return;
|
|
12019
|
+
const sessionIds = this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds();
|
|
11944
12020
|
const taskId = `contacts_${this.now()}_${Math.random().toString(36).slice(2)}`;
|
|
11945
12021
|
this.task = {
|
|
11946
12022
|
id: taskId,
|
|
@@ -11948,7 +12024,7 @@ var DefaultContactsService = class {
|
|
|
11948
12024
|
startedAt: this.now(),
|
|
11949
12025
|
finishedAt: null,
|
|
11950
12026
|
processedSessions: 0,
|
|
11951
|
-
totalSessions:
|
|
12027
|
+
totalSessions: sessionIds.length,
|
|
11952
12028
|
timeRangePreset
|
|
11953
12029
|
};
|
|
11954
12030
|
const abortController = new AbortController();
|
|
@@ -12220,6 +12296,1821 @@ __export(contacts_exports, {
|
|
|
12220
12296
|
getContactsFactsCacheDir: () => getContactsFactsCacheDir
|
|
12221
12297
|
});
|
|
12222
12298
|
|
|
12299
|
+
// ../../packages/node-runtime/src/services/cross-chat-analysis/service.ts
|
|
12300
|
+
import { createHash as createHash2 } from "crypto";
|
|
12301
|
+
var DEFAULT_MAX_SESSIONS = 24;
|
|
12302
|
+
var MAX_MAX_SESSIONS = 100;
|
|
12303
|
+
var DEFAULT_MAX_EVIDENCE = 1e3;
|
|
12304
|
+
var MAX_MAX_EVIDENCE = 1e3;
|
|
12305
|
+
var DEFAULT_MAX_WALL_TIME_MS = 8e3;
|
|
12306
|
+
var MAX_MAX_WALL_TIME_MS = 3e4;
|
|
12307
|
+
var DEFAULT_CONTEXT_SIZE = 10;
|
|
12308
|
+
var MAX_CONTEXT_SIZE = 50;
|
|
12309
|
+
var RECENT_SESSION_MESSAGE_LIMIT = 200;
|
|
12310
|
+
var RECENT_SESSION_SUMMARY_LIMIT = 5;
|
|
12311
|
+
var SEARCH_CONTEXT_BEFORE = 2;
|
|
12312
|
+
var SEARCH_CONTEXT_AFTER = 2;
|
|
12313
|
+
var MAX_RECENT_DAYS = 3650;
|
|
12314
|
+
var CONTACT_SESSIONS_ALGORITHM_VERSION = "contact-sessions-v1";
|
|
12315
|
+
var DEFAULT_INSPECTION_PAGE_SIZE = 50;
|
|
12316
|
+
var MAX_INSPECTION_PAGE_SIZE = 100;
|
|
12317
|
+
var SHARED_INTERACTIONS_ALGORITHM_VERSION = "shared-interactions-v1";
|
|
12318
|
+
var PROXIMITY_ALGORITHM_VERSION = "lookahead-3-decay-120-gap-1800-v2";
|
|
12319
|
+
var DEFAULT_SHARED_INTERACTIONS_PAGE_SIZE = 20;
|
|
12320
|
+
var MAX_SHARED_INTERACTIONS_PAGE_SIZE = 50;
|
|
12321
|
+
var DEFAULT_MAX_ANCHORS_PER_PAIR = 4;
|
|
12322
|
+
var MAX_MAX_ANCHORS_PER_PAIR = 8;
|
|
12323
|
+
var PRIVATE_CONTACTS_RANKING_ALGORITHM_VERSION = "private-contacts-ranking-v1";
|
|
12324
|
+
var GROUP_SESSIONS_RANKING_ALGORITHM_VERSION = "group-sessions-ranking-v1";
|
|
12325
|
+
var DEFAULT_RANKING_LIMIT = 10;
|
|
12326
|
+
var MAX_RANKING_LIMIT = 50;
|
|
12327
|
+
var RANKING_MAX_WALL_TIME_MS = 3e4;
|
|
12328
|
+
var OVERVIEW_TOP_MEMBERS_LIMIT = 5;
|
|
12329
|
+
function createCrossChatAnalysisService(deps) {
|
|
12330
|
+
return new DefaultCrossChatAnalysisService(deps);
|
|
12331
|
+
}
|
|
12332
|
+
var DefaultCrossChatAnalysisService = class {
|
|
12333
|
+
constructor(deps) {
|
|
12334
|
+
this.deps = deps;
|
|
12335
|
+
}
|
|
12336
|
+
deps;
|
|
12337
|
+
lookupContact(rawQuery) {
|
|
12338
|
+
const query = rawQuery.trim();
|
|
12339
|
+
const response = this.deps.contactsService.getContactsPage({
|
|
12340
|
+
acceptStale: true,
|
|
12341
|
+
page: 1,
|
|
12342
|
+
pageSize: 200,
|
|
12343
|
+
query,
|
|
12344
|
+
timeRangePreset: "all"
|
|
12345
|
+
});
|
|
12346
|
+
const normalizedQuery = normalizeContactName(query);
|
|
12347
|
+
const exactMatches = response.contacts.filter(
|
|
12348
|
+
(contact) => [contact.displayName, ...contact.aliases].some((name) => normalizeContactName(name) === normalizedQuery)
|
|
12349
|
+
);
|
|
12350
|
+
const matchedContacts = exactMatches.length > 0 ? exactMatches : response.contacts;
|
|
12351
|
+
const totalCandidates = exactMatches.length > 0 ? exactMatches.length : response.pagination.total;
|
|
12352
|
+
const candidates = matchedContacts.slice(0, 8).map((contact) => {
|
|
12353
|
+
const detail = this.deps.contactsService.getContactDetail(contact.key, {
|
|
12354
|
+
acceptStale: true,
|
|
12355
|
+
timeRangePreset: "all"
|
|
12356
|
+
});
|
|
12357
|
+
return {
|
|
12358
|
+
contactKey: contact.key,
|
|
12359
|
+
displayName: contact.displayName,
|
|
12360
|
+
platform: contact.platform,
|
|
12361
|
+
aliases: contact.aliases,
|
|
12362
|
+
sourceSessions: detail.contact?.sourceSessions.map((session) => ({
|
|
12363
|
+
id: session.id,
|
|
12364
|
+
name: session.name,
|
|
12365
|
+
type: session.type
|
|
12366
|
+
})) ?? []
|
|
12367
|
+
};
|
|
12368
|
+
});
|
|
12369
|
+
const status = totalCandidates === 1 ? "resolved" : totalCandidates > 1 ? "ambiguous" : response.cache.status === "missing" || response.task?.status === "running" ? "unavailable" : "not_found";
|
|
12370
|
+
return {
|
|
12371
|
+
query,
|
|
12372
|
+
status,
|
|
12373
|
+
cacheStatus: response.cache.status,
|
|
12374
|
+
totalCandidates,
|
|
12375
|
+
candidates
|
|
12376
|
+
};
|
|
12377
|
+
}
|
|
12378
|
+
async resolveEntities(refs, options2 = {}) {
|
|
12379
|
+
throwIfAborted(options2.signal);
|
|
12380
|
+
const contacts = [];
|
|
12381
|
+
const sessions = [];
|
|
12382
|
+
const unresolved = [];
|
|
12383
|
+
const candidateSessionIds = /* @__PURE__ */ new Set();
|
|
12384
|
+
const resolvedSessionIds = /* @__PURE__ */ new Set();
|
|
12385
|
+
const failedSessionIds = /* @__PURE__ */ new Set();
|
|
12386
|
+
for (const ref of refs) {
|
|
12387
|
+
throwIfAborted(options2.signal);
|
|
12388
|
+
if (ref.type === "session") {
|
|
12389
|
+
candidateSessionIds.add(ref.sessionId);
|
|
12390
|
+
const descriptor = this.tryGetSessionDescriptor(ref.sessionId);
|
|
12391
|
+
if (descriptor) {
|
|
12392
|
+
resolvedSessionIds.add(ref.sessionId);
|
|
12393
|
+
sessions.push({ ref, status: "resolved", session: descriptor });
|
|
12394
|
+
} else {
|
|
12395
|
+
failedSessionIds.add(ref.sessionId);
|
|
12396
|
+
sessions.push({ ref, status: "unresolved" });
|
|
12397
|
+
unresolved.push({ ref, reason: "session_not_found" });
|
|
12398
|
+
}
|
|
12399
|
+
await yieldToEventLoop2();
|
|
12400
|
+
continue;
|
|
12401
|
+
}
|
|
12402
|
+
const detail = this.deps.contactsService.getContactDetail(ref.contactKey, {
|
|
12403
|
+
acceptStale: true,
|
|
12404
|
+
timeRangePreset: "all"
|
|
12405
|
+
});
|
|
12406
|
+
const contact = detail.contact;
|
|
12407
|
+
if (!contact) {
|
|
12408
|
+
contacts.push({
|
|
12409
|
+
ref,
|
|
12410
|
+
status: "unresolved",
|
|
12411
|
+
cacheStatus: detail.cache.status,
|
|
12412
|
+
sessions: [],
|
|
12413
|
+
unresolvedSessionIds: [],
|
|
12414
|
+
failedSessionIds: []
|
|
12415
|
+
});
|
|
12416
|
+
unresolved.push({
|
|
12417
|
+
ref,
|
|
12418
|
+
reason: detail.cache.status === "missing" ? "contact_snapshot_missing" : "contact_not_found"
|
|
12419
|
+
});
|
|
12420
|
+
await yieldToEventLoop2();
|
|
12421
|
+
continue;
|
|
12422
|
+
}
|
|
12423
|
+
const sourceSessions = contact.sessionScoped ? contact.sourceSessions.filter((source) => source.id === contact.sessionId) : contact.sourceSessions;
|
|
12424
|
+
const resolvedContactSessions = [];
|
|
12425
|
+
const unresolvedContactSessions = [];
|
|
12426
|
+
const failedContactSessions = [];
|
|
12427
|
+
for (const source of sourceSessions) {
|
|
12428
|
+
throwIfAborted(options2.signal);
|
|
12429
|
+
candidateSessionIds.add(source.id);
|
|
12430
|
+
try {
|
|
12431
|
+
const db = this.deps.adapter.openReadonly(source.id);
|
|
12432
|
+
if (!db) {
|
|
12433
|
+
failedContactSessions.push(source.id);
|
|
12434
|
+
failedSessionIds.add(source.id);
|
|
12435
|
+
continue;
|
|
12436
|
+
}
|
|
12437
|
+
const descriptor = getSessionDescriptor(source.id, db);
|
|
12438
|
+
const member = resolveContactMember(db, contact.platformId);
|
|
12439
|
+
if (!descriptor || !member) {
|
|
12440
|
+
unresolvedContactSessions.push(source.id);
|
|
12441
|
+
continue;
|
|
12442
|
+
}
|
|
12443
|
+
resolvedContactSessions.push({
|
|
12444
|
+
...descriptor,
|
|
12445
|
+
memberId: member.id,
|
|
12446
|
+
memberPlatformId: member.platformId,
|
|
12447
|
+
memberName: member.name
|
|
12448
|
+
});
|
|
12449
|
+
resolvedSessionIds.add(source.id);
|
|
12450
|
+
} catch (error) {
|
|
12451
|
+
failedContactSessions.push(source.id);
|
|
12452
|
+
failedSessionIds.add(source.id);
|
|
12453
|
+
appLogger.warn("cross-chat-analysis", `failed to resolve contact in source session: ${source.id}`, error);
|
|
12454
|
+
} finally {
|
|
12455
|
+
await yieldToEventLoop2();
|
|
12456
|
+
}
|
|
12457
|
+
}
|
|
12458
|
+
const status = resolvedContactSessions.length === 0 ? "unresolved" : unresolvedContactSessions.length > 0 || failedContactSessions.length > 0 ? "partial" : "resolved";
|
|
12459
|
+
contacts.push({
|
|
12460
|
+
ref,
|
|
12461
|
+
status,
|
|
12462
|
+
cacheStatus: detail.cache.status,
|
|
12463
|
+
sessions: resolvedContactSessions,
|
|
12464
|
+
unresolvedSessionIds: unresolvedContactSessions,
|
|
12465
|
+
failedSessionIds: failedContactSessions
|
|
12466
|
+
});
|
|
12467
|
+
if (status === "unresolved") unresolved.push({ ref, reason: "member_not_found" });
|
|
12468
|
+
if (sourceSessions.length === 0) await yieldToEventLoop2();
|
|
12469
|
+
}
|
|
12470
|
+
throwIfAborted(options2.signal);
|
|
12471
|
+
return {
|
|
12472
|
+
contacts,
|
|
12473
|
+
sessions,
|
|
12474
|
+
unresolved,
|
|
12475
|
+
coverage: {
|
|
12476
|
+
requestedEntities: refs.length,
|
|
12477
|
+
resolvedEntities: contacts.filter((item) => item.status !== "unresolved").length + sessions.filter((item) => item.status === "resolved").length,
|
|
12478
|
+
candidateSessions: candidateSessionIds.size,
|
|
12479
|
+
resolvedSessions: resolvedSessionIds.size,
|
|
12480
|
+
failedSessions: failedSessionIds.size
|
|
12481
|
+
}
|
|
12482
|
+
};
|
|
12483
|
+
}
|
|
12484
|
+
async inspectContactSessions(request, options2 = {}) {
|
|
12485
|
+
throwIfAborted(options2.signal);
|
|
12486
|
+
const contactKey = request.contactKey.trim();
|
|
12487
|
+
if (!contactKey) throw new Error("contactKey is required");
|
|
12488
|
+
const rangeRequest = normalizeInspectionRangeRequest(request.startTs, request.endTs, request.recentDays);
|
|
12489
|
+
const requestedRange = resolveInspectionRange(rangeRequest, () => this.now());
|
|
12490
|
+
const includeRosterOnly = request.includeRosterOnly !== false;
|
|
12491
|
+
const pageSize = clampInteger(request.pageSize, DEFAULT_INSPECTION_PAGE_SIZE, 1, MAX_INSPECTION_PAGE_SIZE);
|
|
12492
|
+
const maxWallTimeMs = clampInteger(request.maxWallTimeMs, DEFAULT_MAX_WALL_TIME_MS, 1, MAX_MAX_WALL_TIME_MS);
|
|
12493
|
+
const detail = this.deps.contactsService.getContactDetail(contactKey, {
|
|
12494
|
+
acceptStale: true,
|
|
12495
|
+
timeRangePreset: "all"
|
|
12496
|
+
});
|
|
12497
|
+
const contact = detail.contact;
|
|
12498
|
+
if (!contact) {
|
|
12499
|
+
return emptyContactSessionsResult(detail.cache.status, requestedRange);
|
|
12500
|
+
}
|
|
12501
|
+
const startedAt = this.now();
|
|
12502
|
+
const candidateSessionIds = contact.sessionScoped ? contact.sessionId ? [contact.sessionId] : [] : [...new Set(this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds())].sort(
|
|
12503
|
+
(left, right) => left.localeCompare(right)
|
|
12504
|
+
);
|
|
12505
|
+
const cursor = parseInspectionCursor(
|
|
12506
|
+
request.cursor,
|
|
12507
|
+
(range2) => createInspectionFingerprint({
|
|
12508
|
+
candidateSessionIds,
|
|
12509
|
+
contactKey,
|
|
12510
|
+
rangeRequest,
|
|
12511
|
+
startTs: range2.startTs,
|
|
12512
|
+
endTs: range2.endTs,
|
|
12513
|
+
includeRosterOnly
|
|
12514
|
+
}),
|
|
12515
|
+
candidateSessionIds,
|
|
12516
|
+
requestedRange
|
|
12517
|
+
);
|
|
12518
|
+
const range = cursor.range;
|
|
12519
|
+
const sessions = [];
|
|
12520
|
+
const failedSessionIds = [];
|
|
12521
|
+
const truncatedReasons = /* @__PURE__ */ new Set();
|
|
12522
|
+
let scannedSessions = 0;
|
|
12523
|
+
let nextCandidateIndex = cursor.nextCandidateIndex;
|
|
12524
|
+
let dataEarliestMessageTs = null;
|
|
12525
|
+
let dataLatestMessageTs = null;
|
|
12526
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: candidateSessionIds.length });
|
|
12527
|
+
while (nextCandidateIndex < candidateSessionIds.length && sessions.length < pageSize) {
|
|
12528
|
+
throwIfAborted(options2.signal);
|
|
12529
|
+
if (this.now() - startedAt >= maxWallTimeMs) {
|
|
12530
|
+
truncatedReasons.add("time_budget");
|
|
12531
|
+
break;
|
|
12532
|
+
}
|
|
12533
|
+
const sessionId = candidateSessionIds[nextCandidateIndex];
|
|
12534
|
+
options2.onProgress?.({
|
|
12535
|
+
processedSessions: nextCandidateIndex,
|
|
12536
|
+
totalSessions: candidateSessionIds.length,
|
|
12537
|
+
currentSessionId: sessionId
|
|
12538
|
+
});
|
|
12539
|
+
try {
|
|
12540
|
+
const db = this.deps.adapter.openReadonly(sessionId);
|
|
12541
|
+
if (!db) {
|
|
12542
|
+
failedSessionIds.push(sessionId);
|
|
12543
|
+
} else {
|
|
12544
|
+
const descriptor = getSessionDescriptor(sessionId, db);
|
|
12545
|
+
const meta = getSessionMeta(db);
|
|
12546
|
+
if (descriptor && meta?.platform === contact.platform) {
|
|
12547
|
+
const member = getNonSystemMembersForContacts(db).find((item) => item.platformId === contact.platformId);
|
|
12548
|
+
if (member) {
|
|
12549
|
+
const facts = getParticipantSessionFacts(db, member.id, range);
|
|
12550
|
+
if (includeRosterOnly || facts.ownMessageCount > 0) {
|
|
12551
|
+
sessions.push({
|
|
12552
|
+
...descriptor,
|
|
12553
|
+
memberId: member.id,
|
|
12554
|
+
memberName: member.name,
|
|
12555
|
+
presence: facts.ownMessageCount > 0 ? "spoke" : "roster_only",
|
|
12556
|
+
presenceObservedInRange: facts.ownMessageCount > 0,
|
|
12557
|
+
ownMessageCount: facts.ownMessageCount,
|
|
12558
|
+
sessionMessageCount: facts.sessionMessageCount,
|
|
12559
|
+
messageShare: facts.sessionMessageCount > 0 ? facts.ownMessageCount / facts.sessionMessageCount : null,
|
|
12560
|
+
firstOwnMessageTs: facts.firstOwnMessageTs,
|
|
12561
|
+
lastOwnMessageTs: facts.lastOwnMessageTs,
|
|
12562
|
+
activeDays: facts.activeDays,
|
|
12563
|
+
memberCount: descriptor.sessionType === "group" /* GROUP */ ? facts.memberCount : null,
|
|
12564
|
+
sessionFirstMessageTs: facts.sessionFirstMessageTs,
|
|
12565
|
+
lastMessageTs: facts.sessionLastMessageTs
|
|
12566
|
+
});
|
|
12567
|
+
dataEarliestMessageTs = minNullable(dataEarliestMessageTs, facts.sessionFirstMessageTs);
|
|
12568
|
+
dataLatestMessageTs = maxNullable(dataLatestMessageTs, facts.sessionLastMessageTs);
|
|
12569
|
+
}
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12572
|
+
}
|
|
12573
|
+
} catch (error) {
|
|
12574
|
+
failedSessionIds.push(sessionId);
|
|
12575
|
+
appLogger.warn("cross-chat-analysis", `failed to inspect contact session: ${sessionId}`, error);
|
|
12576
|
+
} finally {
|
|
12577
|
+
scannedSessions++;
|
|
12578
|
+
nextCandidateIndex++;
|
|
12579
|
+
}
|
|
12580
|
+
await yieldToEventLoop2();
|
|
12581
|
+
}
|
|
12582
|
+
if (sessions.length >= pageSize && nextCandidateIndex < candidateSessionIds.length) {
|
|
12583
|
+
truncatedReasons.add("page_size");
|
|
12584
|
+
}
|
|
12585
|
+
const complete = nextCandidateIndex >= candidateSessionIds.length;
|
|
12586
|
+
const nextCursor = complete ? null : createInspectionCursor(cursor.fingerprint, candidateSessionIds[nextCandidateIndex - 1] ?? null, range);
|
|
12587
|
+
options2.onProgress?.({ processedSessions: nextCandidateIndex, totalSessions: candidateSessionIds.length });
|
|
12588
|
+
return {
|
|
12589
|
+
algorithmVersion: CONTACT_SESSIONS_ALGORITHM_VERSION,
|
|
12590
|
+
contact: {
|
|
12591
|
+
contactKey: contact.key,
|
|
12592
|
+
displayName: contact.displayName,
|
|
12593
|
+
platform: contact.platform,
|
|
12594
|
+
sessionScoped: contact.sessionScoped
|
|
12595
|
+
},
|
|
12596
|
+
appliedRange: {
|
|
12597
|
+
...range,
|
|
12598
|
+
dataEarliestMessageTs,
|
|
12599
|
+
dataLatestMessageTs
|
|
12600
|
+
},
|
|
12601
|
+
summary: summarizeContactSessions(sessions, !request.cursor && complete),
|
|
12602
|
+
sessions,
|
|
12603
|
+
coverage: {
|
|
12604
|
+
candidateSessions: candidateSessionIds.length,
|
|
12605
|
+
scannedSessions,
|
|
12606
|
+
matchedSessions: sessions.length,
|
|
12607
|
+
returnedSessions: sessions.length,
|
|
12608
|
+
failedSessions: failedSessionIds.length,
|
|
12609
|
+
failedSessionIds,
|
|
12610
|
+
complete,
|
|
12611
|
+
nextCursor,
|
|
12612
|
+
truncated: truncatedReasons.size > 0,
|
|
12613
|
+
truncatedReasons: [...truncatedReasons],
|
|
12614
|
+
contactCacheStatus: detail.cache.status
|
|
12615
|
+
}
|
|
12616
|
+
};
|
|
12617
|
+
}
|
|
12618
|
+
async inspectSharedInteractions(request, options2 = {}) {
|
|
12619
|
+
throwIfAborted(options2.signal);
|
|
12620
|
+
const participantRefs = normalizeParticipantRefs(request.participants);
|
|
12621
|
+
const rangeRequest = normalizeInspectionRangeRequest(request.startTs, request.endTs, request.recentDays);
|
|
12622
|
+
const requestedRange = resolveInspectionRange(rangeRequest, () => this.now());
|
|
12623
|
+
const pageSize = clampInteger(
|
|
12624
|
+
request.pageSize,
|
|
12625
|
+
DEFAULT_SHARED_INTERACTIONS_PAGE_SIZE,
|
|
12626
|
+
1,
|
|
12627
|
+
MAX_SHARED_INTERACTIONS_PAGE_SIZE
|
|
12628
|
+
);
|
|
12629
|
+
const maxAnchorsPerPair = clampInteger(
|
|
12630
|
+
request.maxAnchorsPerPair,
|
|
12631
|
+
DEFAULT_MAX_ANCHORS_PER_PAIR,
|
|
12632
|
+
0,
|
|
12633
|
+
MAX_MAX_ANCHORS_PER_PAIR
|
|
12634
|
+
);
|
|
12635
|
+
const maxWallTimeMs = clampInteger(request.maxWallTimeMs, DEFAULT_MAX_WALL_TIME_MS, 1, MAX_MAX_WALL_TIME_MS);
|
|
12636
|
+
const startedAt = this.now();
|
|
12637
|
+
const resolvedParticipants = participantRefs.map((ref, index) => {
|
|
12638
|
+
if (ref.type === "owner") {
|
|
12639
|
+
return {
|
|
12640
|
+
index,
|
|
12641
|
+
ref,
|
|
12642
|
+
status: "resolved",
|
|
12643
|
+
displayName: "owner",
|
|
12644
|
+
platform: void 0,
|
|
12645
|
+
cacheStatus: void 0,
|
|
12646
|
+
contact: void 0
|
|
12647
|
+
};
|
|
12648
|
+
}
|
|
12649
|
+
const detail = this.deps.contactsService.getContactDetail(ref.contactKey, {
|
|
12650
|
+
acceptStale: true,
|
|
12651
|
+
timeRangePreset: "all"
|
|
12652
|
+
});
|
|
12653
|
+
return {
|
|
12654
|
+
index,
|
|
12655
|
+
ref,
|
|
12656
|
+
status: detail.contact ? "resolved" : "unresolved",
|
|
12657
|
+
displayName: detail.contact?.displayName ?? ref.contactKey,
|
|
12658
|
+
platform: detail.contact?.platform,
|
|
12659
|
+
cacheStatus: detail.cache.status,
|
|
12660
|
+
contact: detail.contact
|
|
12661
|
+
};
|
|
12662
|
+
});
|
|
12663
|
+
const unresolvedParticipantIndexes = resolvedParticipants.filter((participant) => participant.status === "unresolved").map((participant) => participant.index);
|
|
12664
|
+
const publicParticipants = resolvedParticipants.map(({ contact: _contact, ...participant }) => participant);
|
|
12665
|
+
if (unresolvedParticipantIndexes.length > 0) {
|
|
12666
|
+
return emptySharedInteractionsResult(publicParticipants, unresolvedParticipantIndexes, requestedRange);
|
|
12667
|
+
}
|
|
12668
|
+
throwIfAborted(options2.signal);
|
|
12669
|
+
const sessionScopedIds = resolvedParticipants.flatMap(
|
|
12670
|
+
(participant) => participant.ref.type === "contact" && participant.contact?.sessionScoped && participant.contact.sessionId ? [participant.contact.sessionId] : []
|
|
12671
|
+
).filter((sessionId, index, all) => all.indexOf(sessionId) === index);
|
|
12672
|
+
const candidateSessionIds = (sessionScopedIds.length > 1 ? [] : sessionScopedIds.length === 1 ? sessionScopedIds : [...new Set(this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds())]).sort((left, right) => left.localeCompare(right));
|
|
12673
|
+
throwIfAborted(options2.signal);
|
|
12674
|
+
const cursor = parseInspectionCursor(
|
|
12675
|
+
request.cursor,
|
|
12676
|
+
(range2) => createInspectionFingerprint({
|
|
12677
|
+
candidateSessionIds,
|
|
12678
|
+
participantRefs,
|
|
12679
|
+
rangeRequest,
|
|
12680
|
+
startTs: range2.startTs,
|
|
12681
|
+
endTs: range2.endTs,
|
|
12682
|
+
maxAnchorsPerPair
|
|
12683
|
+
}),
|
|
12684
|
+
candidateSessionIds,
|
|
12685
|
+
requestedRange
|
|
12686
|
+
);
|
|
12687
|
+
const range = cursor.range;
|
|
12688
|
+
const sessions = [];
|
|
12689
|
+
const failedSessionIds = [];
|
|
12690
|
+
const truncatedReasons = /* @__PURE__ */ new Set();
|
|
12691
|
+
const ownerResolution = participantRefs.some((ref) => ref.type === "owner") ? { resolvedSessions: 0, missingOwnerSessions: 0, unresolvedOwnerSessions: 0 } : void 0;
|
|
12692
|
+
let identityCollisionSessions = 0;
|
|
12693
|
+
let scannedSessions = 0;
|
|
12694
|
+
let nextCandidateIndex = cursor.nextCandidateIndex;
|
|
12695
|
+
let dataEarliestMessageTs = null;
|
|
12696
|
+
let dataLatestMessageTs = null;
|
|
12697
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: candidateSessionIds.length });
|
|
12698
|
+
while (nextCandidateIndex < candidateSessionIds.length && sessions.length < pageSize) {
|
|
12699
|
+
throwIfAborted(options2.signal);
|
|
12700
|
+
if (this.now() - startedAt >= maxWallTimeMs) {
|
|
12701
|
+
truncatedReasons.add("time_budget");
|
|
12702
|
+
break;
|
|
12703
|
+
}
|
|
12704
|
+
const sessionId = candidateSessionIds[nextCandidateIndex];
|
|
12705
|
+
options2.onProgress?.({
|
|
12706
|
+
processedSessions: nextCandidateIndex,
|
|
12707
|
+
totalSessions: candidateSessionIds.length,
|
|
12708
|
+
currentSessionId: sessionId
|
|
12709
|
+
});
|
|
12710
|
+
try {
|
|
12711
|
+
const db = this.deps.adapter.openReadonly(sessionId);
|
|
12712
|
+
if (!db) {
|
|
12713
|
+
failedSessionIds.push(sessionId);
|
|
12714
|
+
} else {
|
|
12715
|
+
const descriptor = getSessionDescriptor(sessionId, db);
|
|
12716
|
+
const meta = getSessionMeta(db);
|
|
12717
|
+
if (descriptor && meta) {
|
|
12718
|
+
const members = getNonSystemMembersForContacts(db);
|
|
12719
|
+
const participantMembers = resolvedParticipants.map((participant) => {
|
|
12720
|
+
if (participant.ref.type === "owner") {
|
|
12721
|
+
if (!meta.ownerId?.trim()) {
|
|
12722
|
+
if (ownerResolution) ownerResolution.missingOwnerSessions++;
|
|
12723
|
+
return null;
|
|
12724
|
+
}
|
|
12725
|
+
const owner = members.find((member) => member.platformId === meta.ownerId);
|
|
12726
|
+
if (!owner) {
|
|
12727
|
+
if (ownerResolution) ownerResolution.unresolvedOwnerSessions++;
|
|
12728
|
+
return null;
|
|
12729
|
+
}
|
|
12730
|
+
if (ownerResolution) ownerResolution.resolvedSessions++;
|
|
12731
|
+
return owner;
|
|
12732
|
+
}
|
|
12733
|
+
if (!participant.contact || participant.contact.platform !== meta.platform) return null;
|
|
12734
|
+
return members.find((member) => member.platformId === participant.contact?.platformId) ?? null;
|
|
12735
|
+
});
|
|
12736
|
+
if (participantMembers.every((member) => member !== null)) {
|
|
12737
|
+
const resolvedMembers = participantMembers.filter((member) => member !== null);
|
|
12738
|
+
if (new Set(resolvedMembers.map((member) => member.id)).size !== resolvedMembers.length) {
|
|
12739
|
+
identityCollisionSessions++;
|
|
12740
|
+
} else {
|
|
12741
|
+
const facts = getParticipantSetInteractionFacts(
|
|
12742
|
+
db,
|
|
12743
|
+
resolvedMembers.map((member) => member.id),
|
|
12744
|
+
{
|
|
12745
|
+
...range,
|
|
12746
|
+
maxAnchorsPerPair
|
|
12747
|
+
}
|
|
12748
|
+
);
|
|
12749
|
+
const participantIndexByMemberId = new Map(
|
|
12750
|
+
resolvedMembers.map((member, index) => [member.id, resolvedParticipants[index].index])
|
|
12751
|
+
);
|
|
12752
|
+
const item = mapSharedInteractionSession(descriptor, resolvedMembers, participantIndexByMemberId, facts);
|
|
12753
|
+
sessions.push(item);
|
|
12754
|
+
dataEarliestMessageTs = minNullable(dataEarliestMessageTs, facts.sessionFirstMessageTs);
|
|
12755
|
+
dataLatestMessageTs = maxNullable(dataLatestMessageTs, facts.sessionLastMessageTs);
|
|
12756
|
+
if (facts.proximityStatus !== "complete") truncatedReasons.add("message_budget");
|
|
12757
|
+
}
|
|
12758
|
+
}
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
} catch (error) {
|
|
12762
|
+
failedSessionIds.push(sessionId);
|
|
12763
|
+
appLogger.warn("cross-chat-analysis", `failed to inspect shared interactions: ${sessionId}`, error);
|
|
12764
|
+
} finally {
|
|
12765
|
+
scannedSessions++;
|
|
12766
|
+
nextCandidateIndex++;
|
|
12767
|
+
}
|
|
12768
|
+
await yieldToEventLoop2();
|
|
12769
|
+
}
|
|
12770
|
+
if (sessions.length >= pageSize && nextCandidateIndex < candidateSessionIds.length) {
|
|
12771
|
+
truncatedReasons.add("page_size");
|
|
12772
|
+
}
|
|
12773
|
+
sessions.sort(compareSharedInteractionSessions);
|
|
12774
|
+
const complete = nextCandidateIndex >= candidateSessionIds.length;
|
|
12775
|
+
const nextCursor = complete ? null : createInspectionCursor(cursor.fingerprint, candidateSessionIds[nextCandidateIndex - 1] ?? null, range);
|
|
12776
|
+
options2.onProgress?.({ processedSessions: nextCandidateIndex, totalSessions: candidateSessionIds.length });
|
|
12777
|
+
return {
|
|
12778
|
+
algorithmVersion: SHARED_INTERACTIONS_ALGORITHM_VERSION,
|
|
12779
|
+
proximityAlgorithmVersion: PROXIMITY_ALGORITHM_VERSION,
|
|
12780
|
+
participants: publicParticipants,
|
|
12781
|
+
appliedRange: {
|
|
12782
|
+
...range,
|
|
12783
|
+
dataEarliestMessageTs,
|
|
12784
|
+
dataLatestMessageTs
|
|
12785
|
+
},
|
|
12786
|
+
summary: summarizeSharedInteractions(sessions, !request.cursor && complete),
|
|
12787
|
+
sessions,
|
|
12788
|
+
coverage: {
|
|
12789
|
+
candidateSessions: candidateSessionIds.length,
|
|
12790
|
+
scannedSessions,
|
|
12791
|
+
matchedSessions: sessions.length,
|
|
12792
|
+
returnedSessions: sessions.length,
|
|
12793
|
+
failedSessions: failedSessionIds.length,
|
|
12794
|
+
failedSessionIds,
|
|
12795
|
+
complete,
|
|
12796
|
+
nextCursor,
|
|
12797
|
+
truncated: truncatedReasons.size > 0,
|
|
12798
|
+
truncatedReasons: [...truncatedReasons],
|
|
12799
|
+
unresolvedParticipantIndexes,
|
|
12800
|
+
identityCollisionSessions,
|
|
12801
|
+
ownerResolution
|
|
12802
|
+
}
|
|
12803
|
+
};
|
|
12804
|
+
}
|
|
12805
|
+
async searchMessages(request, options2 = {}) {
|
|
12806
|
+
const keywords = request.keywords.map((keyword) => keyword.trim()).filter(Boolean);
|
|
12807
|
+
if (keywords.length === 0 && (!request.scopes || request.scopes.length === 0)) {
|
|
12808
|
+
throw new Error("At least one search keyword is required for an unscoped search");
|
|
12809
|
+
}
|
|
12810
|
+
throwIfAborted(options2.signal);
|
|
12811
|
+
const sender = request.sender === "owner" ? "owner" : "all";
|
|
12812
|
+
const recentDays = normalizeRecentDays(request.recentDays);
|
|
12813
|
+
const effectiveRecentDays = request.startTs === void 0 ? recentDays : void 0;
|
|
12814
|
+
let startTs = request.startTs;
|
|
12815
|
+
let endTs = request.endTs;
|
|
12816
|
+
if (effectiveRecentDays !== void 0) {
|
|
12817
|
+
const range = normalizeInspectionRange(void 0, endTs, effectiveRecentDays, () => this.now());
|
|
12818
|
+
startTs = range.startTs ?? void 0;
|
|
12819
|
+
endTs = range.endTs ?? void 0;
|
|
12820
|
+
}
|
|
12821
|
+
const ownerResolution = sender === "owner" ? { resolvedSessions: 0, missingOwnerSessions: 0, unresolvedOwnerSessions: 0 } : void 0;
|
|
12822
|
+
const maxSessions = clampInteger(request.maxSessions, DEFAULT_MAX_SESSIONS, 1, MAX_MAX_SESSIONS);
|
|
12823
|
+
const maxEvidence = clampInteger(request.maxEvidence, DEFAULT_MAX_EVIDENCE, 1, MAX_MAX_EVIDENCE);
|
|
12824
|
+
const maxWallTimeMs = clampInteger(request.maxWallTimeMs, DEFAULT_MAX_WALL_TIME_MS, 1, MAX_MAX_WALL_TIME_MS);
|
|
12825
|
+
const startedAt = this.now();
|
|
12826
|
+
const failedSessionIds = /* @__PURE__ */ new Set();
|
|
12827
|
+
const { candidates, candidateSessionCount, eligibleCandidateCount, timedOut } = await this.resolveSearchCandidates(
|
|
12828
|
+
request.scopes,
|
|
12829
|
+
failedSessionIds,
|
|
12830
|
+
sender,
|
|
12831
|
+
ownerResolution,
|
|
12832
|
+
maxSessions,
|
|
12833
|
+
startedAt + maxWallTimeMs,
|
|
12834
|
+
options2.signal
|
|
12835
|
+
);
|
|
12836
|
+
const selected = candidates;
|
|
12837
|
+
const truncatedReasons = /* @__PURE__ */ new Set();
|
|
12838
|
+
if (eligibleCandidateCount > selected.length) truncatedReasons.add("session_budget");
|
|
12839
|
+
if (timedOut) truncatedReasons.add("time_budget");
|
|
12840
|
+
const sessionResults = [];
|
|
12841
|
+
let totalMatches = 0;
|
|
12842
|
+
let scannedSessions = 0;
|
|
12843
|
+
let matchedSessions = 0;
|
|
12844
|
+
let processedSessions = 0;
|
|
12845
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: selected.length });
|
|
12846
|
+
for (const [index, candidate] of selected.entries()) {
|
|
12847
|
+
throwIfAborted(options2.signal);
|
|
12848
|
+
if (this.now() - startedAt >= maxWallTimeMs) {
|
|
12849
|
+
truncatedReasons.add("time_budget");
|
|
12850
|
+
break;
|
|
12851
|
+
}
|
|
12852
|
+
options2.onProgress?.({
|
|
12853
|
+
processedSessions: index,
|
|
12854
|
+
totalSessions: selected.length,
|
|
12855
|
+
currentSessionId: candidate.descriptor.sessionId
|
|
12856
|
+
});
|
|
12857
|
+
throwIfAborted(options2.signal);
|
|
12858
|
+
try {
|
|
12859
|
+
const db = this.deps.adapter.openReadonly(candidate.descriptor.sessionId);
|
|
12860
|
+
if (!db) {
|
|
12861
|
+
failedSessionIds.add(candidate.descriptor.sessionId);
|
|
12862
|
+
continue;
|
|
12863
|
+
}
|
|
12864
|
+
const result = searchMessagesByKeywords(db, keywords, {
|
|
12865
|
+
startTs,
|
|
12866
|
+
endTs,
|
|
12867
|
+
senderIds: candidate.memberIds,
|
|
12868
|
+
matchMode: request.matchMode,
|
|
12869
|
+
sort: request.sort,
|
|
12870
|
+
limit: maxEvidence
|
|
12871
|
+
});
|
|
12872
|
+
scannedSessions++;
|
|
12873
|
+
totalMatches += result.total ?? result.messages.length;
|
|
12874
|
+
if (result.messages.length > 0) matchedSessions++;
|
|
12875
|
+
if ((result.total ?? result.messages.length) > result.messages.length) truncatedReasons.add("evidence_budget");
|
|
12876
|
+
sessionResults.push({
|
|
12877
|
+
candidate,
|
|
12878
|
+
db,
|
|
12879
|
+
totalMatches: result.total ?? result.messages.length,
|
|
12880
|
+
matches: result.messages.map((message) => ({
|
|
12881
|
+
...toCrossChatMessage(candidate.descriptor, message),
|
|
12882
|
+
evidenceRole: "match"
|
|
12883
|
+
}))
|
|
12884
|
+
});
|
|
12885
|
+
} catch (error) {
|
|
12886
|
+
failedSessionIds.add(candidate.descriptor.sessionId);
|
|
12887
|
+
appLogger.warn("cross-chat-analysis", `failed to search session: ${candidate.descriptor.sessionId}`, error);
|
|
12888
|
+
} finally {
|
|
12889
|
+
processedSessions++;
|
|
12890
|
+
}
|
|
12891
|
+
await yieldToEventLoop2();
|
|
12892
|
+
}
|
|
12893
|
+
sessionResults.sort(compareSessionSearchResults);
|
|
12894
|
+
const evidence = collectSearchEvidence(sessionResults, maxEvidence, startTs, endTs, {
|
|
12895
|
+
deadline: startedAt + maxWallTimeMs,
|
|
12896
|
+
now: () => this.now(),
|
|
12897
|
+
signal: options2.signal
|
|
12898
|
+
});
|
|
12899
|
+
const messages = evidence.messages;
|
|
12900
|
+
if (evidence.timedOut) truncatedReasons.add("time_budget");
|
|
12901
|
+
const returnedMatchCount = messages.filter((message) => message.evidenceRole === "match").length;
|
|
12902
|
+
if (returnedMatchCount < totalMatches) truncatedReasons.add("evidence_budget");
|
|
12903
|
+
const sortMultiplier = request.sort === "asc" ? 1 : -1;
|
|
12904
|
+
messages.sort(
|
|
12905
|
+
(left, right) => (left.timestamp - right.timestamp || left.sessionId.localeCompare(right.sessionId) || left.messageId - right.messageId) * sortMultiplier
|
|
12906
|
+
);
|
|
12907
|
+
options2.onProgress?.({ processedSessions, totalSessions: selected.length });
|
|
12908
|
+
return {
|
|
12909
|
+
messages,
|
|
12910
|
+
totalMatches,
|
|
12911
|
+
appliedFilters: {
|
|
12912
|
+
startTs: startTs ?? null,
|
|
12913
|
+
endTs: endTs ?? null,
|
|
12914
|
+
recentDays: effectiveRecentDays ?? null,
|
|
12915
|
+
sender
|
|
12916
|
+
},
|
|
12917
|
+
coverage: {
|
|
12918
|
+
candidateSessions: candidateSessionCount,
|
|
12919
|
+
scannedSessions,
|
|
12920
|
+
matchedSessions,
|
|
12921
|
+
failedSessions: failedSessionIds.size,
|
|
12922
|
+
ownerResolution,
|
|
12923
|
+
truncated: truncatedReasons.size > 0,
|
|
12924
|
+
truncatedReasons: [...truncatedReasons]
|
|
12925
|
+
}
|
|
12926
|
+
};
|
|
12927
|
+
}
|
|
12928
|
+
readRecentSession(sessionId) {
|
|
12929
|
+
const db = this.deps.adapter.ensureReadonly(sessionId);
|
|
12930
|
+
const descriptor = getSessionDescriptor(sessionId, db);
|
|
12931
|
+
if (!descriptor) throw createNotFoundError(`Session not found: ${sessionId}`);
|
|
12932
|
+
const recent = searchMessagesByKeywords(db, [], {
|
|
12933
|
+
sort: "desc",
|
|
12934
|
+
limit: RECENT_SESSION_MESSAGE_LIMIT
|
|
12935
|
+
});
|
|
12936
|
+
const summaries = getSegmentSummaries(db, { limit: RECENT_SESSION_SUMMARY_LIMIT }).filter((segment) => typeof segment.summary === "string").map((segment) => ({
|
|
12937
|
+
segmentId: segment.id,
|
|
12938
|
+
startTs: segment.startTs,
|
|
12939
|
+
endTs: segment.endTs,
|
|
12940
|
+
messageCount: segment.messageCount,
|
|
12941
|
+
participants: segment.participants,
|
|
12942
|
+
summary: segment.summary
|
|
12943
|
+
}));
|
|
12944
|
+
const messages = recent.messages.map((message) => toCrossChatMessage(descriptor, message));
|
|
12945
|
+
const totalMessages = recent.total ?? messages.length;
|
|
12946
|
+
return {
|
|
12947
|
+
source: descriptor,
|
|
12948
|
+
messages,
|
|
12949
|
+
summaries,
|
|
12950
|
+
coverage: {
|
|
12951
|
+
totalMessages,
|
|
12952
|
+
returnedMessages: messages.length,
|
|
12953
|
+
returnedSummaries: summaries.length,
|
|
12954
|
+
hasEarlierMessages: totalMessages > messages.length
|
|
12955
|
+
}
|
|
12956
|
+
};
|
|
12957
|
+
}
|
|
12958
|
+
async rankPrivateContacts(request, options2 = {}) {
|
|
12959
|
+
throwIfAborted(options2.signal);
|
|
12960
|
+
const range = normalizeInspectionRange(request.startTs, request.endTs, request.recentDays, () => this.now());
|
|
12961
|
+
const rankBy = request.rankBy === "active_days" ? "active_days" : "message_count";
|
|
12962
|
+
const limit = clampInteger(request.limit, DEFAULT_RANKING_LIMIT, 1, MAX_RANKING_LIMIT);
|
|
12963
|
+
const excludedSessionIds = new Set(this.deps.getExcludedSessionIds?.() ?? []);
|
|
12964
|
+
const sessionIds = this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds();
|
|
12965
|
+
const deadline = this.now() + RANKING_MAX_WALL_TIME_MS;
|
|
12966
|
+
const accumulators = /* @__PURE__ */ new Map();
|
|
12967
|
+
const failedSessionIds = [];
|
|
12968
|
+
const coverage = {
|
|
12969
|
+
candidateSessions: 0,
|
|
12970
|
+
scannedSessions: 0,
|
|
12971
|
+
analyzedSessions: 0,
|
|
12972
|
+
excludedSessions: 0,
|
|
12973
|
+
missingOwnerSessions: 0,
|
|
12974
|
+
unresolvedOwnerSessions: 0,
|
|
12975
|
+
missingContactSessions: 0,
|
|
12976
|
+
ambiguousContactSessions: 0,
|
|
12977
|
+
failedSessions: 0,
|
|
12978
|
+
failedSessionIds,
|
|
12979
|
+
complete: true,
|
|
12980
|
+
truncated: false,
|
|
12981
|
+
truncatedReasons: []
|
|
12982
|
+
};
|
|
12983
|
+
let dataEarliestMessageTs = null;
|
|
12984
|
+
let dataLatestMessageTs = null;
|
|
12985
|
+
let processedSessions = 0;
|
|
12986
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: sessionIds.length });
|
|
12987
|
+
for (const [index, sessionId] of sessionIds.entries()) {
|
|
12988
|
+
throwIfAborted(options2.signal);
|
|
12989
|
+
if (this.now() >= deadline) {
|
|
12990
|
+
coverage.truncated = true;
|
|
12991
|
+
coverage.truncatedReasons.push("time_budget");
|
|
12992
|
+
break;
|
|
12993
|
+
}
|
|
12994
|
+
options2.onProgress?.({ processedSessions: index, totalSessions: sessionIds.length, currentSessionId: sessionId });
|
|
12995
|
+
try {
|
|
12996
|
+
const db = this.deps.adapter.openReadonly(sessionId);
|
|
12997
|
+
if (!db) {
|
|
12998
|
+
coverage.failedSessions++;
|
|
12999
|
+
failedSessionIds.push(sessionId);
|
|
13000
|
+
continue;
|
|
13001
|
+
}
|
|
13002
|
+
const descriptor = getSessionDescriptor(sessionId, db);
|
|
13003
|
+
if (!descriptor || descriptor.sessionType !== "private" /* PRIVATE */) continue;
|
|
13004
|
+
coverage.candidateSessions++;
|
|
13005
|
+
coverage.scannedSessions++;
|
|
13006
|
+
if (excludedSessionIds.has(sessionId)) {
|
|
13007
|
+
coverage.excludedSessions++;
|
|
13008
|
+
continue;
|
|
13009
|
+
}
|
|
13010
|
+
const facts = getCrossChatSessionActivityFacts(db, range);
|
|
13011
|
+
dataEarliestMessageTs = minNullable(dataEarliestMessageTs, facts.dataEarliestMessageTs);
|
|
13012
|
+
dataLatestMessageTs = maxNullable(dataLatestMessageTs, facts.dataLatestMessageTs);
|
|
13013
|
+
const meta = getSessionMeta(db);
|
|
13014
|
+
if (!meta?.ownerId?.trim()) {
|
|
13015
|
+
coverage.missingOwnerSessions++;
|
|
13016
|
+
continue;
|
|
13017
|
+
}
|
|
13018
|
+
const owner = resolveOwnerMember(db);
|
|
13019
|
+
if (!owner) {
|
|
13020
|
+
coverage.unresolvedOwnerSessions++;
|
|
13021
|
+
continue;
|
|
13022
|
+
}
|
|
13023
|
+
const candidates = getNonSystemMembersForContacts(db).filter((member) => member.id !== owner.id);
|
|
13024
|
+
if (candidates.length === 0) {
|
|
13025
|
+
coverage.missingContactSessions++;
|
|
13026
|
+
continue;
|
|
13027
|
+
}
|
|
13028
|
+
const activeMemberIds = new Set(
|
|
13029
|
+
facts.members.filter((member) => member.messageCount > 0).map((member) => member.memberId)
|
|
13030
|
+
);
|
|
13031
|
+
const activeCandidates = candidates.filter((candidate) => activeMemberIds.has(candidate.id));
|
|
13032
|
+
const resolvedCandidates = activeCandidates.length > 0 ? activeCandidates : candidates;
|
|
13033
|
+
if (resolvedCandidates.length > 1) {
|
|
13034
|
+
coverage.ambiguousContactSessions++;
|
|
13035
|
+
continue;
|
|
13036
|
+
}
|
|
13037
|
+
coverage.analyzedSessions++;
|
|
13038
|
+
if (facts.totalMessages === 0) continue;
|
|
13039
|
+
const contact = resolvedCandidates[0];
|
|
13040
|
+
const contactKey = buildContactKey(
|
|
13041
|
+
descriptor.platform,
|
|
13042
|
+
contact.platformId,
|
|
13043
|
+
shouldScopeContactToSession(descriptor.platform, contact) ? sessionId : void 0
|
|
13044
|
+
);
|
|
13045
|
+
const ownerActivity = facts.members.find((member) => member.memberId === owner.id);
|
|
13046
|
+
const contactActivity = facts.members.find((member) => member.memberId === contact.id);
|
|
13047
|
+
const accumulator = accumulators.get(contactKey) ?? {
|
|
13048
|
+
contactKey,
|
|
13049
|
+
platformId: contact.platformId,
|
|
13050
|
+
displayName: pickPreferredContactDisplayName(contact.platformId, contact.name, descriptor.sessionName),
|
|
13051
|
+
platform: descriptor.platform,
|
|
13052
|
+
totalMessages: 0,
|
|
13053
|
+
ownerMessages: 0,
|
|
13054
|
+
contactMessages: 0,
|
|
13055
|
+
activeDayKeys: /* @__PURE__ */ new Set(),
|
|
13056
|
+
firstMessageTs: null,
|
|
13057
|
+
lastMessageTs: null,
|
|
13058
|
+
sessionIds: []
|
|
13059
|
+
};
|
|
13060
|
+
accumulator.displayName = pickPreferredContactDisplayName(
|
|
13061
|
+
contact.platformId,
|
|
13062
|
+
accumulator.displayName,
|
|
13063
|
+
contact.name,
|
|
13064
|
+
descriptor.sessionName
|
|
13065
|
+
);
|
|
13066
|
+
accumulator.totalMessages += facts.totalMessages;
|
|
13067
|
+
accumulator.ownerMessages += ownerActivity?.messageCount ?? 0;
|
|
13068
|
+
accumulator.contactMessages += contactActivity?.messageCount ?? 0;
|
|
13069
|
+
facts.activeDayKeys.forEach((day) => accumulator.activeDayKeys.add(day));
|
|
13070
|
+
accumulator.firstMessageTs = minNullable(accumulator.firstMessageTs, facts.firstMessageTs);
|
|
13071
|
+
accumulator.lastMessageTs = maxNullable(accumulator.lastMessageTs, facts.lastMessageTs);
|
|
13072
|
+
accumulator.sessionIds.push(sessionId);
|
|
13073
|
+
accumulators.set(contactKey, accumulator);
|
|
13074
|
+
} catch (error) {
|
|
13075
|
+
coverage.failedSessions++;
|
|
13076
|
+
failedSessionIds.push(sessionId);
|
|
13077
|
+
appLogger.warn("cross-chat-analysis", `failed to rank private session: ${sessionId}`, error);
|
|
13078
|
+
} finally {
|
|
13079
|
+
processedSessions = index + 1;
|
|
13080
|
+
await yieldToEventLoop2();
|
|
13081
|
+
}
|
|
13082
|
+
}
|
|
13083
|
+
options2.onProgress?.({ processedSessions, totalSessions: sessionIds.length });
|
|
13084
|
+
coverage.complete = !coverage.truncated && coverage.failedSessions === 0 && coverage.missingOwnerSessions === 0 && coverage.unresolvedOwnerSessions === 0 && coverage.missingContactSessions === 0 && coverage.ambiguousContactSessions === 0;
|
|
13085
|
+
const ranked = [...accumulators.values()].sort(
|
|
13086
|
+
(left, right) => comparePrivateContactRankAccumulators(left, right, rankBy)
|
|
13087
|
+
);
|
|
13088
|
+
const items = ranked.slice(0, limit).map((item, index) => {
|
|
13089
|
+
const contactDetail = this.deps.contactsService.getContactDetail(item.contactKey, {
|
|
13090
|
+
acceptStale: true,
|
|
13091
|
+
timeRangePreset: "all"
|
|
13092
|
+
});
|
|
13093
|
+
return {
|
|
13094
|
+
rank: index + 1,
|
|
13095
|
+
contactKey: item.contactKey,
|
|
13096
|
+
displayName: pickPreferredContactDisplayName(
|
|
13097
|
+
item.platformId,
|
|
13098
|
+
contactDetail.contact?.displayName,
|
|
13099
|
+
item.displayName
|
|
13100
|
+
),
|
|
13101
|
+
platform: item.platform,
|
|
13102
|
+
totalMessages: item.totalMessages,
|
|
13103
|
+
ownerMessages: item.ownerMessages,
|
|
13104
|
+
contactMessages: item.contactMessages,
|
|
13105
|
+
activeDays: item.activeDayKeys.size,
|
|
13106
|
+
firstMessageTs: item.firstMessageTs,
|
|
13107
|
+
lastMessageTs: item.lastMessageTs,
|
|
13108
|
+
sessionIds: [...item.sessionIds].sort()
|
|
13109
|
+
};
|
|
13110
|
+
});
|
|
13111
|
+
const currentTs = Math.floor(this.now() / 1e3);
|
|
13112
|
+
return {
|
|
13113
|
+
algorithmVersion: PRIVATE_CONTACTS_RANKING_ALGORITHM_VERSION,
|
|
13114
|
+
rankBy,
|
|
13115
|
+
appliedRange: {
|
|
13116
|
+
...range,
|
|
13117
|
+
dataEarliestMessageTs,
|
|
13118
|
+
dataLatestMessageTs,
|
|
13119
|
+
currentTs
|
|
13120
|
+
},
|
|
13121
|
+
items,
|
|
13122
|
+
coverage
|
|
13123
|
+
};
|
|
13124
|
+
}
|
|
13125
|
+
async rankGroupSessions(request, options2 = {}) {
|
|
13126
|
+
throwIfAborted(options2.signal);
|
|
13127
|
+
if (request.mode !== "owner_activity" && request.mode !== "total_activity") {
|
|
13128
|
+
throw new Error("mode must be owner_activity or total_activity");
|
|
13129
|
+
}
|
|
13130
|
+
const range = normalizeInspectionRange(request.startTs, request.endTs, request.recentDays, () => this.now());
|
|
13131
|
+
const limit = clampInteger(request.limit, DEFAULT_RANKING_LIMIT, 1, MAX_RANKING_LIMIT);
|
|
13132
|
+
const excludedSessionIds = new Set(this.deps.getExcludedSessionIds?.() ?? []);
|
|
13133
|
+
const sessionIds = this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds();
|
|
13134
|
+
const deadline = this.now() + RANKING_MAX_WALL_TIME_MS;
|
|
13135
|
+
const items = [];
|
|
13136
|
+
const failedSessionIds = [];
|
|
13137
|
+
const coverage = {
|
|
13138
|
+
candidateSessions: 0,
|
|
13139
|
+
scannedSessions: 0,
|
|
13140
|
+
analyzedSessions: 0,
|
|
13141
|
+
excludedSessions: 0,
|
|
13142
|
+
missingOwnerSessions: 0,
|
|
13143
|
+
unresolvedOwnerSessions: 0,
|
|
13144
|
+
failedSessions: 0,
|
|
13145
|
+
failedSessionIds,
|
|
13146
|
+
complete: true,
|
|
13147
|
+
truncated: false,
|
|
13148
|
+
truncatedReasons: []
|
|
13149
|
+
};
|
|
13150
|
+
let dataEarliestMessageTs = null;
|
|
13151
|
+
let dataLatestMessageTs = null;
|
|
13152
|
+
let processedSessions = 0;
|
|
13153
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: sessionIds.length });
|
|
13154
|
+
for (const [index, sessionId] of sessionIds.entries()) {
|
|
13155
|
+
throwIfAborted(options2.signal);
|
|
13156
|
+
if (this.now() >= deadline) {
|
|
13157
|
+
coverage.truncated = true;
|
|
13158
|
+
coverage.truncatedReasons.push("time_budget");
|
|
13159
|
+
break;
|
|
13160
|
+
}
|
|
13161
|
+
options2.onProgress?.({ processedSessions: index, totalSessions: sessionIds.length, currentSessionId: sessionId });
|
|
13162
|
+
try {
|
|
13163
|
+
const db = this.deps.adapter.openReadonly(sessionId);
|
|
13164
|
+
if (!db) {
|
|
13165
|
+
coverage.failedSessions++;
|
|
13166
|
+
failedSessionIds.push(sessionId);
|
|
13167
|
+
continue;
|
|
13168
|
+
}
|
|
13169
|
+
const descriptor = getSessionDescriptor(sessionId, db);
|
|
13170
|
+
if (!descriptor || descriptor.sessionType !== "group" /* GROUP */) continue;
|
|
13171
|
+
coverage.candidateSessions++;
|
|
13172
|
+
coverage.scannedSessions++;
|
|
13173
|
+
if (excludedSessionIds.has(sessionId)) {
|
|
13174
|
+
coverage.excludedSessions++;
|
|
13175
|
+
continue;
|
|
13176
|
+
}
|
|
13177
|
+
const facts = getCrossChatSessionActivityFacts(db, range);
|
|
13178
|
+
dataEarliestMessageTs = minNullable(dataEarliestMessageTs, facts.dataEarliestMessageTs);
|
|
13179
|
+
dataLatestMessageTs = maxNullable(dataLatestMessageTs, facts.dataLatestMessageTs);
|
|
13180
|
+
const meta = getSessionMeta(db);
|
|
13181
|
+
let ownerStatus = "missing";
|
|
13182
|
+
let ownerMessages = null;
|
|
13183
|
+
let ownerActiveDays = null;
|
|
13184
|
+
if (!meta?.ownerId?.trim()) {
|
|
13185
|
+
coverage.missingOwnerSessions++;
|
|
13186
|
+
} else {
|
|
13187
|
+
const owner = resolveOwnerMember(db);
|
|
13188
|
+
if (!owner) {
|
|
13189
|
+
ownerStatus = "unresolved";
|
|
13190
|
+
coverage.unresolvedOwnerSessions++;
|
|
13191
|
+
} else {
|
|
13192
|
+
ownerStatus = "resolved";
|
|
13193
|
+
const ownerActivity = facts.members.find((member) => member.memberId === owner.id);
|
|
13194
|
+
ownerMessages = ownerActivity?.messageCount ?? 0;
|
|
13195
|
+
ownerActiveDays = ownerActivity?.activeDays ?? 0;
|
|
13196
|
+
}
|
|
13197
|
+
}
|
|
13198
|
+
if (request.mode === "owner_activity" && ownerStatus !== "resolved") continue;
|
|
13199
|
+
coverage.analyzedSessions++;
|
|
13200
|
+
if (facts.totalMessages === 0) continue;
|
|
13201
|
+
items.push({
|
|
13202
|
+
...descriptor,
|
|
13203
|
+
rank: 0,
|
|
13204
|
+
lastMessageTs: facts.lastMessageTs,
|
|
13205
|
+
totalMessages: facts.totalMessages,
|
|
13206
|
+
ownerMessages,
|
|
13207
|
+
ownerMessageShare: ownerMessages === null || facts.totalMessages === 0 ? null : roundRatio(ownerMessages, facts.totalMessages),
|
|
13208
|
+
ownerActiveDays,
|
|
13209
|
+
activeMembers: facts.activeMembers,
|
|
13210
|
+
activeDays: facts.activeDays,
|
|
13211
|
+
firstMessageTs: facts.firstMessageTs,
|
|
13212
|
+
ownerStatus
|
|
13213
|
+
});
|
|
13214
|
+
} catch (error) {
|
|
13215
|
+
coverage.failedSessions++;
|
|
13216
|
+
failedSessionIds.push(sessionId);
|
|
13217
|
+
appLogger.warn("cross-chat-analysis", `failed to rank group session: ${sessionId}`, error);
|
|
13218
|
+
} finally {
|
|
13219
|
+
processedSessions = index + 1;
|
|
13220
|
+
await yieldToEventLoop2();
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
options2.onProgress?.({ processedSessions, totalSessions: sessionIds.length });
|
|
13224
|
+
coverage.complete = !coverage.truncated && coverage.failedSessions === 0 && (request.mode === "total_activity" || coverage.missingOwnerSessions === 0 && coverage.unresolvedOwnerSessions === 0);
|
|
13225
|
+
items.sort((left, right) => compareGroupSessionRankItems(left, right, request.mode));
|
|
13226
|
+
const rankedItems = items.slice(0, limit).map((item, index) => ({ ...item, rank: index + 1 }));
|
|
13227
|
+
return {
|
|
13228
|
+
algorithmVersion: GROUP_SESSIONS_RANKING_ALGORITHM_VERSION,
|
|
13229
|
+
mode: request.mode,
|
|
13230
|
+
appliedRange: {
|
|
13231
|
+
...range,
|
|
13232
|
+
dataEarliestMessageTs,
|
|
13233
|
+
dataLatestMessageTs,
|
|
13234
|
+
currentTs: Math.floor(this.now() / 1e3)
|
|
13235
|
+
},
|
|
13236
|
+
items: rankedItems,
|
|
13237
|
+
coverage
|
|
13238
|
+
};
|
|
13239
|
+
}
|
|
13240
|
+
getGlobalActivitySummary(request) {
|
|
13241
|
+
const service = this.deps.globalInsightService;
|
|
13242
|
+
if (!service) throw new Error("Global activity summary is unavailable in this runtime");
|
|
13243
|
+
const mode = request.mode ?? "year";
|
|
13244
|
+
if (mode !== "year" && mode !== "recent_365") {
|
|
13245
|
+
throw new Error("mode must be year or recent_365");
|
|
13246
|
+
}
|
|
13247
|
+
if (request.year !== void 0 && (!Number.isInteger(request.year) || request.year < 1970)) {
|
|
13248
|
+
throw new Error("year must be an integer greater than or equal to 1970");
|
|
13249
|
+
}
|
|
13250
|
+
const summary = service.getAnnualSummary(
|
|
13251
|
+
mode === "recent_365" ? { mode: "recent", days: 365, acceptStale: true } : { mode: "year", year: request.year, acceptStale: true }
|
|
13252
|
+
);
|
|
13253
|
+
const dataState = summary.cache.status === "fresh" ? "fresh" : summary.cache.status === "stale" ? "stale" : summary.task.status === "failed" ? "failed" : "preparing";
|
|
13254
|
+
return { mode, dataState, summary };
|
|
13255
|
+
}
|
|
13256
|
+
getMessageContext(request) {
|
|
13257
|
+
const db = this.deps.adapter.ensureReadonly(request.sessionId);
|
|
13258
|
+
const descriptor = getSessionDescriptor(request.sessionId, db);
|
|
13259
|
+
if (!descriptor) throw createNotFoundError(`Session not found: ${request.sessionId}`);
|
|
13260
|
+
const contextSize = clampInteger(request.contextSize, DEFAULT_CONTEXT_SIZE, 0, MAX_CONTEXT_SIZE);
|
|
13261
|
+
const messages = getSearchMessageContext(db, [request.messageId], contextSize, contextSize);
|
|
13262
|
+
if (!messages.some((message) => message.id === request.messageId)) {
|
|
13263
|
+
throw createNotFoundError(`Message not found: ${request.messageId}`);
|
|
13264
|
+
}
|
|
13265
|
+
return {
|
|
13266
|
+
source: descriptor,
|
|
13267
|
+
messages: messages.map((message) => toCrossChatMessage(descriptor, message))
|
|
13268
|
+
};
|
|
13269
|
+
}
|
|
13270
|
+
async getOverview(request, options2 = {}) {
|
|
13271
|
+
throwIfAborted(options2.signal);
|
|
13272
|
+
const scopes = normalizeOverviewScopes(request.scopes);
|
|
13273
|
+
const range = normalizeInspectionRange(request.startTs, request.endTs, request.recentDays, () => this.now());
|
|
13274
|
+
const maxSessions = clampInteger(request.maxSessions, DEFAULT_MAX_SESSIONS, 1, MAX_MAX_SESSIONS);
|
|
13275
|
+
const maxWallTimeMs = clampInteger(request.maxWallTimeMs, DEFAULT_MAX_WALL_TIME_MS, 1, MAX_MAX_WALL_TIME_MS);
|
|
13276
|
+
const selected = scopes.slice(0, maxSessions);
|
|
13277
|
+
const excludedSessionIds = new Set(this.deps.getExcludedSessionIds?.() ?? []);
|
|
13278
|
+
const startedAt = this.now();
|
|
13279
|
+
const items = [];
|
|
13280
|
+
let failedSessions = 0;
|
|
13281
|
+
const failedSessionIds = [];
|
|
13282
|
+
let excludedSessions = 0;
|
|
13283
|
+
let missingOwnerSessions = 0;
|
|
13284
|
+
let unresolvedOwnerSessions = 0;
|
|
13285
|
+
let processedSessions = 0;
|
|
13286
|
+
let dataEarliestMessageTs = null;
|
|
13287
|
+
let dataLatestMessageTs = null;
|
|
13288
|
+
const truncatedReasons = /* @__PURE__ */ new Set();
|
|
13289
|
+
if (selected.length < scopes.length) truncatedReasons.add("session_budget");
|
|
13290
|
+
options2.onProgress?.({ processedSessions: 0, totalSessions: selected.length });
|
|
13291
|
+
for (const [index, scope] of selected.entries()) {
|
|
13292
|
+
throwIfAborted(options2.signal);
|
|
13293
|
+
if (this.now() - startedAt >= maxWallTimeMs) {
|
|
13294
|
+
truncatedReasons.add("time_budget");
|
|
13295
|
+
break;
|
|
13296
|
+
}
|
|
13297
|
+
options2.onProgress?.({
|
|
13298
|
+
processedSessions: index,
|
|
13299
|
+
totalSessions: selected.length,
|
|
13300
|
+
currentSessionId: scope.sessionId
|
|
13301
|
+
});
|
|
13302
|
+
throwIfAborted(options2.signal);
|
|
13303
|
+
try {
|
|
13304
|
+
const db = this.deps.adapter.openReadonly(scope.sessionId);
|
|
13305
|
+
if (!db) {
|
|
13306
|
+
failedSessions++;
|
|
13307
|
+
failedSessionIds.push(scope.sessionId);
|
|
13308
|
+
continue;
|
|
13309
|
+
}
|
|
13310
|
+
const descriptor = getSessionDescriptor(scope.sessionId, db);
|
|
13311
|
+
if (!descriptor) {
|
|
13312
|
+
failedSessions++;
|
|
13313
|
+
failedSessionIds.push(scope.sessionId);
|
|
13314
|
+
continue;
|
|
13315
|
+
}
|
|
13316
|
+
const facts = getCrossChatSessionActivityFacts(db, range);
|
|
13317
|
+
dataEarliestMessageTs = minNullable(dataEarliestMessageTs, facts.dataEarliestMessageTs);
|
|
13318
|
+
dataLatestMessageTs = maxNullable(dataLatestMessageTs, facts.dataLatestMessageTs);
|
|
13319
|
+
let ownerStatus = "missing";
|
|
13320
|
+
let ownerMemberId = null;
|
|
13321
|
+
if (excludedSessionIds.has(scope.sessionId)) {
|
|
13322
|
+
ownerStatus = "excluded";
|
|
13323
|
+
excludedSessions++;
|
|
13324
|
+
} else {
|
|
13325
|
+
const meta = getSessionMeta(db);
|
|
13326
|
+
if (!meta?.ownerId?.trim()) {
|
|
13327
|
+
missingOwnerSessions++;
|
|
13328
|
+
} else {
|
|
13329
|
+
const owner = resolveOwnerMember(db);
|
|
13330
|
+
if (!owner) {
|
|
13331
|
+
ownerStatus = "unresolved";
|
|
13332
|
+
unresolvedOwnerSessions++;
|
|
13333
|
+
} else {
|
|
13334
|
+
ownerStatus = "resolved";
|
|
13335
|
+
ownerMemberId = owner.id;
|
|
13336
|
+
}
|
|
13337
|
+
}
|
|
13338
|
+
}
|
|
13339
|
+
items.push(buildOverviewItem(descriptor, db, scope, facts, ownerStatus, ownerMemberId));
|
|
13340
|
+
} catch (error) {
|
|
13341
|
+
failedSessions++;
|
|
13342
|
+
failedSessionIds.push(scope.sessionId);
|
|
13343
|
+
appLogger.warn("cross-chat-analysis", `failed to build session overview: ${scope.sessionId}`, error);
|
|
13344
|
+
} finally {
|
|
13345
|
+
processedSessions++;
|
|
13346
|
+
}
|
|
13347
|
+
await yieldToEventLoop2();
|
|
13348
|
+
}
|
|
13349
|
+
options2.onProgress?.({ processedSessions, totalSessions: selected.length });
|
|
13350
|
+
return {
|
|
13351
|
+
appliedRange: {
|
|
13352
|
+
...range,
|
|
13353
|
+
dataEarliestMessageTs,
|
|
13354
|
+
dataLatestMessageTs,
|
|
13355
|
+
currentTs: Math.floor(this.now() / 1e3)
|
|
13356
|
+
},
|
|
13357
|
+
items,
|
|
13358
|
+
coverage: {
|
|
13359
|
+
candidateSessions: scopes.length,
|
|
13360
|
+
analyzedSessions: items.length,
|
|
13361
|
+
excludedSessions,
|
|
13362
|
+
missingOwnerSessions,
|
|
13363
|
+
unresolvedOwnerSessions,
|
|
13364
|
+
failedSessions,
|
|
13365
|
+
failedSessionIds,
|
|
13366
|
+
complete: truncatedReasons.size === 0 && failedSessions === 0,
|
|
13367
|
+
truncated: truncatedReasons.size > 0,
|
|
13368
|
+
truncatedReasons: [...truncatedReasons]
|
|
13369
|
+
}
|
|
13370
|
+
};
|
|
13371
|
+
}
|
|
13372
|
+
async resolveSearchCandidates(scopes, failedSessionIds, sender, ownerResolution, maxSessions = DEFAULT_MAX_SESSIONS, deadline = Number.POSITIVE_INFINITY, signal) {
|
|
13373
|
+
throwIfAborted(signal);
|
|
13374
|
+
const normalizedScopes = scopes ? normalizeScopes(scopes) : (this.deps.adapter.listSessionCandidateIds?.() ?? this.deps.adapter.listSessionIds()).map((sessionId) => ({
|
|
13375
|
+
sessionId
|
|
13376
|
+
}));
|
|
13377
|
+
const candidates = [];
|
|
13378
|
+
let eligibleCandidateCount = 0;
|
|
13379
|
+
let timedOut = false;
|
|
13380
|
+
for (const scope of normalizedScopes) {
|
|
13381
|
+
throwIfAborted(signal);
|
|
13382
|
+
if (this.now() >= deadline) {
|
|
13383
|
+
timedOut = true;
|
|
13384
|
+
break;
|
|
13385
|
+
}
|
|
13386
|
+
const descriptor = this.tryGetSessionDescriptor(scope.sessionId);
|
|
13387
|
+
if (!descriptor) {
|
|
13388
|
+
failedSessionIds.add(scope.sessionId);
|
|
13389
|
+
await yieldToEventLoop2();
|
|
13390
|
+
continue;
|
|
13391
|
+
}
|
|
13392
|
+
if (this.now() >= deadline) {
|
|
13393
|
+
timedOut = true;
|
|
13394
|
+
break;
|
|
13395
|
+
}
|
|
13396
|
+
if (sender === "owner") {
|
|
13397
|
+
const db = this.deps.adapter.openReadonly(scope.sessionId);
|
|
13398
|
+
if (!db) {
|
|
13399
|
+
failedSessionIds.add(scope.sessionId);
|
|
13400
|
+
await yieldToEventLoop2();
|
|
13401
|
+
continue;
|
|
13402
|
+
}
|
|
13403
|
+
const meta = getSessionMeta(db);
|
|
13404
|
+
if (!meta?.ownerId?.trim()) {
|
|
13405
|
+
if (ownerResolution) ownerResolution.missingOwnerSessions++;
|
|
13406
|
+
await yieldToEventLoop2();
|
|
13407
|
+
continue;
|
|
13408
|
+
}
|
|
13409
|
+
const owner = resolveOwnerMember(db);
|
|
13410
|
+
if (!owner) {
|
|
13411
|
+
if (ownerResolution) ownerResolution.unresolvedOwnerSessions++;
|
|
13412
|
+
await yieldToEventLoop2();
|
|
13413
|
+
continue;
|
|
13414
|
+
}
|
|
13415
|
+
if (ownerResolution) ownerResolution.resolvedSessions++;
|
|
13416
|
+
eligibleCandidateCount++;
|
|
13417
|
+
addBoundedSearchCandidate(candidates, { descriptor, memberIds: [owner.id] }, maxSessions);
|
|
13418
|
+
} else {
|
|
13419
|
+
eligibleCandidateCount++;
|
|
13420
|
+
addBoundedSearchCandidate(candidates, { descriptor, memberIds: scope.memberIds }, maxSessions);
|
|
13421
|
+
}
|
|
13422
|
+
await yieldToEventLoop2();
|
|
13423
|
+
}
|
|
13424
|
+
throwIfAborted(signal);
|
|
13425
|
+
if (!timedOut && this.now() >= deadline) timedOut = true;
|
|
13426
|
+
return {
|
|
13427
|
+
candidates,
|
|
13428
|
+
candidateSessionCount: normalizedScopes.length,
|
|
13429
|
+
eligibleCandidateCount,
|
|
13430
|
+
timedOut
|
|
13431
|
+
};
|
|
13432
|
+
}
|
|
13433
|
+
tryGetSessionDescriptor(sessionId) {
|
|
13434
|
+
try {
|
|
13435
|
+
const db = this.deps.adapter.openReadonly(sessionId);
|
|
13436
|
+
return db ? getSessionDescriptor(sessionId, db) : null;
|
|
13437
|
+
} catch (error) {
|
|
13438
|
+
appLogger.warn("cross-chat-analysis", `failed to inspect session metadata: ${sessionId}`, error);
|
|
13439
|
+
return null;
|
|
13440
|
+
}
|
|
13441
|
+
}
|
|
13442
|
+
now() {
|
|
13443
|
+
return this.deps.now?.() ?? Date.now();
|
|
13444
|
+
}
|
|
13445
|
+
};
|
|
13446
|
+
function normalizeContactName(value) {
|
|
13447
|
+
return value.trim().toLocaleLowerCase();
|
|
13448
|
+
}
|
|
13449
|
+
function pickPreferredContactDisplayName(platformId, ...candidates) {
|
|
13450
|
+
const normalizedPlatformId = platformId.trim();
|
|
13451
|
+
for (const candidate of candidates) {
|
|
13452
|
+
const value = candidate?.trim();
|
|
13453
|
+
if (value && value !== normalizedPlatformId) return value;
|
|
13454
|
+
}
|
|
13455
|
+
return candidates.find((candidate) => candidate?.trim())?.trim() ?? normalizedPlatformId;
|
|
13456
|
+
}
|
|
13457
|
+
function normalizeRecentDays(value) {
|
|
13458
|
+
if (value === void 0) return void 0;
|
|
13459
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error("recentDays must be a positive number");
|
|
13460
|
+
return Math.min(MAX_RECENT_DAYS, Math.max(1, Math.floor(value)));
|
|
13461
|
+
}
|
|
13462
|
+
function comparePrivateContactRankAccumulators(left, right, rankBy) {
|
|
13463
|
+
const primaryDifference = rankBy === "active_days" ? right.activeDayKeys.size - left.activeDayKeys.size : right.totalMessages - left.totalMessages;
|
|
13464
|
+
return primaryDifference || (right.lastMessageTs ?? Number.NEGATIVE_INFINITY) - (left.lastMessageTs ?? Number.NEGATIVE_INFINITY) || left.contactKey.localeCompare(right.contactKey);
|
|
13465
|
+
}
|
|
13466
|
+
function compareGroupSessionRankItems(left, right, mode) {
|
|
13467
|
+
const primaryDifference = mode === "owner_activity" ? (right.ownerMessages ?? Number.NEGATIVE_INFINITY) - (left.ownerMessages ?? Number.NEGATIVE_INFINITY) : right.totalMessages - left.totalMessages;
|
|
13468
|
+
return primaryDifference || (right.lastMessageTs ?? Number.NEGATIVE_INFINITY) - (left.lastMessageTs ?? Number.NEGATIVE_INFINITY) || left.sessionId.localeCompare(right.sessionId);
|
|
13469
|
+
}
|
|
13470
|
+
function roundRatio(numerator, denominator) {
|
|
13471
|
+
if (denominator <= 0) return 0;
|
|
13472
|
+
return Math.round(numerator / denominator * 1e4) / 1e4;
|
|
13473
|
+
}
|
|
13474
|
+
function compareSessionSearchResults(left, right) {
|
|
13475
|
+
const leftPrivate = left.candidate.descriptor.sessionType === "private" /* PRIVATE */;
|
|
13476
|
+
const rightPrivate = right.candidate.descriptor.sessionType === "private" /* PRIVATE */;
|
|
13477
|
+
if (leftPrivate !== rightPrivate) return leftPrivate ? -1 : 1;
|
|
13478
|
+
return right.totalMatches - left.totalMatches || (right.candidate.descriptor.lastMessageTs ?? Number.NEGATIVE_INFINITY) - (left.candidate.descriptor.lastMessageTs ?? Number.NEGATIVE_INFINITY);
|
|
13479
|
+
}
|
|
13480
|
+
function collectSearchEvidence(results, maxMessages, startTs, endTs, options2 = {
|
|
13481
|
+
deadline: Number.POSITIVE_INFINITY,
|
|
13482
|
+
now: Date.now
|
|
13483
|
+
}) {
|
|
13484
|
+
const selected = /* @__PURE__ */ new Map();
|
|
13485
|
+
let timedOut = false;
|
|
13486
|
+
for (const result of results) {
|
|
13487
|
+
let matchIndex = 0;
|
|
13488
|
+
while (matchIndex < result.matches.length && selected.size < maxMessages) {
|
|
13489
|
+
throwIfAborted(options2.signal);
|
|
13490
|
+
if (options2.now() >= options2.deadline) {
|
|
13491
|
+
timedOut = true;
|
|
13492
|
+
break;
|
|
13493
|
+
}
|
|
13494
|
+
const remaining = maxMessages - selected.size;
|
|
13495
|
+
const expectedBlockSize = SEARCH_CONTEXT_BEFORE + SEARCH_CONTEXT_AFTER + 1;
|
|
13496
|
+
const batchSize = Math.min(
|
|
13497
|
+
result.matches.length - matchIndex,
|
|
13498
|
+
remaining < expectedBlockSize ? 1 : Math.max(1, Math.floor(remaining / expectedBlockSize))
|
|
13499
|
+
);
|
|
13500
|
+
const batch = result.matches.slice(matchIndex, matchIndex + batchSize);
|
|
13501
|
+
const matchIds = new Set(batch.map((message) => message.messageId));
|
|
13502
|
+
const contextMessages = getSearchMessageContext(
|
|
13503
|
+
result.db,
|
|
13504
|
+
[...matchIds],
|
|
13505
|
+
SEARCH_CONTEXT_BEFORE,
|
|
13506
|
+
SEARCH_CONTEXT_AFTER
|
|
13507
|
+
).map((message) => ({
|
|
13508
|
+
...toCrossChatMessage(result.candidate.descriptor, message),
|
|
13509
|
+
evidenceRole: matchIds.has(message.id) ? "match" : "context"
|
|
13510
|
+
})).filter((message) => isWithinSearchRange(message.timestamp, startTs, endTs));
|
|
13511
|
+
for (const matchId of matchIds) {
|
|
13512
|
+
const key = crossChatMessageKey(result.candidate.descriptor.sessionId, matchId);
|
|
13513
|
+
const existing = selected.get(key);
|
|
13514
|
+
if (existing?.evidenceRole === "context") selected.set(key, { ...existing, evidenceRole: "match" });
|
|
13515
|
+
}
|
|
13516
|
+
const unseen = contextMessages.filter(
|
|
13517
|
+
(message) => !selected.has(crossChatMessageKey(message.sessionId, message.messageId))
|
|
13518
|
+
);
|
|
13519
|
+
const accepted = unseen.length <= remaining ? unseen : prioritizeContextMessages(unseen, matchIds).slice(0, remaining);
|
|
13520
|
+
for (const message of accepted) {
|
|
13521
|
+
const key = crossChatMessageKey(message.sessionId, message.messageId);
|
|
13522
|
+
const existing = selected.get(key);
|
|
13523
|
+
if (!existing || existing.evidenceRole === "context" && message.evidenceRole === "match") {
|
|
13524
|
+
selected.set(key, message);
|
|
13525
|
+
}
|
|
13526
|
+
}
|
|
13527
|
+
matchIndex += batchSize;
|
|
13528
|
+
}
|
|
13529
|
+
if (timedOut || selected.size >= maxMessages) break;
|
|
13530
|
+
}
|
|
13531
|
+
return { messages: [...selected.values()], timedOut };
|
|
13532
|
+
}
|
|
13533
|
+
function prioritizeContextMessages(messages, matchIds) {
|
|
13534
|
+
return [...messages].sort((left, right) => {
|
|
13535
|
+
const leftMatch = matchIds.has(left.messageId);
|
|
13536
|
+
const rightMatch = matchIds.has(right.messageId);
|
|
13537
|
+
if (leftMatch !== rightMatch) return leftMatch ? -1 : 1;
|
|
13538
|
+
const leftDistance = nearestMessageIdDistance(left.messageId, matchIds);
|
|
13539
|
+
const rightDistance = nearestMessageIdDistance(right.messageId, matchIds);
|
|
13540
|
+
return leftDistance - rightDistance || left.messageId - right.messageId;
|
|
13541
|
+
});
|
|
13542
|
+
}
|
|
13543
|
+
function nearestMessageIdDistance(messageId, matchIds) {
|
|
13544
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
13545
|
+
for (const matchId of matchIds) distance = Math.min(distance, Math.abs(messageId - matchId));
|
|
13546
|
+
return distance;
|
|
13547
|
+
}
|
|
13548
|
+
function crossChatMessageKey(sessionId, messageId) {
|
|
13549
|
+
return `${sessionId}:${messageId}`;
|
|
13550
|
+
}
|
|
13551
|
+
function isWithinSearchRange(timestamp, startTs, endTs) {
|
|
13552
|
+
return (startTs === void 0 || timestamp >= startTs) && (endTs === void 0 || timestamp <= endTs);
|
|
13553
|
+
}
|
|
13554
|
+
function addBoundedSearchCandidate(candidates, candidate, maxSessions) {
|
|
13555
|
+
candidates.push(candidate);
|
|
13556
|
+
candidates.sort(compareSearchCandidates);
|
|
13557
|
+
if (candidates.length > maxSessions) candidates.length = maxSessions;
|
|
13558
|
+
}
|
|
13559
|
+
function compareSearchCandidates(left, right) {
|
|
13560
|
+
return (right.descriptor.lastMessageTs ?? Number.NEGATIVE_INFINITY) - (left.descriptor.lastMessageTs ?? Number.NEGATIVE_INFINITY);
|
|
13561
|
+
}
|
|
13562
|
+
function getSessionDescriptor(sessionId, db) {
|
|
13563
|
+
const meta = getSessionMeta(db);
|
|
13564
|
+
if (!meta || meta.type !== "private" /* PRIVATE */ && meta.type !== "group" /* GROUP */) return null;
|
|
13565
|
+
const latest = getRecentMessages(db, { limit: 1 })[0];
|
|
13566
|
+
return {
|
|
13567
|
+
sessionId,
|
|
13568
|
+
sessionName: meta.name,
|
|
13569
|
+
sessionType: meta.type,
|
|
13570
|
+
platform: meta.platform,
|
|
13571
|
+
lastMessageTs: latest?.timestamp ?? null
|
|
13572
|
+
};
|
|
13573
|
+
}
|
|
13574
|
+
function toCrossChatMessage(descriptor, message) {
|
|
13575
|
+
return {
|
|
13576
|
+
...descriptor,
|
|
13577
|
+
messageId: message.id,
|
|
13578
|
+
senderId: message.senderId,
|
|
13579
|
+
senderName: message.senderName,
|
|
13580
|
+
senderPlatformId: message.senderPlatformId,
|
|
13581
|
+
content: message.content,
|
|
13582
|
+
timestamp: message.timestamp,
|
|
13583
|
+
messageType: message.type
|
|
13584
|
+
};
|
|
13585
|
+
}
|
|
13586
|
+
function normalizeScopes(scopes) {
|
|
13587
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
13588
|
+
for (const scope of scopes) {
|
|
13589
|
+
const sessionId = scope.sessionId.trim();
|
|
13590
|
+
if (!sessionId) continue;
|
|
13591
|
+
const existing = bySession.get(sessionId);
|
|
13592
|
+
if (!existing) {
|
|
13593
|
+
bySession.set(sessionId, {
|
|
13594
|
+
sessionId,
|
|
13595
|
+
memberIds: scope.memberIds ? [...new Set(scope.memberIds)] : void 0,
|
|
13596
|
+
label: scope.label
|
|
13597
|
+
});
|
|
13598
|
+
continue;
|
|
13599
|
+
}
|
|
13600
|
+
if (!existing.memberIds || !scope.memberIds) {
|
|
13601
|
+
existing.memberIds = void 0;
|
|
13602
|
+
} else {
|
|
13603
|
+
existing.memberIds = [.../* @__PURE__ */ new Set([...existing.memberIds, ...scope.memberIds])];
|
|
13604
|
+
}
|
|
13605
|
+
existing.label ??= scope.label;
|
|
13606
|
+
}
|
|
13607
|
+
return [...bySession.values()];
|
|
13608
|
+
}
|
|
13609
|
+
function normalizeOverviewScopes(scopes) {
|
|
13610
|
+
return scopes.flatMap((scope) => {
|
|
13611
|
+
const sessionId = scope.sessionId.trim();
|
|
13612
|
+
if (!sessionId) return [];
|
|
13613
|
+
return [
|
|
13614
|
+
{
|
|
13615
|
+
sessionId,
|
|
13616
|
+
memberIds: scope.memberIds ? [...new Set(scope.memberIds)] : void 0,
|
|
13617
|
+
label: scope.label
|
|
13618
|
+
}
|
|
13619
|
+
];
|
|
13620
|
+
});
|
|
13621
|
+
}
|
|
13622
|
+
function buildOverviewItem(descriptor, db, scope, facts, ownerStatus, ownerMemberId) {
|
|
13623
|
+
const rosterById = new Map(getNonSystemMembersForContacts(db).map((member) => [member.id, member]));
|
|
13624
|
+
const factsByMemberId = new Map(facts.members.map((member) => [member.memberId, member]));
|
|
13625
|
+
const memberActivities = (scope.memberIds ?? []).flatMap((memberId) => {
|
|
13626
|
+
const rosterMember = rosterById.get(memberId);
|
|
13627
|
+
if (!rosterMember) return [];
|
|
13628
|
+
const activity = factsByMemberId.get(memberId);
|
|
13629
|
+
return [
|
|
13630
|
+
{
|
|
13631
|
+
memberId,
|
|
13632
|
+
platformId: rosterMember.platformId,
|
|
13633
|
+
memberName: rosterMember.name,
|
|
13634
|
+
messageCount: activity?.messageCount ?? 0,
|
|
13635
|
+
activeDays: activity?.activeDays ?? 0,
|
|
13636
|
+
firstMessageTs: activity?.firstMessageTs ?? null,
|
|
13637
|
+
lastMessageTs: activity?.lastMessageTs ?? null
|
|
13638
|
+
}
|
|
13639
|
+
];
|
|
13640
|
+
});
|
|
13641
|
+
const selectedFacts = scope.memberIds ? scope.memberIds.flatMap((memberId) => factsByMemberId.get(memberId) ?? []) : [];
|
|
13642
|
+
const selectedActiveDayKeys = new Set(selectedFacts.flatMap((member) => member.activeDayKeys));
|
|
13643
|
+
const ownerActivity = ownerMemberId === null ? void 0 : factsByMemberId.get(ownerMemberId);
|
|
13644
|
+
const topMembers = descriptor.sessionType === "group" /* GROUP */ ? [...facts.members].sort(
|
|
13645
|
+
(left, right) => right.messageCount - left.messageCount || (right.lastMessageTs ?? Number.NEGATIVE_INFINITY) - (left.lastMessageTs ?? Number.NEGATIVE_INFINITY) || left.memberId - right.memberId
|
|
13646
|
+
).slice(0, OVERVIEW_TOP_MEMBERS_LIMIT).map(toOverviewMemberActivity) : [];
|
|
13647
|
+
const totalMessages = scope.memberIds ? selectedFacts.reduce((sum, member) => sum + member.messageCount, 0) : facts.totalMessages;
|
|
13648
|
+
const firstMessageTs = scope.memberIds ? selectedFacts.reduce((earliest, member) => minNullable(earliest, member.firstMessageTs), null) : facts.firstMessageTs;
|
|
13649
|
+
const lastMessageTs = scope.memberIds ? selectedFacts.reduce((latest, member) => maxNullable(latest, member.lastMessageTs), null) : facts.lastMessageTs;
|
|
13650
|
+
return {
|
|
13651
|
+
...descriptor,
|
|
13652
|
+
label: scope.label ?? descriptor.sessionName,
|
|
13653
|
+
...scope.memberIds ? {
|
|
13654
|
+
memberIds: scope.memberIds,
|
|
13655
|
+
memberNames: memberActivities.map((member) => member.memberName)
|
|
13656
|
+
} : {},
|
|
13657
|
+
memberActivities,
|
|
13658
|
+
totalMessages,
|
|
13659
|
+
activeDays: scope.memberIds ? selectedActiveDayKeys.size : facts.activeDays,
|
|
13660
|
+
activeMembers: scope.memberIds ? selectedFacts.length : facts.activeMembers,
|
|
13661
|
+
firstMessageTs,
|
|
13662
|
+
lastMessageTs,
|
|
13663
|
+
ownerStatus,
|
|
13664
|
+
ownerMessages: ownerStatus === "resolved" ? ownerActivity?.messageCount ?? 0 : null,
|
|
13665
|
+
ownerActiveDays: ownerStatus === "resolved" ? ownerActivity?.activeDays ?? 0 : null,
|
|
13666
|
+
topMembers
|
|
13667
|
+
};
|
|
13668
|
+
}
|
|
13669
|
+
function toOverviewMemberActivity(member) {
|
|
13670
|
+
return {
|
|
13671
|
+
memberId: member.memberId,
|
|
13672
|
+
platformId: member.platformId,
|
|
13673
|
+
memberName: member.memberName,
|
|
13674
|
+
messageCount: member.messageCount,
|
|
13675
|
+
activeDays: member.activeDays,
|
|
13676
|
+
firstMessageTs: member.firstMessageTs,
|
|
13677
|
+
lastMessageTs: member.lastMessageTs
|
|
13678
|
+
};
|
|
13679
|
+
}
|
|
13680
|
+
function clampInteger(value, fallback, min, max) {
|
|
13681
|
+
if (!Number.isFinite(value)) return fallback;
|
|
13682
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
13683
|
+
}
|
|
13684
|
+
function normalizeInspectionRange(startTs, endTs, recentDays, now) {
|
|
13685
|
+
return resolveInspectionRange(normalizeInspectionRangeRequest(startTs, endTs, recentDays), now);
|
|
13686
|
+
}
|
|
13687
|
+
function normalizeInspectionRangeRequest(startTs, endTs, recentDays) {
|
|
13688
|
+
const normalizedStart = normalizeOptionalTimestamp(startTs, "startTs");
|
|
13689
|
+
const normalizedEnd = normalizeOptionalTimestamp(endTs, "endTs");
|
|
13690
|
+
if (normalizedStart !== null && normalizedEnd !== null && normalizedStart > normalizedEnd) {
|
|
13691
|
+
throw new Error("startTs must be less than or equal to endTs");
|
|
13692
|
+
}
|
|
13693
|
+
return {
|
|
13694
|
+
startTs: normalizedStart,
|
|
13695
|
+
endTs: normalizedEnd,
|
|
13696
|
+
recentDays: normalizedStart === null ? normalizeRecentDays(recentDays) ?? null : null
|
|
13697
|
+
};
|
|
13698
|
+
}
|
|
13699
|
+
function resolveInspectionRange(request, now) {
|
|
13700
|
+
if (request.recentDays !== null) {
|
|
13701
|
+
const anchor = new Date((request.endTs ?? Math.floor(now() / 1e3)) * 1e3);
|
|
13702
|
+
const start = new Date(anchor);
|
|
13703
|
+
start.setHours(0, 0, 0, 0);
|
|
13704
|
+
start.setDate(start.getDate() - (request.recentDays - 1));
|
|
13705
|
+
const endExclusive = new Date(anchor);
|
|
13706
|
+
endExclusive.setHours(0, 0, 0, 0);
|
|
13707
|
+
endExclusive.setDate(endExclusive.getDate() + 1);
|
|
13708
|
+
return {
|
|
13709
|
+
startTs: Math.floor(start.getTime() / 1e3),
|
|
13710
|
+
endTs: Math.floor(endExclusive.getTime() / 1e3) - 1
|
|
13711
|
+
};
|
|
13712
|
+
}
|
|
13713
|
+
return { startTs: request.startTs, endTs: request.endTs };
|
|
13714
|
+
}
|
|
13715
|
+
function normalizeOptionalTimestamp(value, name) {
|
|
13716
|
+
if (value === void 0) return null;
|
|
13717
|
+
if (!Number.isFinite(value)) throw new Error(`${name} must be a finite timestamp`);
|
|
13718
|
+
return Math.floor(value);
|
|
13719
|
+
}
|
|
13720
|
+
function summarizeContactSessions(sessions, completeResult) {
|
|
13721
|
+
let ownMessageCount = 0;
|
|
13722
|
+
let firstOwnMessageTs = null;
|
|
13723
|
+
let lastOwnMessageTs = null;
|
|
13724
|
+
let privateSessions = 0;
|
|
13725
|
+
let groupSessions = 0;
|
|
13726
|
+
let spokeSessions = 0;
|
|
13727
|
+
let rosterOnlySessions = 0;
|
|
13728
|
+
for (const session of sessions) {
|
|
13729
|
+
ownMessageCount += session.ownMessageCount;
|
|
13730
|
+
firstOwnMessageTs = minNullable(firstOwnMessageTs, session.firstOwnMessageTs);
|
|
13731
|
+
lastOwnMessageTs = maxNullable(lastOwnMessageTs, session.lastOwnMessageTs);
|
|
13732
|
+
if (session.sessionType === "private" /* PRIVATE */) privateSessions++;
|
|
13733
|
+
else groupSessions++;
|
|
13734
|
+
if (session.presence === "spoke") spokeSessions++;
|
|
13735
|
+
else rosterOnlySessions++;
|
|
13736
|
+
}
|
|
13737
|
+
return {
|
|
13738
|
+
scope: completeResult ? "complete_result" : "current_batch",
|
|
13739
|
+
matchedSessions: sessions.length,
|
|
13740
|
+
privateSessions,
|
|
13741
|
+
groupSessions,
|
|
13742
|
+
spokeSessions,
|
|
13743
|
+
rosterOnlySessions,
|
|
13744
|
+
ownMessageCount,
|
|
13745
|
+
firstOwnMessageTs,
|
|
13746
|
+
lastOwnMessageTs
|
|
13747
|
+
};
|
|
13748
|
+
}
|
|
13749
|
+
function normalizeParticipantRefs(participants) {
|
|
13750
|
+
if (!Array.isArray(participants)) throw new Error("participants must be an array");
|
|
13751
|
+
const deduplicated = /* @__PURE__ */ new Map();
|
|
13752
|
+
for (const participant of participants) {
|
|
13753
|
+
if (participant?.type === "owner") {
|
|
13754
|
+
deduplicated.set("owner", { type: "owner" });
|
|
13755
|
+
continue;
|
|
13756
|
+
}
|
|
13757
|
+
if (participant?.type === "contact") {
|
|
13758
|
+
const contactKey = participant.contactKey?.trim();
|
|
13759
|
+
if (!contactKey) throw new Error("participant contactKey is required");
|
|
13760
|
+
deduplicated.set(`contact:${contactKey}`, { type: "contact", contactKey });
|
|
13761
|
+
continue;
|
|
13762
|
+
}
|
|
13763
|
+
throw new Error("participant type must be owner or contact");
|
|
13764
|
+
}
|
|
13765
|
+
const refs = [...deduplicated.values()];
|
|
13766
|
+
if (refs.length < 2 || refs.length > 5) throw new Error("participants must contain 2 to 5 distinct people");
|
|
13767
|
+
return refs;
|
|
13768
|
+
}
|
|
13769
|
+
function mapSharedInteractionSession(descriptor, members, participantIndexByMemberId, facts) {
|
|
13770
|
+
const participants = facts.participants.map((participant, index) => ({
|
|
13771
|
+
participantIndex: participantIndexByMemberId.get(participant.memberId) ?? index,
|
|
13772
|
+
memberId: participant.memberId,
|
|
13773
|
+
memberName: members[index]?.name ?? String(participant.memberId),
|
|
13774
|
+
messageCount: participant.messageCount,
|
|
13775
|
+
firstMessageTs: participant.firstMessageTs,
|
|
13776
|
+
lastMessageTs: participant.lastMessageTs,
|
|
13777
|
+
activeDays: participant.activeDays,
|
|
13778
|
+
presenceObservedInRange: participant.messageCount > 0
|
|
13779
|
+
}));
|
|
13780
|
+
const pairs = facts.pairs.map((pair) => ({
|
|
13781
|
+
sourceParticipantIndex: participantIndexByMemberId.get(pair.sourceMemberId) ?? 0,
|
|
13782
|
+
targetParticipantIndex: participantIndexByMemberId.get(pair.targetMemberId) ?? 0,
|
|
13783
|
+
directReplyCount: pair.directReplyCount,
|
|
13784
|
+
repliesFromSourceToTarget: pair.repliesFromSourceToTarget,
|
|
13785
|
+
repliesFromTargetToSource: pair.repliesFromTargetToSource,
|
|
13786
|
+
lastDirectReplyTs: pair.lastDirectReplyTs,
|
|
13787
|
+
coOccurrenceCount: pair.coOccurrenceCount,
|
|
13788
|
+
coOccurrenceRawScore: pair.coOccurrenceRawScore,
|
|
13789
|
+
lastProximityTs: pair.lastProximityTs,
|
|
13790
|
+
coActiveDays: pair.coActiveDays,
|
|
13791
|
+
anchors: pair.anchors.map((anchor) => ({
|
|
13792
|
+
sessionId: descriptor.sessionId,
|
|
13793
|
+
messageId: anchor.messageId,
|
|
13794
|
+
relatedMessageId: anchor.relatedMessageId,
|
|
13795
|
+
timestamp: anchor.timestamp,
|
|
13796
|
+
signal: anchor.signal,
|
|
13797
|
+
fromParticipantIndex: participantIndexByMemberId.get(anchor.fromMemberId) ?? 0,
|
|
13798
|
+
toParticipantIndex: participantIndexByMemberId.get(anchor.toMemberId) ?? 0
|
|
13799
|
+
})),
|
|
13800
|
+
anchorsTruncated: pair.anchorsTruncated
|
|
13801
|
+
}));
|
|
13802
|
+
const priorityReasons = [];
|
|
13803
|
+
if (pairs.some((pair) => pair.directReplyCount > 0)) priorityReasons.push("has_direct_reply");
|
|
13804
|
+
if (pairs.some((pair) => (pair.coOccurrenceCount ?? 0) > 0)) priorityReasons.push("has_proximity");
|
|
13805
|
+
if (participants.every((participant) => participant.presenceObservedInRange)) {
|
|
13806
|
+
priorityReasons.push("all_participants_spoke");
|
|
13807
|
+
}
|
|
13808
|
+
return {
|
|
13809
|
+
...descriptor,
|
|
13810
|
+
lastMessageTs: facts.sessionLastMessageTs,
|
|
13811
|
+
memberCount: descriptor.sessionType === "group" /* GROUP */ ? facts.memberCount : null,
|
|
13812
|
+
participants,
|
|
13813
|
+
overlapRange: facts.overlapRange,
|
|
13814
|
+
allParticipantsCoActiveDays: facts.allParticipantsCoActiveDays,
|
|
13815
|
+
pairs,
|
|
13816
|
+
priorityReasons,
|
|
13817
|
+
proximityStatus: facts.proximityStatus
|
|
13818
|
+
};
|
|
13819
|
+
}
|
|
13820
|
+
function summarizeSharedInteractions(sessions, completeResult) {
|
|
13821
|
+
return {
|
|
13822
|
+
scope: completeResult ? "complete_result" : "current_batch",
|
|
13823
|
+
commonSessions: sessions.length,
|
|
13824
|
+
commonPrivateSessions: sessions.filter((session) => session.sessionType === "private" /* PRIVATE */).length,
|
|
13825
|
+
commonGroupSessions: sessions.filter((session) => session.sessionType === "group" /* GROUP */).length,
|
|
13826
|
+
sessionsWithDirectReplies: sessions.filter((session) => session.pairs.some((pair) => pair.directReplyCount > 0)).length,
|
|
13827
|
+
sessionsWithProximitySignals: sessions.filter(
|
|
13828
|
+
(session) => session.pairs.some((pair) => (pair.coOccurrenceCount ?? 0) > 0)
|
|
13829
|
+
).length
|
|
13830
|
+
};
|
|
13831
|
+
}
|
|
13832
|
+
function emptySharedInteractionsResult(participants, unresolvedParticipantIndexes, range) {
|
|
13833
|
+
return {
|
|
13834
|
+
algorithmVersion: SHARED_INTERACTIONS_ALGORITHM_VERSION,
|
|
13835
|
+
proximityAlgorithmVersion: PROXIMITY_ALGORITHM_VERSION,
|
|
13836
|
+
participants,
|
|
13837
|
+
appliedRange: {
|
|
13838
|
+
...range,
|
|
13839
|
+
dataEarliestMessageTs: null,
|
|
13840
|
+
dataLatestMessageTs: null
|
|
13841
|
+
},
|
|
13842
|
+
summary: {
|
|
13843
|
+
scope: "complete_result",
|
|
13844
|
+
commonSessions: 0,
|
|
13845
|
+
commonPrivateSessions: 0,
|
|
13846
|
+
commonGroupSessions: 0,
|
|
13847
|
+
sessionsWithDirectReplies: 0,
|
|
13848
|
+
sessionsWithProximitySignals: 0
|
|
13849
|
+
},
|
|
13850
|
+
sessions: [],
|
|
13851
|
+
coverage: {
|
|
13852
|
+
candidateSessions: 0,
|
|
13853
|
+
scannedSessions: 0,
|
|
13854
|
+
matchedSessions: 0,
|
|
13855
|
+
returnedSessions: 0,
|
|
13856
|
+
failedSessions: 0,
|
|
13857
|
+
failedSessionIds: [],
|
|
13858
|
+
complete: true,
|
|
13859
|
+
nextCursor: null,
|
|
13860
|
+
truncated: false,
|
|
13861
|
+
truncatedReasons: [],
|
|
13862
|
+
unresolvedParticipantIndexes,
|
|
13863
|
+
identityCollisionSessions: 0,
|
|
13864
|
+
ownerResolution: participants.some((participant) => participant.ref.type === "owner") ? { resolvedSessions: 0, missingOwnerSessions: 0, unresolvedOwnerSessions: 0 } : void 0
|
|
13865
|
+
}
|
|
13866
|
+
};
|
|
13867
|
+
}
|
|
13868
|
+
function compareSharedInteractionSessions(left, right) {
|
|
13869
|
+
const leftReplies = left.pairs.reduce((sum, pair) => sum + pair.directReplyCount, 0);
|
|
13870
|
+
const rightReplies = right.pairs.reduce((sum, pair) => sum + pair.directReplyCount, 0);
|
|
13871
|
+
if (leftReplies !== rightReplies) return rightReplies - leftReplies;
|
|
13872
|
+
const leftProximity = left.pairs.reduce((sum, pair) => sum + (pair.coOccurrenceRawScore ?? 0), 0);
|
|
13873
|
+
const rightProximity = right.pairs.reduce((sum, pair) => sum + (pair.coOccurrenceRawScore ?? 0), 0);
|
|
13874
|
+
if (leftProximity !== rightProximity) return rightProximity - leftProximity;
|
|
13875
|
+
const leftCoActiveDays = left.pairs.reduce((sum, pair) => sum + pair.coActiveDays, 0);
|
|
13876
|
+
const rightCoActiveDays = right.pairs.reduce((sum, pair) => sum + pair.coActiveDays, 0);
|
|
13877
|
+
if (leftCoActiveDays !== rightCoActiveDays) return rightCoActiveDays - leftCoActiveDays;
|
|
13878
|
+
const leftLastInteractionTs = latestPairInteractionTs(left.pairs);
|
|
13879
|
+
const rightLastInteractionTs = latestPairInteractionTs(right.pairs);
|
|
13880
|
+
if (leftLastInteractionTs !== rightLastInteractionTs) return rightLastInteractionTs - leftLastInteractionTs;
|
|
13881
|
+
if (left.memberCount !== right.memberCount) {
|
|
13882
|
+
return (left.memberCount ?? Number.POSITIVE_INFINITY) - (right.memberCount ?? Number.POSITIVE_INFINITY);
|
|
13883
|
+
}
|
|
13884
|
+
return left.sessionId.localeCompare(right.sessionId);
|
|
13885
|
+
}
|
|
13886
|
+
function latestPairInteractionTs(pairs) {
|
|
13887
|
+
let latest = 0;
|
|
13888
|
+
for (const pair of pairs) {
|
|
13889
|
+
latest = Math.max(latest, pair.lastDirectReplyTs ?? 0, pair.lastProximityTs ?? 0);
|
|
13890
|
+
}
|
|
13891
|
+
return latest;
|
|
13892
|
+
}
|
|
13893
|
+
function emptyContactSessionsResult(contactCacheStatus, range) {
|
|
13894
|
+
return {
|
|
13895
|
+
algorithmVersion: CONTACT_SESSIONS_ALGORITHM_VERSION,
|
|
13896
|
+
contact: null,
|
|
13897
|
+
appliedRange: {
|
|
13898
|
+
...range,
|
|
13899
|
+
dataEarliestMessageTs: null,
|
|
13900
|
+
dataLatestMessageTs: null
|
|
13901
|
+
},
|
|
13902
|
+
summary: {
|
|
13903
|
+
scope: "complete_result",
|
|
13904
|
+
matchedSessions: 0,
|
|
13905
|
+
privateSessions: 0,
|
|
13906
|
+
groupSessions: 0,
|
|
13907
|
+
spokeSessions: 0,
|
|
13908
|
+
rosterOnlySessions: 0,
|
|
13909
|
+
ownMessageCount: 0,
|
|
13910
|
+
firstOwnMessageTs: null,
|
|
13911
|
+
lastOwnMessageTs: null
|
|
13912
|
+
},
|
|
13913
|
+
sessions: [],
|
|
13914
|
+
coverage: {
|
|
13915
|
+
candidateSessions: 0,
|
|
13916
|
+
scannedSessions: 0,
|
|
13917
|
+
matchedSessions: 0,
|
|
13918
|
+
returnedSessions: 0,
|
|
13919
|
+
failedSessions: 0,
|
|
13920
|
+
failedSessionIds: [],
|
|
13921
|
+
complete: true,
|
|
13922
|
+
nextCursor: null,
|
|
13923
|
+
truncated: false,
|
|
13924
|
+
truncatedReasons: [],
|
|
13925
|
+
contactCacheStatus
|
|
13926
|
+
}
|
|
13927
|
+
};
|
|
13928
|
+
}
|
|
13929
|
+
function minNullable(left, right) {
|
|
13930
|
+
if (left === null) return right;
|
|
13931
|
+
if (right === null) return left;
|
|
13932
|
+
return Math.min(left, right);
|
|
13933
|
+
}
|
|
13934
|
+
function maxNullable(left, right) {
|
|
13935
|
+
if (left === null) return right;
|
|
13936
|
+
if (right === null) return left;
|
|
13937
|
+
return Math.max(left, right);
|
|
13938
|
+
}
|
|
13939
|
+
function createInspectionFingerprint(value) {
|
|
13940
|
+
return createHash2("sha256").update(JSON.stringify(value)).digest("base64url");
|
|
13941
|
+
}
|
|
13942
|
+
function createInspectionCursor(fingerprint, afterSessionId, range) {
|
|
13943
|
+
const payload = { version: 2, fingerprint, afterSessionId, range };
|
|
13944
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
13945
|
+
}
|
|
13946
|
+
function parseInspectionCursor(value, createFingerprint, candidateSessionIds, defaultRange) {
|
|
13947
|
+
if (!value) {
|
|
13948
|
+
return {
|
|
13949
|
+
nextCandidateIndex: 0,
|
|
13950
|
+
fingerprint: createFingerprint(defaultRange),
|
|
13951
|
+
range: defaultRange
|
|
13952
|
+
};
|
|
13953
|
+
}
|
|
13954
|
+
try {
|
|
13955
|
+
const payload = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
13956
|
+
if (payload.version !== 2 || !isInspectionRange(payload.range)) throw new Error("cursor payload is invalid");
|
|
13957
|
+
const fingerprint = createFingerprint(payload.range);
|
|
13958
|
+
if (payload.fingerprint !== fingerprint) throw new Error("cursor mismatch");
|
|
13959
|
+
if (payload.afterSessionId === null) {
|
|
13960
|
+
return { nextCandidateIndex: 0, fingerprint, range: payload.range };
|
|
13961
|
+
}
|
|
13962
|
+
if (typeof payload.afterSessionId !== "string") throw new Error("cursor session is missing");
|
|
13963
|
+
const index = candidateSessionIds.indexOf(payload.afterSessionId);
|
|
13964
|
+
if (index < 0) throw new Error("cursor session no longer exists");
|
|
13965
|
+
return { nextCandidateIndex: index + 1, fingerprint, range: payload.range };
|
|
13966
|
+
} catch {
|
|
13967
|
+
throw new Error("cursor is invalid or does not match the current inspection request");
|
|
13968
|
+
}
|
|
13969
|
+
}
|
|
13970
|
+
function isInspectionRange(value) {
|
|
13971
|
+
if (!value || typeof value !== "object") return false;
|
|
13972
|
+
const range = value;
|
|
13973
|
+
const isTimestamp = (timestamp) => timestamp === null || typeof timestamp === "number" && Number.isFinite(timestamp);
|
|
13974
|
+
return isTimestamp(range.startTs) && isTimestamp(range.endTs) && (range.startTs === null || range.endTs === null || range.startTs <= range.endTs);
|
|
13975
|
+
}
|
|
13976
|
+
function throwIfAborted(signal) {
|
|
13977
|
+
if (!signal?.aborted) return;
|
|
13978
|
+
const error = new Error("Cross-chat analysis was interrupted");
|
|
13979
|
+
error.name = "AbortError";
|
|
13980
|
+
throw error;
|
|
13981
|
+
}
|
|
13982
|
+
function yieldToEventLoop2() {
|
|
13983
|
+
return new Promise((resolve4) => setImmediate(resolve4));
|
|
13984
|
+
}
|
|
13985
|
+
function createNotFoundError(message) {
|
|
13986
|
+
return Object.assign(new Error(message), { statusCode: 404 });
|
|
13987
|
+
}
|
|
13988
|
+
|
|
13989
|
+
// ../../packages/node-runtime/src/services/cross-chat-analysis/preprocess.ts
|
|
13990
|
+
function preprocessCrossChatMessages(adapter, sessionId, messages, config) {
|
|
13991
|
+
const safeConfig = config ? { ...config, mergeConsecutive: false, anonymizeNames: false } : void 0;
|
|
13992
|
+
const processed = preprocessMessages(
|
|
13993
|
+
messages.map((message) => ({ ...message })),
|
|
13994
|
+
safeConfig
|
|
13995
|
+
);
|
|
13996
|
+
if (!config?.anonymizeNames) return processed;
|
|
13997
|
+
let ownerPlatformId = null;
|
|
13998
|
+
try {
|
|
13999
|
+
const db = adapter.openReadonly(sessionId);
|
|
14000
|
+
ownerPlatformId = db ? getSessionMeta(db)?.ownerId?.trim() || null : null;
|
|
14001
|
+
} catch {
|
|
14002
|
+
}
|
|
14003
|
+
return processed.map((message) => ({
|
|
14004
|
+
...message,
|
|
14005
|
+
senderName: ownerPlatformId && message.senderPlatformId === ownerPlatformId ? "Owner" : `U${message.senderId}@${sessionId}`
|
|
14006
|
+
}));
|
|
14007
|
+
}
|
|
14008
|
+
function preprocessCrossChatSummaries(adapter, sessionId, summaries, config) {
|
|
14009
|
+
if (!config) return summaries.map(cloneSummary);
|
|
14010
|
+
const participantPseudonyms = config.anonymizeNames ? buildParticipantPseudonyms(
|
|
14011
|
+
adapter,
|
|
14012
|
+
sessionId,
|
|
14013
|
+
summaries.flatMap((summary) => summary.participants)
|
|
14014
|
+
) : null;
|
|
14015
|
+
return summaries.flatMap((summary) => {
|
|
14016
|
+
if (matchesBlacklist(summary.summary, config.blacklistKeywords)) return [];
|
|
14017
|
+
let safeSummary = config.desensitize ? desensitizeText(summary.summary, config.desensitizeRules) : summary.summary;
|
|
14018
|
+
if (participantPseudonyms) {
|
|
14019
|
+
const anonymizedSummary = replaceKnownParticipantNames(safeSummary, participantPseudonyms);
|
|
14020
|
+
if (anonymizedSummary === null) return [];
|
|
14021
|
+
safeSummary = anonymizedSummary;
|
|
14022
|
+
}
|
|
14023
|
+
const participants = summary.participants.filter((participant) => !matchesBlacklist(participant, config.blacklistKeywords)).map((participant) => {
|
|
14024
|
+
if (participantPseudonyms) return participantPseudonyms.get(participant) ?? `Participant@${sessionId}`;
|
|
14025
|
+
return config.desensitize ? desensitizeText(participant, config.desensitizeRules) : participant;
|
|
14026
|
+
}).filter((participant, index, values) => participant.length > 0 && values.indexOf(participant) === index);
|
|
14027
|
+
return [{ ...summary, participants, summary: safeSummary }];
|
|
14028
|
+
});
|
|
14029
|
+
}
|
|
14030
|
+
function preprocessCrossChatLabel(value, pseudonym, config) {
|
|
14031
|
+
if (!config) return value;
|
|
14032
|
+
if (config.anonymizeNames || matchesBlacklist(value, config.blacklistKeywords)) return pseudonym;
|
|
14033
|
+
return config.desensitize ? desensitizeText(value, config.desensitizeRules) : value;
|
|
14034
|
+
}
|
|
14035
|
+
function cloneSummary(summary) {
|
|
14036
|
+
return { ...summary, participants: [...summary.participants] };
|
|
14037
|
+
}
|
|
14038
|
+
function replaceKnownParticipantNames(summary, pseudonyms) {
|
|
14039
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
14040
|
+
for (const [participantName, pseudonym] of pseudonyms) {
|
|
14041
|
+
const trimmedName = participantName.trim();
|
|
14042
|
+
if (participantName) replacements.set(participantName, pseudonym);
|
|
14043
|
+
if (trimmedName) replacements.set(trimmedName, pseudonym);
|
|
14044
|
+
}
|
|
14045
|
+
if (replacements.size === 0) return summary;
|
|
14046
|
+
const ambiguousLabels = /* @__PURE__ */ new Set();
|
|
14047
|
+
const alternatives = [...replacements.keys()].sort((left, right) => Array.from(right).length - Array.from(left).length).map((participantName) => {
|
|
14048
|
+
const escapedName = escapeRegExp(participantName);
|
|
14049
|
+
if (/^[\p{Script=Latin}\p{N}_]+$/u.test(participantName)) {
|
|
14050
|
+
return `(?<![\\p{Script=Latin}\\p{N}_])${escapedName}(?![\\p{Script=Latin}\\p{N}_])`;
|
|
14051
|
+
}
|
|
14052
|
+
if (Array.from(participantName).length === 1 && /[\p{L}\p{N}]/u.test(participantName)) {
|
|
14053
|
+
ambiguousLabels.add(participantName);
|
|
14054
|
+
return `(?<![\\p{L}\\p{N}_])${escapedName}(?![\\p{L}\\p{N}_])`;
|
|
14055
|
+
}
|
|
14056
|
+
return escapedName;
|
|
14057
|
+
});
|
|
14058
|
+
const pattern = new RegExp(alternatives.join("|"), "gu");
|
|
14059
|
+
const anonymized = summary.replace(pattern, (participantName) => replacements.get(participantName) ?? participantName);
|
|
14060
|
+
return [...ambiguousLabels].some((participantName) => anonymized.includes(participantName)) ? null : anonymized;
|
|
14061
|
+
}
|
|
14062
|
+
function escapeRegExp(value) {
|
|
14063
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14064
|
+
}
|
|
14065
|
+
function buildParticipantPseudonyms(adapter, sessionId, participantNames) {
|
|
14066
|
+
const pseudonyms = /* @__PURE__ */ new Map();
|
|
14067
|
+
const fallbackPseudonyms = /* @__PURE__ */ new Map();
|
|
14068
|
+
let ownerPlatformId = null;
|
|
14069
|
+
let members = [];
|
|
14070
|
+
try {
|
|
14071
|
+
const db = adapter.openReadonly(sessionId);
|
|
14072
|
+
if (db) {
|
|
14073
|
+
ownerPlatformId = getSessionMeta(db)?.ownerId?.trim() || null;
|
|
14074
|
+
members = getNonSystemMembersForContacts(db);
|
|
14075
|
+
}
|
|
14076
|
+
} catch {
|
|
14077
|
+
}
|
|
14078
|
+
for (const participantName of participantNames) {
|
|
14079
|
+
if (pseudonyms.has(participantName)) continue;
|
|
14080
|
+
const normalizedName = participantName.trim().toLocaleLowerCase();
|
|
14081
|
+
const matches = members.filter(
|
|
14082
|
+
(member) => [member.name, member.platformId, ...member.aliases].some(
|
|
14083
|
+
(candidate) => candidate.trim().toLocaleLowerCase() === normalizedName
|
|
14084
|
+
)
|
|
14085
|
+
);
|
|
14086
|
+
const uniqueMatches = [...new Map(matches.map((member) => [member.id, member])).values()];
|
|
14087
|
+
if (uniqueMatches.length === 1) {
|
|
14088
|
+
const member = uniqueMatches[0];
|
|
14089
|
+
pseudonyms.set(
|
|
14090
|
+
participantName,
|
|
14091
|
+
ownerPlatformId && member.platformId === ownerPlatformId ? "Owner" : `U${member.id}@${sessionId}`
|
|
14092
|
+
);
|
|
14093
|
+
continue;
|
|
14094
|
+
}
|
|
14095
|
+
let fallback = fallbackPseudonyms.get(normalizedName);
|
|
14096
|
+
if (!fallback) {
|
|
14097
|
+
fallback = `Participant${fallbackPseudonyms.size + 1}@${sessionId}`;
|
|
14098
|
+
fallbackPseudonyms.set(normalizedName, fallback);
|
|
14099
|
+
}
|
|
14100
|
+
pseudonyms.set(participantName, fallback);
|
|
14101
|
+
}
|
|
14102
|
+
return pseudonyms;
|
|
14103
|
+
}
|
|
14104
|
+
|
|
14105
|
+
// ../../packages/node-runtime/src/services/cross-chat-analysis/index.ts
|
|
14106
|
+
var cross_chat_analysis_exports = {};
|
|
14107
|
+
__export(cross_chat_analysis_exports, {
|
|
14108
|
+
createCrossChatAnalysisService: () => createCrossChatAnalysisService,
|
|
14109
|
+
preprocessCrossChatLabel: () => preprocessCrossChatLabel,
|
|
14110
|
+
preprocessCrossChatMessages: () => preprocessCrossChatMessages,
|
|
14111
|
+
preprocessCrossChatSummaries: () => preprocessCrossChatSummaries
|
|
14112
|
+
});
|
|
14113
|
+
|
|
12223
14114
|
// ../../packages/node-runtime/src/services/people/relationships/signature.ts
|
|
12224
14115
|
function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
|
|
12225
14116
|
const parts = [
|
|
@@ -13624,7 +15515,7 @@ var LocalEmbeddingProvider = class {
|
|
|
13624
15515
|
import path22 from "path";
|
|
13625
15516
|
|
|
13626
15517
|
// ../../packages/node-runtime/src/semantic-index/chunker-config.ts
|
|
13627
|
-
import { createHash as
|
|
15518
|
+
import { createHash as createHash3 } from "crypto";
|
|
13628
15519
|
var CHUNKER_VERSION = "v1.3";
|
|
13629
15520
|
var STRATEGY_ID = "balanced";
|
|
13630
15521
|
var DEFAULT_CHUNKER_CONFIG = {
|
|
@@ -13639,7 +15530,7 @@ var DEFAULT_CHUNKER_CONFIG = {
|
|
|
13639
15530
|
semanticVoidSkipThreshold: 20
|
|
13640
15531
|
};
|
|
13641
15532
|
function sha256Hex(input) {
|
|
13642
|
-
return
|
|
15533
|
+
return createHash3("sha256").update(input, "utf-8").digest("hex");
|
|
13643
15534
|
}
|
|
13644
15535
|
function computeChunkerConfigHash(config) {
|
|
13645
15536
|
const canonical = JSON.stringify({
|
|
@@ -14327,7 +16218,7 @@ var SemanticIndexJobQueue = class {
|
|
|
14327
16218
|
};
|
|
14328
16219
|
|
|
14329
16220
|
// ../../packages/node-runtime/src/semantic-index/chunker.ts
|
|
14330
|
-
import { createHash as
|
|
16221
|
+
import { createHash as createHash4 } from "crypto";
|
|
14331
16222
|
var VOID_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
14332
16223
|
1 /* IMAGE */,
|
|
14333
16224
|
2 /* VOICE */,
|
|
@@ -14410,7 +16301,7 @@ function sumEffectiveChars(messages) {
|
|
|
14410
16301
|
return total;
|
|
14411
16302
|
}
|
|
14412
16303
|
function sha256Hex2(input) {
|
|
14413
|
-
return
|
|
16304
|
+
return createHash4("sha256").update(input, "utf-8").digest("hex");
|
|
14414
16305
|
}
|
|
14415
16306
|
var EMBEDDING_HEADER_BUDGET_RATIO = 0.25;
|
|
14416
16307
|
var SOURCE_TITLE_HEADER_SHARE = 1 / 3;
|
|
@@ -16344,10 +18235,10 @@ async function executePushImportUnlocked(deps, sessionId, payload) {
|
|
|
16344
18235
|
}
|
|
16345
18236
|
|
|
16346
18237
|
// ../../packages/node-runtime/src/services/import-idempotency.ts
|
|
16347
|
-
import { createHash as
|
|
18238
|
+
import { createHash as createHash5 } from "crypto";
|
|
16348
18239
|
var DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS = 60 * 60 * 1e3;
|
|
16349
18240
|
function hashImportBody(body) {
|
|
16350
|
-
return
|
|
18241
|
+
return createHash5("sha256").update(JSON.stringify(body ?? "")).digest("hex");
|
|
16351
18242
|
}
|
|
16352
18243
|
var ImportIdempotencyCache = class {
|
|
16353
18244
|
constructor(ttlMs = DEFAULT_IMPORT_IDEMPOTENCY_TTL_MS) {
|
|
@@ -16414,6 +18305,7 @@ export {
|
|
|
16414
18305
|
MessageBatchInserter,
|
|
16415
18306
|
generateFallbackMessageKey,
|
|
16416
18307
|
createMessageDedupState,
|
|
18308
|
+
hasUnmatchedFallbackOnlyMessage,
|
|
16417
18309
|
registerMessageAndCheckDuplicate,
|
|
16418
18310
|
streamingImport,
|
|
16419
18311
|
analyzeNewImport,
|
|
@@ -16454,6 +18346,11 @@ export {
|
|
|
16454
18346
|
owner_profile_service_exports,
|
|
16455
18347
|
createContactsService,
|
|
16456
18348
|
contacts_exports,
|
|
18349
|
+
createCrossChatAnalysisService,
|
|
18350
|
+
preprocessCrossChatMessages,
|
|
18351
|
+
preprocessCrossChatSummaries,
|
|
18352
|
+
preprocessCrossChatLabel,
|
|
18353
|
+
cross_chat_analysis_exports,
|
|
16457
18354
|
createPeopleRelationshipsService,
|
|
16458
18355
|
relationships_exports,
|
|
16459
18356
|
createGlobalInsightService,
|
|
@@ -16525,4 +18422,4 @@ export {
|
|
|
16525
18422
|
hashImportBody,
|
|
16526
18423
|
ImportIdempotencyCache
|
|
16527
18424
|
};
|
|
16528
|
-
//# sourceMappingURL=chunk-
|
|
18425
|
+
//# sourceMappingURL=chunk-DQR2YS3I.mjs.map
|