chatlab-cli 0.36.1 → 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-XPJ43KSW.mjs → chat-command-WBAJBKIE.mjs} +15 -16
- package/dist/{chat-command-XPJ43KSW.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-Q3NEL2KK.mjs → chunk-4J4MA3RC.mjs} +2606 -671
- package/dist/chunk-4J4MA3RC.mjs.map +1 -0
- package/dist/{chunk-NFTCEJBJ.mjs → chunk-5YOHLJFB.mjs} +98 -284
- package/dist/chunk-5YOHLJFB.mjs.map +1 -0
- package/dist/{chunk-L7TIIVAY.mjs → chunk-CR2MU7LN.mjs} +49 -55
- package/dist/chunk-CR2MU7LN.mjs.map +1 -0
- package/dist/{chunk-2JPFBIAG.mjs → chunk-DKIYHA3O.mjs} +284 -31
- package/dist/chunk-DKIYHA3O.mjs.map +1 -0
- package/dist/{chunk-FJOGBUXU.mjs → chunk-DQR2YS3I.mjs} +1930 -33
- package/dist/chunk-DQR2YS3I.mjs.map +1 -0
- package/dist/{chunk-JOVL7OTW.mjs → chunk-EJBBVLUN.mjs} +3 -3
- package/dist/chunk-EJBBVLUN.mjs.map +1 -0
- package/dist/{chunk-X2QBH24F.mjs → chunk-FM3GXNEO.mjs} +1643 -3
- package/dist/chunk-FM3GXNEO.mjs.map +1 -0
- package/dist/{chunk-R73XMAB2.mjs → chunk-MSMDLDPB.mjs} +96 -5
- package/dist/chunk-MSMDLDPB.mjs.map +1 -0
- package/dist/{chunk-NQHZPSW2.mjs → chunk-N2WGG3MS.mjs} +2 -2
- package/dist/{chunk-67HNPYZJ.mjs → chunk-RXMSZIYD.mjs} +3 -3
- package/dist/{chunk-5MJO4AJB.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-5RVY5J5U.mjs → http-X6OEOOSH.mjs} +12 -12
- package/dist/{import-ILTWVHKU.mjs → import-WBJ6YIQ7.mjs} +7 -7
- package/dist/index.mjs +12 -12
- package/dist/{mcp-Y2YXOFB4.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-OV5ZNE3Z.mjs → src-WE5HNN2O.mjs} +44 -14
- package/dist/{stream-import-XOUM3Z5W.mjs → stream-import-YYQVZDGQ.mjs} +7 -7
- package/dist/{worker-runtime-JNGURHDM.mjs → worker-runtime-YGB5ZDE4.mjs} +5 -5
- package/dist-cli-web/assets/AISettingsTab-Co2sM1iY.js +1 -0
- package/dist-cli-web/assets/{AboutTab-CxI8zJNz.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-_MYcI_E2.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-i64DlwQT.js → BufferResource-C-RfYwQW.js} +1 -1
- package/dist-cli-web/assets/{CanvasRenderer-B76mEt9A.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-CyfPIlIy.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-DqNBNa1D.js → DatePicker.vue_vue_type_script_setup_true_lang-CQuub8-f.js} +1 -1
- package/dist-cli-web/assets/{DebugToolsPanel-CbNsD9co.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-yxkeHO78.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-_j82asQU.js → PageTabs.vue_vue_type_script_setup_true_lang-DYQRFk9s.js} +1 -1
- package/dist-cli-web/assets/{RelationshipGalaxyCanvas-BODk18aA.js → RelationshipGalaxyCanvas-Cn7Eta5U.js} +3 -3
- package/dist-cli-web/assets/{RelationshipGalaxyThreeCanvas-BJyuRiC-.js → RelationshipGalaxyThreeCanvas-LlaX57z6.js} +1 -1
- package/dist-cli-web/assets/{RenderTargetSystem-CVMvAUSv.js → RenderTargetSystem-JhA2PUFi.js} +1 -1
- package/dist-cli-web/assets/{RovingFocusItem-DC_06OmN.js → RovingFocusItem-DHaiTb4j.js} +1 -1
- package/dist-cli-web/assets/{ScreenCaptureModal-DrBFDOtC.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-B0ebN0f8.js → StorageTab-B7GHadia.js} +1 -1
- package/dist-cli-web/assets/Switch-u4zlvUMa.js +1 -0
- package/dist-cli-web/assets/{Tabs-N3Q-Mpqi.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-Ccunouyf.js → TimeSelect.vue_vue_type_script_setup_true_lang-DKa1flT-.js} +1 -1
- package/dist-cli-web/assets/{VisuallyHiddenInput-OAJEt_Y5.js → VisuallyHiddenInput-DGDqcvUy.js} +1 -1
- package/dist-cli-web/assets/{WebGLRenderer-DuUeftYD.js → WebGLRenderer-ifvgA1fe.js} +1 -1
- package/dist-cli-web/assets/{WebGPURenderer-DoWQxiWZ.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-Cb-mtR2y.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-BbcFaCRB.js → fetch-Bf4cqeM3.js} +1 -1
- package/dist-cli-web/assets/{fetch-B-6tyYWu.js → fetch-BiXab0IF.js} +1 -1
- package/dist-cli-web/assets/{fetch-DEub4tOo.js → fetch-C8CcoTWY.js} +1 -1
- package/dist-cli-web/assets/{fetch-Qs38Hx1V.js → fetch-Co2vLmiE.js} +1 -1
- package/dist-cli-web/assets/{fetch-DLTlYnDB.js → fetch-D3nLtFBi.js} +1 -1
- package/dist-cli-web/assets/{fetch-BkA_so1e.js → fetch-DNSDHSqQ.js} +1 -1
- package/dist-cli-web/assets/{fetch-sYDJ-Zs8.js → fetch-DeHqF6yL.js} +1 -1
- package/dist-cli-web/assets/{fetch-B4PYy5JS.js → fetch-jx1VdM9j.js} +3 -3
- package/dist-cli-web/assets/{fetch-CW2fKvGp.js → fetch-mYSeL4Ki.js} +1 -1
- package/dist-cli-web/assets/fetch-qBaUXqFt.js +1 -0
- package/dist-cli-web/assets/{fetch-BzXuAaT3.js → fetch-r6mhEGrP.js} +1 -1
- package/dist-cli-web/assets/{fetch-AvsnJLWJ.js → fetch-wzv71PYy.js} +1 -1
- package/dist-cli-web/assets/{host-DYIkQqvY.js → host-1emQf9_4.js} +1 -1
- package/dist-cli-web/assets/{host-B6SQ6OGo.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-CShh95fU.js → index-CYc_VLgK.js} +1 -1
- package/dist-cli-web/assets/index-ClHMs0-a.js +2 -0
- package/dist-cli-web/assets/{index-D64XYYts.js → index-Cw0IhKZ6.js} +10 -10
- package/dist-cli-web/assets/{index-C_OS8Y8W.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-0jvXFFnh.js → people-page-header-Cx5r9Bk2.js} +1 -1
- package/dist-cli-web/assets/{prompt-BPYj29Vq.js → prompt-BggEDxxi.js} +1 -1
- package/dist-cli-web/assets/{semantic-index-config-builder-vO37z355.js → semantic-index-config-builder-1sO0aEc1.js} +1 -1
- package/dist-cli-web/assets/{service-D29Vftq9.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-BA0EwImD.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-ZPE_yGnC.js → webworkerAll-FCJk6reG.js} +1 -1
- package/dist-cli-web/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-2JPFBIAG.mjs.map +0 -1
- package/dist/chunk-6QP47LID.mjs +0 -41
- package/dist/chunk-6QP47LID.mjs.map +0 -1
- package/dist/chunk-FJOGBUXU.mjs.map +0 -1
- package/dist/chunk-JOVL7OTW.mjs.map +0 -1
- package/dist/chunk-L7TIIVAY.mjs.map +0 -1
- package/dist/chunk-NFTCEJBJ.mjs.map +0 -1
- package/dist/chunk-Q3NEL2KK.mjs.map +0 -1
- package/dist/chunk-R73XMAB2.mjs.map +0 -1
- package/dist/chunk-X2QBH24F.mjs.map +0 -1
- package/dist-cli-web/assets/AISettingsTab-vvjijrc5.js +0 -1
- package/dist-cli-web/assets/ActionToolsPanel-Dp-ojXjO.css +0 -1
- package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-w0hHEodr.js +0 -110
- package/dist-cli-web/assets/ApiSettingsTab-CK7U6dVA.js +0 -2
- package/dist-cli-web/assets/BatchManageTab-1WnEqqpB.js +0 -1
- package/dist-cli-web/assets/ChatRecordDrawer-Bp6OSCrz.js +0 -1
- package/dist-cli-web/assets/Checkbox-Ci7IeRw1.js +0 -1
- package/dist-cli-web/assets/DataDirCleanupNotice-Dh63pOLt.js +0 -1
- package/dist-cli-web/assets/EChart.vue_vue_type_script_setup_true_lang-YeqmodwE.js +0 -1
- package/dist-cli-web/assets/EChartBar.vue_vue_type_script_setup_true_lang-C3pZmq0v.js +0 -1
- package/dist-cli-web/assets/EChartLine.vue_vue_type_script_setup_true_lang-Co6olfwv.js +0 -1
- package/dist-cli-web/assets/EChartWordcloud-rDusZwSq.js +0 -1
- package/dist-cli-web/assets/GlobalTaskBar-BlZ-fFiq.js +0 -1
- package/dist-cli-web/assets/GlobalTaskBar-COW_uBX6.css +0 -1
- package/dist-cli-web/assets/GroupRelationshipGalaxyView-BXQBX0zJ.js +0 -1
- package/dist-cli-web/assets/Label-CFAZFBQX.js +0 -1
- package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-CwgpqtMZ.js +0 -1
- package/dist-cli-web/assets/MessageExportModal-DU71grc7.js +0 -1
- package/dist-cli-web/assets/PageHeader-BDPf4NuI.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-C-Zj2biH.js +0 -1
- package/dist-cli-web/assets/Select-DLK2LYhO.js +0 -1
- package/dist-cli-web/assets/SettingsModal-DqaR4Dx5.js +0 -2
- package/dist-cli-web/assets/Switch-CokhQWxm.js +0 -1
- package/dist-cli-web/assets/Tabs-D1UiZNaZ.js +0 -1
- package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-OT-I0mbU.js +0 -1
- package/dist-cli-web/assets/aiChat-CdADfRnU.js +0 -8
- package/dist-cli-web/assets/cli-web-BHQCZLLg.js +0 -1
- package/dist-cli-web/assets/fetch-ul8-uQIb.js +0 -1
- package/dist-cli-web/assets/index--IEkRjKe.js +0 -64
- package/dist-cli-web/assets/index-9iaY_EWG.js +0 -1
- package/dist-cli-web/assets/index-B4J99zv3.css +0 -1
- package/dist-cli-web/assets/index-BBeqtL8d.css +0 -1
- package/dist-cli-web/assets/index-BnaLj2dx.css +0 -1
- package/dist-cli-web/assets/index-C7omDgiJ.js +0 -2
- package/dist-cli-web/assets/index-CHsM84U1.js +0 -2
- package/dist-cli-web/assets/index-CL8u01Pf.js +0 -137
- package/dist-cli-web/assets/index-DrCc8p_I.css +0 -1
- package/dist-cli-web/assets/index-UpTe1KzQ.js +0 -1
- package/dist-cli-web/assets/index-a8Hw6eD8.js +0 -1
- package/dist-cli-web/assets/index-jwjzZNKm.js +0 -1
- package/dist-cli-web/assets/index-pkxlF8Ee.js +0 -1
- package/dist-cli-web/assets/index-sWxuTsa7.js +0 -36
- package/dist-cli-web/assets/service-CVoA9yNf.js +0 -1
- package/dist-cli-web/assets/service-DMysyLCa.js +0 -1
- package/dist-cli-web/assets/useFormControl-DVawP2DO.js +0 -1
- package/dist-cli-web/assets/useTable-Bv3R_Hux.js +0 -1
- /package/dist/{chunk-NQHZPSW2.mjs.map → chunk-N2WGG3MS.mjs.map} +0 -0
- /package/dist/{chunk-67HNPYZJ.mjs.map → chunk-RXMSZIYD.mjs.map} +0 -0
- /package/dist/{chunk-5MJO4AJB.mjs.map → chunk-SQWN4GTQ.mjs.map} +0 -0
- /package/dist/{http-5RVY5J5U.mjs.map → http-X6OEOOSH.mjs.map} +0 -0
- /package/dist/{import-ILTWVHKU.mjs.map → import-WBJ6YIQ7.mjs.map} +0 -0
- /package/dist/{mcp-Y2YXOFB4.mjs.map → mcp-FR6OKWKL.mjs.map} +0 -0
- /package/dist/{src-OV5ZNE3Z.mjs.map → src-WE5HNN2O.mjs.map} +0 -0
- /package/dist/{stream-import-XOUM3Z5W.mjs.map → stream-import-YYQVZDGQ.mjs.map} +0 -0
- /package/dist/{worker-runtime-JNGURHDM.mjs.map → worker-runtime-YGB5ZDE4.mjs.map} +0 -0
|
@@ -4118,6 +4118,14 @@ function buildMsgConditions(options) {
|
|
|
4118
4118
|
if (options?.senderId != null) {
|
|
4119
4119
|
conds.push("msg.sender_id = ?");
|
|
4120
4120
|
params.push(options.senderId);
|
|
4121
|
+
} else if (options?.senderIds) {
|
|
4122
|
+
const senderIds = [...new Set(options.senderIds)];
|
|
4123
|
+
if (senderIds.length === 0) {
|
|
4124
|
+
conds.push("1 = 0");
|
|
4125
|
+
} else {
|
|
4126
|
+
conds.push(`msg.sender_id IN (${senderIds.map(() => "?").join(", ")})`);
|
|
4127
|
+
params.push(...senderIds);
|
|
4128
|
+
}
|
|
4121
4129
|
}
|
|
4122
4130
|
if (options?.memberId != null) {
|
|
4123
4131
|
conds.push("msg.sender_id = ?");
|
|
@@ -4332,6 +4340,18 @@ function getLaughAnalysis(db, filter, keywords) {
|
|
|
4332
4340
|
};
|
|
4333
4341
|
}
|
|
4334
4342
|
var DEFAULT_CLUSTER_OPTIONS = { lookAhead: 3, decaySeconds: 120, topEdges: 100 };
|
|
4343
|
+
var MAX_CLUSTER_LOOK_AHEAD = 10;
|
|
4344
|
+
var DEFAULT_SELECTED_PROXIMITY_MAX_GAP_SECONDS = 1800;
|
|
4345
|
+
var CO_OCCURRENCE_MESSAGE_BATCH_SIZE = 2e4;
|
|
4346
|
+
function resolveClusterOptions(options) {
|
|
4347
|
+
const requestedLookAhead = options?.lookAhead ?? DEFAULT_CLUSTER_OPTIONS.lookAhead;
|
|
4348
|
+
const lookAhead = Number.isFinite(requestedLookAhead) ? Math.min(MAX_CLUSTER_LOOK_AHEAD, Math.max(0, Math.ceil(requestedLookAhead))) : DEFAULT_CLUSTER_OPTIONS.lookAhead;
|
|
4349
|
+
return {
|
|
4350
|
+
lookAhead,
|
|
4351
|
+
decaySeconds: options?.decaySeconds ?? DEFAULT_CLUSTER_OPTIONS.decaySeconds,
|
|
4352
|
+
topEdges: options?.topEdges ?? DEFAULT_CLUSTER_OPTIONS.topEdges
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4335
4355
|
function roundNum(value, digits = 4) {
|
|
4336
4356
|
const factor = 10 ** digits;
|
|
4337
4357
|
return Math.round(value * factor) / factor;
|
|
@@ -4340,44 +4360,222 @@ function clusterPairKey(aId, bId) {
|
|
|
4340
4360
|
return aId < bId ? `${aId}-${bId}` : `${bId}-${aId}`;
|
|
4341
4361
|
}
|
|
4342
4362
|
function accumulateCoOccurrencePairs(messages, options) {
|
|
4343
|
-
const opts =
|
|
4344
|
-
const
|
|
4345
|
-
const
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
const anchor = messages[i];
|
|
4363
|
+
const opts = resolveClusterOptions(options);
|
|
4364
|
+
const pairStatsBySource = /* @__PURE__ */ new Map();
|
|
4365
|
+
const pairs = [];
|
|
4366
|
+
for (let index = 0; index < messages.length - 1; index++) {
|
|
4367
|
+
const anchor = messages[index];
|
|
4349
4368
|
const seenPartners = /* @__PURE__ */ new Set();
|
|
4350
4369
|
let partnersFound = 0;
|
|
4351
|
-
for (let
|
|
4352
|
-
const candidate = messages[
|
|
4370
|
+
for (let nextIndex = index + 1; nextIndex < messages.length && partnersFound < opts.lookAhead; nextIndex++) {
|
|
4371
|
+
const candidate = messages[nextIndex];
|
|
4353
4372
|
if (candidate.senderId === anchor.senderId || seenPartners.has(candidate.senderId)) continue;
|
|
4354
4373
|
seenPartners.add(candidate.senderId);
|
|
4355
4374
|
partnersFound++;
|
|
4356
|
-
const
|
|
4357
|
-
const decayWeight = Math.exp(-deltaSeconds / opts.decaySeconds);
|
|
4375
|
+
const decayWeight = Math.exp(-(candidate.ts - anchor.ts) / opts.decaySeconds);
|
|
4358
4376
|
const positionWeight = 1 - (partnersFound - 1) * 0.2;
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4377
|
+
applyCoOccurrencePair(
|
|
4378
|
+
anchor.senderId,
|
|
4379
|
+
candidate.senderId,
|
|
4380
|
+
candidate.ts,
|
|
4381
|
+
decayWeight * positionWeight,
|
|
4382
|
+
pairStatsBySource,
|
|
4383
|
+
pairs
|
|
4384
|
+
);
|
|
4364
4385
|
}
|
|
4365
4386
|
}
|
|
4387
|
+
return pairs;
|
|
4388
|
+
}
|
|
4389
|
+
function accumulateCoOccurrencePairBatches(batches, options) {
|
|
4390
|
+
const senderChunks = [];
|
|
4391
|
+
const timestampChunks = [];
|
|
4392
|
+
let messageCount = 0;
|
|
4393
|
+
for (const batch of batches) {
|
|
4394
|
+
if (batch.length === 0) continue;
|
|
4395
|
+
const senderIds2 = new Float64Array(batch.length);
|
|
4396
|
+
const timestamps2 = new Float64Array(batch.length);
|
|
4397
|
+
for (let index = 0; index < batch.length; index++) {
|
|
4398
|
+
senderIds2[index] = batch[index].senderId;
|
|
4399
|
+
timestamps2[index] = batch[index].ts;
|
|
4400
|
+
}
|
|
4401
|
+
senderChunks.push(senderIds2);
|
|
4402
|
+
timestampChunks.push(timestamps2);
|
|
4403
|
+
messageCount += batch.length;
|
|
4404
|
+
}
|
|
4405
|
+
const senderIds = mergeNumberChunks(senderChunks, messageCount);
|
|
4406
|
+
const timestamps = mergeNumberChunks(timestampChunks, messageCount);
|
|
4407
|
+
senderChunks.length = 0;
|
|
4408
|
+
timestampChunks.length = 0;
|
|
4409
|
+
return accumulateCoOccurrenceColumns(senderIds, timestamps, options);
|
|
4410
|
+
}
|
|
4411
|
+
function mergeNumberChunks(chunks, totalLength) {
|
|
4412
|
+
if (chunks.length === 1) return chunks[0];
|
|
4413
|
+
const merged = new Float64Array(totalLength);
|
|
4414
|
+
let offset = 0;
|
|
4415
|
+
for (const chunk of chunks) {
|
|
4416
|
+
merged.set(chunk, offset);
|
|
4417
|
+
offset += chunk.length;
|
|
4418
|
+
}
|
|
4419
|
+
return merged;
|
|
4420
|
+
}
|
|
4421
|
+
function accumulateCoOccurrenceColumns(senderIds, timestamps, options) {
|
|
4422
|
+
const opts = resolveClusterOptions(options);
|
|
4423
|
+
const lookAhead = opts.lookAhead;
|
|
4424
|
+
const candidateIndexes = buildDistinctSpeakerLookAhead(senderIds.length, (index) => senderIds[index], lookAhead);
|
|
4425
|
+
const pairStatsBySource = /* @__PURE__ */ new Map();
|
|
4366
4426
|
const pairs = [];
|
|
4367
|
-
for (
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4427
|
+
for (let index = 0; index < senderIds.length - 1; index++) {
|
|
4428
|
+
for (let partnerIndex = 0; partnerIndex < lookAhead; partnerIndex++) {
|
|
4429
|
+
const candidateIndex = candidateIndexes[index * lookAhead + partnerIndex];
|
|
4430
|
+
if (candidateIndex < 0) break;
|
|
4431
|
+
const decayWeight = Math.exp(-(timestamps[candidateIndex] - timestamps[index]) / opts.decaySeconds);
|
|
4432
|
+
const positionWeight = 1 - partnerIndex * 0.2;
|
|
4433
|
+
applyCoOccurrencePair(
|
|
4434
|
+
senderIds[index],
|
|
4435
|
+
senderIds[candidateIndex],
|
|
4436
|
+
timestamps[candidateIndex],
|
|
4437
|
+
decayWeight * positionWeight,
|
|
4438
|
+
pairStatsBySource,
|
|
4439
|
+
pairs
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4376
4442
|
}
|
|
4377
4443
|
return pairs;
|
|
4378
4444
|
}
|
|
4445
|
+
function applyCoOccurrencePair(leftSenderId, rightSenderId, timestamp, weight, pairStatsBySource, pairs) {
|
|
4446
|
+
const sourceId = Math.min(leftSenderId, rightSenderId);
|
|
4447
|
+
const targetId = Math.max(leftSenderId, rightSenderId);
|
|
4448
|
+
let pairStatsByTarget = pairStatsBySource.get(sourceId);
|
|
4449
|
+
if (!pairStatsByTarget) {
|
|
4450
|
+
pairStatsByTarget = /* @__PURE__ */ new Map();
|
|
4451
|
+
pairStatsBySource.set(sourceId, pairStatsByTarget);
|
|
4452
|
+
}
|
|
4453
|
+
let pair = pairStatsByTarget.get(targetId);
|
|
4454
|
+
if (!pair) {
|
|
4455
|
+
pair = { sourceId, targetId, rawScore: 0, coOccurrenceCount: 0, lastOccurrenceTs: 0 };
|
|
4456
|
+
pairStatsByTarget.set(targetId, pair);
|
|
4457
|
+
pairs.push(pair);
|
|
4458
|
+
}
|
|
4459
|
+
pair.rawScore += weight;
|
|
4460
|
+
pair.coOccurrenceCount++;
|
|
4461
|
+
pair.lastOccurrenceTs = Math.max(pair.lastOccurrenceTs, timestamp);
|
|
4462
|
+
}
|
|
4463
|
+
function accumulateSelectedCoOccurrencePairs(messages, selectedPairs, options) {
|
|
4464
|
+
const opts = resolveClusterOptions(options);
|
|
4465
|
+
const lookAhead = opts.lookAhead;
|
|
4466
|
+
const maxAnchorsPerPair = Math.max(0, Math.floor(options?.maxAnchorsPerPair ?? 2));
|
|
4467
|
+
const maxGapSeconds = Math.max(0, options?.maxGapSeconds ?? DEFAULT_SELECTED_PROXIMITY_MAX_GAP_SECONDS);
|
|
4468
|
+
const selectedKeys = new Set(selectedPairs.map(([left, right]) => clusterPairKey(left, right)));
|
|
4469
|
+
const candidateIndexes = buildDistinctSpeakerLookAhead(
|
|
4470
|
+
messages.length,
|
|
4471
|
+
(index) => messages[index].senderId,
|
|
4472
|
+
lookAhead
|
|
4473
|
+
);
|
|
4474
|
+
const stats = /* @__PURE__ */ new Map();
|
|
4475
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
4476
|
+
const anchor = messages[i];
|
|
4477
|
+
for (let partnerIndex = 0; partnerIndex < lookAhead; partnerIndex++) {
|
|
4478
|
+
const candidateIndex = candidateIndexes[i * lookAhead + partnerIndex];
|
|
4479
|
+
if (candidateIndex < 0) break;
|
|
4480
|
+
const candidate = messages[candidateIndex];
|
|
4481
|
+
const deltaSeconds = candidate.ts - anchor.ts;
|
|
4482
|
+
if (deltaSeconds > maxGapSeconds) break;
|
|
4483
|
+
const key = clusterPairKey(anchor.senderId, candidate.senderId);
|
|
4484
|
+
if (!selectedKeys.has(key)) continue;
|
|
4485
|
+
const decayWeight = Math.exp(-deltaSeconds / opts.decaySeconds);
|
|
4486
|
+
const positionWeight = 1 - partnerIndex * 0.2;
|
|
4487
|
+
const weight = decayWeight * positionWeight;
|
|
4488
|
+
const sourceId = Math.min(anchor.senderId, candidate.senderId);
|
|
4489
|
+
const targetId = Math.max(anchor.senderId, candidate.senderId);
|
|
4490
|
+
const current = stats.get(key) ?? {
|
|
4491
|
+
sourceId,
|
|
4492
|
+
targetId,
|
|
4493
|
+
rawScore: 0,
|
|
4494
|
+
coOccurrenceCount: 0,
|
|
4495
|
+
lastOccurrenceTs: 0,
|
|
4496
|
+
anchors: []
|
|
4497
|
+
};
|
|
4498
|
+
current.rawScore += weight;
|
|
4499
|
+
current.coOccurrenceCount++;
|
|
4500
|
+
current.lastOccurrenceTs = Math.max(current.lastOccurrenceTs, candidate.ts);
|
|
4501
|
+
retainBestSelectedAnchor(
|
|
4502
|
+
current.anchors,
|
|
4503
|
+
{
|
|
4504
|
+
messageId: candidate.messageId,
|
|
4505
|
+
relatedMessageId: anchor.messageId,
|
|
4506
|
+
fromMemberId: anchor.senderId,
|
|
4507
|
+
toMemberId: candidate.senderId,
|
|
4508
|
+
timestamp: candidate.ts,
|
|
4509
|
+
weight
|
|
4510
|
+
},
|
|
4511
|
+
maxAnchorsPerPair
|
|
4512
|
+
);
|
|
4513
|
+
stats.set(key, current);
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
return [...stats.values()].map((item) => ({
|
|
4517
|
+
sourceId: item.sourceId,
|
|
4518
|
+
targetId: item.targetId,
|
|
4519
|
+
rawScore: item.rawScore,
|
|
4520
|
+
coOccurrenceCount: item.coOccurrenceCount,
|
|
4521
|
+
lastOccurrenceTs: item.lastOccurrenceTs,
|
|
4522
|
+
anchors: item.anchors.sort((left, right) => right.weight - left.weight || right.timestamp - left.timestamp).slice(0, maxAnchorsPerPair)
|
|
4523
|
+
}));
|
|
4524
|
+
}
|
|
4525
|
+
function buildDistinctSpeakerLookAhead(messageCount, senderIdAt, lookAhead) {
|
|
4526
|
+
const candidateIndexes = new Int32Array(messageCount * lookAhead);
|
|
4527
|
+
candidateIndexes.fill(-1);
|
|
4528
|
+
if (lookAhead === 0 || messageCount === 0) return candidateIndexes;
|
|
4529
|
+
const previousIndexes = new Int32Array(messageCount);
|
|
4530
|
+
const nextIndexes = new Int32Array(messageCount);
|
|
4531
|
+
previousIndexes.fill(-1);
|
|
4532
|
+
nextIndexes.fill(-1);
|
|
4533
|
+
const activeIndexBySender = /* @__PURE__ */ new Map();
|
|
4534
|
+
let headIndex = -1;
|
|
4535
|
+
for (let index = messageCount - 1; index >= 0; index--) {
|
|
4536
|
+
const senderId = senderIdAt(index);
|
|
4537
|
+
let candidateIndex = headIndex;
|
|
4538
|
+
let found = 0;
|
|
4539
|
+
while (candidateIndex >= 0 && found < lookAhead) {
|
|
4540
|
+
if (senderIdAt(candidateIndex) !== senderId) {
|
|
4541
|
+
candidateIndexes[index * lookAhead + found] = candidateIndex;
|
|
4542
|
+
found++;
|
|
4543
|
+
}
|
|
4544
|
+
candidateIndex = nextIndexes[candidateIndex];
|
|
4545
|
+
}
|
|
4546
|
+
const previousOccurrence = activeIndexBySender.get(senderId);
|
|
4547
|
+
if (previousOccurrence !== void 0) {
|
|
4548
|
+
const previousIndex = previousIndexes[previousOccurrence];
|
|
4549
|
+
const nextIndex = nextIndexes[previousOccurrence];
|
|
4550
|
+
if (previousIndex >= 0) nextIndexes[previousIndex] = nextIndex;
|
|
4551
|
+
else headIndex = nextIndex;
|
|
4552
|
+
if (nextIndex >= 0) previousIndexes[nextIndex] = previousIndex;
|
|
4553
|
+
}
|
|
4554
|
+
previousIndexes[index] = -1;
|
|
4555
|
+
nextIndexes[index] = headIndex;
|
|
4556
|
+
if (headIndex >= 0) previousIndexes[headIndex] = index;
|
|
4557
|
+
headIndex = index;
|
|
4558
|
+
activeIndexBySender.set(senderId, index);
|
|
4559
|
+
}
|
|
4560
|
+
return candidateIndexes;
|
|
4561
|
+
}
|
|
4562
|
+
function retainBestSelectedAnchor(anchors, candidate, limit) {
|
|
4563
|
+
if (limit === 0) return;
|
|
4564
|
+
if (anchors.length < limit) {
|
|
4565
|
+
anchors.push(candidate);
|
|
4566
|
+
return;
|
|
4567
|
+
}
|
|
4568
|
+
let worstIndex = 0;
|
|
4569
|
+
for (let index = 1; index < anchors.length; index++) {
|
|
4570
|
+
if (compareSelectedAnchorQuality(anchors[index], anchors[worstIndex]) < 0) worstIndex = index;
|
|
4571
|
+
}
|
|
4572
|
+
if (compareSelectedAnchorQuality(candidate, anchors[worstIndex]) > 0) anchors[worstIndex] = candidate;
|
|
4573
|
+
}
|
|
4574
|
+
function compareSelectedAnchorQuality(left, right) {
|
|
4575
|
+
return left.weight - right.weight || left.timestamp - right.timestamp;
|
|
4576
|
+
}
|
|
4379
4577
|
function getClusterGraph(db, filter, options) {
|
|
4380
|
-
const opts =
|
|
4578
|
+
const opts = resolveClusterOptions(options);
|
|
4381
4579
|
const emptyResult2 = {
|
|
4382
4580
|
nodes: [],
|
|
4383
4581
|
links: [],
|
|
@@ -4394,28 +4592,26 @@ function getClusterGraph(db, filter, options) {
|
|
|
4394
4592
|
const memberInfo = /* @__PURE__ */ new Map();
|
|
4395
4593
|
for (const m of members)
|
|
4396
4594
|
memberInfo.set(m.id, { name: m.name, platformId: m.platformId, messageCount: m.messageCount });
|
|
4397
|
-
const
|
|
4398
|
-
let
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
)
|
|
4408
|
-
|
|
4595
|
+
const memberMsgCount = /* @__PURE__ */ new Map();
|
|
4596
|
+
let totalMessages = 0;
|
|
4597
|
+
const trackedMessageBatches = (function* () {
|
|
4598
|
+
for (const batch of queryClusterMessageBatches(db, filter)) {
|
|
4599
|
+
for (const message of batch) {
|
|
4600
|
+
memberMsgCount.set(message.senderId, (memberMsgCount.get(message.senderId) || 0) + 1);
|
|
4601
|
+
totalMessages++;
|
|
4602
|
+
}
|
|
4603
|
+
yield batch;
|
|
4604
|
+
}
|
|
4605
|
+
})();
|
|
4606
|
+
const coOccurrencePairs = accumulateCoOccurrencePairBatches(trackedMessageBatches, opts);
|
|
4607
|
+
if (totalMessages < 2)
|
|
4409
4608
|
return {
|
|
4410
4609
|
...emptyResult2,
|
|
4411
|
-
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages
|
|
4610
|
+
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages }
|
|
4412
4611
|
};
|
|
4413
|
-
const memberMsgCount = /* @__PURE__ */ new Map();
|
|
4414
|
-
for (const msg of messages) memberMsgCount.set(msg.senderId, (memberMsgCount.get(msg.senderId) || 0) + 1);
|
|
4415
|
-
const totalMessages = messages.length;
|
|
4416
4612
|
const lookAheadFactor = opts.lookAhead * 0.8;
|
|
4417
4613
|
const rawEdges = [];
|
|
4418
|
-
for (const pair of
|
|
4614
|
+
for (const pair of coOccurrencePairs) {
|
|
4419
4615
|
const aId = pair.sourceId;
|
|
4420
4616
|
const bId = pair.targetId;
|
|
4421
4617
|
const aMsgCount = memberMsgCount.get(aId) || 0;
|
|
@@ -4448,7 +4644,7 @@ function getClusterGraph(db, filter, options) {
|
|
|
4448
4644
|
if (keptEdges.length === 0)
|
|
4449
4645
|
return {
|
|
4450
4646
|
...emptyResult2,
|
|
4451
|
-
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages
|
|
4647
|
+
stats: { ...emptyResult2.stats, totalMembers: members.length, totalMessages }
|
|
4452
4648
|
};
|
|
4453
4649
|
const involvedIds = /* @__PURE__ */ new Set();
|
|
4454
4650
|
for (const edge of keptEdges) {
|
|
@@ -4508,13 +4704,44 @@ function getClusterGraph(db, filter, options) {
|
|
|
4508
4704
|
communities: [],
|
|
4509
4705
|
stats: {
|
|
4510
4706
|
totalMembers: members.length,
|
|
4511
|
-
totalMessages
|
|
4707
|
+
totalMessages,
|
|
4512
4708
|
involvedMembers: involvedIds.size,
|
|
4513
4709
|
edgeCount: keptEdges.length,
|
|
4514
4710
|
communityCount: 0
|
|
4515
4711
|
}
|
|
4516
4712
|
};
|
|
4517
4713
|
}
|
|
4714
|
+
function* queryClusterMessageBatches(db, filter) {
|
|
4715
|
+
const { clause, params } = buildTimeFilter(filter, "msg");
|
|
4716
|
+
const systemMemberCondition = "COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'";
|
|
4717
|
+
const firstWhere = appendSocialConditions(clause, [systemMemberCondition]);
|
|
4718
|
+
const nextWhere = appendSocialConditions(clause, [systemMemberCondition, "(msg.ts, msg.id) > (?, ?)"]);
|
|
4719
|
+
const firstPage = db.prepare(
|
|
4720
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
4721
|
+
FROM message msg JOIN member m ON msg.sender_id = m.id
|
|
4722
|
+
${firstWhere}
|
|
4723
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
4724
|
+
LIMIT ?`
|
|
4725
|
+
);
|
|
4726
|
+
const nextPage = db.prepare(
|
|
4727
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
4728
|
+
FROM message msg JOIN member m ON msg.sender_id = m.id
|
|
4729
|
+
${nextWhere}
|
|
4730
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
4731
|
+
LIMIT ?`
|
|
4732
|
+
);
|
|
4733
|
+
let rows = firstPage.all(...params, CO_OCCURRENCE_MESSAGE_BATCH_SIZE);
|
|
4734
|
+
while (rows.length > 0) {
|
|
4735
|
+
yield rows;
|
|
4736
|
+
if (rows.length < CO_OCCURRENCE_MESSAGE_BATCH_SIZE) break;
|
|
4737
|
+
const last = rows[rows.length - 1];
|
|
4738
|
+
rows = nextPage.all(...params, last.ts, last.id, CO_OCCURRENCE_MESSAGE_BATCH_SIZE);
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
function appendSocialConditions(clause, conditions) {
|
|
4742
|
+
const existing = clause.trim().replace(/^WHERE\s+/i, "");
|
|
4743
|
+
return `WHERE ${[existing, ...conditions].filter(Boolean).join(" AND ")}`;
|
|
4744
|
+
}
|
|
4518
4745
|
|
|
4519
4746
|
// ../../packages/core/src/query/contact-queries.ts
|
|
4520
4747
|
var SYSTEM_MESSAGE_TYPES = [80, 81];
|
|
@@ -4526,6 +4753,10 @@ function isValidContactPlatformId(platformId) {
|
|
|
4526
4753
|
function resolveOwnerMember(db) {
|
|
4527
4754
|
const meta = db.prepare("SELECT owner_id FROM meta LIMIT 1").get();
|
|
4528
4755
|
if (!isValidContactPlatformId(meta?.owner_id)) return null;
|
|
4756
|
+
return resolveContactMember(db, meta.owner_id);
|
|
4757
|
+
}
|
|
4758
|
+
function resolveContactMember(db, platformId) {
|
|
4759
|
+
if (!isValidContactPlatformId(platformId)) return null;
|
|
4529
4760
|
const aliasesSelect = hasColumn(db, "member", "aliases") ? "aliases" : "NULL as aliases";
|
|
4530
4761
|
const row = db.prepare(
|
|
4531
4762
|
`SELECT
|
|
@@ -4537,7 +4768,7 @@ function resolveOwnerMember(db) {
|
|
|
4537
4768
|
FROM member m
|
|
4538
4769
|
WHERE platform_id = ? AND ${nonSystemContactMemberCondition(db, "m")}
|
|
4539
4770
|
LIMIT 1`
|
|
4540
|
-
).get(
|
|
4771
|
+
).get(platformId);
|
|
4541
4772
|
return row ? mapContactMemberRow(row) : null;
|
|
4542
4773
|
}
|
|
4543
4774
|
function getNonSystemMembersForContacts(db) {
|
|
@@ -4564,6 +4795,261 @@ function getLatestContactMessageTs(db) {
|
|
|
4564
4795
|
).get();
|
|
4565
4796
|
return row?.ts ?? null;
|
|
4566
4797
|
}
|
|
4798
|
+
function getParticipantSessionFacts(db, memberId, options = {}) {
|
|
4799
|
+
const timeFilter = createBoundedMessageTimeFilter("msg", options);
|
|
4800
|
+
const row = db.prepare(
|
|
4801
|
+
`SELECT
|
|
4802
|
+
COUNT(*) as sessionMessageCount,
|
|
4803
|
+
COALESCE(SUM(CASE WHEN msg.sender_id = ? THEN 1 ELSE 0 END), 0) as ownMessageCount,
|
|
4804
|
+
MIN(CASE WHEN msg.sender_id = ? THEN msg.ts END) as firstOwnMessageTs,
|
|
4805
|
+
MAX(CASE WHEN msg.sender_id = ? THEN msg.ts END) as lastOwnMessageTs,
|
|
4806
|
+
COUNT(DISTINCT CASE
|
|
4807
|
+
WHEN msg.sender_id = ? THEN strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')
|
|
4808
|
+
END) as activeDays
|
|
4809
|
+
FROM message msg
|
|
4810
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4811
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}${timeFilter.sql}`
|
|
4812
|
+
).get(memberId, memberId, memberId, memberId, ...timeFilter.params);
|
|
4813
|
+
const dataRange = db.prepare(
|
|
4814
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
4815
|
+
FROM message msg
|
|
4816
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4817
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}`
|
|
4818
|
+
).get();
|
|
4819
|
+
return {
|
|
4820
|
+
memberId,
|
|
4821
|
+
ownMessageCount: row?.ownMessageCount ?? 0,
|
|
4822
|
+
sessionMessageCount: row?.sessionMessageCount ?? 0,
|
|
4823
|
+
firstOwnMessageTs: row?.firstOwnMessageTs ?? null,
|
|
4824
|
+
lastOwnMessageTs: row?.lastOwnMessageTs ?? null,
|
|
4825
|
+
activeDays: row?.activeDays ?? 0,
|
|
4826
|
+
memberCount: getNonSystemMembersForContacts(db).length,
|
|
4827
|
+
sessionFirstMessageTs: dataRange?.firstMessageTs ?? null,
|
|
4828
|
+
sessionLastMessageTs: dataRange?.lastMessageTs ?? null
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
function getParticipantSetInteractionFacts(db, rawMemberIds, options = {}) {
|
|
4832
|
+
const memberIds = [...new Set(rawMemberIds)];
|
|
4833
|
+
if (memberIds.length < 2) throw new Error("At least two distinct member IDs are required");
|
|
4834
|
+
const placeholders = memberIds.map(() => "?").join(", ");
|
|
4835
|
+
const timeFilter = createBoundedMessageTimeFilter("msg", options);
|
|
4836
|
+
const activityRows = db.prepare(
|
|
4837
|
+
`SELECT
|
|
4838
|
+
msg.sender_id as memberId,
|
|
4839
|
+
COUNT(*) as messageCount,
|
|
4840
|
+
MIN(msg.ts) as firstMessageTs,
|
|
4841
|
+
MAX(msg.ts) as lastMessageTs,
|
|
4842
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays
|
|
4843
|
+
FROM message msg
|
|
4844
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4845
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}
|
|
4846
|
+
AND msg.sender_id IN (${placeholders})${timeFilter.sql}
|
|
4847
|
+
GROUP BY msg.sender_id`
|
|
4848
|
+
).all(...memberIds, ...timeFilter.params);
|
|
4849
|
+
const activityByMemberId = new Map(activityRows.map((row) => [row.memberId, row]));
|
|
4850
|
+
const participants = memberIds.map((memberId) => {
|
|
4851
|
+
const row = activityByMemberId.get(memberId);
|
|
4852
|
+
return {
|
|
4853
|
+
memberId,
|
|
4854
|
+
messageCount: row?.messageCount ?? 0,
|
|
4855
|
+
firstMessageTs: row?.firstMessageTs ?? null,
|
|
4856
|
+
lastMessageTs: row?.lastMessageTs ?? null,
|
|
4857
|
+
activeDays: row?.activeDays ?? 0
|
|
4858
|
+
};
|
|
4859
|
+
});
|
|
4860
|
+
const activeDayRows = db.prepare(
|
|
4861
|
+
`SELECT
|
|
4862
|
+
msg.sender_id as memberId,
|
|
4863
|
+
strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as activeDay
|
|
4864
|
+
FROM message msg
|
|
4865
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4866
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}
|
|
4867
|
+
AND msg.sender_id IN (${placeholders})${timeFilter.sql}
|
|
4868
|
+
GROUP BY msg.sender_id, activeDay`
|
|
4869
|
+
).all(...memberIds, ...timeFilter.params);
|
|
4870
|
+
const activeDaysByMemberId = new Map(memberIds.map((memberId) => [memberId, /* @__PURE__ */ new Set()]));
|
|
4871
|
+
for (const row of activeDayRows) activeDaysByMemberId.get(row.memberId)?.add(row.activeDay);
|
|
4872
|
+
const pairMap = /* @__PURE__ */ new Map();
|
|
4873
|
+
for (let sourceIndex = 0; sourceIndex < memberIds.length - 1; sourceIndex++) {
|
|
4874
|
+
for (let targetIndex = sourceIndex + 1; targetIndex < memberIds.length; targetIndex++) {
|
|
4875
|
+
const sourceMemberId = memberIds[sourceIndex];
|
|
4876
|
+
const targetMemberId = memberIds[targetIndex];
|
|
4877
|
+
pairMap.set(contactPairKey(sourceMemberId, targetMemberId), {
|
|
4878
|
+
sourceMemberId,
|
|
4879
|
+
targetMemberId,
|
|
4880
|
+
directReplyCount: 0,
|
|
4881
|
+
repliesFromSourceToTarget: 0,
|
|
4882
|
+
repliesFromTargetToSource: 0,
|
|
4883
|
+
lastDirectReplyTs: null,
|
|
4884
|
+
coOccurrenceCount: 0,
|
|
4885
|
+
coOccurrenceRawScore: 0,
|
|
4886
|
+
lastProximityTs: null,
|
|
4887
|
+
coActiveDays: countSetIntersection(
|
|
4888
|
+
activeDaysByMemberId.get(sourceMemberId) ?? /* @__PURE__ */ new Set(),
|
|
4889
|
+
activeDaysByMemberId.get(targetMemberId) ?? /* @__PURE__ */ new Set()
|
|
4890
|
+
),
|
|
4891
|
+
anchors: [],
|
|
4892
|
+
anchorsTruncated: false
|
|
4893
|
+
});
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
const replyStatsRows = db.prepare(
|
|
4897
|
+
`SELECT
|
|
4898
|
+
msg.sender_id as replySenderId,
|
|
4899
|
+
target.sender_id as targetSenderId,
|
|
4900
|
+
COUNT(*) as directReplyCount,
|
|
4901
|
+
MAX(msg.ts) as lastDirectReplyTs
|
|
4902
|
+
FROM message msg
|
|
4903
|
+
JOIN message target ON msg.reply_to_message_id = target.platform_message_id
|
|
4904
|
+
JOIN member sender ON msg.sender_id = sender.id
|
|
4905
|
+
JOIN member targetMember ON target.sender_id = targetMember.id
|
|
4906
|
+
WHERE msg.reply_to_message_id IS NOT NULL
|
|
4907
|
+
AND ${nonSystemMessageCondition(db, "msg", "sender")}
|
|
4908
|
+
AND ${nonSystemMessageCondition(db, "target", "targetMember")}
|
|
4909
|
+
AND msg.sender_id IN (${placeholders})
|
|
4910
|
+
AND +target.sender_id IN (${placeholders})
|
|
4911
|
+
AND msg.sender_id <> target.sender_id${timeFilter.sql}
|
|
4912
|
+
GROUP BY msg.sender_id, target.sender_id`
|
|
4913
|
+
).all(...memberIds, ...memberIds, ...timeFilter.params);
|
|
4914
|
+
const maxAnchorsPerPair = Math.max(0, Math.floor(options.maxAnchorsPerPair ?? 4));
|
|
4915
|
+
const replyDirectionCountsByPair = /* @__PURE__ */ new Map();
|
|
4916
|
+
for (const row of replyStatsRows) {
|
|
4917
|
+
const pair = pairMap.get(contactPairKey(row.replySenderId, row.targetSenderId));
|
|
4918
|
+
if (!pair) continue;
|
|
4919
|
+
pair.directReplyCount += row.directReplyCount;
|
|
4920
|
+
if (row.replySenderId === pair.sourceMemberId) pair.repliesFromSourceToTarget += row.directReplyCount;
|
|
4921
|
+
else pair.repliesFromTargetToSource += row.directReplyCount;
|
|
4922
|
+
if (row.lastDirectReplyTs !== null) {
|
|
4923
|
+
pair.lastDirectReplyTs = Math.max(pair.lastDirectReplyTs ?? 0, row.lastDirectReplyTs);
|
|
4924
|
+
}
|
|
4925
|
+
const pairKey = contactPairKey(row.replySenderId, row.targetSenderId);
|
|
4926
|
+
replyDirectionCountsByPair.set(pairKey, (replyDirectionCountsByPair.get(pairKey) ?? 0) + 1);
|
|
4927
|
+
}
|
|
4928
|
+
if (maxAnchorsPerPair > 0 && replyStatsRows.length > 0) {
|
|
4929
|
+
const replyAnchorStatement = db.prepare(
|
|
4930
|
+
`SELECT
|
|
4931
|
+
msg.id as messageId,
|
|
4932
|
+
target.id as relatedMessageId,
|
|
4933
|
+
msg.sender_id as replySenderId,
|
|
4934
|
+
target.sender_id as targetSenderId,
|
|
4935
|
+
msg.ts as replyTs
|
|
4936
|
+
FROM message msg
|
|
4937
|
+
JOIN message target ON msg.reply_to_message_id = target.platform_message_id
|
|
4938
|
+
JOIN member sender ON msg.sender_id = sender.id
|
|
4939
|
+
JOIN member targetMember ON target.sender_id = targetMember.id
|
|
4940
|
+
WHERE msg.reply_to_message_id IS NOT NULL
|
|
4941
|
+
AND ${nonSystemMessageCondition(db, "msg", "sender")}
|
|
4942
|
+
AND ${nonSystemMessageCondition(db, "target", "targetMember")}
|
|
4943
|
+
AND msg.sender_id = ?
|
|
4944
|
+
AND +target.sender_id = ?${timeFilter.sql}
|
|
4945
|
+
ORDER BY msg.ts DESC, msg.id DESC
|
|
4946
|
+
LIMIT 1`
|
|
4947
|
+
);
|
|
4948
|
+
const anchorsByPair = /* @__PURE__ */ new Map();
|
|
4949
|
+
for (const row of replyStatsRows) {
|
|
4950
|
+
const anchor = replyAnchorStatement.get(row.replySenderId, row.targetSenderId, ...timeFilter.params);
|
|
4951
|
+
if (!anchor) continue;
|
|
4952
|
+
const pairKey = contactPairKey(anchor.replySenderId, anchor.targetSenderId);
|
|
4953
|
+
const pairAnchors = anchorsByPair.get(pairKey) ?? [];
|
|
4954
|
+
pairAnchors.push({
|
|
4955
|
+
messageId: anchor.messageId,
|
|
4956
|
+
relatedMessageId: anchor.relatedMessageId,
|
|
4957
|
+
timestamp: anchor.replyTs,
|
|
4958
|
+
signal: "direct_reply",
|
|
4959
|
+
fromMemberId: anchor.replySenderId,
|
|
4960
|
+
toMemberId: anchor.targetSenderId
|
|
4961
|
+
});
|
|
4962
|
+
anchorsByPair.set(pairKey, pairAnchors);
|
|
4963
|
+
}
|
|
4964
|
+
for (const [pairKey, anchors] of anchorsByPair) {
|
|
4965
|
+
const pair = pairMap.get(pairKey);
|
|
4966
|
+
if (!pair) continue;
|
|
4967
|
+
anchors.sort((left, right) => right.timestamp - left.timestamp || right.messageId - left.messageId);
|
|
4968
|
+
pair.anchors.push(...anchors.slice(0, maxAnchorsPerPair));
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
for (const [pairKey, directionCount] of replyDirectionCountsByPair) {
|
|
4972
|
+
const pair = pairMap.get(pairKey);
|
|
4973
|
+
if (pair && directionCount > maxAnchorsPerPair) pair.anchorsTruncated = true;
|
|
4974
|
+
}
|
|
4975
|
+
const maxProximityMessages = Math.max(0, Math.floor(options.maxProximityMessages ?? 2e5));
|
|
4976
|
+
let proximityStatus = "complete";
|
|
4977
|
+
if (maxProximityMessages === 0) {
|
|
4978
|
+
proximityStatus = "skipped_budget";
|
|
4979
|
+
for (const pair of pairMap.values()) {
|
|
4980
|
+
pair.coOccurrenceCount = null;
|
|
4981
|
+
pair.coOccurrenceRawScore = null;
|
|
4982
|
+
}
|
|
4983
|
+
} else {
|
|
4984
|
+
const proximityRows = db.prepare(
|
|
4985
|
+
`SELECT msg.id as messageId, msg.sender_id as senderId, msg.ts as ts
|
|
4986
|
+
FROM message msg
|
|
4987
|
+
JOIN member m ON msg.sender_id = m.id
|
|
4988
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}${timeFilter.sql}
|
|
4989
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
4990
|
+
LIMIT ?`
|
|
4991
|
+
).all(...timeFilter.params, maxProximityMessages + 1);
|
|
4992
|
+
if (proximityRows.length > maxProximityMessages) {
|
|
4993
|
+
proximityRows.length = maxProximityMessages;
|
|
4994
|
+
proximityStatus = "partial";
|
|
4995
|
+
}
|
|
4996
|
+
const selectedPairFacts = accumulateSelectedCoOccurrencePairs(
|
|
4997
|
+
proximityRows,
|
|
4998
|
+
[...pairMap.values()].map((pair) => [pair.sourceMemberId, pair.targetMemberId]),
|
|
4999
|
+
// 多保留一个候选,用于区分“命中上限”和“确实省略了证据”。
|
|
5000
|
+
{ maxAnchorsPerPair: maxAnchorsPerPair + 1 }
|
|
5001
|
+
);
|
|
5002
|
+
for (const selected of selectedPairFacts) {
|
|
5003
|
+
const pair = pairMap.get(contactPairKey(selected.sourceId, selected.targetId));
|
|
5004
|
+
if (!pair) continue;
|
|
5005
|
+
pair.coOccurrenceCount = selected.coOccurrenceCount;
|
|
5006
|
+
pair.coOccurrenceRawScore = selected.rawScore;
|
|
5007
|
+
pair.lastProximityTs = selected.lastOccurrenceTs;
|
|
5008
|
+
for (const anchor of selected.anchors) {
|
|
5009
|
+
if (pair.anchors.length >= maxAnchorsPerPair) {
|
|
5010
|
+
pair.anchorsTruncated = true;
|
|
5011
|
+
break;
|
|
5012
|
+
}
|
|
5013
|
+
pair.anchors.push({
|
|
5014
|
+
messageId: anchor.messageId,
|
|
5015
|
+
relatedMessageId: anchor.relatedMessageId,
|
|
5016
|
+
timestamp: anchor.timestamp,
|
|
5017
|
+
signal: "proximity",
|
|
5018
|
+
fromMemberId: anchor.fromMemberId,
|
|
5019
|
+
toMemberId: anchor.toMemberId
|
|
5020
|
+
});
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
if (proximityStatus === "partial") {
|
|
5024
|
+
for (const pair of pairMap.values()) {
|
|
5025
|
+
if (pair.coOccurrenceCount === 0) {
|
|
5026
|
+
pair.coOccurrenceCount = null;
|
|
5027
|
+
pair.coOccurrenceRawScore = null;
|
|
5028
|
+
pair.lastProximityTs = null;
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
const activeDaySets = memberIds.map((memberId) => activeDaysByMemberId.get(memberId) ?? /* @__PURE__ */ new Set());
|
|
5034
|
+
const firstMessageTimestamps = participants.map((participant) => participant.firstMessageTs);
|
|
5035
|
+
const lastMessageTimestamps = participants.map((participant) => participant.lastMessageTs);
|
|
5036
|
+
const dataRange = db.prepare(
|
|
5037
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
5038
|
+
FROM message msg
|
|
5039
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5040
|
+
WHERE ${nonSystemMessageCondition(db, "msg", "m")}`
|
|
5041
|
+
).get();
|
|
5042
|
+
return {
|
|
5043
|
+
participants,
|
|
5044
|
+
overlapRange: calculateOverlapRange(firstMessageTimestamps, lastMessageTimestamps),
|
|
5045
|
+
allParticipantsCoActiveDays: countMultipleSetIntersection(activeDaySets),
|
|
5046
|
+
pairs: [...pairMap.values()],
|
|
5047
|
+
proximityStatus,
|
|
5048
|
+
memberCount: getNonSystemMembersForContacts(db).length,
|
|
5049
|
+
sessionFirstMessageTs: dataRange?.firstMessageTs ?? null,
|
|
5050
|
+
sessionLastMessageTs: dataRange?.lastMessageTs ?? null
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
4567
5053
|
function getPrivateContactFacts(db, ownerMemberId, options = {}) {
|
|
4568
5054
|
const candidates = getNonSystemMembersForContacts(db).filter((member) => member.id !== ownerMemberId);
|
|
4569
5055
|
if (candidates.length === 0) return { type: "missing" };
|
|
@@ -4799,9 +5285,52 @@ function getGroupRelationshipGraphFacts(db, ownerMemberId, options = {}) {
|
|
|
4799
5285
|
function createMessageTimeFilter(alias, startTs) {
|
|
4800
5286
|
return typeof startTs === "number" ? { sql: ` AND ${alias}.ts >= ?`, params: [startTs] } : { sql: "", params: [] };
|
|
4801
5287
|
}
|
|
5288
|
+
function createBoundedMessageTimeFilter(alias, options) {
|
|
5289
|
+
const clauses = [];
|
|
5290
|
+
const params = [];
|
|
5291
|
+
if (typeof options.startTs === "number") {
|
|
5292
|
+
clauses.push(`${alias}.ts >= ?`);
|
|
5293
|
+
params.push(options.startTs);
|
|
5294
|
+
}
|
|
5295
|
+
if (typeof options.endTs === "number") {
|
|
5296
|
+
clauses.push(`${alias}.ts <= ?`);
|
|
5297
|
+
params.push(options.endTs);
|
|
5298
|
+
}
|
|
5299
|
+
return {
|
|
5300
|
+
sql: clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "",
|
|
5301
|
+
params
|
|
5302
|
+
};
|
|
5303
|
+
}
|
|
4802
5304
|
function createReplyTimeFilter(startTs) {
|
|
4803
5305
|
return typeof startTs === "number" ? { sql: " AND msg.ts >= ? AND target.ts >= ?", params: [startTs, startTs] } : { sql: "", params: [] };
|
|
4804
5306
|
}
|
|
5307
|
+
function contactPairKey(left, right) {
|
|
5308
|
+
return left < right ? `${left}:${right}` : `${right}:${left}`;
|
|
5309
|
+
}
|
|
5310
|
+
function countSetIntersection(left, right) {
|
|
5311
|
+
const smaller = left.size <= right.size ? left : right;
|
|
5312
|
+
const larger = smaller === left ? right : left;
|
|
5313
|
+
let count = 0;
|
|
5314
|
+
for (const value of smaller) {
|
|
5315
|
+
if (larger.has(value)) count++;
|
|
5316
|
+
}
|
|
5317
|
+
return count;
|
|
5318
|
+
}
|
|
5319
|
+
function countMultipleSetIntersection(sets) {
|
|
5320
|
+
if (sets.length === 0) return 0;
|
|
5321
|
+
return sets.slice(1).reduce((intersection, current) => {
|
|
5322
|
+
for (const value of intersection) {
|
|
5323
|
+
if (!current.has(value)) intersection.delete(value);
|
|
5324
|
+
}
|
|
5325
|
+
return intersection;
|
|
5326
|
+
}, new Set(sets[0])).size;
|
|
5327
|
+
}
|
|
5328
|
+
function calculateOverlapRange(firstTimestamps, lastTimestamps) {
|
|
5329
|
+
if (firstTimestamps.some((value) => value === null) || lastTimestamps.some((value) => value === null)) return null;
|
|
5330
|
+
const startTs = Math.max(...firstTimestamps);
|
|
5331
|
+
const endTs = Math.min(...lastTimestamps);
|
|
5332
|
+
return startTs <= endTs ? { startTs, endTs } : null;
|
|
5333
|
+
}
|
|
4805
5334
|
function mapContactMemberRow(row) {
|
|
4806
5335
|
return {
|
|
4807
5336
|
id: row.id,
|
|
@@ -4938,6 +5467,83 @@ function buildContactKey(platform, platformId, sessionId) {
|
|
|
4938
5467
|
return sessionId?.trim() ? `${normalizedPlatform}:${sessionId.trim()}:${normalizedPlatformId}` : `${normalizedPlatform}:${normalizedPlatformId}`;
|
|
4939
5468
|
}
|
|
4940
5469
|
|
|
5470
|
+
// ../../packages/core/src/query/cross-chat-statistics.ts
|
|
5471
|
+
function getCrossChatSessionActivityFacts(db, options = {}) {
|
|
5472
|
+
const timeFilter = createTimeFilter("msg", options);
|
|
5473
|
+
const messageCondition = nonSystemMessageCondition(db, "msg", "m");
|
|
5474
|
+
const summary = db.prepare(
|
|
5475
|
+
`SELECT
|
|
5476
|
+
COUNT(*) as totalMessages,
|
|
5477
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays,
|
|
5478
|
+
COUNT(DISTINCT msg.sender_id) as activeMembers,
|
|
5479
|
+
MIN(msg.ts) as firstMessageTs,
|
|
5480
|
+
MAX(msg.ts) as lastMessageTs
|
|
5481
|
+
FROM message msg
|
|
5482
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5483
|
+
WHERE ${messageCondition}${timeFilter.sql}`
|
|
5484
|
+
).get(...timeFilter.params);
|
|
5485
|
+
const memberRows = db.prepare(
|
|
5486
|
+
`SELECT
|
|
5487
|
+
msg.sender_id as memberId,
|
|
5488
|
+
m.platform_id as platformId,
|
|
5489
|
+
COALESCE(m.group_nickname, m.account_name, m.platform_id) as memberName,
|
|
5490
|
+
COUNT(*) as messageCount,
|
|
5491
|
+
COUNT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDays,
|
|
5492
|
+
GROUP_CONCAT(DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime')) as activeDayKeys,
|
|
5493
|
+
MIN(msg.ts) as firstMessageTs,
|
|
5494
|
+
MAX(msg.ts) as lastMessageTs
|
|
5495
|
+
FROM message msg
|
|
5496
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5497
|
+
WHERE ${messageCondition}${timeFilter.sql}
|
|
5498
|
+
GROUP BY msg.sender_id, m.platform_id, m.group_nickname, m.account_name
|
|
5499
|
+
ORDER BY msg.sender_id`
|
|
5500
|
+
).all(...timeFilter.params);
|
|
5501
|
+
const members = memberRows.map((row) => ({
|
|
5502
|
+
...row,
|
|
5503
|
+
activeDayKeys: row.activeDayKeys ? row.activeDayKeys.split(",").sort() : []
|
|
5504
|
+
}));
|
|
5505
|
+
const dataRange = db.prepare(
|
|
5506
|
+
`SELECT MIN(msg.ts) as firstMessageTs, MAX(msg.ts) as lastMessageTs
|
|
5507
|
+
FROM message msg
|
|
5508
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5509
|
+
WHERE ${messageCondition}`
|
|
5510
|
+
).get();
|
|
5511
|
+
const activeDayKeys = db.prepare(
|
|
5512
|
+
`SELECT DISTINCT strftime('%Y-%m-%d', msg.ts, 'unixepoch', 'localtime') as activeDay
|
|
5513
|
+
FROM message msg
|
|
5514
|
+
JOIN member m ON msg.sender_id = m.id
|
|
5515
|
+
WHERE ${messageCondition}${timeFilter.sql}
|
|
5516
|
+
ORDER BY activeDay`
|
|
5517
|
+
).all(...timeFilter.params).map((row) => row.activeDay);
|
|
5518
|
+
return {
|
|
5519
|
+
totalMessages: summary?.totalMessages ?? 0,
|
|
5520
|
+
activeDays: summary?.activeDays ?? 0,
|
|
5521
|
+
activeMembers: summary?.activeMembers ?? 0,
|
|
5522
|
+
firstMessageTs: summary?.firstMessageTs ?? null,
|
|
5523
|
+
lastMessageTs: summary?.lastMessageTs ?? null,
|
|
5524
|
+
dataEarliestMessageTs: dataRange?.firstMessageTs ?? null,
|
|
5525
|
+
dataLatestMessageTs: dataRange?.lastMessageTs ?? null,
|
|
5526
|
+
activeDayKeys,
|
|
5527
|
+
members
|
|
5528
|
+
};
|
|
5529
|
+
}
|
|
5530
|
+
function createTimeFilter(alias, options) {
|
|
5531
|
+
const clauses = [];
|
|
5532
|
+
const params = [];
|
|
5533
|
+
if (typeof options.startTs === "number") {
|
|
5534
|
+
clauses.push(`${alias}.ts >= ?`);
|
|
5535
|
+
params.push(options.startTs);
|
|
5536
|
+
}
|
|
5537
|
+
if (typeof options.endTs === "number") {
|
|
5538
|
+
clauses.push(`${alias}.ts <= ?`);
|
|
5539
|
+
params.push(options.endTs);
|
|
5540
|
+
}
|
|
5541
|
+
return {
|
|
5542
|
+
sql: clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "",
|
|
5543
|
+
params
|
|
5544
|
+
};
|
|
5545
|
+
}
|
|
5546
|
+
|
|
4941
5547
|
// ../../packages/core/src/query/global-insight.ts
|
|
4942
5548
|
var SYSTEM_MESSAGE_TYPES2 = [80 /* SYSTEM */, 81 /* RECALL */];
|
|
4943
5549
|
function getAnnualSummarySessionFacts(db, sessionId, range) {
|
|
@@ -5726,6 +6332,7 @@ function searchMessagesByKeywords(db, keywords, options) {
|
|
|
5726
6332
|
startTs: options?.startTs,
|
|
5727
6333
|
endTs: options?.endTs,
|
|
5728
6334
|
senderId: options?.senderId,
|
|
6335
|
+
senderIds: options?.senderIds,
|
|
5729
6336
|
keywords: cleaned.length > 0 ? cleaned : void 0,
|
|
5730
6337
|
matchMode: options?.matchMode,
|
|
5731
6338
|
excludeKeywords: options?.excludeKeywords,
|
|
@@ -5748,6 +6355,33 @@ function searchMessagesByKeywords(db, keywords, options) {
|
|
|
5748
6355
|
});
|
|
5749
6356
|
return { messages, hasMore, total: countRow.total };
|
|
5750
6357
|
}
|
|
6358
|
+
function getRecentMessages(db, options) {
|
|
6359
|
+
const limit = options?.limit ?? 50;
|
|
6360
|
+
const rows = db.prepare(
|
|
6361
|
+
`SELECT
|
|
6362
|
+
msg.id as id,
|
|
6363
|
+
m.id as senderId,
|
|
6364
|
+
COALESCE(m.group_nickname, m.account_name, m.platform_id) as senderName,
|
|
6365
|
+
m.platform_id as senderPlatformId,
|
|
6366
|
+
msg.content as content,
|
|
6367
|
+
msg.ts as timestamp,
|
|
6368
|
+
msg.type as type
|
|
6369
|
+
FROM message msg
|
|
6370
|
+
JOIN member m ON msg.sender_id = m.id
|
|
6371
|
+
WHERE COALESCE(m.account_name, '') != '\u7CFB\u7EDF\u6D88\u606F'
|
|
6372
|
+
ORDER BY msg.ts DESC
|
|
6373
|
+
LIMIT ?`
|
|
6374
|
+
).all(limit);
|
|
6375
|
+
return rows.map((row) => ({
|
|
6376
|
+
id: Number(row.id),
|
|
6377
|
+
senderId: Number(row.senderId),
|
|
6378
|
+
senderName: String(row.senderName || ""),
|
|
6379
|
+
senderPlatformId: String(row.senderPlatformId || ""),
|
|
6380
|
+
content: row.content != null ? String(row.content) : "",
|
|
6381
|
+
timestamp: Number(row.timestamp),
|
|
6382
|
+
type: Number(row.type)
|
|
6383
|
+
}));
|
|
6384
|
+
}
|
|
5751
6385
|
function getMembers(db) {
|
|
5752
6386
|
return db.prepare(
|
|
5753
6387
|
`SELECT
|
|
@@ -6243,6 +6877,7 @@ var REPLY_WEIGHT = 3;
|
|
|
6243
6877
|
var MENTION_WEIGHT = 2;
|
|
6244
6878
|
var CO_OCCURRENCE_COUNT_WEIGHT = 0.05;
|
|
6245
6879
|
var MIN_EDGE_WEIGHT = 1e-6;
|
|
6880
|
+
var MESSAGE_BATCH_SIZE = 2e4;
|
|
6246
6881
|
var DEFAULT_LIMITS = {
|
|
6247
6882
|
nodeLimit: 400,
|
|
6248
6883
|
edgeLimit: 1200,
|
|
@@ -6264,14 +6899,16 @@ function getGroupRelationshipGalaxy(db, filter, options = {}) {
|
|
|
6264
6899
|
const limits = normalizeLimits(options);
|
|
6265
6900
|
const allMembers = getNonSystemMembersForContacts(db);
|
|
6266
6901
|
const memberById = new Map(allMembers.map((member) => [member.id, member]));
|
|
6267
|
-
const
|
|
6268
|
-
const
|
|
6902
|
+
const messageStats = /* @__PURE__ */ new Map();
|
|
6903
|
+
const messageBatches = filterRelationshipMessageBatches(db, filter, memberById, messageStats);
|
|
6904
|
+
const coOccurrencePairs = accumulateCoOccurrencePairBatches(messageBatches);
|
|
6905
|
+
const activeMembers = buildActiveMembers(allMembers, messageStats);
|
|
6269
6906
|
const activeMemberById = new Map(activeMembers.map((member) => [member.id, member]));
|
|
6270
6907
|
if (activeMembers.length < 2) {
|
|
6271
6908
|
return emptyResult(allMembers.length, activeMembers.length);
|
|
6272
6909
|
}
|
|
6273
6910
|
const edgeByPair = /* @__PURE__ */ new Map();
|
|
6274
|
-
for (const pair of
|
|
6911
|
+
for (const pair of coOccurrencePairs) {
|
|
6275
6912
|
if (!activeMemberById.has(pair.sourceId) || !activeMemberById.has(pair.targetId)) continue;
|
|
6276
6913
|
const edge = ensureEdge(edgeByPair, activeMemberById, pair.sourceId, pair.targetId);
|
|
6277
6914
|
edge.coOccurrenceCount += pair.coOccurrenceCount;
|
|
@@ -6389,19 +7026,44 @@ function getGroupRelationshipGalaxy(db, filter, options = {}) {
|
|
|
6389
7026
|
algorithmVersion: GROUP_RELATIONSHIP_GALAXY_ALGORITHM_VERSION
|
|
6390
7027
|
};
|
|
6391
7028
|
}
|
|
6392
|
-
function
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
|
|
7029
|
+
function* filterRelationshipMessageBatches(db, filter, memberById, messageStats) {
|
|
7030
|
+
for (const batch of queryRelationshipMessageBatches(db, filter)) {
|
|
7031
|
+
const filtered = [];
|
|
7032
|
+
for (const message of batch) {
|
|
7033
|
+
if (!memberById.has(message.senderId)) continue;
|
|
7034
|
+
const stats = messageStats.get(message.senderId) ?? { count: 0, lastTs: 0 };
|
|
7035
|
+
stats.count++;
|
|
7036
|
+
stats.lastTs = Math.max(stats.lastTs, message.ts);
|
|
7037
|
+
messageStats.set(message.senderId, stats);
|
|
7038
|
+
filtered.push(message);
|
|
7039
|
+
}
|
|
7040
|
+
yield filtered;
|
|
7041
|
+
}
|
|
6396
7042
|
}
|
|
6397
|
-
function
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
7043
|
+
function* queryRelationshipMessageBatches(db, filter) {
|
|
7044
|
+
const { clause, params } = buildTimeFilter(filter, "msg");
|
|
7045
|
+
const conditions = ["msg.type NOT IN (80, 81)"];
|
|
7046
|
+
const firstPage = db.prepare(
|
|
7047
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
7048
|
+
FROM message msg ${appendConditions(clause, conditions)}
|
|
7049
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
7050
|
+
LIMIT ?`
|
|
7051
|
+
);
|
|
7052
|
+
const nextPage = db.prepare(
|
|
7053
|
+
`SELECT msg.id, msg.sender_id as senderId, msg.ts
|
|
7054
|
+
FROM message msg ${appendConditions(clause, [...conditions, "(msg.ts, msg.id) > (?, ?)"])}
|
|
7055
|
+
ORDER BY msg.ts ASC, msg.id ASC
|
|
7056
|
+
LIMIT ?`
|
|
7057
|
+
);
|
|
7058
|
+
let rows = firstPage.all(...params, MESSAGE_BATCH_SIZE);
|
|
7059
|
+
while (rows.length > 0) {
|
|
7060
|
+
yield rows;
|
|
7061
|
+
if (rows.length < MESSAGE_BATCH_SIZE) break;
|
|
7062
|
+
const last = rows[rows.length - 1];
|
|
7063
|
+
rows = nextPage.all(...params, last.ts, last.id, MESSAGE_BATCH_SIZE);
|
|
6404
7064
|
}
|
|
7065
|
+
}
|
|
7066
|
+
function buildActiveMembers(allMembers, messageStats) {
|
|
6405
7067
|
const active = allMembers.filter((member) => messageStats.has(member.id)).map((member) => ({
|
|
6406
7068
|
...member,
|
|
6407
7069
|
key: memberKey(member.id),
|
|
@@ -6900,8 +7562,8 @@ function getJourneyStats(db, filter) {
|
|
|
6900
7562
|
const indexAvailable = hasSessionIndex(db) && hasTable(db, "message_context");
|
|
6901
7563
|
const segmentRows = indexAvailable ? querySegmentRows(db, effectiveFilter) : [];
|
|
6902
7564
|
const segmentCountByMonth = /* @__PURE__ */ new Map();
|
|
6903
|
-
for (const
|
|
6904
|
-
segmentCountByMonth.set(
|
|
7565
|
+
for (const segment2 of segmentRows) {
|
|
7566
|
+
segmentCountByMonth.set(segment2.startMonth, (segmentCountByMonth.get(segment2.startMonth) ?? 0) + 1);
|
|
6905
7567
|
}
|
|
6906
7568
|
const firstRow = monthlyRows[0];
|
|
6907
7569
|
const lastRow = monthlyRows[monthlyRows.length - 1];
|
|
@@ -7923,6 +8585,7 @@ function computeRepeat(messages) {
|
|
|
7923
8585
|
content,
|
|
7924
8586
|
count: stats.count,
|
|
7925
8587
|
maxChainLength: stats.maxChainLength,
|
|
8588
|
+
originatorId: stats.originatorId,
|
|
7926
8589
|
originatorName: originatorInfo?.name || "\u672A\u77E5",
|
|
7927
8590
|
lastTs: stats.lastTs,
|
|
7928
8591
|
firstMessageId: stats.firstMessageId
|
|
@@ -9000,8 +9663,8 @@ var AssistantManager = class {
|
|
|
9000
9663
|
}
|
|
9001
9664
|
// ==================== Init ====================
|
|
9002
9665
|
init() {
|
|
9003
|
-
const { fs:
|
|
9004
|
-
|
|
9666
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9667
|
+
fs20.ensureDir(assistantsDir);
|
|
9005
9668
|
const { generalCreated, generalUpdated } = this.syncGeneralAssistants();
|
|
9006
9669
|
this.loadAll();
|
|
9007
9670
|
this.initialized = true;
|
|
@@ -9013,25 +9676,25 @@ var AssistantManager = class {
|
|
|
9013
9676
|
return { total: this.cache.size, generalCreated, generalUpdated };
|
|
9014
9677
|
}
|
|
9015
9678
|
syncGeneralAssistants() {
|
|
9016
|
-
const { fs:
|
|
9679
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9017
9680
|
let generalCreated = false;
|
|
9018
9681
|
let generalUpdated = false;
|
|
9019
9682
|
for (const id of this.generalIds) {
|
|
9020
9683
|
const config = this.getBuiltinConfig(id);
|
|
9021
9684
|
if (!config) continue;
|
|
9022
|
-
const filePath =
|
|
9023
|
-
if (!
|
|
9024
|
-
|
|
9685
|
+
const filePath = fs20.joinPath(assistantsDir, `${id}.md`);
|
|
9686
|
+
if (!fs20.fileExists(filePath)) {
|
|
9687
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9025
9688
|
generalCreated = true;
|
|
9026
9689
|
continue;
|
|
9027
9690
|
}
|
|
9028
9691
|
try {
|
|
9029
|
-
const existing = parseAssistantFile(
|
|
9692
|
+
const existing = parseAssistantFile(fs20.readFile(filePath), filePath);
|
|
9030
9693
|
if (!existing) continue;
|
|
9031
9694
|
if (existing.builtinId !== id) {
|
|
9032
9695
|
try {
|
|
9033
9696
|
const backupId = this.backupAssistantOccupyingReservedId(existing);
|
|
9034
|
-
|
|
9697
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9035
9698
|
generalCreated = true;
|
|
9036
9699
|
this.deps.logger?.info("AssistantManager", "Backed up assistant occupying reserved default ID", {
|
|
9037
9700
|
reservedId: id,
|
|
@@ -9047,7 +9710,7 @@ var AssistantManager = class {
|
|
|
9047
9710
|
continue;
|
|
9048
9711
|
}
|
|
9049
9712
|
if (!this.shouldUpgradeBuiltin(existing, config)) continue;
|
|
9050
|
-
|
|
9713
|
+
fs20.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config, existing.id)));
|
|
9051
9714
|
generalUpdated = true;
|
|
9052
9715
|
this.deps.logger?.info("AssistantManager", "Updated unmodified default assistant template", {
|
|
9053
9716
|
id,
|
|
@@ -9063,14 +9726,14 @@ var AssistantManager = class {
|
|
|
9063
9726
|
return { generalCreated, generalUpdated };
|
|
9064
9727
|
}
|
|
9065
9728
|
backupAssistantOccupyingReservedId(config) {
|
|
9066
|
-
const { fs:
|
|
9729
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9067
9730
|
const baseId = this.deps.generateId?.() || `custom_${Date.now().toString(36)}`;
|
|
9068
9731
|
let backupId = baseId;
|
|
9069
9732
|
let suffix = 2;
|
|
9070
|
-
while (this.generalIds.includes(backupId) ||
|
|
9733
|
+
while (this.generalIds.includes(backupId) || fs20.fileExists(fs20.joinPath(assistantsDir, `${backupId}.md`))) {
|
|
9071
9734
|
backupId = `${baseId}_${suffix++}`;
|
|
9072
9735
|
}
|
|
9073
|
-
|
|
9736
|
+
fs20.writeFile(fs20.joinPath(assistantsDir, `${backupId}.md`), serializeAssistant({ ...config, id: backupId }));
|
|
9074
9737
|
return backupId;
|
|
9075
9738
|
}
|
|
9076
9739
|
getConfigDigest(config) {
|
|
@@ -9107,13 +9770,13 @@ var AssistantManager = class {
|
|
|
9107
9770
|
return { isUnmodified, isOutdated };
|
|
9108
9771
|
}
|
|
9109
9772
|
loadAll() {
|
|
9110
|
-
const { fs:
|
|
9773
|
+
const { fs: fs20, assistantsDir } = this.deps;
|
|
9111
9774
|
this.cache.clear();
|
|
9112
|
-
const files =
|
|
9775
|
+
const files = fs20.listFiles(assistantsDir, ".md");
|
|
9113
9776
|
for (const file of files) {
|
|
9114
9777
|
try {
|
|
9115
|
-
const filePath =
|
|
9116
|
-
const content =
|
|
9778
|
+
const filePath = fs20.joinPath(assistantsDir, file);
|
|
9779
|
+
const content = fs20.readFile(filePath);
|
|
9117
9780
|
const config = parseAssistantFile(content, filePath);
|
|
9118
9781
|
if (config) {
|
|
9119
9782
|
this.cache.set(config.id, config);
|
|
@@ -9367,6 +10030,7 @@ var AIChatManager = class {
|
|
|
9367
10030
|
CREATE TABLE IF NOT EXISTS ai_chat (
|
|
9368
10031
|
id TEXT PRIMARY KEY,
|
|
9369
10032
|
session_id TEXT NOT NULL,
|
|
10033
|
+
kind TEXT NOT NULL DEFAULT 'session',
|
|
9370
10034
|
title TEXT,
|
|
9371
10035
|
assistant_id TEXT DEFAULT '${DEFAULT_GENERAL_ID}',
|
|
9372
10036
|
active_message_id TEXT,
|
|
@@ -9388,6 +10052,7 @@ var AIChatManager = class {
|
|
|
9388
10052
|
branch_index INTEGER DEFAULT 0,
|
|
9389
10053
|
debug_context TEXT,
|
|
9390
10054
|
token_usage TEXT,
|
|
10055
|
+
entity_refs TEXT,
|
|
9391
10056
|
FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE
|
|
9392
10057
|
);
|
|
9393
10058
|
|
|
@@ -9423,9 +10088,19 @@ var AIChatManager = class {
|
|
|
9423
10088
|
if (!messageColumns.includes("branch_index")) {
|
|
9424
10089
|
db.exec(`ALTER TABLE ${tableName} ADD COLUMN branch_index INTEGER DEFAULT 0`);
|
|
9425
10090
|
}
|
|
10091
|
+
if (!messageColumns.includes("entity_refs")) {
|
|
10092
|
+
db.exec(`ALTER TABLE ${tableName} ADD COLUMN entity_refs TEXT`);
|
|
10093
|
+
}
|
|
10094
|
+
}
|
|
10095
|
+
ensureChatMigrationColumns(db) {
|
|
10096
|
+
const chatColumns = this.getTableColumns(db, "ai_chat");
|
|
10097
|
+
if (!chatColumns.includes("kind")) {
|
|
10098
|
+
db.exec(`ALTER TABLE ai_chat ADD COLUMN kind TEXT NOT NULL DEFAULT 'session'`);
|
|
10099
|
+
}
|
|
9426
10100
|
}
|
|
9427
10101
|
migrateDatabase(db) {
|
|
9428
10102
|
try {
|
|
10103
|
+
this.ensureChatMigrationColumns(db);
|
|
9429
10104
|
const hasLegacyConversationTable = this.tableExists(db, "ai_conversation");
|
|
9430
10105
|
if (hasLegacyConversationTable) {
|
|
9431
10106
|
const convColumns = this.getTableColumns(db, "ai_conversation");
|
|
@@ -9467,15 +10142,16 @@ var AIChatManager = class {
|
|
|
9467
10142
|
branch_index INTEGER DEFAULT 0,
|
|
9468
10143
|
debug_context TEXT,
|
|
9469
10144
|
token_usage TEXT,
|
|
10145
|
+
entity_refs TEXT,
|
|
9470
10146
|
FOREIGN KEY(ai_chat_id) REFERENCES ai_chat(id) ON DELETE CASCADE
|
|
9471
10147
|
);
|
|
9472
10148
|
|
|
9473
10149
|
INSERT INTO ai_message (
|
|
9474
10150
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9475
|
-
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage
|
|
10151
|
+
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage, entity_refs
|
|
9476
10152
|
)
|
|
9477
10153
|
SELECT id, conversation_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9478
|
-
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage
|
|
10154
|
+
content_blocks, parent_id, sibling_group_id, branch_index, debug_context, token_usage, entity_refs
|
|
9479
10155
|
FROM ai_message_legacy;
|
|
9480
10156
|
|
|
9481
10157
|
DROP TABLE ai_message_legacy;
|
|
@@ -9560,7 +10236,8 @@ var AIChatManager = class {
|
|
|
9560
10236
|
dataKeywords: row.dataKeywords ? JSON.parse(row.dataKeywords) : void 0,
|
|
9561
10237
|
dataMessageCount: row.dataMessageCount ?? void 0,
|
|
9562
10238
|
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0,
|
|
9563
|
-
tokenUsage: row.tokenUsage ? JSON.parse(row.tokenUsage) : void 0
|
|
10239
|
+
tokenUsage: row.tokenUsage ? JSON.parse(row.tokenUsage) : void 0,
|
|
10240
|
+
entityRefs: row.entityRefs ? JSON.parse(row.entityRefs) : void 0
|
|
9564
10241
|
};
|
|
9565
10242
|
}
|
|
9566
10243
|
getMessageRow(messageId) {
|
|
@@ -9569,7 +10246,7 @@ var AIChatManager = class {
|
|
|
9569
10246
|
`SELECT id, ai_chat_id as aiChatId, role, content, timestamp,
|
|
9570
10247
|
parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,
|
|
9571
10248
|
data_keywords as dataKeywords, data_message_count as dataMessageCount,
|
|
9572
|
-
content_blocks as contentBlocks, token_usage as tokenUsage
|
|
10249
|
+
content_blocks as contentBlocks, token_usage as tokenUsage, entity_refs as entityRefs
|
|
9573
10250
|
FROM ai_message WHERE id = ?`
|
|
9574
10251
|
).get(messageId);
|
|
9575
10252
|
return row ?? null;
|
|
@@ -9593,7 +10270,7 @@ var AIChatManager = class {
|
|
|
9593
10270
|
`SELECT id, ai_chat_id as aiChatId, role, content, timestamp,
|
|
9594
10271
|
parent_id as parentId, sibling_group_id as siblingGroupId, branch_index as branchIndex,
|
|
9595
10272
|
data_keywords as dataKeywords, data_message_count as dataMessageCount,
|
|
9596
|
-
content_blocks as contentBlocks, token_usage as tokenUsage
|
|
10273
|
+
content_blocks as contentBlocks, token_usage as tokenUsage, entity_refs as entityRefs
|
|
9597
10274
|
FROM ai_message WHERE ai_chat_id = ? ORDER BY timestamp ASC, id ASC`
|
|
9598
10275
|
).all(aiChatId);
|
|
9599
10276
|
}
|
|
@@ -9603,16 +10280,16 @@ var AIChatManager = class {
|
|
|
9603
10280
|
if (allRows.length === 0) return [];
|
|
9604
10281
|
const rowMap = new Map(allRows.map((row) => [row.id, row]));
|
|
9605
10282
|
let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
|
|
9606
|
-
const
|
|
10283
|
+
const path31 = [];
|
|
9607
10284
|
const seen = /* @__PURE__ */ new Set();
|
|
9608
10285
|
while (currentId && !seen.has(currentId)) {
|
|
9609
10286
|
seen.add(currentId);
|
|
9610
10287
|
const row = rowMap.get(currentId);
|
|
9611
10288
|
if (!row) break;
|
|
9612
|
-
|
|
10289
|
+
path31.push(row);
|
|
9613
10290
|
currentId = row.parentId;
|
|
9614
10291
|
}
|
|
9615
|
-
return
|
|
10292
|
+
return path31.length > 0 ? path31.reverse() : allRows;
|
|
9616
10293
|
}
|
|
9617
10294
|
// ==================== 生命周期 ====================
|
|
9618
10295
|
close() {
|
|
@@ -9669,20 +10346,35 @@ var AIChatManager = class {
|
|
|
9669
10346
|
}
|
|
9670
10347
|
// ==================== 对话管理 ====================
|
|
9671
10348
|
createAIChat(sessionId, title, assistantId) {
|
|
10349
|
+
return this.createChat("session", sessionId, title, assistantId);
|
|
10350
|
+
}
|
|
10351
|
+
createGlobalAIChat(title, assistantId) {
|
|
10352
|
+
return this.createChat("global", "", title, assistantId);
|
|
10353
|
+
}
|
|
10354
|
+
createChat(kind, sessionId, title, assistantId) {
|
|
9672
10355
|
const db = this.getDb();
|
|
9673
10356
|
const now = Math.floor(Date.now() / 1e3);
|
|
9674
10357
|
const id = this.generateId("conv");
|
|
9675
10358
|
db.prepare(
|
|
9676
|
-
`INSERT INTO ai_chat (id, session_id, title, assistant_id, created_at, updated_at)
|
|
9677
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
9678
|
-
).run(id, sessionId, title || null, assistantId, now, now);
|
|
9679
|
-
return {
|
|
10359
|
+
`INSERT INTO ai_chat (id, session_id, kind, title, assistant_id, created_at, updated_at)
|
|
10360
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
10361
|
+
).run(id, sessionId, kind, title || null, assistantId, now, now);
|
|
10362
|
+
return {
|
|
10363
|
+
id,
|
|
10364
|
+
sessionId,
|
|
10365
|
+
kind,
|
|
10366
|
+
title: title || null,
|
|
10367
|
+
assistantId,
|
|
10368
|
+
activeMessageId: null,
|
|
10369
|
+
createdAt: now,
|
|
10370
|
+
updatedAt: now
|
|
10371
|
+
};
|
|
9680
10372
|
}
|
|
9681
10373
|
getAIChatCountsBySession() {
|
|
9682
10374
|
const result = /* @__PURE__ */ new Map();
|
|
9683
10375
|
try {
|
|
9684
10376
|
const db = this.getDb();
|
|
9685
|
-
const rows = db.prepare("SELECT session_id, COUNT(*) as count FROM ai_chat GROUP BY session_id").all();
|
|
10377
|
+
const rows = db.prepare("SELECT session_id, COUNT(*) as count FROM ai_chat WHERE kind = 'session' GROUP BY session_id").all();
|
|
9686
10378
|
for (const row of rows) {
|
|
9687
10379
|
result.set(row.session_id, row.count);
|
|
9688
10380
|
}
|
|
@@ -9693,16 +10385,24 @@ var AIChatManager = class {
|
|
|
9693
10385
|
getAIChats(sessionId) {
|
|
9694
10386
|
const db = this.getDb();
|
|
9695
10387
|
return db.prepare(
|
|
9696
|
-
`SELECT id, session_id as sessionId, title, assistant_id as assistantId,
|
|
10388
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
9697
10389
|
active_message_id as activeMessageId,
|
|
9698
10390
|
created_at as createdAt, updated_at as updatedAt
|
|
9699
|
-
FROM ai_chat WHERE session_id = ? ORDER BY updated_at DESC`
|
|
10391
|
+
FROM ai_chat WHERE kind = 'session' AND session_id = ? ORDER BY updated_at DESC`
|
|
9700
10392
|
).all(sessionId);
|
|
9701
10393
|
}
|
|
10394
|
+
getGlobalAIChats() {
|
|
10395
|
+
return this.getDb().prepare(
|
|
10396
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
10397
|
+
active_message_id as activeMessageId,
|
|
10398
|
+
created_at as createdAt, updated_at as updatedAt
|
|
10399
|
+
FROM ai_chat WHERE kind = 'global' ORDER BY updated_at DESC`
|
|
10400
|
+
).all();
|
|
10401
|
+
}
|
|
9702
10402
|
getAIChat(aiChatId) {
|
|
9703
10403
|
const db = this.getDb();
|
|
9704
10404
|
const row = db.prepare(
|
|
9705
|
-
`SELECT id, session_id as sessionId, title, assistant_id as assistantId,
|
|
10405
|
+
`SELECT id, session_id as sessionId, kind, title, assistant_id as assistantId,
|
|
9706
10406
|
active_message_id as activeMessageId,
|
|
9707
10407
|
created_at as createdAt, updated_at as updatedAt
|
|
9708
10408
|
FROM ai_chat WHERE id = ?`
|
|
@@ -9722,13 +10422,79 @@ var AIChatManager = class {
|
|
|
9722
10422
|
return result.changes > 0;
|
|
9723
10423
|
}
|
|
9724
10424
|
// ==================== 消息管理 ====================
|
|
9725
|
-
addMessage(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage) {
|
|
10425
|
+
addMessage(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage, entityRefs) {
|
|
10426
|
+
return this.addMessageWithEntityRefs(
|
|
10427
|
+
aiChatId,
|
|
10428
|
+
role,
|
|
10429
|
+
content,
|
|
10430
|
+
dataKeywords,
|
|
10431
|
+
dataMessageCount,
|
|
10432
|
+
contentBlocks,
|
|
10433
|
+
tokenUsage,
|
|
10434
|
+
role === "user" ? entityRefs : void 0
|
|
10435
|
+
);
|
|
10436
|
+
}
|
|
10437
|
+
addMessagePair(aiChatId, userMessage, assistantMessage) {
|
|
10438
|
+
const db = this.getDb();
|
|
10439
|
+
return db.transaction(() => ({
|
|
10440
|
+
userMessage: this.addMessage(
|
|
10441
|
+
aiChatId,
|
|
10442
|
+
"user",
|
|
10443
|
+
userMessage.content,
|
|
10444
|
+
void 0,
|
|
10445
|
+
void 0,
|
|
10446
|
+
void 0,
|
|
10447
|
+
void 0,
|
|
10448
|
+
userMessage.entityRefs
|
|
10449
|
+
),
|
|
10450
|
+
assistantMessage: this.addMessage(
|
|
10451
|
+
aiChatId,
|
|
10452
|
+
"assistant",
|
|
10453
|
+
assistantMessage.content,
|
|
10454
|
+
void 0,
|
|
10455
|
+
void 0,
|
|
10456
|
+
assistantMessage.contentBlocks,
|
|
10457
|
+
assistantMessage.tokenUsage
|
|
10458
|
+
)
|
|
10459
|
+
}))();
|
|
10460
|
+
}
|
|
10461
|
+
/** Atomically replaces the latest user/assistant round on the active path. */
|
|
10462
|
+
replaceLatestMessageRound(aiChatId, input) {
|
|
10463
|
+
const db = this.getDb();
|
|
10464
|
+
return db.transaction(() => {
|
|
10465
|
+
const activePath = this.getActivePathRows(aiChatId);
|
|
10466
|
+
const latestUserIndex = activePath.findLastIndex((message) => message.role === "user");
|
|
10467
|
+
const userMessage = activePath[latestUserIndex];
|
|
10468
|
+
if (!userMessage || userMessage.id !== input.userMessageId) {
|
|
10469
|
+
throw new Error("Only the latest user message can be edited");
|
|
10470
|
+
}
|
|
10471
|
+
const followingMessages = activePath.slice(latestUserIndex + 1);
|
|
10472
|
+
if (followingMessages.length > 1 || followingMessages.length === 1 && (followingMessages[0]?.role !== "assistant" || followingMessages[0].parentId !== input.userMessageId)) {
|
|
10473
|
+
throw new Error("Latest user message is not a replaceable round");
|
|
10474
|
+
}
|
|
10475
|
+
const oldAssistantMessage = followingMessages[0];
|
|
10476
|
+
this.updateMessageContent(input.userMessageId, input.userContent);
|
|
10477
|
+
if (oldAssistantMessage) {
|
|
10478
|
+
this.deleteAndRelinkMessage(aiChatId, oldAssistantMessage.id);
|
|
10479
|
+
}
|
|
10480
|
+
return this.insertMessageAfter(
|
|
10481
|
+
aiChatId,
|
|
10482
|
+
input.userMessageId,
|
|
10483
|
+
"assistant",
|
|
10484
|
+
input.assistantMessage.content,
|
|
10485
|
+
input.assistantMessage.contentBlocks,
|
|
10486
|
+
input.assistantMessage.tokenUsage
|
|
10487
|
+
);
|
|
10488
|
+
})();
|
|
10489
|
+
}
|
|
10490
|
+
addMessageWithEntityRefs(aiChatId, role, content, dataKeywords, dataMessageCount, contentBlocks, tokenUsage, entityRefs) {
|
|
9726
10491
|
const db = this.getDb();
|
|
9727
10492
|
const now = Math.floor(Date.now() / 1e3);
|
|
9728
10493
|
const id = this.generateId("msg");
|
|
9729
10494
|
const parentId = this.getActiveMessageId(aiChatId);
|
|
9730
10495
|
const siblingGroupId = id;
|
|
9731
10496
|
const branchIndex = 0;
|
|
10497
|
+
const persistedEntityRefs = entityRefs?.length ? entityRefs : void 0;
|
|
9732
10498
|
const pendingDebug = role === "assistant" ? this.pendingDebugContextMap.get(aiChatId) : void 0;
|
|
9733
10499
|
if (pendingDebug) {
|
|
9734
10500
|
this.pendingDebugContextMap.delete(aiChatId);
|
|
@@ -9736,9 +10502,9 @@ var AIChatManager = class {
|
|
|
9736
10502
|
db.prepare(
|
|
9737
10503
|
`INSERT INTO ai_message (
|
|
9738
10504
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9739
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10505
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9740
10506
|
)
|
|
9741
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
10507
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
9742
10508
|
).run(
|
|
9743
10509
|
id,
|
|
9744
10510
|
aiChatId,
|
|
@@ -9752,7 +10518,8 @@ var AIChatManager = class {
|
|
|
9752
10518
|
pendingDebug ?? null,
|
|
9753
10519
|
parentId,
|
|
9754
10520
|
siblingGroupId,
|
|
9755
|
-
branchIndex
|
|
10521
|
+
branchIndex,
|
|
10522
|
+
persistedEntityRefs?.length ? JSON.stringify(persistedEntityRefs) : null
|
|
9756
10523
|
);
|
|
9757
10524
|
db.prepare("UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?").run(id, now, aiChatId);
|
|
9758
10525
|
return {
|
|
@@ -9765,12 +10532,17 @@ var AIChatManager = class {
|
|
|
9765
10532
|
dataKeywords,
|
|
9766
10533
|
dataMessageCount,
|
|
9767
10534
|
contentBlocks,
|
|
9768
|
-
tokenUsage
|
|
10535
|
+
tokenUsage,
|
|
10536
|
+
entityRefs: persistedEntityRefs?.length ? persistedEntityRefs : void 0
|
|
9769
10537
|
};
|
|
9770
10538
|
}
|
|
9771
10539
|
getMessages(aiChatId) {
|
|
9772
10540
|
return this.getActivePathRows(aiChatId).map((row) => this.parseMessageRow(row));
|
|
9773
10541
|
}
|
|
10542
|
+
getMessage(messageId) {
|
|
10543
|
+
const row = this.getMessageRow(messageId);
|
|
10544
|
+
return row ? this.parseMessageRow(row) : null;
|
|
10545
|
+
}
|
|
9774
10546
|
deleteMessage(messageId) {
|
|
9775
10547
|
const db = this.getDb();
|
|
9776
10548
|
const result = db.prepare("DELETE FROM ai_message WHERE id = ?").run(messageId);
|
|
@@ -9810,9 +10582,9 @@ var AIChatManager = class {
|
|
|
9810
10582
|
const newConvId = this.generateId("conv");
|
|
9811
10583
|
const forkTitle = title || `${source.title || "Untitled"} (fork)`;
|
|
9812
10584
|
db.prepare(
|
|
9813
|
-
`INSERT INTO ai_chat (id, session_id, title, assistant_id, active_message_id, created_at, updated_at)
|
|
9814
|
-
VALUES (?, ?, ?, ?, NULL, ?, ?)`
|
|
9815
|
-
).run(newConvId, source.sessionId, forkTitle, source.assistantId, now, now);
|
|
10585
|
+
`INSERT INTO ai_chat (id, session_id, kind, title, assistant_id, active_message_id, created_at, updated_at)
|
|
10586
|
+
VALUES (?, ?, ?, ?, ?, NULL, ?, ?)`
|
|
10587
|
+
).run(newConvId, source.sessionId, source.kind, forkTitle, source.assistantId, now, now);
|
|
9816
10588
|
const idMap = /* @__PURE__ */ new Map();
|
|
9817
10589
|
let lastNewId = null;
|
|
9818
10590
|
for (const row of messagesToCopy) {
|
|
@@ -9822,9 +10594,9 @@ var AIChatManager = class {
|
|
|
9822
10594
|
db.prepare(
|
|
9823
10595
|
`INSERT INTO ai_message (
|
|
9824
10596
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9825
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10597
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9826
10598
|
)
|
|
9827
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, 0)`
|
|
10599
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, 0, ?)`
|
|
9828
10600
|
).run(
|
|
9829
10601
|
newMsgId,
|
|
9830
10602
|
newConvId,
|
|
@@ -9836,7 +10608,8 @@ var AIChatManager = class {
|
|
|
9836
10608
|
row.contentBlocks,
|
|
9837
10609
|
row.tokenUsage,
|
|
9838
10610
|
newParentId,
|
|
9839
|
-
newMsgId
|
|
10611
|
+
newMsgId,
|
|
10612
|
+
row.role === "user" || row.role === "summary" ? row.entityRefs : null
|
|
9840
10613
|
);
|
|
9841
10614
|
lastNewId = newMsgId;
|
|
9842
10615
|
}
|
|
@@ -9846,6 +10619,7 @@ var AIChatManager = class {
|
|
|
9846
10619
|
return {
|
|
9847
10620
|
id: newConvId,
|
|
9848
10621
|
sessionId: source.sessionId,
|
|
10622
|
+
kind: source.kind,
|
|
9849
10623
|
title: forkTitle,
|
|
9850
10624
|
assistantId: source.assistantId,
|
|
9851
10625
|
activeMessageId: lastNewId,
|
|
@@ -9853,9 +10627,15 @@ var AIChatManager = class {
|
|
|
9853
10627
|
updatedAt: now
|
|
9854
10628
|
};
|
|
9855
10629
|
}
|
|
9856
|
-
|
|
10630
|
+
/** Replace message text and user entity references atomically; omitted references clear the previous value. */
|
|
10631
|
+
updateMessageContent(messageId, newContent, entityRefs) {
|
|
9857
10632
|
const db = this.getDb();
|
|
9858
|
-
const
|
|
10633
|
+
const serializedEntityRefs = entityRefs?.length ? JSON.stringify(entityRefs) : null;
|
|
10634
|
+
const result = db.prepare(
|
|
10635
|
+
`UPDATE ai_message
|
|
10636
|
+
SET content = ?, entity_refs = CASE WHEN role = 'user' THEN ? ELSE NULL END
|
|
10637
|
+
WHERE id = ?`
|
|
10638
|
+
).run(newContent, serializedEntityRefs, messageId);
|
|
9859
10639
|
if (result.changes === 0) throw new Error("Message not found");
|
|
9860
10640
|
}
|
|
9861
10641
|
deleteAndRelinkMessage(aiChatId, messageId) {
|
|
@@ -9880,10 +10660,11 @@ var AIChatManager = class {
|
|
|
9880
10660
|
}
|
|
9881
10661
|
db.prepare("DELETE FROM ai_message WHERE id = ?").run(messageId);
|
|
9882
10662
|
}
|
|
9883
|
-
insertMessageAfter(aiChatId, afterMessageId, role, content, contentBlocks, tokenUsage) {
|
|
10663
|
+
insertMessageAfter(aiChatId, afterMessageId, role, content, contentBlocks, tokenUsage, entityRefs) {
|
|
9884
10664
|
const db = this.getDb();
|
|
9885
10665
|
const now = Math.floor(Date.now() / 1e3);
|
|
9886
10666
|
const id = this.generateId("msg");
|
|
10667
|
+
const persistedEntityRefs = role === "user" ? entityRefs : void 0;
|
|
9887
10668
|
const pendingDebug = role === "assistant" ? this.pendingDebugContextMap.get(aiChatId) : void 0;
|
|
9888
10669
|
if (pendingDebug) {
|
|
9889
10670
|
this.pendingDebugContextMap.delete(aiChatId);
|
|
@@ -9892,9 +10673,9 @@ var AIChatManager = class {
|
|
|
9892
10673
|
db.prepare(
|
|
9893
10674
|
`INSERT INTO ai_message (
|
|
9894
10675
|
id, ai_chat_id, role, content, timestamp, data_keywords, data_message_count,
|
|
9895
|
-
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index
|
|
10676
|
+
content_blocks, token_usage, debug_context, parent_id, sibling_group_id, branch_index, entity_refs
|
|
9896
10677
|
)
|
|
9897
|
-
VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, 0)`
|
|
10678
|
+
VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, 0, ?)`
|
|
9898
10679
|
).run(
|
|
9899
10680
|
id,
|
|
9900
10681
|
aiChatId,
|
|
@@ -9905,7 +10686,8 @@ var AIChatManager = class {
|
|
|
9905
10686
|
tokenUsage ? JSON.stringify(tokenUsage) : null,
|
|
9906
10687
|
pendingDebug ?? null,
|
|
9907
10688
|
afterMessageId,
|
|
9908
|
-
id
|
|
10689
|
+
id,
|
|
10690
|
+
persistedEntityRefs?.length ? JSON.stringify(persistedEntityRefs) : null
|
|
9909
10691
|
);
|
|
9910
10692
|
if (childRow) {
|
|
9911
10693
|
db.prepare("UPDATE ai_message SET parent_id = ? WHERE id = ?").run(id, childRow.id);
|
|
@@ -9921,7 +10703,8 @@ var AIChatManager = class {
|
|
|
9921
10703
|
timestamp: now,
|
|
9922
10704
|
parentId: afterMessageId,
|
|
9923
10705
|
contentBlocks,
|
|
9924
|
-
tokenUsage
|
|
10706
|
+
tokenUsage,
|
|
10707
|
+
entityRefs: persistedEntityRefs?.length ? persistedEntityRefs : void 0
|
|
9925
10708
|
};
|
|
9926
10709
|
}
|
|
9927
10710
|
getAIChatTokenUsage(aiChatId) {
|
|
@@ -9985,11 +10768,26 @@ var AIChatManager = class {
|
|
|
9985
10768
|
}
|
|
9986
10769
|
const contextMessages = bufferBoundary ? validMessages.filter((m) => m.role !== "summary" && m.timestamp >= bufferBoundary) : [];
|
|
9987
10770
|
result = [
|
|
9988
|
-
{
|
|
9989
|
-
|
|
10771
|
+
{
|
|
10772
|
+
role: "summary",
|
|
10773
|
+
content: summaryMsg.content,
|
|
10774
|
+
contentBlocks: summaryMsg.contentBlocks,
|
|
10775
|
+
entityRefs: summaryMsg.entityRefs
|
|
10776
|
+
},
|
|
10777
|
+
...contextMessages.map((m) => ({
|
|
10778
|
+
role: m.role,
|
|
10779
|
+
content: m.content,
|
|
10780
|
+
contentBlocks: m.contentBlocks,
|
|
10781
|
+
entityRefs: m.role === "user" ? m.entityRefs : void 0
|
|
10782
|
+
}))
|
|
9990
10783
|
];
|
|
9991
10784
|
} else {
|
|
9992
|
-
result = validMessages.map((m) => ({
|
|
10785
|
+
result = validMessages.map((m) => ({
|
|
10786
|
+
role: m.role,
|
|
10787
|
+
content: m.content,
|
|
10788
|
+
contentBlocks: m.contentBlocks,
|
|
10789
|
+
entityRefs: m.role === "user" ? m.entityRefs : void 0
|
|
10790
|
+
}));
|
|
9993
10791
|
}
|
|
9994
10792
|
if (maxMessages && result.length > maxMessages) {
|
|
9995
10793
|
if (result.length > 0 && result[0].role === "summary") {
|
|
@@ -10002,7 +10800,7 @@ var AIChatManager = class {
|
|
|
10002
10800
|
return result;
|
|
10003
10801
|
}
|
|
10004
10802
|
// ==================== Summary / 压缩专用 ====================
|
|
10005
|
-
addSummaryMessage(aiChatId, content, meta) {
|
|
10803
|
+
addSummaryMessage(aiChatId, content, meta, entityRefs) {
|
|
10006
10804
|
const contentBlocks = [
|
|
10007
10805
|
{
|
|
10008
10806
|
type: "summary_meta",
|
|
@@ -10010,7 +10808,16 @@ var AIChatManager = class {
|
|
|
10010
10808
|
compressedMessageCount: meta.compressedMessageCount
|
|
10011
10809
|
}
|
|
10012
10810
|
];
|
|
10013
|
-
return this.
|
|
10811
|
+
return this.addMessageWithEntityRefs(
|
|
10812
|
+
aiChatId,
|
|
10813
|
+
"summary",
|
|
10814
|
+
content,
|
|
10815
|
+
void 0,
|
|
10816
|
+
void 0,
|
|
10817
|
+
contentBlocks,
|
|
10818
|
+
void 0,
|
|
10819
|
+
entityRefs
|
|
10820
|
+
);
|
|
10014
10821
|
}
|
|
10015
10822
|
getLatestSummary(aiChatId) {
|
|
10016
10823
|
const row = [...this.getActivePathRows(aiChatId)].reverse().find((message) => message.role === "summary");
|
|
@@ -10027,7 +10834,8 @@ var AIChatManager = class {
|
|
|
10027
10834
|
role: row.role,
|
|
10028
10835
|
content: row.content,
|
|
10029
10836
|
timestamp: row.timestamp,
|
|
10030
|
-
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0
|
|
10837
|
+
contentBlocks: row.contentBlocks ? JSON.parse(row.contentBlocks) : void 0,
|
|
10838
|
+
entityRefs: row.role === "user" && row.entityRefs ? JSON.parse(row.entityRefs) : void 0
|
|
10031
10839
|
};
|
|
10032
10840
|
}
|
|
10033
10841
|
getMessageCountAfterSummary(aiChatId) {
|
|
@@ -10045,6 +10853,747 @@ var AIChatManager = class {
|
|
|
10045
10853
|
}
|
|
10046
10854
|
};
|
|
10047
10855
|
|
|
10856
|
+
// ../../packages/node-runtime/src/nlp/segmenter.ts
|
|
10857
|
+
import * as fs9 from "fs";
|
|
10858
|
+
import * as path16 from "path";
|
|
10859
|
+
import { createRequire } from "module";
|
|
10860
|
+
var _nlpDir = null;
|
|
10861
|
+
var jiebaInstances = /* @__PURE__ */ new Map();
|
|
10862
|
+
var require2 = createRequire(import.meta.url);
|
|
10863
|
+
function initNlpDir(nlpDir) {
|
|
10864
|
+
_nlpDir = nlpDir;
|
|
10865
|
+
}
|
|
10866
|
+
function getNlpDir() {
|
|
10867
|
+
return _nlpDir;
|
|
10868
|
+
}
|
|
10869
|
+
function existsDictOnDisk(dictId) {
|
|
10870
|
+
if (!_nlpDir) return false;
|
|
10871
|
+
return fs9.existsSync(path16.join(_nlpDir, `${dictId}.dict`));
|
|
10872
|
+
}
|
|
10873
|
+
function tryLoadDictFromDisk(dictId) {
|
|
10874
|
+
if (!_nlpDir) return null;
|
|
10875
|
+
const dictPath = path16.join(_nlpDir, `${dictId}.dict`);
|
|
10876
|
+
if (!fs9.existsSync(dictPath)) return null;
|
|
10877
|
+
try {
|
|
10878
|
+
return fs9.readFileSync(dictPath);
|
|
10879
|
+
} catch {
|
|
10880
|
+
return null;
|
|
10881
|
+
}
|
|
10882
|
+
}
|
|
10883
|
+
function getJieba(dictType = "default") {
|
|
10884
|
+
const effectiveType = dictType === "default" ? "zh-CN" : dictType;
|
|
10885
|
+
const cached = jiebaInstances.get(effectiveType);
|
|
10886
|
+
if (cached) {
|
|
10887
|
+
if (cached.fromDiskDict === existsDictOnDisk(effectiveType)) return cached.instance;
|
|
10888
|
+
jiebaInstances.delete(effectiveType);
|
|
10889
|
+
console.error(`[NLP] jieba cache invalidated (dict ${cached.fromDiskDict ? "removed" : "added"}): ${effectiveType}`);
|
|
10890
|
+
}
|
|
10891
|
+
try {
|
|
10892
|
+
const { Jieba } = require2("@node-rs/jieba");
|
|
10893
|
+
let instance2;
|
|
10894
|
+
const diskDict = tryLoadDictFromDisk(effectiveType);
|
|
10895
|
+
if (diskDict) {
|
|
10896
|
+
instance2 = Jieba.withDict(diskDict);
|
|
10897
|
+
console.error(`[NLP] jieba dict loaded: ${effectiveType} (${diskDict.length} bytes)`);
|
|
10898
|
+
} else {
|
|
10899
|
+
instance2 = new Jieba();
|
|
10900
|
+
console.warn(`[NLP] jieba dict missing: ${effectiveType}, fallback to built-in tokenizer`);
|
|
10901
|
+
}
|
|
10902
|
+
jiebaInstances.set(effectiveType, { instance: instance2, fromDiskDict: !!diskDict });
|
|
10903
|
+
return instance2;
|
|
10904
|
+
} catch (error) {
|
|
10905
|
+
console.error(`[NLP] Failed to load jieba module (dict=${effectiveType}):`, error);
|
|
10906
|
+
throw new Error(`jieba \u6A21\u5757\u52A0\u8F7D\u5931\u8D25 (${effectiveType})`);
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
10909
|
+
function clearJiebaInstance(dictType) {
|
|
10910
|
+
jiebaInstances.delete(dictType);
|
|
10911
|
+
console.error(`[NLP] jieba instance cleared: ${dictType}`);
|
|
10912
|
+
}
|
|
10913
|
+
function segmentChinese(text, options = {}) {
|
|
10914
|
+
const { posFilterMode = "meaningful", customPosTags, dictType = "default" } = options;
|
|
10915
|
+
const cleaned = cleanText(text);
|
|
10916
|
+
if (!cleaned) return [];
|
|
10917
|
+
try {
|
|
10918
|
+
const jieba = getJieba(dictType);
|
|
10919
|
+
if (posFilterMode === "all") return jieba.cut(cleaned, false);
|
|
10920
|
+
const tagged = jieba.tag(cleaned);
|
|
10921
|
+
const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
|
|
10922
|
+
return tagged.filter((item) => allowedTags.has(item.tag)).map((item) => item.word);
|
|
10923
|
+
} catch (error) {
|
|
10924
|
+
console.error("[NLP] Chinese segmentation failed:", error);
|
|
10925
|
+
try {
|
|
10926
|
+
return getJieba("default").cut(cleanText(text), false);
|
|
10927
|
+
} catch {
|
|
10928
|
+
return cleaned.split("");
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
}
|
|
10932
|
+
function segmentEnglish(text) {
|
|
10933
|
+
const cleaned = cleanText(text);
|
|
10934
|
+
if (!cleaned) return [];
|
|
10935
|
+
try {
|
|
10936
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
|
|
10937
|
+
return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
|
|
10938
|
+
} catch {
|
|
10939
|
+
return cleaned.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
function segmentJapanese(text) {
|
|
10943
|
+
const cleaned = cleanText(text);
|
|
10944
|
+
if (!cleaned) return [];
|
|
10945
|
+
try {
|
|
10946
|
+
const segmenter = new Intl.Segmenter("ja", { granularity: "word" });
|
|
10947
|
+
return [...segmenter.segment(cleaned)].filter((s) => s.isWordLike).map((s) => s.segment);
|
|
10948
|
+
} catch {
|
|
10949
|
+
return cleaned.split("").filter((ch) => ch.trim().length > 0);
|
|
10950
|
+
}
|
|
10951
|
+
}
|
|
10952
|
+
function segment(text, locale, options = {}) {
|
|
10953
|
+
const {
|
|
10954
|
+
minLength,
|
|
10955
|
+
posFilterMode = "meaningful",
|
|
10956
|
+
customPosTags,
|
|
10957
|
+
enableStopwords = true,
|
|
10958
|
+
dictType = "default"
|
|
10959
|
+
} = options;
|
|
10960
|
+
const isChinese2 = locale.startsWith("zh");
|
|
10961
|
+
const isJapanese = locale === "ja-JP";
|
|
10962
|
+
const defaultMinLength = isChinese2 || isJapanese ? 2 : 3;
|
|
10963
|
+
const effectiveMinLength = minLength ?? defaultMinLength;
|
|
10964
|
+
let words;
|
|
10965
|
+
if (isChinese2) {
|
|
10966
|
+
words = segmentChinese(text, { posFilterMode, customPosTags, dictType });
|
|
10967
|
+
} else if (isJapanese) {
|
|
10968
|
+
words = segmentJapanese(text);
|
|
10969
|
+
} else {
|
|
10970
|
+
words = segmentEnglish(text);
|
|
10971
|
+
}
|
|
10972
|
+
return words.filter((word) => isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword));
|
|
10973
|
+
}
|
|
10974
|
+
function batchSegmentWithFrequency(texts, locale, options = {}) {
|
|
10975
|
+
const {
|
|
10976
|
+
minLength,
|
|
10977
|
+
minCount = 2,
|
|
10978
|
+
topN = 100,
|
|
10979
|
+
posFilterMode,
|
|
10980
|
+
customPosTags,
|
|
10981
|
+
enableStopwords,
|
|
10982
|
+
dictType,
|
|
10983
|
+
excludeWords
|
|
10984
|
+
} = options;
|
|
10985
|
+
const wordFrequency = /* @__PURE__ */ new Map();
|
|
10986
|
+
const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
|
|
10987
|
+
for (const text of texts) {
|
|
10988
|
+
const words = segment(text, locale, { minLength, posFilterMode, customPosTags, enableStopwords, dictType });
|
|
10989
|
+
for (const word of words) {
|
|
10990
|
+
if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
|
|
10991
|
+
wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
10995
|
+
let totalWords = 0;
|
|
10996
|
+
for (const [word, count] of wordFrequency) {
|
|
10997
|
+
if (count >= minCount) {
|
|
10998
|
+
filtered.set(word, count);
|
|
10999
|
+
totalWords += count;
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
|
|
11003
|
+
return { words: new Map(sorted), uniqueWords: filtered.size, totalWords };
|
|
11004
|
+
}
|
|
11005
|
+
function batchSegmentChineseWithStats(texts, locale, options = {}) {
|
|
11006
|
+
const {
|
|
11007
|
+
minLength,
|
|
11008
|
+
minCount = 2,
|
|
11009
|
+
topN = 100,
|
|
11010
|
+
posFilterMode = "meaningful",
|
|
11011
|
+
customPosTags,
|
|
11012
|
+
enableStopwords = true,
|
|
11013
|
+
dictType = "default",
|
|
11014
|
+
excludeWords
|
|
11015
|
+
} = options;
|
|
11016
|
+
const effectiveMinLength = minLength ?? 2;
|
|
11017
|
+
const allowedTags = posFilterMode === "custom" && customPosTags ? new Set(customPosTags) : MEANINGFUL_POS_TAGS;
|
|
11018
|
+
const excludeSet = excludeWords?.length ? new Set(excludeWords.map((w) => w.toLowerCase())) : null;
|
|
11019
|
+
const wordFrequency = /* @__PURE__ */ new Map();
|
|
11020
|
+
const posTagStats = /* @__PURE__ */ new Map();
|
|
11021
|
+
try {
|
|
11022
|
+
const jieba = getJieba(dictType);
|
|
11023
|
+
for (const text of texts) {
|
|
11024
|
+
const cleaned = cleanText(text);
|
|
11025
|
+
if (!cleaned) continue;
|
|
11026
|
+
for (const { tag, word } of jieba.tag(cleaned)) {
|
|
11027
|
+
if (!isValidWord(word, locale, effectiveMinLength, enableStopwords, isStopword)) continue;
|
|
11028
|
+
posTagStats.set(tag, (posTagStats.get(tag) || 0) + 1);
|
|
11029
|
+
if (!allowedTags.has(tag)) continue;
|
|
11030
|
+
if (excludeSet && excludeSet.has(word.toLowerCase())) continue;
|
|
11031
|
+
wordFrequency.set(word, (wordFrequency.get(word) || 0) + 1);
|
|
11032
|
+
}
|
|
11033
|
+
}
|
|
11034
|
+
} catch (error) {
|
|
11035
|
+
console.error("[NLP] Chinese single-pass segmentation failed:", error);
|
|
11036
|
+
}
|
|
11037
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
11038
|
+
let totalWords = 0;
|
|
11039
|
+
for (const [word, count] of wordFrequency) {
|
|
11040
|
+
if (count >= minCount) {
|
|
11041
|
+
filtered.set(word, count);
|
|
11042
|
+
totalWords += count;
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
const sorted = [...filtered.entries()].sort((a, b) => b[1] - a[1]).slice(0, topN);
|
|
11046
|
+
return { words: new Map(sorted), uniqueWords: filtered.size, totalWords, posTagStats };
|
|
11047
|
+
}
|
|
11048
|
+
function collectPosTagStats(texts, minWordLength = 2, enableStopwords = true, dictType = "default") {
|
|
11049
|
+
const posStats = /* @__PURE__ */ new Map();
|
|
11050
|
+
try {
|
|
11051
|
+
const jieba = getJieba(dictType);
|
|
11052
|
+
for (const text of texts) {
|
|
11053
|
+
const cleaned = cleanText(text);
|
|
11054
|
+
if (!cleaned) continue;
|
|
11055
|
+
const tagged = jieba.tag(cleaned);
|
|
11056
|
+
for (const item of tagged) {
|
|
11057
|
+
if (!isValidWord(item.word, "zh-CN", minWordLength, enableStopwords, isStopword)) continue;
|
|
11058
|
+
posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1);
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
11061
|
+
} catch (error) {
|
|
11062
|
+
console.error("[NLP] Failed to collect POS stats:", error);
|
|
11063
|
+
}
|
|
11064
|
+
return posStats;
|
|
11065
|
+
}
|
|
11066
|
+
function getPosTagDefinitions() {
|
|
11067
|
+
return POS_TAG_DEFINITIONS;
|
|
11068
|
+
}
|
|
11069
|
+
|
|
11070
|
+
// ../../packages/node-runtime/src/ai/memory.ts
|
|
11071
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11072
|
+
import * as fs10 from "fs";
|
|
11073
|
+
import * as path17 from "path";
|
|
11074
|
+
import Database3 from "better-sqlite3";
|
|
11075
|
+
var AI_MEMORY_CONTENT_MAX_CHARS = 2e3;
|
|
11076
|
+
var AI_MEMORY_SCHEMA_VERSION = 2;
|
|
11077
|
+
var AI_MEMORY_PROMPT_MAX_ENTRIES = 20;
|
|
11078
|
+
var AI_MEMORY_PROMPT_MAX_CONTENT_CHARS = 4e3;
|
|
11079
|
+
var AI_MEMORY_PROMPT_BASE_USER_ENTRIES = 5;
|
|
11080
|
+
var AI_MEMORY_SUBSTRING_MIN_CHARS = 3;
|
|
11081
|
+
var NUMERIC_MEMORY_TOKEN_REGEX = new RegExp("\\p{Number}+", "gu");
|
|
11082
|
+
var SYMBOL_SUFFIX_MEMORY_TOKEN_REGEX = /[\p{Letter}\p{Number}]+(?:\+\+|#)/gu;
|
|
11083
|
+
function rankMemoryEntries(entries, options) {
|
|
11084
|
+
const recentEntries = [...entries].sort(compareRecentMemoryEntries);
|
|
11085
|
+
const query = normalizeSearchText(options.query);
|
|
11086
|
+
if (!query) {
|
|
11087
|
+
return { entries: recentEntries, retrievalMode: "recent_fallback", matchedCount: 0 };
|
|
11088
|
+
}
|
|
11089
|
+
const locale = normalizeMemoryLocale(options.locale);
|
|
11090
|
+
const queryTokens = tokenizeMemoryText(options.query, locale);
|
|
11091
|
+
const scored = recentEntries.map((entry) => ({
|
|
11092
|
+
entry,
|
|
11093
|
+
score: scoreMemoryEntry(entry.content, query, queryTokens, locale)
|
|
11094
|
+
}));
|
|
11095
|
+
const matchedCount = scored.filter((item) => item.score > 0).length;
|
|
11096
|
+
if (matchedCount === 0) {
|
|
11097
|
+
return { entries: recentEntries, retrievalMode: "recent_fallback", matchedCount: 0 };
|
|
11098
|
+
}
|
|
11099
|
+
scored.sort((left, right) => {
|
|
11100
|
+
if (left.score !== right.score) return right.score - left.score;
|
|
11101
|
+
if (left.entry.sourceType !== right.entry.sourceType) return left.entry.sourceType === "user" ? -1 : 1;
|
|
11102
|
+
return compareRecentMemoryEntries(left.entry, right.entry);
|
|
11103
|
+
});
|
|
11104
|
+
return {
|
|
11105
|
+
entries: scored.map((item) => item.entry),
|
|
11106
|
+
retrievalMode: "relevance",
|
|
11107
|
+
matchedCount
|
|
11108
|
+
};
|
|
11109
|
+
}
|
|
11110
|
+
function buildGlobalMemoryPrompt(entries, locale = "zh-CN", query = "") {
|
|
11111
|
+
const ranking = rankMemoryEntries(entries, { query, locale });
|
|
11112
|
+
const rankedEntries = ranking.entries;
|
|
11113
|
+
const recentUserEntries = [...entries].filter((entry) => entry.sourceType === "user").sort(compareRecentMemoryEntries).slice(0, AI_MEMORY_PROMPT_BASE_USER_ENTRIES);
|
|
11114
|
+
const prioritizedEntries = [];
|
|
11115
|
+
const prioritizedIds = /* @__PURE__ */ new Set();
|
|
11116
|
+
const topRelevantEntries = ranking.retrievalMode === "relevance" ? rankedEntries.slice(0, 1) : [];
|
|
11117
|
+
for (const entry of [...topRelevantEntries, ...recentUserEntries, ...rankedEntries]) {
|
|
11118
|
+
if (prioritizedIds.has(entry.id)) continue;
|
|
11119
|
+
prioritizedIds.add(entry.id);
|
|
11120
|
+
prioritizedEntries.push(entry);
|
|
11121
|
+
}
|
|
11122
|
+
const selected = [];
|
|
11123
|
+
let contentChars = 0;
|
|
11124
|
+
for (const entry of prioritizedEntries) {
|
|
11125
|
+
if (selected.length >= AI_MEMORY_PROMPT_MAX_ENTRIES) break;
|
|
11126
|
+
if (contentChars + entry.content.length > AI_MEMORY_PROMPT_MAX_CONTENT_CHARS) continue;
|
|
11127
|
+
selected.push(entry);
|
|
11128
|
+
contentChars += entry.content.length;
|
|
11129
|
+
}
|
|
11130
|
+
if (selected.length === 0) return "";
|
|
11131
|
+
const lines = selected.map((entry) => `- [id=${entry.id}; source=${entry.sourceType}] ${entry.content}`);
|
|
11132
|
+
if (selected.length < prioritizedEntries.length) {
|
|
11133
|
+
lines.push(
|
|
11134
|
+
locale.startsWith("zh") ? "- \u90E8\u5206\u5168\u5C40\u8BB0\u5FC6\u672A\u6CE8\u5165\uFF1B\u9700\u8981\u65F6\u8C03\u7528 memory_read \u8BFB\u53D6\u3002" : "- Some global memories were not injected; call memory_read when needed."
|
|
11135
|
+
);
|
|
11136
|
+
}
|
|
11137
|
+
return lines.join("\n");
|
|
11138
|
+
}
|
|
11139
|
+
function buildEntityMemoryPrompt(entityRefs, loadEntries, locale = "zh-CN", query = "") {
|
|
11140
|
+
const buckets = [];
|
|
11141
|
+
const seenScopes = /* @__PURE__ */ new Set();
|
|
11142
|
+
for (const ref of entityRefs ?? []) {
|
|
11143
|
+
const scope = ref.type === "contact" ? { scopeType: "contact", scopeId: ref.contactKey } : ref.sessionType === "group" ? { scopeType: "group", scopeId: ref.sessionId } : null;
|
|
11144
|
+
if (!scope?.scopeId) continue;
|
|
11145
|
+
const scopeKey = `${scope.scopeType}:${scope.scopeId}`;
|
|
11146
|
+
if (seenScopes.has(scopeKey)) continue;
|
|
11147
|
+
seenScopes.add(scopeKey);
|
|
11148
|
+
buckets.push({
|
|
11149
|
+
scope,
|
|
11150
|
+
displayName: ref.displayName,
|
|
11151
|
+
entries: rankMemoryEntries(loadEntries(scope), { query, locale }).entries
|
|
11152
|
+
});
|
|
11153
|
+
}
|
|
11154
|
+
const candidates = [];
|
|
11155
|
+
const maxBucketSize = Math.max(0, ...buckets.map((bucket) => bucket.entries.length));
|
|
11156
|
+
for (let index = 0; index < maxBucketSize; index++) {
|
|
11157
|
+
for (const bucket of buckets) {
|
|
11158
|
+
const entry = bucket.entries[index];
|
|
11159
|
+
if (entry) candidates.push({ bucket, entry });
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
const selected = [];
|
|
11163
|
+
let contentChars = 0;
|
|
11164
|
+
for (const candidate of candidates) {
|
|
11165
|
+
if (selected.length >= AI_MEMORY_PROMPT_MAX_ENTRIES) break;
|
|
11166
|
+
if (contentChars + candidate.entry.content.length > AI_MEMORY_PROMPT_MAX_CONTENT_CHARS) continue;
|
|
11167
|
+
selected.push(candidate);
|
|
11168
|
+
contentChars += candidate.entry.content.length;
|
|
11169
|
+
}
|
|
11170
|
+
if (selected.length === 0) return "";
|
|
11171
|
+
const lines = selected.map(({ bucket, entry }) => {
|
|
11172
|
+
return `- [scope=${bucket.scope.scopeType}; scope_id=${JSON.stringify(bucket.scope.scopeId)}; display_name=${JSON.stringify(bucket.displayName)}; id=${entry.id}; source=${entry.sourceType}] ${entry.content}`;
|
|
11173
|
+
});
|
|
11174
|
+
if (selected.length < candidates.length) {
|
|
11175
|
+
lines.push(
|
|
11176
|
+
locale.startsWith("zh") ? "- \u90E8\u5206\u5F53\u524D\u5B9E\u4F53\u8BB0\u5FC6\u672A\u6CE8\u5165\uFF1B\u9700\u8981\u65F6\u8C03\u7528 memory_read \u8BFB\u53D6\u3002" : "- Some memories for the current entities were not injected; call memory_read when needed."
|
|
11177
|
+
);
|
|
11178
|
+
}
|
|
11179
|
+
return lines.join("\n");
|
|
11180
|
+
}
|
|
11181
|
+
var AIMemoryService = class {
|
|
11182
|
+
db = null;
|
|
11183
|
+
dbPath;
|
|
11184
|
+
nativeBinding;
|
|
11185
|
+
now;
|
|
11186
|
+
idFactory;
|
|
11187
|
+
constructor(aiDataDir, options = {}) {
|
|
11188
|
+
this.dbPath = path17.join(aiDataDir, "memory.db");
|
|
11189
|
+
this.nativeBinding = options.nativeBinding;
|
|
11190
|
+
this.now = options.now ?? Date.now;
|
|
11191
|
+
this.idFactory = options.idFactory ?? (() => `memory_${randomUUID3()}`);
|
|
11192
|
+
}
|
|
11193
|
+
list(scope) {
|
|
11194
|
+
const db = this.getDb();
|
|
11195
|
+
if (!scope) {
|
|
11196
|
+
return db.prepare(
|
|
11197
|
+
`SELECT id,
|
|
11198
|
+
scope_type AS scopeType,
|
|
11199
|
+
scope_id AS scopeId,
|
|
11200
|
+
content,
|
|
11201
|
+
source_type AS sourceType,
|
|
11202
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11203
|
+
source_message_id AS sourceMessageId,
|
|
11204
|
+
created_at AS createdAt,
|
|
11205
|
+
updated_at AS updatedAt
|
|
11206
|
+
FROM ai_memory
|
|
11207
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11208
|
+
).all();
|
|
11209
|
+
}
|
|
11210
|
+
const normalized = normalizeScope(scope);
|
|
11211
|
+
if (normalized.scopeType === "global" || normalized.scopeType === "self") {
|
|
11212
|
+
return db.prepare(
|
|
11213
|
+
`SELECT id,
|
|
11214
|
+
scope_type AS scopeType,
|
|
11215
|
+
scope_id AS scopeId,
|
|
11216
|
+
content,
|
|
11217
|
+
source_type AS sourceType,
|
|
11218
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11219
|
+
source_message_id AS sourceMessageId,
|
|
11220
|
+
created_at AS createdAt,
|
|
11221
|
+
updated_at AS updatedAt
|
|
11222
|
+
FROM ai_memory
|
|
11223
|
+
WHERE scope_type = ? AND scope_id IS NULL
|
|
11224
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11225
|
+
).all(normalized.scopeType);
|
|
11226
|
+
}
|
|
11227
|
+
return db.prepare(
|
|
11228
|
+
`SELECT id,
|
|
11229
|
+
scope_type AS scopeType,
|
|
11230
|
+
scope_id AS scopeId,
|
|
11231
|
+
content,
|
|
11232
|
+
source_type AS sourceType,
|
|
11233
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11234
|
+
source_message_id AS sourceMessageId,
|
|
11235
|
+
created_at AS createdAt,
|
|
11236
|
+
updated_at AS updatedAt
|
|
11237
|
+
FROM ai_memory
|
|
11238
|
+
WHERE scope_type = ? AND scope_id = ?
|
|
11239
|
+
ORDER BY updated_at DESC, id ASC`
|
|
11240
|
+
).all(normalized.scopeType, normalized.scopeId);
|
|
11241
|
+
}
|
|
11242
|
+
search(scope, query, locale = "zh-CN") {
|
|
11243
|
+
const startedAt = Date.now();
|
|
11244
|
+
const result = rankMemoryEntries(this.list(scope), { query, locale });
|
|
11245
|
+
appLogger.debug("ai-memory", "AI memory relevance search completed", {
|
|
11246
|
+
candidateCount: result.entries.length,
|
|
11247
|
+
matchedCount: result.matchedCount,
|
|
11248
|
+
retrievalMode: result.retrievalMode,
|
|
11249
|
+
durationMs: Date.now() - startedAt
|
|
11250
|
+
});
|
|
11251
|
+
return result;
|
|
11252
|
+
}
|
|
11253
|
+
get(id) {
|
|
11254
|
+
const row = this.getDb().prepare(
|
|
11255
|
+
`SELECT id,
|
|
11256
|
+
scope_type AS scopeType,
|
|
11257
|
+
scope_id AS scopeId,
|
|
11258
|
+
content,
|
|
11259
|
+
source_type AS sourceType,
|
|
11260
|
+
source_ai_chat_id AS sourceAIChatId,
|
|
11261
|
+
source_message_id AS sourceMessageId,
|
|
11262
|
+
created_at AS createdAt,
|
|
11263
|
+
updated_at AS updatedAt
|
|
11264
|
+
FROM ai_memory
|
|
11265
|
+
WHERE id = ?`
|
|
11266
|
+
).get(id);
|
|
11267
|
+
return row ?? null;
|
|
11268
|
+
}
|
|
11269
|
+
create(input) {
|
|
11270
|
+
return this.runWrite("create", () => {
|
|
11271
|
+
const scope = normalizeScope(input);
|
|
11272
|
+
const content = normalizeContent(input.content);
|
|
11273
|
+
const sourceType = normalizeSourceType(input.sourceType);
|
|
11274
|
+
const id = this.idFactory();
|
|
11275
|
+
const now = this.now();
|
|
11276
|
+
this.getDb().prepare(
|
|
11277
|
+
`INSERT INTO ai_memory (
|
|
11278
|
+
id, scope_type, scope_id, content, source_type,
|
|
11279
|
+
source_ai_chat_id, source_message_id, created_at, updated_at
|
|
11280
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
11281
|
+
).run(
|
|
11282
|
+
id,
|
|
11283
|
+
scope.scopeType,
|
|
11284
|
+
scope.scopeId,
|
|
11285
|
+
content,
|
|
11286
|
+
sourceType,
|
|
11287
|
+
normalizeOptionalId(input.sourceAIChatId),
|
|
11288
|
+
normalizeOptionalId(input.sourceMessageId),
|
|
11289
|
+
now,
|
|
11290
|
+
now
|
|
11291
|
+
);
|
|
11292
|
+
return this.requireEntry(id);
|
|
11293
|
+
});
|
|
11294
|
+
}
|
|
11295
|
+
update(id, input) {
|
|
11296
|
+
return this.runWrite("update", () => {
|
|
11297
|
+
const content = normalizeContent(input.content);
|
|
11298
|
+
const sourceType = normalizeSourceType(input.sourceType);
|
|
11299
|
+
const result = this.getDb().prepare(
|
|
11300
|
+
`UPDATE ai_memory
|
|
11301
|
+
SET content = ?,
|
|
11302
|
+
source_type = ?,
|
|
11303
|
+
source_ai_chat_id = ?,
|
|
11304
|
+
source_message_id = ?,
|
|
11305
|
+
updated_at = ?
|
|
11306
|
+
WHERE id = ?`
|
|
11307
|
+
).run(
|
|
11308
|
+
content,
|
|
11309
|
+
sourceType,
|
|
11310
|
+
normalizeOptionalId(input.sourceAIChatId),
|
|
11311
|
+
normalizeOptionalId(input.sourceMessageId),
|
|
11312
|
+
this.now(),
|
|
11313
|
+
id
|
|
11314
|
+
);
|
|
11315
|
+
if (result.changes === 0) throw new Error(`AI memory not found: ${id}`);
|
|
11316
|
+
return this.requireEntry(id);
|
|
11317
|
+
});
|
|
11318
|
+
}
|
|
11319
|
+
linkSourceMessages(links) {
|
|
11320
|
+
if (links.length === 0) return [];
|
|
11321
|
+
return this.runWrite("link sources", () => {
|
|
11322
|
+
const db = this.getDb();
|
|
11323
|
+
const update = db.prepare(
|
|
11324
|
+
`UPDATE ai_memory
|
|
11325
|
+
SET source_message_id = ?
|
|
11326
|
+
WHERE id = ?
|
|
11327
|
+
AND source_ai_chat_id = ?
|
|
11328
|
+
AND (source_message_id IS NULL OR source_message_id = ?)`
|
|
11329
|
+
);
|
|
11330
|
+
return db.transaction(() => {
|
|
11331
|
+
const linkedIds = [];
|
|
11332
|
+
for (const link of links) {
|
|
11333
|
+
const result = update.run(link.sourceMessageId, link.id, link.sourceAIChatId, link.sourceMessageId);
|
|
11334
|
+
if (result.changes === 0) throw new Error("AI memory source changed before it could be linked");
|
|
11335
|
+
linkedIds.push(link.id);
|
|
11336
|
+
}
|
|
11337
|
+
return linkedIds;
|
|
11338
|
+
})();
|
|
11339
|
+
});
|
|
11340
|
+
}
|
|
11341
|
+
forget(id) {
|
|
11342
|
+
return this.runWrite("forget", () => this.getDb().prepare("DELETE FROM ai_memory WHERE id = ?").run(id).changes > 0);
|
|
11343
|
+
}
|
|
11344
|
+
clear(scope) {
|
|
11345
|
+
return this.runWrite("clear", () => {
|
|
11346
|
+
const db = this.getDb();
|
|
11347
|
+
if (!scope) return db.prepare("DELETE FROM ai_memory").run().changes;
|
|
11348
|
+
const normalized = normalizeScope(scope);
|
|
11349
|
+
if (normalized.scopeType === "global" || normalized.scopeType === "self") {
|
|
11350
|
+
return db.prepare("DELETE FROM ai_memory WHERE scope_type = ? AND scope_id IS NULL").run(normalized.scopeType).changes;
|
|
11351
|
+
}
|
|
11352
|
+
return db.prepare("DELETE FROM ai_memory WHERE scope_type = ? AND scope_id = ?").run(normalized.scopeType, normalized.scopeId).changes;
|
|
11353
|
+
});
|
|
11354
|
+
}
|
|
11355
|
+
getSchemaVersion() {
|
|
11356
|
+
return this.getDb().pragma("user_version", { simple: true });
|
|
11357
|
+
}
|
|
11358
|
+
close() {
|
|
11359
|
+
if (!this.db) return;
|
|
11360
|
+
this.db.close();
|
|
11361
|
+
this.db = null;
|
|
11362
|
+
appLogger.debug("ai-memory", "AI memory database closed");
|
|
11363
|
+
}
|
|
11364
|
+
getDb() {
|
|
11365
|
+
if (this.db) return this.db;
|
|
11366
|
+
fs10.mkdirSync(path17.dirname(this.dbPath), { recursive: true });
|
|
11367
|
+
const db = this.nativeBinding ? new Database3(this.dbPath, { nativeBinding: this.nativeBinding }) : new Database3(this.dbPath);
|
|
11368
|
+
try {
|
|
11369
|
+
db.pragma("journal_mode = WAL");
|
|
11370
|
+
db.pragma("busy_timeout = 5000");
|
|
11371
|
+
migrateMemorySchema(db);
|
|
11372
|
+
} catch (error) {
|
|
11373
|
+
db.close();
|
|
11374
|
+
throw error;
|
|
11375
|
+
}
|
|
11376
|
+
this.db = db;
|
|
11377
|
+
appLogger.info("ai-memory", "AI memory database initialized", { schemaVersion: AI_MEMORY_SCHEMA_VERSION });
|
|
11378
|
+
return db;
|
|
11379
|
+
}
|
|
11380
|
+
requireEntry(id) {
|
|
11381
|
+
const entry = this.get(id);
|
|
11382
|
+
if (!entry) throw new Error(`AI memory not found after write: ${id}`);
|
|
11383
|
+
return entry;
|
|
11384
|
+
}
|
|
11385
|
+
runWrite(operation, write) {
|
|
11386
|
+
try {
|
|
11387
|
+
return write();
|
|
11388
|
+
} catch (error) {
|
|
11389
|
+
appLogger.error("ai-memory", `AI memory ${operation} failed`, error);
|
|
11390
|
+
throw error;
|
|
11391
|
+
}
|
|
11392
|
+
}
|
|
11393
|
+
};
|
|
11394
|
+
function compareRecentMemoryEntries(left, right) {
|
|
11395
|
+
return right.updatedAt - left.updatedAt || left.id.localeCompare(right.id);
|
|
11396
|
+
}
|
|
11397
|
+
function normalizeMemoryLocale(locale) {
|
|
11398
|
+
if (locale?.startsWith("zh-TW")) return "zh-TW";
|
|
11399
|
+
if (locale?.startsWith("zh")) return "zh-CN";
|
|
11400
|
+
if (locale?.startsWith("ja")) return "ja-JP";
|
|
11401
|
+
return "en-US";
|
|
11402
|
+
}
|
|
11403
|
+
function normalizeSearchText(value) {
|
|
11404
|
+
return value.normalize("NFKC").toLocaleLowerCase().replace(/[\p{P}\p{S}\s]+/gu, "");
|
|
11405
|
+
}
|
|
11406
|
+
function tokenizeMemoryText(value, locale) {
|
|
11407
|
+
const words = locale.startsWith("zh") ? segmentChineseMemoryText(value, locale) : segment(value, locale, { posFilterMode: "all", enableStopwords: true });
|
|
11408
|
+
const tokens = new Set(words.map(normalizeSearchText).filter((token) => token.length >= 2));
|
|
11409
|
+
for (const token of extractNumericMemoryTokens(value)) tokens.add(token);
|
|
11410
|
+
for (const token of extractSymbolSuffixMemoryTokens(value)) tokens.add(token);
|
|
11411
|
+
if (locale.startsWith("zh")) {
|
|
11412
|
+
for (const token of extractCjkBigrams(value)) tokens.add(token);
|
|
11413
|
+
}
|
|
11414
|
+
return tokens;
|
|
11415
|
+
}
|
|
11416
|
+
function extractNumericMemoryTokens(value) {
|
|
11417
|
+
return value.normalize("NFKC").match(NUMERIC_MEMORY_TOKEN_REGEX) ?? [];
|
|
11418
|
+
}
|
|
11419
|
+
function extractSymbolSuffixMemoryTokens(value) {
|
|
11420
|
+
return value.normalize("NFKC").toLocaleLowerCase().match(SYMBOL_SUFFIX_MEMORY_TOKEN_REGEX) ?? [];
|
|
11421
|
+
}
|
|
11422
|
+
function segmentChineseMemoryText(value, locale) {
|
|
11423
|
+
try {
|
|
11424
|
+
const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
|
|
11425
|
+
return [...segmenter.segment(value)].filter((item) => item.isWordLike).map((item) => item.segment);
|
|
11426
|
+
} catch {
|
|
11427
|
+
return value.match(/[\p{Letter}\p{Number}]+/gu) ?? [];
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11430
|
+
function extractCjkBigrams(value) {
|
|
11431
|
+
const runs = value.normalize("NFKC").match(/[\p{Script=Han}]+/gu) ?? [];
|
|
11432
|
+
return runs.flatMap(
|
|
11433
|
+
(run) => Array.from({ length: Math.max(0, run.length - 1) }, (_, index) => run.slice(index, index + 2))
|
|
11434
|
+
);
|
|
11435
|
+
}
|
|
11436
|
+
function scoreMemoryEntry(content, normalizedQuery, queryTokens, locale) {
|
|
11437
|
+
const normalizedContent = normalizeSearchText(content);
|
|
11438
|
+
let score = 0;
|
|
11439
|
+
const matchesFullQuery = normalizedQuery.length >= AI_MEMORY_SUBSTRING_MIN_CHARS && normalizedContent.includes(normalizedQuery);
|
|
11440
|
+
const isMeaningfulReverseMatch = normalizedContent.length >= AI_MEMORY_SUBSTRING_MIN_CHARS && normalizedQuery.includes(normalizedContent);
|
|
11441
|
+
if (matchesFullQuery || isMeaningfulReverseMatch) score += 3;
|
|
11442
|
+
if (queryTokens.size === 0) return score;
|
|
11443
|
+
const contentTokens = tokenizeMemoryText(content, locale);
|
|
11444
|
+
if (contentTokens.size === 0) return score;
|
|
11445
|
+
let overlap = 0;
|
|
11446
|
+
for (const token of queryTokens) {
|
|
11447
|
+
if (contentTokens.has(token)) overlap += 1;
|
|
11448
|
+
}
|
|
11449
|
+
if (overlap === 0) return score;
|
|
11450
|
+
return score + overlap / queryTokens.size * 2 + overlap / contentTokens.size;
|
|
11451
|
+
}
|
|
11452
|
+
function normalizeScope(scope) {
|
|
11453
|
+
const scopeType = scope.scopeType;
|
|
11454
|
+
if (scopeType !== "global" && scopeType !== "self" && scopeType !== "contact" && scopeType !== "group") {
|
|
11455
|
+
throw new Error(`Unsupported AI memory scope type: ${String(scopeType)}`);
|
|
11456
|
+
}
|
|
11457
|
+
const scopeId = normalizeOptionalId(scope.scopeId);
|
|
11458
|
+
if (scopeType === "global" || scopeType === "self") {
|
|
11459
|
+
if (scopeId !== null) throw new Error(`${scopeType} AI memory scopeId must be null`);
|
|
11460
|
+
return { scopeType, scopeId: null };
|
|
11461
|
+
}
|
|
11462
|
+
if (!scopeId) throw new Error(`${scopeType} AI memory scopeId is required`);
|
|
11463
|
+
return { scopeType, scopeId };
|
|
11464
|
+
}
|
|
11465
|
+
function normalizeSourceType(sourceType) {
|
|
11466
|
+
if (sourceType !== "user" && sourceType !== "ai") {
|
|
11467
|
+
throw new Error(`Unsupported AI memory source type: ${String(sourceType)}`);
|
|
11468
|
+
}
|
|
11469
|
+
return sourceType;
|
|
11470
|
+
}
|
|
11471
|
+
function normalizeContent(content) {
|
|
11472
|
+
const normalized = typeof content === "string" ? content.trim() : "";
|
|
11473
|
+
if (!normalized || normalized.length > AI_MEMORY_CONTENT_MAX_CHARS) {
|
|
11474
|
+
throw new Error(`AI memory content must be between 1 and ${AI_MEMORY_CONTENT_MAX_CHARS} characters`);
|
|
11475
|
+
}
|
|
11476
|
+
return normalized;
|
|
11477
|
+
}
|
|
11478
|
+
function normalizeOptionalId(value) {
|
|
11479
|
+
if (typeof value !== "string") return null;
|
|
11480
|
+
const normalized = value.trim();
|
|
11481
|
+
return normalized || null;
|
|
11482
|
+
}
|
|
11483
|
+
function migrateMemorySchema(db) {
|
|
11484
|
+
const version = db.pragma("user_version", { simple: true });
|
|
11485
|
+
if (version > AI_MEMORY_SCHEMA_VERSION) {
|
|
11486
|
+
throw new Error(`Unsupported AI memory schema version: ${version}`);
|
|
11487
|
+
}
|
|
11488
|
+
if (version === AI_MEMORY_SCHEMA_VERSION) return;
|
|
11489
|
+
if (version !== 0) {
|
|
11490
|
+
throw new Error(`Unsupported AI memory schema version: ${version}`);
|
|
11491
|
+
}
|
|
11492
|
+
const migrate = db.transaction(() => {
|
|
11493
|
+
db.exec(`
|
|
11494
|
+
CREATE TABLE ai_memory (
|
|
11495
|
+
id TEXT PRIMARY KEY,
|
|
11496
|
+
scope_type TEXT NOT NULL CHECK (scope_type IN ('global', 'self', 'contact', 'group')),
|
|
11497
|
+
scope_id TEXT,
|
|
11498
|
+
content TEXT NOT NULL,
|
|
11499
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('user', 'ai')),
|
|
11500
|
+
source_ai_chat_id TEXT,
|
|
11501
|
+
source_message_id TEXT,
|
|
11502
|
+
created_at INTEGER NOT NULL,
|
|
11503
|
+
updated_at INTEGER NOT NULL,
|
|
11504
|
+
CHECK (
|
|
11505
|
+
(scope_type IN ('global', 'self') AND scope_id IS NULL) OR
|
|
11506
|
+
(scope_type IN ('contact', 'group') AND scope_id IS NOT NULL)
|
|
11507
|
+
)
|
|
11508
|
+
);
|
|
11509
|
+
`);
|
|
11510
|
+
db.exec(`
|
|
11511
|
+
CREATE INDEX idx_ai_memory_scope_updated
|
|
11512
|
+
ON ai_memory(scope_type, scope_id, updated_at DESC, id ASC);
|
|
11513
|
+
`);
|
|
11514
|
+
db.pragma(`user_version = ${AI_MEMORY_SCHEMA_VERSION}`);
|
|
11515
|
+
});
|
|
11516
|
+
migrate();
|
|
11517
|
+
appLogger.info("ai-memory", "AI memory database schema migrated", {
|
|
11518
|
+
fromVersion: version,
|
|
11519
|
+
toVersion: AI_MEMORY_SCHEMA_VERSION
|
|
11520
|
+
});
|
|
11521
|
+
}
|
|
11522
|
+
|
|
11523
|
+
// ../../packages/node-runtime/src/ai/memory-provenance.ts
|
|
11524
|
+
function listAIMemoriesWithSourceStatus(memoryService, aiChatManager, scope) {
|
|
11525
|
+
return memoryService.list(scope).map((entry) => ({
|
|
11526
|
+
...entry,
|
|
11527
|
+
sourceStatus: resolveAIMemorySourceStatus(entry, aiChatManager)
|
|
11528
|
+
}));
|
|
11529
|
+
}
|
|
11530
|
+
function linkAIMemorySources(memoryService, aiChatManager, input, options) {
|
|
11531
|
+
const aiChatId = requireId(input.aiChatId, "aiChatId");
|
|
11532
|
+
const conversation = aiChatManager.getAIChat(aiChatId);
|
|
11533
|
+
if (!conversation || conversation.kind !== "global") {
|
|
11534
|
+
throw new Error("Global AI conversation not found");
|
|
11535
|
+
}
|
|
11536
|
+
const userMessage = requireSourceMessage(aiChatManager, input.userMessageId, aiChatId, "user");
|
|
11537
|
+
const assistantMessage = requireSourceMessage(aiChatManager, input.assistantMessageId, aiChatId, "assistant");
|
|
11538
|
+
if (assistantMessage.parentId !== userMessage.id) {
|
|
11539
|
+
throw new Error("Assistant source message must directly reply to the user source message");
|
|
11540
|
+
}
|
|
11541
|
+
if (options && options.expectedParentMessageId !== void 0 && userMessage.parentId !== options.expectedParentMessageId) {
|
|
11542
|
+
throw new Error("User source message does not belong to the requested agent turn");
|
|
11543
|
+
}
|
|
11544
|
+
const memoryIds = normalizeMemoryIds(input.memoryIds);
|
|
11545
|
+
const memories = memoryIds.map((id) => ({ id, entry: memoryService.get(id) }));
|
|
11546
|
+
for (const { entry } of memories) {
|
|
11547
|
+
if (entry && entry.sourceAIChatId !== aiChatId) {
|
|
11548
|
+
throw new Error("AI memory does not belong to the requested conversation");
|
|
11549
|
+
}
|
|
11550
|
+
if (entry?.sourceMessageId) {
|
|
11551
|
+
const expectedSourceMessageId = entry.sourceType === "user" ? userMessage.id : assistantMessage.id;
|
|
11552
|
+
if (entry.sourceMessageId !== expectedSourceMessageId) {
|
|
11553
|
+
throw new Error("AI memory is already linked to another source message");
|
|
11554
|
+
}
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
const skippedMemoryIds = [];
|
|
11558
|
+
const links = [];
|
|
11559
|
+
for (const { id, entry } of memories) {
|
|
11560
|
+
if (!entry) {
|
|
11561
|
+
skippedMemoryIds.push(id);
|
|
11562
|
+
continue;
|
|
11563
|
+
}
|
|
11564
|
+
const sourceMessage = entry.sourceType === "user" ? userMessage : assistantMessage;
|
|
11565
|
+
links.push({ id, sourceAIChatId: aiChatId, sourceMessageId: sourceMessage.id });
|
|
11566
|
+
}
|
|
11567
|
+
const linkedMemoryIds = memoryService.linkSourceMessages(links);
|
|
11568
|
+
return { linkedMemoryIds, skippedMemoryIds };
|
|
11569
|
+
}
|
|
11570
|
+
function resolveAIMemorySourceStatus(entry, aiChatManager) {
|
|
11571
|
+
if (!entry.sourceAIChatId) return "none";
|
|
11572
|
+
const conversation = aiChatManager.getAIChat(entry.sourceAIChatId);
|
|
11573
|
+
if (!conversation || conversation.kind !== "global") return "unavailable";
|
|
11574
|
+
if (!entry.sourceMessageId) return "conversation";
|
|
11575
|
+
const message = aiChatManager.getMessage(entry.sourceMessageId);
|
|
11576
|
+
const expectedRole = entry.sourceType === "user" ? "user" : "assistant";
|
|
11577
|
+
return message?.aiChatId === entry.sourceAIChatId && message.role === expectedRole ? "message" : "unavailable";
|
|
11578
|
+
}
|
|
11579
|
+
function requireSourceMessage(aiChatManager, messageId, aiChatId, role) {
|
|
11580
|
+
const normalizedMessageId = requireId(messageId, `${role}MessageId`);
|
|
11581
|
+
const message = aiChatManager.getMessage(normalizedMessageId);
|
|
11582
|
+
if (!message || message.aiChatId !== aiChatId || message.role !== role) {
|
|
11583
|
+
throw new Error(`${role} source message does not belong to the requested conversation`);
|
|
11584
|
+
}
|
|
11585
|
+
return message;
|
|
11586
|
+
}
|
|
11587
|
+
function normalizeMemoryIds(memoryIds) {
|
|
11588
|
+
if (!Array.isArray(memoryIds)) throw new Error("memoryIds must be an array");
|
|
11589
|
+
return [...new Set(memoryIds.map((id) => requireId(id, "memoryId")))];
|
|
11590
|
+
}
|
|
11591
|
+
function requireId(value, field) {
|
|
11592
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
11593
|
+
if (!normalized) throw new Error(`${field} is required`);
|
|
11594
|
+
return normalized;
|
|
11595
|
+
}
|
|
11596
|
+
|
|
10048
11597
|
// ../../packages/node-runtime/src/ai/tokenizer.ts
|
|
10049
11598
|
import { Tiktoken } from "js-tiktoken/lite";
|
|
10050
11599
|
var encoder = null;
|
|
@@ -10182,21 +11731,21 @@ var SkillManagerCore = class {
|
|
|
10182
11731
|
}
|
|
10183
11732
|
// ==================== Init ====================
|
|
10184
11733
|
init() {
|
|
10185
|
-
const { fs:
|
|
10186
|
-
|
|
11734
|
+
const { fs: fs20, skillsDir } = this.deps;
|
|
11735
|
+
fs20.ensureDir(skillsDir);
|
|
10187
11736
|
this.loadAll();
|
|
10188
11737
|
this.initialized = true;
|
|
10189
11738
|
this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
|
|
10190
11739
|
return { total: this.cache.size };
|
|
10191
11740
|
}
|
|
10192
11741
|
loadAll() {
|
|
10193
|
-
const { fs:
|
|
11742
|
+
const { fs: fs20, skillsDir } = this.deps;
|
|
10194
11743
|
this.cache.clear();
|
|
10195
|
-
const files =
|
|
11744
|
+
const files = fs20.listFiles(skillsDir, ".md");
|
|
10196
11745
|
for (const file of files) {
|
|
10197
11746
|
try {
|
|
10198
|
-
const filePath =
|
|
10199
|
-
const content =
|
|
11747
|
+
const filePath = fs20.joinPath(skillsDir, file);
|
|
11748
|
+
const content = fs20.readFile(filePath);
|
|
10200
11749
|
const def = parseSkillFile(content, filePath);
|
|
10201
11750
|
if (def) {
|
|
10202
11751
|
this.cache.set(def.id, def);
|
|
@@ -10348,8 +11897,8 @@ var SkillManagerCore = class {
|
|
|
10348
11897
|
};
|
|
10349
11898
|
|
|
10350
11899
|
// ../../packages/node-runtime/src/ai/skill-manager.ts
|
|
10351
|
-
import * as
|
|
10352
|
-
import * as
|
|
11900
|
+
import * as fs11 from "fs";
|
|
11901
|
+
import * as path18 from "path";
|
|
10353
11902
|
var SKILLS_DIR_NAME = "skills";
|
|
10354
11903
|
var defaultLogger2 = {
|
|
10355
11904
|
info: () => {
|
|
@@ -10359,26 +11908,26 @@ var defaultLogger2 = {
|
|
|
10359
11908
|
};
|
|
10360
11909
|
var nodeFs = {
|
|
10361
11910
|
ensureDir(dir) {
|
|
10362
|
-
|
|
11911
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
10363
11912
|
},
|
|
10364
11913
|
listFiles(dir, ext) {
|
|
10365
|
-
if (!
|
|
10366
|
-
return
|
|
11914
|
+
if (!fs11.existsSync(dir)) return [];
|
|
11915
|
+
return fs11.readdirSync(dir).filter((file) => file.endsWith(ext));
|
|
10367
11916
|
},
|
|
10368
11917
|
readFile(filePath) {
|
|
10369
|
-
return
|
|
11918
|
+
return fs11.readFileSync(filePath, "utf-8");
|
|
10370
11919
|
},
|
|
10371
11920
|
writeFile(filePath, content) {
|
|
10372
|
-
|
|
11921
|
+
fs11.writeFileSync(filePath, content, "utf-8");
|
|
10373
11922
|
},
|
|
10374
11923
|
deleteFile(filePath) {
|
|
10375
|
-
|
|
11924
|
+
fs11.unlinkSync(filePath);
|
|
10376
11925
|
},
|
|
10377
11926
|
fileExists(filePath) {
|
|
10378
|
-
return
|
|
11927
|
+
return fs11.existsSync(filePath);
|
|
10379
11928
|
},
|
|
10380
11929
|
joinPath(...parts) {
|
|
10381
|
-
return
|
|
11930
|
+
return path18.join(...parts);
|
|
10382
11931
|
}
|
|
10383
11932
|
};
|
|
10384
11933
|
var SkillManager = class {
|
|
@@ -10388,7 +11937,7 @@ var SkillManager = class {
|
|
|
10388
11937
|
this.logger = logger ?? defaultLogger2;
|
|
10389
11938
|
this.core = new SkillManagerCore({
|
|
10390
11939
|
fs: nodeFs,
|
|
10391
|
-
skillsDir:
|
|
11940
|
+
skillsDir: path18.join(aiDataDir, SKILLS_DIR_NAME),
|
|
10392
11941
|
builtinRawSkills: [],
|
|
10393
11942
|
logger: {
|
|
10394
11943
|
info: () => {
|
|
@@ -10693,6 +12242,12 @@ var DEFAULT_CONTEXT_COMPRESSION_CONFIG = Object.freeze({
|
|
|
10693
12242
|
});
|
|
10694
12243
|
|
|
10695
12244
|
// ../../packages/node-runtime/src/ai/agent/history.ts
|
|
12245
|
+
function appendEntityRefsForModel(text, entityRefs) {
|
|
12246
|
+
if (!entityRefs?.length) return text;
|
|
12247
|
+
return `${text}
|
|
12248
|
+
|
|
12249
|
+
<chatlab_entity_refs>${JSON.stringify(entityRefs)}</chatlab_entity_refs>`;
|
|
12250
|
+
}
|
|
10696
12251
|
function createEmptyPiUsage() {
|
|
10697
12252
|
return {
|
|
10698
12253
|
input: 0,
|
|
@@ -10766,7 +12321,7 @@ function toPiHistoryMessages(messages, options) {
|
|
|
10766
12321
|
if (msg.role === "user") {
|
|
10767
12322
|
out.push({
|
|
10768
12323
|
role: "user",
|
|
10769
|
-
content: [{ type: "text", text: msg.content || "" }],
|
|
12324
|
+
content: [{ type: "text", text: appendEntityRefsForModel(msg.content || "", msg.entityRefs) }],
|
|
10770
12325
|
timestamp: Date.now()
|
|
10771
12326
|
});
|
|
10772
12327
|
continue;
|
|
@@ -10775,7 +12330,18 @@ function toPiHistoryMessages(messages, options) {
|
|
|
10775
12330
|
if (replayable) {
|
|
10776
12331
|
replayAssistantBlocks(msg.contentBlocks, out, options);
|
|
10777
12332
|
} else {
|
|
10778
|
-
out.push(
|
|
12333
|
+
out.push(
|
|
12334
|
+
makeAssistantMessage(
|
|
12335
|
+
[
|
|
12336
|
+
{
|
|
12337
|
+
type: "text",
|
|
12338
|
+
text: appendEntityRefsForModel(msg.content || "", msg.role === "summary" ? msg.entityRefs : void 0)
|
|
12339
|
+
}
|
|
12340
|
+
],
|
|
12341
|
+
"stop",
|
|
12342
|
+
options?.modelInfo
|
|
12343
|
+
)
|
|
12344
|
+
);
|
|
10779
12345
|
}
|
|
10780
12346
|
}
|
|
10781
12347
|
return out;
|
|
@@ -10824,8 +12390,9 @@ var defaultLogger3 = {
|
|
|
10824
12390
|
error: () => {
|
|
10825
12391
|
}
|
|
10826
12392
|
};
|
|
10827
|
-
async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger = defaultLogger3) {
|
|
12393
|
+
async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger = defaultLogger3, options = {}) {
|
|
10828
12394
|
try {
|
|
12395
|
+
throwIfAborted(options.signal);
|
|
10829
12396
|
const contextWindow = llmAdapter.contextWindow || DEFAULT_CONTEXT_WINDOW;
|
|
10830
12397
|
const thresholdTokens = Math.floor(contextWindow * (config.tokenThresholdPercent / 100) * 0.95);
|
|
10831
12398
|
const summary = convManager.getLatestSummary(aiChatId);
|
|
@@ -10841,10 +12408,16 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10841
12408
|
}
|
|
10842
12409
|
const historyForTokenCount = [];
|
|
10843
12410
|
if (summary) {
|
|
10844
|
-
historyForTokenCount.push({
|
|
12411
|
+
historyForTokenCount.push({
|
|
12412
|
+
role: "assistant",
|
|
12413
|
+
content: appendEntityRefsForModel(summary.content, summary.entityRefs)
|
|
12414
|
+
});
|
|
10845
12415
|
}
|
|
10846
12416
|
for (const msg of messages) {
|
|
10847
|
-
historyForTokenCount.push({
|
|
12417
|
+
historyForTokenCount.push({
|
|
12418
|
+
role: msg.role,
|
|
12419
|
+
content: appendEntityRefsForModel(msg.content, msg.entityRefs)
|
|
12420
|
+
});
|
|
10848
12421
|
for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
|
|
10849
12422
|
historyForTokenCount.push({ role: "tool", content: toolText });
|
|
10850
12423
|
}
|
|
@@ -10869,19 +12442,32 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10869
12442
|
const targetTokens = Math.min(Math.floor(contextWindow * 0.1), 16384);
|
|
10870
12443
|
const template = isProgressive ? PROGRESSIVE_COMPRESSION_PROMPT : INITIAL_COMPRESSION_PROMPT;
|
|
10871
12444
|
const prompt = template.replace("{maxTokens}", String(targetTokens)).replace("{messages}", compressInput);
|
|
10872
|
-
let summaryText = await llmAdapter.compress(prompt, targetTokens);
|
|
12445
|
+
let summaryText = await llmAdapter.compress(prompt, targetTokens, options.signal);
|
|
12446
|
+
throwIfAborted(options.signal);
|
|
10873
12447
|
if (!summaryText) {
|
|
10874
12448
|
logger.warn("Compression", "LLM compression failed, falling back to truncation");
|
|
10875
12449
|
summaryText = forceTruncate(compressInput, targetTokens);
|
|
10876
12450
|
}
|
|
10877
12451
|
const bufferBoundary = bufferMessages.length > 0 ? bufferMessages[0].timestamp : messagesToCompress[messagesToCompress.length - 1].timestamp + 1;
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
12452
|
+
const summaryEntityRefs = mergeEntityRefs(
|
|
12453
|
+
summary?.entityRefs,
|
|
12454
|
+
...messagesToCompress.map((message) => message.entityRefs)
|
|
12455
|
+
);
|
|
12456
|
+
throwIfAborted(options.signal);
|
|
12457
|
+
convManager.addSummaryMessage(
|
|
12458
|
+
aiChatId,
|
|
12459
|
+
summaryText,
|
|
12460
|
+
{
|
|
12461
|
+
bufferBoundaryTimestamp: bufferBoundary,
|
|
12462
|
+
compressedMessageCount: messagesToCompress.length
|
|
12463
|
+
},
|
|
12464
|
+
summaryEntityRefs
|
|
12465
|
+
);
|
|
12466
|
+
const afterTokenCount = [
|
|
12467
|
+
{ role: "assistant", content: appendEntityRefsForModel(summaryText, summaryEntityRefs) }
|
|
12468
|
+
];
|
|
10883
12469
|
for (const m of bufferMessages) {
|
|
10884
|
-
afterTokenCount.push({ role: m.role, content: m.content });
|
|
12470
|
+
afterTokenCount.push({ role: m.role, content: appendEntityRefsForModel(m.content, m.entityRefs) });
|
|
10885
12471
|
for (const toolText of replayedToolResultTexts(m.contentBlocks)) {
|
|
10886
12472
|
afterTokenCount.push({ role: "tool", content: toolText });
|
|
10887
12473
|
}
|
|
@@ -10909,10 +12495,19 @@ async function checkAndCompress(aiChatId, config, systemPrompt, llmAdapter, conv
|
|
|
10909
12495
|
summaryContent: summaryText
|
|
10910
12496
|
};
|
|
10911
12497
|
} catch (error) {
|
|
12498
|
+
if (options.signal?.aborted) {
|
|
12499
|
+
return { compressed: false, reason: "error", error: "Compression aborted" };
|
|
12500
|
+
}
|
|
10912
12501
|
logger.error("Compression", "Compression failed", { error: String(error) });
|
|
10913
12502
|
return { compressed: false, reason: "error", error: String(error) };
|
|
10914
12503
|
}
|
|
10915
12504
|
}
|
|
12505
|
+
function throwIfAborted(signal) {
|
|
12506
|
+
if (!signal?.aborted) return;
|
|
12507
|
+
const error = new Error("Compression aborted");
|
|
12508
|
+
error.name = "AbortError";
|
|
12509
|
+
throw error;
|
|
12510
|
+
}
|
|
10916
12511
|
async function manualCompress(aiChatId, config, systemPrompt, llmAdapter, convManager, logger) {
|
|
10917
12512
|
const messageCount = convManager.getMessageCountAfterSummary(aiChatId);
|
|
10918
12513
|
if (messageCount < 5) {
|
|
@@ -10926,7 +12521,7 @@ function replayedToolResultTexts(blocks) {
|
|
|
10926
12521
|
return blocks.filter(isReplayableToolBlock).map((block) => truncateToolResultText(block.tool.result));
|
|
10927
12522
|
}
|
|
10928
12523
|
function countMessageTokensWithTools(msg) {
|
|
10929
|
-
let tokens = countTokens(msg.content) + 4;
|
|
12524
|
+
let tokens = countTokens(appendEntityRefsForModel(msg.content, msg.entityRefs)) + 4;
|
|
10930
12525
|
for (const toolText of replayedToolResultTexts(msg.contentBlocks)) {
|
|
10931
12526
|
tokens += countTokens(toolText) + 4;
|
|
10932
12527
|
}
|
|
@@ -10946,6 +12541,10 @@ function splitMessagesForCompression(messages, bufferTokenBudget) {
|
|
|
10946
12541
|
splitIndex = 0;
|
|
10947
12542
|
}
|
|
10948
12543
|
}
|
|
12544
|
+
if (splitIndex > 0) {
|
|
12545
|
+
const boundaryTimestamp = messages[Math.min(splitIndex, messages.length - 1)].timestamp;
|
|
12546
|
+
while (splitIndex > 0 && messages[splitIndex - 1].timestamp === boundaryTimestamp) splitIndex--;
|
|
12547
|
+
}
|
|
10949
12548
|
return {
|
|
10950
12549
|
bufferMessages: messages.slice(splitIndex),
|
|
10951
12550
|
messagesToCompress: messages.slice(0, splitIndex)
|
|
@@ -10954,14 +12553,16 @@ function splitMessagesForCompression(messages, bufferTokenBudget) {
|
|
|
10954
12553
|
function buildCompressionInput(messagesToCompress, existingSummary) {
|
|
10955
12554
|
const parts = [];
|
|
10956
12555
|
if (existingSummary) {
|
|
10957
|
-
parts.push(
|
|
10958
|
-
|
|
10959
|
-
|
|
12556
|
+
parts.push(
|
|
12557
|
+
`[PREVIOUS SUMMARY \u2014 MUST PRESERVE]
|
|
12558
|
+
${appendEntityRefsForModel(existingSummary.content, existingSummary.entityRefs)}
|
|
12559
|
+
`
|
|
12560
|
+
);
|
|
10960
12561
|
parts.push(`[NEW MESSAGES \u2014 SUMMARIZE AND MERGE]`);
|
|
10961
12562
|
}
|
|
10962
12563
|
for (const msg of messagesToCompress) {
|
|
10963
12564
|
const roleLabel = msg.role === "user" ? "User" : "Assistant";
|
|
10964
|
-
parts.push(`${roleLabel}: ${msg.content}`);
|
|
12565
|
+
parts.push(`${roleLabel}: ${appendEntityRefsForModel(msg.content, msg.entityRefs)}`);
|
|
10965
12566
|
for (const block of (msg.contentBlocks ?? []).filter(isReplayableToolBlock)) {
|
|
10966
12567
|
parts.push(`[Tool result: ${block.tool.name}]
|
|
10967
12568
|
${truncateToolResultText(block.tool.result)}`);
|
|
@@ -10969,6 +12570,16 @@ ${truncateToolResultText(block.tool.result)}`);
|
|
|
10969
12570
|
}
|
|
10970
12571
|
return parts.join("\n\n");
|
|
10971
12572
|
}
|
|
12573
|
+
function mergeEntityRefs(...groups) {
|
|
12574
|
+
const refs = /* @__PURE__ */ new Map();
|
|
12575
|
+
for (const group of groups) {
|
|
12576
|
+
for (const ref of group ?? []) {
|
|
12577
|
+
const key = ref.type === "contact" ? `contact:${ref.contactKey}` : `session:${ref.sessionId}`;
|
|
12578
|
+
refs.set(key, ref);
|
|
12579
|
+
}
|
|
12580
|
+
}
|
|
12581
|
+
return refs.size > 0 ? [...refs.values()] : void 0;
|
|
12582
|
+
}
|
|
10972
12583
|
function forceTruncate(input, targetTokens) {
|
|
10973
12584
|
const lines = input.split("\n");
|
|
10974
12585
|
const result = [];
|
|
@@ -11015,7 +12626,7 @@ function createCompressionLlmAdapter(options) {
|
|
|
11015
12626
|
const contextWindow = options.contextWindow ?? piModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW2;
|
|
11016
12627
|
return {
|
|
11017
12628
|
contextWindow,
|
|
11018
|
-
compress: async (prompt, maxTokens) => {
|
|
12629
|
+
compress: async (prompt, maxTokens, signal) => {
|
|
11019
12630
|
onCompressing?.();
|
|
11020
12631
|
try {
|
|
11021
12632
|
const result = await completeSimple(
|
|
@@ -11024,11 +12635,12 @@ function createCompressionLlmAdapter(options) {
|
|
|
11024
12635
|
systemPrompt: void 0,
|
|
11025
12636
|
messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }]
|
|
11026
12637
|
},
|
|
11027
|
-
{ apiKey, maxTokens }
|
|
12638
|
+
{ apiKey, maxTokens, signal }
|
|
11028
12639
|
);
|
|
11029
12640
|
const text = result.content.filter((item) => item.type === "text").map((item) => item.text).join("");
|
|
11030
12641
|
return text || null;
|
|
11031
12642
|
} catch (error) {
|
|
12643
|
+
if (signal?.aborted) return null;
|
|
11032
12644
|
onError?.(error);
|
|
11033
12645
|
return null;
|
|
11034
12646
|
}
|
|
@@ -12578,287 +14190,6 @@ function buildSemanticSearchGuidance(locale) {
|
|
|
12578
14190
|
].join("");
|
|
12579
14191
|
}
|
|
12580
14192
|
|
|
12581
|
-
// ../../packages/node-runtime/src/ai/agent/event-handler.ts
|
|
12582
|
-
function estimateTokensFromText(text) {
|
|
12583
|
-
if (!text) return 0;
|
|
12584
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
12585
|
-
if (!normalized) return 0;
|
|
12586
|
-
const cjkCount = (normalized.match(/[\u3400-\u9fff\uf900-\ufaff]/g) || []).length;
|
|
12587
|
-
const latinCount = normalized.length - cjkCount;
|
|
12588
|
-
return Math.max(1, Math.ceil(cjkCount * 1.15 + latinCount / 4));
|
|
12589
|
-
}
|
|
12590
|
-
function extractMessageText(message) {
|
|
12591
|
-
if (message.role === "user") {
|
|
12592
|
-
if (typeof message.content === "string") return message.content;
|
|
12593
|
-
return message.content.map((item) => {
|
|
12594
|
-
if (item.type === "text") return item.text;
|
|
12595
|
-
if (item.type === "image") return "[image]";
|
|
12596
|
-
return "";
|
|
12597
|
-
}).join("\n");
|
|
12598
|
-
}
|
|
12599
|
-
if (message.role === "assistant") {
|
|
12600
|
-
return message.content.map((item) => {
|
|
12601
|
-
if (item.type === "text") return item.text;
|
|
12602
|
-
if (item.type === "thinking") return item.thinking;
|
|
12603
|
-
if (item.type === "toolCall") return `${item.name} ${JSON.stringify(item.arguments || {})}`;
|
|
12604
|
-
return "";
|
|
12605
|
-
}).join("\n");
|
|
12606
|
-
}
|
|
12607
|
-
if (message.role === "toolResult") {
|
|
12608
|
-
return message.content.map((item) => {
|
|
12609
|
-
if (item.type === "text") return item.text;
|
|
12610
|
-
return "[binary]";
|
|
12611
|
-
}).join("\n");
|
|
12612
|
-
}
|
|
12613
|
-
return "";
|
|
12614
|
-
}
|
|
12615
|
-
function estimateContextTokens(systemPrompt, messages, pendingUserMessage) {
|
|
12616
|
-
let tokens = estimateTokensFromText(systemPrompt);
|
|
12617
|
-
for (const message of messages) {
|
|
12618
|
-
if (message.role === "toolResult") continue;
|
|
12619
|
-
tokens += estimateTokensFromText(extractMessageText(message));
|
|
12620
|
-
}
|
|
12621
|
-
if (pendingUserMessage) {
|
|
12622
|
-
tokens += estimateTokensFromText(pendingUserMessage);
|
|
12623
|
-
}
|
|
12624
|
-
return tokens;
|
|
12625
|
-
}
|
|
12626
|
-
var AgentEventHandler = class {
|
|
12627
|
-
toolsUsed = [];
|
|
12628
|
-
toolRounds = 0;
|
|
12629
|
-
totalUsage = {
|
|
12630
|
-
promptTokens: 0,
|
|
12631
|
-
completionTokens: 0,
|
|
12632
|
-
totalTokens: 0,
|
|
12633
|
-
cacheReadTokens: 0,
|
|
12634
|
-
cacheWriteTokens: 0
|
|
12635
|
-
};
|
|
12636
|
-
lastStatusAt = 0;
|
|
12637
|
-
onChunk;
|
|
12638
|
-
context;
|
|
12639
|
-
systemPrompt;
|
|
12640
|
-
activeTools = /* @__PURE__ */ new Map();
|
|
12641
|
-
constructor(config) {
|
|
12642
|
-
this.onChunk = config.onChunk;
|
|
12643
|
-
this.context = config.context;
|
|
12644
|
-
this.systemPrompt = config.systemPrompt;
|
|
12645
|
-
}
|
|
12646
|
-
handleCoreEvent(event, messages) {
|
|
12647
|
-
switch (event.type) {
|
|
12648
|
-
case "content":
|
|
12649
|
-
this.onChunk({ type: "content", content: event.content });
|
|
12650
|
-
this.emitStatus("responding", messages);
|
|
12651
|
-
break;
|
|
12652
|
-
case "thinking_start":
|
|
12653
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12654
|
-
break;
|
|
12655
|
-
case "thinking_delta":
|
|
12656
|
-
this.onChunk({ type: "think", content: event.content, thinkTag: "thinking" });
|
|
12657
|
-
this.emitStatus("thinking", messages);
|
|
12658
|
-
break;
|
|
12659
|
-
case "thinking_end":
|
|
12660
|
-
this.onChunk({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: event.durationMs });
|
|
12661
|
-
this.emitStatus("responding", messages, { force: true });
|
|
12662
|
-
break;
|
|
12663
|
-
case "tool_start": {
|
|
12664
|
-
const params = this.normalizeToolParams(event.toolName, event.toolParams);
|
|
12665
|
-
this.toolsUsed.push(event.toolName);
|
|
12666
|
-
this.activeTools.set(event.toolCallId, event.toolName);
|
|
12667
|
-
this.onChunk({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params });
|
|
12668
|
-
this.emitStatus("tool_running", messages, { currentTool: event.toolName, force: true });
|
|
12669
|
-
break;
|
|
12670
|
-
}
|
|
12671
|
-
case "tool_update":
|
|
12672
|
-
this.onChunk({
|
|
12673
|
-
type: "tool_update",
|
|
12674
|
-
toolCallId: event.toolCallId,
|
|
12675
|
-
toolName: event.toolName,
|
|
12676
|
-
toolProgress: event.progress
|
|
12677
|
-
});
|
|
12678
|
-
break;
|
|
12679
|
-
case "tool_end":
|
|
12680
|
-
this.activeTools.delete(event.toolCallId);
|
|
12681
|
-
this.onChunk({
|
|
12682
|
-
type: "tool_result",
|
|
12683
|
-
toolCallId: event.toolCallId,
|
|
12684
|
-
toolName: event.toolName,
|
|
12685
|
-
toolResult: event.toolResult,
|
|
12686
|
-
toolIsError: event.isError
|
|
12687
|
-
});
|
|
12688
|
-
if (this.activeTools.size > 0) {
|
|
12689
|
-
const currentTool = Array.from(this.activeTools.values()).at(-1);
|
|
12690
|
-
this.emitStatus("tool_running", messages, { currentTool, force: true });
|
|
12691
|
-
} else {
|
|
12692
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12693
|
-
}
|
|
12694
|
-
break;
|
|
12695
|
-
case "turn_end":
|
|
12696
|
-
this.activeTools.clear();
|
|
12697
|
-
this.toolRounds = event.round;
|
|
12698
|
-
this.emitStatus("thinking", messages, { force: true });
|
|
12699
|
-
break;
|
|
12700
|
-
case "usage_update":
|
|
12701
|
-
this.totalUsage = { ...event.usage };
|
|
12702
|
-
this.emitStatus("responding", messages, { force: true });
|
|
12703
|
-
break;
|
|
12704
|
-
}
|
|
12705
|
-
}
|
|
12706
|
-
cloneUsage() {
|
|
12707
|
-
return {
|
|
12708
|
-
promptTokens: this.totalUsage.promptTokens,
|
|
12709
|
-
completionTokens: this.totalUsage.completionTokens,
|
|
12710
|
-
totalTokens: this.totalUsage.totalTokens,
|
|
12711
|
-
cacheReadTokens: this.totalUsage.cacheReadTokens,
|
|
12712
|
-
cacheWriteTokens: this.totalUsage.cacheWriteTokens
|
|
12713
|
-
};
|
|
12714
|
-
}
|
|
12715
|
-
emitStatus(phase, messages, options) {
|
|
12716
|
-
const now = Date.now();
|
|
12717
|
-
if (!options?.force && now - this.lastStatusAt < 240) return;
|
|
12718
|
-
this.lastStatusAt = now;
|
|
12719
|
-
const contextTokens = estimateContextTokens(this.systemPrompt, messages, options?.pendingUserMessage);
|
|
12720
|
-
const status = {
|
|
12721
|
-
phase,
|
|
12722
|
-
round: this.toolRounds,
|
|
12723
|
-
toolsUsed: this.toolsUsed.length,
|
|
12724
|
-
currentTool: options?.currentTool,
|
|
12725
|
-
contextTokens,
|
|
12726
|
-
totalUsage: this.cloneUsage(),
|
|
12727
|
-
updatedAt: now
|
|
12728
|
-
};
|
|
12729
|
-
this.onChunk({ type: "status", status });
|
|
12730
|
-
}
|
|
12731
|
-
normalizeToolParams(toolName, params) {
|
|
12732
|
-
const normalized = { ...params };
|
|
12733
|
-
const toolsWithLimit = ["search_messages", "get_recent_messages", "get_conversation_between"];
|
|
12734
|
-
if (this.context.maxMessagesLimit && toolsWithLimit.includes(toolName)) {
|
|
12735
|
-
normalized.limit = this.context.maxMessagesLimit;
|
|
12736
|
-
}
|
|
12737
|
-
if (this.context.timeFilter && (toolName === "search_messages" || toolName === "get_recent_messages")) {
|
|
12738
|
-
normalized._timeFilter = this.context.timeFilter;
|
|
12739
|
-
}
|
|
12740
|
-
return normalized;
|
|
12741
|
-
}
|
|
12742
|
-
};
|
|
12743
|
-
|
|
12744
|
-
// ../../packages/node-runtime/src/ai/agent/prompt-builder.ts
|
|
12745
|
-
function agentT(t2, key, locale, options) {
|
|
12746
|
-
return t2(key, { lng: locale, ...options });
|
|
12747
|
-
}
|
|
12748
|
-
function formatNullableTimestamp2(timestamp) {
|
|
12749
|
-
return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
|
|
12750
|
-
}
|
|
12751
|
-
function formatSharePercent2(share) {
|
|
12752
|
-
if (!Number.isFinite(share)) return "0%";
|
|
12753
|
-
const rounded = Math.round(share * 10) / 10;
|
|
12754
|
-
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
|
|
12755
|
-
}
|
|
12756
|
-
function normalizeInlineText(text) {
|
|
12757
|
-
return text.replace(/\s+/g, " ").trim();
|
|
12758
|
-
}
|
|
12759
|
-
function formatDataSnapshotContext(t2, dataSnapshot, locale) {
|
|
12760
|
-
const memberHints = dataSnapshot.activeMemberHints ?? [];
|
|
12761
|
-
const memberHintTitle = memberHints.length === 0 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsUnavailable", locale) : dataSnapshot.totalMembers <= 10 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsAll", locale) : agentT(t2, "ai.agent.dataSnapshotMemberHintsTop", locale);
|
|
12762
|
-
const memberHintLines = memberHints.length > 0 ? memberHints.map((member, index) => {
|
|
12763
|
-
return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent2(member.share)}`;
|
|
12764
|
-
}).join("\n") : agentT(t2, "ai.agent.dataSnapshotMemberHintsEmpty", locale);
|
|
12765
|
-
return agentT(t2, "ai.agent.dataSnapshotContext", locale, {
|
|
12766
|
-
name: dataSnapshot.name,
|
|
12767
|
-
platform: dataSnapshot.platform,
|
|
12768
|
-
type: dataSnapshot.type,
|
|
12769
|
-
totalMessages: dataSnapshot.totalMessages,
|
|
12770
|
-
totalMembers: dataSnapshot.totalMembers,
|
|
12771
|
-
firstMessageTs: formatNullableTimestamp2(dataSnapshot.firstMessageTs),
|
|
12772
|
-
firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
|
12773
|
-
lastMessageTs: formatNullableTimestamp2(dataSnapshot.lastMessageTs),
|
|
12774
|
-
lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
|
12775
|
-
segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
|
|
12776
|
-
memberHintTitle,
|
|
12777
|
-
memberHintLines,
|
|
12778
|
-
usageRules: agentT(t2, "ai.agent.dataSnapshotUsageRules", locale)
|
|
12779
|
-
});
|
|
12780
|
-
}
|
|
12781
|
-
function getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot) {
|
|
12782
|
-
const now = /* @__PURE__ */ new Date();
|
|
12783
|
-
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
12784
|
-
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
12785
|
-
year: "numeric",
|
|
12786
|
-
month: "long",
|
|
12787
|
-
day: "numeric",
|
|
12788
|
-
weekday: "long"
|
|
12789
|
-
});
|
|
12790
|
-
const isPrivate = chatType === "private";
|
|
12791
|
-
const chatContext = agentT(t2, `ai.agent.chatContext.${chatType}`, locale);
|
|
12792
|
-
const ownerNote = ownerInfo ? agentT(t2, "ai.agent.ownerNote", locale, {
|
|
12793
|
-
displayName: ownerInfo.displayName,
|
|
12794
|
-
platformId: ownerInfo.platformId,
|
|
12795
|
-
chatContext
|
|
12796
|
-
}) : "";
|
|
12797
|
-
const memberNote = isPrivate ? agentT(t2, "ai.agent.memberNotePrivate", locale) : agentT(t2, "ai.agent.memberNoteGroup", locale);
|
|
12798
|
-
const mentionedMembersNote = mentionedMembers && mentionedMembers.length > 0 ? `${agentT(t2, "ai.agent.mentionedMembersNote", locale)}
|
|
12799
|
-
${mentionedMembers.map((member) => {
|
|
12800
|
-
const aliasPart = member.aliases.length > 0 ? ` | aliases=${member.aliases.join(",")}` : "";
|
|
12801
|
-
return `- member_id=${member.memberId} | mention=${member.mentionText} | display_name=${member.displayName} | platform_id=${member.platformId}${aliasPart}`;
|
|
12802
|
-
}).join("\n")}
|
|
12803
|
-
` : "";
|
|
12804
|
-
const year = now.getFullYear();
|
|
12805
|
-
const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t2, dataSnapshot, locale)}
|
|
12806
|
-
` : "";
|
|
12807
|
-
return `${agentT(t2, "ai.agent.currentDateIs", locale)} ${currentDate}\u3002
|
|
12808
|
-
${ownerNote}
|
|
12809
|
-
${mentionedMembersNote}
|
|
12810
|
-
${dataSnapshotNote}
|
|
12811
|
-
${memberNote}
|
|
12812
|
-
${agentT(t2, "ai.agent.timeParamsIntro", locale)}
|
|
12813
|
-
${agentT(t2, "ai.agent.defaultYearNote", locale, { year })}
|
|
12814
|
-
${agentT(t2, "ai.agent.evidencePolicy", locale)}
|
|
12815
|
-
|
|
12816
|
-
${agentT(t2, "ai.agent.responseInstruction", locale)}`;
|
|
12817
|
-
}
|
|
12818
|
-
function getFallbackRoleDefinition(t2, chatType, locale) {
|
|
12819
|
-
return agentT(t2, `ai.agent.fallbackRoleDefinition.${chatType}`, locale);
|
|
12820
|
-
}
|
|
12821
|
-
function formatTimestamp(timestamp, locale) {
|
|
12822
|
-
if (!timestamp) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
12823
|
-
const date = new Date(timestamp * 1e3);
|
|
12824
|
-
if (Number.isNaN(date.getTime())) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
12825
|
-
const dateLocale = locale.startsWith("zh") ? "zh-CN" : locale;
|
|
12826
|
-
return date.toLocaleString(dateLocale, {
|
|
12827
|
-
year: "numeric",
|
|
12828
|
-
month: "2-digit",
|
|
12829
|
-
day: "2-digit",
|
|
12830
|
-
hour: "2-digit",
|
|
12831
|
-
minute: "2-digit"
|
|
12832
|
-
});
|
|
12833
|
-
}
|
|
12834
|
-
function buildSystemPrompt(options) {
|
|
12835
|
-
const {
|
|
12836
|
-
t: t2,
|
|
12837
|
-
chatType = "group",
|
|
12838
|
-
assistantSystemPrompt,
|
|
12839
|
-
ownerInfo,
|
|
12840
|
-
locale = "zh-CN",
|
|
12841
|
-
skillCtx,
|
|
12842
|
-
mentionedMembers,
|
|
12843
|
-
dataSnapshot
|
|
12844
|
-
} = options;
|
|
12845
|
-
const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t2, chatType, locale);
|
|
12846
|
-
const lockedSection = getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot);
|
|
12847
|
-
let skillSection = "";
|
|
12848
|
-
if (skillCtx?.skillDef) {
|
|
12849
|
-
skillSection = `
|
|
12850
|
-
## ${agentT(t2, "ai.agent.currentTask", locale)}\uFF1A${skillCtx.skillDef.name}
|
|
12851
|
-
${agentT(t2, "ai.agent.skillPriorityNote", locale)}
|
|
12852
|
-
` + skillCtx.skillDef.prompt;
|
|
12853
|
-
} else if (skillCtx?.skillMenu) {
|
|
12854
|
-
skillSection = `
|
|
12855
|
-
${skillCtx.skillMenu}`;
|
|
12856
|
-
}
|
|
12857
|
-
return `${systemPrompt}${skillSection}
|
|
12858
|
-
|
|
12859
|
-
${lockedSection}`;
|
|
12860
|
-
}
|
|
12861
|
-
|
|
12862
14193
|
// ../../packages/node-runtime/src/ai/i18n/locales/zh-CN.ts
|
|
12863
14194
|
var zh_CN_default = {
|
|
12864
14195
|
ai: {
|
|
@@ -14226,113 +15557,684 @@ var zh_TW_default = {
|
|
|
14226
15557
|
- last_message_time: {{lastMessageTime}} (\u8CC7\u6599\u5EAB\u4E2D\u5DF2\u532F\u5165\u8A0A\u606F\u7684\u622A\u6B62\u6642\u9593\uFF0C\u4E0D\u4EE3\u8868\u7FA4\u7D44/\u5C0D\u8A71\u7576\u524D\u662F\u5426\u6D3B\u8E8D)
|
|
14227
15558
|
- segment_summaries_available: {{segmentSummaryCount}}
|
|
14228
15559
|
|
|
14229
|
-
{{memberHintTitle}}
|
|
14230
|
-
{{memberHintLines}}
|
|
15560
|
+
{{memberHintTitle}}
|
|
15561
|
+
{{memberHintLines}}
|
|
15562
|
+
|
|
15563
|
+
\u4F7F\u7528\u898F\u5247\uFF1A
|
|
15564
|
+
{{usageRules}}`,
|
|
15565
|
+
dataSnapshotMemberHintsAll: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF08\u5168\u90E8\u6210\u54E1\uFF09\uFF1A",
|
|
15566
|
+
dataSnapshotMemberHintsTop: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF08\u6309\u6B77\u53F2\u7E3D\u8A0A\u606F\u91CF Top 10\uFF09\uFF1A",
|
|
15567
|
+
dataSnapshotMemberHintsUnavailable: "\u6D3B\u8E8D\u6210\u54E1\u67E5\u8A62\u63D0\u793A\uFF1A",
|
|
15568
|
+
dataSnapshotMemberHintsEmpty: "\u7121\u53EF\u7528\u6210\u54E1\u63D0\u793A\u3002",
|
|
15569
|
+
dataSnapshotUsageRules: `- member_id \u662F\u5DE5\u5177\u67E5\u8A62\u63D0\u793A\uFF1Bdisplay_name \u50C5\u7528\u65BC\u4EBA\u985E\u8B58\u5225\uFF0C\u53EF\u80FD\u4E0D\u552F\u4E00\u3002
|
|
15570
|
+
- \u4E0D\u8981\u5728\u6700\u7D42\u56DE\u7B54\u4E2D\u4E3B\u52D5\u66B4\u9732 member_id \u6216\u555F\u52D5\u4E0A\u4E0B\u6587\u672C\u8EAB\uFF0C\u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\u6280\u8853\u7D30\u7BC0\u3002
|
|
15571
|
+
- \u6D3B\u8E8D\u6210\u54E1\u6392\u884C\u53EA\u4EE3\u8868\u6B77\u53F2\u7E3D\u8A0A\u606F\u91CF\uFF0C\u4E0D\u4EE3\u8868\u8FD1\u671F\u6D3B\u8E8D\u60C5\u6CC1\uFF1B\u4E5F\u4E0D\u8DB3\u4EE5\u4F5C\u70BA\u5F71\u97FF\u529B\u3001\u95DC\u4FC2\u6216\u8FD1\u671F\u8DA8\u52E2\u7D50\u8AD6\u7684\u8B49\u64DA\u3002
|
|
15572
|
+
- \u76F8\u5C0D\u6642\u9593\u8868\u9054\u4EE5\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u70BA\u57FA\u6E96\uFF0C\u800C\u4E0D\u662F\u8CC7\u6599\u5EAB\u6700\u5F8C\u8A0A\u606F\u6642\u9593\u3002
|
|
15573
|
+
-\u300C\u6700\u8FD1\u4E00\u5E74/\u904E\u53BB\u4E00\u5E74\u300D\u8868\u793A\u5F9E\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u56DE\u63A8\u4E00\u5E74\u5230\u4ECA\u5929\uFF1B\u300C\u53BB\u5E74\u300D\u8868\u793A\u4E0A\u4E00\u81EA\u7136\u5E74\u3002
|
|
15574
|
+
- \u8CC7\u6599\u5EAB\u6642\u9593\u908A\u754C\u53EA\u7528\u65BC\u8AAA\u660E\u8986\u84CB\u7BC4\u570D\uFF0C\u4E0D\u7528\u65BC\u91CD\u5B9A\u7FA9\u4F7F\u7528\u8005\u8981\u6C42\u7684\u6642\u9593\u7BC4\u570D\u3002
|
|
15575
|
+
- \u4F7F\u7528\u9810\u8A2D\u300C\u8FD1 N \u5929\u300D\u7684\u5DE5\u5177\u6642\uFF0C\u5148\u9078\u64C7\u8207\u8CC7\u6599\u5EAB\u6642\u9593\u908A\u754C\u6709\u4EA4\u96C6\u7684\u7BC4\u570D\uFF0C\u4E0D\u8981\u5148\u63A2\u6E2C\u771F\u5BE6\u76EE\u524D\u65E5\u671F\u8996\u7A97\u5C0E\u81F4\u7A7A\u7D50\u679C\u3002
|
|
15576
|
+
- \u4E0D\u8981\u53EA\u70BA\u4E86\u91CD\u65B0\u767C\u73FE min/max timestamp \u547C\u53EB\u5DE5\u5177\uFF1B\u4F46\u56DE\u7B54\u5177\u9AD4\u804A\u5929\u4E8B\u5BE6\u3001\u7D71\u8A08\u548C\u7D50\u8AD6\u4ECD\u5FC5\u9808\u547C\u53EB\u5DE5\u5177\u53D6\u5F97\u8B49\u64DA\u3002
|
|
15577
|
+
- last_message_time \u662F\u8CC7\u6599\u5EAB\u4E2D\u5DF2\u532F\u5165\u8A0A\u606F\u7684\u622A\u6B62\u6642\u9593\uFF0C\u4E0D\u662F\u7FA4\u7D44/\u5C0D\u8A71\u5728\u73FE\u5BE6\u4E2D\u6700\u5F8C\u4E00\u6B21\u767C\u8A00\u7684\u6642\u9593\uFF1B\u4F7F\u7528\u8005\u53EF\u80FD\u53EA\u662F\u5C1A\u672A\u532F\u5165\u66F4\u65B0\u7684\u7D00\u9304\u3002\u4E0D\u8981\u64DA\u6B64\u63A8\u65B7\u7FA4\u7D44\u300C\u591A\u4E45\u6C92\u52D5\u975C\u300D\uFF0C\u66F4\u4E0D\u8981\u4E3B\u52D5\u5EFA\u8B70\u4F7F\u7528\u8005\u53BB\u300C\u559A\u9192\u300D\u6216\u300C\u6FC0\u6D3B\u300D\u7FA4\u7D44\u3002`,
|
|
15578
|
+
evidencePolicy: `\u8B49\u64DA\u7B56\u7565\uFF1A
|
|
15579
|
+
- AI \u5C0D\u8A71\u6B77\u53F2\u3001\u6B77\u53F2 AI \u56DE\u8986\u548C\u58D3\u7E2E\u6458\u8981\u53EA\u7528\u65BC\u7406\u89E3\u4F7F\u7528\u8005\u610F\u5716\uFF0C\u4E0D\u80FD\u4F5C\u70BA\u804A\u5929\u7D00\u9304\u4E8B\u5BE6\u8B49\u64DA\u3002
|
|
15580
|
+
- \u53EA\u8981\u4F7F\u7528\u8005\u8A62\u554F\u804A\u5929\u7D00\u9304\u5167\u5BB9\u3001\u6700\u8FD1\u804A\u4EC0\u9EBC\u3001\u67D0\u4EBA\u8AAA\u904E\u4EC0\u9EBC\u3001\u7D71\u8A08\u6392\u884C\u3001\u662F\u5426\u51FA\u73FE\u904E\u67D0\u8A71\u984C\u6216\u8981\u6C42\u5F15\u7528\u539F\u8A71\uFF0C\u5FC5\u9808\u5148\u547C\u53EB\u5408\u9069\u7684\u8CC7\u6599\u5DE5\u5177\u6AA2\u7D22\u76EE\u524D\u8CC7\u6599\u5EAB\u3002
|
|
15581
|
+
- \u5982\u679C\u5DF2\u8FFD\u52A0\u65B0\u804A\u5929\u7D00\u9304\uFF0C\u76EE\u524D\u8CC7\u6599\u5EAB\u53EF\u80FD\u4E0D\u540C\u65BC\u820A\u8996\u7A97\u6B77\u53F2\uFF1B\u512A\u5148\u4F7F\u7528 get_chat_overview \u6216 get_recent_messages/search_messages \u53D6\u5F97\u6700\u65B0\u8B49\u64DA\u3002
|
|
15582
|
+
- \u53EA\u6709\u5DE5\u5177\u56DE\u50B3\u7684\u8A0A\u606F\u3001\u7D71\u8A08\u6216\u8CC7\u6599\u5EAB\u6982\u89BD\u53EF\u4EE5\u4F5C\u70BA\u4E8B\u5BE6\u4F9D\u64DA\uFF1B\u5DE5\u5177\u6C92\u6709\u56DE\u50B3\u8DB3\u5920\u8B49\u64DA\u6642\uFF0C\u660E\u78BA\u8AAA\u660E\u8CC7\u6599\u4E0D\u8DB3\uFF0C\u4E0D\u8981\u7DE8\u9020\u4E0D\u5B58\u5728\u7684\u804A\u5929\u7D00\u9304\u3002`,
|
|
15583
|
+
responseInstruction: "\u6839\u64DA\u4F7F\u7528\u8005\u7684\u554F\u984C\uFF0C\u5728\u9700\u8981\u6642\u9078\u64C7\u5408\u9069\u7684\u5DE5\u5177\u53D6\u5F97\u8CC7\u6599\uFF0C\u7136\u5F8C\u81EA\u7136\u3001\u76F4\u63A5\u5730\u7D66\u51FA\u56DE\u7B54\u3002",
|
|
15584
|
+
fallbackRoleDefinition: {
|
|
15585
|
+
group: `\u4F60\u662F ChatLab \u88E1\u7684\u7FA4\u804A\u8A18\u9304\u5206\u6790\u642D\u6A94\uFF0C\u5E6B\u52A9\u4F7F\u7528\u8005\u5F9E\u804A\u5929\u8A18\u9304\u4E2D\u7406\u89E3\u4E8B\u5BE6\u3001\u8B8A\u5316\u548C\u4E92\u52D5\u6A21\u5F0F\uFF0C\u800C\u4E0D\u662F\u6A5F\u68B0\u5730\u7522\u751F\u5206\u6790\u5831\u544A\u3002
|
|
15586
|
+
|
|
15587
|
+
## \u4EA4\u6D41\u65B9\u5F0F
|
|
15588
|
+
- \u8DDF\u96A8\u4F7F\u7528\u8005\u7684\u8A9E\u8A00\u548C\u4EA4\u6D41\u65B9\u5F0F\uFF0C\u8AAA\u81EA\u7136\u3001\u76F4\u63A5\u7684\u4EBA\u8A71\uFF0C\u907F\u514D\u5BA2\u670D\u8154\u548C\u904E\u5EA6\u6B63\u5F0F\u7684\u8868\u9054\u3002
|
|
15589
|
+
- \u7C21\u55AE\u554F\u984C\u76F4\u63A5\u56DE\u7B54\uFF1B\u8907\u96DC\u554F\u984C\u518D\u5206\u5C64\u8AAA\u660E\u3002\u6A19\u984C\u3001\u5217\u8868\u548C\u8868\u683C\u53EA\u5728\u6709\u52A9\u65BC\u7406\u89E3\u6642\u4F7F\u7528\u3002
|
|
15590
|
+
- \u5728\u8B49\u64DA\u5141\u8A31\u6642\u7D66\u51FA\u660E\u78BA\u5224\u65B7\uFF1B\u7121\u6CD5\u78BA\u8A8D\u7684\u90E8\u5206\u8981\u5766\u7387\u8AAA\u660E\u3002
|
|
15591
|
+
- \u53EF\u4EE5\u6709\u4E00\u9EDE\u81EA\u7136\u7684\u5E7D\u9ED8\u611F\uFF0C\u4F46\u4E0D\u8981\u5F37\u884C\u73A9\u6897\u3001\u5806\u8868\u60C5\u6216\u72A7\u7272\u6E96\u78BA\u6027\u3002
|
|
15592
|
+
|
|
15593
|
+
## \u5206\u6790\u908A\u754C
|
|
15594
|
+
- \u5340\u5206\u80FD\u5920\u78BA\u8A8D\u7684\u4E8B\u5BE6\u3001\u57FA\u65BC\u8B49\u64DA\u7684\u63A8\u8AD6\uFF0C\u4EE5\u53CA\u66AB\u6642\u7121\u6CD5\u5224\u65B7\u7684\u90E8\u5206\u3002
|
|
15595
|
+
- \u6D89\u53CA\u6027\u683C\u3001\u60C5\u7DD2\u3001\u95DC\u4FC2\u548C\u52D5\u6A5F\u6642\u5C24\u5176\u8B39\u614E\uFF0C\u4E0D\u7DE8\u9020\u5FC3\u7406\u6D3B\u52D5\uFF0C\u4E5F\u4E0D\u8F15\u6613\u7D66\u4EBA\u8CBC\u6A19\u7C64\u3002
|
|
15596
|
+
- \u9069\u91CF\u5F15\u7528\u6709\u4EE3\u8868\u6027\u7684\u539F\u8A71\u548C\u8CC7\u6599\uFF0C\u4E0D\u8981\u5927\u6BB5\u5806\u780C\u804A\u5929\u8A18\u9304\u3002
|
|
15597
|
+
- \u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\uFF0C\u4E0D\u8AAA\u6559\u3001\u4E0D\u64C5\u81EA\u7D66\u4EBA\u751F\u5EFA\u8B70\uFF0C\u4E5F\u4E0D\u628A\u6BCF\u6B21\u56DE\u7B54\u90FD\u5BEB\u6210\u7E3D\u7D50\u5831\u544A\u3002`,
|
|
15598
|
+
private: `\u4F60\u662F ChatLab \u88E1\u7684\u79C1\u804A\u8A18\u9304\u5206\u6790\u642D\u6A94\uFF0C\u5E6B\u52A9\u4F7F\u7528\u8005\u5F9E\u804A\u5929\u8A18\u9304\u4E2D\u7406\u89E3\u4E8B\u5BE6\u3001\u8B8A\u5316\u548C\u4E92\u52D5\u65B9\u5F0F\uFF0C\u800C\u4E0D\u662F\u6A5F\u68B0\u5730\u7522\u751F\u5206\u6790\u5831\u544A\u3002
|
|
15599
|
+
|
|
15600
|
+
## \u4EA4\u6D41\u65B9\u5F0F
|
|
15601
|
+
- \u8DDF\u96A8\u4F7F\u7528\u8005\u7684\u8A9E\u8A00\u548C\u4EA4\u6D41\u65B9\u5F0F\uFF0C\u8AAA\u81EA\u7136\u3001\u76F4\u63A5\u7684\u4EBA\u8A71\uFF0C\u907F\u514D\u5BA2\u670D\u8154\u548C\u904E\u5EA6\u6B63\u5F0F\u7684\u8868\u9054\u3002
|
|
15602
|
+
- \u7C21\u55AE\u554F\u984C\u76F4\u63A5\u56DE\u7B54\uFF1B\u8907\u96DC\u554F\u984C\u518D\u5206\u5C64\u8AAA\u660E\u3002\u6A19\u984C\u3001\u5217\u8868\u548C\u8868\u683C\u53EA\u5728\u6709\u52A9\u65BC\u7406\u89E3\u6642\u4F7F\u7528\u3002
|
|
15603
|
+
- \u5728\u8B49\u64DA\u5141\u8A31\u6642\u7D66\u51FA\u660E\u78BA\u5224\u65B7\uFF1B\u7121\u6CD5\u78BA\u8A8D\u7684\u90E8\u5206\u8981\u5766\u7387\u8AAA\u660E\u3002
|
|
15604
|
+
- \u53EF\u4EE5\u6709\u4E00\u9EDE\u81EA\u7136\u7684\u5E7D\u9ED8\u611F\uFF0C\u4F46\u4E0D\u8981\u5F37\u884C\u73A9\u6897\u3001\u5806\u8868\u60C5\u6216\u72A7\u7272\u6E96\u78BA\u6027\u3002
|
|
15605
|
+
|
|
15606
|
+
## \u5206\u6790\u908A\u754C
|
|
15607
|
+
- \u5340\u5206\u80FD\u5920\u78BA\u8A8D\u7684\u4E8B\u5BE6\u3001\u57FA\u65BC\u8B49\u64DA\u7684\u63A8\u8AD6\uFF0C\u4EE5\u53CA\u66AB\u6642\u7121\u6CD5\u5224\u65B7\u7684\u90E8\u5206\u3002
|
|
15608
|
+
- \u6D89\u53CA\u6027\u683C\u3001\u60C5\u7DD2\u3001\u95DC\u4FC2\u548C\u52D5\u6A5F\u6642\u5C24\u5176\u8B39\u614E\uFF0C\u4E0D\u7DE8\u9020\u5FC3\u7406\u6D3B\u52D5\uFF0C\u4E5F\u4E0D\u8F15\u6613\u7D66\u4EBA\u8CBC\u6A19\u7C64\u3002
|
|
15609
|
+
- \u9069\u91CF\u5F15\u7528\u6709\u4EE3\u8868\u6027\u7684\u539F\u8A71\u548C\u8CC7\u6599\uFF0C\u4E0D\u8981\u5927\u6BB5\u5806\u780C\u804A\u5929\u8A18\u9304\u3002
|
|
15610
|
+
- \u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\uFF0C\u4E0D\u8AAA\u6559\u3001\u4E0D\u64C5\u81EA\u7D66\u4EBA\u751F\u5EFA\u8B70\uFF0C\u4E5F\u4E0D\u628A\u6BCF\u6B21\u56DE\u7B54\u90FD\u5BEB\u6210\u7E3D\u7D50\u5831\u544A\u3002`
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
},
|
|
15614
|
+
llm: {
|
|
15615
|
+
notConfigured: "LLM \u670D\u52D9\u5C1A\u672A\u8A2D\u5B9A\uFF0C\u8ACB\u5148\u5728\u8A2D\u5B9A\u4E2D\u586B\u5165 API Key",
|
|
15616
|
+
maxConfigs: "\u6700\u591A\u53EA\u80FD\u65B0\u589E {{count}} \u7D44\u8A2D\u5B9A",
|
|
15617
|
+
configNotFound: "\u627E\u4E0D\u5230\u8A2D\u5B9A",
|
|
15618
|
+
noActiveConfig: "\u6C92\u6709\u555F\u7528\u4E2D\u7684\u8A2D\u5B9A",
|
|
15619
|
+
callFailed: "LLM \u547C\u53EB\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u6A21\u578B\u8A2D\u5B9A\u662F\u5426\u6B63\u78BA",
|
|
15620
|
+
genericProviderName: "API \u670D\u52D9",
|
|
15621
|
+
rawErrorLabel: "\u539F\u59CB\u932F\u8AA4"
|
|
15622
|
+
},
|
|
15623
|
+
summary: {
|
|
15624
|
+
segmentNotFound: "\u6703\u8A71\u4E0D\u5B58\u5728\u6216\u8CC7\u6599\u5EAB\u958B\u555F\u5931\u6557",
|
|
15625
|
+
tooFewMessages: "\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
15626
|
+
tooFewValidMessages: "\u6709\u6548\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
15627
|
+
segmentNotExist: "\u627E\u4E0D\u5230\u6703\u8A71",
|
|
15628
|
+
messagesTooFew: "\u8A0A\u606F\u592A\u5C11",
|
|
15629
|
+
validMessagesTooFew: "\u6709\u6548\u8A0A\u606F\u592A\u5C11",
|
|
15630
|
+
systemPromptDirect: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u7528\u7C21\u6F54\u7684\u8A9E\u8A00\u7E3D\u7D50\u5C0D\u8A71\u5167\u5BB9\u3002",
|
|
15631
|
+
systemPromptMerge: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u5C07\u591A\u500B\u6458\u8981\u5408\u4F75\u6210\u4E00\u500B\u9023\u8CAB\u7684\u7E3D\u7D50\u3002"
|
|
15632
|
+
}
|
|
15633
|
+
};
|
|
15634
|
+
|
|
15635
|
+
// ../../packages/node-runtime/src/ai/i18n/index.ts
|
|
15636
|
+
var localeMap = {
|
|
15637
|
+
"zh-CN": zh_CN_default,
|
|
15638
|
+
"en-US": en_US_default,
|
|
15639
|
+
"ja-JP": ja_JP_default,
|
|
15640
|
+
"zh-TW": zh_TW_default
|
|
15641
|
+
};
|
|
15642
|
+
function getNestedValue(obj, path31) {
|
|
15643
|
+
let current = obj;
|
|
15644
|
+
for (const segment2 of path31.split(".")) {
|
|
15645
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
15646
|
+
current = current[segment2];
|
|
15647
|
+
}
|
|
15648
|
+
return current;
|
|
15649
|
+
}
|
|
15650
|
+
function interpolate(template, vars) {
|
|
15651
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
15652
|
+
const val = vars[key];
|
|
15653
|
+
return val != null ? String(val) : "";
|
|
15654
|
+
});
|
|
15655
|
+
}
|
|
15656
|
+
function createAiTranslate(defaultLocale = "zh-CN") {
|
|
15657
|
+
return (key, options) => {
|
|
15658
|
+
const lng = options?.lng || defaultLocale;
|
|
15659
|
+
const locale = localeMap[lng] ? lng : "en-US";
|
|
15660
|
+
const value = getNestedValue(localeMap[locale], key) ?? getNestedValue(localeMap["en-US"], key);
|
|
15661
|
+
if (typeof value !== "string") return key;
|
|
15662
|
+
if (!options) return value;
|
|
15663
|
+
return interpolate(value, options);
|
|
15664
|
+
};
|
|
15665
|
+
}
|
|
15666
|
+
var aiLocales = { "zh-CN": zh_CN_default, "en-US": en_US_default, "ja-JP": ja_JP_default, "zh-TW": zh_TW_default };
|
|
15667
|
+
|
|
15668
|
+
// ../../packages/node-runtime/src/ai/agent/event-handler.ts
|
|
15669
|
+
function estimateTokensFromText(text) {
|
|
15670
|
+
if (!text) return 0;
|
|
15671
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
15672
|
+
if (!normalized) return 0;
|
|
15673
|
+
const cjkCount = (normalized.match(/[\u3400-\u9fff\uf900-\ufaff]/g) || []).length;
|
|
15674
|
+
const latinCount = normalized.length - cjkCount;
|
|
15675
|
+
return Math.max(1, Math.ceil(cjkCount * 1.15 + latinCount / 4));
|
|
15676
|
+
}
|
|
15677
|
+
function extractMessageText(message) {
|
|
15678
|
+
if (message.role === "user") {
|
|
15679
|
+
if (typeof message.content === "string") return message.content;
|
|
15680
|
+
return message.content.map((item) => {
|
|
15681
|
+
if (item.type === "text") return item.text;
|
|
15682
|
+
if (item.type === "image") return "[image]";
|
|
15683
|
+
return "";
|
|
15684
|
+
}).join("\n");
|
|
15685
|
+
}
|
|
15686
|
+
if (message.role === "assistant") {
|
|
15687
|
+
return message.content.map((item) => {
|
|
15688
|
+
if (item.type === "text") return item.text;
|
|
15689
|
+
if (item.type === "thinking") return item.thinking;
|
|
15690
|
+
if (item.type === "toolCall") return `${item.name} ${JSON.stringify(item.arguments || {})}`;
|
|
15691
|
+
return "";
|
|
15692
|
+
}).join("\n");
|
|
15693
|
+
}
|
|
15694
|
+
if (message.role === "toolResult") {
|
|
15695
|
+
return message.content.map((item) => {
|
|
15696
|
+
if (item.type === "text") return item.text;
|
|
15697
|
+
return "[binary]";
|
|
15698
|
+
}).join("\n");
|
|
15699
|
+
}
|
|
15700
|
+
return "";
|
|
15701
|
+
}
|
|
15702
|
+
function estimateContextTokens(systemPrompt, messages, pendingUserMessage) {
|
|
15703
|
+
let tokens = estimateTokensFromText(systemPrompt);
|
|
15704
|
+
for (const message of messages) {
|
|
15705
|
+
if (message.role === "toolResult") continue;
|
|
15706
|
+
tokens += estimateTokensFromText(extractMessageText(message));
|
|
15707
|
+
}
|
|
15708
|
+
if (pendingUserMessage) {
|
|
15709
|
+
tokens += estimateTokensFromText(pendingUserMessage);
|
|
15710
|
+
}
|
|
15711
|
+
return tokens;
|
|
15712
|
+
}
|
|
15713
|
+
var AgentEventHandler = class {
|
|
15714
|
+
toolsUsed = [];
|
|
15715
|
+
toolRounds = 0;
|
|
15716
|
+
totalUsage = {
|
|
15717
|
+
promptTokens: 0,
|
|
15718
|
+
completionTokens: 0,
|
|
15719
|
+
totalTokens: 0,
|
|
15720
|
+
cacheReadTokens: 0,
|
|
15721
|
+
cacheWriteTokens: 0
|
|
15722
|
+
};
|
|
15723
|
+
lastStatusAt = 0;
|
|
15724
|
+
onChunk;
|
|
15725
|
+
context;
|
|
15726
|
+
systemPrompt;
|
|
15727
|
+
activeTools = /* @__PURE__ */ new Map();
|
|
15728
|
+
constructor(config) {
|
|
15729
|
+
this.onChunk = config.onChunk;
|
|
15730
|
+
this.context = config.context;
|
|
15731
|
+
this.systemPrompt = config.systemPrompt;
|
|
15732
|
+
}
|
|
15733
|
+
handleCoreEvent(event, messages) {
|
|
15734
|
+
switch (event.type) {
|
|
15735
|
+
case "content":
|
|
15736
|
+
this.onChunk({ type: "content", content: event.content });
|
|
15737
|
+
this.emitStatus("responding", messages);
|
|
15738
|
+
break;
|
|
15739
|
+
case "thinking_start":
|
|
15740
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15741
|
+
break;
|
|
15742
|
+
case "thinking_delta":
|
|
15743
|
+
this.onChunk({ type: "think", content: event.content, thinkTag: "thinking" });
|
|
15744
|
+
this.emitStatus("thinking", messages);
|
|
15745
|
+
break;
|
|
15746
|
+
case "thinking_end":
|
|
15747
|
+
this.onChunk({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: event.durationMs });
|
|
15748
|
+
this.emitStatus("responding", messages, { force: true });
|
|
15749
|
+
break;
|
|
15750
|
+
case "tool_start": {
|
|
15751
|
+
const params = this.normalizeToolParams(event.toolName, event.toolParams);
|
|
15752
|
+
this.toolsUsed.push(event.toolName);
|
|
15753
|
+
this.activeTools.set(event.toolCallId, event.toolName);
|
|
15754
|
+
this.onChunk({ type: "tool_start", toolCallId: event.toolCallId, toolName: event.toolName, toolParams: params });
|
|
15755
|
+
this.emitStatus("tool_running", messages, { currentTool: event.toolName, force: true });
|
|
15756
|
+
break;
|
|
15757
|
+
}
|
|
15758
|
+
case "tool_update":
|
|
15759
|
+
this.onChunk({
|
|
15760
|
+
type: "tool_update",
|
|
15761
|
+
toolCallId: event.toolCallId,
|
|
15762
|
+
toolName: event.toolName,
|
|
15763
|
+
toolProgress: event.progress
|
|
15764
|
+
});
|
|
15765
|
+
break;
|
|
15766
|
+
case "tool_end":
|
|
15767
|
+
this.activeTools.delete(event.toolCallId);
|
|
15768
|
+
this.onChunk({
|
|
15769
|
+
type: "tool_result",
|
|
15770
|
+
toolCallId: event.toolCallId,
|
|
15771
|
+
toolName: event.toolName,
|
|
15772
|
+
toolResult: event.toolResult,
|
|
15773
|
+
toolIsError: event.isError
|
|
15774
|
+
});
|
|
15775
|
+
if (this.activeTools.size > 0) {
|
|
15776
|
+
const currentTool = Array.from(this.activeTools.values()).at(-1);
|
|
15777
|
+
this.emitStatus("tool_running", messages, { currentTool, force: true });
|
|
15778
|
+
} else {
|
|
15779
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15780
|
+
}
|
|
15781
|
+
break;
|
|
15782
|
+
case "turn_end":
|
|
15783
|
+
this.activeTools.clear();
|
|
15784
|
+
this.toolRounds = event.round;
|
|
15785
|
+
this.onChunk({ type: "turn_end", hadToolCalls: event.hadToolCalls });
|
|
15786
|
+
this.emitStatus("thinking", messages, { force: true });
|
|
15787
|
+
break;
|
|
15788
|
+
case "usage_update":
|
|
15789
|
+
this.totalUsage = { ...event.usage };
|
|
15790
|
+
this.emitStatus("responding", messages, { force: true });
|
|
15791
|
+
break;
|
|
15792
|
+
}
|
|
15793
|
+
}
|
|
15794
|
+
cloneUsage() {
|
|
15795
|
+
return {
|
|
15796
|
+
promptTokens: this.totalUsage.promptTokens,
|
|
15797
|
+
completionTokens: this.totalUsage.completionTokens,
|
|
15798
|
+
totalTokens: this.totalUsage.totalTokens,
|
|
15799
|
+
cacheReadTokens: this.totalUsage.cacheReadTokens,
|
|
15800
|
+
cacheWriteTokens: this.totalUsage.cacheWriteTokens
|
|
15801
|
+
};
|
|
15802
|
+
}
|
|
15803
|
+
emitStatus(phase, messages, options) {
|
|
15804
|
+
const now = Date.now();
|
|
15805
|
+
if (!options?.force && now - this.lastStatusAt < 240) return;
|
|
15806
|
+
this.lastStatusAt = now;
|
|
15807
|
+
const contextTokens = estimateContextTokens(this.systemPrompt, messages, options?.pendingUserMessage);
|
|
15808
|
+
const status = {
|
|
15809
|
+
phase,
|
|
15810
|
+
round: this.toolRounds,
|
|
15811
|
+
toolsUsed: this.toolsUsed.length,
|
|
15812
|
+
currentTool: options?.currentTool,
|
|
15813
|
+
contextTokens,
|
|
15814
|
+
totalUsage: this.cloneUsage(),
|
|
15815
|
+
updatedAt: now
|
|
15816
|
+
};
|
|
15817
|
+
this.onChunk({ type: "status", status });
|
|
15818
|
+
}
|
|
15819
|
+
normalizeToolParams(toolName, params) {
|
|
15820
|
+
const normalized = { ...params };
|
|
15821
|
+
const toolsWithLimit = ["search_messages", "get_recent_messages", "get_conversation_between"];
|
|
15822
|
+
if (this.context.maxMessagesLimit && toolsWithLimit.includes(toolName)) {
|
|
15823
|
+
normalized.limit = this.context.maxMessagesLimit;
|
|
15824
|
+
}
|
|
15825
|
+
if (this.context.timeFilter && (toolName === "search_messages" || toolName === "get_recent_messages")) {
|
|
15826
|
+
normalized._timeFilter = this.context.timeFilter;
|
|
15827
|
+
}
|
|
15828
|
+
return normalized;
|
|
15829
|
+
}
|
|
15830
|
+
};
|
|
15831
|
+
|
|
15832
|
+
// ../../packages/node-runtime/src/ai/cross-chat-agent.ts
|
|
15833
|
+
var CROSS_CHAT_MAX_TOOL_RESULT_TOKENS = 128e3;
|
|
15834
|
+
function resolveCrossChatToolResultTokenBudget(contextWindow) {
|
|
15835
|
+
const maxToolResultPercent = DEFAULT_CONTEXT_COMPRESSION_CONFIG.maxToolResultPercent ?? 50;
|
|
15836
|
+
const proportionalBudget = Math.floor(contextWindow * (maxToolResultPercent / 100));
|
|
15837
|
+
return Math.min(proportionalBudget, CROSS_CHAT_MAX_TOOL_RESULT_TOKENS);
|
|
15838
|
+
}
|
|
15839
|
+
async function runCrossChatAgent(options) {
|
|
15840
|
+
const {
|
|
15841
|
+
userMessage,
|
|
15842
|
+
entityRefs,
|
|
15843
|
+
aiChatId,
|
|
15844
|
+
historyLeafMessageId,
|
|
15845
|
+
locale = "zh-CN",
|
|
15846
|
+
piModel,
|
|
15847
|
+
apiKey,
|
|
15848
|
+
tools,
|
|
15849
|
+
aiChatManager,
|
|
15850
|
+
memoryService,
|
|
15851
|
+
onEvent,
|
|
15852
|
+
abortSignal,
|
|
15853
|
+
thinkingLevel,
|
|
15854
|
+
logger
|
|
15855
|
+
} = options;
|
|
15856
|
+
await initTokenizer();
|
|
15857
|
+
logger?.info("CrossChatAgent", "Cross-chat agent execution started", {
|
|
15858
|
+
aiChatId,
|
|
15859
|
+
entityRefCount: entityRefs?.length ?? 0,
|
|
15860
|
+
toolCount: tools.length
|
|
15861
|
+
});
|
|
15862
|
+
const globalMemoryPrompt = buildGlobalMemoryPrompt(
|
|
15863
|
+
memoryService.list({ scopeType: "global", scopeId: null }),
|
|
15864
|
+
locale,
|
|
15865
|
+
userMessage
|
|
15866
|
+
);
|
|
15867
|
+
const entityMemoryPrompt = buildEntityMemoryPrompt(
|
|
15868
|
+
entityRefs,
|
|
15869
|
+
(scope) => memoryService.list(scope),
|
|
15870
|
+
locale,
|
|
15871
|
+
userMessage
|
|
15872
|
+
);
|
|
15873
|
+
const systemPrompt = buildCrossChatSystemPrompt(locale, /* @__PURE__ */ new Date(), globalMemoryPrompt, entityMemoryPrompt);
|
|
15874
|
+
const handler = new AgentEventHandler({ onChunk: onEvent, context: {}, systemPrompt });
|
|
15875
|
+
let cachedMessages = [];
|
|
15876
|
+
const finishAborted = () => {
|
|
15877
|
+
handler.emitStatus("aborted", cachedMessages, { force: true });
|
|
15878
|
+
onEvent({ type: "done", isFinished: true, usage: handler.cloneUsage() });
|
|
15879
|
+
};
|
|
15880
|
+
if (abortSignal?.aborted) {
|
|
15881
|
+
finishAborted();
|
|
15882
|
+
return;
|
|
15883
|
+
}
|
|
15884
|
+
if (historyLeafMessageId === void 0) {
|
|
15885
|
+
const compressionResult = await checkAndCompress(
|
|
15886
|
+
aiChatId,
|
|
15887
|
+
DEFAULT_CONTEXT_COMPRESSION_CONFIG,
|
|
15888
|
+
systemPrompt,
|
|
15889
|
+
createCompressionLlmAdapter({
|
|
15890
|
+
piModel,
|
|
15891
|
+
apiKey,
|
|
15892
|
+
onCompressing: () => handler.emitStatus("compressing", [])
|
|
15893
|
+
}),
|
|
15894
|
+
aiChatManager,
|
|
15895
|
+
logger,
|
|
15896
|
+
{ signal: abortSignal }
|
|
15897
|
+
);
|
|
15898
|
+
if (compressionResult.compressed) {
|
|
15899
|
+
onEvent({
|
|
15900
|
+
type: "compression_done",
|
|
15901
|
+
compressionResult: {
|
|
15902
|
+
summaryContent: compressionResult.summaryContent ?? "",
|
|
15903
|
+
tokensBefore: compressionResult.tokensBefore ?? 0,
|
|
15904
|
+
tokensAfter: compressionResult.tokensAfter ?? 0,
|
|
15905
|
+
timestamp: Date.now()
|
|
15906
|
+
}
|
|
15907
|
+
});
|
|
15908
|
+
}
|
|
15909
|
+
}
|
|
15910
|
+
if (abortSignal?.aborted) {
|
|
15911
|
+
finishAborted();
|
|
15912
|
+
return;
|
|
15913
|
+
}
|
|
15914
|
+
const history = aiChatManager.getHistoryForAgent(aiChatId, void 0, historyLeafMessageId);
|
|
15915
|
+
const modelUserMessage = appendEntityRefsForModel(userMessage, entityRefs);
|
|
15916
|
+
handler.emitStatus("preparing", [], { pendingUserMessage: modelUserMessage, force: true });
|
|
15917
|
+
try {
|
|
15918
|
+
const routeInput = {
|
|
15919
|
+
userMessage: modelUserMessage,
|
|
15920
|
+
chatType: "group",
|
|
15921
|
+
locale,
|
|
15922
|
+
availableTools: tools.map((tool) => tool.name),
|
|
15923
|
+
availableCapabilities: []
|
|
15924
|
+
};
|
|
15925
|
+
const routeDecision = await decideRequestRoute(routeInput, {
|
|
15926
|
+
llmRouter: createLlmRouteDecider({ piModel, apiKey, abortSignal })
|
|
15927
|
+
});
|
|
15928
|
+
onEvent({ type: "route", routeDecision });
|
|
15929
|
+
let effectiveSystemPrompt = systemPrompt;
|
|
15930
|
+
if (routeDecision.route === "planned_execution") {
|
|
15931
|
+
const planner = createAnalysisPlanner({
|
|
15932
|
+
piModel,
|
|
15933
|
+
apiKey,
|
|
15934
|
+
onPlanDelta: (delta) => onEvent({ type: "plan_delta", planDelta: delta }),
|
|
15935
|
+
onThinkingDelta: (delta) => onEvent({ type: "think", content: delta, thinkTag: "thinking" }),
|
|
15936
|
+
onThinkingEnd: (durationMs) => onEvent({ type: "think", content: "", thinkTag: "thinking", thinkDurationMs: durationMs }),
|
|
15937
|
+
onValidationDelta: (delta) => onEvent({ type: "think", content: delta, thinkTag: "plan_validation" }),
|
|
15938
|
+
onValidationEnd: (durationMs) => onEvent({ type: "think", content: "", thinkTag: "plan_validation", thinkDurationMs: durationMs })
|
|
15939
|
+
});
|
|
15940
|
+
const plan = await planner(routeInput, abortSignal);
|
|
15941
|
+
if (plan) {
|
|
15942
|
+
onEvent({ type: "plan", plan: createPlanContentBlock(plan) });
|
|
15943
|
+
effectiveSystemPrompt = `${systemPrompt}
|
|
15944
|
+
|
|
15945
|
+
${buildPlanGuidance(plan)}`;
|
|
15946
|
+
} else {
|
|
15947
|
+
onEvent({ type: "plan_skipped" });
|
|
15948
|
+
}
|
|
15949
|
+
}
|
|
15950
|
+
const result = await runAgentCore({
|
|
15951
|
+
piModel,
|
|
15952
|
+
apiKey,
|
|
15953
|
+
systemPrompt: effectiveSystemPrompt,
|
|
15954
|
+
tools,
|
|
15955
|
+
history,
|
|
15956
|
+
userMessage: modelUserMessage,
|
|
15957
|
+
maxToolRounds: DEFAULT_MAX_TOOL_ROUNDS,
|
|
15958
|
+
abortSignal,
|
|
15959
|
+
providerSessionId: aiChatId,
|
|
15960
|
+
steerMessage: createAiTranslate(locale)("ai.agent.answerWithoutTools"),
|
|
15961
|
+
thinkingLevel,
|
|
15962
|
+
onConvertToLlm: (messages) => {
|
|
15963
|
+
cachedMessages = messages;
|
|
15964
|
+
},
|
|
15965
|
+
onEvent: (event) => handler.handleCoreEvent(event, cachedMessages),
|
|
15966
|
+
onDebugContext: (messages) => {
|
|
15967
|
+
try {
|
|
15968
|
+
aiChatManager.setPendingDebugContext(aiChatId, JSON.stringify(messages, null, 2));
|
|
15969
|
+
} catch {
|
|
15970
|
+
}
|
|
15971
|
+
}
|
|
15972
|
+
});
|
|
15973
|
+
if (abortSignal?.aborted) {
|
|
15974
|
+
finishAborted();
|
|
15975
|
+
return;
|
|
15976
|
+
}
|
|
15977
|
+
if (result.error) {
|
|
15978
|
+
onEvent({ type: "error", error: { name: "AgentError", message: formatAIError(result.error) } });
|
|
15979
|
+
}
|
|
15980
|
+
handler.emitStatus("completed", cachedMessages, { force: true });
|
|
15981
|
+
logger?.info("CrossChatAgent", "Cross-chat agent execution completed", {
|
|
15982
|
+
aiChatId,
|
|
15983
|
+
toolRounds: result.toolRounds,
|
|
15984
|
+
toolsUsed: result.toolsUsed.length
|
|
15985
|
+
});
|
|
15986
|
+
onEvent({ type: "done", isFinished: true, usage: result.usage });
|
|
15987
|
+
} catch (error) {
|
|
15988
|
+
if (abortSignal?.aborted) {
|
|
15989
|
+
finishAborted();
|
|
15990
|
+
return;
|
|
15991
|
+
}
|
|
15992
|
+
logger?.error("CrossChatAgent", "Cross-chat agent execution failed", error);
|
|
15993
|
+
handler.emitStatus("error", cachedMessages, { force: true });
|
|
15994
|
+
onEvent({ type: "error", error: { name: "AgentError", message: formatAIError(error) } });
|
|
15995
|
+
onEvent({ type: "done", isFinished: true, usage: handler.cloneUsage() });
|
|
15996
|
+
}
|
|
15997
|
+
}
|
|
15998
|
+
function buildCrossChatSystemPrompt(locale = "zh-CN", now = /* @__PURE__ */ new Date(), globalMemoryPrompt = "", entityMemoryPrompt = "") {
|
|
15999
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
16000
|
+
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
16001
|
+
year: "numeric",
|
|
16002
|
+
month: "long",
|
|
16003
|
+
day: "numeric",
|
|
16004
|
+
weekday: "long"
|
|
16005
|
+
});
|
|
16006
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
|
|
16007
|
+
if (locale.startsWith("zh")) {
|
|
16008
|
+
return `\u4F60\u662F ChatLab \u7684\u8DE8\u5BF9\u8BDD\u5206\u6790\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u6309\u7528\u6237\u5F53\u524D\u95EE\u9898\uFF0C\u5728\u5176\u672C\u5730\u804A\u5929\u6570\u636E\u5E93\u4E2D\u6309\u9700\u68C0\u7D22\u591A\u4E2A\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u3002
|
|
16009
|
+
|
|
16010
|
+
\u6570\u636E\u4E0E\u8303\u56F4\u89C4\u5219\uFF1A
|
|
16011
|
+
- \u5F53\u524D\u65E5\u671F\u662F ${currentDate}\uFF08\u65F6\u533A\uFF1A${timeZone}\uFF09\u3002\u7528\u6237\u7684\u76F8\u5BF9\u65F6\u95F4\u8868\u8FBE\u4EE5\u771F\u5B9E\u5F53\u524D\u65F6\u95F4\u4E3A\u57FA\u51C6\uFF1B\u6570\u636E\u5E93\u622A\u6B62\u65F6\u95F4\u53EA\u8BF4\u660E\u5DF2\u5BFC\u5165\u6570\u636E\u7684\u8986\u76D6\u8303\u56F4\u3002
|
|
16012
|
+
- \u7528\u6237\u5DF2\u6388\u6743\u4F60\u67E5\u8BE2\u5168\u90E8\u672C\u5730\u804A\u5929\u6570\u636E\uFF0C\u4F46\u53EA\u80FD\u4E3A\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u6309\u9700\u8C03\u7528\u5DE5\u5177\uFF0C\u7981\u6B62\u65E0\u76EE\u7684\u904D\u5386\u3002
|
|
16013
|
+
- <chatlab_entity_refs> \u662F\u754C\u9762\u9009\u62E9\u5668\u5199\u5165\u7684\u7A33\u5B9A\u5B9E\u4F53\u5F15\u7528\uFF0C\u6D89\u53CA\u8FD9\u4E9B\u5B9E\u4F53\u65F6\u76F4\u63A5\u8C03\u7528 resolve_chat_entities\u3002\u7528\u6237\u53EA\u8F93\u5165\u8054\u7CFB\u4EBA\u540D\u79F0\u65F6\uFF0C\u4E5F\u8981\u7528 resolve_chat_entities \u67E5\u8BE2\u8054\u7CFB\u4EBA\u76EE\u5F55\uFF1A\u552F\u4E00\u5019\u9009\u81EA\u52A8\u7EE7\u7EED\uFF0C\u591A\u4E2A\u5019\u9009\u5FC5\u987B\u505C\u4E0B\u6765\u8BF7\u7528\u6237\u786E\u8BA4\uFF0C\u6CA1\u6709\u5019\u9009\u65F6\u518D\u63D0\u793A\u8865\u5145\u4FE1\u606F\uFF1B\u7981\u6B62\u5728\u591A\u4E2A\u5019\u9009\u4E2D\u81EA\u884C\u731C\u6D4B\u3002
|
|
16014
|
+
- \u5F53\u524D\u7528\u6237\u6D88\u606F\u4E2D\u7684 <chatlab_entity_refs> \u662F\u672C\u8F6E\u6700\u65B0\u9009\u62E9\u3002\u7528\u6237\u8BF4\u201C\u5F53\u524D\u5BF9\u8C61\u201D\u201C\u6240\u9009\u5B9E\u4F53\u201D\u7B49\u6307\u4EE3\u65F6\uFF0C\u53EA\u4F7F\u7528\u672C\u8F6E\u5F15\u7528\uFF0C\u4E0D\u5F97\u590D\u7528\u5386\u53F2\u6D88\u606F\u4E2D\u7684\u65E7\u5B9E\u4F53\uFF1B\u672C\u8F6E\u540C\u65F6\u9009\u62E9\u591A\u4E2A\u5B9E\u4F53\u65F6\u53EF\u4EE5\u6309\u95EE\u9898\u4F7F\u7528\u5176\u4E2D\u4EFB\u610F\u4E00\u4E2A\u3002
|
|
16015
|
+
- \u5BF9\u8BDD\u5386\u53F2\u4E2D\u7684\u5B9E\u4F53\u5F15\u7528\u53EA\u5E2E\u52A9\u7406\u89E3\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u6784\u6210\u6C38\u4E45\u9501\u5B9A\u8303\u56F4\u3002\u6BCF\u4E00\u8F6E\u6839\u636E\u7528\u6237\u8BED\u4E49\u51B3\u5B9A\u7EE7\u7EED\u539F\u5BF9\u8C61\u3001\u5207\u6362\u5BF9\u8C61\u6216\u6267\u884C\u5168\u5C40\u53D1\u73B0\u3002
|
|
16016
|
+
- \u8054\u7CFB\u4EBA\u9ED8\u8BA4\u8986\u76D6\u5176\u5B9E\u9645\u53C2\u4E0E\u7684\u79C1\u804A\u548C\u7FA4\u804A\u3002\u591A\u4E2A\u5BF9\u8C61\u65E2\u53EF\u80FD\u662F\u4EA4\u96C6\u3001\u5E76\u96C6\uFF0C\u4E5F\u53EF\u80FD\u9700\u8981\u5206\u522B\u68C0\u7D22\u6BD4\u8F83\uFF1B\u4EE5\u7528\u6237\u8BED\u4E49\u4E3A\u51C6\uFF0C\u4E0D\u8981\u673A\u68B0\u5957\u7528\u4E00\u79CD\u96C6\u5408\u89C4\u5219\u3002
|
|
16017
|
+
- \u5F53\u7528\u6237\u95EE\u201C\u6211\u548C\u67D0\u4EBA\u6700\u8FD1\u804A\u4E86\u4EC0\u4E48\u201D\u6216\u660E\u786E\u8981\u6C42\u56DE\u987E\u4E24\u4EBA\u7684\u79C1\u804A\u5185\u5BB9\u65F6\uFF0C\u53EA\u4F7F\u7528 resolve_chat_entities \u8FD4\u56DE\u7684\u5BF9\u5E94\u79C1\u804A session\uFF0C\u4E0D\u8981\u6DF7\u5165\u8BE5\u8054\u7CFB\u4EBA\u6240\u5728\u7684\u7FA4\u804A\u3002\u7528\u6237\u6CA1\u6709\u7ED9\u51FA\u660E\u786E\u65E5\u671F\u6216\u5929\u6570\u65F6\uFF0C\u8FD9\u91CC\u7684\u201C\u6700\u8FD1\u201D\u6307\u5DF2\u5BFC\u5165\u79C1\u804A\u4E2D\u6700\u65B0\u4E00\u6BB5\u53EF\u7528\u5185\u5BB9\uFF1A\u89E3\u6790\u5230\u552F\u4E00\u79C1\u804A\u540E\u76F4\u63A5\u8C03\u7528 read_recent_session\uFF0C\u4E0D\u8981\u5148\u7528 search_messages_globally \u586B\u6EE1\u8DE8\u5BF9\u8BDD\u8BC1\u636E\u9884\u7B97\u3002\u53EA\u6709\u660E\u786E\u8BF4\u201C\u6700\u8FD1 N \u5929\u201D\u3001\u7ED9\u51FA\u65E5\u671F\u8303\u56F4\u3001\u8981\u6C42\u66F4\u65E9\u5185\u5BB9\u6216\u6DF1\u5EA6\u8C03\u67E5\u65F6\u624D\u6539\u7528 search_messages_globally\u3002\u6CA1\u6709\u5BF9\u5E94\u79C1\u804A\u65F6\u5148\u660E\u786E\u8BF4\u660E\uFF0C\u518D\u8BE2\u95EE\u662F\u5426\u6539\u67E5\u5171\u540C\u7FA4\u3002
|
|
16018
|
+
- \u7528\u6237\u8BE2\u95EE\u67D0\u4E2A\u8054\u7CFB\u4EBA\u51FA\u73B0\u5728\u54EA\u4E9B\u5DF2\u5BFC\u5165\u4F1A\u8BDD\u3001\u5206\u522B\u8BF4\u4E86\u591A\u5C11\u6216\u6700\u8FD1\u5728\u54EA\u91CC\u6D3B\u8DC3\u65F6\uFF0C\u5148\u7528 inspect_contact_sessions \u76D8\u70B9\u6765\u6E90\u3002\u4E0D\u8981\u4EC5\u56E0\u4E3A\u6D88\u606F\u4E2D\u51FA\u73B0\u4E86 @\u8054\u7CFB\u4EBA\u5C31\u673A\u68B0\u8C03\u7528\uFF1B\u666E\u901A\u4E8B\u5B9E\u9898\u4ECD\u6309\u95EE\u9898\u4F7F\u7528\u641C\u7D22\u4E0E\u4E0A\u4E0B\u6587\u5DE5\u5177\u3002
|
|
16019
|
+
- \u7528\u6237\u8BE2\u95EE 2\u20145 \u4EBA\u7684\u5173\u7CFB\u3001\u76F8\u8BC6\u80CC\u666F\u3001\u5171\u540C\u5708\u5B50\u6216\u4E92\u52A8\u53D8\u5316\u65F6\uFF0C\u5148\u7528 inspect_shared_interactions \u627E\u5168\u5458\u5171\u540C\u4F1A\u8BDD\u3001\u9010\u4EBA\u6D3B\u8DC3\u3001\u76F4\u63A5\u56DE\u590D\u3001\u76F8\u90BB\u53D1\u8A00\u548C\u6D88\u606F\u951A\u70B9\uFF1B\u4E0D\u8981\u4E3A\u4E86\u591A\u4EBA\u95EE\u9898\u673A\u68B0\u5730\u5148\u5BF9\u6BCF\u4E2A\u4EBA\u8C03\u7528 inspect_contact_sessions\u3002
|
|
16020
|
+
- \u7528\u6237\u8BE2\u95EE\u201C\u6211\u548C\u8C01\u79C1\u804A\u6700\u591A\u201D\u201C\u79C1\u804A\u9891\u7387\u6392\u884C\u201D\u6216\u201C\u548C\u54EA\u4E9B\u8054\u7CFB\u4EBA\u804A\u5929\u7684\u6D3B\u8DC3\u5929\u6570\u6700\u591A\u201D\u65F6\uFF0C\u76F4\u63A5\u8C03\u7528 rank_private_contacts\uFF0C\u5E76\u6309\u7528\u6237\u7ED9\u51FA\u7684\u81EA\u7136\u5E74\u3001\u65E5\u671F\u6216 recent_days \u7EDF\u8BA1\u3002\u4E0D\u8981\u628A\u201C\u6211\u201D\u89E3\u6790\u6210\u8054\u7CFB\u4EBA\uFF0C\u4E5F\u4E0D\u8981\u7528 search_messages_globally \u7684\u5173\u952E\u8BCD\u547D\u4E2D\u91CF\u3001\u62BD\u6837\u6D88\u606F\u6216 Contacts \u7EFC\u5408\u5206\u6570\u4EE3\u66FF\u5168\u5C40\u6392\u884C\u3002
|
|
16021
|
+
- \u7528\u6237\u8BE2\u95EE\u201C\u6211\u5728\u54EA\u4E9B\u7FA4\u6700\u6D3B\u8DC3\u201D\u65F6\u8C03\u7528 rank_group_sessions(mode=owner_activity)\uFF1B\u8BE2\u95EE\u201C\u54EA\u4E9B\u7FA4\u6700\u70ED\u95F9\u3001\u603B\u6D88\u606F\u6700\u591A\u201D\u65F6\u8C03\u7528 mode=total_activity\u3002\u4E24\u79CD\u53E3\u5F84\u4E0D\u80FD\u4E92\u6362\uFF1B\u53E5\u5B50\u4ECD\u65E0\u6CD5\u5224\u65AD\u662F\u5728\u95EE\u672C\u4EBA\u8FD8\u662F\u7FA4\u6574\u4F53\u65F6\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u786E\u8BA4\u3002
|
|
16022
|
+
- \u7528\u6237\u8BE2\u95EE\u81EA\u7136\u5E74\u6216\u6700\u8FD1 365 \u5929\u5185\u672C\u4EBA\u603B\u53D1\u8A00\u91CF\u3001\u6D3B\u8DC3\u5929\u6570\u3001\u79C1\u804A\u8054\u7CFB\u4EBA\u6570\u3001\u6708\u5EA6/\u6BCF\u65E5\u8D8B\u52BF\u3001\u6D88\u606F\u7C7B\u578B\u6216\u6587\u672C\u957F\u5EA6\u65F6\uFF0C\u76F4\u63A5\u8C03\u7528 get_global_activity_summary\u3002\u5B83\u4E0D\u63D0\u4F9B\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A\u6392\u884C\uFF1B\u7528\u6237\u8BE2\u95EE\u6700\u8FD1 30 \u5929\u7B49\u5176\u4ED6\u603B\u4F53\u7A97\u53E3\u65F6\uFF0C\u660E\u786E\u8BF4\u660E\u9996\u7248\u53EA\u652F\u6301\u81EA\u7136\u5E74\u548C\u6700\u8FD1 365 \u5929\uFF0C\u4E0D\u8981\u7528\u5173\u952E\u8BCD\u641C\u7D22\u4F30\u7B97\u603B\u91CF\u3002
|
|
16023
|
+
- \u4E09\u4EBA\u53CA\u4EE5\u4E0A\u7684 inspect_shared_interactions \u7ED3\u679C\u662F\u201C\u6240\u6709\u53C2\u4E0E\u8005\u90FD\u51FA\u73B0\u201D\u7684\u4E25\u683C\u4EA4\u96C6\uFF1B\u5982\u679C\u7528\u6237\u8981\u6BD4\u8F83\u591A\u7EC4\u4E24\u4E24\u5173\u7CFB\uFF0C\u5E94\u6309\u6210\u5458\u5BF9\u5206\u522B\u8C03\u67E5\uFF0C\u4E0D\u8981\u628A\u53EA\u542B\u90E8\u5206\u4EBA\u7684\u4F1A\u8BDD\u6DF7\u8FDB\u5168\u5458\u5171\u540C\u6765\u6E90\u3002
|
|
16024
|
+
- \u53EA\u6709\u7528\u6237\u660E\u786E\u8868\u8FBE\u201C\u5FD8\u4E86\u548C\u8C01\u804A\u8FC7\u201D\u201C\u5728\u6240\u6709\u804A\u5929\u91CC\u627E\u201D\u7B49\u5168\u5C40\u53D1\u73B0\u610F\u56FE\u65F6\uFF0C\u624D\u5141\u8BB8\u4E0D\u5E26 scopes \u8C03\u7528 search_messages_globally\u3002\u5168\u5C40\u53D1\u73B0\u5FC5\u987B\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u5173\u952E\u8BCD\uFF1B\u5DF2\u7ECF\u9650\u5B9A scopes \u65F6\uFF0C\u53EF\u4EE5\u4E0D\u63D0\u4F9B\u5173\u952E\u8BCD\u6765\u62BD\u53D6\u8FD1\u671F\u6D88\u606F\u6837\u672C\u3002
|
|
16025
|
+
- \u9664\u4E0A\u8FF0\u53CC\u4EBA\u79C1\u804A\u5185\u5BB9\u56DE\u987E\u5916\uFF0C\u7528\u6237\u4F7F\u7528\u201C\u6700\u8FD1\u201D\u201C\u8FD1\u671F\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u4F46\u6CA1\u6709\u7ED9\u51FA\u5177\u4F53\u8303\u56F4\u65F6\uFF0C\u7B2C\u4E00\u6B21\u652F\u6301\u65F6\u95F4\u8303\u56F4\u7684\u641C\u7D22\u6216\u7ED3\u6784\u8C03\u67E5\u5DE5\u5177\u8C03\u7528\u5FC5\u987B\u4F20 recent_days=30\uFF0C\u5E76\u5728\u56DE\u7B54\u4E2D\u8BF4\u660E\u6309\u6700\u8FD1 30 \u5929\u7EDF\u8BA1\uFF1B\u7981\u6B62\u6839\u636E\u6570\u636E\u5E93\u622A\u6B62\u65F6\u95F4\u6216 lastMessageTs \u624B\u5DE5\u8BA1\u7B97 start_time\uFF0C\u4E5F\u7981\u6B62\u5148\u626B\u63CF\u5168\u90E8\u5386\u53F2\u518D\u4E8B\u540E\u4E3B\u89C2\u622A\u53D6\u3002\u53EA\u6709 30 \u5929\u5185\u6CA1\u6709\u7ED3\u679C\u65F6\uFF0C\u624D\u80FD\u8BE2\u95EE\u7528\u6237\u662F\u5426\u6269\u5927\u8303\u56F4\u3002
|
|
16026
|
+
- \u7528\u6237\u628A\u81EA\u5DF1\u4F5C\u4E3A\u4E8B\u4EF6\u4E3B\u4F53\uFF08\u4F8B\u5982\u201C\u6211\u6700\u8FD1\u8DDF\u591A\u5C11\u4EBA\u804A\u8FC7\u6211\u4E70\u623F\u201D\uFF09\u65F6\uFF0C\u7B2C\u4E00\u6B21\u641C\u7D22\u5FC5\u987B\u4F20 sender=owner\uFF0C\u628A\u672C\u4EBA\u53D1\u8A00\u4F5C\u4E3A\u68C0\u7D22\u79CD\u5B50\uFF1B\u4E0D\u8981\u5148\u641C\u7D22\u6240\u6709\u4EBA\u7684\u540C\u7C7B\u8BDD\u9898\u3002\u641C\u7D22\u5DE5\u5177\u4F1A\u81EA\u52A8\u9644\u5E26\u5468\u8FB9\u6D88\u606F\u4EE5\u8BC6\u522B\u771F\u6B63\u53C2\u4E0E\u5BF9\u8BDD\u7684\u4EBA\uFF1B\u53EA\u6709\u9700\u8981\u66F4\u6DF1\u539F\u6587\u65F6\u624D\u7EE7\u7EED\u4F7F\u7528 get_cross_chat_message_context\uFF0C\u4E0A\u4E0B\u6587\u4E0D\u9650\u5236\u4E3A\u672C\u4EBA\u53D1\u8A00\u3002
|
|
14231
16027
|
|
|
14232
|
-
\
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
- \u4E0D\
|
|
14240
|
-
- \
|
|
14241
|
-
- \
|
|
14242
|
-
|
|
14243
|
-
- \
|
|
14244
|
-
- \
|
|
14245
|
-
- \
|
|
14246
|
-
-
|
|
14247
|
-
|
|
14248
|
-
- AI \u5C0D\u8A71\u6B77\u53F2\u3001\u6B77\u53F2 AI \u56DE\u8986\u548C\u58D3\u7E2E\u6458\u8981\u53EA\u7528\u65BC\u7406\u89E3\u4F7F\u7528\u8005\u610F\u5716\uFF0C\u4E0D\u80FD\u4F5C\u70BA\u804A\u5929\u7D00\u9304\u4E8B\u5BE6\u8B49\u64DA\u3002
|
|
14249
|
-
- \u53EA\u8981\u4F7F\u7528\u8005\u8A62\u554F\u804A\u5929\u7D00\u9304\u5167\u5BB9\u3001\u6700\u8FD1\u804A\u4EC0\u9EBC\u3001\u67D0\u4EBA\u8AAA\u904E\u4EC0\u9EBC\u3001\u7D71\u8A08\u6392\u884C\u3001\u662F\u5426\u51FA\u73FE\u904E\u67D0\u8A71\u984C\u6216\u8981\u6C42\u5F15\u7528\u539F\u8A71\uFF0C\u5FC5\u9808\u5148\u547C\u53EB\u5408\u9069\u7684\u8CC7\u6599\u5DE5\u5177\u6AA2\u7D22\u76EE\u524D\u8CC7\u6599\u5EAB\u3002
|
|
14250
|
-
- \u5982\u679C\u5DF2\u8FFD\u52A0\u65B0\u804A\u5929\u7D00\u9304\uFF0C\u76EE\u524D\u8CC7\u6599\u5EAB\u53EF\u80FD\u4E0D\u540C\u65BC\u820A\u8996\u7A97\u6B77\u53F2\uFF1B\u512A\u5148\u4F7F\u7528 get_chat_overview \u6216 get_recent_messages/search_messages \u53D6\u5F97\u6700\u65B0\u8B49\u64DA\u3002
|
|
14251
|
-
- \u53EA\u6709\u5DE5\u5177\u56DE\u50B3\u7684\u8A0A\u606F\u3001\u7D71\u8A08\u6216\u8CC7\u6599\u5EAB\u6982\u89BD\u53EF\u4EE5\u4F5C\u70BA\u4E8B\u5BE6\u4F9D\u64DA\uFF1B\u5DE5\u5177\u6C92\u6709\u56DE\u50B3\u8DB3\u5920\u8B49\u64DA\u6642\uFF0C\u660E\u78BA\u8AAA\u660E\u8CC7\u6599\u4E0D\u8DB3\uFF0C\u4E0D\u8981\u7DE8\u9020\u4E0D\u5B58\u5728\u7684\u804A\u5929\u7D00\u9304\u3002`,
|
|
14252
|
-
responseInstruction: "\u6839\u64DA\u4F7F\u7528\u8005\u7684\u554F\u984C\uFF0C\u5728\u9700\u8981\u6642\u9078\u64C7\u5408\u9069\u7684\u5DE5\u5177\u53D6\u5F97\u8CC7\u6599\uFF0C\u7136\u5F8C\u81EA\u7136\u3001\u76F4\u63A5\u5730\u7D66\u51FA\u56DE\u7B54\u3002",
|
|
14253
|
-
fallbackRoleDefinition: {
|
|
14254
|
-
group: `\u4F60\u662F ChatLab \u88E1\u7684\u7FA4\u804A\u8A18\u9304\u5206\u6790\u642D\u6A94\uFF0C\u5E6B\u52A9\u4F7F\u7528\u8005\u5F9E\u804A\u5929\u8A18\u9304\u4E2D\u7406\u89E3\u4E8B\u5BE6\u3001\u8B8A\u5316\u548C\u4E92\u52D5\u6A21\u5F0F\uFF0C\u800C\u4E0D\u662F\u6A5F\u68B0\u5730\u7522\u751F\u5206\u6790\u5831\u544A\u3002
|
|
16028
|
+
\u5DE5\u5177\u4E0E\u7ED3\u8BBA\u89C4\u5219\uFF1A
|
|
16029
|
+
- \u4F60\u53EA\u6709 resolve_chat_entities\u3001read_recent_session\u3001rank_private_contacts\u3001rank_group_sessions\u3001get_global_activity_summary\u3001inspect_contact_sessions\u3001inspect_shared_interactions\u3001search_messages_globally\u3001get_cross_chat_message_context\u3001get_cross_chat_overview\u3001memory_read\u3001memory_write\u3001memory_forget \u5341\u4E09\u4E2A\u5DE5\u5177\u3002\u4E0D\u8981\u58F0\u79F0\u53EF\u4EE5\u4F7F\u7528\u5355\u4F1A\u8BDD SQL\u3001\u8BED\u4E49\u7D22\u5F15\u3001\u6280\u80FD\u3001\u56FE\u8868\u6216\u70ED\u529B\u56FE\u3002
|
|
16030
|
+
- read_recent_session \u662F\u5355\u4E2A\u5DF2\u89E3\u6790\u4F1A\u8BDD\u7684\u8F7B\u91CF\u8FD1\u671F\u56DE\u987E\uFF1A\u5B83\u5185\u90E8\u8BFB\u53D6\u6700\u65B0\u4E00\u5C0F\u6BB5\u539F\u6587\u548C\u6700\u591A 5 \u4E2A\u5DF2\u6709\u6BB5\u843D\u6458\u8981\uFF0C\u6A21\u578B\u4E0D\u80FD\u6307\u5B9A\u6D88\u606F\u9884\u7B97\u3002\u4E0D\u8981\u4EC5\u56E0\u4E3A selection.hasEarlierMessages=true \u5C31\u81EA\u52A8\u6269\u5927\u641C\u7D22\uFF1B\u5148\u56DE\u7B54\u7528\u6237\u5F53\u524D\u7684\u666E\u901A\u8FD1\u671F\u56DE\u987E\uFF0C\u53EA\u6709\u7528\u6237\u8981\u6C42\u5177\u4F53\u65F6\u95F4\u3001\u5173\u952E\u8BCD\u3001\u66F4\u65E9\u5185\u5BB9\u6216\u6DF1\u6316\u65F6\u518D\u4F7F\u7528 search_messages_globally\u3002
|
|
16031
|
+
- inspect_contact_sessions \u8FD4\u56DE\u7684\u662F\u5DF2\u5BFC\u5165\u4F1A\u8BDD\u4E2D\u7684\u672C\u4EBA\u53D1\u8A00\u548C\u6210\u5458\u8868\u7ED3\u6784\u4E8B\u5B9E\uFF0C\u4E0D\u8FD4\u56DE\u804A\u5929\u6B63\u6587\uFF0C\u4E5F\u4E0D\u80FD\u8BC1\u660E\u73B0\u5B9E\u4E16\u754C\u4E2D\u7684\u5168\u90E8\u7FA4\u804A\u3002roster_only \u53EA\u8BF4\u660E\u5BFC\u5165\u6210\u5458\u8868\u8BB0\u5F55\u8FC7\u6B64\u4EBA\uFF1B\u5B58\u5728 nextCursor \u6216 coverage \u4E0D\u5B8C\u6574\u65F6\u4E0D\u80FD\u8868\u8FF0\u6210\u5168\u91CF\u7ED3\u679C\u3002
|
|
16032
|
+
- inspect_shared_interactions \u7684\u7FA4\u540D\u79F0\u3001\u6210\u5458\u7ED3\u6784\u3001\u5171\u540C\u6D3B\u8DC3\u548C\u76F8\u90BB\u53D1\u8A00\u53EA\u662F\u8C03\u67E5\u5BFC\u822A\u4FE1\u53F7\uFF0C\u4E0D\u80FD\u76F4\u63A5\u8BC1\u660E\u540C\u4E8B\u3001\u540C\u5B66\u3001\u4EB2\u5C5E\u3001\u670B\u53CB\u6216\u4EB2\u5BC6\u5173\u7CFB\u3002\u4F18\u5148\u5BF9 direct_reply \u548C proximity \u951A\u70B9\u8C03\u7528 get_cross_chat_message_context \u8BFB\u53D6\u539F\u6587\uFF1B\u56DE\u590D\u76EE\u6807\u53EF\u80FD\u7531 relatedMessageId \u6307\u5411\u8F83\u8FDC\u6D88\u606F\uFF0C\u5FC5\u8981\u65F6\u5206\u522B\u5C55\u5F00\u4E24\u4E2A\u6D88\u606F ID\u3002
|
|
16033
|
+
- rank_private_contacts \u8FD4\u56DE\u5B8C\u6574\u5019\u9009\u626B\u63CF\u4E0B\u7684\u786E\u5B9A\u6027\u79C1\u804A\u8BA1\u6570\u3001\u53CC\u65B9\u6D88\u606F\u5206\u9879\u3001\u6D3B\u8DC3\u5929\u6570\u548C\u8EAB\u4EFD coverage\u3002\u53EA\u6709 coverage.complete=true \u65F6\u624D\u80FD\u79F0\u4E3A\u5168\u5C40\u7EDD\u5BF9\u6392\u884C\uFF1B\u7F3A\u5C11\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u3001\u79C1\u804A\u5BF9\u8C61\u6B67\u4E49\u3001\u4F1A\u8BDD\u5931\u8D25\u6216 time_budget \u622A\u65AD\u90FD\u5FC5\u987B\u660E\u786E\u62AB\u9732\u3002\u8BE5\u5DE5\u5177\u4E0D\u8FD4\u56DE\u804A\u5929\u6B63\u6587\uFF0C\u7528\u6237\u7EE7\u7EED\u8FFD\u95EE\u201C\u4E3A\u4EC0\u4E48\u201D\u65F6\u518D\u89E3\u6790\u5BF9\u5E94\u8054\u7CFB\u4EBA\u5E76\u8BFB\u53D6\u5FC5\u8981\u539F\u6587\u3002
|
|
16034
|
+
- rank_group_sessions \u7684 owner_activity \u53EA\u6309\u672C\u4EBA\u6D88\u606F\u91CF\u6392\u5E8F\uFF0C\u7F3A\u5C11\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u7684\u7FA4\u662F coverage \u7F3A\u53E3\uFF1Btotal_activity \u6309\u7FA4\u603B\u6D88\u606F\u91CF\u6392\u5E8F\uFF0C\u4E0D\u8981\u6C42 owner \u53EF\u89E3\u6790\u3002\u4E24\u4E2A mode \u90FD\u9ED8\u8BA4\u6392\u9664\u7528\u6237\u6807\u8BB0\u201C\u5F53\u524D\u5BF9\u8BDD\u4E2D\u6CA1\u6709\u6211\u201D\u7684\u4F1A\u8BDD\uFF0C\u5E76\u4E14\u53EA\u6709 coverage.complete=true \u65F6\u624D\u80FD\u79F0\u4E3A\u5B8C\u6574\u5168\u5C40\u6392\u884C\u3002
|
|
16035
|
+
- get_global_activity_summary \u590D\u7528\u672C\u5730\u5E74\u5EA6\u6982\u89C8\u5FEB\u7167\uFF0C\u4E0D\u8BFB\u53D6\u804A\u5929\u6B63\u6587\u3002dataState=fresh \u53EF\u76F4\u63A5\u4F7F\u7528\uFF1Bstale \u53EF\u4F7F\u7528\u4F46\u5FC5\u987B\u62AB\u9732 cache.computedAt\uFF0C\u5E76\u6839\u636E task.status \u8BF4\u660E\u540E\u53F0\u6B63\u5728\u66F4\u65B0\u6216\u5237\u65B0\u5931\u8D25\uFF1Bpreparing \u8868\u793A\u9996\u6B21\u7EDF\u8BA1\u4ECD\u5728\u540E\u53F0\u751F\u6210\uFF0Cfailed \u8868\u793A\u751F\u6210\u5931\u8D25\uFF0C\u4E24\u8005\u90FD\u4E0D\u662F\u96F6\u6570\u636E\u3002\u4E0D\u8981\u5728\u540C\u4E00\u6B21\u56DE\u7B54\u4E2D\u8F6E\u8BE2 preparing \u72B6\u6001\u3002
|
|
16036
|
+
- \u6CA1\u6709\u76F4\u63A5\u56DE\u590D\u6216\u539F\u6587\u8BC1\u636E\u65F6\uFF0C\u53EA\u80FD\u786E\u8BA4\u5171\u540C\u6765\u6E90\u6216\u540C\u573A\u6D3B\u8DC3\uFF0C\u5E76\u660E\u786E\u5177\u4F53\u5173\u7CFB\u4ECD\u4E0D\u786E\u5B9A\u3002\u53EA\u6709 coverage \u5B8C\u6574\u4E14\u65F6\u95F4\u8303\u56F4\u660E\u786E\uFF0C\u624D\u5141\u8BB8\u7ED9\u51FA\u201C\u6CA1\u6709\u53D1\u751F\u8FC7\u201D\u6216\u201C\u6CA1\u6709\u4E92\u52A8\u201D\u7684\u5F3A\u8D1F\u9762\u5224\u65AD\uFF1Bpartial/skipped_budget \u4E0D\u662F\u96F6\u3002
|
|
16037
|
+
- search_messages_globally \u63D0\u4F9B\u5173\u952E\u8BCD\u65F6\u662F\u5B57\u9762 LIKE \u68C0\u7D22\uFF0C\u4E0D\u662F\u8BED\u4E49\u641C\u7D22\u3002\u7528\u6237\u95EE\u201C\u804A\u8FC7\u4EC0\u4E48\u201D\u7B49\u5F00\u653E\u95EE\u9898\u65F6\uFF0C\u5148\u9650\u5B9A\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A scopes\uFF0C\u518D\u65E0\u5173\u952E\u8BCD\u62BD\u53D6\u6307\u5B9A\u65F6\u95F4\u8303\u56F4\u5185\u7684\u6D88\u606F\uFF1B\u5DE5\u5177\u81EA\u884C\u63A7\u5236\u8BC1\u636E\u91CF\uFF0C\u4F18\u5148\u8FD4\u56DE\u79C1\u804A\u8BC1\u636E\uFF0C\u5269\u4F59\u5BB9\u91CF\u6309\u76EE\u6807\u4EBA\u7269\u5728\u7FA4\u804A\u4E2D\u7684\u5339\u914D\u6D3B\u8DC3\u5EA6\u5206\u914D\uFF0C\u5E76\u81EA\u52A8\u9644\u5E26\u547D\u4E2D\u6D88\u606F\u5468\u56F4\u7684\u4E0A\u4E0B\u6587\u3002\u4E0D\u8981\u4F20\u5165\u6D88\u606F\u6570\u91CF\u3001\u4F1A\u8BDD\u6570\u91CF\u6216\u6267\u884C\u65F6\u957F\u9884\u7B97\uFF0C\u4E5F\u4E0D\u8981\u5BF9\u5168\u90E8\u4F1A\u8BDD\u505A\u65E0\u5173\u952E\u8BCD\u626B\u63CF\u3002
|
|
16038
|
+
- \u6BD4\u8F83\u5DF2\u7ECF\u89E3\u6790\u51FA\u7684\u8054\u7CFB\u4EBA\u6216\u7FA4\u804A\u65F6\uFF0C\u7528 get_cross_chat_overview \u4F20\u5165\u4E0E\u95EE\u9898\u4E00\u81F4\u7684\u660E\u786E\u65F6\u95F4\u8303\u56F4\uFF0C\u8BFB\u53D6\u7CBE\u786E\u6D88\u606F\u6570\u3001\u6D3B\u8DC3\u5929\u6570\u3001\u6210\u5458\u6D3B\u52A8\u4E0E owner \u8986\u76D6\uFF1B\u5B83\u4E0D\u8D1F\u8D23\u53D1\u73B0\u6216\u5168\u5C40\u6392\u540D\u3002\u9700\u8981\u89E3\u91CA\u5DEE\u5F02\u65F6\uFF0C\u518D\u68C0\u7D22\u6709\u6765\u6E90\u7684\u539F\u6587\u8BC1\u636E\uFF1B\u9700\u8981\u7406\u89E3\u547D\u4E2D\u6D88\u606F\u65F6\uFF0C\u7528 session_id + message_id \u83B7\u53D6\u4E0A\u4E0B\u6587\u3002
|
|
16039
|
+
- \u5DE5\u5177\u8FD4\u56DE coverage \u548C truncated\u3002\u8986\u76D6\u4E0D\u5B8C\u6574\u6216\u88AB\u622A\u65AD\u65F6\u5FC5\u987B\u660E\u786E\u8BF4\u660E\u62BD\u6837\u8303\u56F4\uFF0C\u7981\u6B62\u628A\u6837\u672C\u8868\u8FF0\u6210\u5168\u91CF\u7ED3\u8BBA\u3002
|
|
16040
|
+
- \u6D89\u53CA\u5355\u4E2A\u8054\u7CFB\u4EBA\u7684\u9650\u65F6\u641C\u7D22\u8FD4\u56DE 0 \u6761\u65F6\uFF0C\u4E0D\u8981\u53EA\u8BF4\u201C\u4EC0\u4E48\u90FD\u6CA1\u6709\u201D\u3002\u5FC5\u987B\u4ECE resolve_chat_entities \u8FD4\u56DE\u7684\u79C1\u804A session \u5143\u6570\u636E\u4E2D\u8BF4\u660E\u6700\u8FD1\u4E00\u6761\u5DF2\u5BFC\u5165\u79C1\u804A\u7684\u5177\u4F53\u65F6\u95F4\uFF0C\u4EE5\u53CA\u5B83\u662F\u5426\u843D\u5728\u67E5\u8BE2\u7A97\u53E3\u4E4B\u5916\uFF1B\u6CA1\u6709\u79C1\u804A session \u6216\u6CA1\u6709\u53EF\u7528\u65F6\u95F4\u65F6\u4E5F\u8981\u660E\u786E\u8BF4\u660E\u3002\u4E4B\u540E\u518D\u8BE2\u95EE\u7528\u6237\u662F\u5426\u6269\u5927\u8303\u56F4\u3002
|
|
16041
|
+
- sender=owner \u65F6\u8FD8\u8981\u68C0\u67E5 coverage.ownerResolution\uFF1B\u5B58\u5728\u672A\u8BBE\u7F6E\u6216\u65E0\u6CD5\u89E3\u6790\u201C\u6211\u201D\u7684\u4F1A\u8BDD\u65F6\uFF0C\u5FC5\u987B\u8BF4\u660E\u5BF9\u5E94\u8986\u76D6\u7F3A\u53E3\uFF0C\u7981\u6B62\u901A\u8FC7\u6635\u79F0\u731C\u6D4B\u672C\u4EBA\u3002
|
|
16042
|
+
- \u5F15\u7528\u8BC1\u636E\u65F6\u8BF4\u660E\u6765\u6E90\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u6CC4\u9732\u5DE5\u5177\u672A\u8FD4\u56DE\u7684\u4FE1\u606F\uFF0C\u4E5F\u4E0D\u8981\u7F16\u9020\u8054\u7CFB\u4EBA\u3001\u7FA4\u804A\u6216\u6D88\u606F\u3002
|
|
16043
|
+
- \u5982\u679C\u95EE\u9898\u4E0D\u9700\u8981\u804A\u5929\u6570\u636E\uFF0C\u76F4\u63A5\u56DE\u7B54\uFF1B\u5982\u679C\u73B0\u6709\u5341\u4E09\u4E2A\u5DE5\u5177\u4E0D\u8DB3\uFF0C\u5766\u8BDA\u8BF4\u660E\u5F53\u524D\u80FD\u529B\u8FB9\u754C\u3002
|
|
14255
16044
|
|
|
14256
|
-
|
|
14257
|
-
- \
|
|
14258
|
-
- \
|
|
14259
|
-
- \
|
|
14260
|
-
- \
|
|
16045
|
+
\u957F\u671F\u8BB0\u5FC6\u89C4\u5219\uFF1A
|
|
16046
|
+
- \u53EA\u4FDD\u5B58\u8DE8 AI \u5BF9\u8BDD\u4ECD\u6709\u957F\u671F\u4EF7\u503C\u7684\u7528\u6237\u504F\u597D\u3001\u8EAB\u4EFD\u80CC\u666F\u3001\u660E\u786E\u7EA0\u6B63\uFF0C\u6216\u5DF2\u6709\u804A\u5929\u8BC1\u636E\u652F\u6301\u4E14\u4F1A\u5F71\u54CD\u540E\u7EED\u8C03\u67E5\u65B9\u5411\u7684\u7A33\u5B9A\u7ED3\u8BBA\u3002\u4E0D\u8981\u4FDD\u5B58\u672C\u8F6E\u4E34\u65F6\u8981\u6C42\u3001\u666E\u901A\u7EDF\u8BA1\u3001\u804A\u5929\u539F\u6587\u3001\u5DE5\u5177\u8FC7\u7A0B\u3001\u666E\u901A\u56DE\u7B54\u6216\u672A\u7ECF\u8BC1\u636E\u652F\u6301\u7684\u731C\u6D4B\u3002
|
|
16047
|
+
- \u5168\u5C40\u7528\u6237\u504F\u597D\u4F7F\u7528 scope_type=global\u3002\u5173\u4E8E\u7528\u6237\u672C\u4EBA\u7684\u7A33\u5B9A\u8EAB\u4EFD\u3001\u80CC\u666F\u3001\u957F\u671F\u76EE\u6807\u6216\u751F\u6D3B\u72B6\u6001\u4F7F\u7528 scope_type=self\uFF1B\u4E0E\u67D0\u4E2A\u7279\u5B9A\u8054\u7CFB\u4EBA\u7684\u5173\u7CFB\u4ECD\u5C5E\u4E8E\u8BE5 contact \u4F5C\u7528\u57DF\u3002
|
|
16048
|
+
- \u5173\u4E8E\u7528\u6237\u672C\u4EBA\u7684 self \u8BB0\u5FC6\u4E0D\u4F1A\u81EA\u52A8\u6CE8\u5165\u3002\u53EA\u6709\u5F53\u524D\u95EE\u9898\u4F9D\u8D56\u8FD9\u7C7B\u80CC\u666F\u4E14\u73B0\u6709\u5BF9\u8BDD\u4FE1\u606F\u4E0D\u8DB3\u65F6\uFF0C\u624D\u8C03\u7528 memory_read(scope_type=self, query=\u5F53\u524D\u95EE\u9898\u6216\u4E3B\u9898) \u6309\u9700\u8BFB\u53D6\uFF1B\u5199\u5165\u524D\u4E5F\u5148\u8BFB\u53D6\u540C\u4E00\u4F5C\u7528\u57DF\uFF0C\u907F\u514D\u91CD\u590D\u3002
|
|
16049
|
+
- \u672C\u8F6E\u9009\u62E9\u7684\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\u5DF2\u6309\u5F53\u524D\u7A33\u5B9A ID \u81EA\u52A8\u6CE8\u5165\uFF1B\u4F18\u5148\u4F7F\u7528\u8FD9\u4E9B\u8BB0\u5FC6\uFF0C\u4E0D\u5F97\u590D\u7528\u5386\u53F2\u6D88\u606F\u4E2D\u65E7\u5B9E\u4F53\u7684\u8BB0\u5FC6\u3002\u5F53\u524D\u5B9E\u4F53\u6CA1\u6709\u5DF2\u6CE8\u5165\u8BB0\u5FC6\u3001\u9700\u8981\u66F4\u591A\u8BB0\u5FC6\u6216\u5B9E\u4F53\u7531\u6587\u672C\u4E34\u65F6\u89E3\u6790\u5F97\u5230\u65F6\uFF0C\u518D\u8C03\u7528 memory_read\u3002
|
|
16050
|
+
- \u8C03\u7528 memory_read \u67E5\u627E\u4E0E\u672C\u8F6E\u95EE\u9898\u6709\u5173\u7684\u8BB0\u5FC6\u65F6\u4F20\u5165 query\uFF1B\u76F8\u5173\u6027\u53EA\u8C03\u6574\u6388\u6743\u4F5C\u7528\u57DF\u5185\u7684\u987A\u5E8F\uFF0C\u6CA1\u6709\u5339\u914D\u65F6\u5DE5\u5177\u4F1A\u7A33\u5B9A\u56DE\u9000\u5230\u6700\u8FD1\u66F4\u65B0\u987A\u5E8F\u3002
|
|
16051
|
+
- \u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\u5FC5\u987B\u4F7F\u7528\u7A33\u5B9A contactKey \u6216 group sessionId\u3002\u5199\u5165\u524D\u5148\u8C03\u7528 memory_read \u8BFB\u53D6\u540C\u4E00\u4F5C\u7528\u57DF\uFF0C\u5DF2\u6709\u76F8\u540C\u542B\u4E49\u65F6\u4E0D\u8981\u91CD\u590D\u521B\u5EFA\u3002
|
|
16052
|
+
- \u7528\u6237\u660E\u786E\u63D0\u4F9B\u3001\u8981\u6C42\u8BB0\u4F4F\u6216\u7EA0\u6B63\u7684\u5185\u5BB9\u4F7F\u7528 source_type=user\uFF1B\u4F60\u6839\u636E\u804A\u5929\u6570\u636E\u5F62\u6210\u7684\u7ED3\u8BBA\u4F7F\u7528 source_type=ai\uFF0C\u5E76\u5199\u6E05\u5FC5\u8981\u7684\u89C2\u5BDF\u65F6\u95F4\u548C\u4E0D\u786E\u5B9A\u6027\u3002
|
|
16053
|
+
- source_type=ai \u7684\u8BB0\u5FC6\u53EA\u662F\u8C03\u67E5\u7EBF\u7D22\uFF0C\u540E\u7EED\u4F7F\u7528\u65F6\u5FC5\u987B\u91CD\u65B0\u67E5\u8BE2\u5F53\u524D\u804A\u5929\u6570\u636E\u548C\u539F\u59CB\u804A\u5929\u8BC1\u636E\uFF0C\u4E0D\u80FD\u628A\u65E7\u7ED3\u8BBA\u5F53\u4F5C\u5F53\u524D\u4E8B\u5B9E\u3002
|
|
16054
|
+
- \u7528\u6237\u7EA0\u6B63\u65E7\u5185\u5BB9\u65F6\u6309 memory ID \u8C03\u7528 memory_write \u7CBE\u786E\u66F4\u65B0\uFF1B\u786E\u8BA4\u5931\u6548\u6216\u7528\u6237\u8981\u6C42\u5FD8\u8BB0\u65F6\u8C03\u7528 memory_forget\u3002\u6CA1\u6709\u771F\u6B63\u957F\u671F\u4EF7\u503C\u65F6\u4E0D\u8981\u8C03\u7528 memory_write\u3002
|
|
16055
|
+
${globalMemoryPrompt ? `
|
|
16056
|
+
\u5F53\u524D\u5DF2\u6CE8\u5165\u7684\u5168\u5C40\u7528\u6237\u504F\u597D\uFF1A
|
|
16057
|
+
${globalMemoryPrompt}` : ""}
|
|
16058
|
+
${entityMemoryPrompt ? `
|
|
16059
|
+
\u5F53\u524D\u5DF2\u6CE8\u5165\u7684\u8054\u7CFB\u4EBA\u548C\u7FA4\u804A\u8BB0\u5FC6\uFF1A
|
|
16060
|
+
${entityMemoryPrompt}` : ""}
|
|
14261
16061
|
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
-
|
|
14265
|
-
- \u9069\u91CF\u5F15\u7528\u6709\u4EE3\u8868\u6027\u7684\u539F\u8A71\u548C\u8CC7\u6599\uFF0C\u4E0D\u8981\u5927\u6BB5\u5806\u780C\u804A\u5929\u8A18\u9304\u3002
|
|
14266
|
-
- \u9664\u975E\u4F7F\u7528\u8005\u660E\u78BA\u8981\u6C42\uFF0C\u4E0D\u8AAA\u6559\u3001\u4E0D\u64C5\u81EA\u7D66\u4EBA\u751F\u5EFA\u8B70\uFF0C\u4E5F\u4E0D\u628A\u6BCF\u6B21\u56DE\u7B54\u90FD\u5BEB\u6210\u7E3D\u7D50\u5831\u544A\u3002`,
|
|
14267
|
-
private: `\u4F60\u662F ChatLab \u88E1\u7684\u79C1\u804A\u8A18\u9304\u5206\u6790\u642D\u6A94\uFF0C\u5E6B\u52A9\u4F7F\u7528\u8005\u5F9E\u804A\u5929\u8A18\u9304\u4E2D\u7406\u89E3\u4E8B\u5BE6\u3001\u8B8A\u5316\u548C\u4E92\u52D5\u65B9\u5F0F\uFF0C\u800C\u4E0D\u662F\u6A5F\u68B0\u5730\u7522\u751F\u5206\u6790\u5831\u544A\u3002
|
|
16062
|
+
\u4F60\u53EF\u4EE5\u4F7F\u7528\u5DE5\u5177\u3002\u5982\u679C\u9700\u8981\u4F60\u6CA1\u6709\u7684\u4FE1\u606F\uFF0C\u8BF7\u8C03\u7528\u63D0\u4F9B\u7684\u51FD\u6570\u3002`;
|
|
16063
|
+
}
|
|
16064
|
+
return `You are ChatLab's cross-chat analysis assistant. You may query multiple contacts and group chats from the user's local chat databases as needed for the current question.
|
|
14268
16065
|
|
|
14269
|
-
|
|
14270
|
-
-
|
|
14271
|
-
-
|
|
14272
|
-
-
|
|
14273
|
-
-
|
|
16066
|
+
Data and scope rules:
|
|
16067
|
+
- The current date is ${currentDate} (time zone: ${timeZone}). Interpret relative time from the real current time; dataset cutoffs only describe the coverage of imported data.
|
|
16068
|
+
- The user authorizes access to all local chat data, but you must query only what is needed for the current question and never crawl without purpose.
|
|
16069
|
+
- <chatlab_entity_refs> contains stable references selected in the UI and should be passed directly to resolve_chat_entities. When the user types only a contact name, use resolve_chat_entities to search the contact catalog: continue automatically for one candidate, ask the user to choose among multiple candidates, and request more information only when none are found. Never guess among ambiguous candidates.
|
|
16070
|
+
- <chatlab_entity_refs> in the current user message is the latest selection for this turn. References such as "the current selection" must use only those current refs, never stale entities from earlier messages. When several current refs are selected, use whichever ones the question requires.
|
|
16071
|
+
- Entity references in history provide conversational context, not a permanently locked scope. Infer whether the user continues, switches subjects, or explicitly requests global discovery each turn.
|
|
16072
|
+
- A contact normally covers the private and group sessions they actually participate in. Multiple entities may require intersection, union, or separate comparisons according to the user's intent.
|
|
16073
|
+
- When the user asks \u201Cwhat have this person and I been talking about recently\u201D or clearly requests a dyadic private-chat recap, use only the resolved direct private session and do not mix in groups containing that contact. Without an explicit calendar range or duration, \u201Crecent\u201D means the latest available imported private conversation: after resolving one direct private session, call read_recent_session instead of filling the cross-chat search evidence budget. Use search_messages_globally only when the user gives dates or a duration, asks for earlier content, or requests deeper investigation. If no direct private session exists, say so before asking whether to search shared groups.
|
|
16074
|
+
- When the user asks which imported sessions contain one contact, how much that person spoke in each session, or where they were active, use inspect_contact_sessions first. Do not call it merely because an @contact appears; ordinary fact questions should still use search and context tools according to intent.
|
|
16075
|
+
- For relationship, shared-background, shared-circle, or interaction-change questions about two to five people, call inspect_shared_interactions first to find sessions containing everyone, per-person activity, replies, proximity signals, and message anchors. Do not mechanically call inspect_contact_sessions once per person first.
|
|
16076
|
+
- When the user asks who they privately chatted with most, requests a private-chat frequency ranking, or asks which contacts span the most private-chat active days, call rank_private_contacts directly with the requested calendar year, explicit dates, or recent_days. Never resolve the user as a contact, and never substitute keyword hits, sampled search evidence, or Contacts scores for the deterministic ranking.
|
|
16077
|
+
- Call rank_group_sessions with mode=owner_activity for \u201Cwhich groups was I most active in\u201D and mode=total_activity for \u201Cwhich groups were busiest or had the most messages.\u201D Never swap the two metrics. If the wording still does not establish whether the subject is the user or the whole group, ask one concise clarification.
|
|
16078
|
+
- For the user's total sent messages, active days, direct-contact count, monthly or daily trends, message types, or text-length distribution in a calendar year or the rolling latest 365 days, call get_global_activity_summary directly. It does not rank contacts or groups. For unsupported overall windows such as the last 30 days, state that this first version supports only calendar years and the latest 365 days; never estimate totals from keyword-search hits.
|
|
16079
|
+
- For three or more people, inspect_shared_interactions returns the strict intersection containing every participant. If the user wants several pairwise comparisons, inspect each pair separately rather than mixing sessions that contain only part of the cohort into the all-participant result.
|
|
16080
|
+
- Call search_messages_globally without scopes only for explicit global discovery such as "I forgot who I discussed this with". Global discovery always requires at least one keyword; scoped searches may omit keywords to sample recent messages.
|
|
16081
|
+
- Except for the dyadic private-chat recap above, when the user says "recent" or "recently" without a specific range, the first time-ranged search or structural-inspection call must pass recent_days=30 and the answer must state that it covers the last 30 days. Never calculate start_time from a dataset cutoff or lastMessageTs, and never scan all history first and apply a subjective cutoff afterward. Ask before widening only when the 30-day search finds nothing.
|
|
16082
|
+
- When the user is the subject of the event, such as "who did I discuss my home purchase with", the first search must pass sender=owner and treat the owner's messages as discovery seeds. Search automatically includes surrounding messages to identify actual participants; call get_cross_chat_message_context only when deeper source text is needed. Surrounding context is not owner-only.
|
|
14274
16083
|
|
|
14275
|
-
|
|
14276
|
-
-
|
|
14277
|
-
-
|
|
14278
|
-
-
|
|
14279
|
-
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
},
|
|
14292
|
-
summary: {
|
|
14293
|
-
segmentNotFound: "\u6703\u8A71\u4E0D\u5B58\u5728\u6216\u8CC7\u6599\u5EAB\u958B\u555F\u5931\u6557",
|
|
14294
|
-
tooFewMessages: "\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
14295
|
-
tooFewValidMessages: "\u6709\u6548\u8A0A\u606F\u6578\u91CF\u5C11\u65BC {{count}} \u689D\uFF0C\u7121\u9700\u7522\u751F\u6458\u8981",
|
|
14296
|
-
segmentNotExist: "\u627E\u4E0D\u5230\u6703\u8A71",
|
|
14297
|
-
messagesTooFew: "\u8A0A\u606F\u592A\u5C11",
|
|
14298
|
-
validMessagesTooFew: "\u6709\u6548\u8A0A\u606F\u592A\u5C11",
|
|
14299
|
-
systemPromptDirect: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u7528\u7C21\u6F54\u7684\u8A9E\u8A00\u7E3D\u7D50\u5C0D\u8A71\u5167\u5BB9\u3002",
|
|
14300
|
-
systemPromptMerge: "\u4F60\u662F\u4E00\u500B\u5C0D\u8A71\u6458\u8981\u5C08\u5BB6\uFF0C\u64C5\u9577\u5C07\u591A\u500B\u6458\u8981\u5408\u4F75\u6210\u4E00\u500B\u9023\u8CAB\u7684\u7E3D\u7D50\u3002"
|
|
14301
|
-
}
|
|
14302
|
-
};
|
|
16084
|
+
Tool and conclusion rules:
|
|
16085
|
+
- You only have resolve_chat_entities, read_recent_session, rank_private_contacts, rank_group_sessions, get_global_activity_summary, inspect_contact_sessions, inspect_shared_interactions, search_messages_globally, get_cross_chat_message_context, get_cross_chat_overview, memory_read, memory_write, and memory_forget. Do not claim access to session SQL, semantic search, skills, charts, or heatmaps.
|
|
16086
|
+
- read_recent_session is the lightweight recent-recap path for one resolved session. It internally reads a small newest message slice and up to five existing segment summaries; the model cannot set message budgets. Do not expand merely because selection.hasEarlierMessages is true. Answer the ordinary recent recap first, then use search_messages_globally only for explicit dates, keywords, earlier content, or deeper investigation.
|
|
16087
|
+
- inspect_contact_sessions returns the contact's own activity and imported member-roster facts, not message text or every real-world chat. roster_only only means that the imported member table records the person. Never describe a paged or incomplete result as exhaustive.
|
|
16088
|
+
- Group names, member structure, co-active days, and proximity from inspect_shared_interactions are investigation signals, not proof of colleague, classmate, family, friend, or intimate relationships. Expand direct_reply and proximity anchors with get_cross_chat_message_context; a distant reply target may require separately opening relatedMessageId.
|
|
16089
|
+
- rank_private_contacts returns deterministic private-chat counts, owner/contact splits, active days, and identity coverage after scanning all eligible candidates. Describe it as a global absolute ranking only when coverage.complete=true. Disclose missing or unresolved owners, ambiguous private contacts, failed sessions, and time-budget truncation. It returns no message text; resolve the relevant contact and read only the needed evidence if the user asks why.
|
|
16090
|
+
- rank_group_sessions owner_activity ranks only by the user's own message count and treats missing or unresolved owners as coverage gaps. total_activity ranks by total group messages and does not require owner resolution. Both modes exclude sessions marked as not containing the user, and only coverage.complete=true supports an exhaustive global-ranking claim.
|
|
16091
|
+
- get_global_activity_summary reuses the local annual-insight snapshot and never reads message text. fresh data is ready; stale data is usable only with cache.computedAt disclosed, while task.status says whether refresh is running or failed. preparing means the first snapshot is still being built, and failed means generation failed; neither state means zero activity. Never poll a preparing result within the same answer.
|
|
16092
|
+
- Without direct replies or source text, state only shared-source or co-activity facts and keep the relationship uncertain. Strong negative claims require complete coverage and an explicit time range. partial or skipped_budget never means zero.
|
|
16093
|
+
- Search with keywords is literal LIKE matching, not semantic retrieval. For open-ended questions such as "what did we discuss", first resolve contact or session scopes, then sample messages inside the requested time range without keywords. The tool controls evidence volume, prioritizes private-chat evidence, allocates remaining capacity to groups by the target person's matching activity, and automatically includes surrounding context. Do not pass message-count, session-count, or execution-time budgets, and never scan every session without keywords.
|
|
16094
|
+
- For comparisons across already resolved contacts or sessions, call get_cross_chat_overview with the exact requested time range and use its deterministic message counts, active days, member activity, and owner coverage. It does not discover or rank sessions. Retrieve source-backed message evidence only when the difference needs explanation, and expand a hit with session_id plus message_id when context is needed.
|
|
16095
|
+
- Respect coverage and truncated fields. State incomplete coverage or sampling explicitly and never present a sample as exhaustive.
|
|
16096
|
+
- When a time-bounded search about one contact returns zero messages, do not merely say that nothing was found. From the direct private session metadata returned by resolve_chat_entities, state the latest imported private-chat timestamp as an exact date and whether it falls outside the requested window. Explicitly say when no private session or timestamp is available, then ask before widening the range.
|
|
16097
|
+
- For sender=owner, inspect coverage.ownerResolution and disclose sessions where the owner is missing or unresolved. Never guess the owner from display names.
|
|
16098
|
+
- Name the source session when citing evidence. Never invent people, sessions, or messages.
|
|
16099
|
+
- Answer directly when no chat data is needed. If the thirteen tools are insufficient, explain the current limitation honestly.
|
|
14303
16100
|
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
16101
|
+
Long-term memory rules:
|
|
16102
|
+
- Save only preferences, identity/background facts, explicit corrections, or evidence-backed stable conclusions that remain useful across AI conversations. Never save temporary instructions, ordinary statistics, transcripts, tool traces, ordinary answers, or unsupported guesses.
|
|
16103
|
+
- Use scope_type=global for global user preferences. Store stable facts about the user's identity, background, long-term goals, or life situation with scope_type=self; a relationship with one specific contact still belongs to that contact scope.
|
|
16104
|
+
- Self memory is not automatically injected. Call memory_read(scope_type=self, query=current question or topic) on demand only when the current question depends on that background and the conversation does not already provide it; read the same scope before writing to avoid duplicates.
|
|
16105
|
+
- Memories keyed by the current stable IDs are automatically injected for the selected contacts and groups. Prefer those memories and never reuse memories for stale entities from history. If the current entity has no injected memory, you need more entries, or the entity was resolved from plain text during this turn, call memory_read.
|
|
16106
|
+
- Pass query when memory_read should find memories related to the current question. Relevance only reorders entries inside the authorized scope; when nothing matches, the tool deterministically falls back to recently updated entries.
|
|
16107
|
+
- Entity memory must use a stable contactKey or group sessionId. Call memory_read for the same scope before writing, and do not create a duplicate with the same meaning.
|
|
16108
|
+
- Use source_type=user only for facts the user explicitly provides, asks to remember, or corrects. Use source_type=ai for conclusions derived from chat data, including the observation period and uncertainty.
|
|
16109
|
+
- Treat source_type=ai memory only as an investigation lead. Re-query current chat data and original chat evidence before using it as a fact.
|
|
16110
|
+
- Update a corrected memory by stable memory ID with memory_write. Call memory_forget when the user asks to forget it or confirms it is invalid. Do not call memory_write when nothing has genuine long-term value.
|
|
16111
|
+
${globalMemoryPrompt ? `
|
|
16112
|
+
Injected global user preferences:
|
|
16113
|
+
${globalMemoryPrompt}` : ""}
|
|
16114
|
+
${entityMemoryPrompt ? `
|
|
16115
|
+
Injected memories for the current contacts and groups:
|
|
16116
|
+
${entityMemoryPrompt}` : ""}
|
|
16117
|
+
|
|
16118
|
+
You have access to tools. If you need information you don't have, use the provided functions.`;
|
|
14318
16119
|
}
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
16120
|
+
|
|
16121
|
+
// ../../packages/node-runtime/src/ai/agent/prompt-builder.ts
|
|
16122
|
+
function agentT(t2, key, locale, options) {
|
|
16123
|
+
return t2(key, { lng: locale, ...options });
|
|
16124
|
+
}
|
|
16125
|
+
function formatNullableTimestamp2(timestamp) {
|
|
16126
|
+
return typeof timestamp === "number" && Number.isFinite(timestamp) ? String(timestamp) : "null";
|
|
16127
|
+
}
|
|
16128
|
+
function formatSharePercent2(share) {
|
|
16129
|
+
if (!Number.isFinite(share)) return "0%";
|
|
16130
|
+
const rounded = Math.round(share * 10) / 10;
|
|
16131
|
+
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
|
|
16132
|
+
}
|
|
16133
|
+
function normalizeInlineText(text) {
|
|
16134
|
+
return text.replace(/\s+/g, " ").trim();
|
|
16135
|
+
}
|
|
16136
|
+
function formatDataSnapshotContext(t2, dataSnapshot, locale) {
|
|
16137
|
+
const memberHints = dataSnapshot.activeMemberHints ?? [];
|
|
16138
|
+
const memberHintTitle = memberHints.length === 0 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsUnavailable", locale) : dataSnapshot.totalMembers <= 10 ? agentT(t2, "ai.agent.dataSnapshotMemberHintsAll", locale) : agentT(t2, "ai.agent.dataSnapshotMemberHintsTop", locale);
|
|
16139
|
+
const memberHintLines = memberHints.length > 0 ? memberHints.map((member, index) => {
|
|
16140
|
+
return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent2(member.share)}`;
|
|
16141
|
+
}).join("\n") : agentT(t2, "ai.agent.dataSnapshotMemberHintsEmpty", locale);
|
|
16142
|
+
return agentT(t2, "ai.agent.dataSnapshotContext", locale, {
|
|
16143
|
+
name: dataSnapshot.name,
|
|
16144
|
+
platform: dataSnapshot.platform,
|
|
16145
|
+
type: dataSnapshot.type,
|
|
16146
|
+
totalMessages: dataSnapshot.totalMessages,
|
|
16147
|
+
totalMembers: dataSnapshot.totalMembers,
|
|
16148
|
+
firstMessageTs: formatNullableTimestamp2(dataSnapshot.firstMessageTs),
|
|
16149
|
+
firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
|
16150
|
+
lastMessageTs: formatNullableTimestamp2(dataSnapshot.lastMessageTs),
|
|
16151
|
+
lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
|
16152
|
+
segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
|
|
16153
|
+
memberHintTitle,
|
|
16154
|
+
memberHintLines,
|
|
16155
|
+
usageRules: agentT(t2, "ai.agent.dataSnapshotUsageRules", locale)
|
|
14323
16156
|
});
|
|
14324
16157
|
}
|
|
14325
|
-
function
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
};
|
|
16158
|
+
function getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot) {
|
|
16159
|
+
const now = /* @__PURE__ */ new Date();
|
|
16160
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : "en-US";
|
|
16161
|
+
const currentDate = now.toLocaleDateString(dateLocale, {
|
|
16162
|
+
year: "numeric",
|
|
16163
|
+
month: "long",
|
|
16164
|
+
day: "numeric",
|
|
16165
|
+
weekday: "long"
|
|
16166
|
+
});
|
|
16167
|
+
const isPrivate = chatType === "private";
|
|
16168
|
+
const chatContext = agentT(t2, `ai.agent.chatContext.${chatType}`, locale);
|
|
16169
|
+
const ownerNote = ownerInfo ? agentT(t2, "ai.agent.ownerNote", locale, {
|
|
16170
|
+
displayName: ownerInfo.displayName,
|
|
16171
|
+
platformId: ownerInfo.platformId,
|
|
16172
|
+
chatContext
|
|
16173
|
+
}) : "";
|
|
16174
|
+
const memberNote = isPrivate ? agentT(t2, "ai.agent.memberNotePrivate", locale) : agentT(t2, "ai.agent.memberNoteGroup", locale);
|
|
16175
|
+
const mentionedMembersNote = mentionedMembers && mentionedMembers.length > 0 ? `${agentT(t2, "ai.agent.mentionedMembersNote", locale)}
|
|
16176
|
+
${mentionedMembers.map((member) => {
|
|
16177
|
+
const aliasPart = member.aliases.length > 0 ? ` | aliases=${member.aliases.join(",")}` : "";
|
|
16178
|
+
return `- member_id=${member.memberId} | mention=${member.mentionText} | display_name=${member.displayName} | platform_id=${member.platformId}${aliasPart}`;
|
|
16179
|
+
}).join("\n")}
|
|
16180
|
+
` : "";
|
|
16181
|
+
const year = now.getFullYear();
|
|
16182
|
+
const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t2, dataSnapshot, locale)}
|
|
16183
|
+
` : "";
|
|
16184
|
+
return `${agentT(t2, "ai.agent.currentDateIs", locale)} ${currentDate}\u3002
|
|
16185
|
+
${ownerNote}
|
|
16186
|
+
${mentionedMembersNote}
|
|
16187
|
+
${dataSnapshotNote}
|
|
16188
|
+
${memberNote}
|
|
16189
|
+
${agentT(t2, "ai.agent.timeParamsIntro", locale)}
|
|
16190
|
+
${agentT(t2, "ai.agent.defaultYearNote", locale, { year })}
|
|
16191
|
+
${agentT(t2, "ai.agent.evidencePolicy", locale)}
|
|
16192
|
+
|
|
16193
|
+
${agentT(t2, "ai.agent.responseInstruction", locale)}`;
|
|
16194
|
+
}
|
|
16195
|
+
function getFallbackRoleDefinition(t2, chatType, locale) {
|
|
16196
|
+
return agentT(t2, `ai.agent.fallbackRoleDefinition.${chatType}`, locale);
|
|
16197
|
+
}
|
|
16198
|
+
function formatTimestamp(timestamp, locale) {
|
|
16199
|
+
if (!timestamp) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
16200
|
+
const date = new Date(timestamp * 1e3);
|
|
16201
|
+
if (Number.isNaN(date.getTime())) return locale.startsWith("zh") ? "\u672A\u77E5" : "unknown";
|
|
16202
|
+
const dateLocale = locale.startsWith("zh") ? "zh-CN" : locale;
|
|
16203
|
+
return date.toLocaleString(dateLocale, {
|
|
16204
|
+
year: "numeric",
|
|
16205
|
+
month: "2-digit",
|
|
16206
|
+
day: "2-digit",
|
|
16207
|
+
hour: "2-digit",
|
|
16208
|
+
minute: "2-digit"
|
|
16209
|
+
});
|
|
16210
|
+
}
|
|
16211
|
+
function buildSystemPrompt(options) {
|
|
16212
|
+
const {
|
|
16213
|
+
t: t2,
|
|
16214
|
+
chatType = "group",
|
|
16215
|
+
assistantSystemPrompt,
|
|
16216
|
+
ownerInfo,
|
|
16217
|
+
locale = "zh-CN",
|
|
16218
|
+
skillCtx,
|
|
16219
|
+
mentionedMembers,
|
|
16220
|
+
dataSnapshot
|
|
16221
|
+
} = options;
|
|
16222
|
+
const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t2, chatType, locale);
|
|
16223
|
+
const lockedSection = getLockedPromptSection(t2, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot);
|
|
16224
|
+
let skillSection = "";
|
|
16225
|
+
if (skillCtx?.skillDef) {
|
|
16226
|
+
skillSection = `
|
|
16227
|
+
## ${agentT(t2, "ai.agent.currentTask", locale)}\uFF1A${skillCtx.skillDef.name}
|
|
16228
|
+
${agentT(t2, "ai.agent.skillPriorityNote", locale)}
|
|
16229
|
+
` + skillCtx.skillDef.prompt;
|
|
16230
|
+
} else if (skillCtx?.skillMenu) {
|
|
16231
|
+
skillSection = `
|
|
16232
|
+
${skillCtx.skillMenu}`;
|
|
16233
|
+
}
|
|
16234
|
+
return `${systemPrompt}${skillSection}
|
|
16235
|
+
|
|
16236
|
+
${lockedSection}`;
|
|
14334
16237
|
}
|
|
14335
|
-
var aiLocales = { "zh-CN": zh_CN_default, "en-US": en_US_default, "ja-JP": ja_JP_default, "zh-TW": zh_TW_default };
|
|
14336
16238
|
|
|
14337
16239
|
// ../../packages/node-runtime/src/ai/summary/index.ts
|
|
14338
16240
|
var MIN_MESSAGE_COUNT = 3;
|
|
@@ -14588,8 +16490,8 @@ async function generateSessionSummary(deps, segmentId, options = {}) {
|
|
|
14588
16490
|
const segments = splitIntoSegments(validMessages, MAX_CONTENT_PER_CALL);
|
|
14589
16491
|
log?.info("Summary", `Long session segmented: ${segments.length} segments`);
|
|
14590
16492
|
const subSummaries = [];
|
|
14591
|
-
for (const
|
|
14592
|
-
const segContent = formatMessages(
|
|
16493
|
+
for (const segment2 of segments) {
|
|
16494
|
+
const segContent = formatMessages(segment2);
|
|
14593
16495
|
const sub = await deps.llmComplete(
|
|
14594
16496
|
deps.t("summary.systemPromptDirect"),
|
|
14595
16497
|
buildSubSummaryPrompt(segContent, locale, strategy),
|
|
@@ -14892,10 +16794,10 @@ var LLMConfigStore = class {
|
|
|
14892
16794
|
};
|
|
14893
16795
|
|
|
14894
16796
|
// ../../packages/node-runtime/src/ai/file-config-storage.ts
|
|
14895
|
-
import * as
|
|
16797
|
+
import * as path19 from "path";
|
|
14896
16798
|
function createFileConfigStorage(baseDir) {
|
|
14897
16799
|
const heldLocks = /* @__PURE__ */ new Set();
|
|
14898
|
-
const getFilePath = (key) =>
|
|
16800
|
+
const getFilePath = (key) => path19.join(baseDir, `${key}.json`);
|
|
14899
16801
|
return {
|
|
14900
16802
|
readJson(key) {
|
|
14901
16803
|
return readJsonFile(getFilePath(key));
|
|
@@ -14933,6 +16835,9 @@ function getAuthProfile2(config) {
|
|
|
14933
16835
|
function getNamedAuthProfile(config) {
|
|
14934
16836
|
return config.name?.toLowerCase().replace(/\s+/g, "-") || config.provider;
|
|
14935
16837
|
}
|
|
16838
|
+
function getConfigAuthProfile(config) {
|
|
16839
|
+
return `${getNamedAuthProfile(config)}-${config.id}`;
|
|
16840
|
+
}
|
|
14936
16841
|
function restoreLegacyAuthProfileReferences(configs, authProfiles) {
|
|
14937
16842
|
const usedProfiles = new Set(configs.map(getAuthProfile2).filter((name) => Boolean(name)));
|
|
14938
16843
|
for (const config of configs) {
|
|
@@ -14967,7 +16872,7 @@ function createAuthProfileLlmConfigStore(storage, deps = {}) {
|
|
|
14967
16872
|
},
|
|
14968
16873
|
resolveApiKey: resolvedDeps.resolveApiKey,
|
|
14969
16874
|
onApiKeyCreated: (config, apiKey) => {
|
|
14970
|
-
const profileName =
|
|
16875
|
+
const profileName = getConfigAuthProfile(config);
|
|
14971
16876
|
resolvedDeps.writeAuthProfile(profileName, { type: "api_key", provider: config.provider, key: apiKey });
|
|
14972
16877
|
return profileName;
|
|
14973
16878
|
},
|
|
@@ -15403,29 +17308,29 @@ async function runSimpleLlmStream(options) {
|
|
|
15403
17308
|
import { Type } from "@earendil-works/pi-ai";
|
|
15404
17309
|
|
|
15405
17310
|
// ../../packages/node-runtime/src/migrations/electron-data-migration.ts
|
|
15406
|
-
import * as
|
|
17311
|
+
import * as fs12 from "fs";
|
|
15407
17312
|
import * as os3 from "os";
|
|
15408
|
-
import * as
|
|
17313
|
+
import * as path20 from "path";
|
|
15409
17314
|
var SYSTEM_SUBDIRS = ["ai", "settings", "cache", "logs", "nlp"];
|
|
15410
17315
|
var STORAGE_CONFIG_FILE = "storage.json";
|
|
15411
17316
|
function getElectronUserDataDir(options = {}) {
|
|
15412
17317
|
const home = options.homeDir ?? os3.homedir();
|
|
15413
17318
|
switch (options.platform ?? process.platform) {
|
|
15414
17319
|
case "darwin":
|
|
15415
|
-
return
|
|
17320
|
+
return path20.join(home, "Library", "Application Support", "ChatLab");
|
|
15416
17321
|
case "win32": {
|
|
15417
|
-
const appData = options.appDataDir ?? process.env.APPDATA ??
|
|
15418
|
-
return
|
|
17322
|
+
const appData = options.appDataDir ?? process.env.APPDATA ?? path20.join(home, "AppData", "Roaming");
|
|
17323
|
+
return path20.join(appData, "ChatLab");
|
|
15419
17324
|
}
|
|
15420
17325
|
default:
|
|
15421
|
-
return
|
|
17326
|
+
return path20.join(home, ".config", "ChatLab");
|
|
15422
17327
|
}
|
|
15423
17328
|
}
|
|
15424
17329
|
function readElectronStorageConfig(electronUserData) {
|
|
15425
|
-
const configPath =
|
|
15426
|
-
if (!
|
|
17330
|
+
const configPath = path20.join(electronUserData, STORAGE_CONFIG_FILE);
|
|
17331
|
+
if (!fs12.existsSync(configPath)) return {};
|
|
15427
17332
|
try {
|
|
15428
|
-
return JSON.parse(
|
|
17333
|
+
return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
|
|
15429
17334
|
} catch {
|
|
15430
17335
|
return {};
|
|
15431
17336
|
}
|
|
@@ -15433,45 +17338,45 @@ function readElectronStorageConfig(electronUserData) {
|
|
|
15433
17338
|
function resolveElectronDataDir() {
|
|
15434
17339
|
const electronUserData = getElectronUserDataDir();
|
|
15435
17340
|
const storageConfig = readElectronStorageConfig(electronUserData);
|
|
15436
|
-
if (storageConfig.dataDir &&
|
|
17341
|
+
if (storageConfig.dataDir && path20.isAbsolute(storageConfig.dataDir)) {
|
|
15437
17342
|
return storageConfig.dataDir;
|
|
15438
17343
|
}
|
|
15439
|
-
return
|
|
17344
|
+
return path20.join(electronUserData, "data");
|
|
15440
17345
|
}
|
|
15441
17346
|
function hasDatabases(dir) {
|
|
15442
|
-
const dbDir =
|
|
15443
|
-
if (!
|
|
17347
|
+
const dbDir = path20.join(dir, "databases");
|
|
17348
|
+
if (!fs12.existsSync(dbDir)) return false;
|
|
15444
17349
|
try {
|
|
15445
|
-
return
|
|
17350
|
+
return fs12.readdirSync(dbDir).some((f) => f.endsWith(".db"));
|
|
15446
17351
|
} catch {
|
|
15447
17352
|
return false;
|
|
15448
17353
|
}
|
|
15449
17354
|
}
|
|
15450
17355
|
function ensureDir2(dirPath) {
|
|
15451
|
-
if (!
|
|
15452
|
-
|
|
17356
|
+
if (!fs12.existsSync(dirPath)) {
|
|
17357
|
+
fs12.mkdirSync(dirPath, { recursive: true });
|
|
15453
17358
|
}
|
|
15454
17359
|
}
|
|
15455
17360
|
function copyDirMerge(src, dest) {
|
|
15456
17361
|
const stats = { copied: 0, skipped: 0 };
|
|
15457
|
-
if (!
|
|
17362
|
+
if (!fs12.existsSync(src)) return stats;
|
|
15458
17363
|
ensureDir2(dest);
|
|
15459
17364
|
let entries;
|
|
15460
17365
|
try {
|
|
15461
|
-
entries =
|
|
17366
|
+
entries = fs12.readdirSync(src, { withFileTypes: true });
|
|
15462
17367
|
} catch {
|
|
15463
17368
|
return stats;
|
|
15464
17369
|
}
|
|
15465
17370
|
for (const entry of entries) {
|
|
15466
|
-
const srcPath =
|
|
15467
|
-
const destPath =
|
|
17371
|
+
const srcPath = path20.join(src, entry.name);
|
|
17372
|
+
const destPath = path20.join(dest, entry.name);
|
|
15468
17373
|
try {
|
|
15469
17374
|
if (entry.isDirectory()) {
|
|
15470
17375
|
const sub = copyDirMerge(srcPath, destPath);
|
|
15471
17376
|
stats.copied += sub.copied;
|
|
15472
17377
|
stats.skipped += sub.skipped;
|
|
15473
|
-
} else if (!
|
|
15474
|
-
|
|
17378
|
+
} else if (!fs12.existsSync(destPath)) {
|
|
17379
|
+
fs12.copyFileSync(srcPath, destPath);
|
|
15475
17380
|
stats.copied++;
|
|
15476
17381
|
} else {
|
|
15477
17382
|
stats.skipped++;
|
|
@@ -15484,22 +17389,22 @@ function copyDirMerge(src, dest) {
|
|
|
15484
17389
|
function writeMigrationLog(logDir, message) {
|
|
15485
17390
|
try {
|
|
15486
17391
|
ensureDir2(logDir);
|
|
15487
|
-
const logPath =
|
|
17392
|
+
const logPath = path20.join(logDir, "app.log");
|
|
15488
17393
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
15489
|
-
|
|
17394
|
+
fs12.appendFileSync(logPath, `[${ts}] [MIGRATION] ${message}
|
|
15490
17395
|
`, "utf-8");
|
|
15491
17396
|
} catch {
|
|
15492
17397
|
}
|
|
15493
17398
|
}
|
|
15494
17399
|
function wasElectronUsed() {
|
|
15495
17400
|
const electronUserData = getElectronUserDataDir();
|
|
15496
|
-
if (!
|
|
15497
|
-
return
|
|
17401
|
+
if (!fs12.existsSync(electronUserData)) return false;
|
|
17402
|
+
return fs12.existsSync(path20.join(electronUserData, "Preferences")) || fs12.existsSync(path20.join(electronUserData, "Local Storage")) || fs12.existsSync(path20.join(electronUserData, "Session Storage")) || fs12.existsSync(path20.join(electronUserData, "data"));
|
|
15498
17403
|
}
|
|
15499
17404
|
function verifyCliDataPath(databaseDir) {
|
|
15500
|
-
if (
|
|
17405
|
+
if (fs12.existsSync(databaseDir)) {
|
|
15501
17406
|
try {
|
|
15502
|
-
if (
|
|
17407
|
+
if (fs12.readdirSync(databaseDir).some((f) => f.endsWith(".db"))) return true;
|
|
15503
17408
|
} catch {
|
|
15504
17409
|
}
|
|
15505
17410
|
}
|
|
@@ -15517,9 +17422,9 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15517
17422
|
let totalSkipped = 0;
|
|
15518
17423
|
const movedDirs = [];
|
|
15519
17424
|
for (const subDir of SYSTEM_SUBDIRS) {
|
|
15520
|
-
const srcDir =
|
|
15521
|
-
const destDir =
|
|
15522
|
-
if (!
|
|
17425
|
+
const srcDir = path20.join(electronDataDir, subDir);
|
|
17426
|
+
const destDir = path20.join(systemDir, subDir);
|
|
17427
|
+
if (!fs12.existsSync(srcDir)) continue;
|
|
15523
17428
|
const { copied, skipped } = copyDirMerge(srcDir, destDir);
|
|
15524
17429
|
if (copied > 0 || skipped > 0) {
|
|
15525
17430
|
movedDirs.push(subDir);
|
|
@@ -15529,13 +17434,13 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15529
17434
|
}
|
|
15530
17435
|
}
|
|
15531
17436
|
for (const subDir of movedDirs) {
|
|
15532
|
-
const srcDir =
|
|
17437
|
+
const srcDir = path20.join(electronDataDir, subDir);
|
|
15533
17438
|
try {
|
|
15534
|
-
|
|
17439
|
+
fs12.rmSync(srcDir, { recursive: true, force: true });
|
|
15535
17440
|
} catch {
|
|
15536
17441
|
}
|
|
15537
17442
|
}
|
|
15538
|
-
const logsDir =
|
|
17443
|
+
const logsDir = path20.join(systemDir, "logs");
|
|
15539
17444
|
const summary = `CLI first-run: detected Electron data at ${electronDataDir}, pointed user_data_dir there, copied ${totalCopied} system files (${movedDirs.join(", ") || "none"})`;
|
|
15540
17445
|
writeMigrationLog(logsDir, summary);
|
|
15541
17446
|
console.log(`[Migration] ${summary}`);
|
|
@@ -15549,7 +17454,7 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15549
17454
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
15550
17455
|
console.error("[Migration] Electron data migration failed:", errorMsg);
|
|
15551
17456
|
try {
|
|
15552
|
-
writeMigrationLog(
|
|
17457
|
+
writeMigrationLog(path20.join(systemDir, "logs"), `Electron migration failed: ${errorMsg}`);
|
|
15553
17458
|
} catch {
|
|
15554
17459
|
}
|
|
15555
17460
|
return { migrated: false, electronDetected: true, error: errorMsg };
|
|
@@ -15557,26 +17462,26 @@ function migrateFromElectronIfNeeded(systemDir) {
|
|
|
15557
17462
|
}
|
|
15558
17463
|
|
|
15559
17464
|
// ../../packages/node-runtime/src/cache/session-cache.ts
|
|
15560
|
-
import * as
|
|
15561
|
-
import * as
|
|
17465
|
+
import * as fs13 from "fs";
|
|
17466
|
+
import * as path21 from "path";
|
|
15562
17467
|
function getCachePath(sessionId, cacheDir) {
|
|
15563
|
-
return
|
|
17468
|
+
return path21.join(cacheDir, `${sessionId}.cache.json`);
|
|
15564
17469
|
}
|
|
15565
17470
|
function readCacheFile(cachePath) {
|
|
15566
17471
|
try {
|
|
15567
|
-
if (!
|
|
15568
|
-
return JSON.parse(
|
|
17472
|
+
if (!fs13.existsSync(cachePath)) return null;
|
|
17473
|
+
return JSON.parse(fs13.readFileSync(cachePath, "utf-8"));
|
|
15569
17474
|
} catch {
|
|
15570
17475
|
return null;
|
|
15571
17476
|
}
|
|
15572
17477
|
}
|
|
15573
17478
|
function writeCacheFile(cachePath, content) {
|
|
15574
17479
|
try {
|
|
15575
|
-
const dir =
|
|
15576
|
-
if (!
|
|
15577
|
-
|
|
17480
|
+
const dir = path21.dirname(cachePath);
|
|
17481
|
+
if (!fs13.existsSync(dir)) {
|
|
17482
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
15578
17483
|
}
|
|
15579
|
-
|
|
17484
|
+
fs13.writeFileSync(cachePath, JSON.stringify(content), "utf-8");
|
|
15580
17485
|
} catch {
|
|
15581
17486
|
}
|
|
15582
17487
|
}
|
|
@@ -15596,7 +17501,7 @@ function invalidateCache(sessionId, cacheDir, key) {
|
|
|
15596
17501
|
const cachePath = getCachePath(sessionId, cacheDir);
|
|
15597
17502
|
try {
|
|
15598
17503
|
if (!key) {
|
|
15599
|
-
if (
|
|
17504
|
+
if (fs13.existsSync(cachePath)) fs13.unlinkSync(cachePath);
|
|
15600
17505
|
} else {
|
|
15601
17506
|
const file = readCacheFile(cachePath);
|
|
15602
17507
|
if (file && file[key]) {
|
|
@@ -15610,7 +17515,7 @@ function invalidateCache(sessionId, cacheDir, key) {
|
|
|
15610
17515
|
function deleteSessionCache(sessionId, cacheDir) {
|
|
15611
17516
|
const cachePath = getCachePath(sessionId, cacheDir);
|
|
15612
17517
|
try {
|
|
15613
|
-
if (
|
|
17518
|
+
if (fs13.existsSync(cachePath)) fs13.unlinkSync(cachePath);
|
|
15614
17519
|
} catch {
|
|
15615
17520
|
}
|
|
15616
17521
|
}
|
|
@@ -15664,8 +17569,8 @@ function computeAndSetMembersCache(db, sessionId, cacheDir) {
|
|
|
15664
17569
|
}
|
|
15665
17570
|
|
|
15666
17571
|
// ../../packages/node-runtime/src/data-dir-compat.ts
|
|
15667
|
-
import
|
|
15668
|
-
import
|
|
17572
|
+
import fs14 from "fs";
|
|
17573
|
+
import path22 from "path";
|
|
15669
17574
|
var DataDirCompatibilityError = class extends Error {
|
|
15670
17575
|
code;
|
|
15671
17576
|
userDataDir;
|
|
@@ -15685,10 +17590,10 @@ var DataDirCompatibilityError = class extends Error {
|
|
|
15685
17590
|
};
|
|
15686
17591
|
function readDataDirCompatibilityMeta(userDataDir) {
|
|
15687
17592
|
const metaPath = getMetaPath(userDataDir);
|
|
15688
|
-
if (!
|
|
17593
|
+
if (!fs14.existsSync(metaPath)) return null;
|
|
15689
17594
|
let parsed;
|
|
15690
17595
|
try {
|
|
15691
|
-
parsed = JSON.parse(
|
|
17596
|
+
parsed = JSON.parse(fs14.readFileSync(metaPath, "utf-8"));
|
|
15692
17597
|
} catch (error) {
|
|
15693
17598
|
throw createInvalidMetaError(userDataDir, metaPath, "Invalid data directory compatibility meta JSON.", error);
|
|
15694
17599
|
}
|
|
@@ -15776,7 +17681,7 @@ function raiseDataDirMinRuntimeVersion(pathProvider, input) {
|
|
|
15776
17681
|
return nextMeta;
|
|
15777
17682
|
}
|
|
15778
17683
|
function getMetaPath(userDataDir) {
|
|
15779
|
-
return
|
|
17684
|
+
return path22.join(userDataDir, ".chatlab-meta.json");
|
|
15780
17685
|
}
|
|
15781
17686
|
function validateDataDirCompatibilityMeta(value, userDataDir, metaPath) {
|
|
15782
17687
|
if (!isRecord3(value)) {
|
|
@@ -15847,15 +17752,15 @@ function mergeReasons(existing, next) {
|
|
|
15847
17752
|
return [.../* @__PURE__ */ new Set([...existing, next])];
|
|
15848
17753
|
}
|
|
15849
17754
|
function writeMetaAtomic(userDataDir, metaPath, meta) {
|
|
15850
|
-
|
|
15851
|
-
const tempPath =
|
|
17755
|
+
fs14.mkdirSync(userDataDir, { recursive: true });
|
|
17756
|
+
const tempPath = path22.join(userDataDir, `.chatlab-meta.json.${process.pid}.${Date.now()}.tmp`);
|
|
15852
17757
|
try {
|
|
15853
|
-
|
|
17758
|
+
fs14.writeFileSync(tempPath, `${JSON.stringify(meta, null, 2)}
|
|
15854
17759
|
`, "utf-8");
|
|
15855
|
-
|
|
17760
|
+
fs14.renameSync(tempPath, metaPath);
|
|
15856
17761
|
} catch (error) {
|
|
15857
17762
|
try {
|
|
15858
|
-
if (
|
|
17763
|
+
if (fs14.existsSync(tempPath)) fs14.unlinkSync(tempPath);
|
|
15859
17764
|
} catch {
|
|
15860
17765
|
}
|
|
15861
17766
|
throw error;
|
|
@@ -16184,51 +18089,51 @@ function getChatDbMigrations() {
|
|
|
16184
18089
|
}
|
|
16185
18090
|
|
|
16186
18091
|
// ../../packages/node-runtime/src/services/contacts/paths.ts
|
|
16187
|
-
import
|
|
18092
|
+
import path23 from "path";
|
|
16188
18093
|
var CONTACTS_DIR_NAME = "contacts";
|
|
16189
18094
|
var CONTACTS_FACTS_DIR_NAME = "facts";
|
|
16190
18095
|
function getContactsDir(userDataDir) {
|
|
16191
|
-
return
|
|
18096
|
+
return path23.join(userDataDir, CONTACTS_DIR_NAME);
|
|
16192
18097
|
}
|
|
16193
18098
|
function getContactsFactsCacheDir(userDataDir) {
|
|
16194
|
-
return
|
|
18099
|
+
return path23.join(getContactsDir(userDataDir), CONTACTS_FACTS_DIR_NAME);
|
|
16195
18100
|
}
|
|
16196
18101
|
|
|
16197
18102
|
// ../../packages/node-runtime/src/services/global-insight/paths.ts
|
|
16198
|
-
import
|
|
18103
|
+
import path24 from "path";
|
|
16199
18104
|
function getGlobalInsightDir(userDataDir) {
|
|
16200
|
-
return
|
|
18105
|
+
return path24.join(userDataDir, "insight", "annual-summary");
|
|
16201
18106
|
}
|
|
16202
18107
|
function getGlobalInsightFactsCacheDir(userDataDir) {
|
|
16203
|
-
return
|
|
18108
|
+
return path24.join(getGlobalInsightDir(userDataDir), "facts");
|
|
16204
18109
|
}
|
|
16205
18110
|
function getTimeInvestmentDir(userDataDir) {
|
|
16206
|
-
return
|
|
18111
|
+
return path24.join(userDataDir, "insight", "time-investment");
|
|
16207
18112
|
}
|
|
16208
18113
|
function getTimeInvestmentFactsCacheDir(userDataDir) {
|
|
16209
|
-
return
|
|
18114
|
+
return path24.join(getTimeInvestmentDir(userDataDir), "facts");
|
|
16210
18115
|
}
|
|
16211
18116
|
|
|
16212
18117
|
// ../../packages/node-runtime/src/services/people/relationships/paths.ts
|
|
16213
|
-
import
|
|
18118
|
+
import path25 from "path";
|
|
16214
18119
|
var PEOPLE_DIR_NAME = "people";
|
|
16215
18120
|
var RELATIONSHIPS_DIR_NAME = "relationships";
|
|
16216
18121
|
var RELATIONSHIPS_FACTS_DIR_NAME = "facts";
|
|
16217
18122
|
function getPeopleRelationshipsDir(userDataDir) {
|
|
16218
|
-
return
|
|
18123
|
+
return path25.join(userDataDir, PEOPLE_DIR_NAME, RELATIONSHIPS_DIR_NAME);
|
|
16219
18124
|
}
|
|
16220
18125
|
function getPeopleRelationshipsFactsCacheDir(userDataDir) {
|
|
16221
|
-
return
|
|
18126
|
+
return path25.join(getPeopleRelationshipsDir(userDataDir), RELATIONSHIPS_FACTS_DIR_NAME);
|
|
16222
18127
|
}
|
|
16223
18128
|
|
|
16224
18129
|
// ../../packages/node-runtime/src/services/topics/paths.ts
|
|
16225
|
-
import
|
|
18130
|
+
import path26 from "path";
|
|
16226
18131
|
var CHAT_TOPICS_DB_FILENAME = "topics.db";
|
|
16227
18132
|
function getChatTopicsDir(userDataDir) {
|
|
16228
|
-
return
|
|
18133
|
+
return path26.join(userDataDir, "insight");
|
|
16229
18134
|
}
|
|
16230
18135
|
function getChatTopicsDbPath(userDataDir) {
|
|
16231
|
-
return
|
|
18136
|
+
return path26.join(getChatTopicsDir(userDataDir), CHAT_TOPICS_DB_FILENAME);
|
|
16232
18137
|
}
|
|
16233
18138
|
|
|
16234
18139
|
// ../../packages/node-runtime/src/services/topics/model-client.ts
|
|
@@ -16416,13 +18321,13 @@ function getZonedDateTimeParts(timestampMs, timezone) {
|
|
|
16416
18321
|
}
|
|
16417
18322
|
|
|
16418
18323
|
// ../../packages/node-runtime/src/services/topics/store.ts
|
|
16419
|
-
import
|
|
16420
|
-
import
|
|
18324
|
+
import fs15 from "fs";
|
|
18325
|
+
import path27 from "path";
|
|
16421
18326
|
var CHAT_TOPICS_SCHEMA_VERSION = 3;
|
|
16422
18327
|
var ChatTopicStore = class {
|
|
16423
18328
|
db;
|
|
16424
18329
|
constructor(dbPath, options = {}) {
|
|
16425
|
-
|
|
18330
|
+
fs15.mkdirSync(path27.dirname(dbPath), { recursive: true });
|
|
16426
18331
|
this.db = openBetterSqliteDatabase(dbPath, { nativeBinding: options.nativeBinding });
|
|
16427
18332
|
this.db.pragma("foreign_keys = ON");
|
|
16428
18333
|
this.initialize();
|
|
@@ -17018,7 +18923,7 @@ var ChatTopicStore = class {
|
|
|
17018
18923
|
};
|
|
17019
18924
|
function deleteSessionChatTopics(userDataDir, sessionId, options = {}) {
|
|
17020
18925
|
const dbPath = getChatTopicsDbPath(userDataDir);
|
|
17021
|
-
if (!
|
|
18926
|
+
if (!fs15.existsSync(dbPath)) return false;
|
|
17022
18927
|
const store = new ChatTopicStore(dbPath, options);
|
|
17023
18928
|
try {
|
|
17024
18929
|
return store.deleteSession(sessionId);
|
|
@@ -17028,7 +18933,7 @@ function deleteSessionChatTopics(userDataDir, sessionId, options = {}) {
|
|
|
17028
18933
|
}
|
|
17029
18934
|
function assertSessionChatTopicsIdle(userDataDir, sessionId, options = {}) {
|
|
17030
18935
|
const dbPath = getChatTopicsDbPath(userDataDir);
|
|
17031
|
-
if (!
|
|
18936
|
+
if (!fs15.existsSync(dbPath)) return;
|
|
17032
18937
|
const store = new ChatTopicStore(dbPath, options);
|
|
17033
18938
|
try {
|
|
17034
18939
|
const timestamp = Date.now();
|
|
@@ -17096,7 +19001,7 @@ var ChatTopicWorkCoordinator = class {
|
|
|
17096
19001
|
var chatTopicWorkCoordinator = new ChatTopicWorkCoordinator();
|
|
17097
19002
|
|
|
17098
19003
|
// ../../packages/node-runtime/src/services/topics/service.ts
|
|
17099
|
-
import { randomUUID as
|
|
19004
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
17100
19005
|
|
|
17101
19006
|
// ../../packages/node-runtime/src/services/topics/ledger.ts
|
|
17102
19007
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -17734,8 +19639,8 @@ var CHAT_TOPICS_MIN_RUNTIME_VERSION = "0.35.1";
|
|
|
17734
19639
|
var CHAT_TOPICS_DATA_COMPATIBILITY_VERSION = 2;
|
|
17735
19640
|
function createChatTopicService(deps) {
|
|
17736
19641
|
const now = deps.now ?? Date.now;
|
|
17737
|
-
const generateId = deps.generateId ??
|
|
17738
|
-
const runtimeId = deps.runtimeId ??
|
|
19642
|
+
const generateId = deps.generateId ?? randomUUID4;
|
|
19643
|
+
const runtimeId = deps.runtimeId ?? randomUUID4();
|
|
17739
19644
|
const store = new ChatTopicStore(getChatTopicsDbPath(deps.pathProvider.getUserDataDir()), {
|
|
17740
19645
|
nativeBinding: deps.nativeBinding
|
|
17741
19646
|
});
|
|
@@ -18349,12 +20254,12 @@ function validationErrorMessage(error) {
|
|
|
18349
20254
|
}
|
|
18350
20255
|
|
|
18351
20256
|
// ../../packages/node-runtime/src/database-manager.ts
|
|
18352
|
-
import * as
|
|
18353
|
-
import * as
|
|
20257
|
+
import * as fs18 from "fs";
|
|
20258
|
+
import * as path30 from "path";
|
|
18354
20259
|
|
|
18355
20260
|
// ../../packages/node-runtime/src/services/global-insight/snapshot.ts
|
|
18356
|
-
import
|
|
18357
|
-
import
|
|
20261
|
+
import fs16 from "fs";
|
|
20262
|
+
import path28 from "path";
|
|
18358
20263
|
|
|
18359
20264
|
// ../../packages/node-runtime/src/services/global-insight/time-range.ts
|
|
18360
20265
|
function normalizeAnnualSummaryRange(input, nowInput = /* @__PURE__ */ new Date()) {
|
|
@@ -18405,17 +20310,17 @@ function toAnnualSummaryLocalDate(range) {
|
|
|
18405
20310
|
// ../../packages/node-runtime/src/services/global-insight/snapshot.ts
|
|
18406
20311
|
var SNAPSHOT_TMP_PREFIX = "annual-summary.tmp-";
|
|
18407
20312
|
function getAnnualSummarySnapshotPath(snapshotDir, range) {
|
|
18408
|
-
return
|
|
20313
|
+
return path28.join(snapshotDir, `annual-summary-${toAnnualSummaryRangeKey(range)}.json`);
|
|
18409
20314
|
}
|
|
18410
20315
|
function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
|
|
18411
20316
|
const snapshotPath = getAnnualSummarySnapshotPath(snapshotDir, range);
|
|
18412
|
-
if (!
|
|
20317
|
+
if (!fs16.existsSync(snapshotPath)) return null;
|
|
18413
20318
|
try {
|
|
18414
|
-
return JSON.parse(
|
|
20319
|
+
return JSON.parse(fs16.readFileSync(snapshotPath, "utf-8"));
|
|
18415
20320
|
} catch (error) {
|
|
18416
|
-
const backupPath =
|
|
20321
|
+
const backupPath = path28.join(snapshotDir, `annual-summary.corrupt-${options.now?.() ?? Date.now()}.json`);
|
|
18417
20322
|
try {
|
|
18418
|
-
|
|
20323
|
+
fs16.renameSync(snapshotPath, backupPath);
|
|
18419
20324
|
} catch (renameError) {
|
|
18420
20325
|
appLogger.warn("global-insight", "failed to backup corrupt annual summary snapshot", renameError);
|
|
18421
20326
|
}
|
|
@@ -18424,28 +20329,28 @@ function readAnnualSummarySnapshot(snapshotDir, range, options = {}) {
|
|
|
18424
20329
|
}
|
|
18425
20330
|
}
|
|
18426
20331
|
function writeAnnualSummarySnapshot(snapshotDir, snapshot) {
|
|
18427
|
-
|
|
18428
|
-
const tmpPath =
|
|
18429
|
-
|
|
18430
|
-
|
|
20332
|
+
fs16.mkdirSync(snapshotDir, { recursive: true });
|
|
20333
|
+
const tmpPath = path28.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
20334
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20335
|
+
fs16.renameSync(tmpPath, getAnnualSummarySnapshotPath(snapshotDir, snapshot.range));
|
|
18431
20336
|
}
|
|
18432
20337
|
function cleanupAnnualSummarySnapshotTempFiles(snapshotDir) {
|
|
18433
|
-
if (!
|
|
18434
|
-
for (const name of
|
|
20338
|
+
if (!fs16.existsSync(snapshotDir)) return;
|
|
20339
|
+
for (const name of fs16.readdirSync(snapshotDir)) {
|
|
18435
20340
|
if (!name.startsWith(SNAPSHOT_TMP_PREFIX)) continue;
|
|
18436
20341
|
try {
|
|
18437
|
-
|
|
20342
|
+
fs16.rmSync(path28.join(snapshotDir, name), { force: true });
|
|
18438
20343
|
} catch (error) {
|
|
18439
20344
|
appLogger.warn("global-insight", "failed to remove annual summary snapshot temp file", error);
|
|
18440
20345
|
}
|
|
18441
20346
|
}
|
|
18442
20347
|
}
|
|
18443
20348
|
function deleteAnnualSummarySnapshots(snapshotDir) {
|
|
18444
|
-
if (!
|
|
18445
|
-
for (const name of
|
|
20349
|
+
if (!fs16.existsSync(snapshotDir)) return;
|
|
20350
|
+
for (const name of fs16.readdirSync(snapshotDir)) {
|
|
18446
20351
|
if (!name.startsWith("annual-summary-") && !name.startsWith("annual-summary.")) continue;
|
|
18447
20352
|
try {
|
|
18448
|
-
|
|
20353
|
+
fs16.rmSync(path28.join(snapshotDir, name), { force: true });
|
|
18449
20354
|
} catch (error) {
|
|
18450
20355
|
appLogger.warn("global-insight", "failed to remove annual summary snapshot", error);
|
|
18451
20356
|
}
|
|
@@ -18453,21 +20358,21 @@ function deleteAnnualSummarySnapshots(snapshotDir) {
|
|
|
18453
20358
|
}
|
|
18454
20359
|
|
|
18455
20360
|
// ../../packages/node-runtime/src/services/global-insight/time-investment-snapshot.ts
|
|
18456
|
-
import
|
|
18457
|
-
import
|
|
20361
|
+
import fs17 from "fs";
|
|
20362
|
+
import path29 from "path";
|
|
18458
20363
|
var SNAPSHOT_TMP_PREFIX2 = "time-investment.tmp-";
|
|
18459
20364
|
function getTimeInvestmentSnapshotPath(snapshotDir, range) {
|
|
18460
|
-
return
|
|
20365
|
+
return path29.join(snapshotDir, `time-investment-${toAnnualSummaryRangeKey(range)}.json`);
|
|
18461
20366
|
}
|
|
18462
20367
|
function readTimeInvestmentSnapshot(snapshotDir, range, options = {}) {
|
|
18463
20368
|
const snapshotPath = getTimeInvestmentSnapshotPath(snapshotDir, range);
|
|
18464
|
-
if (!
|
|
20369
|
+
if (!fs17.existsSync(snapshotPath)) return null;
|
|
18465
20370
|
try {
|
|
18466
|
-
return JSON.parse(
|
|
20371
|
+
return JSON.parse(fs17.readFileSync(snapshotPath, "utf-8"));
|
|
18467
20372
|
} catch (error) {
|
|
18468
|
-
const backupPath =
|
|
20373
|
+
const backupPath = path29.join(snapshotDir, `time-investment.corrupt-${options.now?.() ?? Date.now()}.json`);
|
|
18469
20374
|
try {
|
|
18470
|
-
|
|
20375
|
+
fs17.renameSync(snapshotPath, backupPath);
|
|
18471
20376
|
} catch (renameError) {
|
|
18472
20377
|
appLogger.warn("global-insight", "failed to backup corrupt time investment snapshot", renameError);
|
|
18473
20378
|
}
|
|
@@ -18476,28 +20381,28 @@ function readTimeInvestmentSnapshot(snapshotDir, range, options = {}) {
|
|
|
18476
20381
|
}
|
|
18477
20382
|
}
|
|
18478
20383
|
function writeTimeInvestmentSnapshot(snapshotDir, snapshot) {
|
|
18479
|
-
|
|
18480
|
-
const tmpPath =
|
|
18481
|
-
|
|
18482
|
-
|
|
20384
|
+
fs17.mkdirSync(snapshotDir, { recursive: true });
|
|
20385
|
+
const tmpPath = path29.join(snapshotDir, `${SNAPSHOT_TMP_PREFIX2}${process.pid}-${Date.now()}`);
|
|
20386
|
+
fs17.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
20387
|
+
fs17.renameSync(tmpPath, getTimeInvestmentSnapshotPath(snapshotDir, snapshot.range));
|
|
18483
20388
|
}
|
|
18484
20389
|
function cleanupTimeInvestmentSnapshotTempFiles(snapshotDir) {
|
|
18485
|
-
if (!
|
|
18486
|
-
for (const name of
|
|
20390
|
+
if (!fs17.existsSync(snapshotDir)) return;
|
|
20391
|
+
for (const name of fs17.readdirSync(snapshotDir)) {
|
|
18487
20392
|
if (!name.startsWith(SNAPSHOT_TMP_PREFIX2)) continue;
|
|
18488
20393
|
try {
|
|
18489
|
-
|
|
20394
|
+
fs17.rmSync(path29.join(snapshotDir, name), { force: true });
|
|
18490
20395
|
} catch (error) {
|
|
18491
20396
|
appLogger.warn("global-insight", "failed to remove time investment snapshot temp file", error);
|
|
18492
20397
|
}
|
|
18493
20398
|
}
|
|
18494
20399
|
}
|
|
18495
20400
|
function deleteTimeInvestmentSnapshots(snapshotDir) {
|
|
18496
|
-
if (!
|
|
18497
|
-
for (const name of
|
|
20401
|
+
if (!fs17.existsSync(snapshotDir)) return;
|
|
20402
|
+
for (const name of fs17.readdirSync(snapshotDir)) {
|
|
18498
20403
|
if (!name.startsWith("time-investment-") && !name.startsWith("time-investment.")) continue;
|
|
18499
20404
|
try {
|
|
18500
|
-
|
|
20405
|
+
fs17.rmSync(path29.join(snapshotDir, name), { force: true });
|
|
18501
20406
|
} catch (error) {
|
|
18502
20407
|
appLogger.warn("global-insight", "failed to remove time investment snapshot", error);
|
|
18503
20408
|
}
|
|
@@ -18506,8 +20411,8 @@ function deleteTimeInvestmentSnapshots(snapshotDir) {
|
|
|
18506
20411
|
|
|
18507
20412
|
// ../../packages/node-runtime/src/database-manager.ts
|
|
18508
20413
|
function listDatabaseCandidateIds(databaseDir) {
|
|
18509
|
-
if (!
|
|
18510
|
-
return
|
|
20414
|
+
if (!fs18.existsSync(databaseDir)) return [];
|
|
20415
|
+
return fs18.readdirSync(databaseDir).filter((fileName) => fileName.endsWith(".db") && !fileName.startsWith("._")).map((fileName) => fileName.slice(0, -3));
|
|
18511
20416
|
}
|
|
18512
20417
|
var DatabaseManager = class {
|
|
18513
20418
|
constructor(pathProvider, options = {}) {
|
|
@@ -18531,7 +20436,7 @@ var DatabaseManager = class {
|
|
|
18531
20436
|
return this.cache.get(sessionId);
|
|
18532
20437
|
}
|
|
18533
20438
|
const dbPath = this.getDbPath(sessionId);
|
|
18534
|
-
if (!
|
|
20439
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
18535
20440
|
this.migrateIfNeeded(dbPath);
|
|
18536
20441
|
this.assertCompatible();
|
|
18537
20442
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
@@ -18577,7 +20482,7 @@ var DatabaseManager = class {
|
|
|
18577
20482
|
this.cache.delete(sessionId);
|
|
18578
20483
|
}
|
|
18579
20484
|
const dbPath = this.getDbPath(sessionId);
|
|
18580
|
-
if (!
|
|
20485
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
18581
20486
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
18582
20487
|
readonly: false,
|
|
18583
20488
|
nativeBinding: this.nativeBinding
|
|
@@ -18615,14 +20520,20 @@ var DatabaseManager = class {
|
|
|
18615
20520
|
* 列举数据库目录下的所有聊天会话 ID
|
|
18616
20521
|
*/
|
|
18617
20522
|
listSessionIds() {
|
|
18618
|
-
this.
|
|
18619
|
-
const dbDir = this.pathProvider.getDatabaseDir();
|
|
18620
|
-
return listDatabaseCandidateIds(dbDir).filter((id) => {
|
|
20523
|
+
return this.listSessionCandidateIds().filter((id) => {
|
|
18621
20524
|
const db = this.open(id);
|
|
18622
20525
|
if (!db) return false;
|
|
18623
20526
|
return isChatSessionDb(db);
|
|
18624
20527
|
});
|
|
18625
20528
|
}
|
|
20529
|
+
/**
|
|
20530
|
+
* 仅列出数据库文件候选,不同步打开所有数据库。
|
|
20531
|
+
* 需要逐库检查、取消和时间预算的调用方应使用此入口后自行验证。
|
|
20532
|
+
*/
|
|
20533
|
+
listSessionCandidateIds() {
|
|
20534
|
+
this.assertCompatible();
|
|
20535
|
+
return listDatabaseCandidateIds(this.pathProvider.getDatabaseDir());
|
|
20536
|
+
}
|
|
18626
20537
|
/**
|
|
18627
20538
|
* 只读列举聊天会话 ID,不触发数据库迁移或兼容门禁提升。
|
|
18628
20539
|
*/
|
|
@@ -18645,17 +20556,17 @@ var DatabaseManager = class {
|
|
|
18645
20556
|
* 获取数据库文件路径
|
|
18646
20557
|
*/
|
|
18647
20558
|
getDbPath(sessionId) {
|
|
18648
|
-
return
|
|
20559
|
+
return path30.join(this.pathProvider.getDatabaseDir(), `${sessionId}.db`);
|
|
18649
20560
|
}
|
|
18650
20561
|
openRawSessionDatabase(sessionId, options = {}) {
|
|
18651
20562
|
this.assertCompatible();
|
|
18652
20563
|
const dbPath = this.getDbPath(sessionId);
|
|
18653
|
-
if (!options.create && !
|
|
20564
|
+
if (!options.create && !fs18.existsSync(dbPath)) {
|
|
18654
20565
|
throw new Error(`Session database not found: ${sessionId}`);
|
|
18655
20566
|
}
|
|
18656
20567
|
if (!options.readonly) {
|
|
18657
20568
|
this.close(sessionId);
|
|
18658
|
-
|
|
20569
|
+
fs18.mkdirSync(path30.dirname(dbPath), { recursive: true });
|
|
18659
20570
|
}
|
|
18660
20571
|
const adapter = openBetterSqliteDatabase(dbPath, {
|
|
18661
20572
|
readonly: options.readonly ?? false,
|
|
@@ -18674,19 +20585,19 @@ var DatabaseManager = class {
|
|
|
18674
20585
|
assertSessionChatTopicsIdle(this.pathProvider.getUserDataDir(), sessionId, { nativeBinding: this.nativeBinding });
|
|
18675
20586
|
this.close(sessionId);
|
|
18676
20587
|
const dbPath = this.getDbPath(sessionId);
|
|
18677
|
-
const existed = ["", "-wal", "-shm"].some((suffix) =>
|
|
18678
|
-
if (
|
|
20588
|
+
const existed = ["", "-wal", "-shm"].some((suffix) => fs18.existsSync(dbPath + suffix));
|
|
20589
|
+
if (fs18.existsSync(dbPath)) fs18.unlinkSync(dbPath);
|
|
18679
20590
|
for (const suffix of ["-wal", "-shm"]) {
|
|
18680
20591
|
try {
|
|
18681
20592
|
const filePath = dbPath + suffix;
|
|
18682
|
-
if (
|
|
20593
|
+
if (fs18.existsSync(filePath)) fs18.unlinkSync(filePath);
|
|
18683
20594
|
} catch {
|
|
18684
20595
|
}
|
|
18685
20596
|
}
|
|
18686
20597
|
deleteSessionChatTopics(this.pathProvider.getUserDataDir(), sessionId, { nativeBinding: this.nativeBinding });
|
|
18687
20598
|
const cacheDir = this.pathProvider.getCacheDir();
|
|
18688
20599
|
deleteSessionCache(sessionId, cacheDir);
|
|
18689
|
-
deleteSessionCache(sessionId,
|
|
20600
|
+
deleteSessionCache(sessionId, path30.join(cacheDir, "query"));
|
|
18690
20601
|
deleteSessionCache(sessionId, getContactsFactsCacheDir(this.pathProvider.getUserDataDir()));
|
|
18691
20602
|
deleteSessionCache(sessionId, getGlobalInsightFactsCacheDir(this.pathProvider.getUserDataDir()));
|
|
18692
20603
|
deleteAnnualSummarySnapshots(getGlobalInsightDir(this.pathProvider.getUserDataDir()));
|
|
@@ -18729,12 +20640,12 @@ function shouldRepairCompatibilityGate(schemaVersion) {
|
|
|
18729
20640
|
}
|
|
18730
20641
|
|
|
18731
20642
|
// ../../packages/node-runtime/src/cache/analytics-cache.ts
|
|
18732
|
-
import * as
|
|
20643
|
+
import * as fs19 from "fs";
|
|
18733
20644
|
function getDbFileVersion(dbPath) {
|
|
18734
20645
|
const parts = [];
|
|
18735
20646
|
for (const p of [dbPath, `${dbPath}-wal`]) {
|
|
18736
20647
|
try {
|
|
18737
|
-
const st =
|
|
20648
|
+
const st = fs19.statSync(p);
|
|
18738
20649
|
parts.push(`${Math.floor(st.mtimeMs)}:${st.size}`);
|
|
18739
20650
|
} catch {
|
|
18740
20651
|
parts.push("-");
|
|
@@ -18808,6 +20719,7 @@ var StaticPathProvider = class {
|
|
|
18808
20719
|
function createDatabaseManagerAdapter(dbManager) {
|
|
18809
20720
|
return {
|
|
18810
20721
|
listSessionIds: () => dbManager.listSessionIds(),
|
|
20722
|
+
listSessionCandidateIds: () => dbManager.listSessionCandidateIds(),
|
|
18811
20723
|
openReadonly: (sessionId) => dbManager.open(sessionId),
|
|
18812
20724
|
openWritable: (sessionId) => dbManager.openWritable(sessionId),
|
|
18813
20725
|
closeSession: (sessionId) => dbManager.close(sessionId),
|
|
@@ -18888,14 +20800,16 @@ export {
|
|
|
18888
20800
|
POS_TAG_DEFINITIONS,
|
|
18889
20801
|
MEANINGFUL_POS_TAGS,
|
|
18890
20802
|
isStopword,
|
|
18891
|
-
cleanText,
|
|
18892
|
-
isValidWord,
|
|
18893
20803
|
buildExcludeKeywordsConditions,
|
|
18894
20804
|
getMentionAnalysis,
|
|
18895
20805
|
getLaughAnalysis,
|
|
18896
20806
|
getClusterGraph,
|
|
18897
20807
|
resolveOwnerMember,
|
|
20808
|
+
resolveContactMember,
|
|
20809
|
+
getNonSystemMembersForContacts,
|
|
18898
20810
|
getLatestContactMessageTs,
|
|
20811
|
+
getParticipantSessionFacts,
|
|
20812
|
+
getParticipantSetInteractionFacts,
|
|
18899
20813
|
getPrivateContactFacts,
|
|
18900
20814
|
getGroupContactFacts,
|
|
18901
20815
|
getGroupRelationshipGraphFacts,
|
|
@@ -18904,6 +20818,7 @@ export {
|
|
|
18904
20818
|
matchOwnerProfile,
|
|
18905
20819
|
shouldScopeContactToSession,
|
|
18906
20820
|
buildContactKey,
|
|
20821
|
+
getCrossChatSessionActivityFacts,
|
|
18907
20822
|
CHATLAB_FORMAT_VERSION,
|
|
18908
20823
|
CHATLAB_SUPPORTED_FORMAT_VERSIONS,
|
|
18909
20824
|
isSupportedChatLabFormatVersion,
|
|
@@ -18924,6 +20839,7 @@ export {
|
|
|
18924
20839
|
queryMessages,
|
|
18925
20840
|
searchMessagesLike,
|
|
18926
20841
|
searchMessagesByKeywords,
|
|
20842
|
+
getRecentMessages,
|
|
18927
20843
|
getMembers,
|
|
18928
20844
|
executeReadonlySql,
|
|
18929
20845
|
executeSql,
|
|
@@ -18986,6 +20902,21 @@ export {
|
|
|
18986
20902
|
parseSkillFile,
|
|
18987
20903
|
extractSkillId,
|
|
18988
20904
|
AIChatManager,
|
|
20905
|
+
initNlpDir,
|
|
20906
|
+
getNlpDir,
|
|
20907
|
+
getJieba,
|
|
20908
|
+
clearJiebaInstance,
|
|
20909
|
+
segment,
|
|
20910
|
+
batchSegmentWithFrequency,
|
|
20911
|
+
batchSegmentChineseWithStats,
|
|
20912
|
+
collectPosTagStats,
|
|
20913
|
+
getPosTagDefinitions,
|
|
20914
|
+
AI_MEMORY_CONTENT_MAX_CHARS,
|
|
20915
|
+
buildGlobalMemoryPrompt,
|
|
20916
|
+
AIMemoryService,
|
|
20917
|
+
listAIMemoriesWithSourceStatus,
|
|
20918
|
+
linkAIMemorySources,
|
|
20919
|
+
resolveAIMemorySourceStatus,
|
|
18989
20920
|
initTokenizer,
|
|
18990
20921
|
countTokens,
|
|
18991
20922
|
countMessagesTokens,
|
|
@@ -19040,11 +20971,15 @@ export {
|
|
|
19040
20971
|
createPlanContentBlock,
|
|
19041
20972
|
buildPlanGuidance,
|
|
19042
20973
|
buildSemanticSearchGuidance,
|
|
20974
|
+
createAiTranslate,
|
|
20975
|
+
aiLocales,
|
|
19043
20976
|
estimateTokensFromText,
|
|
19044
20977
|
AgentEventHandler,
|
|
20978
|
+
CROSS_CHAT_MAX_TOOL_RESULT_TOKENS,
|
|
20979
|
+
resolveCrossChatToolResultTokenBudget,
|
|
20980
|
+
runCrossChatAgent,
|
|
20981
|
+
buildCrossChatSystemPrompt,
|
|
19045
20982
|
buildSystemPrompt,
|
|
19046
|
-
createAiTranslate,
|
|
19047
|
-
aiLocales,
|
|
19048
20983
|
isValidMessage,
|
|
19049
20984
|
filterValidMessages,
|
|
19050
20985
|
splitIntoSegments,
|
|
@@ -19123,4 +21058,4 @@ export {
|
|
|
19123
21058
|
StaticPathProvider,
|
|
19124
21059
|
createDatabaseManagerAdapter
|
|
19125
21060
|
};
|
|
19126
|
-
//# sourceMappingURL=chunk-
|
|
21061
|
+
//# sourceMappingURL=chunk-4J4MA3RC.mjs.map
|