@xopcai/xopc 0.0.134 → 0.0.136
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-mQ4lbZRf.js → agent-avatar-dicebear-CER17NHM.js} +1 -1
- package/dist/gateway/static/root/assets/agents-Bv5R-N8Q.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-Bj2YJiQe.js → app-management-settings-panel-B3Ap_wpX.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-B93olV5Z.js → appearance-settings-3kBO22Xd.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-BHYbGQcZ.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-D_yymKFJ.js → archive-plugin-D_vGeNB8.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-kfyH3HV-.js → attachment-load-hWV_8pHL.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-DyGBB2WH.js → automation-suggestion-card-DVKmVTIo.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-BCTXpOBp.js +4 -0
- package/dist/gateway/static/root/assets/{binary-plugins-BX1AXf98.js → binary-plugins-BuXIeSPc.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D-WEVNHU.js → block-editor-C1m0u-UU.js} +39 -39
- package/dist/gateway/static/root/assets/browser-settings-page-ChUF08V5.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-B7WWYaEh.js +1 -0
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-CHExb90z.js → capability-presets-settings-panel-D_Bh1PnA.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-RANYrzfa.js → channel-recipient-api-sV06LCht.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BI2ChQc7.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-MY4Xh4fM.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-qgvEp09M.js +2 -0
- package/dist/gateway/static/root/assets/desktop-pet-BxMJlvb-.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-settings-C3JDiXSu.js → desktop-pet-settings-DCvpDImn.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-MT71cjgl.js → directory-picker-path-field-D1nxs9PA.js} +1 -1
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
- package/dist/gateway/static/root/assets/{dreaming-settings-Cfx_Qg4R.js → dreaming-settings-EAWkpOgw.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-Wp23cLkf.js → extension-debug-page-LUQ8dE8z.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-B5HRNtVY.js → extension-page-B_mnhoRd.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-zPfMpsYi.js → extension-settings-page-BWQsGmvW.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-6HXXdO77.js → fetch-BWXmBizE.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-CsS_3Ii6.js → gateway-config-api-M9bzPASf.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-DOxh0hei.js → gateway-settings-CbgtpprN.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BE1M9fH_.js → gateway-startup-retry-BALL91S7.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-CbP4t9aM.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-DlwI-MZZ.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api--3Vzceq-.js → goals-config-api-BBhFVzBS.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-D26r7knp.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-Dg_hQNVC.js → goals-settings-DdmVG_TR.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CecoDTZJ.js → heartbeat-settings-DZW2Xq-U.js} +1 -1
- package/dist/gateway/static/root/assets/index-CPwVzi9x.css +1 -0
- package/dist/gateway/static/root/assets/index-DZOm5_ex.js +262 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BWMLAFxf.js → keyboard-shortcuts-settings-1IWd88-T.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-DxoKJO7-.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-5tvDwUMS.js → markdown-split-BtdBj32h.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-RSjPG4B0.js → media-plugins-CpoPj3s8.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CQ_k8RHj.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-D1Rp__dV.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-CgR1bO2W.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-ozsnmDTV.js +2 -0
- package/dist/gateway/static/root/assets/{page-tabs-DYNWfN6S.js → page-tabs-CVLqCfQB.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B50PdIc8.js → preview-open-alternatives-CKUXJk2t.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B4dqFAQF.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-DlRGXv4a.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-rZEYQMZV.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-DggNb0SF.js → save-bar-controls-BtGni268.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-T4ti2C8S.js → schedule-field-BDGzKVWn.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-D6wGEfEa.js → schema-form-DQJsKMCE.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-DxGoCGU8.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-4e8ZQwxr.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-Cgd_aLBV.js → settings-collapsible-section-c8XGd4-8.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DnDrLMWZ.js → settings-form-section-By0YiDWo.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-A2OshMK8.js → settings-loading-skeleton-SYCPY-WM.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CXMK9Vb3.js → settings-page-D9NwvnBt.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-Da_LNPDS.js → settings-page-layout-CrICyGob.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-IjK-hDxf.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-BGZV-PCJ.js → share-preview-page-Dfnk9M_m.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-vQiRBra7.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BIWv778A.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CruV215G.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-D8uzXP0Y.js → system-settings-panel-BBVbSDFD.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-DQxv2BHa.js → typed-models-lib-CwFayhxu.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-BCBfSoHx.js → use-channel-catalog-BKtHOM50.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-DkZSHsIs.js → user-profile-settings-panel-_KDWzagh.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-ivGpUrZi.js → utils-BiUjIHb4.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-agJqlm4o.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-Bdw4ATCb.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-D-Dy6mTy.js +3 -0
- package/dist/gateway/static/root/assets/workflow-start-dialog-GammZ2GK.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-Cw5zrqti.js +18 -0
- package/dist/gateway/static/root/index.html +7 -8
- package/dist/package.js +1 -1
- package/dist/src/agent/image/inbound-image-handling.d.ts +1 -0
- package/dist/src/agent/image/inbound-image-handling.js +4 -1
- package/dist/src/agent/image/inbound-image-handling.js.map +1 -1
- package/dist/src/agent/image/tool-model-config.d.ts +18 -2
- package/dist/src/agent/image/tool-model-config.js +100 -14
- package/dist/src/agent/image/tool-model-config.js.map +1 -1
- package/dist/src/agent/image/vision-detection.js +4 -23
- package/dist/src/agent/image/vision-detection.js.map +1 -1
- package/dist/src/agent/inbound/attachment-pipeline.js +3 -0
- package/dist/src/agent/inbound/attachment-pipeline.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.d.ts +5 -2
- package/dist/src/agent/messaging/command-handler.js +4 -3
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/service/btw-query.d.ts +2 -0
- package/dist/src/agent/service/btw-query.js +4 -2
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
- package/dist/src/agent/service/direct-turn-helpers.js +1 -1
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +58 -1
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +1 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.d.ts +2 -1
- package/dist/src/agent/session/session-inspector.js +5 -3
- package/dist/src/agent/session/session-inspector.js.map +1 -1
- package/dist/src/agent/tools/factory.js +2 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/image-tool.d.ts +1 -0
- package/dist/src/agent/tools/image-tool.js +4 -1
- package/dist/src/agent/tools/image-tool.js.map +1 -1
- package/dist/src/chat-commands/builtins/review.js +94 -7
- package/dist/src/chat-commands/builtins/review.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +5 -3
- package/dist/src/chat-commands/context.js +5 -2
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/index.d.ts +1 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +12 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agents-admin.d.ts +5 -0
- package/dist/src/gateway/agents-admin.js +6 -1
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +2 -1
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +88 -0
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +2 -1
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.js +1 -0
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
- package/dist/src/gateway/image-capabilities.d.ts +7 -1
- package/dist/src/gateway/image-capabilities.js +16 -3
- package/dist/src/gateway/image-capabilities.js.map +1 -1
- package/dist/src/gateway/service/sessions-api.d.ts +1 -0
- package/dist/src/gateway/service/sessions-api.js.map +1 -1
- package/dist/src/gateway/service.js +3 -3
- package/dist/src/projects/project-loop.js +2 -2
- package/dist/src/projects/project-loop.js.map +1 -1
- package/dist/src/review/review-types.js +2 -1
- package/dist/src/review/review-types.js.map +1 -1
- package/dist/src/session/client-history.d.ts +1 -0
- package/dist/src/session/client-history.js +56 -0
- package/dist/src/session/client-history.js.map +1 -1
- package/dist/src/session/index.js +1 -1
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/store.d.ts +1 -0
- package/dist/src/session/store.js +30 -1
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/components/assistant-message.js +10 -3
- package/dist/src/tui/components/assistant-message.js.map +1 -1
- package/dist/src/tui/components/bash-execution.d.ts +4 -0
- package/dist/src/tui/components/bash-execution.js +15 -7
- package/dist/src/tui/components/bash-execution.js.map +1 -1
- package/dist/src/tui/tool-summary.js +9 -3
- package/dist/src/tui/tool-summary.js.map +1 -1
- package/dist/src/tui/tui-submit.d.ts +3 -0
- package/dist/src/tui/tui-submit.js +24 -1
- package/dist/src/tui/tui-submit.js.map +1 -1
- package/dist/src/tui/tui.js +29 -2
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/work-items/index.d.ts +1 -0
- package/dist/src/work-items/index.js +2 -1
- package/dist/src/work-items/work-item-workflow-judge.d.ts +8 -0
- package/dist/src/work-items/work-item-workflow-judge.js +50 -0
- package/dist/src/work-items/work-item-workflow-judge.js.map +1 -0
- package/dist/src/workflows/domain/run.d.ts +17 -0
- package/dist/src/workflows/domain/run.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.d.ts +3 -0
- package/dist/src/workflows/service/workflow-run-service.js +67 -0
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +4 -1
- package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
- package/dist/src/workflows/service/workflow-session-bridge.js +45 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
- package/dist/gateway/static/root/assets/agents-BP0iRtiu.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-12_2SDFq.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-V-tw6y1o.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-aMPnxWXi.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-CsaY2Z8B.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-N8NujD--.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-BG_j4Ci-.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-zBJbFgnK.js +0 -2
- package/dist/gateway/static/root/assets/desktop-pet-DfA-hVlt.js +0 -1
- package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
- package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-BOWSTGB-.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-CCLgkqj1.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-CsfIp8Vl.js +0 -2
- package/dist/gateway/static/root/assets/index-BOme955B.js +0 -262
- package/dist/gateway/static/root/assets/index-DXUkWmeK.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-CW7AMYhN.js +0 -7
- package/dist/gateway/static/root/assets/messages-DR9ODkb8.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-mKfeyGVq.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-DYw5zPeJ.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-SPdKpvSN.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-D3yP4K3k.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-B6EV45NR.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-9foX60FQ.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BReBhxWd.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DNDBN4nT.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-C8UtBMJ_.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-xXAMKlc7.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CurjHfEM.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BRR7NQkL.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-DCiECZN1.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DAel5CHH.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-2w0zj9eJ.js +0 -3
- package/dist/gateway/static/root/assets/workflow-start-dialog-B5xdDFV-.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-DRhiSwuU.js +0 -18
|
@@ -3,29 +3,10 @@ import { init_providers, resolveModel } from "../../providers/index.js";
|
|
|
3
3
|
init_providers();
|
|
4
4
|
function modelSupportsVision(modelRef) {
|
|
5
5
|
try {
|
|
6
|
-
|
|
7
|
-
} catch {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"gpt-4-turbo",
|
|
11
|
-
"gpt-4-vision",
|
|
12
|
-
"gpt-4.1",
|
|
13
|
-
"gpt-5",
|
|
14
|
-
"o1",
|
|
15
|
-
"o3",
|
|
16
|
-
"o4-mini",
|
|
17
|
-
"claude-sonnet",
|
|
18
|
-
"claude-opus",
|
|
19
|
-
"claude-haiku",
|
|
20
|
-
"gemini",
|
|
21
|
-
"qwen-vl",
|
|
22
|
-
"qwen2.5-vl",
|
|
23
|
-
"qwen-max-vl",
|
|
24
|
-
"qvq"
|
|
25
|
-
];
|
|
26
|
-
const normalizedRef = modelRef.toLowerCase();
|
|
27
|
-
const modelPart = normalizedRef.includes("/") ? normalizedRef.slice(normalizedRef.indexOf("/") + 1) : normalizedRef;
|
|
28
|
-
return knownVisionModels.some((known) => modelPart.startsWith(known) || modelPart.includes(known));
|
|
6
|
+
return resolveModel(modelRef)?.input?.includes("image") === true;
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
29
10
|
}
|
|
30
11
|
function resolveImageHandlingStrategy(modelRef) {
|
|
31
12
|
return modelSupportsVision(modelRef) ? "native" : "describe";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vision-detection.js","names":[],"sources":["../../../../src/agent/image/vision-detection.ts"],"sourcesContent":["import { resolveModel } from '../../providers/index.js';\n\nexport function modelSupportsVision(modelRef: string): boolean {\n try {\n const model = resolveModel(modelRef);\n
|
|
1
|
+
{"version":3,"file":"vision-detection.js","names":[],"sources":["../../../../src/agent/image/vision-detection.ts"],"sourcesContent":["import { resolveModel } from '../../providers/index.js';\n\nexport function modelSupportsVision(modelRef: string): boolean {\n try {\n const model = resolveModel(modelRef);\n return model?.input?.includes('image') === true;\n } catch {\n return false;\n }\n}\n\nexport type ImageHandlingStrategy = 'native' | 'describe';\n\nexport function resolveImageHandlingStrategy(modelRef: string): ImageHandlingStrategy {\n return modelSupportsVision(modelRef) ? 'native' : 'describe';\n}\n"],"mappings":";;gBAAwD;AAExD,SAAgB,oBAAoB,UAA2B;AAC7D,KAAI;AAEF,SADc,aAAa,SACf,EAAE,OAAO,SAAS,QAAQ,KAAK;SACrC;AACN,SAAO;;;AAMX,SAAgB,6BAA6B,UAAyC;AACpF,QAAO,oBAAoB,SAAS,GAAG,WAAW"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractProfileAgentId } from "../../config/agent-profile.js";
|
|
1
2
|
import { readAgentMessageContent } from "../memory/agent-message-access.js";
|
|
2
3
|
import { readMediaReferenceBase64 } from "../../media/media-reference.js";
|
|
3
4
|
import { isImageInboundAttachment } from "../../channels/attachments/attachment-classifiers.js";
|
|
@@ -83,9 +84,11 @@ async function buildTranscriptUserMessage(opts) {
|
|
|
83
84
|
appendMediaAttachedLines(textParts, prepared.filter((m) => !isImageInboundAttachment(m)));
|
|
84
85
|
if (imageRefs.length > 0 && strategy !== "native") {
|
|
85
86
|
const images = await Promise.all(imageRefs.map((ref) => readImageBase64FromRef(ref)));
|
|
87
|
+
const agentId = opts.config ? extractProfileAgentId(opts.sessionKey, opts.config) : void 0;
|
|
86
88
|
const parts = await resolveInboundImageContentParts({
|
|
87
89
|
modelRef: opts.modelRef,
|
|
88
90
|
cfg: opts.config,
|
|
91
|
+
agentId,
|
|
89
92
|
userTextForContext: expandedText,
|
|
90
93
|
images
|
|
91
94
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-pipeline.js","names":[],"sources":["../../../../src/agent/inbound/attachment-pipeline.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport {\n isImageInboundAttachment,\n type MediaRef,\n} from '../../channels/attachments/inbound-persist.js';\nimport { readMediaReferenceBase64 } from '../../media/media-reference.js';\nimport { expandAtFileMentionsInPlainText } from '../context/expand-at-file-mentions.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { resolveInboundImageContentParts } from '../image/inbound-image-handling.js';\nimport { resolveImageHandlingStrategy } from '../image/vision-detection.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\n\nexport type TranscriptUserMessage = AgentMessage & {\n media?: MediaRef[];\n MediaPath?: string;\n MediaPaths?: string[];\n MediaTypes?: string[];\n};\n\nexport type LlmUserTurn = {\n text: string;\n images: ImageContent[];\n};\n\nconst VISION_INLINE_MAX_BYTES = 2 * 1024 * 1024;\n\nfunction textBlocksFromContent(content: unknown): string[] {\n if (typeof content === 'string') {\n return content.trim() ? [content] : [];\n }\n if (!Array.isArray(content)) {\n return [];\n }\n const out: string[] = [];\n for (const block of content) {\n if (block && typeof block === 'object' && (block as { type?: string }).type === 'text') {\n const t = (block as { text?: string }).text;\n if (typeof t === 'string' && t.trim()) {\n out.push(t);\n }\n }\n }\n return out;\n}\n\nfunction joinTextBlocks(parts: string[]): string {\n return parts.join('\\n').trim();\n}\n\nfunction buildMediaPathFields(media: MediaRef[]): Pick<TranscriptUserMessage, 'MediaPath' | 'MediaPaths' | 'MediaTypes'> {\n if (!media.length) return {};\n return {\n MediaPath: media[0]!.path,\n MediaPaths: media.map((m) => m.path),\n MediaTypes: media.map((m) => m.mimeType),\n };\n}\n\nfunction assertUserContentHasNoInlineMedia(content: unknown): void {\n if (!Array.isArray(content)) {\n return;\n }\n for (const block of content) {\n if (!block || typeof block !== 'object') {\n continue;\n }\n const type = (block as { type?: string }).type;\n if (type === 'image') {\n throw new Error('User transcript content must not contain image blocks');\n }\n }\n}\n\nexport function assertTranscriptUserMessage(message: AgentMessage): void {\n if ((message as { role?: string }).role !== 'user') {\n return;\n }\n assertUserContentHasNoInlineMedia((message as { content?: unknown }).content);\n const media = (message as TranscriptUserMessage).media;\n if (!media?.length) {\n return;\n }\n for (const ref of media) {\n if (!ref.uri?.trim()) {\n throw new Error('User transcript media ref missing uri');\n }\n if ('data' in ref && typeof (ref as { data?: unknown }).data === 'string') {\n throw new Error('User transcript media ref must not include data');\n }\n }\n}\n\nasync function readImageBase64FromRef(ref: MediaRef): Promise<{ data: string; mimeType: string }> {\n const loaded = await readMediaReferenceBase64(ref.uri, VISION_INLINE_MAX_BYTES);\n return {\n data: loaded.data,\n mimeType: ref.mimeType || loaded.mimeType,\n };\n}\n\nfunction appendMediaAttachedLines(textParts: string[], media: MediaRef[]): void {\n for (const ref of media) {\n if (!isImageInboundAttachment(ref)) {\n textParts.push(\n [\n `[media attached: ${ref.name} (${ref.mimeType}, ${ref.size} bytes)]`,\n `xopc-media-uri:${ref.uri}`,\n `xopc-media-path:${ref.path}`,\n 'Use the read_media tool with the xopc-media-uri value when you need to inspect this attachment.',\n ].join('\\n'),\n );\n }\n }\n}\n\nasync function expandUserText(\n text: string,\n sessionKey: string,\n agentManager: AgentInstanceGateway,\n): Promise<string> {\n if (!text.trim()) {\n return '';\n }\n let out = text;\n if (/@file:/.test(out)) {\n const wsKey = sessionKey || 'agent:main:main';\n const root = agentManager.getResolvedWorkspaceForSession(wsKey);\n out = await expandAtFileMentionsInPlainText(out, root);\n }\n return out.trim();\n}\n\n/**\n * Build the transcript row for a user turn (text + media metadata only).\n */\nexport async function buildTranscriptUserMessage(opts: {\n text: string;\n prepared: MediaRef[] | undefined;\n sessionKey: string;\n modelRef: string;\n config: Config | undefined;\n agentManager: AgentInstanceGateway;\n}): Promise<TranscriptUserMessage> {\n const expandedText = await expandUserText(opts.text, opts.sessionKey, opts.agentManager);\n const prepared = opts.prepared ?? [];\n const imageRefs = prepared.filter(isImageInboundAttachment);\n\n const textParts: string[] = [];\n if (expandedText) {\n textParts.push(expandedText);\n }\n\n const strategy = resolveImageHandlingStrategy(opts.modelRef);\n\n appendMediaAttachedLines(textParts, prepared.filter((m) => !isImageInboundAttachment(m)));\n\n if (imageRefs.length > 0 && strategy !== 'native') {\n const images = await Promise.all(imageRefs.map((ref) => readImageBase64FromRef(ref)));\n const parts = await resolveInboundImageContentParts({\n modelRef: opts.modelRef,\n cfg: opts.config,\n userTextForContext: expandedText,\n images,\n });\n for (const part of parts) {\n if (part.type === 'text' && part.text.trim()) {\n textParts.push(part.text.trim());\n }\n }\n }\n\n if (textParts.length === 0 && prepared.length > 0) {\n textParts.push('Please analyze the attachment(s) I sent.');\n }\n\n const content = joinTextBlocks(textParts);\n const message: TranscriptUserMessage = {\n role: 'user',\n content,\n timestamp: Date.now(),\n ...(prepared.length > 0 ? { media: prepared, ...buildMediaPathFields(prepared) } : {}),\n };\n assertTranscriptUserMessage(message);\n return message;\n}\n\n/** Hydrate images from media store for the current LLM prompt. */\nexport async function hydrateUserTurnForLlm(opts: {\n message: TranscriptUserMessage;\n modelRef: string;\n}): Promise<LlmUserTurn> {\n const text = joinTextBlocks(textBlocksFromContent(readAgentMessageContent(opts.message)));\n const media = opts.message.media ?? [];\n const images: ImageContent[] = [];\n\n if (resolveImageHandlingStrategy(opts.modelRef) === 'native') {\n for (const ref of media.filter(isImageInboundAttachment)) {\n const loaded = await readImageBase64FromRef(ref);\n images.push({ type: 'image', data: loaded.data, mimeType: loaded.mimeType });\n }\n }\n\n return { text, images };\n}\n\nconst pendingTranscriptBySession = new Map<string, TranscriptUserMessage>();\n\nfunction messageTextForPendingCompare(message: AgentMessage): string {\n return joinTextBlocks(textBlocksFromContent(readAgentMessageContent(message)));\n}\n\nfunction pendingMatchesMessage(pending: TranscriptUserMessage, message: AgentMessage): boolean {\n const pendingText = messageTextForPendingCompare(pending);\n const actualText = messageTextForPendingCompare(message);\n if (!pendingText) {\n return true;\n }\n if (!actualText) {\n return false;\n }\n return actualText.includes(pendingText) || pendingText.includes(actualText);\n}\n\nexport function setPendingTranscriptUserMessage(sessionKey: string, message: TranscriptUserMessage): void {\n assertTranscriptUserMessage(message);\n pendingTranscriptBySession.set(sessionKey, message);\n}\n\nexport function takePendingTranscriptUserMessage(sessionKey: string): TranscriptUserMessage | undefined {\n const msg = pendingTranscriptBySession.get(sessionKey);\n pendingTranscriptBySession.delete(sessionKey);\n return msg;\n}\n\nexport function clearPendingTranscriptUserMessage(sessionKey: string, message?: TranscriptUserMessage): void {\n const existing = pendingTranscriptBySession.get(sessionKey);\n if (!existing) {\n return;\n }\n if (message && existing !== message) {\n return;\n }\n pendingTranscriptBySession.delete(sessionKey);\n}\n\nexport function pendingTranscriptReferencesMediaUri(sessionKey: string, uri: string): boolean {\n const pending = pendingTranscriptBySession.get(sessionKey);\n return pending?.media?.some((ref) => ref.uri === uri.trim()) === true;\n}\n\nexport function transformUserMessageForPersistence(\n sessionKey: string | undefined,\n message: AgentMessage,\n): AgentMessage {\n if ((message as { role?: string }).role !== 'user') {\n return message;\n }\n if (sessionKey) {\n const pending = pendingTranscriptBySession.get(sessionKey);\n if (pending && pendingMatchesMessage(pending, message)) {\n pendingTranscriptBySession.delete(sessionKey);\n return pending;\n }\n }\n assertTranscriptUserMessage(message);\n return message;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"attachment-pipeline.js","names":[],"sources":["../../../../src/agent/inbound/attachment-pipeline.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { extractProfileAgentId } from '../../config/agent-profile.js';\nimport {\n isImageInboundAttachment,\n type MediaRef,\n} from '../../channels/attachments/inbound-persist.js';\nimport { readMediaReferenceBase64 } from '../../media/media-reference.js';\nimport { expandAtFileMentionsInPlainText } from '../context/expand-at-file-mentions.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { resolveInboundImageContentParts } from '../image/inbound-image-handling.js';\nimport { resolveImageHandlingStrategy } from '../image/vision-detection.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\n\nexport type TranscriptUserMessage = AgentMessage & {\n media?: MediaRef[];\n MediaPath?: string;\n MediaPaths?: string[];\n MediaTypes?: string[];\n};\n\nexport type LlmUserTurn = {\n text: string;\n images: ImageContent[];\n};\n\nconst VISION_INLINE_MAX_BYTES = 2 * 1024 * 1024;\n\nfunction textBlocksFromContent(content: unknown): string[] {\n if (typeof content === 'string') {\n return content.trim() ? [content] : [];\n }\n if (!Array.isArray(content)) {\n return [];\n }\n const out: string[] = [];\n for (const block of content) {\n if (block && typeof block === 'object' && (block as { type?: string }).type === 'text') {\n const t = (block as { text?: string }).text;\n if (typeof t === 'string' && t.trim()) {\n out.push(t);\n }\n }\n }\n return out;\n}\n\nfunction joinTextBlocks(parts: string[]): string {\n return parts.join('\\n').trim();\n}\n\nfunction buildMediaPathFields(media: MediaRef[]): Pick<TranscriptUserMessage, 'MediaPath' | 'MediaPaths' | 'MediaTypes'> {\n if (!media.length) return {};\n return {\n MediaPath: media[0]!.path,\n MediaPaths: media.map((m) => m.path),\n MediaTypes: media.map((m) => m.mimeType),\n };\n}\n\nfunction assertUserContentHasNoInlineMedia(content: unknown): void {\n if (!Array.isArray(content)) {\n return;\n }\n for (const block of content) {\n if (!block || typeof block !== 'object') {\n continue;\n }\n const type = (block as { type?: string }).type;\n if (type === 'image') {\n throw new Error('User transcript content must not contain image blocks');\n }\n }\n}\n\nexport function assertTranscriptUserMessage(message: AgentMessage): void {\n if ((message as { role?: string }).role !== 'user') {\n return;\n }\n assertUserContentHasNoInlineMedia((message as { content?: unknown }).content);\n const media = (message as TranscriptUserMessage).media;\n if (!media?.length) {\n return;\n }\n for (const ref of media) {\n if (!ref.uri?.trim()) {\n throw new Error('User transcript media ref missing uri');\n }\n if ('data' in ref && typeof (ref as { data?: unknown }).data === 'string') {\n throw new Error('User transcript media ref must not include data');\n }\n }\n}\n\nasync function readImageBase64FromRef(ref: MediaRef): Promise<{ data: string; mimeType: string }> {\n const loaded = await readMediaReferenceBase64(ref.uri, VISION_INLINE_MAX_BYTES);\n return {\n data: loaded.data,\n mimeType: ref.mimeType || loaded.mimeType,\n };\n}\n\nfunction appendMediaAttachedLines(textParts: string[], media: MediaRef[]): void {\n for (const ref of media) {\n if (!isImageInboundAttachment(ref)) {\n textParts.push(\n [\n `[media attached: ${ref.name} (${ref.mimeType}, ${ref.size} bytes)]`,\n `xopc-media-uri:${ref.uri}`,\n `xopc-media-path:${ref.path}`,\n 'Use the read_media tool with the xopc-media-uri value when you need to inspect this attachment.',\n ].join('\\n'),\n );\n }\n }\n}\n\nasync function expandUserText(\n text: string,\n sessionKey: string,\n agentManager: AgentInstanceGateway,\n): Promise<string> {\n if (!text.trim()) {\n return '';\n }\n let out = text;\n if (/@file:/.test(out)) {\n const wsKey = sessionKey || 'agent:main:main';\n const root = agentManager.getResolvedWorkspaceForSession(wsKey);\n out = await expandAtFileMentionsInPlainText(out, root);\n }\n return out.trim();\n}\n\n/**\n * Build the transcript row for a user turn (text + media metadata only).\n */\nexport async function buildTranscriptUserMessage(opts: {\n text: string;\n prepared: MediaRef[] | undefined;\n sessionKey: string;\n modelRef: string;\n config: Config | undefined;\n agentManager: AgentInstanceGateway;\n}): Promise<TranscriptUserMessage> {\n const expandedText = await expandUserText(opts.text, opts.sessionKey, opts.agentManager);\n const prepared = opts.prepared ?? [];\n const imageRefs = prepared.filter(isImageInboundAttachment);\n\n const textParts: string[] = [];\n if (expandedText) {\n textParts.push(expandedText);\n }\n\n const strategy = resolveImageHandlingStrategy(opts.modelRef);\n\n appendMediaAttachedLines(textParts, prepared.filter((m) => !isImageInboundAttachment(m)));\n\n if (imageRefs.length > 0 && strategy !== 'native') {\n const images = await Promise.all(imageRefs.map((ref) => readImageBase64FromRef(ref)));\n const agentId = opts.config ? extractProfileAgentId(opts.sessionKey, opts.config) : undefined;\n const parts = await resolveInboundImageContentParts({\n modelRef: opts.modelRef,\n cfg: opts.config,\n agentId,\n userTextForContext: expandedText,\n images,\n });\n for (const part of parts) {\n if (part.type === 'text' && part.text.trim()) {\n textParts.push(part.text.trim());\n }\n }\n }\n\n if (textParts.length === 0 && prepared.length > 0) {\n textParts.push('Please analyze the attachment(s) I sent.');\n }\n\n const content = joinTextBlocks(textParts);\n const message: TranscriptUserMessage = {\n role: 'user',\n content,\n timestamp: Date.now(),\n ...(prepared.length > 0 ? { media: prepared, ...buildMediaPathFields(prepared) } : {}),\n };\n assertTranscriptUserMessage(message);\n return message;\n}\n\n/** Hydrate images from media store for the current LLM prompt. */\nexport async function hydrateUserTurnForLlm(opts: {\n message: TranscriptUserMessage;\n modelRef: string;\n}): Promise<LlmUserTurn> {\n const text = joinTextBlocks(textBlocksFromContent(readAgentMessageContent(opts.message)));\n const media = opts.message.media ?? [];\n const images: ImageContent[] = [];\n\n if (resolveImageHandlingStrategy(opts.modelRef) === 'native') {\n for (const ref of media.filter(isImageInboundAttachment)) {\n const loaded = await readImageBase64FromRef(ref);\n images.push({ type: 'image', data: loaded.data, mimeType: loaded.mimeType });\n }\n }\n\n return { text, images };\n}\n\nconst pendingTranscriptBySession = new Map<string, TranscriptUserMessage>();\n\nfunction messageTextForPendingCompare(message: AgentMessage): string {\n return joinTextBlocks(textBlocksFromContent(readAgentMessageContent(message)));\n}\n\nfunction pendingMatchesMessage(pending: TranscriptUserMessage, message: AgentMessage): boolean {\n const pendingText = messageTextForPendingCompare(pending);\n const actualText = messageTextForPendingCompare(message);\n if (!pendingText) {\n return true;\n }\n if (!actualText) {\n return false;\n }\n return actualText.includes(pendingText) || pendingText.includes(actualText);\n}\n\nexport function setPendingTranscriptUserMessage(sessionKey: string, message: TranscriptUserMessage): void {\n assertTranscriptUserMessage(message);\n pendingTranscriptBySession.set(sessionKey, message);\n}\n\nexport function takePendingTranscriptUserMessage(sessionKey: string): TranscriptUserMessage | undefined {\n const msg = pendingTranscriptBySession.get(sessionKey);\n pendingTranscriptBySession.delete(sessionKey);\n return msg;\n}\n\nexport function clearPendingTranscriptUserMessage(sessionKey: string, message?: TranscriptUserMessage): void {\n const existing = pendingTranscriptBySession.get(sessionKey);\n if (!existing) {\n return;\n }\n if (message && existing !== message) {\n return;\n }\n pendingTranscriptBySession.delete(sessionKey);\n}\n\nexport function pendingTranscriptReferencesMediaUri(sessionKey: string, uri: string): boolean {\n const pending = pendingTranscriptBySession.get(sessionKey);\n return pending?.media?.some((ref) => ref.uri === uri.trim()) === true;\n}\n\nexport function transformUserMessageForPersistence(\n sessionKey: string | undefined,\n message: AgentMessage,\n): AgentMessage {\n if ((message as { role?: string }).role !== 'user') {\n return message;\n }\n if (sessionKey) {\n const pending = pendingTranscriptBySession.get(sessionKey);\n if (pending && pendingMatchesMessage(pending, message)) {\n pendingTranscriptBySession.delete(sessionKey);\n return pending;\n }\n }\n assertTranscriptUserMessage(message);\n return message;\n}\n"],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import type { Config } from '../../config/schema.js';
|
|
|
8
8
|
import type { SessionConfigStore, SessionStore } from '../../session/index.js';
|
|
9
9
|
import type { ThinkLevel } from '../transcript/thinking-types.js';
|
|
10
10
|
import type { CompactionResult } from '../memory/compaction.js';
|
|
11
|
+
import { type BtwQueryOptions, type CommandStreamEvent } from '../../chat-commands/index.js';
|
|
11
12
|
import type { PersistentGoalApis } from '../goals/persistent-goal-apis.js';
|
|
12
13
|
import type { WorkflowRunServiceLike } from '../../workflows/service/workflow-run-service.types.js';
|
|
13
14
|
import type { SkillInstallToolOptions, SkillInstallToolResult } from '../tools/skill-install-tool.js';
|
|
@@ -46,7 +47,7 @@ export interface CommandHandlerConfig {
|
|
|
46
47
|
instructions?: string;
|
|
47
48
|
force?: boolean;
|
|
48
49
|
}) => Promise<CompactionResult>;
|
|
49
|
-
btwQuery?: (sessionKey: string, question: string) => Promise<{
|
|
50
|
+
btwQuery?: (sessionKey: string, question: string, options?: BtwQueryOptions) => Promise<{
|
|
50
51
|
text: string;
|
|
51
52
|
error?: string;
|
|
52
53
|
}>;
|
|
@@ -93,7 +94,9 @@ export declare class CommandHandler {
|
|
|
93
94
|
* Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.
|
|
94
95
|
* Same bus side effects as {@link executeCommand}.
|
|
95
96
|
*/
|
|
96
|
-
executeCommandAndAggregateReply(commandName: string, args: string, context: CommandContext
|
|
97
|
+
executeCommandAndAggregateReply(commandName: string, args: string, context: CommandContext, options?: {
|
|
98
|
+
emitEvent?: (event: CommandStreamEvent) => void | Promise<void>;
|
|
99
|
+
}): Promise<{
|
|
97
100
|
handled: boolean;
|
|
98
101
|
aggregatedText: string;
|
|
99
102
|
metadata?: Record<string, unknown>;
|
|
@@ -57,7 +57,7 @@ var CommandHandler = class {
|
|
|
57
57
|
* Build the unified command context shared by all execute paths.
|
|
58
58
|
* When `recorder` is set, every reply text is also captured (for SSE / CLI aggregation).
|
|
59
59
|
*/
|
|
60
|
-
buildCommandContext(context, recorder) {
|
|
60
|
+
buildCommandContext(context, recorder, emitEvent) {
|
|
61
61
|
const skipBusOutbound = shouldSkipBusOutboundForChannel(context.channel);
|
|
62
62
|
return createCommandContext({
|
|
63
63
|
sessionKey: context.sessionKey,
|
|
@@ -139,6 +139,7 @@ var CommandHandler = class {
|
|
|
139
139
|
}) : void 0,
|
|
140
140
|
compactSession: this.compactSession,
|
|
141
141
|
btwQuery: this.btwQuery,
|
|
142
|
+
emitEvent,
|
|
142
143
|
getSessionContextReport: this.getSessionContextReport,
|
|
143
144
|
persistentGoalApis: this.getPersistentGoalApisForCommand({
|
|
144
145
|
sessionKey: context.sessionKey,
|
|
@@ -172,7 +173,7 @@ var CommandHandler = class {
|
|
|
172
173
|
* Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.
|
|
173
174
|
* Same bus side effects as {@link executeCommand}.
|
|
174
175
|
*/
|
|
175
|
-
async executeCommandAndAggregateReply(commandName, args, context) {
|
|
176
|
+
async executeCommandAndAggregateReply(commandName, args, context, options) {
|
|
176
177
|
if (!commandRegistry.has(commandName)) return {
|
|
177
178
|
handled: false,
|
|
178
179
|
aggregatedText: ""
|
|
@@ -182,7 +183,7 @@ var CommandHandler = class {
|
|
|
182
183
|
sessionKey: context.sessionKey
|
|
183
184
|
}, "Executing command (aggregate reply)");
|
|
184
185
|
const segments = [];
|
|
185
|
-
const wrapped = this.buildCommandContext(context, (text) => segments.push(text));
|
|
186
|
+
const wrapped = this.buildCommandContext(context, (text) => segments.push(text), options?.emitEvent);
|
|
186
187
|
const result = await commandRegistry.execute(commandName, wrapped, args);
|
|
187
188
|
if (result.content) {
|
|
188
189
|
segments.push(result.content);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-handler.js","names":[],"sources":["../../../../src/agent/messaging/command-handler.ts"],"sourcesContent":["/**\n * Command Handler - Parses and executes commands\n *\n * Handles command execution using the unified command system.\n */\n\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { CompactionResult } from '../memory/compaction.js';\nimport { createLogger } from '../../utils/logger.js';\nimport {\n commandRegistry,\n createCommandContext,\n type CommandContext as UnifiedCommandContext,\n} from '../../chat-commands/index.js';\nimport { getAllProviders, getModelsByProvider, getProviderDisplayName } from '../../providers/index.js';\nimport type { PersistentGoalApis } from '../goals/persistent-goal-apis.js';\nimport type { WorkflowRunServiceLike } from '../../workflows/service/workflow-run-service.types.js';\nimport type {\n SkillInstallToolOptions,\n SkillInstallToolResult,\n} from '../tools/skill-install-tool.js';\n\nconst log = createLogger('CommandHandler');\n\n/** Gateway console direct stream uses SSE tokens; there is no ChannelPlugin outbound for `webchat`. */\nfunction shouldSkipBusOutboundForChannel(channel: string): boolean {\n return channel === 'webchat';\n}\n\nexport interface CommandContext {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n /** From inbound message metadata (thread/account, etc.) for `/goal` continuation routing. */\n inboundMetadata?: Record<string, unknown>;\n}\n\nexport interface CommandHandlerConfig {\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After /think persists, sync pi-agent */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n getCurrentModel: () => string;\n switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n /** Drop in-memory agent after session file is cleared (e.g. /new) */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Reset session in place (archive transcript, new session id; preserve overrides) */\n resetSession?: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n /** Cancel streaming preview + in-flight LLM work for this session (e.g. /abort) */\n abortSessionTurn?: (sessionKey: string) => Promise<void>;\n /** Reload skills from disk and refresh active agent prompts. */\n reloadSkills?: () => void | Promise<void>;\n /** Install a managed skill from an explicit source and refresh active agent prompts. */\n installSkillFromSource?: (opts: SkillInstallToolOptions) => Promise<SkillInstallToolResult>;\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (sessionKey: string, question: string) => Promise<{ text: string; error?: string }>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n\n getPersistentGoalApisForCommand: (routing: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n }) => PersistentGoalApis;\n getWorkflowRunService?: () => WorkflowRunServiceLike | undefined;\n}\n\nexport class CommandHandler {\n private config: Config;\n private bus: MessageBus;\n private sessionStore: SessionStore;\n private sessionConfigStore?: SessionConfigStore;\n private applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n private getCurrentModel: () => string;\n private switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n private invalidateAgentSession?: (sessionKey: string) => void;\n private abortSessionTurn?: (sessionKey: string) => Promise<void>;\n private reloadSkills?: CommandHandlerConfig['reloadSkills'];\n private installSkillFromSource?: CommandHandlerConfig['installSkillFromSource'];\n private compactSession?: CommandHandlerConfig['compactSession'];\n private btwQuery?: CommandHandlerConfig['btwQuery'];\n private getSessionContextReport?: CommandHandlerConfig['getSessionContextReport'];\n private getPersistentGoalApisForCommand: CommandHandlerConfig['getPersistentGoalApisForCommand'];\n private resetSession?: CommandHandlerConfig['resetSession'];\n private getWorkflowRunService?: CommandHandlerConfig['getWorkflowRunService'];\n\n constructor(handlerConfig: CommandHandlerConfig) {\n this.config = handlerConfig.config;\n this.bus = handlerConfig.bus;\n this.sessionStore = handlerConfig.sessionStore;\n this.sessionConfigStore = handlerConfig.sessionConfigStore;\n this.applySessionThinkingLevel = handlerConfig.applySessionThinkingLevel;\n this.getCurrentModel = handlerConfig.getCurrentModel;\n this.switchModelForSession = handlerConfig.switchModelForSession;\n this.invalidateAgentSession = handlerConfig.invalidateAgentSession;\n this.abortSessionTurn = handlerConfig.abortSessionTurn;\n this.reloadSkills = handlerConfig.reloadSkills;\n this.installSkillFromSource = handlerConfig.installSkillFromSource;\n this.compactSession = handlerConfig.compactSession;\n this.btwQuery = handlerConfig.btwQuery;\n this.getSessionContextReport = handlerConfig.getSessionContextReport;\n this.getPersistentGoalApisForCommand = handlerConfig.getPersistentGoalApisForCommand;\n this.resetSession = handlerConfig.resetSession;\n this.getWorkflowRunService = handlerConfig.getWorkflowRunService;\n }\n\n /** Replace config reference after hot reload or gateway PATCH so commands see current defaults. */\n updateAgentConfig(config: Config): void {\n this.config = config;\n }\n\n /**\n * Build the unified command context shared by all execute paths.\n * When `recorder` is set, every reply text is also captured (for SSE / CLI aggregation).\n */\n private buildCommandContext(\n context: CommandContext,\n recorder?: (text: string) => void,\n ): UnifiedCommandContext {\n const skipBusOutbound = shouldSkipBusOutboundForChannel(context.channel);\n\n return createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n recorder?.(text);\n if (skipBusOutbound) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n if (skipBusOutbound) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as { input?: number; output?: number };\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n resetSession: this.resetSession\n ? async (sk) => {\n await this.resetSession!(sk);\n }\n : undefined,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n reloadSkills: this.reloadSkills\n ? async () => {\n await this.reloadSkills!();\n }\n : undefined,\n installSkillFromSource: this.installSkillFromSource\n ? (opts) => this.installSkillFromSource!({ ...opts, sessionKey: context.sessionKey })\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n getSessionContextReport: this.getSessionContextReport,\n persistentGoalApis: this.getPersistentGoalApisForCommand({\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n inboundMetadata: context.inboundMetadata,\n }),\n workflowRunService: this.getWorkflowRunService?.(),\n });\n }\n\n /**\n * Execute a command using the unified command system\n */\n async executeCommand(\n commandName: string,\n args: string,\n context: CommandContext,\n ): Promise<boolean> {\n if (!commandRegistry.has(commandName)) {\n return false;\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command via new system');\n\n const cmdCtx = this.buildCommandContext(context);\n const result = await commandRegistry.execute(commandName, cmdCtx, args);\n\n if (result.content && !shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n\n return true;\n }\n\n /**\n * Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.\n * Same bus side effects as {@link executeCommand}.\n */\n async executeCommandAndAggregateReply(\n commandName: string,\n args: string,\n context: CommandContext,\n ): Promise<{ handled: boolean; aggregatedText: string; metadata?: Record<string, unknown> }> {\n if (!commandRegistry.has(commandName)) {\n return { handled: false, aggregatedText: '' };\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command (aggregate reply)');\n\n const segments: string[] = [];\n const wrapped = this.buildCommandContext(context, (text) => segments.push(text));\n const result = await commandRegistry.execute(commandName, wrapped, args);\n\n if (result.content) {\n segments.push(result.content);\n if (!shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n }\n\n const aggregatedText = segments.filter((s) => s && s.trim()).join('\\n\\n');\n return { handled: true, aggregatedText, metadata: result.metadata };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-handler.js","names":[],"sources":["../../../../src/agent/messaging/command-handler.ts"],"sourcesContent":["/**\n * Command Handler - Parses and executes commands\n *\n * Handles command execution using the unified command system.\n */\n\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport type { SessionConfigStore, SessionStore } from '../../session/index.js';\nimport type { ThinkLevel } from '../transcript/thinking-types.js';\nimport type { CompactionResult } from '../memory/compaction.js';\nimport { createLogger } from '../../utils/logger.js';\nimport {\n commandRegistry,\n createCommandContext,\n type BtwQueryOptions,\n type CommandContext as UnifiedCommandContext,\n type CommandStreamEvent,\n} from '../../chat-commands/index.js';\nimport { getAllProviders, getModelsByProvider, getProviderDisplayName } from '../../providers/index.js';\nimport type { PersistentGoalApis } from '../goals/persistent-goal-apis.js';\nimport type { WorkflowRunServiceLike } from '../../workflows/service/workflow-run-service.types.js';\nimport type {\n SkillInstallToolOptions,\n SkillInstallToolResult,\n} from '../tools/skill-install-tool.js';\n\nconst log = createLogger('CommandHandler');\n\n/** Gateway console direct stream uses SSE tokens; there is no ChannelPlugin outbound for `webchat`. */\nfunction shouldSkipBusOutboundForChannel(channel: string): boolean {\n return channel === 'webchat';\n}\n\nexport interface CommandContext {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n /** From inbound message metadata (thread/account, etc.) for `/goal` continuation routing. */\n inboundMetadata?: Record<string, unknown>;\n}\n\nexport interface CommandHandlerConfig {\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After /think persists, sync pi-agent */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n getCurrentModel: () => string;\n switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n /** Drop in-memory agent after session file is cleared (e.g. /new) */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Reset session in place (archive transcript, new session id; preserve overrides) */\n resetSession?: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n /** Cancel streaming preview + in-flight LLM work for this session (e.g. /abort) */\n abortSessionTurn?: (sessionKey: string) => Promise<void>;\n /** Reload skills from disk and refresh active agent prompts. */\n reloadSkills?: () => void | Promise<void>;\n /** Install a managed skill from an explicit source and refresh active agent prompts. */\n installSkillFromSource?: (opts: SkillInstallToolOptions) => Promise<SkillInstallToolResult>;\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (\n sessionKey: string,\n question: string,\n options?: BtwQueryOptions,\n ) => Promise<{ text: string; error?: string }>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n\n getPersistentGoalApisForCommand: (routing: {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n }) => PersistentGoalApis;\n getWorkflowRunService?: () => WorkflowRunServiceLike | undefined;\n}\n\nexport class CommandHandler {\n private config: Config;\n private bus: MessageBus;\n private sessionStore: SessionStore;\n private sessionConfigStore?: SessionConfigStore;\n private applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n private getCurrentModel: () => string;\n private switchModelForSession: (sessionKey: string, modelId: string) => Promise<boolean>;\n private invalidateAgentSession?: (sessionKey: string) => void;\n private abortSessionTurn?: (sessionKey: string) => Promise<void>;\n private reloadSkills?: CommandHandlerConfig['reloadSkills'];\n private installSkillFromSource?: CommandHandlerConfig['installSkillFromSource'];\n private compactSession?: CommandHandlerConfig['compactSession'];\n private btwQuery?: CommandHandlerConfig['btwQuery'];\n private getSessionContextReport?: CommandHandlerConfig['getSessionContextReport'];\n private getPersistentGoalApisForCommand: CommandHandlerConfig['getPersistentGoalApisForCommand'];\n private resetSession?: CommandHandlerConfig['resetSession'];\n private getWorkflowRunService?: CommandHandlerConfig['getWorkflowRunService'];\n\n constructor(handlerConfig: CommandHandlerConfig) {\n this.config = handlerConfig.config;\n this.bus = handlerConfig.bus;\n this.sessionStore = handlerConfig.sessionStore;\n this.sessionConfigStore = handlerConfig.sessionConfigStore;\n this.applySessionThinkingLevel = handlerConfig.applySessionThinkingLevel;\n this.getCurrentModel = handlerConfig.getCurrentModel;\n this.switchModelForSession = handlerConfig.switchModelForSession;\n this.invalidateAgentSession = handlerConfig.invalidateAgentSession;\n this.abortSessionTurn = handlerConfig.abortSessionTurn;\n this.reloadSkills = handlerConfig.reloadSkills;\n this.installSkillFromSource = handlerConfig.installSkillFromSource;\n this.compactSession = handlerConfig.compactSession;\n this.btwQuery = handlerConfig.btwQuery;\n this.getSessionContextReport = handlerConfig.getSessionContextReport;\n this.getPersistentGoalApisForCommand = handlerConfig.getPersistentGoalApisForCommand;\n this.resetSession = handlerConfig.resetSession;\n this.getWorkflowRunService = handlerConfig.getWorkflowRunService;\n }\n\n /** Replace config reference after hot reload or gateway PATCH so commands see current defaults. */\n updateAgentConfig(config: Config): void {\n this.config = config;\n }\n\n /**\n * Build the unified command context shared by all execute paths.\n * When `recorder` is set, every reply text is also captured (for SSE / CLI aggregation).\n */\n private buildCommandContext(\n context: CommandContext,\n recorder?: (text: string) => void,\n emitEvent?: (event: CommandStreamEvent) => void | Promise<void>,\n ): UnifiedCommandContext {\n const skipBusOutbound = shouldSkipBusOutboundForChannel(context.channel);\n\n return createCommandContext({\n sessionKey: context.sessionKey,\n source: context.channel as 'telegram' | 'webui' | 'cli' | 'api' | 'system' | 'gateway',\n channelId: context.channel,\n chatId: context.chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n config: this.config,\n bus: this.bus,\n sessionStore: this.sessionStore,\n sessionConfigStore: this.sessionConfigStore,\n applySessionThinkingLevel: this.applySessionThinkingLevel,\n\n replyHandler: async (text: string, _options?) => {\n recorder?.(text);\n if (skipBusOutbound) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: text,\n type: 'message',\n });\n },\n\n typingHandler: async (typing: boolean) => {\n if (skipBusOutbound) return;\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n type: typing ? 'typing_on' : 'typing_off',\n });\n },\n\n supportedFeatures: ['markdown', 'typing'],\n\n getCurrentModel: this.getCurrentModel,\n\n switchModel: async (modelId: string) => {\n return this.switchModelForSession(context.sessionKey, modelId);\n },\n\n listModels: async () => {\n const providers = getAllProviders();\n const models: Array<{ id: string; name: string; provider: string }> = [];\n\n for (const providerId of providers) {\n if (isProviderConfiguredSync(providerId)) {\n const providerModels = getModelsByProvider(providerId);\n for (const m of providerModels) {\n models.push({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: getProviderDisplayName(providerId),\n });\n }\n }\n }\n\n return models;\n },\n\n getUsage: async () => {\n const messages = await this.sessionStore.load(context.sessionKey);\n let promptTokens = 0;\n let completionTokens = 0;\n\n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n const usage = msg.usage as { input?: number; output?: number };\n promptTokens += usage.input || 0;\n completionTokens += usage.output || 0;\n }\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n },\n\n invalidateAgentSession: this.invalidateAgentSession,\n\n resetSession: this.resetSession\n ? async (sk) => {\n await this.resetSession!(sk);\n }\n : undefined,\n\n abortCurrentTurn: this.abortSessionTurn\n ? async () => {\n await this.abortSessionTurn!(context.sessionKey);\n }\n : undefined,\n reloadSkills: this.reloadSkills\n ? async () => {\n await this.reloadSkills!();\n }\n : undefined,\n installSkillFromSource: this.installSkillFromSource\n ? (opts) => this.installSkillFromSource!({ ...opts, sessionKey: context.sessionKey })\n : undefined,\n\n compactSession: this.compactSession,\n btwQuery: this.btwQuery,\n emitEvent,\n getSessionContextReport: this.getSessionContextReport,\n persistentGoalApis: this.getPersistentGoalApisForCommand({\n sessionKey: context.sessionKey,\n channel: context.channel,\n chatId: context.chatId,\n inboundMetadata: context.inboundMetadata,\n }),\n workflowRunService: this.getWorkflowRunService?.(),\n });\n }\n\n /**\n * Execute a command using the unified command system\n */\n async executeCommand(\n commandName: string,\n args: string,\n context: CommandContext,\n ): Promise<boolean> {\n if (!commandRegistry.has(commandName)) {\n return false;\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command via new system');\n\n const cmdCtx = this.buildCommandContext(context);\n const result = await commandRegistry.execute(commandName, cmdCtx, args);\n\n if (result.content && !shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n\n return true;\n }\n\n /**\n * Run command and return all user-visible text (ctx.reply + result.content) for SSE/CLI.\n * Same bus side effects as {@link executeCommand}.\n */\n async executeCommandAndAggregateReply(\n commandName: string,\n args: string,\n context: CommandContext,\n options?: { emitEvent?: (event: CommandStreamEvent) => void | Promise<void> },\n ): Promise<{ handled: boolean; aggregatedText: string; metadata?: Record<string, unknown> }> {\n if (!commandRegistry.has(commandName)) {\n return { handled: false, aggregatedText: '' };\n }\n\n log.info({ command: commandName, sessionKey: context.sessionKey }, 'Executing command (aggregate reply)');\n\n const segments: string[] = [];\n const wrapped = this.buildCommandContext(context, (text) => segments.push(text), options?.emitEvent);\n const result = await commandRegistry.execute(commandName, wrapped, args);\n\n if (result.content) {\n segments.push(result.content);\n if (!shouldSkipBusOutboundForChannel(context.channel)) {\n await this.bus.publishOutbound({\n channel: context.channel,\n chat_id: context.chatId,\n content: result.content,\n type: 'message',\n });\n }\n }\n\n const aggregatedText = segments.filter((s) => s && s.trim()).join('\\n\\n');\n return { handled: true, aggregatedText, metadata: result.metadata };\n }\n}\n"],"mappings":"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"}
|
|
@@ -68,9 +68,11 @@ async function runBtwQuery(opts) {
|
|
|
68
68
|
const controller = new AbortController();
|
|
69
69
|
const timeoutId = setTimeout(() => controller.abort(), 9e4);
|
|
70
70
|
try {
|
|
71
|
+
const requestedMaxTokens = Math.max(1, Math.trunc(opts.maxTokens ?? 2048));
|
|
72
|
+
const modelMaxTokens = typeof model.maxTokens === "number" && Number.isFinite(model.maxTokens) ? Math.max(1, Math.trunc(model.maxTokens)) : void 0;
|
|
71
73
|
const text = textFromCompleteContent((await complete(model, { messages: [userMessage] }, {
|
|
72
|
-
maxTokens:
|
|
73
|
-
temperature: .4,
|
|
74
|
+
maxTokens: modelMaxTokens ? Math.min(requestedMaxTokens, modelMaxTokens) : requestedMaxTokens,
|
|
75
|
+
temperature: opts.temperature ?? .4,
|
|
74
76
|
signal: controller.signal
|
|
75
77
|
})).content).trim();
|
|
76
78
|
return text ? { text } : {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btw-query.js","names":[],"sources":["../../../../src/agent/service/btw-query.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport type { SessionStore } from '../../session/index.js';\nimport { resolveModel } from '../../providers/index.js';\n\ntype BtwLog = { warn: (obj: Record<string, unknown>, msg: string) => void };\n\nfunction textFromCompleteContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n return content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n c?.type === 'text' && typeof (c as { text?: unknown }).text === 'string',\n )\n .map((c) => c.text || '')\n .join('');\n}\n\nfunction formatMessagesForBtw(messages: AgentMessage[]): string {\n return messages\n .map((m) => {\n const role = m.role;\n let body = '';\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') {\n body = raw;\n } else if (Array.isArray(raw)) {\n body = raw\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text || '')\n .join('\\n');\n }\n const line = `[${role}]: ${body}`;\n return line.length > 4000 ? `${line.slice(0, 4000)}…` : line;\n })\n .join('\\n\\n');\n}\n\n/** One-shot LLM answer for /btw: transcript as background only; does not persist. */\nexport async function runBtwQuery(opts: {\n sessionKey: string;\n question: string;\n sessionStore: SessionStore;\n modelForSession: string;\n log: BtwLog;\n}): Promise<{ text: string; error?: string }> {\n const q = opts.question.trim();\n if (!q) {\n return { text: '', error: 'Empty question.' };\n }\n const messages = await opts.sessionStore.load(opts.sessionKey);\n const modelRef = opts.modelForSession;\n let model;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, modelRef, errorMessage: em }, 'btwQuery: model resolve failed');\n return { text: '', error: `Could not resolve model: ${modelRef}` };\n }\n\n const background = formatMessagesForBtw(messages.slice(-40));\n const systemBlock = [\n 'You are answering an ephemeral /btw side question about the current conversation.',\n 'Use the conversation only as background context.',\n 'Answer only the side question. Do not continue or complete any unfinished task from the conversation.',\n 'Do not use tools, commands, or file writes unless the question explicitly requires a tiny code snippet.',\n 'If the question can be answered briefly, answer briefly.',\n ].join('\\n');\n\n const userPrompt = [\n systemBlock,\n '',\n '---',\n 'Conversation background (read-only):',\n background || '(empty)',\n '',\n '---',\n 'Side question:',\n q,\n ].join('\\n');\n\n const userMessage: UserMessage = { role: 'user', content: userPrompt, timestamp: Date.now() };\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 90_000);\n try {\n const out = await complete(model, { messages: [userMessage] }, {\n maxTokens:
|
|
1
|
+
{"version":3,"file":"btw-query.js","names":[],"sources":["../../../../src/agent/service/btw-query.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport type { SessionStore } from '../../session/index.js';\nimport { resolveModel } from '../../providers/index.js';\n\ntype BtwLog = { warn: (obj: Record<string, unknown>, msg: string) => void };\n\nfunction textFromCompleteContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n return content\n .filter(\n (c): c is { type: 'text'; text: string } =>\n c?.type === 'text' && typeof (c as { text?: unknown }).text === 'string',\n )\n .map((c) => c.text || '')\n .join('');\n}\n\nfunction formatMessagesForBtw(messages: AgentMessage[]): string {\n return messages\n .map((m) => {\n const role = m.role;\n let body = '';\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') {\n body = raw;\n } else if (Array.isArray(raw)) {\n body = raw\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text || '')\n .join('\\n');\n }\n const line = `[${role}]: ${body}`;\n return line.length > 4000 ? `${line.slice(0, 4000)}…` : line;\n })\n .join('\\n\\n');\n}\n\n/** One-shot LLM answer for /btw: transcript as background only; does not persist. */\nexport async function runBtwQuery(opts: {\n sessionKey: string;\n question: string;\n sessionStore: SessionStore;\n modelForSession: string;\n log: BtwLog;\n maxTokens?: number;\n temperature?: number;\n}): Promise<{ text: string; error?: string }> {\n const q = opts.question.trim();\n if (!q) {\n return { text: '', error: 'Empty question.' };\n }\n const messages = await opts.sessionStore.load(opts.sessionKey);\n const modelRef = opts.modelForSession;\n let model;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, modelRef, errorMessage: em }, 'btwQuery: model resolve failed');\n return { text: '', error: `Could not resolve model: ${modelRef}` };\n }\n\n const background = formatMessagesForBtw(messages.slice(-40));\n const systemBlock = [\n 'You are answering an ephemeral /btw side question about the current conversation.',\n 'Use the conversation only as background context.',\n 'Answer only the side question. Do not continue or complete any unfinished task from the conversation.',\n 'Do not use tools, commands, or file writes unless the question explicitly requires a tiny code snippet.',\n 'If the question can be answered briefly, answer briefly.',\n ].join('\\n');\n\n const userPrompt = [\n systemBlock,\n '',\n '---',\n 'Conversation background (read-only):',\n background || '(empty)',\n '',\n '---',\n 'Side question:',\n q,\n ].join('\\n');\n\n const userMessage: UserMessage = { role: 'user', content: userPrompt, timestamp: Date.now() };\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 90_000);\n try {\n const requestedMaxTokens = Math.max(1, Math.trunc(opts.maxTokens ?? 2048));\n const modelMaxTokens = typeof model.maxTokens === 'number' && Number.isFinite(model.maxTokens)\n ? Math.max(1, Math.trunc(model.maxTokens))\n : undefined;\n const out = await complete(model, { messages: [userMessage] }, {\n maxTokens: modelMaxTokens ? Math.min(requestedMaxTokens, modelMaxTokens) : requestedMaxTokens,\n temperature: opts.temperature ?? 0.4,\n signal: controller.signal as AbortSignal,\n });\n const text = textFromCompleteContent((out as { content?: unknown }).content).trim();\n return text ? { text } : { text: '', error: 'No text returned from model.' };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, sessionKey: opts.sessionKey, errorMessage: em }, 'btwQuery failed');\n return { text: '', error: em };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":";;;;gBAKwD;AAIxD,SAAS,wBAAwB,SAA0B;AACzD,KAAI,OAAO,YAAY,SACrB,QAAO;AAET,KAAI,CAAC,MAAM,QAAQ,QAAQ,CACzB,QAAO;AAET,QAAO,QACJ,QACE,MACC,GAAG,SAAS,UAAU,OAAQ,EAAyB,SAAS,SACnE,CACA,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;;AAGb,SAAS,qBAAqB,UAAkC;AAC9D,QAAO,SACJ,KAAK,MAAM;EACV,MAAM,OAAO,EAAE;EACf,IAAI,OAAO;EACX,MAAM,MAAM,wBAAwB,EAAE;AACtC,MAAI,OAAO,QAAQ,SACjB,QAAO;WACE,MAAM,QAAQ,IAAI,CAC3B,QAAO,IACJ,QAAQ,MAA2C,EAAE,SAAS,OAAO,CACrE,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,KAAK;EAEf,MAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,SAAO,KAAK,SAAS,MAAO,GAAG,KAAK,MAAM,GAAG,IAAK,CAAC,KAAK;GACxD,CACD,KAAK,OAAO;;;AAIjB,eAAsB,YAAY,MAQY;CAC5C,MAAM,IAAI,KAAK,SAAS,MAAM;AAC9B,KAAI,CAAC,EACH,QAAO;EAAE,MAAM;EAAI,OAAO;EAAmB;CAE/C,MAAM,WAAW,MAAM,KAAK,aAAa,KAAK,KAAK,WAAW;CAC9D,MAAM,WAAW,KAAK;CACtB,IAAI;AACJ,KAAI;AACF,UAAQ,aAAa,SAAS;UACvB,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KAAK;GAAE;GAAK;GAAU,cAAc;GAAI,EAAE,iCAAiC;AACpF,SAAO;GAAE,MAAM;GAAI,OAAO,4BAA4B;GAAY;;CAGpE,MAAM,aAAa,qBAAqB,SAAS,MAAM,IAAI,CAAC;CAqB5D,MAAM,cAA2B;EAAE,MAAM;EAAQ,SAZ9B;GARC;IAClB;IACA;IACA;IACA;IACA;IACD,CAAC,KAAK,KAGM;GACX;GACA;GACA;GACA,cAAc;GACd;GACA;GACA;GACA;GACD,CAAC,KAAK,KAE6D;EAAE,WAAW,KAAK,KAAK;EAAE;CAC7F,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,IAAO;AAC9D,KAAI;EACF,MAAM,qBAAqB,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa,KAAK,CAAC;EAC1E,MAAM,iBAAiB,OAAO,MAAM,cAAc,YAAY,OAAO,SAAS,MAAM,UAAU,GAC1F,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,UAAU,CAAC,GACxC,KAAA;EAMJ,MAAM,OAAO,yBAAyB,MALpB,SAAS,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE,EAAE;GAC7D,WAAW,iBAAiB,KAAK,IAAI,oBAAoB,eAAe,GAAG;GAC3E,aAAa,KAAK,eAAe;GACjC,QAAQ,WAAW;GACpB,CAAC,EACkE,QAAQ,CAAC,MAAM;AACnF,SAAO,OAAO,EAAE,MAAM,GAAG;GAAE,MAAM;GAAI,OAAO;GAAgC;UACrE,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KAAK;GAAE;GAAK,YAAY,KAAK;GAAY,cAAc;GAAI,EAAE,kBAAkB;AACxF,SAAO;GAAE,MAAM;GAAI,OAAO;GAAI;WACtB;AACR,eAAa,UAAU"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
9
9
|
import type { ImageContent } from '@earendil-works/pi-ai';
|
|
10
10
|
import type { CommandHandler } from '../messaging/command-handler.js';
|
|
11
|
+
import type { CommandStreamEvent } from '../../chat-commands/types.js';
|
|
11
12
|
import type { SessionStore } from '../../session/index.js';
|
|
12
13
|
import type { Config } from '../../config/schema.js';
|
|
13
14
|
import type { AgentInstanceGateway } from '../agent-instance-gateway.js';
|
|
@@ -50,6 +51,7 @@ export declare function tryRunSlashCommand(deps: TryRunSlashCommandDeps, ctx: {
|
|
|
50
51
|
inboundMetadata?: Record<string, unknown>;
|
|
51
52
|
}, content: string, options?: {
|
|
52
53
|
skipResetCommands?: boolean;
|
|
54
|
+
emitEvent?: (event: CommandStreamEvent) => void | Promise<void>;
|
|
53
55
|
}): Promise<SlashCommandOutcome>;
|
|
54
56
|
export interface RunDirectAgentTurnDeps {
|
|
55
57
|
sessionStore: SessionStore;
|
|
@@ -48,7 +48,7 @@ async function tryRunSlashCommand(deps, ctx, content, options) {
|
|
|
48
48
|
senderId: ctx.senderId ?? "",
|
|
49
49
|
isGroup: ctx.isGroup ?? false,
|
|
50
50
|
inboundMetadata: ctx.inboundMetadata ?? {}
|
|
51
|
-
});
|
|
51
|
+
}, { emitEvent: options?.emitEvent });
|
|
52
52
|
return {
|
|
53
53
|
matched: true,
|
|
54
54
|
aggregatedText: aggregatedText ?? "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"direct-turn-helpers.js","names":[],"sources":["../../../../src/agent/service/direct-turn-helpers.ts"],"sourcesContent":["/**\n * Shared building blocks for the two direct-turn entry points (streaming\n * webchat SSE and one-shot CLI). Both flows do the same hydrate → maybe-slash\n * → run-embedded-turn → after-turn dance; this module captures that core so\n * the entry points only manage their I/O specifics (event sink, voice STT,\n * TTS, transcript persistence).\n */\n\nimport crypto from 'node:crypto';\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport { shouldSkipResetOverlapCommand } from '../../session/reset-triggers.js';\nimport { hydrateUserTurnForLlm, type TranscriptUserMessage } from '../inbound/attachment-pipeline.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runEmbeddedTurnForSession } from '../embedded/run-for-session.js';\nimport type { EmbeddedStreamEvent } from '../embedded/types.js';\nimport { resolveImageHandlingStrategy } from '../image/vision-detection.js';\n\nexport interface HydratePerTurnStateDeps {\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n}\n\n/** Workspace + model + thinking level — common prep before any direct turn. */\nexport async function hydratePerTurnState(\n deps: HydratePerTurnStateDeps,\n sessionKey: string,\n thinking?: string,\n): Promise<void> {\n await deps.hydrateSessionWorkspaceFromStore(sessionKey);\n await deps.hydrateSessionModelFromStore(sessionKey);\n await deps.applyResolvedThinkingLevel(sessionKey, thinking);\n}\n\nexport interface SlashCommandOutcome {\n /** True if the input parsed as a registered slash command (handled or not). */\n matched: boolean;\n /** Aggregated user-visible reply text (assistant view). */\n aggregatedText: string;\n /** The parsed command name when matched. */\n command?: string;\n /** Structured command result metadata for webchat and other rich clients. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TryRunSlashCommandDeps {\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n}\n\n/**\n * Detect a slash command and, if registered, execute it and aggregate the reply\n * text. Errors thrown inside the command surface as `aggregatedText` so callers\n * can persist the receipt or stream it as a token.\n */\nexport async function tryRunSlashCommand(\n deps: TryRunSlashCommandDeps,\n ctx: {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId?: string;\n isGroup?: boolean;\n inboundMetadata?: Record<string, unknown>;\n },\n content: string,\n options?: {
|
|
1
|
+
{"version":3,"file":"direct-turn-helpers.js","names":[],"sources":["../../../../src/agent/service/direct-turn-helpers.ts"],"sourcesContent":["/**\n * Shared building blocks for the two direct-turn entry points (streaming\n * webchat SSE and one-shot CLI). Both flows do the same hydrate → maybe-slash\n * → run-embedded-turn → after-turn dance; this module captures that core so\n * the entry points only manage their I/O specifics (event sink, voice STT,\n * TTS, transcript persistence).\n */\n\nimport crypto from 'node:crypto';\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport { commandRegistry } from '../../chat-commands/index.js';\nimport { parseSlashCommand } from '../../chat-commands/command-parse.js';\nimport { shouldSkipResetOverlapCommand } from '../../session/reset-triggers.js';\nimport { hydrateUserTurnForLlm, type TranscriptUserMessage } from '../inbound/attachment-pipeline.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { CommandStreamEvent } from '../../chat-commands/types.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport { extractAgentUserPlainText } from '../memory/user-message-text.js';\nimport { runEmbeddedTurnForSession } from '../embedded/run-for-session.js';\nimport type { EmbeddedStreamEvent } from '../embedded/types.js';\nimport { resolveImageHandlingStrategy } from '../image/vision-detection.js';\n\nexport interface HydratePerTurnStateDeps {\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n}\n\n/** Workspace + model + thinking level — common prep before any direct turn. */\nexport async function hydratePerTurnState(\n deps: HydratePerTurnStateDeps,\n sessionKey: string,\n thinking?: string,\n): Promise<void> {\n await deps.hydrateSessionWorkspaceFromStore(sessionKey);\n await deps.hydrateSessionModelFromStore(sessionKey);\n await deps.applyResolvedThinkingLevel(sessionKey, thinking);\n}\n\nexport interface SlashCommandOutcome {\n /** True if the input parsed as a registered slash command (handled or not). */\n matched: boolean;\n /** Aggregated user-visible reply text (assistant view). */\n aggregatedText: string;\n /** The parsed command name when matched. */\n command?: string;\n /** Structured command result metadata for webchat and other rich clients. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface TryRunSlashCommandDeps {\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n log: { warn: (obj: Record<string, unknown>, msg: string) => void };\n}\n\n/**\n * Detect a slash command and, if registered, execute it and aggregate the reply\n * text. Errors thrown inside the command surface as `aggregatedText` so callers\n * can persist the receipt or stream it as a token.\n */\nexport async function tryRunSlashCommand(\n deps: TryRunSlashCommandDeps,\n ctx: {\n sessionKey: string;\n channel: string;\n chatId: string;\n senderId?: string;\n isGroup?: boolean;\n inboundMetadata?: Record<string, unknown>;\n },\n content: string,\n options?: {\n skipResetCommands?: boolean;\n emitEvent?: (event: CommandStreamEvent) => void | Promise<void>;\n },\n): Promise<SlashCommandOutcome> {\n const parsed = parseSlashCommand(content);\n if (!parsed) {\n return { matched: false, aggregatedText: '' };\n }\n if (options?.skipResetCommands && shouldSkipResetOverlapCommand(parsed.command, true)) {\n return { matched: false, aggregatedText: '' };\n }\n if (!commandRegistry.has(parsed.command)) {\n return { matched: false, aggregatedText: '' };\n }\n try {\n const { aggregatedText, metadata } = await deps.commandHandler.executeCommandAndAggregateReply(\n parsed.command,\n parsed.args,\n {\n sessionKey: ctx.sessionKey,\n channel: ctx.channel,\n chatId: ctx.chatId,\n senderId: ctx.senderId ?? '',\n isGroup: ctx.isGroup ?? false,\n inboundMetadata: ctx.inboundMetadata ?? {},\n },\n { emitEvent: options?.emitEvent },\n );\n return { matched: true, aggregatedText: aggregatedText ?? '', command: parsed.command, metadata };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n deps.log.warn(\n { err, sessionKey: ctx.sessionKey, command: parsed.command },\n `Slash command failed: ${em}`,\n );\n return { matched: true, aggregatedText: `Command error: ${em}`, command: parsed.command };\n }\n}\n\nexport interface RunDirectAgentTurnDeps {\n sessionStore: SessionStore;\n agentManager: AgentInstanceGateway;\n modelManager: ModelManager;\n config: Config | undefined;\n}\n\nexport interface RunDirectAgentTurnInput {\n sessionKey: string;\n runId?: string;\n userMessage: AgentMessage;\n abortSignal?: AbortSignal;\n sourceImages?: ImageContent[];\n onEvent?: (event: EmbeddedStreamEvent) => void;\n}\n\nexport interface RunDirectAgentTurnResult {\n ok: boolean;\n errorMessage?: string;\n lastAssistantText?: string;\n}\n\n/**\n * Convert a user message into an embedded turn, including the standard memory\n * prefetch + background-review nudge wiring used by every direct entry point.\n */\nexport async function runDirectAgentTurn(\n deps: RunDirectAgentTurnDeps,\n input: RunDirectAgentTurnInput,\n): Promise<RunDirectAgentTurnResult> {\n const userPlain = extractAgentUserPlainText(input.userMessage);\n const userMessageForModel = await deps.agentManager.applyMemoryPrefetchToUserMessage(\n input.userMessage,\n input.sessionKey,\n );\n\n const modelRef = deps.modelManager.getModelForSession(input.sessionKey);\n const llmTurn = await hydrateUserTurnForLlm({\n message: input.userMessage as TranscriptUserMessage,\n modelRef,\n });\n const sourceImages = resolveImageHandlingStrategy(modelRef) === 'native' ? (input.sourceImages ?? []) : [];\n const llmImages = [...llmTurn.images, ...sourceImages];\n\n const result = await runEmbeddedTurnForSession({\n sessionKey: input.sessionKey,\n runId: input.runId ?? crypto.randomUUID(),\n userMessage: userMessageForModel,\n llmImages,\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n getConfig: () => deps.config,\n abortSignal: input.abortSignal,\n beforeTurn: () => deps.agentManager.beginBackgroundReviewUserTurn(input.sessionKey),\n onEvent: input.onEvent,\n });\n\n deps.agentManager.afterAgentTurn(input.sessionKey, userPlain);\n deps.agentManager.scheduleBackgroundReviewAfterUserTurn(input.sessionKey);\n\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmCA,eAAsB,oBACpB,MACA,YACA,UACe;AACf,OAAM,KAAK,iCAAiC,WAAW;AACvD,OAAM,KAAK,6BAA6B,WAAW;AACnD,OAAM,KAAK,2BAA2B,YAAY,SAAS;;;;;;;AAwB7D,eAAsB,mBACpB,MACA,KAQA,SACA,SAI8B;CAC9B,MAAM,SAAS,kBAAkB,QAAQ;AACzC,KAAI,CAAC,OACH,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,SAAS,qBAAqB,8BAA8B,OAAO,SAAS,KAAK,CACnF,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI,CAAC,gBAAgB,IAAI,OAAO,QAAQ,CACtC,QAAO;EAAE,SAAS;EAAO,gBAAgB;EAAI;AAE/C,KAAI;EACF,MAAM,EAAE,gBAAgB,aAAa,MAAM,KAAK,eAAe,gCAC7D,OAAO,SACP,OAAO,MACP;GACE,YAAY,IAAI;GAChB,SAAS,IAAI;GACb,QAAQ,IAAI;GACZ,UAAU,IAAI,YAAY;GAC1B,SAAS,IAAI,WAAW;GACxB,iBAAiB,IAAI,mBAAmB,EAAE;GAC3C,EACD,EAAE,WAAW,SAAS,WAAW,CAClC;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAI,SAAS,OAAO;GAAS;GAAU;UAC1F,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KACP;GAAE;GAAK,YAAY,IAAI;GAAY,SAAS,OAAO;GAAS,EAC5D,yBAAyB,KAC1B;AACD,SAAO;GAAE,SAAS;GAAM,gBAAgB,kBAAkB;GAAM,SAAS,OAAO;GAAS;;;;;;;AA8B7F,eAAsB,mBACpB,MACA,OACmC;CACnC,MAAM,YAAY,0BAA0B,MAAM,YAAY;CAC9D,MAAM,sBAAsB,MAAM,KAAK,aAAa,iCAClD,MAAM,aACN,MAAM,WACP;CAED,MAAM,WAAW,KAAK,aAAa,mBAAmB,MAAM,WAAW;CACvE,MAAM,UAAU,MAAM,sBAAsB;EAC1C,SAAS,MAAM;EACf;EACD,CAAC;CACF,MAAM,eAAe,6BAA6B,SAAS,KAAK,WAAY,MAAM,gBAAgB,EAAE,GAAI,EAAE;CAC1G,MAAM,YAAY,CAAC,GAAG,QAAQ,QAAQ,GAAG,aAAa;CAEtD,MAAM,SAAS,MAAM,0BAA0B;EAC7C,YAAY,MAAM;EAClB,OAAO,MAAM,SAAS,OAAO,YAAY;EACzC,aAAa;EACb;EACA,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,cAAc,KAAK;EACnB,iBAAiB,KAAK;EACtB,aAAa,MAAM;EACnB,kBAAkB,KAAK,aAAa,8BAA8B,MAAM,WAAW;EACnF,SAAS,MAAM;EAChB,CAAC;AAEF,MAAK,aAAa,eAAe,MAAM,YAAY,UAAU;AAC7D,MAAK,aAAa,sCAAsC,MAAM,WAAW;AAEzE,QAAO"}
|
|
@@ -15,6 +15,51 @@ import { hydratePerTurnState, runDirectAgentTurn, tryRunSlashCommand } from "./d
|
|
|
15
15
|
function isReviewOutput(value) {
|
|
16
16
|
return !!value && typeof value === "object" && !Array.isArray(value) && value.type === "review";
|
|
17
17
|
}
|
|
18
|
+
const REVIEW_TRACE_PREVIEW_MAX = 2e3;
|
|
19
|
+
function boundedPreview(text) {
|
|
20
|
+
return text.length > REVIEW_TRACE_PREVIEW_MAX ? `${text.slice(0, REVIEW_TRACE_PREVIEW_MAX)}\n[trace preview truncated]` : text;
|
|
21
|
+
}
|
|
22
|
+
function extractTraceResultText(result) {
|
|
23
|
+
if (typeof result === "string") return result;
|
|
24
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return result == null ? "" : String(result);
|
|
25
|
+
const rec = result;
|
|
26
|
+
if (typeof rec.text === "string") return rec.text;
|
|
27
|
+
if (Array.isArray(rec.content)) return rec.content.map((block) => {
|
|
28
|
+
if (!block || typeof block !== "object") return "";
|
|
29
|
+
const b = block;
|
|
30
|
+
return b.type === "text" && typeof b.text === "string" ? b.text : "";
|
|
31
|
+
}).join("");
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
function reviewTraceContextEntryFromCommandEvent(event, runId) {
|
|
35
|
+
if (event.type !== "tool_execution_start" && event.type !== "tool_execution_end") return null;
|
|
36
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
37
|
+
if (!toolName.startsWith("review.")) return null;
|
|
38
|
+
const toolCallId = typeof event.toolCallId === "string" && event.toolCallId ? event.toolCallId : `review_trace_${Date.now()}`;
|
|
39
|
+
const ended = event.type === "tool_execution_end";
|
|
40
|
+
const isError = ended && event.isError === true;
|
|
41
|
+
const resultPreview = ended ? boundedPreview(extractTraceResultText(event.result).trim()) : void 0;
|
|
42
|
+
const details = ended && event.result && typeof event.result === "object" && !Array.isArray(event.result) ? event.result.details : void 0;
|
|
43
|
+
return {
|
|
44
|
+
id: `review-trace:${toolCallId}:${ended ? "end" : "start"}`,
|
|
45
|
+
text: ended ? `Review trace: ${toolName} ${isError ? "failed" : "completed"}` : `Review trace: ${toolName} started`,
|
|
46
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47
|
+
data: {
|
|
48
|
+
type: "review_trace",
|
|
49
|
+
scope: "review",
|
|
50
|
+
event: ended ? "tool_end" : "tool_start",
|
|
51
|
+
llmInput: false,
|
|
52
|
+
...runId ? { runId } : {},
|
|
53
|
+
toolCallId,
|
|
54
|
+
toolName,
|
|
55
|
+
status: ended ? isError ? "error" : "done" : "running",
|
|
56
|
+
...event.args !== void 0 ? { input: event.args } : {},
|
|
57
|
+
...resultPreview ? { resultPreview } : {},
|
|
58
|
+
...details !== void 0 ? { details } : {},
|
|
59
|
+
...isError ? { isError: true } : {}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
18
63
|
function makeAssistantReceiptMessage(text, metadata) {
|
|
19
64
|
const review = isReviewOutput(metadata?.review) ? metadata.review : void 0;
|
|
20
65
|
return {
|
|
@@ -76,6 +121,7 @@ async function* runProcessDirectStreaming(deps, input) {
|
|
|
76
121
|
let mergedUserText = input.content;
|
|
77
122
|
let webchatSlashReceipt;
|
|
78
123
|
let slashCommandMetadata;
|
|
124
|
+
const slashTraceRows = [];
|
|
79
125
|
const taskPromise = (async () => {
|
|
80
126
|
try {
|
|
81
127
|
const cfg = deps.getConfig();
|
|
@@ -145,7 +191,17 @@ async function* runProcessDirectStreaming(deps, input) {
|
|
|
145
191
|
senderId: context.senderId,
|
|
146
192
|
isGroup: context.isGroup,
|
|
147
193
|
inboundMetadata: context.metadata
|
|
148
|
-
}, mergedUserText, {
|
|
194
|
+
}, mergedUserText, {
|
|
195
|
+
skipResetCommands: resetTriggeredAtInit,
|
|
196
|
+
emitEvent: (event) => {
|
|
197
|
+
pushVisible({
|
|
198
|
+
...event,
|
|
199
|
+
runId: input.runId
|
|
200
|
+
});
|
|
201
|
+
const traceRow = reviewTraceContextEntryFromCommandEvent(event, input.runId);
|
|
202
|
+
if (traceRow) slashTraceRows.push(traceRow);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
149
205
|
if (slash.matched) {
|
|
150
206
|
ranSlashCommand = true;
|
|
151
207
|
slashCommandMetadata = slash.metadata;
|
|
@@ -247,6 +303,7 @@ async function* runProcessDirectStreaming(deps, input) {
|
|
|
247
303
|
timestamp: Date.now()
|
|
248
304
|
};
|
|
249
305
|
await deps.sessionStore.appendTranscriptMessage(sessionKey, userMsg);
|
|
306
|
+
for (const traceRow of slashTraceRows) await deps.sessionStore.appendTranscriptContextEntry(sessionKey, traceRow);
|
|
250
307
|
if (webchatSlashReceipt?.trim()) {
|
|
251
308
|
const assistantMsg = {
|
|
252
309
|
role: "assistant",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-direct-streaming.js","names":[],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport {\n isVoiceLikeAttachment,\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n initSessionTurn,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { formatAgentRunErrorForClient } from '../client-error-format.js';\nimport { abortEmbeddedRun } from '../embedded/runs.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\nimport { injectSourceContextIntoUserMessage } from '../source-context/injector.js';\nimport { isSessionSourceBinding, type AgentSourceContextResolver } from '../source-context/types.js';\nimport type { ReviewOutput } from '../../review/review-types.js';\n\nimport { AsyncQueue } from './async-queue.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\n\nexport type DirectStreamInboundAttachment = InboundAttachmentInput;\n\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n resolveSessionEndpoint: (sessionKey: string) => Promise<{ channel: string; chatId: string }>;\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentInstanceGateway;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<MediaRef[] | undefined>;\n buildTranscriptUserMessage: (\n content: string,\n prepared: MediaRef[] | undefined,\n sessionKey: string,\n ) => Promise<TranscriptUserMessage>;\n recordPersistentGoalStreamOutcome?: (\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ) => void;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n enqueueProvisionalSessionTitle?: (sessionKey: string, userText: string) => void;\n /** Disk-only transcript sync (slash receipt already streamed as tokens). */\n reloadWebchatTranscript?: (sessionKey: string) => void;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; uri: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n sourceContextResolver?: AgentSourceContextResolver;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n runId?: string;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nfunction isReviewOutput(value: unknown): value is ReviewOutput {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n && (value as { type?: unknown }).type === 'review';\n}\n\nfunction makeAssistantReceiptMessage(text: string, metadata?: Record<string, unknown>): AgentMessage {\n const review = isReviewOutput(metadata?.review) ? metadata.review : undefined;\n return {\n role: 'assistant',\n content: review ? [review] : [{ type: 'text', text }],\n timestamp: Date.now(),\n ...(metadata && Object.keys(metadata).length > 0 ? { metadata } : {}),\n } as AgentMessage;\n}\n\nfunction pushAssistantReceipt(\n queue: AsyncQueue<ProcessDirectStreamingSseEvent>,\n text: string,\n runId?: string,\n metadata?: Record<string, unknown>,\n): void {\n const message = makeAssistantReceiptMessage(text, metadata);\n queue.push({ type: 'message_start', runId, message });\n queue.push({ type: 'message_update', runId, message });\n queue.push({ type: 'message_end', runId, message });\n}\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const sessionKey = input.sessionKey ?? 'agent:main:main';\n const { channel, chatId } = await deps.resolveSessionEndpoint(sessionKey);\n const context = deps.initDirectStreamingSession(sessionKey, channel, chatId);\n\n const queue = new AsyncQueue<ProcessDirectStreamingSseEvent>();\n let reasoningLevel: ReasoningLevel = 'stream';\n\n const pushVisible = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n queue.push(visible);\n }\n };\n\n const formatStreamError = (raw: string): string => {\n let provider: string | undefined;\n let modelRef: string | undefined;\n try {\n const resolved = deps.modelManager.getResolvedModelForSession(sessionKey);\n provider = resolved.provider;\n modelRef = deps.modelManager.getModelForSession(sessionKey);\n } catch {\n /* ignore — format without provider context */\n }\n return formatAgentRunErrorForClient(raw, { provider, modelRef });\n };\n\n if (channel === 'webchat') {\n deps.registerWebchatSsePublisher(sessionKey, pushVisible);\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n let inboundVoice = false;\n let ranSlashCommand = false;\n let mergedUserText = input.content;\n let webchatSlashReceipt: string | undefined;\n let slashCommandMetadata: Record<string, unknown> | undefined;\n\n const taskPromise = (async () => {\n try {\n const cfg = deps.getConfig();\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n if (cfg) {\n const turn = await initSessionTurn({\n cfg,\n sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n ranSlashCommand = true;\n webchatSlashReceipt = turn.ackMessage;\n pushAssistantReceipt(queue, turn.ackMessage, input.runId);\n return;\n }\n turnBody = turn.bodyStripped;\n if (turn.isNewSession) {\n deps.log.debug(\n {\n sessionKey,\n sessionId: turn.sessionId,\n previousSessionId: turn.previousSessionId,\n resetTriggered: turn.resetTriggered,\n staleRollover: turn.staleRollover,\n },\n 'Session reset boundary at direct turn start',\n );\n }\n }\n\n await hydratePerTurnState(deps, sessionKey, input.thinking);\n {\n const defReason = 'stream' as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(deps.sessionConfigStore, sessionKey, defReason);\n }\n\n const prepared = await deps.prepareInboundAttachments(sessionKey, input.attachments);\n\n const sttCfg = mergeSttConfigFromAppConfig(deps.getConfig()?.tools?.media?.audio, deps.getConfig()?.tools?.media);\n const voiceMerge = await mergeVoiceTranscriptsIntoUserText(prepared, turnBody, sttCfg);\n mergedUserText = voiceMerge.text;\n inboundVoice = voiceMerge.inboundVoice;\n\n if (inboundVoice) {\n const transcriptParts = [\n voiceMerge.voiceTranscripts.filter(Boolean).join('\\n'),\n turnBody.trim(),\n ].filter(Boolean);\n const voiceMedia = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({\n uri: att.uri,\n mimeType: att.mimeType,\n name: att.name,\n }));\n pushVisible({\n type: 'user_transcript',\n text: transcriptParts.join('\\n\\n'),\n media: voiceMedia,\n });\n }\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n void abortEmbeddedRun(sessionKey);\n queue.close();\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n return;\n }\n signal.addEventListener('abort', armAbort, { once: true });\n }\n\n const slash = await tryRunSlashCommand(\n deps,\n {\n sessionKey,\n channel,\n chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n inboundMetadata: context.metadata,\n },\n mergedUserText,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n ranSlashCommand = true;\n slashCommandMetadata = slash.metadata;\n const text = slash.aggregatedText.trim();\n if (text) {\n webchatSlashReceipt = text;\n pushAssistantReceipt(queue, text, input.runId, slashCommandMetadata);\n } else if (channel === 'webchat') {\n webchatSlashReceipt =\n 'Command finished with no assistant text. If you used `/goal`, a follow-up turn may still be scheduled automatically.';\n pushAssistantReceipt(queue, webchatSlashReceipt, input.runId);\n }\n const workflowRun = slash.metadata?.workflowRun;\n if (workflowRun && typeof workflowRun === 'object') {\n queue.push({\n type: 'workflow_run_started',\n runId: input.runId,\n payload: { workflowRun },\n });\n }\n return;\n }\n\n const skillTurn = deps.agentManager.prepareSkillTurn(sessionKey, mergedUserText);\n const textForAgent = skillTurn.text;\n const userMessage = await deps.buildTranscriptUserMessage(textForAgent, prepared, sessionKey);\n let sourceEnrichedUserMessage: AgentMessage = userMessage;\n let sourceImages: ImageContent[] | undefined;\n if (deps.sourceContextResolver) {\n const metadata = await deps.sessionStore.getMetadata(sessionKey).catch(() => null);\n const sourceBinding = metadata?.customData && typeof metadata.customData === 'object'\n ? (metadata.customData as Record<string, unknown>).sourceBinding\n : undefined;\n if (isSessionSourceBinding(sourceBinding)) {\n const sourceContext = await deps.sourceContextResolver(sourceBinding, sessionKey);\n sourceEnrichedUserMessage = injectSourceContextIntoUserMessage(userMessage, sourceContext);\n sourceImages = sourceContext?.images;\n }\n }\n\n if (channel === 'webchat') {\n pushVisible({\n type: 'user_message',\n timestamp: userMessage.timestamp ?? Date.now(),\n content: readAgentMessageContent(userMessage),\n media: userMessage.media,\n });\n if (textForAgent.trim()) {\n deps.enqueueProvisionalSessionTitle?.(sessionKey, textForAgent);\n }\n }\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n\n try {\n const result = await deps.agentManager.withSkillCapabilities(\n sessionKey,\n skillTurn.activatedCapabilityNames,\n () =>\n runDirectAgentTurn(\n {\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n config: deps.getConfig(),\n },\n {\n sessionKey,\n userMessage: sourceEnrichedUserMessage,\n abortSignal: signal,\n sourceImages,\n runId: input.runId,\n onEvent: (embeddedEvent) => {\n const event = { ...embeddedEvent };\n if (event.type === 'error' && typeof event.content === 'string') {\n event.content = formatStreamError(event.content);\n }\n pushVisible(event);\n },\n },\n ),\n );\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(sessionKey, result.lastAssistantText);\n }\n if (!result.ok && result.errorMessage && !abortHandled) {\n pushVisible({ type: 'error', content: formatStreamError(result.errorMessage) });\n }\n } finally {\n clearPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n }\n } catch (err) {\n if (!abortHandled) {\n const em = err instanceof Error ? err.message : String(err);\n pushVisible({ type: 'error', content: formatStreamError(em) });\n }\n } finally {\n if (!userAborted && channel === 'webchat') {\n try {\n const ttsAudioEvent = await deps.maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n queue.push(ttsAudioEvent);\n }\n } catch (ttsErr) {\n deps.log.warn({ err: ttsErr, sessionKey }, 'Failed to emit TTS audio before stream close');\n }\n }\n queue.close();\n }\n })();\n\n try {\n for await (const event of queue) {\n yield event;\n }\n await taskPromise;\n\n if (channel === 'webchat' && ranSlashCommand) {\n try {\n const userMsg = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: mergedUserText }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, userMsg);\n if (webchatSlashReceipt?.trim()) {\n const assistantMsg = {\n role: 'assistant' as const,\n content: isReviewOutput(slashCommandMetadata?.review)\n ? [slashCommandMetadata.review]\n : [{ type: 'text' as const, text: webchatSlashReceipt.trim() }],\n timestamp: Date.now(),\n ...(slashCommandMetadata && Object.keys(slashCommandMetadata).length > 0\n ? { metadata: slashCommandMetadata }\n : {}),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, assistantMsg);\n }\n deps.reloadWebchatTranscript?.(sessionKey);\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Failed to persist webchat slash command receipt');\n }\n }\n\n deps.recordPersistentGoalStreamOutcome?.(sessionKey, { skipPersistentGoalPostTurn: ranSlashCommand });\n } finally {\n if (channel === 'webchat') {\n deps.unregisterWebchatSsePublisher(sessionKey);\n }\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA4GA,SAAS,eAAe,OAAuC;AAC7D,QAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,IAC9D,MAA6B,SAAS;;AAG9C,SAAS,4BAA4B,MAAc,UAAkD;CACnG,MAAM,SAAS,eAAe,UAAU,OAAO,GAAG,SAAS,SAAS,KAAA;AACpE,QAAO;EACL,MAAM;EACN,SAAS,SAAS,CAAC,OAAO,GAAG,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC;EACrD,WAAW,KAAK,KAAK;EACrB,GAAI,YAAY,OAAO,KAAK,SAAS,CAAC,SAAS,IAAI,EAAE,UAAU,GAAG,EAAE;EACrE;;AAGH,SAAS,qBACP,OACA,MACA,OACA,UACM;CACN,MAAM,UAAU,4BAA4B,MAAM,SAAS;AAC3D,OAAM,KAAK;EAAE,MAAM;EAAiB;EAAO;EAAS,CAAC;AACrD,OAAM,KAAK;EAAE,MAAM;EAAkB;EAAO;EAAS,CAAC;AACtD,OAAM,KAAK;EAAE,MAAM;EAAe;EAAO;EAAS,CAAC;;AAGrD,gBAAuB,0BACrB,MACA,OAC+D;CAC/D,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,EAAE,SAAS,WAAW,MAAM,KAAK,uBAAuB,WAAW;CACzE,MAAM,UAAU,KAAK,2BAA2B,YAAY,SAAS,OAAO;CAE5E,MAAM,QAAQ,IAAI,YAA4C;CAC9D,IAAI,iBAAiC;CAErC,MAAM,eAAe,UAA0C;EAC7D,MAAM,UAAU,mCAAmC,OAAO,eAAe;AACzE,MAAI,YAAY,KACd,OAAM,KAAK,QAAQ;;CAIvB,MAAM,qBAAqB,QAAwB;EACjD,IAAI;EACJ,IAAI;AACJ,MAAI;AAEF,cADiB,KAAK,aAAa,2BAA2B,WAC3C,CAAC;AACpB,cAAW,KAAK,aAAa,mBAAmB,WAAW;UACrD;AAGR,SAAO,6BAA6B,KAAK;GAAE;GAAU;GAAU,CAAC;;AAGlE,KAAI,YAAY,UACd,MAAK,4BAA4B,YAAY,YAAY;CAG3D,MAAM,SAAS,MAAM;CACrB,IAAI,cAAc;CAClB,IAAI,eAAe;CACnB,IAAI,eAAe;CACnB,IAAI,kBAAkB;CACtB,IAAI,iBAAiB,MAAM;CAC3B,IAAI;CACJ,IAAI;CAEJ,MAAM,eAAe,YAAY;AAC/B,MAAI;GACF,MAAM,MAAM,KAAK,WAAW;GAC5B,IAAI,WAAW,MAAM;GACrB,IAAI,uBAAuB;AAC3B,OAAI,KAAK;IACP,MAAM,OAAO,MAAM,gBAAgB;KACjC;KACA;KACA,MAAM,MAAM;KACZ,cAAc,KAAK;KACpB,CAAC;AACF,2BAAuB,KAAK;AAC5B,QAAI,KAAK,aAAa,KAAK,YAAY;AACrC,uBAAkB;AAClB,2BAAsB,KAAK;AAC3B,0BAAqB,OAAO,KAAK,YAAY,MAAM,MAAM;AACzD;;AAEF,eAAW,KAAK;AAChB,QAAI,KAAK,aACP,MAAK,IAAI,MACP;KACE;KACA,WAAW,KAAK;KAChB,mBAAmB,KAAK;KACxB,gBAAgB,KAAK;KACrB,eAAe,KAAK;KACrB,EACD,8CACD;;AAIL,SAAM,oBAAoB,MAAM,YAAY,MAAM,SAAS;AAGzD,oBAAiB,MAAM,+BAA+B,KAAK,oBAAoB,YAAY,SAAU;GAGvG,MAAM,WAAW,MAAM,KAAK,0BAA0B,YAAY,MAAM,YAAY;GAEpF,MAAM,SAAS,4BAA4B,KAAK,WAAW,EAAE,OAAO,OAAO,OAAO,KAAK,WAAW,EAAE,OAAO,MAAM;GACjH,MAAM,aAAa,MAAM,kCAAkC,UAAU,UAAU,OAAO;AACtF,oBAAiB,WAAW;AAC5B,kBAAe,WAAW;AAE1B,OAAI,cAAc;IAChB,MAAM,kBAAkB,CACtB,WAAW,iBAAiB,OAAO,QAAQ,CAAC,KAAK,KAAK,EACtD,SAAS,MAAM,CAChB,CAAC,OAAO,QAAQ;IACjB,MAAM,cAAc,YAAY,EAAE,EAAE,OAAO,sBAAsB,CAAC,KAAK,SAAS;KAC9E,KAAK,IAAI;KACT,UAAU,IAAI;KACd,MAAM,IAAI;KACX,EAAE;AACH,gBAAY;KACV,MAAM;KACN,MAAM,gBAAgB,KAAK,OAAO;KAClC,OAAO;KACR,CAAC;;GAGJ,MAAM,iBAAiB;AACrB,QAAI,aACF;AAEF,mBAAe;AACf,kBAAc;AACT,qBAAiB,WAAW;AACjC,UAAM,OAAO;;AAEf,OAAI,QAAQ;AACV,QAAI,OAAO,SAAS;AAClB,eAAU;AACV;;AAEF,WAAO,iBAAiB,SAAS,UAAU,EAAE,MAAM,MAAM,CAAC;;GAG5D,MAAM,QAAQ,MAAM,mBAClB,MACA;IACE;IACA;IACA;IACA,UAAU,QAAQ;IAClB,SAAS,QAAQ;IACjB,iBAAiB,QAAQ;IAC1B,EACD,gBACA,EAAE,mBAAmB,sBAAsB,CAC5C;AACD,OAAI,MAAM,SAAS;AACjB,sBAAkB;AAClB,2BAAuB,MAAM;IAC7B,MAAM,OAAO,MAAM,eAAe,MAAM;AACxC,QAAI,MAAM;AACR,2BAAsB;AACtB,0BAAqB,OAAO,MAAM,MAAM,OAAO,qBAAqB;eAC3D,YAAY,WAAW;AAChC,2BACE;AACF,0BAAqB,OAAO,qBAAqB,MAAM,MAAM;;IAE/D,MAAM,cAAc,MAAM,UAAU;AACpC,QAAI,eAAe,OAAO,gBAAgB,SACxC,OAAM,KAAK;KACT,MAAM;KACN,OAAO,MAAM;KACb,SAAS,EAAE,aAAa;KACzB,CAAC;AAEJ;;GAGF,MAAM,YAAY,KAAK,aAAa,iBAAiB,YAAY,eAAe;GAChF,MAAM,eAAe,UAAU;GAC/B,MAAM,cAAc,MAAM,KAAK,2BAA2B,cAAc,UAAU,WAAW;GAC7F,IAAI,4BAA0C;GAC9C,IAAI;AACJ,OAAI,KAAK,uBAAuB;IAC9B,MAAM,WAAW,MAAM,KAAK,aAAa,YAAY,WAAW,CAAC,YAAY,KAAK;IAClF,MAAM,gBAAgB,UAAU,cAAc,OAAO,SAAS,eAAe,WACxE,SAAS,WAAuC,gBACjD,KAAA;AACJ,QAAI,uBAAuB,cAAc,EAAE;KACzC,MAAM,gBAAgB,MAAM,KAAK,sBAAsB,eAAe,WAAW;AACjF,iCAA4B,mCAAmC,aAAa,cAAc;AAC1F,oBAAe,eAAe;;;AAIlC,OAAI,YAAY,WAAW;AACzB,gBAAY;KACV,MAAM;KACN,WAAW,YAAY,aAAa,KAAK,KAAK;KAC9C,SAAS,wBAAwB,YAAY;KAC7C,OAAO,YAAY;KACpB,CAAC;AACF,QAAI,aAAa,MAAM,CACrB,MAAK,iCAAiC,YAAY,aAAa;;GAInE,MAAM,qBAAqB;AAC3B,mCAAgC,YAAY,mBAAmB;AAE/D,OAAI;IACF,MAAM,SAAS,MAAM,KAAK,aAAa,sBACrC,YACA,UAAU,gCAER,mBACE;KACE,cAAc,KAAK;KACnB,cAAc,KAAK;KACnB,cAAc,KAAK;KACnB,QAAQ,KAAK,WAAW;KACzB,EACD;KACE;KACA,aAAa;KACb,aAAa;KACb;KACA,OAAO,MAAM;KACb,UAAU,kBAAkB;MAC1B,MAAM,QAAQ,EAAE,GAAG,eAAe;AAClC,UAAI,MAAM,SAAS,WAAW,OAAO,MAAM,YAAY,SACrD,OAAM,UAAU,kBAAkB,MAAM,QAAQ;AAElD,kBAAY,MAAM;;KAErB,CACF,CACJ;AAED,QAAI,OAAO,kBACT,MAAK,iBAAiB,YAAY,OAAO,kBAAkB;AAE7D,QAAI,CAAC,OAAO,MAAM,OAAO,gBAAgB,CAAC,aACxC,aAAY;KAAE,MAAM;KAAS,SAAS,kBAAkB,OAAO,aAAa;KAAE,CAAC;aAEzE;AACR,sCAAkC,YAAY,mBAAmB;;WAE5D,KAAK;AACZ,OAAI,CAAC,aAEH,aAAY;IAAE,MAAM;IAAS,SAAS,kBAD3B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACA;IAAE,CAAC;YAExD;AACR,OAAI,CAAC,eAAe,YAAY,UAC9B,KAAI;IACF,MAAM,gBAAgB,MAAM,KAAK,oBAAoB,YAAY,aAAa;AAC9E,QAAI,cACF,OAAM,KAAK,cAAc;YAEpB,QAAQ;AACf,SAAK,IAAI,KAAK;KAAE,KAAK;KAAQ;KAAY,EAAE,+CAA+C;;AAG9F,SAAM,OAAO;;KAEb;AAEJ,KAAI;AACF,aAAW,MAAM,SAAS,MACxB,OAAM;AAER,QAAM;AAEN,MAAI,YAAY,aAAa,gBAC3B,KAAI;GACF,MAAM,UAAU;IACd,MAAM;IACN,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM;KAAgB,CAAC;IAC1D,WAAW,KAAK,KAAK;IACtB;AACD,SAAM,KAAK,aAAa,wBAAwB,YAAY,QAAQ;AACpE,OAAI,qBAAqB,MAAM,EAAE;IAC/B,MAAM,eAAe;KACnB,MAAM;KACN,SAAS,eAAe,sBAAsB,OAAO,GACjD,CAAC,qBAAqB,OAAO,GAC7B,CAAC;MAAE,MAAM;MAAiB,MAAM,oBAAoB,MAAM;MAAE,CAAC;KACjE,WAAW,KAAK,KAAK;KACrB,GAAI,wBAAwB,OAAO,KAAK,qBAAqB,CAAC,SAAS,IACnE,EAAE,UAAU,sBAAsB,GAClC,EAAE;KACP;AACD,UAAM,KAAK,aAAa,wBAAwB,YAAY,aAAa;;AAE3E,QAAK,0BAA0B,WAAW;WACnC,KAAK;AACZ,QAAK,IAAI,KAAK;IAAE;IAAK;IAAY,EAAE,kDAAkD;;AAIzF,OAAK,oCAAoC,YAAY,EAAE,4BAA4B,iBAAiB,CAAC;WAC7F;AACR,MAAI,YAAY,UACd,MAAK,8BAA8B,WAAW;AAEhD,OAAK,yBAAyB"}
|
|
1
|
+
{"version":3,"file":"process-direct-streaming.js","names":[],"sources":["../../../../src/agent/service/process-direct-streaming.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport type { InboundAttachmentInput, MediaRef } from '../../channels/attachments/inbound-persist.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport {\n isVoiceLikeAttachment,\n mergeVoiceTranscriptsIntoUserText,\n mergeSttConfigFromAppConfig,\n} from '../../channels/attachments/voice-stt-webchat.js';\nimport {\n resolveEffectiveReasoningLevel,\n initSessionTurn,\n type SessionConfigStore,\n type SessionStore,\n} from '../../session/index.js';\nimport type { SessionContext } from '../session/index.js';\nimport { applyReasoningVisibilityToSseEvent } from '../streaming/reasoning-visibility-sse.js';\nimport type { ReasoningLevel } from '../transcript/thinking-types.js';\nimport { formatAgentRunErrorForClient } from '../client-error-format.js';\nimport { abortEmbeddedRun } from '../embedded/runs.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { ModelManager } from '../models/index.js';\nimport { injectSourceContextIntoUserMessage } from '../source-context/injector.js';\nimport { isSessionSourceBinding, type AgentSourceContextResolver } from '../source-context/types.js';\nimport type { ReviewOutput } from '../../review/review-types.js';\n\nimport { AsyncQueue } from './async-queue.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\n\nexport type DirectStreamInboundAttachment = InboundAttachmentInput;\n\nexport type ProcessDirectStreamLog = {\n info: (obj: Record<string, unknown>, msg: string) => void;\n warn: (obj: Record<string, unknown>, msg: string) => void;\n debug?: (obj: Record<string, unknown>, msg: string) => void;\n};\n\nexport interface ProcessDirectStreamingDeps {\n log: ProcessDirectStreamLog;\n resolveSessionEndpoint: (sessionKey: string) => Promise<{ channel: string; chatId: string }>;\n initDirectStreamingSession: (\n sessionKey: string,\n channel: string,\n chatId: string,\n ) => SessionContext;\n registerWebchatSsePublisher: (\n sessionKey: string,\n publisher: (event: { type: string; [key: string]: unknown }) => void,\n ) => void;\n unregisterWebchatSsePublisher: (sessionKey: string) => void;\n agentManager: AgentInstanceGateway;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n getConfig: () => Config | undefined;\n sessionConfigStore: SessionConfigStore;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: DirectStreamInboundAttachment[],\n ) => Promise<MediaRef[] | undefined>;\n buildTranscriptUserMessage: (\n content: string,\n prepared: MediaRef[] | undefined,\n sessionKey: string,\n ) => Promise<TranscriptUserMessage>;\n recordPersistentGoalStreamOutcome?: (\n sessionKey: string,\n outcome: { skipPersistentGoalPostTurn: boolean },\n ) => void;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n enqueueProvisionalSessionTitle?: (sessionKey: string, userText: string) => void;\n /** Disk-only transcript sync (slash receipt already streamed as tokens). */\n reloadWebchatTranscript?: (sessionKey: string) => void;\n maybeEmitWebchatTts: (\n sessionKey: string,\n hadInboundVoice: boolean,\n ) => Promise<{ type: 'tts_audio'; uri: string; mimeType: string; name: string } | null>;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n sourceContextResolver?: AgentSourceContextResolver;\n}\n\nexport interface ProcessDirectStreamingInput {\n content: string;\n sessionKey?: string;\n attachments?: DirectStreamInboundAttachment[];\n thinking?: string;\n signal?: AbortSignal;\n runId?: string;\n}\n\nexport type ProcessDirectStreamingSseEvent = { type: string; [key: string]: unknown };\n\nfunction isReviewOutput(value: unknown): value is ReviewOutput {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n && (value as { type?: unknown }).type === 'review';\n}\n\nconst REVIEW_TRACE_PREVIEW_MAX = 2_000;\n\ntype ReviewTraceContextEntry = {\n id: string;\n text: string;\n data: Record<string, unknown>;\n createdAt: string;\n};\n\nfunction boundedPreview(text: string): string {\n return text.length > REVIEW_TRACE_PREVIEW_MAX\n ? `${text.slice(0, REVIEW_TRACE_PREVIEW_MAX)}\\n[trace preview truncated]`\n : text;\n}\n\nfunction extractTraceResultText(result: unknown): string {\n if (typeof result === 'string') return result;\n if (!result || typeof result !== 'object' || Array.isArray(result)) {\n return result == null ? '' : String(result);\n }\n const rec = result as Record<string, unknown>;\n if (typeof rec.text === 'string') return rec.text;\n if (Array.isArray(rec.content)) {\n return rec.content\n .map((block) => {\n if (!block || typeof block !== 'object') return '';\n const b = block as Record<string, unknown>;\n return b.type === 'text' && typeof b.text === 'string' ? b.text : '';\n })\n .join('');\n }\n return '';\n}\n\nfunction reviewTraceContextEntryFromCommandEvent(\n event: { type: string; [key: string]: unknown },\n runId: string | undefined,\n): ReviewTraceContextEntry | null {\n if (event.type !== 'tool_execution_start' && event.type !== 'tool_execution_end') {\n return null;\n }\n const toolName = typeof event.toolName === 'string' ? event.toolName : '';\n if (!toolName.startsWith('review.')) {\n return null;\n }\n const toolCallId = typeof event.toolCallId === 'string' && event.toolCallId\n ? event.toolCallId\n : `review_trace_${Date.now()}`;\n const ended = event.type === 'tool_execution_end';\n const isError = ended && event.isError === true;\n const resultPreview = ended ? boundedPreview(extractTraceResultText(event.result).trim()) : undefined;\n const details = ended && event.result && typeof event.result === 'object' && !Array.isArray(event.result)\n ? (event.result as Record<string, unknown>).details\n : undefined;\n\n return {\n id: `review-trace:${toolCallId}:${ended ? 'end' : 'start'}`,\n text: ended\n ? `Review trace: ${toolName} ${isError ? 'failed' : 'completed'}`\n : `Review trace: ${toolName} started`,\n createdAt: new Date().toISOString(),\n data: {\n type: 'review_trace',\n scope: 'review',\n event: ended ? 'tool_end' : 'tool_start',\n llmInput: false,\n ...(runId ? { runId } : {}),\n toolCallId,\n toolName,\n status: ended ? (isError ? 'error' : 'done') : 'running',\n ...(event.args !== undefined ? { input: event.args } : {}),\n ...(resultPreview ? { resultPreview } : {}),\n ...(details !== undefined ? { details } : {}),\n ...(isError ? { isError: true } : {}),\n },\n };\n}\n\nfunction makeAssistantReceiptMessage(text: string, metadata?: Record<string, unknown>): AgentMessage {\n const review = isReviewOutput(metadata?.review) ? metadata.review : undefined;\n return {\n role: 'assistant',\n content: review ? [review] : [{ type: 'text', text }],\n timestamp: Date.now(),\n ...(metadata && Object.keys(metadata).length > 0 ? { metadata } : {}),\n } as AgentMessage;\n}\n\nfunction pushAssistantReceipt(\n queue: AsyncQueue<ProcessDirectStreamingSseEvent>,\n text: string,\n runId?: string,\n metadata?: Record<string, unknown>,\n): void {\n const message = makeAssistantReceiptMessage(text, metadata);\n queue.push({ type: 'message_start', runId, message });\n queue.push({ type: 'message_update', runId, message });\n queue.push({ type: 'message_end', runId, message });\n}\n\nexport async function* runProcessDirectStreaming(\n deps: ProcessDirectStreamingDeps,\n input: ProcessDirectStreamingInput,\n): AsyncGenerator<ProcessDirectStreamingSseEvent, void, unknown> {\n const sessionKey = input.sessionKey ?? 'agent:main:main';\n const { channel, chatId } = await deps.resolveSessionEndpoint(sessionKey);\n const context = deps.initDirectStreamingSession(sessionKey, channel, chatId);\n\n const queue = new AsyncQueue<ProcessDirectStreamingSseEvent>();\n let reasoningLevel: ReasoningLevel = 'stream';\n\n const pushVisible = (event: ProcessDirectStreamingSseEvent) => {\n const visible = applyReasoningVisibilityToSseEvent(event, reasoningLevel);\n if (visible !== null) {\n queue.push(visible);\n }\n };\n\n const formatStreamError = (raw: string): string => {\n let provider: string | undefined;\n let modelRef: string | undefined;\n try {\n const resolved = deps.modelManager.getResolvedModelForSession(sessionKey);\n provider = resolved.provider;\n modelRef = deps.modelManager.getModelForSession(sessionKey);\n } catch {\n /* ignore — format without provider context */\n }\n return formatAgentRunErrorForClient(raw, { provider, modelRef });\n };\n\n if (channel === 'webchat') {\n deps.registerWebchatSsePublisher(sessionKey, pushVisible);\n }\n\n const signal = input.signal;\n let userAborted = false;\n let abortHandled = false;\n let inboundVoice = false;\n let ranSlashCommand = false;\n let mergedUserText = input.content;\n let webchatSlashReceipt: string | undefined;\n let slashCommandMetadata: Record<string, unknown> | undefined;\n const slashTraceRows: ReviewTraceContextEntry[] = [];\n\n const taskPromise = (async () => {\n try {\n const cfg = deps.getConfig();\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n if (cfg) {\n const turn = await initSessionTurn({\n cfg,\n sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n ranSlashCommand = true;\n webchatSlashReceipt = turn.ackMessage;\n pushAssistantReceipt(queue, turn.ackMessage, input.runId);\n return;\n }\n turnBody = turn.bodyStripped;\n if (turn.isNewSession) {\n deps.log.debug(\n {\n sessionKey,\n sessionId: turn.sessionId,\n previousSessionId: turn.previousSessionId,\n resetTriggered: turn.resetTriggered,\n staleRollover: turn.staleRollover,\n },\n 'Session reset boundary at direct turn start',\n );\n }\n }\n\n await hydratePerTurnState(deps, sessionKey, input.thinking);\n {\n const defReason = 'stream' as ReasoningLevel;\n reasoningLevel = await resolveEffectiveReasoningLevel(deps.sessionConfigStore, sessionKey, defReason);\n }\n\n const prepared = await deps.prepareInboundAttachments(sessionKey, input.attachments);\n\n const sttCfg = mergeSttConfigFromAppConfig(deps.getConfig()?.tools?.media?.audio, deps.getConfig()?.tools?.media);\n const voiceMerge = await mergeVoiceTranscriptsIntoUserText(prepared, turnBody, sttCfg);\n mergedUserText = voiceMerge.text;\n inboundVoice = voiceMerge.inboundVoice;\n\n if (inboundVoice) {\n const transcriptParts = [\n voiceMerge.voiceTranscripts.filter(Boolean).join('\\n'),\n turnBody.trim(),\n ].filter(Boolean);\n const voiceMedia = (prepared ?? []).filter(isVoiceLikeAttachment).map((att) => ({\n uri: att.uri,\n mimeType: att.mimeType,\n name: att.name,\n }));\n pushVisible({\n type: 'user_transcript',\n text: transcriptParts.join('\\n\\n'),\n media: voiceMedia,\n });\n }\n\n const armAbort = () => {\n if (abortHandled) {\n return;\n }\n abortHandled = true;\n userAborted = true;\n void abortEmbeddedRun(sessionKey);\n queue.close();\n };\n if (signal) {\n if (signal.aborted) {\n armAbort();\n return;\n }\n signal.addEventListener('abort', armAbort, { once: true });\n }\n\n const slash = await tryRunSlashCommand(\n deps,\n {\n sessionKey,\n channel,\n chatId,\n senderId: context.senderId,\n isGroup: context.isGroup,\n inboundMetadata: context.metadata,\n },\n mergedUserText,\n {\n skipResetCommands: resetTriggeredAtInit,\n emitEvent: (event) => {\n pushVisible({ ...event, runId: input.runId });\n const traceRow = reviewTraceContextEntryFromCommandEvent(event, input.runId);\n if (traceRow) {\n slashTraceRows.push(traceRow);\n }\n },\n },\n );\n if (slash.matched) {\n ranSlashCommand = true;\n slashCommandMetadata = slash.metadata;\n const text = slash.aggregatedText.trim();\n if (text) {\n webchatSlashReceipt = text;\n pushAssistantReceipt(queue, text, input.runId, slashCommandMetadata);\n } else if (channel === 'webchat') {\n webchatSlashReceipt =\n 'Command finished with no assistant text. If you used `/goal`, a follow-up turn may still be scheduled automatically.';\n pushAssistantReceipt(queue, webchatSlashReceipt, input.runId);\n }\n const workflowRun = slash.metadata?.workflowRun;\n if (workflowRun && typeof workflowRun === 'object') {\n queue.push({\n type: 'workflow_run_started',\n runId: input.runId,\n payload: { workflowRun },\n });\n }\n return;\n }\n\n const skillTurn = deps.agentManager.prepareSkillTurn(sessionKey, mergedUserText);\n const textForAgent = skillTurn.text;\n const userMessage = await deps.buildTranscriptUserMessage(textForAgent, prepared, sessionKey);\n let sourceEnrichedUserMessage: AgentMessage = userMessage;\n let sourceImages: ImageContent[] | undefined;\n if (deps.sourceContextResolver) {\n const metadata = await deps.sessionStore.getMetadata(sessionKey).catch(() => null);\n const sourceBinding = metadata?.customData && typeof metadata.customData === 'object'\n ? (metadata.customData as Record<string, unknown>).sourceBinding\n : undefined;\n if (isSessionSourceBinding(sourceBinding)) {\n const sourceContext = await deps.sourceContextResolver(sourceBinding, sessionKey);\n sourceEnrichedUserMessage = injectSourceContextIntoUserMessage(userMessage, sourceContext);\n sourceImages = sourceContext?.images;\n }\n }\n\n if (channel === 'webchat') {\n pushVisible({\n type: 'user_message',\n timestamp: userMessage.timestamp ?? Date.now(),\n content: readAgentMessageContent(userMessage),\n media: userMessage.media,\n });\n if (textForAgent.trim()) {\n deps.enqueueProvisionalSessionTitle?.(sessionKey, textForAgent);\n }\n }\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n\n try {\n const result = await deps.agentManager.withSkillCapabilities(\n sessionKey,\n skillTurn.activatedCapabilityNames,\n () =>\n runDirectAgentTurn(\n {\n sessionStore: deps.sessionStore,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n config: deps.getConfig(),\n },\n {\n sessionKey,\n userMessage: sourceEnrichedUserMessage,\n abortSignal: signal,\n sourceImages,\n runId: input.runId,\n onEvent: (embeddedEvent) => {\n const event = { ...embeddedEvent };\n if (event.type === 'error' && typeof event.content === 'string') {\n event.content = formatStreamError(event.content);\n }\n pushVisible(event);\n },\n },\n ),\n );\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(sessionKey, result.lastAssistantText);\n }\n if (!result.ok && result.errorMessage && !abortHandled) {\n pushVisible({ type: 'error', content: formatStreamError(result.errorMessage) });\n }\n } finally {\n clearPendingTranscriptUserMessage(sessionKey, pendingUserMessage);\n }\n } catch (err) {\n if (!abortHandled) {\n const em = err instanceof Error ? err.message : String(err);\n pushVisible({ type: 'error', content: formatStreamError(em) });\n }\n } finally {\n if (!userAborted && channel === 'webchat') {\n try {\n const ttsAudioEvent = await deps.maybeEmitWebchatTts(sessionKey, inboundVoice);\n if (ttsAudioEvent) {\n queue.push(ttsAudioEvent);\n }\n } catch (ttsErr) {\n deps.log.warn({ err: ttsErr, sessionKey }, 'Failed to emit TTS audio before stream close');\n }\n }\n queue.close();\n }\n })();\n\n try {\n for await (const event of queue) {\n yield event;\n }\n await taskPromise;\n\n if (channel === 'webchat' && ranSlashCommand) {\n try {\n const userMsg = {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: mergedUserText }],\n timestamp: Date.now(),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, userMsg);\n for (const traceRow of slashTraceRows) {\n await deps.sessionStore.appendTranscriptContextEntry(sessionKey, traceRow);\n }\n if (webchatSlashReceipt?.trim()) {\n const assistantMsg = {\n role: 'assistant' as const,\n content: isReviewOutput(slashCommandMetadata?.review)\n ? [slashCommandMetadata.review]\n : [{ type: 'text' as const, text: webchatSlashReceipt.trim() }],\n timestamp: Date.now(),\n ...(slashCommandMetadata && Object.keys(slashCommandMetadata).length > 0\n ? { metadata: slashCommandMetadata }\n : {}),\n } as AgentMessage;\n await deps.sessionStore.appendTranscriptMessage(sessionKey, assistantMsg);\n }\n deps.reloadWebchatTranscript?.(sessionKey);\n } catch (err) {\n deps.log.warn({ err, sessionKey }, 'Failed to persist webchat slash command receipt');\n }\n }\n\n deps.recordPersistentGoalStreamOutcome?.(sessionKey, { skipPersistentGoalPostTurn: ranSlashCommand });\n } finally {\n if (channel === 'webchat') {\n deps.unregisterWebchatSsePublisher(sessionKey);\n }\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":"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"}
|