dominds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +157 -0
- package/README.md +250 -0
- package/README.zh.md +161 -0
- package/dist/access-control.js +253 -0
- package/dist/cli/create.js +263 -0
- package/dist/cli/read.js +84 -0
- package/dist/cli/tui.js +199 -0
- package/dist/cli/webui.js +169 -0
- package/dist/cli.js +227 -0
- package/dist/dialog-factory.js +53 -0
- package/dist/dialog-global-registry.js +68 -0
- package/dist/dialog-instance-registry.js +78 -0
- package/dist/dialog-run-state.js +198 -0
- package/dist/dialog.js +1024 -0
- package/dist/evt-registry.js +103 -0
- package/dist/index.js +8 -0
- package/dist/llm/client.js +69 -0
- package/dist/llm/defaults.yaml +386 -0
- package/dist/llm/driver.js +3214 -0
- package/dist/llm/gen/anthropic.js +611 -0
- package/dist/llm/gen/codex.js +375 -0
- package/dist/llm/gen/mock.js +326 -0
- package/dist/llm/gen/openai.js +470 -0
- package/dist/llm/gen/registry.js +26 -0
- package/dist/llm/gen.js +2 -0
- package/dist/llm/tools-projection.js +37 -0
- package/dist/log.js +228 -0
- package/dist/mcp/config.js +230 -0
- package/dist/mcp/sdk-client.js +129 -0
- package/dist/mcp/server-runtime.js +57 -0
- package/dist/mcp/stdio-client.js +280 -0
- package/dist/mcp/supervisor.js +979 -0
- package/dist/mcp/tool-names.js +109 -0
- package/dist/minds/builtin/cmdr/persona.md +3 -0
- package/dist/minds/builtin/dijiang/knowledge.md +287 -0
- package/dist/minds/builtin/dijiang/persona.md +7 -0
- package/dist/minds/builtin/fuxi/persona.en.md +59 -0
- package/dist/minds/builtin/fuxi/persona.zh.md +49 -0
- package/dist/minds/builtin/pangu/persona.en.md +78 -0
- package/dist/minds/builtin/pangu/persona.zh.md +71 -0
- package/dist/minds/load.js +617 -0
- package/dist/minds/minds-i18n.js +131 -0
- package/dist/minds/system-prompt.js +281 -0
- package/dist/persistence.js +3128 -0
- package/dist/problems.js +109 -0
- package/dist/server/api-routes.js +1031 -0
- package/dist/server/auth.js +180 -0
- package/dist/server/mime-types.js +32 -0
- package/dist/server/prompts-routes.js +543 -0
- package/dist/server/server-core.js +235 -0
- package/dist/server/setup-routes.js +697 -0
- package/dist/server/static-server.js +132 -0
- package/dist/server/websocket-handler.js +1011 -0
- package/dist/server.js +164 -0
- package/dist/shared/async-fifo-mutex.js +36 -0
- package/dist/shared/diligence.js +20 -0
- package/dist/shared/dotenv.js +144 -0
- package/dist/shared/evt.js +195 -0
- package/dist/shared/i18n/driver-messages.js +267 -0
- package/dist/shared/i18n/text.js +9 -0
- package/dist/shared/i18n/tool-result-messages.js +51 -0
- package/dist/shared/rtws-cli.js +73 -0
- package/dist/shared/runtime-language.js +47 -0
- package/dist/shared/team-mgmt-manual.js +116 -0
- package/dist/shared/types/context-health.js +2 -0
- package/dist/shared/types/dialog.js +11 -0
- package/dist/shared/types/i18n.js +2 -0
- package/dist/shared/types/index.js +26 -0
- package/dist/shared/types/language.js +40 -0
- package/dist/shared/types/problems.js +2 -0
- package/dist/shared/types/prompts.js +2 -0
- package/dist/shared/types/q4h.js +7 -0
- package/dist/shared/types/run-state.js +8 -0
- package/dist/shared/types/setup.js +2 -0
- package/dist/shared/types/storage.js +10 -0
- package/dist/shared/types/tellask.js +8 -0
- package/dist/shared/types/tools-registry.js +2 -0
- package/dist/shared/types/wire.js +12 -0
- package/dist/shared/utils/fmt.js +9 -0
- package/dist/shared/utils/html.js +20 -0
- package/dist/shared/utils/id.js +18 -0
- package/dist/shared/utils/inter-dialog-format.js +101 -0
- package/dist/shared/utils/time.js +13 -0
- package/dist/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/static/assets/_baseUniq-Crfl3d5Y.js +661 -0
- package/dist/static/assets/_baseUniq-Crfl3d5Y.js.map +1 -0
- package/dist/static/assets/arc-CbA_x9GD.js +132 -0
- package/dist/static/assets/arc-CbA_x9GD.js.map +1 -0
- package/dist/static/assets/architectureDiagram-VXUJARFQ-lcFS8ZQJ.js +8685 -0
- package/dist/static/assets/architectureDiagram-VXUJARFQ-lcFS8ZQJ.js.map +1 -0
- package/dist/static/assets/blockDiagram-VD42YOAC-B3Q36qRc.js +3608 -0
- package/dist/static/assets/blockDiagram-VD42YOAC-B3Q36qRc.js.map +1 -0
- package/dist/static/assets/c4Diagram-YG6GDRKO-Mt-aq3VH.js +2482 -0
- package/dist/static/assets/c4Diagram-YG6GDRKO-Mt-aq3VH.js.map +1 -0
- package/dist/static/assets/channel-BVr1Yke-.js +8 -0
- package/dist/static/assets/channel-BVr1Yke-.js.map +1 -0
- package/dist/static/assets/chunk-4BX2VUAB-qCIn5Iic.js +17 -0
- package/dist/static/assets/chunk-4BX2VUAB-qCIn5Iic.js.map +1 -0
- package/dist/static/assets/chunk-55IACEB6-q172NeCV.js +14 -0
- package/dist/static/assets/chunk-55IACEB6-q172NeCV.js.map +1 -0
- package/dist/static/assets/chunk-B4BG7PRW-CMJmtYzq.js +1827 -0
- package/dist/static/assets/chunk-B4BG7PRW-CMJmtYzq.js.map +1 -0
- package/dist/static/assets/chunk-DI55MBZ5-DiuwwZPL.js +1916 -0
- package/dist/static/assets/chunk-DI55MBZ5-DiuwwZPL.js.map +1 -0
- package/dist/static/assets/chunk-FMBD7UC4-06sqZTTn.js +20 -0
- package/dist/static/assets/chunk-FMBD7UC4-06sqZTTn.js.map +1 -0
- package/dist/static/assets/chunk-QN33PNHL-CnpBNkpP.js +25 -0
- package/dist/static/assets/chunk-QN33PNHL-CnpBNkpP.js.map +1 -0
- package/dist/static/assets/chunk-QZHKN3VN-CNgjMR-e.js +18 -0
- package/dist/static/assets/chunk-QZHKN3VN-CNgjMR-e.js.map +1 -0
- package/dist/static/assets/chunk-TZMSLE5B-BxtzW6--.js +109 -0
- package/dist/static/assets/chunk-TZMSLE5B-BxtzW6--.js.map +1 -0
- package/dist/static/assets/classDiagram-2ON5EDUG-29huvmn-.js +23 -0
- package/dist/static/assets/classDiagram-2ON5EDUG-29huvmn-.js.map +1 -0
- package/dist/static/assets/classDiagram-v2-WZHVMYZB-29huvmn-.js +23 -0
- package/dist/static/assets/classDiagram-v2-WZHVMYZB-29huvmn-.js.map +1 -0
- package/dist/static/assets/clone-D2OgLSSn.js +9 -0
- package/dist/static/assets/clone-D2OgLSSn.js.map +1 -0
- package/dist/static/assets/cose-bilkent-S5V4N54A-BNegDCxl.js +4943 -0
- package/dist/static/assets/cose-bilkent-S5V4N54A-BNegDCxl.js.map +1 -0
- package/dist/static/assets/cytoscape.esm-Bm8DJGmZ.js +30240 -0
- package/dist/static/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -0
- package/dist/static/assets/dagre-6UL2VRFP-f1XrTRSn.js +695 -0
- package/dist/static/assets/dagre-6UL2VRFP-f1XrTRSn.js.map +1 -0
- package/dist/static/assets/defaultLocale-DVr69WTU.js +207 -0
- package/dist/static/assets/defaultLocale-DVr69WTU.js.map +1 -0
- package/dist/static/assets/diagram-PSM6KHXK-8w1WbeDi.js +849 -0
- package/dist/static/assets/diagram-PSM6KHXK-8w1WbeDi.js.map +1 -0
- package/dist/static/assets/diagram-QEK2KX5R-CF4wtMmR.js +303 -0
- package/dist/static/assets/diagram-QEK2KX5R-CF4wtMmR.js.map +1 -0
- package/dist/static/assets/diagram-S2PKOQOG-8p3Avgn2.js +213 -0
- package/dist/static/assets/diagram-S2PKOQOG-8p3Avgn2.js.map +1 -0
- package/dist/static/assets/erDiagram-Q2GNP2WA-BMKLxlM9.js +1159 -0
- package/dist/static/assets/erDiagram-Q2GNP2WA-BMKLxlM9.js.map +1 -0
- package/dist/static/assets/favicon-Cmg5RbCj.svg +8 -0
- package/dist/static/assets/flowDiagram-NV44I4VS-CgEuPNK2.js +2332 -0
- package/dist/static/assets/flowDiagram-NV44I4VS-CgEuPNK2.js.map +1 -0
- package/dist/static/assets/ganttDiagram-JELNMOA3-bJkDCf-9.js +3681 -0
- package/dist/static/assets/ganttDiagram-JELNMOA3-bJkDCf-9.js.map +1 -0
- package/dist/static/assets/gitGraphDiagram-NY62KEGX-4QE9kesp.js +1206 -0
- package/dist/static/assets/gitGraphDiagram-NY62KEGX-4QE9kesp.js.map +1 -0
- package/dist/static/assets/graph-CS0Pmm7c.js +597 -0
- package/dist/static/assets/graph-CS0Pmm7c.js.map +1 -0
- package/dist/static/assets/index-BS6HnGzC.js +112303 -0
- package/dist/static/assets/index-BS6HnGzC.js.map +1 -0
- package/dist/static/assets/index-DaIsSzC_.css +483 -0
- package/dist/static/assets/infoDiagram-WHAUD3N6-ypBcKfUs.js +34 -0
- package/dist/static/assets/infoDiagram-WHAUD3N6-ypBcKfUs.js.map +1 -0
- package/dist/static/assets/init-ZxktEp_H.js +17 -0
- package/dist/static/assets/init-ZxktEp_H.js.map +1 -0
- package/dist/static/assets/journeyDiagram-XKPGCS4Q-QnrxDowJ.js +1255 -0
- package/dist/static/assets/journeyDiagram-XKPGCS4Q-QnrxDowJ.js.map +1 -0
- package/dist/static/assets/kanban-definition-3W4ZIXB7-CfvEc4z5.js +1048 -0
- package/dist/static/assets/kanban-definition-3W4ZIXB7-CfvEc4z5.js.map +1 -0
- package/dist/static/assets/layout-8TGxpm23.js +2218 -0
- package/dist/static/assets/layout-8TGxpm23.js.map +1 -0
- package/dist/static/assets/linear-BATBPQQv.js +341 -0
- package/dist/static/assets/linear-BATBPQQv.js.map +1 -0
- package/dist/static/assets/min-B3oVH3AC.js +42 -0
- package/dist/static/assets/min-B3oVH3AC.js.map +1 -0
- package/dist/static/assets/mindmap-definition-VGOIOE7T-L7VLwwF8.js +1127 -0
- package/dist/static/assets/mindmap-definition-VGOIOE7T-L7VLwwF8.js.map +1 -0
- package/dist/static/assets/ordinal-CxptdPJm.js +77 -0
- package/dist/static/assets/ordinal-CxptdPJm.js.map +1 -0
- package/dist/static/assets/pieDiagram-ADFJNKIX-CFW3zIhM.js +241 -0
- package/dist/static/assets/pieDiagram-ADFJNKIX-CFW3zIhM.js.map +1 -0
- package/dist/static/assets/quadrantDiagram-AYHSOK5B-B7ssen3E.js +1338 -0
- package/dist/static/assets/quadrantDiagram-AYHSOK5B-B7ssen3E.js.map +1 -0
- package/dist/static/assets/requirementDiagram-UZGBJVZJ-D0v5BArv.js +1162 -0
- package/dist/static/assets/requirementDiagram-UZGBJVZJ-D0v5BArv.js.map +1 -0
- package/dist/static/assets/sankeyDiagram-TZEHDZUN-B7slncJe.js +1195 -0
- package/dist/static/assets/sankeyDiagram-TZEHDZUN-B7slncJe.js.map +1 -0
- package/dist/static/assets/sequenceDiagram-WL72ISMW-oXU2lRh_.js +3875 -0
- package/dist/static/assets/sequenceDiagram-WL72ISMW-oXU2lRh_.js.map +1 -0
- package/dist/static/assets/stateDiagram-FKZM4ZOC-CFYsEd0x.js +452 -0
- package/dist/static/assets/stateDiagram-FKZM4ZOC-CFYsEd0x.js.map +1 -0
- package/dist/static/assets/stateDiagram-v2-4FDKWEC3-C0UWaNA7.js +22 -0
- package/dist/static/assets/stateDiagram-v2-4FDKWEC3-C0UWaNA7.js.map +1 -0
- package/dist/static/assets/timeline-definition-IT6M3QCI-C3KODUrh.js +1223 -0
- package/dist/static/assets/timeline-definition-IT6M3QCI-C3KODUrh.js.map +1 -0
- package/dist/static/assets/treemap-KMMF4GRG-DAGDLhj2.js +18753 -0
- package/dist/static/assets/treemap-KMMF4GRG-DAGDLhj2.js.map +1 -0
- package/dist/static/assets/xychartDiagram-PRI3JC2R-C0J9iwTO.js +1888 -0
- package/dist/static/assets/xychartDiagram-PRI3JC2R-C0J9iwTO.js.map +1 -0
- package/dist/static/index.html +71 -0
- package/dist/static/testing/dom-observation-utils.js +425 -0
- package/dist/static/testing/e2e-test-helper.js +3119 -0
- package/dist/team.js +1160 -0
- package/dist/tellask.js +431 -0
- package/dist/tool.js +150 -0
- package/dist/tools/apply-patch.js +542 -0
- package/dist/tools/builtins.js +196 -0
- package/dist/tools/context-health.js +177 -0
- package/dist/tools/ctrl.js +478 -0
- package/dist/tools/diag.js +583 -0
- package/dist/tools/env.js +184 -0
- package/dist/tools/fs.js +818 -0
- package/dist/tools/mcp.js +138 -0
- package/dist/tools/mem.js +349 -0
- package/dist/tools/os.js +751 -0
- package/dist/tools/prompts/team_mgmt.en.md +70 -0
- package/dist/tools/prompts/team_mgmt.zh.md +70 -0
- package/dist/tools/prompts/ws_mod.en.md +86 -0
- package/dist/tools/prompts/ws_mod.zh.md +87 -0
- package/dist/tools/registry-snapshot.js +31 -0
- package/dist/tools/registry.js +121 -0
- package/dist/tools/ripgrep.js +678 -0
- package/dist/tools/team-mgmt.js +3300 -0
- package/dist/tools/txt.js +3178 -0
- package/dist/utils/id.js +72 -0
- package/dist/utils/task-doc.js +236 -0
- package/dist/utils/task-package.js +522 -0
- package/dist/utils/taskdoc-search.js +280 -0
- package/dist/utils/taskdoc.js +400 -0
- package/package.json +69 -0
|
@@ -0,0 +1,3214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Module: llm/driver
|
|
4
|
+
*
|
|
5
|
+
* Drives dialog streaming end-to-end:
|
|
6
|
+
* - Loads minds/tools, selects generator, streams outputs
|
|
7
|
+
* - Parses tellask blocks (teammate tellasks), handles human prompts
|
|
8
|
+
* - Supports autonomous teammate tellasks: when an agent mentions a teammate (e.g., @teammate), a subdialog is created and driven; the parent logs the initiating assistant bubble and system creation/result, while subdialog conversation stays in the subdialog
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.createSayingEventsReceiver = createSayingEventsReceiver;
|
|
45
|
+
exports.emitThinkingEvents = emitThinkingEvents;
|
|
46
|
+
exports.emitSayingEvents = emitSayingEvents;
|
|
47
|
+
exports.driveDialogStream = driveDialogStream;
|
|
48
|
+
exports.runBackendDriver = runBackendDriver;
|
|
49
|
+
exports.checkAndReviveSuspendedDialogs = checkAndReviveSuspendedDialogs;
|
|
50
|
+
exports.restoreDialogHierarchy = restoreDialogHierarchy;
|
|
51
|
+
exports.parseTeammateTellask = parseTeammateTellask;
|
|
52
|
+
exports.continueDialogWithHumanResponse = continueDialogWithHumanResponse;
|
|
53
|
+
exports.continueRootDialog = continueRootDialog;
|
|
54
|
+
exports.createSubdialogForSupdialog = createSubdialogForSupdialog;
|
|
55
|
+
exports.supplyResponseToSupdialog = supplyResponseToSupdialog;
|
|
56
|
+
exports.areAllSubdialogsSatisfied = areAllSubdialogsSatisfied;
|
|
57
|
+
exports.incorporateSubdialogResponses = incorporateSubdialogResponses;
|
|
58
|
+
const fs = __importStar(require("fs"));
|
|
59
|
+
const path = __importStar(require("path"));
|
|
60
|
+
const dialog_1 = require("../dialog");
|
|
61
|
+
const util_1 = require("util");
|
|
62
|
+
const dialog_global_registry_1 = require("../dialog-global-registry");
|
|
63
|
+
const dialog_instance_registry_1 = require("../dialog-instance-registry");
|
|
64
|
+
const dialog_run_state_1 = require("../dialog-run-state");
|
|
65
|
+
const evt_registry_1 = require("../evt-registry");
|
|
66
|
+
const log_1 = require("../log");
|
|
67
|
+
const load_1 = require("../minds/load");
|
|
68
|
+
const persistence_1 = require("../persistence");
|
|
69
|
+
const problems_1 = require("../problems");
|
|
70
|
+
const async_fifo_mutex_1 = require("../shared/async-fifo-mutex");
|
|
71
|
+
const diligence_1 = require("../shared/diligence");
|
|
72
|
+
const driver_messages_1 = require("../shared/i18n/driver-messages");
|
|
73
|
+
const runtime_language_1 = require("../shared/runtime-language");
|
|
74
|
+
const id_1 = require("../shared/utils/id");
|
|
75
|
+
const inter_dialog_format_1 = require("../shared/utils/inter-dialog-format");
|
|
76
|
+
const time_1 = require("../shared/utils/time");
|
|
77
|
+
const team_1 = require("../team");
|
|
78
|
+
const tellask_1 = require("../tellask");
|
|
79
|
+
const tool_1 = require("../tool");
|
|
80
|
+
const id_2 = require("../utils/id");
|
|
81
|
+
const taskdoc_1 = require("../utils/taskdoc");
|
|
82
|
+
const client_1 = require("./client");
|
|
83
|
+
const registry_1 = require("./gen/registry");
|
|
84
|
+
const tools_projection_1 = require("./tools-projection");
|
|
85
|
+
const DEFAULT_KEEP_GOING_MAX_NUM_PROMPTS = diligence_1.DEFAULT_DILIGENCE_PUSH_MAX;
|
|
86
|
+
function isNodeErrorWithCode(error) {
|
|
87
|
+
return error instanceof Error && 'code' in error;
|
|
88
|
+
}
|
|
89
|
+
function resolveMemberDiligencePushMax(team, agentId) {
|
|
90
|
+
const member = team.getMember(agentId);
|
|
91
|
+
if (member && member.diligence_push_max !== undefined) {
|
|
92
|
+
return member.diligence_push_max;
|
|
93
|
+
}
|
|
94
|
+
return DEFAULT_KEEP_GOING_MAX_NUM_PROMPTS;
|
|
95
|
+
}
|
|
96
|
+
function stripMarkdownFrontmatter(raw) {
|
|
97
|
+
// We no longer honor frontmatter config in diligence files.
|
|
98
|
+
// If frontmatter exists, strip it to preserve backward compatibility with old files.
|
|
99
|
+
const match = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
100
|
+
return match ? (match[1] ?? '') : raw;
|
|
101
|
+
}
|
|
102
|
+
async function resolveRtwsDiligenceConfig(workLanguage) {
|
|
103
|
+
const langSpecificPath = path.resolve(process.cwd(), '.minds', `diligence.${workLanguage}.md`);
|
|
104
|
+
const genericPath = path.resolve(process.cwd(), '.minds', 'diligence.md');
|
|
105
|
+
async function resolveFromFile(filePath) {
|
|
106
|
+
let raw;
|
|
107
|
+
try {
|
|
108
|
+
raw = await fs.promises.readFile(filePath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isNodeErrorWithCode(error) && error.code === 'ENOENT') {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
log_1.log.warn('Failed to read rtws diligence file; falling back to built-in defaults', error, {
|
|
115
|
+
filePath,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
kind: 'enabled',
|
|
119
|
+
diligenceText: diligence_1.DILIGENCE_FALLBACK_TEXT[workLanguage],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const trimmed = raw.trim();
|
|
123
|
+
// Existing empty file explicitly disables keep-going.
|
|
124
|
+
if (trimmed === '') {
|
|
125
|
+
return { kind: 'disabled', reason: 'empty_file' };
|
|
126
|
+
}
|
|
127
|
+
const bodyTrimmed = stripMarkdownFrontmatter(raw).trim();
|
|
128
|
+
if (bodyTrimmed === '') {
|
|
129
|
+
return { kind: 'disabled', reason: 'empty_body' };
|
|
130
|
+
}
|
|
131
|
+
return { kind: 'enabled', diligenceText: bodyTrimmed };
|
|
132
|
+
}
|
|
133
|
+
const langSpecific = await resolveFromFile(langSpecificPath);
|
|
134
|
+
if (langSpecific) {
|
|
135
|
+
return langSpecific;
|
|
136
|
+
}
|
|
137
|
+
const generic = await resolveFromFile(genericPath);
|
|
138
|
+
if (generic) {
|
|
139
|
+
return generic;
|
|
140
|
+
}
|
|
141
|
+
// No diligence file found: use built-in prompt + default max.
|
|
142
|
+
return {
|
|
143
|
+
kind: 'enabled',
|
|
144
|
+
diligenceText: diligence_1.DILIGENCE_FALLBACK_TEXT[workLanguage],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function maybePrepareDiligenceAutoContinuePrompt(options) {
|
|
148
|
+
if (!options.isRootDialog) {
|
|
149
|
+
return { kind: 'disabled', nextInjectedCount: options.alreadyInjectedCount };
|
|
150
|
+
}
|
|
151
|
+
if (options.dlg.disableDiligencePush) {
|
|
152
|
+
return { kind: 'disabled', nextInjectedCount: options.alreadyInjectedCount };
|
|
153
|
+
}
|
|
154
|
+
if (options.diligencePushMax < 1) {
|
|
155
|
+
return { kind: 'disabled', nextInjectedCount: options.alreadyInjectedCount };
|
|
156
|
+
}
|
|
157
|
+
const resolved = await resolveRtwsDiligenceConfig((0, runtime_language_1.getWorkLanguage)());
|
|
158
|
+
if (resolved.kind === 'disabled') {
|
|
159
|
+
return { kind: 'disabled', nextInjectedCount: options.alreadyInjectedCount };
|
|
160
|
+
}
|
|
161
|
+
const maxInjectCount = options.diligencePushMax;
|
|
162
|
+
if (maxInjectCount < 1) {
|
|
163
|
+
return { kind: 'disabled', nextInjectedCount: options.alreadyInjectedCount };
|
|
164
|
+
}
|
|
165
|
+
if (options.alreadyInjectedCount >= maxInjectCount) {
|
|
166
|
+
return {
|
|
167
|
+
kind: 'budget_exhausted',
|
|
168
|
+
maxInjectCount,
|
|
169
|
+
nextInjectedCount: options.alreadyInjectedCount,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const prompt = {
|
|
173
|
+
content: resolved.diligenceText,
|
|
174
|
+
msgId: (0, id_1.generateShortId)(),
|
|
175
|
+
grammar: 'markdown',
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
kind: 'prompt',
|
|
179
|
+
prompt,
|
|
180
|
+
maxInjectCount,
|
|
181
|
+
nextInjectedCount: options.alreadyInjectedCount + 1,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function suspendForKeepGoingBudgetExhausted(options) {
|
|
185
|
+
const { dlg, maxInjectCount } = options;
|
|
186
|
+
const questionId = `q4h-${(0, id_2.generateDialogID)()}`;
|
|
187
|
+
const question = {
|
|
188
|
+
id: questionId,
|
|
189
|
+
kind: 'keep_going_budget_exhausted',
|
|
190
|
+
headLine: '@human',
|
|
191
|
+
bodyContent: `Keep-going budget exhausted (max ${maxInjectCount}).\n\n` +
|
|
192
|
+
'Please confirm what to do next:\n' +
|
|
193
|
+
'- Reply with “continue” to allow the agent to keep going, or\n' +
|
|
194
|
+
'- Reply with “stop” if you want to stop this dialog here.\n',
|
|
195
|
+
askedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
196
|
+
callSiteRef: {
|
|
197
|
+
round: dlg.currentRound,
|
|
198
|
+
messageIndex: dlg.msgs.length,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
const existingQuestions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dlg.id);
|
|
202
|
+
existingQuestions.push(question);
|
|
203
|
+
await persistence_1.DialogPersistence._saveQuestions4HumanState(dlg.id, existingQuestions);
|
|
204
|
+
const newQuestionEvent = {
|
|
205
|
+
type: 'new_q4h_asked',
|
|
206
|
+
question: {
|
|
207
|
+
id: question.id,
|
|
208
|
+
kind: question.kind,
|
|
209
|
+
selfId: dlg.id.selfId,
|
|
210
|
+
headLine: question.headLine,
|
|
211
|
+
bodyContent: question.bodyContent,
|
|
212
|
+
askedAt: question.askedAt,
|
|
213
|
+
callSiteRef: question.callSiteRef,
|
|
214
|
+
rootId: dlg.id.rootId,
|
|
215
|
+
agentId: dlg.agentId,
|
|
216
|
+
taskDocPath: dlg.taskDocPath,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
(0, evt_registry_1.postDialogEvent)(dlg, newQuestionEvent);
|
|
220
|
+
}
|
|
221
|
+
function showErrorToAi(err) {
|
|
222
|
+
try {
|
|
223
|
+
if (err instanceof Error) {
|
|
224
|
+
return `${err.name}: ${err.message}${err.stack ? `\n${err.stack}` : ''}`;
|
|
225
|
+
}
|
|
226
|
+
if (typeof err === 'string') {
|
|
227
|
+
const s = err.trim();
|
|
228
|
+
return s.length > 500 ? s.slice(0, 497) + '...' : s;
|
|
229
|
+
}
|
|
230
|
+
return (0, util_1.inspect)(err, { depth: 5, breakLength: 120, compact: false, sorted: true });
|
|
231
|
+
}
|
|
232
|
+
catch (fallbackErr) {
|
|
233
|
+
return `Unknown error of type ${typeof err}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
class DialogInterruptedError extends Error {
|
|
237
|
+
constructor(reason) {
|
|
238
|
+
super('Dialog interrupted');
|
|
239
|
+
this.reason = reason;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function throwIfAborted(abortSignal, dlgId) {
|
|
243
|
+
if (!abortSignal?.aborted)
|
|
244
|
+
return;
|
|
245
|
+
const stopRequested = (0, dialog_run_state_1.getStopRequestedReason)(dlgId);
|
|
246
|
+
if (stopRequested === 'emergency_stop') {
|
|
247
|
+
throw new DialogInterruptedError({ kind: 'emergency_stop' });
|
|
248
|
+
}
|
|
249
|
+
if (stopRequested === 'user_stop') {
|
|
250
|
+
throw new DialogInterruptedError({ kind: 'user_stop' });
|
|
251
|
+
}
|
|
252
|
+
throw new DialogInterruptedError({ kind: 'system_stop', detail: 'Aborted.' });
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Validate streaming configuration for a team member.
|
|
256
|
+
* Streaming supports function tools; no restrictions to enforce here.
|
|
257
|
+
*/
|
|
258
|
+
function validateStreamingConfiguration(_agent, _agentTools) { }
|
|
259
|
+
function isPlainObject(value) {
|
|
260
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
261
|
+
}
|
|
262
|
+
function validateFuncToolArguments(tool, rawArgs) {
|
|
263
|
+
if (!isPlainObject(rawArgs)) {
|
|
264
|
+
return { ok: false, error: 'Arguments must be an object' };
|
|
265
|
+
}
|
|
266
|
+
if (tool.argsValidation === 'passthrough') {
|
|
267
|
+
return { ok: true, args: rawArgs };
|
|
268
|
+
}
|
|
269
|
+
const validation = (0, tool_1.validateArgs)(tool.parameters, rawArgs);
|
|
270
|
+
return validation.ok
|
|
271
|
+
? { ok: true, args: rawArgs }
|
|
272
|
+
: { ok: false, error: validation.error };
|
|
273
|
+
}
|
|
274
|
+
function classifyLlmFailure(err) {
|
|
275
|
+
const fallbackMessage = err instanceof Error
|
|
276
|
+
? err.message || err.name
|
|
277
|
+
: typeof err === 'string'
|
|
278
|
+
? err
|
|
279
|
+
: JSON.stringify(err);
|
|
280
|
+
if (err instanceof Error && err.message === 'AbortError') {
|
|
281
|
+
return { kind: 'fatal', message: 'Aborted.' };
|
|
282
|
+
}
|
|
283
|
+
{
|
|
284
|
+
const msg = err instanceof Error
|
|
285
|
+
? err.message && err.message.length > 0
|
|
286
|
+
? err.message
|
|
287
|
+
: err.name
|
|
288
|
+
: typeof err === 'string'
|
|
289
|
+
? err
|
|
290
|
+
: undefined;
|
|
291
|
+
if (typeof msg === 'string' && msg.length > 0) {
|
|
292
|
+
const lower = msg.toLowerCase();
|
|
293
|
+
if (lower.includes('fetch failed') || lower.includes('socket hang up')) {
|
|
294
|
+
return { kind: 'retriable', message: msg };
|
|
295
|
+
}
|
|
296
|
+
if (lower.includes('terminated')) {
|
|
297
|
+
return { kind: 'retriable', message: msg };
|
|
298
|
+
}
|
|
299
|
+
if (lower.includes('timeout') ||
|
|
300
|
+
lower.includes('timed out') ||
|
|
301
|
+
lower.includes('rate limit')) {
|
|
302
|
+
return { kind: 'retriable', message: msg };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (isPlainObject(err)) {
|
|
307
|
+
const status = 'status' in err && typeof err.status === 'number'
|
|
308
|
+
? err.status
|
|
309
|
+
: 'statusCode' in err && typeof err.statusCode === 'number'
|
|
310
|
+
? err.statusCode
|
|
311
|
+
: undefined;
|
|
312
|
+
const code = 'code' in err && typeof err.code === 'string'
|
|
313
|
+
? err.code
|
|
314
|
+
: 'errno' in err && typeof err.errno === 'string'
|
|
315
|
+
? err.errno
|
|
316
|
+
: undefined;
|
|
317
|
+
const msg = 'message' in err && typeof err.message === 'string' && err.message.length > 0
|
|
318
|
+
? err.message
|
|
319
|
+
: fallbackMessage;
|
|
320
|
+
if (typeof status === 'number') {
|
|
321
|
+
if (status === 408 || status === 429 || status >= 500) {
|
|
322
|
+
return { kind: 'retriable', status, message: msg };
|
|
323
|
+
}
|
|
324
|
+
if (status >= 400 && status < 500) {
|
|
325
|
+
return { kind: 'rejected', status, message: msg };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (typeof code === 'string') {
|
|
329
|
+
const retriableCodes = new Set([
|
|
330
|
+
'ETIMEDOUT',
|
|
331
|
+
'ECONNRESET',
|
|
332
|
+
'ECONNREFUSED',
|
|
333
|
+
'EAI_AGAIN',
|
|
334
|
+
'ENOTFOUND',
|
|
335
|
+
'ENETUNREACH',
|
|
336
|
+
'EHOSTUNREACH',
|
|
337
|
+
// undici / Node.js fetch
|
|
338
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
339
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
340
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
341
|
+
'UND_ERR_SOCKET',
|
|
342
|
+
]);
|
|
343
|
+
if (retriableCodes.has(code)) {
|
|
344
|
+
return { kind: 'retriable', code, message: msg };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const lower = msg.toLowerCase();
|
|
348
|
+
if (lower.includes('fetch failed') || lower.includes('socket hang up')) {
|
|
349
|
+
return { kind: 'retriable', message: msg };
|
|
350
|
+
}
|
|
351
|
+
if (lower.includes('terminated')) {
|
|
352
|
+
return { kind: 'retriable', message: msg };
|
|
353
|
+
}
|
|
354
|
+
if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('rate limit')) {
|
|
355
|
+
return { kind: 'retriable', message: msg };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return { kind: 'fatal', message: fallbackMessage };
|
|
359
|
+
}
|
|
360
|
+
async function sleepWithAbort(ms, abortSignal) {
|
|
361
|
+
if (abortSignal?.aborted) {
|
|
362
|
+
throw new Error('AbortError');
|
|
363
|
+
}
|
|
364
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
365
|
+
if (abortSignal?.aborted) {
|
|
366
|
+
throw new Error('AbortError');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function runLlmRequestWithRetry(params) {
|
|
370
|
+
const providerProblemId = `llm/provider_rejected/${params.dlg.id.valueOf()}`;
|
|
371
|
+
for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
|
|
372
|
+
try {
|
|
373
|
+
const res = await params.doRequest();
|
|
374
|
+
(0, problems_1.removeProblem)(providerProblemId);
|
|
375
|
+
return res;
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
if (params.abortSignal?.aborted) {
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
const failure = classifyLlmFailure(err);
|
|
382
|
+
const detail = (0, log_1.extractErrorDetails)(err).message;
|
|
383
|
+
if (failure.kind === 'rejected') {
|
|
384
|
+
(0, problems_1.upsertProblem)({
|
|
385
|
+
kind: 'llm_provider_rejected_request',
|
|
386
|
+
source: 'llm',
|
|
387
|
+
id: providerProblemId,
|
|
388
|
+
severity: 'error',
|
|
389
|
+
timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
390
|
+
message: `LLM provider rejected the request`,
|
|
391
|
+
detail: {
|
|
392
|
+
dialogId: params.dlg.id.valueOf(),
|
|
393
|
+
provider: params.provider,
|
|
394
|
+
errorText: detail,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
try {
|
|
398
|
+
await params.dlg.streamError(detail);
|
|
399
|
+
}
|
|
400
|
+
catch (_emitErr) {
|
|
401
|
+
// best-effort
|
|
402
|
+
}
|
|
403
|
+
throw new DialogInterruptedError({
|
|
404
|
+
kind: 'system_stop',
|
|
405
|
+
detail: `Provider '${params.provider}' rejected the request: ${failure.message}`,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const canRetry = failure.kind === 'retriable' && params.canRetry();
|
|
409
|
+
const isLastAttempt = attempt >= params.maxRetries;
|
|
410
|
+
if (!canRetry || isLastAttempt) {
|
|
411
|
+
try {
|
|
412
|
+
await params.dlg.streamError(detail);
|
|
413
|
+
}
|
|
414
|
+
catch (_emitErr) {
|
|
415
|
+
// best-effort
|
|
416
|
+
}
|
|
417
|
+
throw new DialogInterruptedError({
|
|
418
|
+
kind: 'system_stop',
|
|
419
|
+
detail: canRetry
|
|
420
|
+
? `LLM failed after retries: ${failure.message}`
|
|
421
|
+
: `LLM failed: ${failure.message}`,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
// Exponential backoff with cap. (No jitter for determinism.)
|
|
425
|
+
// We intentionally use a larger cap because transient provider termination errors often
|
|
426
|
+
// recover only after a few seconds.
|
|
427
|
+
const backoffMs = Math.min(30000, 1000 * 2 ** attempt);
|
|
428
|
+
log_1.log.warn(`Retrying LLM request after retriable error`, {
|
|
429
|
+
provider: params.provider,
|
|
430
|
+
attempt: attempt + 1,
|
|
431
|
+
backoffMs,
|
|
432
|
+
failure,
|
|
433
|
+
});
|
|
434
|
+
await sleepWithAbort(backoffMs, params.abortSignal);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
throw new DialogInterruptedError({ kind: 'system_stop', detail: 'LLM failed.' });
|
|
439
|
+
}
|
|
440
|
+
function resolveModelContextLimitTokens(modelInfo) {
|
|
441
|
+
if (modelInfo &&
|
|
442
|
+
typeof modelInfo.context_length === 'number' &&
|
|
443
|
+
Number.isFinite(modelInfo.context_length)) {
|
|
444
|
+
const n = Math.floor(modelInfo.context_length);
|
|
445
|
+
return n > 0 ? n : null;
|
|
446
|
+
}
|
|
447
|
+
if (modelInfo &&
|
|
448
|
+
typeof modelInfo.input_length === 'number' &&
|
|
449
|
+
Number.isFinite(modelInfo.input_length)) {
|
|
450
|
+
const n = Math.floor(modelInfo.input_length);
|
|
451
|
+
return n > 0 ? n : null;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
function resolveEffectiveOptimalMaxTokens(args) {
|
|
456
|
+
const configuredOptimal = args.modelInfo &&
|
|
457
|
+
typeof args.modelInfo.optimal_max_tokens === 'number' &&
|
|
458
|
+
Number.isFinite(args.modelInfo.optimal_max_tokens)
|
|
459
|
+
? Math.floor(args.modelInfo.optimal_max_tokens)
|
|
460
|
+
: undefined;
|
|
461
|
+
const optimalMaxTokensConfigured = configuredOptimal !== undefined && configuredOptimal > 0 ? configuredOptimal : undefined;
|
|
462
|
+
const configuredCritical = args.modelInfo &&
|
|
463
|
+
typeof args.modelInfo.critical_max_tokens === 'number' &&
|
|
464
|
+
Number.isFinite(args.modelInfo.critical_max_tokens)
|
|
465
|
+
? Math.floor(args.modelInfo.critical_max_tokens)
|
|
466
|
+
: undefined;
|
|
467
|
+
const criticalMaxTokensConfigured = configuredCritical !== undefined && configuredCritical > 0 ? configuredCritical : undefined;
|
|
468
|
+
// Default threshold (when not configured): 100K.
|
|
469
|
+
const defaultOptimal = 100000;
|
|
470
|
+
const effectiveOptimalMaxTokens = optimalMaxTokensConfigured !== undefined ? optimalMaxTokensConfigured : defaultOptimal;
|
|
471
|
+
// Default threshold (when not configured): 90% of hard max.
|
|
472
|
+
const defaultCritical = Math.max(1, Math.floor(args.modelContextLimitTokens * 0.9));
|
|
473
|
+
const effectiveCriticalMaxTokens = criticalMaxTokensConfigured !== undefined ? criticalMaxTokensConfigured : defaultCritical;
|
|
474
|
+
return {
|
|
475
|
+
effectiveOptimalMaxTokens,
|
|
476
|
+
optimalMaxTokensConfigured,
|
|
477
|
+
effectiveCriticalMaxTokens,
|
|
478
|
+
criticalMaxTokensConfigured,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function computeContextHealthSnapshot(args) {
|
|
482
|
+
const modelInfo = args.providerCfg.models[args.model];
|
|
483
|
+
const modelContextWindowText = modelInfo && typeof modelInfo.context_window === 'string'
|
|
484
|
+
? modelInfo.context_window
|
|
485
|
+
: undefined;
|
|
486
|
+
const modelContextLimitTokens = resolveModelContextLimitTokens(modelInfo);
|
|
487
|
+
if (modelContextLimitTokens === null) {
|
|
488
|
+
return { kind: 'unavailable', reason: 'model_limit_unavailable', modelContextWindowText };
|
|
489
|
+
}
|
|
490
|
+
const { effectiveOptimalMaxTokens, optimalMaxTokensConfigured, effectiveCriticalMaxTokens, criticalMaxTokensConfigured, } = resolveEffectiveOptimalMaxTokens({
|
|
491
|
+
modelInfo,
|
|
492
|
+
modelContextLimitTokens,
|
|
493
|
+
});
|
|
494
|
+
if (args.usage.kind !== 'available') {
|
|
495
|
+
return {
|
|
496
|
+
kind: 'unavailable',
|
|
497
|
+
reason: 'usage_unavailable',
|
|
498
|
+
modelContextWindowText,
|
|
499
|
+
modelContextLimitTokens,
|
|
500
|
+
effectiveOptimalMaxTokens,
|
|
501
|
+
optimalMaxTokensConfigured,
|
|
502
|
+
effectiveCriticalMaxTokens,
|
|
503
|
+
criticalMaxTokensConfigured,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const hardUtil = args.usage.promptTokens / modelContextLimitTokens;
|
|
507
|
+
const optimalUtil = args.usage.promptTokens / effectiveOptimalMaxTokens;
|
|
508
|
+
const level = args.usage.promptTokens > effectiveCriticalMaxTokens
|
|
509
|
+
? 'critical'
|
|
510
|
+
: args.usage.promptTokens > effectiveOptimalMaxTokens
|
|
511
|
+
? 'caution'
|
|
512
|
+
: 'healthy';
|
|
513
|
+
return {
|
|
514
|
+
kind: 'available',
|
|
515
|
+
promptTokens: args.usage.promptTokens,
|
|
516
|
+
completionTokens: args.usage.completionTokens,
|
|
517
|
+
totalTokens: args.usage.totalTokens,
|
|
518
|
+
modelContextWindowText,
|
|
519
|
+
modelContextLimitTokens,
|
|
520
|
+
effectiveOptimalMaxTokens,
|
|
521
|
+
optimalMaxTokensConfigured,
|
|
522
|
+
effectiveCriticalMaxTokens,
|
|
523
|
+
criticalMaxTokensConfigured,
|
|
524
|
+
hardUtil,
|
|
525
|
+
optimalUtil,
|
|
526
|
+
level,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const contextHealthV3StateByDialogKey = new Map();
|
|
530
|
+
function getContextHealthV3State(dlg) {
|
|
531
|
+
const key = dlg.id.key();
|
|
532
|
+
const existing = contextHealthV3StateByDialogKey.get(key);
|
|
533
|
+
if (existing)
|
|
534
|
+
return existing;
|
|
535
|
+
const created = {};
|
|
536
|
+
contextHealthV3StateByDialogKey.set(key, created);
|
|
537
|
+
return created;
|
|
538
|
+
}
|
|
539
|
+
function resetContextHealthV3State(dlg) {
|
|
540
|
+
contextHealthV3StateByDialogKey.delete(dlg.id.key());
|
|
541
|
+
}
|
|
542
|
+
const defaultCautionHardCadenceGenerations = 10;
|
|
543
|
+
const defaultCriticalCountdownGenerations = 5;
|
|
544
|
+
function shouldInjectCautionRemediationGuide(args) {
|
|
545
|
+
const { dlg } = args;
|
|
546
|
+
const state = getContextHealthV3State(dlg);
|
|
547
|
+
const modelInfo = args.providerCfg.models[args.model];
|
|
548
|
+
const cadence = modelInfo &&
|
|
549
|
+
typeof modelInfo.caution_remediation_cadence_generations === 'number' &&
|
|
550
|
+
Number.isFinite(modelInfo.caution_remediation_cadence_generations)
|
|
551
|
+
? Math.floor(modelInfo.caution_remediation_cadence_generations)
|
|
552
|
+
: undefined;
|
|
553
|
+
const effectiveCadence = typeof cadence === 'number' && Number.isFinite(cadence) && cadence > 0
|
|
554
|
+
? Math.floor(cadence)
|
|
555
|
+
: defaultCautionHardCadenceGenerations;
|
|
556
|
+
const genSeq = dlg.activeGenSeq;
|
|
557
|
+
if (genSeq === undefined)
|
|
558
|
+
return true;
|
|
559
|
+
const lastInjected = state.lastCautionGuideInjectedAtGenSeq;
|
|
560
|
+
if (lastInjected === undefined)
|
|
561
|
+
return true;
|
|
562
|
+
return genSeq - lastInjected >= effectiveCadence;
|
|
563
|
+
}
|
|
564
|
+
async function suspendForContextHealthCritical(dlg) {
|
|
565
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
566
|
+
const questionId = `q4h-${(0, id_2.generateDialogID)()}`;
|
|
567
|
+
const question = {
|
|
568
|
+
id: questionId,
|
|
569
|
+
kind: 'context_health_critical',
|
|
570
|
+
headLine: '@human',
|
|
571
|
+
bodyContent: language === 'zh'
|
|
572
|
+
? [
|
|
573
|
+
'上下文健康已进入 🔴 告急(critical),且 critical remediation 无法自动完成。',
|
|
574
|
+
'',
|
|
575
|
+
'为避免继续污染对话状态,系统已暂停该对话(suspended)。',
|
|
576
|
+
'',
|
|
577
|
+
'请人工介入:',
|
|
578
|
+
'- 检查当前对话/系统提示是否存在冲突或工具不可用导致 agent 无法调用 clear_mind;',
|
|
579
|
+
'- 修复后,再通过 UI 恢复/继续该对话(或手动触发 clear_mind)。',
|
|
580
|
+
].join('\n')
|
|
581
|
+
: [
|
|
582
|
+
'Context health is 🔴 critical, and critical remediation could not complete automatically.',
|
|
583
|
+
'',
|
|
584
|
+
'To avoid further state pollution, the dialog is now suspended.',
|
|
585
|
+
'',
|
|
586
|
+
'Please intervene:',
|
|
587
|
+
'- Check whether the system prompt/tooling is preventing clear_mind calls;',
|
|
588
|
+
'- Fix the issue, then resume the dialog in the UI (or manually trigger clear_mind).',
|
|
589
|
+
].join('\n'),
|
|
590
|
+
askedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
591
|
+
callSiteRef: {
|
|
592
|
+
round: dlg.currentRound,
|
|
593
|
+
messageIndex: dlg.msgs.length,
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
const existingQuestions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dlg.id);
|
|
597
|
+
existingQuestions.push(question);
|
|
598
|
+
await persistence_1.DialogPersistence._saveQuestions4HumanState(dlg.id, existingQuestions);
|
|
599
|
+
const newQuestionEvent = {
|
|
600
|
+
type: 'new_q4h_asked',
|
|
601
|
+
question: {
|
|
602
|
+
id: question.id,
|
|
603
|
+
kind: question.kind,
|
|
604
|
+
selfId: dlg.id.selfId,
|
|
605
|
+
headLine: question.headLine,
|
|
606
|
+
bodyContent: question.bodyContent,
|
|
607
|
+
askedAt: question.askedAt,
|
|
608
|
+
callSiteRef: question.callSiteRef,
|
|
609
|
+
rootId: dlg.id.rootId,
|
|
610
|
+
agentId: dlg.agentId,
|
|
611
|
+
taskDocPath: dlg.taskDocPath,
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
(0, evt_registry_1.postDialogEvent)(dlg, newQuestionEvent);
|
|
615
|
+
}
|
|
616
|
+
async function applyContextHealthV3Remediation(args) {
|
|
617
|
+
const { dlg } = args;
|
|
618
|
+
const snapshot = dlg.getLastContextHealth();
|
|
619
|
+
if (!snapshot || snapshot.kind !== 'available') {
|
|
620
|
+
resetContextHealthV3State(dlg);
|
|
621
|
+
return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
|
|
622
|
+
}
|
|
623
|
+
if (snapshot.level === 'healthy') {
|
|
624
|
+
resetContextHealthV3State(dlg);
|
|
625
|
+
return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
|
|
626
|
+
}
|
|
627
|
+
if (snapshot.level === 'caution') {
|
|
628
|
+
const state = getContextHealthV3State(dlg);
|
|
629
|
+
if (state.lastSeenLevel !== 'caution') {
|
|
630
|
+
state.lastSeenLevel = 'caution';
|
|
631
|
+
state.criticalCountdownRemaining = undefined;
|
|
632
|
+
}
|
|
633
|
+
if (!shouldInjectCautionRemediationGuide({
|
|
634
|
+
dlg,
|
|
635
|
+
providerCfg: args.providerCfg,
|
|
636
|
+
model: args.model,
|
|
637
|
+
})) {
|
|
638
|
+
return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
|
|
639
|
+
}
|
|
640
|
+
// Caution remediation at cadence (including the entry injection): require reminder curation
|
|
641
|
+
// (no forced clear_mind).
|
|
642
|
+
const activeGenSeq = dlg.activeGenSeqOrUndefined;
|
|
643
|
+
if (activeGenSeq === undefined) {
|
|
644
|
+
return { kind: 'proceed', ctxMsgs: args.ctxMsgs };
|
|
645
|
+
}
|
|
646
|
+
const guideText = (0, driver_messages_1.formatUserFacingContextHealthV3RemediationGuide)((0, runtime_language_1.getWorkLanguage)(), {
|
|
647
|
+
kind: 'caution',
|
|
648
|
+
mode: 'soft',
|
|
649
|
+
});
|
|
650
|
+
state.lastCautionGuideInjectedAtGenSeq = activeGenSeq;
|
|
651
|
+
// Prefer a recorded user prompt (visible in UI) when there isn't already a user prompt
|
|
652
|
+
// in this generation.
|
|
653
|
+
if (!args.hadUserPromptThisGen) {
|
|
654
|
+
const msgId = (0, id_1.generateShortId)();
|
|
655
|
+
const userLanguageCode = (0, runtime_language_1.getWorkLanguage)();
|
|
656
|
+
const promptMsg = {
|
|
657
|
+
type: 'prompting_msg',
|
|
658
|
+
role: 'user',
|
|
659
|
+
genseq: activeGenSeq,
|
|
660
|
+
msgId,
|
|
661
|
+
grammar: 'markdown',
|
|
662
|
+
content: guideText,
|
|
663
|
+
};
|
|
664
|
+
await dlg.addChatMessages(promptMsg);
|
|
665
|
+
await dlg.persistUserMessage(guideText, msgId, 'markdown', userLanguageCode);
|
|
666
|
+
await emitUserMarkdown(dlg, guideText);
|
|
667
|
+
try {
|
|
668
|
+
(0, evt_registry_1.postDialogEvent)(dlg, {
|
|
669
|
+
type: 'end_of_user_saying_evt',
|
|
670
|
+
round: dlg.currentRound,
|
|
671
|
+
genseq: activeGenSeq,
|
|
672
|
+
msgId,
|
|
673
|
+
content: guideText,
|
|
674
|
+
grammar: 'markdown',
|
|
675
|
+
userLanguageCode,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
log_1.log.warn('Failed to emit end_of_user_saying_evt for caution guide prompt', err);
|
|
680
|
+
}
|
|
681
|
+
return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, promptMsg] };
|
|
682
|
+
}
|
|
683
|
+
// Fallback: still guide the LLM even if we cannot safely emit a second user prompt for UI.
|
|
684
|
+
const guide = { type: 'environment_msg', role: 'user', content: guideText };
|
|
685
|
+
return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, guide] };
|
|
686
|
+
}
|
|
687
|
+
if (snapshot.level === 'critical') {
|
|
688
|
+
const state = getContextHealthV3State(dlg);
|
|
689
|
+
if (state.lastSeenLevel !== 'critical') {
|
|
690
|
+
state.lastSeenLevel = 'critical';
|
|
691
|
+
state.criticalCountdownRemaining = defaultCriticalCountdownGenerations;
|
|
692
|
+
}
|
|
693
|
+
const promptsBeforeAutoClear = typeof state.criticalCountdownRemaining === 'number' &&
|
|
694
|
+
Number.isFinite(state.criticalCountdownRemaining)
|
|
695
|
+
? Math.floor(state.criticalCountdownRemaining)
|
|
696
|
+
: defaultCriticalCountdownGenerations;
|
|
697
|
+
if (promptsBeforeAutoClear <= 0) {
|
|
698
|
+
// Countdown exhausted: force clear_mind automatically (no Q4H) to keep long-running
|
|
699
|
+
// autonomy stable.
|
|
700
|
+
const clearTool = args.agentTools.find((t) => t.type === 'func' && t.name === 'clear_mind');
|
|
701
|
+
if (!clearTool) {
|
|
702
|
+
log_1.log.warn('clear_mind tool not found in agent tools during critical remediation', {
|
|
703
|
+
dialogId: dlg.id.valueOf(),
|
|
704
|
+
});
|
|
705
|
+
// Keep countdown running even if tooling is misconfigured.
|
|
706
|
+
await suspendForContextHealthCritical(dlg);
|
|
707
|
+
return { kind: 'suspend' };
|
|
708
|
+
}
|
|
709
|
+
const funcId = `auto-clear-${(0, id_2.generateDialogID)()}`;
|
|
710
|
+
const callGenseq = dlg.activeGenSeq;
|
|
711
|
+
const toolArgs = {};
|
|
712
|
+
const argsStr = JSON.stringify(toolArgs);
|
|
713
|
+
const funcCall = {
|
|
714
|
+
type: 'func_call_msg',
|
|
715
|
+
role: 'assistant',
|
|
716
|
+
genseq: callGenseq,
|
|
717
|
+
id: funcId,
|
|
718
|
+
name: 'clear_mind',
|
|
719
|
+
arguments: argsStr,
|
|
720
|
+
};
|
|
721
|
+
try {
|
|
722
|
+
await dlg.funcCallRequested(funcId, 'clear_mind', argsStr);
|
|
723
|
+
}
|
|
724
|
+
catch (err) {
|
|
725
|
+
log_1.log.warn('Failed to emit func_call_requested for critical auto-clear clear_mind', err);
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
await dlg.persistFunctionCall(funcId, 'clear_mind', toolArgs, callGenseq);
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
log_1.log.warn('Failed to persist clear_mind function call for critical auto-clear', err);
|
|
732
|
+
}
|
|
733
|
+
let funcResult;
|
|
734
|
+
try {
|
|
735
|
+
const content = await clearTool.call(dlg, args.agent, toolArgs);
|
|
736
|
+
funcResult = {
|
|
737
|
+
type: 'func_result_msg',
|
|
738
|
+
id: funcId,
|
|
739
|
+
name: 'clear_mind',
|
|
740
|
+
content: String(content),
|
|
741
|
+
role: 'tool',
|
|
742
|
+
genseq: callGenseq,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
catch (err) {
|
|
746
|
+
funcResult = {
|
|
747
|
+
type: 'func_result_msg',
|
|
748
|
+
id: funcId,
|
|
749
|
+
name: 'clear_mind',
|
|
750
|
+
content: `Function 'clear_mind' execution failed: ${showErrorToAi(err)}`,
|
|
751
|
+
role: 'tool',
|
|
752
|
+
genseq: callGenseq,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
await dlg.receiveFuncResult(funcResult);
|
|
756
|
+
await dlg.addChatMessages(funcCall, funcResult);
|
|
757
|
+
resetContextHealthV3State(dlg);
|
|
758
|
+
const nextPrompt = resolveUpNextPrompt(dlg);
|
|
759
|
+
return { kind: 'continue', nextPrompt };
|
|
760
|
+
}
|
|
761
|
+
const guideText = (0, driver_messages_1.formatUserFacingContextHealthV3RemediationGuide)((0, runtime_language_1.getWorkLanguage)(), {
|
|
762
|
+
kind: 'critical',
|
|
763
|
+
mode: 'countdown',
|
|
764
|
+
promptsRemainingAfterThis: promptsBeforeAutoClear - 1,
|
|
765
|
+
promptsTotal: defaultCriticalCountdownGenerations,
|
|
766
|
+
});
|
|
767
|
+
state.criticalCountdownRemaining = promptsBeforeAutoClear - 1;
|
|
768
|
+
// Prefer a recorded prompt (visible in UI as user prompt) when there isn't already a
|
|
769
|
+
// user prompt in this generation.
|
|
770
|
+
if (!args.hadUserPromptThisGen) {
|
|
771
|
+
const msgId = (0, id_1.generateShortId)();
|
|
772
|
+
const userLanguageCode = (0, runtime_language_1.getWorkLanguage)();
|
|
773
|
+
const promptMsg = {
|
|
774
|
+
type: 'prompting_msg',
|
|
775
|
+
role: 'user',
|
|
776
|
+
genseq: dlg.activeGenSeq,
|
|
777
|
+
msgId,
|
|
778
|
+
grammar: 'markdown',
|
|
779
|
+
content: guideText,
|
|
780
|
+
};
|
|
781
|
+
await dlg.addChatMessages(promptMsg);
|
|
782
|
+
await dlg.persistUserMessage(guideText, msgId, 'markdown', userLanguageCode);
|
|
783
|
+
await emitUserMarkdown(dlg, guideText);
|
|
784
|
+
try {
|
|
785
|
+
(0, evt_registry_1.postDialogEvent)(dlg, {
|
|
786
|
+
type: 'end_of_user_saying_evt',
|
|
787
|
+
round: dlg.currentRound,
|
|
788
|
+
genseq: dlg.activeGenSeq,
|
|
789
|
+
msgId,
|
|
790
|
+
content: guideText,
|
|
791
|
+
grammar: 'markdown',
|
|
792
|
+
userLanguageCode,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
catch (err) {
|
|
796
|
+
log_1.log.warn('Failed to emit end_of_user_saying_evt for critical countdown prompt', err);
|
|
797
|
+
}
|
|
798
|
+
return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, promptMsg] };
|
|
799
|
+
}
|
|
800
|
+
// Fallback: still guide the LLM even if we cannot safely emit a second user prompt for UI.
|
|
801
|
+
const guide = {
|
|
802
|
+
type: 'environment_msg',
|
|
803
|
+
role: 'user',
|
|
804
|
+
content: guideText,
|
|
805
|
+
};
|
|
806
|
+
return { kind: 'proceed', ctxMsgs: [...args.ctxMsgs, guide] };
|
|
807
|
+
}
|
|
808
|
+
const _exhaustive = snapshot.level;
|
|
809
|
+
return _exhaustive;
|
|
810
|
+
}
|
|
811
|
+
// === UNIFIED STREAMING HANDLERS ===
|
|
812
|
+
/**
|
|
813
|
+
* Create a TellaskEventsReceiver for unified saying event emission.
|
|
814
|
+
* Handles tellask call blocks and markdown using TellaskStreamParser.
|
|
815
|
+
* Used by both streaming and non-streaming modes.
|
|
816
|
+
*/
|
|
817
|
+
function createSayingEventsReceiver(dlg) {
|
|
818
|
+
return {
|
|
819
|
+
markdownStart: async () => {
|
|
820
|
+
await dlg.markdownStart();
|
|
821
|
+
},
|
|
822
|
+
markdownChunk: async (chunk) => {
|
|
823
|
+
await dlg.markdownChunk(chunk);
|
|
824
|
+
},
|
|
825
|
+
markdownFinish: async () => {
|
|
826
|
+
await dlg.markdownFinish();
|
|
827
|
+
},
|
|
828
|
+
callStart: async (validation) => {
|
|
829
|
+
await dlg.callingStart(validation);
|
|
830
|
+
},
|
|
831
|
+
callHeadLineChunk: async (chunk) => {
|
|
832
|
+
await dlg.callingHeadlineChunk(chunk);
|
|
833
|
+
},
|
|
834
|
+
callHeadLineFinish: async () => {
|
|
835
|
+
await dlg.callingHeadlineFinish();
|
|
836
|
+
},
|
|
837
|
+
callBodyStart: async () => {
|
|
838
|
+
await dlg.callingBodyStart();
|
|
839
|
+
},
|
|
840
|
+
callBodyChunk: async (chunk) => {
|
|
841
|
+
await dlg.callingBodyChunk(chunk);
|
|
842
|
+
},
|
|
843
|
+
callBodyFinish: async () => {
|
|
844
|
+
await dlg.callingBodyFinish();
|
|
845
|
+
},
|
|
846
|
+
callFinish: async (callId) => {
|
|
847
|
+
await dlg.callingFinish(callId);
|
|
848
|
+
},
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Emit thinking events for a thinking message (non-streaming mode).
|
|
853
|
+
* Emits thinkingStart, thinkingChunk with full content, and thinkingFinish.
|
|
854
|
+
* Returns the extracted signature for caller to use.
|
|
855
|
+
*/
|
|
856
|
+
async function emitThinkingEvents(dlg, content) {
|
|
857
|
+
if (!content.trim())
|
|
858
|
+
return undefined;
|
|
859
|
+
await dlg.thinkingStart();
|
|
860
|
+
await dlg.thinkingChunk(content);
|
|
861
|
+
await dlg.thinkingFinish();
|
|
862
|
+
// Extract and return signature for caller to use
|
|
863
|
+
const signatureMatch = content.match(/<thinking[^>]*>(.*?)<\/thinking>/s);
|
|
864
|
+
return signatureMatch?.[1]?.trim();
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Emit saying events using TellaskStreamParser (non-streaming mode).
|
|
868
|
+
* Processes the entire content at once, handling markdown + tellask calls.
|
|
869
|
+
*/
|
|
870
|
+
async function emitSayingEvents(dlg, content) {
|
|
871
|
+
if (!content.trim())
|
|
872
|
+
return [];
|
|
873
|
+
const receiver = createSayingEventsReceiver(dlg);
|
|
874
|
+
const parser = new tellask_1.TellaskStreamParser(receiver);
|
|
875
|
+
await parser.takeUpstreamChunk(content);
|
|
876
|
+
await parser.finalize();
|
|
877
|
+
return parser.getCollectedCalls();
|
|
878
|
+
}
|
|
879
|
+
async function emitUserMarkdown(dlg, content) {
|
|
880
|
+
if (!content.trim()) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
await dlg.markdownStart();
|
|
884
|
+
await dlg.markdownChunk(content);
|
|
885
|
+
await dlg.markdownFinish();
|
|
886
|
+
}
|
|
887
|
+
function resolveUpNextPrompt(dlg, humanPrompt) {
|
|
888
|
+
if (humanPrompt) {
|
|
889
|
+
return humanPrompt;
|
|
890
|
+
}
|
|
891
|
+
const upNext = dlg.takeUpNext();
|
|
892
|
+
if (!upNext) {
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
content: upNext.prompt,
|
|
897
|
+
msgId: upNext.msgId,
|
|
898
|
+
grammar: 'markdown',
|
|
899
|
+
userLanguageCode: upNext.userLanguageCode,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function scheduleUpNextDrive(dlg, upNext) {
|
|
903
|
+
const prompt = {
|
|
904
|
+
content: upNext.prompt,
|
|
905
|
+
msgId: upNext.msgId,
|
|
906
|
+
grammar: 'markdown',
|
|
907
|
+
userLanguageCode: upNext.userLanguageCode,
|
|
908
|
+
};
|
|
909
|
+
void driveDialogStream(dlg, prompt, true);
|
|
910
|
+
}
|
|
911
|
+
const suspensionStateMutexes = new Map();
|
|
912
|
+
async function withSuspensionStateLock(dialogId, fn) {
|
|
913
|
+
const key = dialogId.key();
|
|
914
|
+
let mutex = suspensionStateMutexes.get(key);
|
|
915
|
+
if (!mutex) {
|
|
916
|
+
mutex = new async_fifo_mutex_1.AsyncFifoMutex();
|
|
917
|
+
suspensionStateMutexes.set(key, mutex);
|
|
918
|
+
}
|
|
919
|
+
const release = await mutex.acquire();
|
|
920
|
+
try {
|
|
921
|
+
return await fn();
|
|
922
|
+
}
|
|
923
|
+
finally {
|
|
924
|
+
release();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
// TODO: certain scenarios should pass `waitInQue=true`:
|
|
928
|
+
// - supdialog call for clarification
|
|
929
|
+
/**
|
|
930
|
+
* Drive a dialog stream with the following phases:
|
|
931
|
+
*
|
|
932
|
+
* Phase 1 - Lock Acquisition:
|
|
933
|
+
* - Attempt to acquire exclusive lock for the dialog using mutex
|
|
934
|
+
* - If dialog is already being driven, either wait in queue or throw error
|
|
935
|
+
*
|
|
936
|
+
* Phase 2 - Human Prompt Processing (first iteration only):
|
|
937
|
+
* - If humanPrompt is provided, add it as a prompting_msg
|
|
938
|
+
* - Persist user message to storage
|
|
939
|
+
*
|
|
940
|
+
* Phase 3 - User Tool Calls Collection & Execution:
|
|
941
|
+
* - Parse user text for tellask blocks using TellaskStreamParser
|
|
942
|
+
* - Execute tellasks (teammate tellasks / Q4H / supdialog)
|
|
943
|
+
* - Handle subdialog creation for @teammate mentions
|
|
944
|
+
*
|
|
945
|
+
* Phase 4 - Context Building:
|
|
946
|
+
* - Load agent minds (team, agent, system prompt, memories, tools)
|
|
947
|
+
* - Build context messages: memories, task doc, assignment from supdialog, dialog msgs
|
|
948
|
+
* - Process and render reminders
|
|
949
|
+
*
|
|
950
|
+
* Phase 5 - LLM Generation:
|
|
951
|
+
* - For streaming=false: Generate all messages at once
|
|
952
|
+
* - For streaming=true: Stream responses with thinking/saying events
|
|
953
|
+
*
|
|
954
|
+
* Phase 6 - Function/Tellask Call Execution:
|
|
955
|
+
* - Execute function calls (non-streaming mode)
|
|
956
|
+
* - Execute tellask calls (streaming mode)
|
|
957
|
+
* - Collect and persist results
|
|
958
|
+
*
|
|
959
|
+
* Phase 7 - Loop or Complete:
|
|
960
|
+
* - Check if more generation iterations are needed
|
|
961
|
+
* - Continue loop if new function calls or tool outputs exist
|
|
962
|
+
* - Break and release lock when complete
|
|
963
|
+
*/
|
|
964
|
+
async function driveDialogStream(dlg, humanPrompt, waitInQue = false) {
|
|
965
|
+
if (!waitInQue && dlg.isLocked()) {
|
|
966
|
+
throw new Error(`Dialog busy driven, see how it proceeded and try again.`);
|
|
967
|
+
}
|
|
968
|
+
const release = await dlg.acquire();
|
|
969
|
+
let followUp;
|
|
970
|
+
let generatedAssistantResponse = null;
|
|
971
|
+
try {
|
|
972
|
+
const effectivePrompt = resolveUpNextPrompt(dlg, humanPrompt);
|
|
973
|
+
if (effectivePrompt && effectivePrompt.userLanguageCode) {
|
|
974
|
+
dlg.setLastUserLanguageCode(effectivePrompt.userLanguageCode);
|
|
975
|
+
}
|
|
976
|
+
generatedAssistantResponse = await _driveDialogStream(dlg, effectivePrompt);
|
|
977
|
+
followUp = dlg.takeUpNext();
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
release();
|
|
981
|
+
}
|
|
982
|
+
if (followUp) {
|
|
983
|
+
scheduleUpNextDrive(dlg, followUp);
|
|
984
|
+
}
|
|
985
|
+
else if (dlg instanceof dialog_1.SubDialog && generatedAssistantResponse !== null) {
|
|
986
|
+
await supplySubdialogResponseToCallerIfPending(dlg, generatedAssistantResponse);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Backend coroutine that continuously drives dialogs.
|
|
991
|
+
* Uses dynamic canDrive() checks instead of stored suspend state.
|
|
992
|
+
*/
|
|
993
|
+
async function runBackendDriver() {
|
|
994
|
+
while (true) {
|
|
995
|
+
try {
|
|
996
|
+
const dialogsToDrive = dialog_global_registry_1.globalDialogRegistry.getDialogsNeedingDrive();
|
|
997
|
+
for (const rootDialog of dialogsToDrive) {
|
|
998
|
+
try {
|
|
999
|
+
if (!(await rootDialog.canDrive())) {
|
|
1000
|
+
dialog_global_registry_1.globalDialogRegistry.markNotNeedingDrive(rootDialog.id.rootId);
|
|
1001
|
+
await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, false, rootDialog.status);
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
const release = await rootDialog.acquire();
|
|
1005
|
+
try {
|
|
1006
|
+
await driveDialogToSuspension(rootDialog);
|
|
1007
|
+
}
|
|
1008
|
+
finally {
|
|
1009
|
+
release();
|
|
1010
|
+
}
|
|
1011
|
+
if (rootDialog.hasUpNext()) {
|
|
1012
|
+
dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(rootDialog.id.rootId);
|
|
1013
|
+
await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, true, rootDialog.status);
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
dialog_global_registry_1.globalDialogRegistry.markNotNeedingDrive(rootDialog.id.rootId);
|
|
1017
|
+
await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, false, rootDialog.status);
|
|
1018
|
+
}
|
|
1019
|
+
const status = await rootDialog.getSuspensionStatus();
|
|
1020
|
+
if (status.subdialogs) {
|
|
1021
|
+
log_1.log.info(`Dialog ${rootDialog.id.rootId} suspended, waiting for subdialogs`);
|
|
1022
|
+
}
|
|
1023
|
+
if (status.q4h) {
|
|
1024
|
+
log_1.log.info(`Dialog ${rootDialog.id.rootId} awaiting Q4H answer`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
catch (err) {
|
|
1028
|
+
log_1.log.error(`Error driving dialog ${rootDialog.id.rootId}:`, err, undefined, {
|
|
1029
|
+
dialogId: rootDialog.id.rootId,
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1034
|
+
}
|
|
1035
|
+
catch (loopErr) {
|
|
1036
|
+
log_1.log.error('Error in backend driver loop:', loopErr);
|
|
1037
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Drive a dialog until it suspends or completes.
|
|
1043
|
+
* Called with mutex already acquired.
|
|
1044
|
+
*/
|
|
1045
|
+
async function driveDialogToSuspension(dlg) {
|
|
1046
|
+
try {
|
|
1047
|
+
const effectivePrompt = resolveUpNextPrompt(dlg);
|
|
1048
|
+
if (effectivePrompt && effectivePrompt.userLanguageCode) {
|
|
1049
|
+
dlg.setLastUserLanguageCode(effectivePrompt.userLanguageCode);
|
|
1050
|
+
}
|
|
1051
|
+
await _driveDialogStream(dlg, effectivePrompt);
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
log_1.log.warn(`Error in driveDialogToSuspension for ${dlg.id.selfId}:`, err);
|
|
1055
|
+
throw err;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Frontend-triggered revive check (crash-recovery).
|
|
1060
|
+
*/
|
|
1061
|
+
async function checkAndReviveSuspendedDialogs() {
|
|
1062
|
+
const allDialogs = dialog_global_registry_1.globalDialogRegistry.getAll();
|
|
1063
|
+
for (const rootDialog of allDialogs) {
|
|
1064
|
+
const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialog.id);
|
|
1065
|
+
if (pending.length > 0) {
|
|
1066
|
+
const allSatisfied = await areAllSubdialogsSatisfied(rootDialog.id);
|
|
1067
|
+
if (allSatisfied) {
|
|
1068
|
+
await withSuspensionStateLock(rootDialog.id, async () => {
|
|
1069
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(rootDialog.id, []);
|
|
1070
|
+
await persistence_1.DialogPersistence.setNeedsDrive(rootDialog.id, true, rootDialog.status);
|
|
1071
|
+
});
|
|
1072
|
+
dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(rootDialog.id.rootId);
|
|
1073
|
+
log_1.log.info(`All subdialogs complete for ${rootDialog.id.rootId}, auto-reviving`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const subdialogs = rootDialog.getAllDialogs().filter((d) => d !== rootDialog);
|
|
1077
|
+
for (const subdialog of subdialogs) {
|
|
1078
|
+
const hasAnswer = await checkQ4HAnswered(subdialog.id);
|
|
1079
|
+
if (hasAnswer && !(await subdialog.hasPendingQ4H())) {
|
|
1080
|
+
void driveDialogStream(subdialog, undefined, true);
|
|
1081
|
+
log_1.log.info(`Q4H answered for dialog ${subdialog.id.selfId}, auto-reviving`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
async function checkQ4HAnswered(dialogId) {
|
|
1087
|
+
try {
|
|
1088
|
+
const questions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogId);
|
|
1089
|
+
return questions.length === 0;
|
|
1090
|
+
}
|
|
1091
|
+
catch (err) {
|
|
1092
|
+
log_1.log.warn(`Error checking Q4H state ${dialogId.key()}:`, err);
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
async function _driveDialogStream(dlg, humanPrompt) {
|
|
1097
|
+
const abortSignal = (0, dialog_run_state_1.createActiveRun)(dlg.id);
|
|
1098
|
+
let finalRunState;
|
|
1099
|
+
let shouldEmitResumedMarker = false;
|
|
1100
|
+
if (!humanPrompt) {
|
|
1101
|
+
try {
|
|
1102
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(dlg.id, 'running');
|
|
1103
|
+
shouldEmitResumedMarker =
|
|
1104
|
+
latest?.runState !== undefined && latest.runState.kind === 'interrupted';
|
|
1105
|
+
}
|
|
1106
|
+
catch (err) {
|
|
1107
|
+
log_1.log.warn('Failed to load latest.yaml for resumption marker', err, {
|
|
1108
|
+
dialogId: dlg.id.valueOf(),
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (shouldEmitResumedMarker) {
|
|
1113
|
+
(0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, { kind: 'resumed' });
|
|
1114
|
+
}
|
|
1115
|
+
await (0, dialog_run_state_1.setDialogRunState)(dlg.id, { kind: 'proceeding' });
|
|
1116
|
+
let pubRemindersVer = dlg.remindersVer;
|
|
1117
|
+
let lastAssistantSayingContent = null;
|
|
1118
|
+
let generationHadError = false;
|
|
1119
|
+
let tookSubdialogResponses = false;
|
|
1120
|
+
let takenSubdialogResponses = [];
|
|
1121
|
+
let genIterNo = 0;
|
|
1122
|
+
let pendingPrompt = humanPrompt;
|
|
1123
|
+
try {
|
|
1124
|
+
while (true) {
|
|
1125
|
+
genIterNo++;
|
|
1126
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1127
|
+
// reload the agent's minds from disk every round, in case the disk files changed by human or ai meanwhile
|
|
1128
|
+
const { team, agent, systemPrompt, memories, agentTools } = await (0, load_1.loadAgentMinds)(dlg.agentId, dlg);
|
|
1129
|
+
// reload cfgs every round, in case it's been updated by human or ai meanwhile
|
|
1130
|
+
// Validate streaming configuration
|
|
1131
|
+
try {
|
|
1132
|
+
validateStreamingConfiguration(agent, agentTools);
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
log_1.log.warn(`Streaming configuration error for agent ${dlg.agentId}:`, error);
|
|
1136
|
+
throw error;
|
|
1137
|
+
}
|
|
1138
|
+
// Validate that required provider and model are configured
|
|
1139
|
+
// Validate that required provider and model are configured
|
|
1140
|
+
const provider = agent.provider ?? team.memberDefaults.provider;
|
|
1141
|
+
const model = agent.model ?? team.memberDefaults.model;
|
|
1142
|
+
if (!provider) {
|
|
1143
|
+
const error = new Error(`Configuration Error: No provider configured for agent '${dlg.agentId}'. Please specify a provider in the agent's configuration or in member_defaults section of .minds/team.yaml.`);
|
|
1144
|
+
log_1.log.warn(`Provider not configured for agent ${dlg.agentId}`, error);
|
|
1145
|
+
throw error;
|
|
1146
|
+
}
|
|
1147
|
+
if (!model) {
|
|
1148
|
+
const error = new Error(`Configuration Error: No model configured for agent '${dlg.agentId}'. Please specify a model in the agent's configuration or in member_defaults section of .minds/team.yaml.`);
|
|
1149
|
+
log_1.log.warn(`Model not configured for agent ${dlg.agentId}`, error);
|
|
1150
|
+
throw error;
|
|
1151
|
+
}
|
|
1152
|
+
const llmCfg = await client_1.LlmConfig.load();
|
|
1153
|
+
const providerCfg = llmCfg.getProvider(provider);
|
|
1154
|
+
if (!providerCfg) {
|
|
1155
|
+
const error = new Error(`Provider configuration error: Provider '${provider}' not found for agent '${dlg.agentId}'. Please check .minds/llm.yaml and .minds/team.yaml configuration.`);
|
|
1156
|
+
log_1.log.warn(`Provider not found for agent ${dlg.agentId}`, error);
|
|
1157
|
+
throw error;
|
|
1158
|
+
}
|
|
1159
|
+
const llmGen = (0, registry_1.getLlmGenerator)(providerCfg.apiType);
|
|
1160
|
+
if (!llmGen) {
|
|
1161
|
+
const error = new Error(`LLM generator not found: API type '${providerCfg.apiType}' for provider '${provider}' in agent '${dlg.agentId}'. Please check .minds/llm.yaml configuration.`);
|
|
1162
|
+
log_1.log.warn(`LLM generator not found for agent ${dlg.agentId}`, error);
|
|
1163
|
+
throw error;
|
|
1164
|
+
}
|
|
1165
|
+
const canonicalFuncTools = agentTools.filter((t) => t.type === 'func');
|
|
1166
|
+
const projected = (0, tools_projection_1.projectFuncToolsForProvider)(providerCfg.apiType, canonicalFuncTools);
|
|
1167
|
+
const funcTools = projected.tools;
|
|
1168
|
+
let suspendForHuman = false;
|
|
1169
|
+
let promptContent = '';
|
|
1170
|
+
let contextHealthForGen;
|
|
1171
|
+
let llmGenModelForGen = model;
|
|
1172
|
+
try {
|
|
1173
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1174
|
+
await dlg.notifyGeneratingStart();
|
|
1175
|
+
const currentPrompt = pendingPrompt;
|
|
1176
|
+
pendingPrompt = undefined;
|
|
1177
|
+
if (currentPrompt) {
|
|
1178
|
+
promptContent = currentPrompt.content;
|
|
1179
|
+
const msgId = currentPrompt.msgId;
|
|
1180
|
+
const promptGrammar = currentPrompt.grammar;
|
|
1181
|
+
const persistedUserLanguageCode = currentPrompt.userLanguageCode ?? dlg.getLastUserLanguageCode();
|
|
1182
|
+
await dlg.addChatMessages({
|
|
1183
|
+
type: 'prompting_msg',
|
|
1184
|
+
role: 'user',
|
|
1185
|
+
genseq: dlg.activeGenSeq,
|
|
1186
|
+
content: promptContent,
|
|
1187
|
+
msgId: msgId,
|
|
1188
|
+
grammar: promptGrammar,
|
|
1189
|
+
});
|
|
1190
|
+
// Persist user message to storage FIRST
|
|
1191
|
+
await dlg.persistUserMessage(promptContent, msgId, promptGrammar, persistedUserLanguageCode);
|
|
1192
|
+
if (promptGrammar === 'tellask') {
|
|
1193
|
+
// Collect and execute tellask calls from user text using streaming parser
|
|
1194
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1195
|
+
const collectedUserCalls = await emitSayingEvents(dlg, promptContent);
|
|
1196
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1197
|
+
const userResult = await executeTellaskCalls(dlg, agent, collectedUserCalls);
|
|
1198
|
+
if (dlg.hasUpNext()) {
|
|
1199
|
+
return lastAssistantSayingContent;
|
|
1200
|
+
}
|
|
1201
|
+
if (userResult.toolOutputs.length > 0) {
|
|
1202
|
+
await dlg.addChatMessages(...userResult.toolOutputs);
|
|
1203
|
+
}
|
|
1204
|
+
if (userResult.suspend) {
|
|
1205
|
+
suspendForHuman = true;
|
|
1206
|
+
}
|
|
1207
|
+
// No teammate-call fallback here: rely exclusively on TellaskStreamParser.
|
|
1208
|
+
// Pending subdialogs are tracked in persistence (pending-subdialogs.json) as the source of truth.
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
await emitUserMarkdown(dlg, promptContent);
|
|
1212
|
+
}
|
|
1213
|
+
try {
|
|
1214
|
+
(0, evt_registry_1.postDialogEvent)(dlg, {
|
|
1215
|
+
type: 'end_of_user_saying_evt',
|
|
1216
|
+
round: dlg.currentRound,
|
|
1217
|
+
genseq: dlg.activeGenSeq,
|
|
1218
|
+
msgId,
|
|
1219
|
+
content: promptContent,
|
|
1220
|
+
grammar: promptGrammar,
|
|
1221
|
+
userLanguageCode: persistedUserLanguageCode,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
catch (err) {
|
|
1225
|
+
log_1.log.warn('Failed to emit end_of_user_saying_evt', err);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (suspendForHuman) {
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
// Take any queued subdialog responses (once per drive) and inject them as fresh user context.
|
|
1232
|
+
// This is the core "revival" mechanism: the parent is driven again when all pending subdialogs
|
|
1233
|
+
// are resolved, and the queued responses become the next user-visible input to the model.
|
|
1234
|
+
if (genIterNo === 1 && !tookSubdialogResponses) {
|
|
1235
|
+
tookSubdialogResponses = true;
|
|
1236
|
+
try {
|
|
1237
|
+
takenSubdialogResponses = await withSuspensionStateLock(dlg.id, async () => {
|
|
1238
|
+
return await persistence_1.DialogPersistence.takeSubdialogResponses(dlg.id);
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
catch (err) {
|
|
1242
|
+
log_1.log.warn('Failed to take subdialog responses for injection', {
|
|
1243
|
+
dialogId: dlg.id.selfId,
|
|
1244
|
+
error: err,
|
|
1245
|
+
});
|
|
1246
|
+
generationHadError = true;
|
|
1247
|
+
takenSubdialogResponses = [];
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
// use fresh memory + updated msgs from dialog object
|
|
1251
|
+
// Build ctxMsgs messages in logical order, then inject reminders as late as possible:
|
|
1252
|
+
// 1) memories
|
|
1253
|
+
// 2) task doc (user)
|
|
1254
|
+
// 3) historical dialog msgs
|
|
1255
|
+
// Finally, render reminders and place them immediately before the last 'user' message
|
|
1256
|
+
// so they are salient for the next response without polluting earlier context.
|
|
1257
|
+
const taskDocMsg = dlg.taskDocPath
|
|
1258
|
+
? await (0, taskdoc_1.formatTaskDocContent)(dlg)
|
|
1259
|
+
: undefined;
|
|
1260
|
+
const ctxMsgs = [
|
|
1261
|
+
...memories,
|
|
1262
|
+
...(taskDocMsg ? [taskDocMsg] : []),
|
|
1263
|
+
...dlg.msgs,
|
|
1264
|
+
];
|
|
1265
|
+
if (genIterNo === 1 && takenSubdialogResponses.length > 0) {
|
|
1266
|
+
for (const response of takenSubdialogResponses) {
|
|
1267
|
+
ctxMsgs.push({
|
|
1268
|
+
type: 'environment_msg',
|
|
1269
|
+
role: 'user',
|
|
1270
|
+
content: (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
1271
|
+
responderId: response.responderId,
|
|
1272
|
+
requesterId: response.originMemberId,
|
|
1273
|
+
originalCallHeadLine: response.headLine,
|
|
1274
|
+
responseBody: response.response,
|
|
1275
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
1276
|
+
}),
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
await dlg.processReminderUpdates();
|
|
1281
|
+
const renderedReminders = dlg.reminders.length > 0
|
|
1282
|
+
? await Promise.all(dlg.reminders.map(async (reminder, index) => {
|
|
1283
|
+
if (reminder.owner) {
|
|
1284
|
+
return await reminder.owner.renderReminder(dlg, reminder, index);
|
|
1285
|
+
}
|
|
1286
|
+
return {
|
|
1287
|
+
type: 'environment_msg',
|
|
1288
|
+
role: 'user',
|
|
1289
|
+
content: (0, driver_messages_1.formatReminderItemGuide)((0, runtime_language_1.getWorkLanguage)(), index + 1, reminder.content),
|
|
1290
|
+
};
|
|
1291
|
+
}))
|
|
1292
|
+
: [];
|
|
1293
|
+
if (renderedReminders.length > 0) {
|
|
1294
|
+
let insertIndex = -1;
|
|
1295
|
+
for (let i = ctxMsgs.length - 1; i >= 0; i--) {
|
|
1296
|
+
const m = ctxMsgs[i];
|
|
1297
|
+
if (m &&
|
|
1298
|
+
(m.type === 'prompting_msg' || m.type === 'environment_msg') &&
|
|
1299
|
+
m.role === 'user') {
|
|
1300
|
+
insertIndex = i;
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (insertIndex >= 0) {
|
|
1305
|
+
ctxMsgs.splice(insertIndex, 0, ...renderedReminders);
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
ctxMsgs.push(...renderedReminders);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
{
|
|
1312
|
+
const uiLanguage = dlg.getLastUserLanguageCode();
|
|
1313
|
+
const workingLanguage = (0, runtime_language_1.getWorkLanguage)();
|
|
1314
|
+
const guideMsg = {
|
|
1315
|
+
type: 'transient_guide_msg',
|
|
1316
|
+
role: 'assistant',
|
|
1317
|
+
content: (0, driver_messages_1.formatUserFacingLanguageGuide)(workingLanguage, uiLanguage),
|
|
1318
|
+
};
|
|
1319
|
+
let insertIndex = -1;
|
|
1320
|
+
for (let i = ctxMsgs.length - 1; i >= 0; i--) {
|
|
1321
|
+
const m = ctxMsgs[i];
|
|
1322
|
+
if (m &&
|
|
1323
|
+
(m.type === 'prompting_msg' || m.type === 'environment_msg') &&
|
|
1324
|
+
m.role === 'user') {
|
|
1325
|
+
insertIndex = i;
|
|
1326
|
+
break;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (insertIndex >= 0) {
|
|
1330
|
+
ctxMsgs.splice(insertIndex, 0, guideMsg);
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
ctxMsgs.push(guideMsg);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const remediation = await applyContextHealthV3Remediation({
|
|
1337
|
+
dlg,
|
|
1338
|
+
agent,
|
|
1339
|
+
agentTools,
|
|
1340
|
+
providerCfg,
|
|
1341
|
+
provider,
|
|
1342
|
+
systemPrompt,
|
|
1343
|
+
funcTools,
|
|
1344
|
+
ctxMsgs,
|
|
1345
|
+
llmGen,
|
|
1346
|
+
abortSignal,
|
|
1347
|
+
model,
|
|
1348
|
+
hadUserPromptThisGen: currentPrompt !== undefined,
|
|
1349
|
+
});
|
|
1350
|
+
if (remediation.kind === 'continue') {
|
|
1351
|
+
if (remediation.contextHealthForGen) {
|
|
1352
|
+
contextHealthForGen = remediation.contextHealthForGen;
|
|
1353
|
+
}
|
|
1354
|
+
pendingPrompt = remediation.nextPrompt;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (remediation.kind === 'suspend') {
|
|
1358
|
+
if (remediation.contextHealthForGen) {
|
|
1359
|
+
contextHealthForGen = remediation.contextHealthForGen;
|
|
1360
|
+
}
|
|
1361
|
+
suspendForHuman = true;
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
const ctxMsgsForGen = remediation.ctxMsgs;
|
|
1365
|
+
if (agent.streaming === false) {
|
|
1366
|
+
let nonStreamResult;
|
|
1367
|
+
try {
|
|
1368
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1369
|
+
nonStreamResult = await runLlmRequestWithRetry({
|
|
1370
|
+
dlg,
|
|
1371
|
+
provider,
|
|
1372
|
+
abortSignal,
|
|
1373
|
+
maxRetries: 5,
|
|
1374
|
+
canRetry: () => true,
|
|
1375
|
+
doRequest: async () => {
|
|
1376
|
+
return await llmGen.genMoreMessages(providerCfg, agent, systemPrompt, funcTools, ctxMsgsForGen, dlg.activeGenSeq, abortSignal);
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
catch (err) {
|
|
1381
|
+
if (abortSignal.aborted) {
|
|
1382
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1383
|
+
}
|
|
1384
|
+
generationHadError = true;
|
|
1385
|
+
throw err;
|
|
1386
|
+
}
|
|
1387
|
+
if (typeof nonStreamResult.llmGenModel === 'string' &&
|
|
1388
|
+
nonStreamResult.llmGenModel.trim() !== '') {
|
|
1389
|
+
llmGenModelForGen = nonStreamResult.llmGenModel.trim();
|
|
1390
|
+
}
|
|
1391
|
+
if (!agent.model) {
|
|
1392
|
+
throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
|
|
1393
|
+
}
|
|
1394
|
+
contextHealthForGen = computeContextHealthSnapshot({
|
|
1395
|
+
providerCfg,
|
|
1396
|
+
model: agent.model,
|
|
1397
|
+
usage: nonStreamResult.usage,
|
|
1398
|
+
});
|
|
1399
|
+
dlg.setLastContextHealth(contextHealthForGen);
|
|
1400
|
+
const nonStreamMsgs = nonStreamResult.messages;
|
|
1401
|
+
const assistantMsgs = nonStreamMsgs.filter((m) => m.type === 'saying_msg' || m.type === 'thinking_msg');
|
|
1402
|
+
const collectedAssistantCalls = [];
|
|
1403
|
+
if (assistantMsgs.length > 0) {
|
|
1404
|
+
await dlg.addChatMessages(...assistantMsgs);
|
|
1405
|
+
for (const msg of assistantMsgs) {
|
|
1406
|
+
if (msg.role === 'assistant' &&
|
|
1407
|
+
msg.genseq !== undefined &&
|
|
1408
|
+
(msg.type === 'thinking_msg' || msg.type === 'saying_msg')) {
|
|
1409
|
+
// Only persist saying_msg - thinking_msg is persisted via thinkingFinish
|
|
1410
|
+
if (msg.type === 'saying_msg') {
|
|
1411
|
+
lastAssistantSayingContent = msg.content;
|
|
1412
|
+
await dlg.persistAgentMessage(msg.content, msg.genseq, 'saying_msg');
|
|
1413
|
+
}
|
|
1414
|
+
// Emit thinking events using shared handler (non-streaming mode)
|
|
1415
|
+
if (msg.type === 'thinking_msg') {
|
|
1416
|
+
await emitThinkingEvents(dlg, msg.content);
|
|
1417
|
+
}
|
|
1418
|
+
// Emit saying events using shared TellaskStreamParser integration
|
|
1419
|
+
if (msg.type === 'saying_msg') {
|
|
1420
|
+
const calls = await emitSayingEvents(dlg, msg.content);
|
|
1421
|
+
collectedAssistantCalls.push(...calls);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
let assistantToolOutputsCount = 0;
|
|
1427
|
+
if (collectedAssistantCalls.length > 0) {
|
|
1428
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1429
|
+
const assistantResult = await executeTellaskCalls(dlg, agent, collectedAssistantCalls);
|
|
1430
|
+
if (dlg.hasUpNext()) {
|
|
1431
|
+
return lastAssistantSayingContent;
|
|
1432
|
+
}
|
|
1433
|
+
assistantToolOutputsCount = assistantResult.toolOutputs.length;
|
|
1434
|
+
if (assistantResult.toolOutputs.length > 0) {
|
|
1435
|
+
await dlg.addChatMessages(...assistantResult.toolOutputs);
|
|
1436
|
+
}
|
|
1437
|
+
if (assistantResult.suspend) {
|
|
1438
|
+
suspendForHuman = true;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const funcCalls = nonStreamMsgs.filter((m) => m.type === 'func_call_msg');
|
|
1442
|
+
const funcResults = [];
|
|
1443
|
+
const functionPromises = funcCalls.map(async (func) => {
|
|
1444
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1445
|
+
// Use the genseq from the func_call_msg to ensure tool results share the same generation sequence
|
|
1446
|
+
// This is critical for correct grouping in reconstructAnthropicContext()
|
|
1447
|
+
const callGenseq = func.genseq;
|
|
1448
|
+
// Use the LLM-allocated unique id for tracking
|
|
1449
|
+
// This id comes from func_call_msg and is the proper unique identifier
|
|
1450
|
+
const callId = func.id;
|
|
1451
|
+
// argsStr is still needed for UI event (funcCallRequested)
|
|
1452
|
+
const argsStr = typeof func.arguments === 'string'
|
|
1453
|
+
? func.arguments
|
|
1454
|
+
: JSON.stringify(func.arguments ?? {});
|
|
1455
|
+
const tool = agentTools.find((t) => t.type === 'func' && t.name === func.name);
|
|
1456
|
+
if (!tool) {
|
|
1457
|
+
const errorResult = {
|
|
1458
|
+
type: 'func_result_msg',
|
|
1459
|
+
id: func.id,
|
|
1460
|
+
name: func.name,
|
|
1461
|
+
content: `Tool '${func.name}' not found`,
|
|
1462
|
+
role: 'tool',
|
|
1463
|
+
genseq: callGenseq,
|
|
1464
|
+
};
|
|
1465
|
+
await dlg.receiveFuncResult(errorResult);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
let rawArgs = {};
|
|
1469
|
+
if (typeof func.arguments === 'string' && func.arguments.trim()) {
|
|
1470
|
+
try {
|
|
1471
|
+
rawArgs = JSON.parse(func.arguments);
|
|
1472
|
+
}
|
|
1473
|
+
catch (parseErr) {
|
|
1474
|
+
rawArgs = null;
|
|
1475
|
+
log_1.log.warn('Failed to parse function arguments as JSON', {
|
|
1476
|
+
funcName: func.name,
|
|
1477
|
+
arguments: func.arguments,
|
|
1478
|
+
error: parseErr,
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
let result;
|
|
1483
|
+
const argsValidation = validateFuncToolArguments(tool, rawArgs);
|
|
1484
|
+
if (argsValidation.ok) {
|
|
1485
|
+
const argsObj = argsValidation.args;
|
|
1486
|
+
// Emit func_call_requested event to build the func-call section UI
|
|
1487
|
+
try {
|
|
1488
|
+
await dlg.funcCallRequested(func.id, func.name, argsStr);
|
|
1489
|
+
}
|
|
1490
|
+
catch (err) {
|
|
1491
|
+
log_1.log.warn('Failed to emit func_call_requested event', err);
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
await dlg.persistFunctionCall(func.id, func.name, argsObj, callGenseq);
|
|
1495
|
+
}
|
|
1496
|
+
catch (err) {
|
|
1497
|
+
log_1.log.warn('Failed to persist function call', err);
|
|
1498
|
+
}
|
|
1499
|
+
try {
|
|
1500
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1501
|
+
const content = await tool.call(dlg, agent, argsObj);
|
|
1502
|
+
result = {
|
|
1503
|
+
type: 'func_result_msg',
|
|
1504
|
+
id: func.id,
|
|
1505
|
+
name: func.name,
|
|
1506
|
+
content: String(content),
|
|
1507
|
+
role: 'tool',
|
|
1508
|
+
genseq: callGenseq,
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
catch (err) {
|
|
1512
|
+
result = {
|
|
1513
|
+
type: 'func_result_msg',
|
|
1514
|
+
id: func.id,
|
|
1515
|
+
name: func.name,
|
|
1516
|
+
content: `Function '${func.name}' execution failed: ${showErrorToAi(err)}`,
|
|
1517
|
+
role: 'tool',
|
|
1518
|
+
genseq: callGenseq,
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
result = {
|
|
1524
|
+
type: 'func_result_msg',
|
|
1525
|
+
id: func.id,
|
|
1526
|
+
name: func.name,
|
|
1527
|
+
content: `Invalid arguments: ${argsValidation.error}`,
|
|
1528
|
+
role: 'tool',
|
|
1529
|
+
genseq: callGenseq,
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
await dlg.receiveFuncResult(result);
|
|
1533
|
+
funcResults.push(result);
|
|
1534
|
+
});
|
|
1535
|
+
const allFuncResults = await Promise.all(functionPromises);
|
|
1536
|
+
await Promise.resolve();
|
|
1537
|
+
// Add function calls AND results to dialog messages so LLM sees tool context in next iteration
|
|
1538
|
+
// Both are needed: func_call_msg for the tool definition, func_result_msg for the output
|
|
1539
|
+
if (funcCalls.length > 0) {
|
|
1540
|
+
await dlg.addChatMessages(...funcCalls);
|
|
1541
|
+
}
|
|
1542
|
+
if (funcResults.length > 0) {
|
|
1543
|
+
await dlg.addChatMessages(...funcResults);
|
|
1544
|
+
}
|
|
1545
|
+
if (dlg.hasUpNext()) {
|
|
1546
|
+
pendingPrompt = resolveUpNextPrompt(dlg);
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1549
|
+
if (suspendForHuman) {
|
|
1550
|
+
try {
|
|
1551
|
+
// Q4H suspension resets keep-going budget so post-Q4H continuation gets a fresh counter.
|
|
1552
|
+
if (await dlg.hasPendingQ4H()) {
|
|
1553
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
catch (err) {
|
|
1557
|
+
log_1.log.warn('Failed to check Q4H state for keep-going reset', err, {
|
|
1558
|
+
dialogId: dlg.id.valueOf(),
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
// Continue when there is any tool output / function output that requires another iteration.
|
|
1564
|
+
const shouldContinue = funcCalls.length > 0 ||
|
|
1565
|
+
assistantToolOutputsCount > 0 ||
|
|
1566
|
+
(funcResults.length > 0 && funcCalls.length === 0);
|
|
1567
|
+
if (!shouldContinue) {
|
|
1568
|
+
// Keep-going (root dialog only): prevent ALL stopping except legitimate suspension.
|
|
1569
|
+
// If disabled (empty diligence file) or budget exhausted, we suspend via Q4H.
|
|
1570
|
+
if (dlg instanceof dialog_1.RootDialog) {
|
|
1571
|
+
const suspension = await dlg.getSuspensionStatus();
|
|
1572
|
+
if (!suspension.canDrive) {
|
|
1573
|
+
if (suspension.q4h) {
|
|
1574
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1575
|
+
}
|
|
1576
|
+
break;
|
|
1577
|
+
}
|
|
1578
|
+
const prepared = await maybePrepareDiligenceAutoContinuePrompt({
|
|
1579
|
+
dlg,
|
|
1580
|
+
isRootDialog: true,
|
|
1581
|
+
alreadyInjectedCount: dlg.diligenceAutoContinueCount,
|
|
1582
|
+
diligencePushMax: resolveMemberDiligencePushMax(team, dlg.agentId),
|
|
1583
|
+
});
|
|
1584
|
+
dlg.diligenceAutoContinueCount = prepared.nextInjectedCount;
|
|
1585
|
+
if (prepared.kind !== 'disabled') {
|
|
1586
|
+
(0, evt_registry_1.postDialogEvent)(dlg, {
|
|
1587
|
+
type: 'diligence_budget_evt',
|
|
1588
|
+
maxInjectCount: prepared.maxInjectCount,
|
|
1589
|
+
injectedCount: prepared.nextInjectedCount,
|
|
1590
|
+
remainingCount: Math.max(0, prepared.maxInjectCount - prepared.nextInjectedCount),
|
|
1591
|
+
disableDiligencePush: dlg.disableDiligencePush,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
if (prepared.kind === 'budget_exhausted') {
|
|
1595
|
+
await suspendForKeepGoingBudgetExhausted({
|
|
1596
|
+
dlg,
|
|
1597
|
+
maxInjectCount: prepared.maxInjectCount,
|
|
1598
|
+
});
|
|
1599
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
if (prepared.kind === 'prompt') {
|
|
1603
|
+
pendingPrompt = prepared.prompt;
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
else {
|
|
1612
|
+
const newMsgs = [];
|
|
1613
|
+
const streamedFuncCalls = [];
|
|
1614
|
+
// Track thinking content for signature extraction during streaming
|
|
1615
|
+
let currentThinkingContent = '';
|
|
1616
|
+
let currentThinkingSignature = '';
|
|
1617
|
+
let currentSayingContent = '';
|
|
1618
|
+
let sawAnyStreamContent = false;
|
|
1619
|
+
// Create receiver using shared helper (unified TellaskStreamParser integration)
|
|
1620
|
+
const receiver = createSayingEventsReceiver(dlg);
|
|
1621
|
+
// Direct streaming parser that forwards events without state tracking
|
|
1622
|
+
const parser = new tellask_1.TellaskStreamParser(receiver);
|
|
1623
|
+
let streamResult;
|
|
1624
|
+
try {
|
|
1625
|
+
streamResult = await runLlmRequestWithRetry({
|
|
1626
|
+
dlg,
|
|
1627
|
+
provider,
|
|
1628
|
+
abortSignal,
|
|
1629
|
+
maxRetries: 5,
|
|
1630
|
+
canRetry: () => !sawAnyStreamContent,
|
|
1631
|
+
doRequest: async () => {
|
|
1632
|
+
return await llmGen.genToReceiver(providerCfg, agent, systemPrompt, funcTools, ctxMsgsForGen, {
|
|
1633
|
+
thinkingStart: async () => {
|
|
1634
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1635
|
+
sawAnyStreamContent = true;
|
|
1636
|
+
currentThinkingContent = '';
|
|
1637
|
+
currentThinkingSignature = '';
|
|
1638
|
+
await dlg.thinkingStart();
|
|
1639
|
+
},
|
|
1640
|
+
thinkingChunk: async (chunk) => {
|
|
1641
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1642
|
+
sawAnyStreamContent = true;
|
|
1643
|
+
currentThinkingContent += chunk;
|
|
1644
|
+
// Extract Anthropic thinking signature from content
|
|
1645
|
+
const signatureMatch = currentThinkingContent.match(/<thinking[^>]*>(.*?)<\/thinking>/s);
|
|
1646
|
+
if (signatureMatch && signatureMatch[1]) {
|
|
1647
|
+
currentThinkingSignature = signatureMatch[1].trim();
|
|
1648
|
+
}
|
|
1649
|
+
await dlg.thinkingChunk(chunk);
|
|
1650
|
+
},
|
|
1651
|
+
thinkingFinish: async () => {
|
|
1652
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1653
|
+
// Create thinking message with genseq and signature
|
|
1654
|
+
const genseq = dlg.activeGenSeq;
|
|
1655
|
+
if (genseq) {
|
|
1656
|
+
const thinkingMessage = {
|
|
1657
|
+
type: 'thinking_msg',
|
|
1658
|
+
role: 'assistant',
|
|
1659
|
+
genseq,
|
|
1660
|
+
content: currentThinkingContent,
|
|
1661
|
+
provider_data: currentThinkingSignature
|
|
1662
|
+
? { signature: currentThinkingSignature }
|
|
1663
|
+
: undefined,
|
|
1664
|
+
};
|
|
1665
|
+
newMsgs.push(thinkingMessage);
|
|
1666
|
+
}
|
|
1667
|
+
await dlg.thinkingFinish();
|
|
1668
|
+
},
|
|
1669
|
+
sayingStart: async () => {
|
|
1670
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1671
|
+
sawAnyStreamContent = true;
|
|
1672
|
+
currentSayingContent = '';
|
|
1673
|
+
await dlg.sayingStart();
|
|
1674
|
+
},
|
|
1675
|
+
sayingChunk: async (chunk) => {
|
|
1676
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1677
|
+
sawAnyStreamContent = true;
|
|
1678
|
+
currentSayingContent += chunk;
|
|
1679
|
+
await parser.takeUpstreamChunk(chunk);
|
|
1680
|
+
// Dialog store handles persistence - maintain ordering guarantee
|
|
1681
|
+
await dlg.sayingChunk(chunk);
|
|
1682
|
+
},
|
|
1683
|
+
sayingFinish: async () => {
|
|
1684
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1685
|
+
await parser.finalize();
|
|
1686
|
+
const sayingMessage = {
|
|
1687
|
+
type: 'saying_msg',
|
|
1688
|
+
role: 'assistant',
|
|
1689
|
+
genseq: dlg.activeGenSeq,
|
|
1690
|
+
content: currentSayingContent,
|
|
1691
|
+
};
|
|
1692
|
+
newMsgs.push(sayingMessage);
|
|
1693
|
+
lastAssistantSayingContent = currentSayingContent;
|
|
1694
|
+
await dlg.sayingFinish();
|
|
1695
|
+
},
|
|
1696
|
+
funcCall: async (callId, name, args) => {
|
|
1697
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1698
|
+
sawAnyStreamContent = true;
|
|
1699
|
+
const genseq = dlg.activeGenSeq;
|
|
1700
|
+
if (genseq === undefined) {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
streamedFuncCalls.push({
|
|
1704
|
+
type: 'func_call_msg',
|
|
1705
|
+
role: 'assistant',
|
|
1706
|
+
genseq,
|
|
1707
|
+
id: callId,
|
|
1708
|
+
name,
|
|
1709
|
+
arguments: args,
|
|
1710
|
+
});
|
|
1711
|
+
},
|
|
1712
|
+
}, dlg.activeGenSeq, abortSignal);
|
|
1713
|
+
},
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
catch (err) {
|
|
1717
|
+
if (abortSignal.aborted) {
|
|
1718
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1719
|
+
}
|
|
1720
|
+
generationHadError = true;
|
|
1721
|
+
log_1.log.error(`LLM gen error:`, err);
|
|
1722
|
+
throw err;
|
|
1723
|
+
}
|
|
1724
|
+
if (!streamResult) {
|
|
1725
|
+
throw new Error('Internal error: missing stream result after successful generation');
|
|
1726
|
+
}
|
|
1727
|
+
if (typeof streamResult.llmGenModel === 'string' &&
|
|
1728
|
+
streamResult.llmGenModel.trim() !== '') {
|
|
1729
|
+
llmGenModelForGen = streamResult.llmGenModel.trim();
|
|
1730
|
+
}
|
|
1731
|
+
if (!agent.model) {
|
|
1732
|
+
throw new Error(`Internal error: Model is undefined for agent '${agent.id}'`);
|
|
1733
|
+
}
|
|
1734
|
+
contextHealthForGen = computeContextHealthSnapshot({
|
|
1735
|
+
providerCfg,
|
|
1736
|
+
model: agent.model,
|
|
1737
|
+
usage: streamResult.usage,
|
|
1738
|
+
});
|
|
1739
|
+
dlg.setLastContextHealth(contextHealthForGen);
|
|
1740
|
+
// Execute collected calls concurrently after streaming completes
|
|
1741
|
+
const collectedCalls = parser.getCollectedCalls();
|
|
1742
|
+
const malformedToolOutputs = await emitMalformedTellaskResponses(dlg, collectedCalls);
|
|
1743
|
+
if (collectedCalls.length > 0 && !collectedCalls[0].callId) {
|
|
1744
|
+
throw new Error('Collected calls missing callId - parser should have allocated one per call');
|
|
1745
|
+
}
|
|
1746
|
+
const validCalls = collectedCalls.filter((call) => call.validation.kind === 'valid');
|
|
1747
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1748
|
+
const results = await Promise.all(validCalls.map((call) => executeTellaskCall(dlg, agent, call.validation.firstMention, call.headLine, call.body, call.callId)));
|
|
1749
|
+
if (dlg.hasUpNext()) {
|
|
1750
|
+
return lastAssistantSayingContent;
|
|
1751
|
+
}
|
|
1752
|
+
// Combine results from all concurrent calls and track tool outputs for termination logic
|
|
1753
|
+
let toolOutputsCount = 0;
|
|
1754
|
+
if (malformedToolOutputs.length > 0) {
|
|
1755
|
+
toolOutputsCount += malformedToolOutputs.length;
|
|
1756
|
+
newMsgs.push(...malformedToolOutputs);
|
|
1757
|
+
}
|
|
1758
|
+
for (const result of results) {
|
|
1759
|
+
if (result.toolOutputs.length > 0) {
|
|
1760
|
+
toolOutputsCount += result.toolOutputs.length;
|
|
1761
|
+
newMsgs.push(...result.toolOutputs);
|
|
1762
|
+
}
|
|
1763
|
+
if (result.suspend) {
|
|
1764
|
+
suspendForHuman = true;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
const funcResults = [];
|
|
1768
|
+
if (streamedFuncCalls.length > 0) {
|
|
1769
|
+
const functionPromises = streamedFuncCalls.map(async (func) => {
|
|
1770
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1771
|
+
// Use the genseq from the func_call_msg to ensure tool results share the same generation sequence
|
|
1772
|
+
// This is critical for correct grouping in reconstructAnthropicContext()
|
|
1773
|
+
const callGenseq = func.genseq;
|
|
1774
|
+
// Use the LLM-allocated unique id for tracking
|
|
1775
|
+
// This id comes from func_call_msg and is the proper unique identifier
|
|
1776
|
+
const callId = func.id;
|
|
1777
|
+
// argsStr is still needed for UI event (funcCallRequested)
|
|
1778
|
+
const argsStr = typeof func.arguments === 'string'
|
|
1779
|
+
? func.arguments
|
|
1780
|
+
: JSON.stringify(func.arguments ?? {});
|
|
1781
|
+
const tool = agentTools.find((t) => t.type === 'func' && t.name === func.name);
|
|
1782
|
+
if (!tool) {
|
|
1783
|
+
const errorResult = {
|
|
1784
|
+
type: 'func_result_msg',
|
|
1785
|
+
id: func.id,
|
|
1786
|
+
name: func.name,
|
|
1787
|
+
content: `Tool '${func.name}' not found`,
|
|
1788
|
+
role: 'tool',
|
|
1789
|
+
genseq: callGenseq,
|
|
1790
|
+
};
|
|
1791
|
+
await dlg.receiveFuncResult(errorResult);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
let rawArgs = {};
|
|
1795
|
+
if (typeof func.arguments === 'string' && func.arguments.trim()) {
|
|
1796
|
+
try {
|
|
1797
|
+
rawArgs = JSON.parse(func.arguments);
|
|
1798
|
+
}
|
|
1799
|
+
catch (parseErr) {
|
|
1800
|
+
rawArgs = null;
|
|
1801
|
+
log_1.log.warn('Failed to parse function arguments as JSON', {
|
|
1802
|
+
funcName: func.name,
|
|
1803
|
+
arguments: func.arguments,
|
|
1804
|
+
error: parseErr,
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
let result;
|
|
1809
|
+
const argsValidation = validateFuncToolArguments(tool, rawArgs);
|
|
1810
|
+
if (argsValidation.ok) {
|
|
1811
|
+
const argsObj = argsValidation.args;
|
|
1812
|
+
// Emit func_call_requested event to build the func-call section UI
|
|
1813
|
+
try {
|
|
1814
|
+
await dlg.funcCallRequested(func.id, func.name, argsStr);
|
|
1815
|
+
}
|
|
1816
|
+
catch (err) {
|
|
1817
|
+
log_1.log.warn('Failed to emit func_call_requested event', err);
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
await dlg.persistFunctionCall(func.id, func.name, argsObj, callGenseq);
|
|
1821
|
+
}
|
|
1822
|
+
catch (err) {
|
|
1823
|
+
log_1.log.warn('Failed to persist function call', err);
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
throwIfAborted(abortSignal, dlg.id);
|
|
1827
|
+
const content = await tool.call(dlg, agent, argsObj);
|
|
1828
|
+
result = {
|
|
1829
|
+
type: 'func_result_msg',
|
|
1830
|
+
id: func.id,
|
|
1831
|
+
name: func.name,
|
|
1832
|
+
content: String(content),
|
|
1833
|
+
role: 'tool',
|
|
1834
|
+
genseq: callGenseq,
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
catch (err) {
|
|
1838
|
+
result = {
|
|
1839
|
+
type: 'func_result_msg',
|
|
1840
|
+
id: func.id,
|
|
1841
|
+
name: func.name,
|
|
1842
|
+
content: `Function '${func.name}' execution failed: ${showErrorToAi(err)}`,
|
|
1843
|
+
role: 'tool',
|
|
1844
|
+
genseq: callGenseq,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
result = {
|
|
1850
|
+
type: 'func_result_msg',
|
|
1851
|
+
id: func.id,
|
|
1852
|
+
name: func.name,
|
|
1853
|
+
content: `Invalid arguments: ${argsValidation.error}`,
|
|
1854
|
+
role: 'tool',
|
|
1855
|
+
genseq: callGenseq,
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
await dlg.receiveFuncResult(result);
|
|
1859
|
+
funcResults.push(result);
|
|
1860
|
+
});
|
|
1861
|
+
await Promise.all(functionPromises);
|
|
1862
|
+
}
|
|
1863
|
+
if (streamedFuncCalls.length > 0) {
|
|
1864
|
+
newMsgs.push(...streamedFuncCalls);
|
|
1865
|
+
}
|
|
1866
|
+
if (funcResults.length > 0) {
|
|
1867
|
+
newMsgs.push(...funcResults);
|
|
1868
|
+
}
|
|
1869
|
+
await dlg.addChatMessages(...newMsgs);
|
|
1870
|
+
if (dlg.hasUpNext()) {
|
|
1871
|
+
pendingPrompt = resolveUpNextPrompt(dlg);
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
// After tool execution, check latest remindersVer with published info,
|
|
1875
|
+
// publish new version to propagate updated reminders to ui
|
|
1876
|
+
if (dlg.remindersVer > pubRemindersVer) {
|
|
1877
|
+
try {
|
|
1878
|
+
await dlg.processReminderUpdates();
|
|
1879
|
+
pubRemindersVer = dlg.remindersVer;
|
|
1880
|
+
}
|
|
1881
|
+
catch (err) {
|
|
1882
|
+
log_1.log.warn('Failed to propagate reminder text after tools', err);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
await Promise.resolve();
|
|
1886
|
+
if (suspendForHuman) {
|
|
1887
|
+
try {
|
|
1888
|
+
// Q4H suspension resets keep-going budget so post-Q4H continuation gets a fresh counter.
|
|
1889
|
+
if (await dlg.hasPendingQ4H()) {
|
|
1890
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
catch (err) {
|
|
1894
|
+
log_1.log.warn('Failed to check Q4H state for keep-going reset', err, {
|
|
1895
|
+
dialogId: dlg.id.valueOf(),
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
const shouldContinue = toolOutputsCount > 0 || streamedFuncCalls.length > 0 || funcResults.length > 0;
|
|
1901
|
+
if (!shouldContinue) {
|
|
1902
|
+
// Keep-going (root dialog only): prevent ALL stopping except legitimate suspension.
|
|
1903
|
+
if (dlg instanceof dialog_1.RootDialog) {
|
|
1904
|
+
const suspension = await dlg.getSuspensionStatus();
|
|
1905
|
+
if (!suspension.canDrive) {
|
|
1906
|
+
if (suspension.q4h) {
|
|
1907
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1908
|
+
}
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
const prepared = await maybePrepareDiligenceAutoContinuePrompt({
|
|
1912
|
+
dlg,
|
|
1913
|
+
isRootDialog: true,
|
|
1914
|
+
alreadyInjectedCount: dlg.diligenceAutoContinueCount,
|
|
1915
|
+
diligencePushMax: resolveMemberDiligencePushMax(team, dlg.agentId),
|
|
1916
|
+
});
|
|
1917
|
+
dlg.diligenceAutoContinueCount = prepared.nextInjectedCount;
|
|
1918
|
+
if (prepared.kind === 'budget_exhausted') {
|
|
1919
|
+
await suspendForKeepGoingBudgetExhausted({
|
|
1920
|
+
dlg,
|
|
1921
|
+
maxInjectCount: prepared.maxInjectCount,
|
|
1922
|
+
});
|
|
1923
|
+
dlg.diligenceAutoContinueCount = 0;
|
|
1924
|
+
break;
|
|
1925
|
+
}
|
|
1926
|
+
if (prepared.kind === 'prompt') {
|
|
1927
|
+
pendingPrompt = prepared.prompt;
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
finally {
|
|
1936
|
+
await dlg.notifyGeneratingFinish(contextHealthForGen, llmGenModelForGen);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
finalRunState = await (0, dialog_run_state_1.computeIdleRunState)(dlg);
|
|
1940
|
+
return lastAssistantSayingContent;
|
|
1941
|
+
}
|
|
1942
|
+
catch (err) {
|
|
1943
|
+
const stopRequested = (0, dialog_run_state_1.getStopRequestedReason)(dlg.id);
|
|
1944
|
+
const interruptedReason = err instanceof DialogInterruptedError
|
|
1945
|
+
? err.reason
|
|
1946
|
+
: abortSignal.aborted
|
|
1947
|
+
? stopRequested === 'emergency_stop'
|
|
1948
|
+
? { kind: 'emergency_stop' }
|
|
1949
|
+
: stopRequested === 'user_stop'
|
|
1950
|
+
? { kind: 'user_stop' }
|
|
1951
|
+
: { kind: 'system_stop', detail: 'Aborted.' }
|
|
1952
|
+
: undefined;
|
|
1953
|
+
if (interruptedReason) {
|
|
1954
|
+
finalRunState = { kind: 'interrupted', reason: interruptedReason };
|
|
1955
|
+
(0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, { kind: 'interrupted', reason: interruptedReason });
|
|
1956
|
+
return null;
|
|
1957
|
+
}
|
|
1958
|
+
generationHadError = true;
|
|
1959
|
+
const errText = (0, log_1.extractErrorDetails)(err).message;
|
|
1960
|
+
try {
|
|
1961
|
+
await dlg.streamError(errText);
|
|
1962
|
+
}
|
|
1963
|
+
catch (_emitErr) {
|
|
1964
|
+
// best-effort
|
|
1965
|
+
}
|
|
1966
|
+
finalRunState = { kind: 'interrupted', reason: { kind: 'system_stop', detail: errText } };
|
|
1967
|
+
(0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, {
|
|
1968
|
+
kind: 'interrupted',
|
|
1969
|
+
reason: { kind: 'system_stop', detail: errText },
|
|
1970
|
+
});
|
|
1971
|
+
return null;
|
|
1972
|
+
}
|
|
1973
|
+
finally {
|
|
1974
|
+
(0, dialog_run_state_1.clearActiveRun)(dlg.id);
|
|
1975
|
+
if (!finalRunState) {
|
|
1976
|
+
try {
|
|
1977
|
+
finalRunState = await (0, dialog_run_state_1.computeIdleRunState)(dlg);
|
|
1978
|
+
}
|
|
1979
|
+
catch (stateErr) {
|
|
1980
|
+
log_1.log.warn('Failed to compute final run state; falling back to idle', stateErr, {
|
|
1981
|
+
dialogId: dlg.id.valueOf(),
|
|
1982
|
+
});
|
|
1983
|
+
finalRunState = { kind: 'idle_waiting_user' };
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
await (0, dialog_run_state_1.setDialogRunState)(dlg.id, finalRunState);
|
|
1987
|
+
if (tookSubdialogResponses) {
|
|
1988
|
+
try {
|
|
1989
|
+
await withSuspensionStateLock(dlg.id, async () => {
|
|
1990
|
+
if (generationHadError) {
|
|
1991
|
+
await persistence_1.DialogPersistence.rollbackTakenSubdialogResponses(dlg.id);
|
|
1992
|
+
}
|
|
1993
|
+
else {
|
|
1994
|
+
await persistence_1.DialogPersistence.commitTakenSubdialogResponses(dlg.id);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
catch (err2) {
|
|
1999
|
+
log_1.log.warn('Failed to finalize subdialog response queue after drive', {
|
|
2000
|
+
dialogId: dlg.id.selfId,
|
|
2001
|
+
error: err2,
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
} // Close while loop
|
|
2007
|
+
// Dialog stream has completed - no need to mark queue as complete since we're using receivers
|
|
2008
|
+
// === SINGLE DIALOG HIERARCHY RESTORATION API ===
|
|
2009
|
+
/**
|
|
2010
|
+
* Single API for restoring the complete dialog hierarchy (main dialog + all subdialogs)
|
|
2011
|
+
* This is the only public restoration API - all serialization is implicit
|
|
2012
|
+
*/
|
|
2013
|
+
async function restoreDialogHierarchy(rootDialogId) {
|
|
2014
|
+
try {
|
|
2015
|
+
// Assert that the ID refers to a root dialog, not a subdialog selfId.
|
|
2016
|
+
const rootMeta = await persistence_1.DialogPersistence.loadRootDialogMetadata(new dialog_1.DialogID(rootDialogId), 'running');
|
|
2017
|
+
if (rootMeta?.supdialogId) {
|
|
2018
|
+
throw new Error(`Expected root dialog ${rootDialogId} but found subdialog metadata with supdialogId: ${rootMeta.supdialogId}`);
|
|
2019
|
+
}
|
|
2020
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(rootDialogId, 'running');
|
|
2021
|
+
if (!rootDialog) {
|
|
2022
|
+
throw new Error(`Failed to restore dialog hierarchy for ${rootDialogId}`);
|
|
2023
|
+
}
|
|
2024
|
+
dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
|
|
2025
|
+
// Restore all subdialogs under this root by reading the persisted subdialogs directory,
|
|
2026
|
+
// then ensuring each subdialog is loaded into the root dialog's local registry.
|
|
2027
|
+
const subdialogs = new Map();
|
|
2028
|
+
const rootPath = persistence_1.DialogPersistence.getRootDialogPath(new dialog_1.DialogID(rootDialogId), 'running');
|
|
2029
|
+
const subPath = path.join(rootPath, persistence_1.DialogPersistence.SUBDIALOGS_DIR);
|
|
2030
|
+
let allSubdialogIds = [];
|
|
2031
|
+
try {
|
|
2032
|
+
const entries = await fs.promises.readdir(subPath, { withFileTypes: true });
|
|
2033
|
+
allSubdialogIds = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2034
|
+
}
|
|
2035
|
+
catch (err) {
|
|
2036
|
+
const code = typeof err === 'object' && err !== null && 'code' in err
|
|
2037
|
+
? err.code
|
|
2038
|
+
: undefined;
|
|
2039
|
+
if (code !== 'ENOENT') {
|
|
2040
|
+
log_1.log.warn(`Failed to read subdialogs directory: ${subPath}, returning empty array`, err);
|
|
2041
|
+
}
|
|
2042
|
+
allSubdialogIds = [];
|
|
2043
|
+
}
|
|
2044
|
+
for (const subdialogId of allSubdialogIds) {
|
|
2045
|
+
const restoredSubdialogId = new dialog_1.DialogID(subdialogId, rootDialog.id.rootId);
|
|
2046
|
+
const dialog = await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, restoredSubdialogId, 'running');
|
|
2047
|
+
if (dialog && dialog.id.selfId !== dialog.id.rootId) {
|
|
2048
|
+
subdialogs.set(subdialogId, dialog);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
// Calculate summary statistics
|
|
2052
|
+
let totalMessages = rootDialog.msgs.length;
|
|
2053
|
+
let totalRounds = rootDialog.currentRound;
|
|
2054
|
+
for (const dlg of subdialogs.values()) {
|
|
2055
|
+
totalMessages += dlg.msgs.length;
|
|
2056
|
+
if (dlg.currentRound > totalRounds)
|
|
2057
|
+
totalRounds = dlg.currentRound;
|
|
2058
|
+
}
|
|
2059
|
+
const summary = {
|
|
2060
|
+
totalMessages,
|
|
2061
|
+
totalRounds,
|
|
2062
|
+
completionStatus: 'incomplete',
|
|
2063
|
+
};
|
|
2064
|
+
return {
|
|
2065
|
+
rootDialog,
|
|
2066
|
+
subdialogs,
|
|
2067
|
+
summary,
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
catch (error) {
|
|
2071
|
+
log_1.log.error(`Failed to restore dialog hierarchy for ${rootDialogId}:`, error);
|
|
2072
|
+
throw error;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function isValidTellaskSession(tellaskSession) {
|
|
2076
|
+
const segments = tellaskSession.split('.');
|
|
2077
|
+
if (segments.length === 0)
|
|
2078
|
+
return false;
|
|
2079
|
+
return segments.every((segment) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(segment));
|
|
2080
|
+
}
|
|
2081
|
+
function parseTellaskSessionDirectiveFromHeadline(headLine) {
|
|
2082
|
+
const re = /(^|\s)!tellaskSession\s+([^\s]+)/g;
|
|
2083
|
+
const ids = [];
|
|
2084
|
+
for (const match of headLine.matchAll(re)) {
|
|
2085
|
+
const raw = match[2] ?? '';
|
|
2086
|
+
const candidate = raw.trim();
|
|
2087
|
+
const m = candidate.match(/^([a-zA-Z][a-zA-Z0-9_-]*(?:\.[a-zA-Z0-9_-]+)*)/);
|
|
2088
|
+
const tellaskSession = (m?.[1] ?? '').trim();
|
|
2089
|
+
if (!isValidTellaskSession(tellaskSession)) {
|
|
2090
|
+
return { kind: 'invalid' };
|
|
2091
|
+
}
|
|
2092
|
+
ids.push(tellaskSession);
|
|
2093
|
+
}
|
|
2094
|
+
const unique = Array.from(new Set(ids));
|
|
2095
|
+
if (unique.length === 0)
|
|
2096
|
+
return { kind: 'none' };
|
|
2097
|
+
if (unique.length === 1)
|
|
2098
|
+
return { kind: 'one', tellaskSession: unique[0] ?? '' };
|
|
2099
|
+
return { kind: 'multiple' };
|
|
2100
|
+
}
|
|
2101
|
+
function extractSingleTellaskSessionFromHeadline(headLine) {
|
|
2102
|
+
const parsed = parseTellaskSessionDirectiveFromHeadline(headLine);
|
|
2103
|
+
if (parsed.kind === 'one')
|
|
2104
|
+
return parsed.tellaskSession;
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Parse a teammate tellask pattern and return the appropriate type result.
|
|
2109
|
+
*
|
|
2110
|
+
* Patterns:
|
|
2111
|
+
* - @<supdialogAgentId> (in subdialog context, matching supdialog.agentId) → Type A (supdialog suspension)
|
|
2112
|
+
* - @<agentId> !tellaskSession <tellaskSession> → Type B (registered subdialog)
|
|
2113
|
+
* - @<agentId> → Type C (transient subdialog)
|
|
2114
|
+
*
|
|
2115
|
+
* @param firstMention The first teammate mention extracted by the streaming parser (e.g., "teammate")
|
|
2116
|
+
* @param headLine The full headline text from the streaming parser
|
|
2117
|
+
* @param currentDialog Optional current dialog context to detect Type A (subdialog calling parent)
|
|
2118
|
+
* @returns The parsed TeammateTellaskParseResult
|
|
2119
|
+
*/
|
|
2120
|
+
function parseTeammateTellask(firstMention, headLine, currentDialog) {
|
|
2121
|
+
// Fresh Boots Reasoning (FBR) syntax sugar:
|
|
2122
|
+
// `@self` always targets the current dialog's agentId (same persona/config).
|
|
2123
|
+
//
|
|
2124
|
+
// This avoids ambiguous `@teammate`-to-`@teammate` self-calls which can also be produced accidentally
|
|
2125
|
+
// by echoing/quoting an assignment headline. We keep parsing behavior the same for all other
|
|
2126
|
+
// mentions.
|
|
2127
|
+
if (firstMention === 'self') {
|
|
2128
|
+
const agentId = currentDialog?.agentId ?? 'self';
|
|
2129
|
+
const tellaskSession = extractSingleTellaskSessionFromHeadline(headLine);
|
|
2130
|
+
if (tellaskSession) {
|
|
2131
|
+
return {
|
|
2132
|
+
type: 'B',
|
|
2133
|
+
agentId,
|
|
2134
|
+
tellaskSession,
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
return {
|
|
2138
|
+
type: 'C',
|
|
2139
|
+
agentId,
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
const tellaskSession = extractSingleTellaskSessionFromHeadline(headLine);
|
|
2143
|
+
if (tellaskSession) {
|
|
2144
|
+
return {
|
|
2145
|
+
type: 'B',
|
|
2146
|
+
agentId: firstMention,
|
|
2147
|
+
tellaskSession,
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
// Phase 11: Check if this is a Type A call (subdialog calling its direct parent)
|
|
2151
|
+
// Type A only applies when:
|
|
2152
|
+
// 1. A current dialog context is provided
|
|
2153
|
+
// 2. The current dialog is a SubDialog (has a supdialog)
|
|
2154
|
+
// 3. The @agentId matches the supdialog's agentId
|
|
2155
|
+
if (currentDialog &&
|
|
2156
|
+
currentDialog.supdialog &&
|
|
2157
|
+
firstMention === currentDialog.supdialog.agentId) {
|
|
2158
|
+
return {
|
|
2159
|
+
type: 'A',
|
|
2160
|
+
agentId: firstMention,
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
// Type C: Any @agentId is a transient subdialog call
|
|
2164
|
+
return {
|
|
2165
|
+
type: 'C',
|
|
2166
|
+
agentId: firstMention,
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
// === CONVENIENCE METHODS USING SINGLE RESTORATION API ===
|
|
2170
|
+
/**
|
|
2171
|
+
* Continue dialog with human response (uses single restoration API)
|
|
2172
|
+
*/
|
|
2173
|
+
async function continueDialogWithHumanResponse(rootDialogId, humanPrompt, options) {
|
|
2174
|
+
try {
|
|
2175
|
+
// Restore the complete dialog hierarchy (pure restoration, no continuation)
|
|
2176
|
+
const result = await restoreDialogHierarchy(rootDialogId);
|
|
2177
|
+
// Then perform continuation separately
|
|
2178
|
+
if (options?.targetSubdialogId && result.subdialogs.has(options.targetSubdialogId)) {
|
|
2179
|
+
// Continue specific subdialog
|
|
2180
|
+
const targetSubdialog = result.subdialogs.get(options.targetSubdialogId);
|
|
2181
|
+
await driveDialogStream(targetSubdialog, humanPrompt);
|
|
2182
|
+
}
|
|
2183
|
+
else {
|
|
2184
|
+
// Continue root dialog
|
|
2185
|
+
await driveDialogStream(result.rootDialog, humanPrompt);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
catch (error) {
|
|
2189
|
+
log_1.log.error(`Failed to continue dialog with human response:`, error);
|
|
2190
|
+
throw error;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Continue root dialog with followup message (uses single restoration API)
|
|
2195
|
+
*/
|
|
2196
|
+
async function continueRootDialog(rootDialogId, humanPrompt) {
|
|
2197
|
+
try {
|
|
2198
|
+
// Restore the complete dialog hierarchy (pure restoration, no continuation)
|
|
2199
|
+
const result = await restoreDialogHierarchy(rootDialogId);
|
|
2200
|
+
// Then perform continuation separately
|
|
2201
|
+
await driveDialogStream(result.rootDialog, humanPrompt);
|
|
2202
|
+
}
|
|
2203
|
+
catch (error) {
|
|
2204
|
+
log_1.log.error(`Failed to continue root dialog:`, error);
|
|
2205
|
+
throw error;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Unified function to extract the last assistant message from an array of messages.
|
|
2210
|
+
* Prefers saying_msg over thinking_msg, returns full content without truncation.
|
|
2211
|
+
*
|
|
2212
|
+
* @param messages Array of chat messages to search
|
|
2213
|
+
* @param defaultMessage Default message if no assistant message found
|
|
2214
|
+
* @returns The extracted message content or default
|
|
2215
|
+
*/
|
|
2216
|
+
function extractLastAssistantResponse(messages, defaultMessage) {
|
|
2217
|
+
let responseText = '';
|
|
2218
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2219
|
+
const msg = messages[i];
|
|
2220
|
+
if (msg.type === 'saying_msg' && typeof msg.content === 'string') {
|
|
2221
|
+
responseText = msg.content;
|
|
2222
|
+
break;
|
|
2223
|
+
}
|
|
2224
|
+
if (msg.type === 'thinking_msg' && typeof msg.content === 'string') {
|
|
2225
|
+
responseText = msg.content;
|
|
2226
|
+
// Keep looking for a saying_msg which is more complete
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
// If no assistant message found, use the default
|
|
2230
|
+
if (!responseText) {
|
|
2231
|
+
responseText = defaultMessage;
|
|
2232
|
+
}
|
|
2233
|
+
return responseText;
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Phase 11: Extract response from supdialog's current messages for Type A mechanism.
|
|
2237
|
+
* Used when a subdialog calls its parent (supdialog) and needs the parent's response.
|
|
2238
|
+
* Reads from the in-memory dialog object which contains the latest messages after driving.
|
|
2239
|
+
*
|
|
2240
|
+
* @param supdialog The supdialog that was just driven
|
|
2241
|
+
* @returns The response text from the supdialog's last assistant message
|
|
2242
|
+
*/
|
|
2243
|
+
async function extractSupdialogResponseForTypeA(supdialog) {
|
|
2244
|
+
try {
|
|
2245
|
+
return extractLastAssistantResponse(supdialog.msgs, 'Supdialog completed without producing output.');
|
|
2246
|
+
}
|
|
2247
|
+
catch (err) {
|
|
2248
|
+
log_1.log.warn('Failed to extract supdialog response for Type A', { error: err });
|
|
2249
|
+
return 'Supdialog completed with errors.';
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
async function updateSubdialogAssignment(subdialog, assignment) {
|
|
2253
|
+
subdialog.assignmentFromSup = assignment;
|
|
2254
|
+
await persistence_1.DialogPersistence.updateSubdialogAssignment(subdialog.id, assignment);
|
|
2255
|
+
}
|
|
2256
|
+
async function supplySubdialogResponseToCallerIfPending(subdialog, responseText) {
|
|
2257
|
+
const assignment = subdialog.assignmentFromSup;
|
|
2258
|
+
if (!assignment) {
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
const rootDialog = subdialog.rootDialog;
|
|
2262
|
+
const callerDialog = rootDialog.lookupDialog(assignment.callerDialogId);
|
|
2263
|
+
if (!callerDialog) {
|
|
2264
|
+
log_1.log.warn('Missing caller dialog for subdialog response supply', {
|
|
2265
|
+
rootId: rootDialog.id.rootId,
|
|
2266
|
+
subdialogId: subdialog.id.selfId,
|
|
2267
|
+
callerDialogId: assignment.callerDialogId,
|
|
2268
|
+
});
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const pending = await persistence_1.DialogPersistence.loadPendingSubdialogs(callerDialog.id);
|
|
2272
|
+
const pendingRecord = pending.find((p) => p.subdialogId === subdialog.id.selfId);
|
|
2273
|
+
if (!pendingRecord) {
|
|
2274
|
+
// Caller is not waiting on this subdialog anymore; do not auto-revive.
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
await supplyResponseToSupdialog(callerDialog, subdialog.id, responseText, pendingRecord.callType, assignment.callId);
|
|
2278
|
+
}
|
|
2279
|
+
// === PHASE 6: SUBDIALOG SUPPLY MECHANISM ===
|
|
2280
|
+
/**
|
|
2281
|
+
* Create a Type A subdialog for supdialog suspension.
|
|
2282
|
+
* Creates subdialog, persists pending record, and returns suspended promise.
|
|
2283
|
+
*
|
|
2284
|
+
* Type A: Supdialog suspension call where subdialog calls parent and parent suspends.
|
|
2285
|
+
*
|
|
2286
|
+
* @param supdialog The supdialog making the call
|
|
2287
|
+
* @param targetAgentId The agent to handle the subdialog
|
|
2288
|
+
* @param headLine The headline for the subdialog
|
|
2289
|
+
* @param callBody The body content for the subdialog
|
|
2290
|
+
* @returns Promise resolving when subdialog is created and pending record saved
|
|
2291
|
+
*/
|
|
2292
|
+
async function createSubdialogForSupdialog(supdialog, targetAgentId, headLine, callBody, callId) {
|
|
2293
|
+
try {
|
|
2294
|
+
// Create the subdialog
|
|
2295
|
+
const subdialog = await supdialog.createSubDialog(targetAgentId, headLine, callBody, {
|
|
2296
|
+
originMemberId: supdialog.agentId,
|
|
2297
|
+
callerDialogId: supdialog.id.selfId,
|
|
2298
|
+
callId,
|
|
2299
|
+
collectiveTargets: [targetAgentId],
|
|
2300
|
+
});
|
|
2301
|
+
// Persist pending subdialog record
|
|
2302
|
+
const pendingRecord = {
|
|
2303
|
+
subdialogId: subdialog.id.selfId,
|
|
2304
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2305
|
+
headLine,
|
|
2306
|
+
targetAgentId,
|
|
2307
|
+
callType: 'A',
|
|
2308
|
+
};
|
|
2309
|
+
// Load existing pending subdialogs and add new one
|
|
2310
|
+
await withSuspensionStateLock(supdialog.id, async () => {
|
|
2311
|
+
const existingPending = await persistence_1.DialogPersistence.loadPendingSubdialogs(supdialog.id);
|
|
2312
|
+
existingPending.push(pendingRecord);
|
|
2313
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(supdialog.id, existingPending);
|
|
2314
|
+
});
|
|
2315
|
+
// Drive the subdialog asynchronously
|
|
2316
|
+
void (async () => {
|
|
2317
|
+
try {
|
|
2318
|
+
const initPrompt = {
|
|
2319
|
+
content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
|
|
2320
|
+
fromAgentId: supdialog.agentId,
|
|
2321
|
+
toAgentId: subdialog.agentId,
|
|
2322
|
+
headLine,
|
|
2323
|
+
callBody: callBody,
|
|
2324
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2325
|
+
collectiveTargets: [targetAgentId],
|
|
2326
|
+
}),
|
|
2327
|
+
msgId: (0, id_1.generateShortId)(),
|
|
2328
|
+
grammar: 'markdown',
|
|
2329
|
+
};
|
|
2330
|
+
await driveDialogStream(subdialog, initPrompt, true);
|
|
2331
|
+
}
|
|
2332
|
+
catch (err) {
|
|
2333
|
+
log_1.log.warn('Type A subdialog processing error:', err);
|
|
2334
|
+
}
|
|
2335
|
+
})();
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
log_1.log.error('Failed to create Type A subdialog for supdialog', {
|
|
2339
|
+
supdialogId: supdialog.id.selfId,
|
|
2340
|
+
targetAgentId,
|
|
2341
|
+
error,
|
|
2342
|
+
});
|
|
2343
|
+
throw error;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Create a Type B registered subdialog with registry lookup/register.
|
|
2348
|
+
* Creates or resumes a registered subdialog tracked in registry.yaml.
|
|
2349
|
+
*
|
|
2350
|
+
* Type B: @<agentId> !tellaskSession <tellaskSession> - Creates/resumes registered subdialog.
|
|
2351
|
+
*
|
|
2352
|
+
* @param rootDialog The root dialog making the call
|
|
2353
|
+
* @param agentId The agent to handle the subdialog
|
|
2354
|
+
* @param tellaskSession The tellask session key for registry lookup
|
|
2355
|
+
* @param headLine The headline for the subdialog
|
|
2356
|
+
* @param callBody The body content for the subdialog
|
|
2357
|
+
* @returns Promise resolving when subdialog is created/registered
|
|
2358
|
+
*/
|
|
2359
|
+
/**
|
|
2360
|
+
* Supply a response from a completed subdialog to the supdialog.
|
|
2361
|
+
* Writes the response to persistence for later incorporation.
|
|
2362
|
+
*
|
|
2363
|
+
* @param parentDialog The supdialog that created the subdialog
|
|
2364
|
+
* @param subdialogId The ID of the completed subdialog
|
|
2365
|
+
* @param responseText The full response text from the subdialog
|
|
2366
|
+
* @param callType The call type ('A', 'B', or 'C')
|
|
2367
|
+
* @param callId Optional callId for Type C subdialog tracking
|
|
2368
|
+
*/
|
|
2369
|
+
async function supplyResponseToSupdialog(parentDialog, subdialogId, responseText, callType, callId) {
|
|
2370
|
+
try {
|
|
2371
|
+
const result = await withSuspensionStateLock(parentDialog.id, async () => {
|
|
2372
|
+
const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(parentDialog.id);
|
|
2373
|
+
let pendingRecord;
|
|
2374
|
+
const filteredPending = [];
|
|
2375
|
+
for (const pending of pendingSubdialogs) {
|
|
2376
|
+
if (pending.subdialogId === subdialogId.selfId) {
|
|
2377
|
+
pendingRecord = pending;
|
|
2378
|
+
}
|
|
2379
|
+
else {
|
|
2380
|
+
filteredPending.push(pending);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
let responderId = subdialogId.rootId;
|
|
2384
|
+
let responderAgentId;
|
|
2385
|
+
let headLine = responseText;
|
|
2386
|
+
let originMemberId;
|
|
2387
|
+
try {
|
|
2388
|
+
let metadata = await persistence_1.DialogPersistence.loadDialogMetadata(subdialogId, 'running');
|
|
2389
|
+
if (!metadata) {
|
|
2390
|
+
metadata = await persistence_1.DialogPersistence.loadDialogMetadata(subdialogId, 'completed');
|
|
2391
|
+
}
|
|
2392
|
+
if (metadata && metadata.assignmentFromSup) {
|
|
2393
|
+
originMemberId = metadata.assignmentFromSup.originMemberId;
|
|
2394
|
+
if (!pendingRecord) {
|
|
2395
|
+
const assignmentHead = metadata.assignmentFromSup.headLine;
|
|
2396
|
+
if (typeof assignmentHead === 'string' && assignmentHead.trim() !== '') {
|
|
2397
|
+
headLine = assignmentHead;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (!pendingRecord && metadata && typeof metadata.agentId === 'string') {
|
|
2402
|
+
if (metadata.agentId.trim() !== '') {
|
|
2403
|
+
responderId = metadata.agentId;
|
|
2404
|
+
responderAgentId = metadata.agentId;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
catch (err) {
|
|
2409
|
+
log_1.log.warn('Failed to load subdialog metadata for response record', {
|
|
2410
|
+
parentId: parentDialog.id.selfId,
|
|
2411
|
+
subdialogId: subdialogId.selfId,
|
|
2412
|
+
error: err,
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
if (!originMemberId) {
|
|
2416
|
+
originMemberId = parentDialog.agentId;
|
|
2417
|
+
}
|
|
2418
|
+
if (pendingRecord) {
|
|
2419
|
+
responderId = pendingRecord.targetAgentId;
|
|
2420
|
+
responderAgentId = pendingRecord.targetAgentId;
|
|
2421
|
+
headLine = pendingRecord.headLine;
|
|
2422
|
+
}
|
|
2423
|
+
if (headLine.trim() === '') {
|
|
2424
|
+
headLine = responseText.slice(0, 100) + (responseText.length > 100 ? '...' : '');
|
|
2425
|
+
}
|
|
2426
|
+
const responseContent = (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
2427
|
+
responderId,
|
|
2428
|
+
requesterId: originMemberId,
|
|
2429
|
+
originalCallHeadLine: headLine,
|
|
2430
|
+
responseBody: responseText,
|
|
2431
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2432
|
+
});
|
|
2433
|
+
const completedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
|
|
2434
|
+
const responseId = (0, id_1.generateShortId)();
|
|
2435
|
+
await persistence_1.DialogPersistence.appendSubdialogResponse(parentDialog.id, {
|
|
2436
|
+
responseId,
|
|
2437
|
+
subdialogId: subdialogId.selfId,
|
|
2438
|
+
response: responseText,
|
|
2439
|
+
completedAt,
|
|
2440
|
+
callType,
|
|
2441
|
+
headLine,
|
|
2442
|
+
responderId,
|
|
2443
|
+
originMemberId,
|
|
2444
|
+
callId: callId ?? '',
|
|
2445
|
+
});
|
|
2446
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(parentDialog.id, filteredPending);
|
|
2447
|
+
const hasQ4H = await parentDialog.hasPendingQ4H();
|
|
2448
|
+
const shouldRevive = !hasQ4H && filteredPending.length === 0;
|
|
2449
|
+
if (shouldRevive && parentDialog instanceof dialog_1.RootDialog) {
|
|
2450
|
+
await persistence_1.DialogPersistence.setNeedsDrive(parentDialog.id, true, parentDialog.status);
|
|
2451
|
+
}
|
|
2452
|
+
return {
|
|
2453
|
+
responderId,
|
|
2454
|
+
responderAgentId,
|
|
2455
|
+
headLine,
|
|
2456
|
+
originMemberId,
|
|
2457
|
+
responseContent,
|
|
2458
|
+
filteredPendingCount: filteredPending.length,
|
|
2459
|
+
shouldRevive,
|
|
2460
|
+
};
|
|
2461
|
+
});
|
|
2462
|
+
const resolvedAgentId = result.responderAgentId ?? result.responderId;
|
|
2463
|
+
const resolvedOriginMemberId = result.originMemberId ?? parentDialog.agentId;
|
|
2464
|
+
const resolvedCallId = callId ?? '';
|
|
2465
|
+
await parentDialog.receiveTeammateResponse(result.responderId, result.headLine, 'completed', subdialogId, {
|
|
2466
|
+
response: responseText,
|
|
2467
|
+
agentId: resolvedAgentId,
|
|
2468
|
+
callId: resolvedCallId,
|
|
2469
|
+
originMemberId: resolvedOriginMemberId,
|
|
2470
|
+
});
|
|
2471
|
+
if (result.shouldRevive) {
|
|
2472
|
+
log_1.log.info(`All Type ${callType} subdialogs complete, parent ${parentDialog.id.selfId} auto-reviving`);
|
|
2473
|
+
if (parentDialog instanceof dialog_1.RootDialog) {
|
|
2474
|
+
dialog_global_registry_1.globalDialogRegistry.markNeedsDrive(parentDialog.id.rootId);
|
|
2475
|
+
}
|
|
2476
|
+
else {
|
|
2477
|
+
void driveDialogStream(parentDialog, undefined, true);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
catch (error) {
|
|
2482
|
+
log_1.log.error('Failed to supply subdialog response', {
|
|
2483
|
+
parentId: parentDialog.id.selfId,
|
|
2484
|
+
subdialogId: subdialogId.selfId,
|
|
2485
|
+
error,
|
|
2486
|
+
});
|
|
2487
|
+
throw error;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Check if all pending Type A subdialogs are satisfied (have responses).
|
|
2492
|
+
*
|
|
2493
|
+
* @param rootDialogId The root dialog ID to check
|
|
2494
|
+
* @returns Promise<boolean> True if all Type A subdialogs have responses
|
|
2495
|
+
*/
|
|
2496
|
+
async function areAllSubdialogsSatisfied(rootDialogId) {
|
|
2497
|
+
try {
|
|
2498
|
+
const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialogId);
|
|
2499
|
+
const responses = await persistence_1.DialogPersistence.loadSubdialogResponses(rootDialogId);
|
|
2500
|
+
// Check if any pending subdialogs have responses
|
|
2501
|
+
const pendingIds = new Set(pendingSubdialogs.map((p) => p.subdialogId));
|
|
2502
|
+
const respondedIds = new Set(responses.map((r) => r.subdialogId));
|
|
2503
|
+
// Check if all pending subdialogs have been responded to
|
|
2504
|
+
for (const pendingId of pendingIds) {
|
|
2505
|
+
if (!respondedIds.has(pendingId)) {
|
|
2506
|
+
return false;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return true;
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
log_1.log.error('Failed to check subdialog satisfaction', {
|
|
2513
|
+
rootDialogId: rootDialogId.selfId,
|
|
2514
|
+
error,
|
|
2515
|
+
});
|
|
2516
|
+
return false;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Incorporate subdialog responses into the supdialog and resume.
|
|
2521
|
+
* Reads responses from persistence and clears them after incorporation.
|
|
2522
|
+
*
|
|
2523
|
+
* @param rootDialog The root dialog to resume
|
|
2524
|
+
* @returns Promise<Array<{ subdialogId: string; response: string; callType: 'A' | 'B' | 'C' }>>
|
|
2525
|
+
* Array of incorporated responses (response holds full response text)
|
|
2526
|
+
*/
|
|
2527
|
+
async function incorporateSubdialogResponses(rootDialog) {
|
|
2528
|
+
try {
|
|
2529
|
+
const responses = await persistence_1.DialogPersistence.loadSubdialogResponses(rootDialog.id);
|
|
2530
|
+
// Incorporate each response
|
|
2531
|
+
for (const response of responses) {
|
|
2532
|
+
const subdialogId = new dialog_1.DialogID(response.subdialogId, rootDialog.id.rootId);
|
|
2533
|
+
// Emit subdialog response event (payload contains full response text)
|
|
2534
|
+
await rootDialog.postSubdialogResponse(subdialogId, response.response);
|
|
2535
|
+
}
|
|
2536
|
+
// Clear responses after incorporation
|
|
2537
|
+
await persistence_1.DialogPersistence.saveSubdialogResponses(rootDialog.id, []);
|
|
2538
|
+
// Clear pending subdialogs that have been responded to
|
|
2539
|
+
const pendingSubdialogs = await persistence_1.DialogPersistence.loadPendingSubdialogs(rootDialog.id);
|
|
2540
|
+
const respondedIds = new Set(responses.map((r) => r.subdialogId));
|
|
2541
|
+
const filteredPending = pendingSubdialogs.filter((p) => !respondedIds.has(p.subdialogId));
|
|
2542
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(rootDialog.id, filteredPending);
|
|
2543
|
+
return responses;
|
|
2544
|
+
}
|
|
2545
|
+
catch (error) {
|
|
2546
|
+
log_1.log.error('Failed to incorporate subdialog responses', {
|
|
2547
|
+
rootDialogId: rootDialog.id.selfId,
|
|
2548
|
+
error,
|
|
2549
|
+
});
|
|
2550
|
+
throw error;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Collect tellask calls using the streaming parser, then execute them
|
|
2555
|
+
*/
|
|
2556
|
+
async function executeTellaskCalls(dlg, agent, collectedCalls) {
|
|
2557
|
+
const malformedToolOutputs = await emitMalformedTellaskResponses(dlg, collectedCalls);
|
|
2558
|
+
const validCalls = collectedCalls.filter((call) => call.validation.kind === 'valid');
|
|
2559
|
+
// Execute collected calls concurrently
|
|
2560
|
+
const results = await Promise.all(validCalls.map((call) => executeTellaskCall(dlg, agent, call.validation.firstMention, call.headLine, call.body, call.callId)));
|
|
2561
|
+
// Combine results from all concurrent calls
|
|
2562
|
+
const suspend = results.some((result) => result.suspend);
|
|
2563
|
+
const toolOutputs = [...malformedToolOutputs, ...results.flatMap((result) => result.toolOutputs)];
|
|
2564
|
+
const subdialogsCreated = results.flatMap((result) => result.subdialogsCreated);
|
|
2565
|
+
return { suspend, toolOutputs, subdialogsCreated };
|
|
2566
|
+
}
|
|
2567
|
+
async function emitMalformedTellaskResponses(dlg, collectedCalls) {
|
|
2568
|
+
const toolOutputs = [];
|
|
2569
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
2570
|
+
for (const call of collectedCalls) {
|
|
2571
|
+
if (call.validation.kind !== 'malformed')
|
|
2572
|
+
continue;
|
|
2573
|
+
const firstLineAfterPrefix = (call.headLine.split('\n')[0] ?? '').trim();
|
|
2574
|
+
const msg = (0, driver_messages_1.formatDomindsNoteMalformedTellaskCall)(language, call.validation.reason, {
|
|
2575
|
+
firstLineAfterPrefix,
|
|
2576
|
+
});
|
|
2577
|
+
toolOutputs.push({
|
|
2578
|
+
type: 'environment_msg',
|
|
2579
|
+
role: 'user',
|
|
2580
|
+
content: msg,
|
|
2581
|
+
});
|
|
2582
|
+
toolOutputs.push({
|
|
2583
|
+
type: 'call_result_msg',
|
|
2584
|
+
role: 'tool',
|
|
2585
|
+
responderId: 'dominds',
|
|
2586
|
+
headLine: call.headLine,
|
|
2587
|
+
status: 'failed',
|
|
2588
|
+
content: msg,
|
|
2589
|
+
});
|
|
2590
|
+
await dlg.receiveToolResponse('dominds', call.headLine, msg, 'failed', call.callId);
|
|
2591
|
+
dlg.clearCurrentCallId();
|
|
2592
|
+
}
|
|
2593
|
+
return toolOutputs;
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Execute a single tellask call using Phase 5 3-Type Taxonomy.
|
|
2597
|
+
* Handles Type A (supdialog suspension), Type B (registered subdialog), and Type C (transient subdialog).
|
|
2598
|
+
*/
|
|
2599
|
+
async function executeTellaskCall(dlg, agent, firstMention, headLine, body, callId, options) {
|
|
2600
|
+
const toolOutputs = [];
|
|
2601
|
+
let suspend = false;
|
|
2602
|
+
const subdialogsCreated = [];
|
|
2603
|
+
const team = await team_1.Team.load();
|
|
2604
|
+
const isSelfAlias = firstMention === 'self';
|
|
2605
|
+
const isSuperAlias = firstMention === 'super';
|
|
2606
|
+
const member = isSelfAlias ? team.getMember(dlg.agentId) : team.getMember(firstMention);
|
|
2607
|
+
// Multi-teammate fan-out (collective teammate tellask):
|
|
2608
|
+
// A single tellask block can target multiple teammates by including multiple teammate mentions
|
|
2609
|
+
// anywhere inside the (possibly multiline) headline. The full headline/body is passed verbatim
|
|
2610
|
+
// to each target so each subdialog can see this is a collective assignment.
|
|
2611
|
+
const allowMultiTeammateTargets = options?.allowMultiTeammateTargets ?? true;
|
|
2612
|
+
if (allowMultiTeammateTargets && member && !isSelfAlias && !isSuperAlias) {
|
|
2613
|
+
const mentioned = extractMentionIdsFromHeadline(headLine);
|
|
2614
|
+
const uniqueMentioned = Array.from(new Set(mentioned));
|
|
2615
|
+
const knownTargets = uniqueMentioned.filter((id) => team.getMember(id) !== null);
|
|
2616
|
+
if (!knownTargets.includes(firstMention)) {
|
|
2617
|
+
knownTargets.unshift(firstMention);
|
|
2618
|
+
}
|
|
2619
|
+
if (knownTargets.length >= 2) {
|
|
2620
|
+
const unknown = uniqueMentioned.filter((id) => team.getMember(id) === null &&
|
|
2621
|
+
id !== 'self' &&
|
|
2622
|
+
id !== 'super' &&
|
|
2623
|
+
id !== 'human' &&
|
|
2624
|
+
id !== 'dominds');
|
|
2625
|
+
if (unknown.length > 0) {
|
|
2626
|
+
const msg = (0, driver_messages_1.formatDomindsNoteInvalidMultiTeammateTargets)((0, runtime_language_1.getWorkLanguage)(), { unknown });
|
|
2627
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
2628
|
+
toolOutputs.push({
|
|
2629
|
+
type: 'call_result_msg',
|
|
2630
|
+
role: 'tool',
|
|
2631
|
+
responderId: 'dominds',
|
|
2632
|
+
headLine,
|
|
2633
|
+
status: 'failed',
|
|
2634
|
+
content: msg,
|
|
2635
|
+
});
|
|
2636
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
2637
|
+
dlg.clearCurrentCallId();
|
|
2638
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2639
|
+
}
|
|
2640
|
+
if (options?.skipTellaskSessionDirectiveValidation !== true) {
|
|
2641
|
+
const tellaskSessionDirective = parseTellaskSessionDirectiveFromHeadline(headLine);
|
|
2642
|
+
if (tellaskSessionDirective.kind === 'multiple') {
|
|
2643
|
+
const msg = (0, driver_messages_1.formatDomindsNoteMultipleTellaskSessionDirectives)((0, runtime_language_1.getWorkLanguage)());
|
|
2644
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
2645
|
+
toolOutputs.push({
|
|
2646
|
+
type: 'call_result_msg',
|
|
2647
|
+
role: 'tool',
|
|
2648
|
+
responderId: 'dominds',
|
|
2649
|
+
headLine,
|
|
2650
|
+
status: 'failed',
|
|
2651
|
+
content: msg,
|
|
2652
|
+
});
|
|
2653
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
2654
|
+
dlg.clearCurrentCallId();
|
|
2655
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2656
|
+
}
|
|
2657
|
+
if (tellaskSessionDirective.kind === 'invalid') {
|
|
2658
|
+
const msg = (0, driver_messages_1.formatDomindsNoteInvalidTellaskSessionDirective)((0, runtime_language_1.getWorkLanguage)());
|
|
2659
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
2660
|
+
toolOutputs.push({
|
|
2661
|
+
type: 'call_result_msg',
|
|
2662
|
+
role: 'tool',
|
|
2663
|
+
responderId: 'dominds',
|
|
2664
|
+
headLine,
|
|
2665
|
+
status: 'failed',
|
|
2666
|
+
content: msg,
|
|
2667
|
+
});
|
|
2668
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
2669
|
+
dlg.clearCurrentCallId();
|
|
2670
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
const perTargetResults = await Promise.all(knownTargets.map(async (targetId) => {
|
|
2674
|
+
return await executeTellaskCall(dlg, agent, targetId, headLine, body, callId, {
|
|
2675
|
+
allowMultiTeammateTargets: false,
|
|
2676
|
+
collectiveTargets: knownTargets,
|
|
2677
|
+
skipTellaskSessionDirectiveValidation: true,
|
|
2678
|
+
});
|
|
2679
|
+
}));
|
|
2680
|
+
return {
|
|
2681
|
+
toolOutputs: perTargetResults.flatMap((r) => r.toolOutputs),
|
|
2682
|
+
suspend: perTargetResults.some((r) => r.suspend),
|
|
2683
|
+
subdialogsCreated: perTargetResults.flatMap((r) => r.subdialogsCreated),
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
// === Q4H: Handle @human teammate tellasks (Questions for Human) ===
|
|
2688
|
+
// Q4H works for both user-initiated and assistant-initiated @human calls
|
|
2689
|
+
const isQ4H = firstMention === 'human';
|
|
2690
|
+
if (isQ4H) {
|
|
2691
|
+
try {
|
|
2692
|
+
// Create HumanQuestion entry
|
|
2693
|
+
const questionId = `q4h-${(0, id_2.generateDialogID)()}`;
|
|
2694
|
+
const question = {
|
|
2695
|
+
id: questionId,
|
|
2696
|
+
kind: 'generic',
|
|
2697
|
+
headLine: headLine.trim(),
|
|
2698
|
+
bodyContent: body.trim(),
|
|
2699
|
+
askedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2700
|
+
callSiteRef: {
|
|
2701
|
+
round: dlg.currentRound,
|
|
2702
|
+
messageIndex: dlg.msgs.length,
|
|
2703
|
+
},
|
|
2704
|
+
};
|
|
2705
|
+
// Load existing questions and add new one
|
|
2706
|
+
const existingQuestions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dlg.id);
|
|
2707
|
+
const previousCount = existingQuestions.length;
|
|
2708
|
+
existingQuestions.push(question);
|
|
2709
|
+
// Save to q4h.yaml
|
|
2710
|
+
await persistence_1.DialogPersistence._saveQuestions4HumanState(dlg.id, existingQuestions);
|
|
2711
|
+
// Emit new_q4h_asked event
|
|
2712
|
+
const newQuestionEvent = {
|
|
2713
|
+
type: 'new_q4h_asked',
|
|
2714
|
+
question: {
|
|
2715
|
+
id: question.id,
|
|
2716
|
+
kind: question.kind,
|
|
2717
|
+
selfId: dlg.id.selfId,
|
|
2718
|
+
headLine: question.headLine,
|
|
2719
|
+
bodyContent: question.bodyContent,
|
|
2720
|
+
askedAt: question.askedAt,
|
|
2721
|
+
callSiteRef: question.callSiteRef,
|
|
2722
|
+
rootId: dlg.id.rootId,
|
|
2723
|
+
agentId: dlg.agentId,
|
|
2724
|
+
taskDocPath: dlg.taskDocPath,
|
|
2725
|
+
},
|
|
2726
|
+
};
|
|
2727
|
+
(0, evt_registry_1.postDialogEvent)(dlg, newQuestionEvent);
|
|
2728
|
+
// Return empty output and suspend for human answer
|
|
2729
|
+
return { toolOutputs, suspend: true, subdialogsCreated: [] };
|
|
2730
|
+
}
|
|
2731
|
+
catch (q4hErr) {
|
|
2732
|
+
const errMsg = q4hErr instanceof Error ? q4hErr.message : String(q4hErr);
|
|
2733
|
+
const errStack = q4hErr instanceof Error ? q4hErr.stack : '';
|
|
2734
|
+
log_1.log.error('Q4H: Failed to register question', q4hErr, {
|
|
2735
|
+
dialogId: dlg.id.selfId,
|
|
2736
|
+
headLine: headLine.substring(0, 100),
|
|
2737
|
+
});
|
|
2738
|
+
// Don't throw - allow fallback to "Unknown call" handler
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (member || isSelfAlias || isSuperAlias) {
|
|
2742
|
+
// This is a teammate tellask - parse using Phase 5 taxonomy (Type A/B/C).
|
|
2743
|
+
if (isSuperAlias && !(dlg instanceof dialog_1.SubDialog)) {
|
|
2744
|
+
const response = (0, driver_messages_1.formatDomindsNoteSuperOnlyInSubdialog)((0, runtime_language_1.getWorkLanguage)());
|
|
2745
|
+
try {
|
|
2746
|
+
await dlg.receiveTeammateResponse('dominds', headLine, 'failed', dlg.id, {
|
|
2747
|
+
response,
|
|
2748
|
+
agentId: 'dominds',
|
|
2749
|
+
callId,
|
|
2750
|
+
originMemberId: dlg.agentId,
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
catch (err) {
|
|
2754
|
+
log_1.log.warn('Failed to emit @super misuse response', err, {
|
|
2755
|
+
dialogId: dlg.id.selfId,
|
|
2756
|
+
agentId: dlg.agentId,
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2760
|
+
}
|
|
2761
|
+
if (options?.skipTellaskSessionDirectiveValidation !== true) {
|
|
2762
|
+
const tellaskSessionDirective = parseTellaskSessionDirectiveFromHeadline(headLine);
|
|
2763
|
+
if (isSuperAlias && tellaskSessionDirective.kind !== 'none') {
|
|
2764
|
+
const response = (0, driver_messages_1.formatDomindsNoteSuperNoTellaskSession)((0, runtime_language_1.getWorkLanguage)());
|
|
2765
|
+
try {
|
|
2766
|
+
await dlg.receiveTeammateResponse('dominds', headLine, 'failed', dlg.id, {
|
|
2767
|
+
response,
|
|
2768
|
+
agentId: 'dominds',
|
|
2769
|
+
callId,
|
|
2770
|
+
originMemberId: dlg.agentId,
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2773
|
+
catch (err) {
|
|
2774
|
+
log_1.log.warn('Failed to emit @super !tellaskSession syntax error response', err, {
|
|
2775
|
+
dialogId: dlg.id.selfId,
|
|
2776
|
+
agentId: dlg.agentId,
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2780
|
+
}
|
|
2781
|
+
if (tellaskSessionDirective.kind === 'multiple') {
|
|
2782
|
+
const msg = (0, driver_messages_1.formatDomindsNoteMultipleTellaskSessionDirectives)((0, runtime_language_1.getWorkLanguage)());
|
|
2783
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
2784
|
+
toolOutputs.push({
|
|
2785
|
+
type: 'call_result_msg',
|
|
2786
|
+
role: 'tool',
|
|
2787
|
+
responderId: 'dominds',
|
|
2788
|
+
headLine,
|
|
2789
|
+
status: 'failed',
|
|
2790
|
+
content: msg,
|
|
2791
|
+
});
|
|
2792
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
2793
|
+
dlg.clearCurrentCallId();
|
|
2794
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2795
|
+
}
|
|
2796
|
+
if (tellaskSessionDirective.kind === 'invalid') {
|
|
2797
|
+
const msg = (0, driver_messages_1.formatDomindsNoteInvalidTellaskSessionDirective)((0, runtime_language_1.getWorkLanguage)());
|
|
2798
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
2799
|
+
toolOutputs.push({
|
|
2800
|
+
type: 'call_result_msg',
|
|
2801
|
+
role: 'tool',
|
|
2802
|
+
responderId: 'dominds',
|
|
2803
|
+
headLine,
|
|
2804
|
+
status: 'failed',
|
|
2805
|
+
content: msg,
|
|
2806
|
+
});
|
|
2807
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
2808
|
+
dlg.clearCurrentCallId();
|
|
2809
|
+
return { toolOutputs, suspend: false, subdialogsCreated: [] };
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
const parseResult = isSuperAlias
|
|
2813
|
+
? { type: 'A', agentId: dlg.supdialog.agentId }
|
|
2814
|
+
: parseTeammateTellask(firstMention, headLine, dlg);
|
|
2815
|
+
// If the agent calls itself via `@<agentId>` (instead of `@self`), allow it to proceed
|
|
2816
|
+
// (self-calls are useful for FBR), but emit a correction bubble so the user can distinguish
|
|
2817
|
+
// intentional self-FBR from accidental echo/quote triggers.
|
|
2818
|
+
const isDirectSelfCall = !isSelfAlias && !isSuperAlias && parseResult.agentId === dlg.agentId;
|
|
2819
|
+
if (isDirectSelfCall) {
|
|
2820
|
+
const response = (0, driver_messages_1.formatDomindsNoteDirectSelfCall)((0, runtime_language_1.getWorkLanguage)());
|
|
2821
|
+
try {
|
|
2822
|
+
await dlg.receiveTeammateResponse('dominds', headLine, 'completed', dlg.id, {
|
|
2823
|
+
response,
|
|
2824
|
+
agentId: 'dominds',
|
|
2825
|
+
callId,
|
|
2826
|
+
originMemberId: dlg.agentId,
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
catch (err) {
|
|
2830
|
+
log_1.log.warn('Failed to emit self-call correction response', err, {
|
|
2831
|
+
dialogId: dlg.id.selfId,
|
|
2832
|
+
agentId: dlg.agentId,
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
// Phase 11: Type A handling - subdialog calling its direct parent (supdialog)
|
|
2837
|
+
// This suspends the subdialog, drives the supdialog for one round, then returns to subdialog
|
|
2838
|
+
if (parseResult.type === 'A') {
|
|
2839
|
+
// Type A is only valid from a subdialog (calling back to its supdialog).
|
|
2840
|
+
if (dlg instanceof dialog_1.SubDialog) {
|
|
2841
|
+
const supdialog = dlg.supdialog;
|
|
2842
|
+
// Suspend the subdialog
|
|
2843
|
+
dlg.setSuspensionState('suspended');
|
|
2844
|
+
try {
|
|
2845
|
+
const headLineForSupdialog = isSuperAlias && headLine.startsWith('@super')
|
|
2846
|
+
? `@${supdialog.agentId}${headLine.slice('@super'.length)}`
|
|
2847
|
+
: headLine;
|
|
2848
|
+
const assignment = dlg.assignmentFromSup;
|
|
2849
|
+
const supPrompt = {
|
|
2850
|
+
content: (0, inter_dialog_format_1.formatSupdialogCallPrompt)({
|
|
2851
|
+
fromAgentId: dlg.agentId,
|
|
2852
|
+
toAgentId: supdialog.agentId,
|
|
2853
|
+
subdialogRequest: {
|
|
2854
|
+
headLine: headLineForSupdialog,
|
|
2855
|
+
callBody: body,
|
|
2856
|
+
},
|
|
2857
|
+
supdialogAssignment: {
|
|
2858
|
+
headLine: assignment.headLine,
|
|
2859
|
+
callBody: assignment.callBody,
|
|
2860
|
+
},
|
|
2861
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2862
|
+
}),
|
|
2863
|
+
msgId: (0, id_1.generateShortId)(),
|
|
2864
|
+
grammar: 'markdown',
|
|
2865
|
+
};
|
|
2866
|
+
// Drive the supdialog for one round (queue if already driving)
|
|
2867
|
+
await driveDialogStream(supdialog, supPrompt, true);
|
|
2868
|
+
// Extract response from supdialog's last assistant message
|
|
2869
|
+
const responseText = await extractSupdialogResponseForTypeA(supdialog);
|
|
2870
|
+
const responseContent = (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
2871
|
+
responderId: parseResult.agentId,
|
|
2872
|
+
requesterId: dlg.agentId,
|
|
2873
|
+
originalCallHeadLine: headLine,
|
|
2874
|
+
responseBody: responseText,
|
|
2875
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2876
|
+
});
|
|
2877
|
+
// Resume the subdialog with the supdialog's response
|
|
2878
|
+
dlg.setSuspensionState('resumed');
|
|
2879
|
+
const resultMsg = {
|
|
2880
|
+
type: 'call_result_msg',
|
|
2881
|
+
role: 'tool',
|
|
2882
|
+
responderId: parseResult.agentId,
|
|
2883
|
+
headLine,
|
|
2884
|
+
status: 'completed',
|
|
2885
|
+
content: responseContent,
|
|
2886
|
+
};
|
|
2887
|
+
toolOutputs.push(resultMsg);
|
|
2888
|
+
await dlg.receiveTeammateResponse(parseResult.agentId, headLine, 'completed', supdialog.id, {
|
|
2889
|
+
response: responseText,
|
|
2890
|
+
agentId: parseResult.agentId,
|
|
2891
|
+
callId,
|
|
2892
|
+
originMemberId: dlg.agentId,
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
catch (err) {
|
|
2896
|
+
log_1.log.warn('Type A supdialog processing error:', err);
|
|
2897
|
+
// Resume the subdialog even on error
|
|
2898
|
+
dlg.setSuspensionState('resumed');
|
|
2899
|
+
const errorText = `❌ **Error processing request to @${parseResult.agentId}:**\n\n${showErrorToAi(err)}`;
|
|
2900
|
+
const resultMsg = {
|
|
2901
|
+
type: 'call_result_msg',
|
|
2902
|
+
role: 'tool',
|
|
2903
|
+
responderId: parseResult.agentId,
|
|
2904
|
+
headLine,
|
|
2905
|
+
status: 'failed',
|
|
2906
|
+
content: errorText,
|
|
2907
|
+
};
|
|
2908
|
+
toolOutputs.push(resultMsg);
|
|
2909
|
+
await dlg.receiveTeammateResponse(parseResult.agentId, headLine, 'failed', supdialog.id, {
|
|
2910
|
+
response: errorText,
|
|
2911
|
+
agentId: parseResult.agentId,
|
|
2912
|
+
callId,
|
|
2913
|
+
originMemberId: dlg.agentId,
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
else {
|
|
2918
|
+
log_1.log.warn('Type A call on dialog without supdialog, falling back to Type C', {
|
|
2919
|
+
dialogId: dlg.id.selfId,
|
|
2920
|
+
});
|
|
2921
|
+
// Fall through to Type C handling
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
else if (parseResult.type === 'B') {
|
|
2925
|
+
// Type B: Registered subdialog with tellaskSession (root registry, caller can be root or subdialog)
|
|
2926
|
+
const callerDialog = dlg;
|
|
2927
|
+
let rootDialog;
|
|
2928
|
+
if (dlg instanceof dialog_1.RootDialog) {
|
|
2929
|
+
rootDialog = dlg;
|
|
2930
|
+
}
|
|
2931
|
+
else if (dlg instanceof dialog_1.SubDialog) {
|
|
2932
|
+
rootDialog = dlg.rootDialog;
|
|
2933
|
+
}
|
|
2934
|
+
if (!rootDialog) {
|
|
2935
|
+
log_1.log.warn('Type B call without root dialog, falling back to Type C', {
|
|
2936
|
+
dialogId: dlg.id.selfId,
|
|
2937
|
+
});
|
|
2938
|
+
try {
|
|
2939
|
+
const sub = await dlg.createSubDialog(parseResult.agentId, headLine, body, {
|
|
2940
|
+
originMemberId: dlg.agentId,
|
|
2941
|
+
callerDialogId: callerDialog.id.selfId,
|
|
2942
|
+
callId,
|
|
2943
|
+
tellaskSession: parseResult.tellaskSession,
|
|
2944
|
+
collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
|
|
2945
|
+
});
|
|
2946
|
+
const pendingRecord = {
|
|
2947
|
+
subdialogId: sub.id.selfId,
|
|
2948
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2949
|
+
headLine,
|
|
2950
|
+
targetAgentId: parseResult.agentId,
|
|
2951
|
+
callType: 'C',
|
|
2952
|
+
tellaskSession: parseResult.tellaskSession,
|
|
2953
|
+
};
|
|
2954
|
+
await withSuspensionStateLock(dlg.id, async () => {
|
|
2955
|
+
const existingPending = await persistence_1.DialogPersistence.loadPendingSubdialogs(dlg.id);
|
|
2956
|
+
existingPending.push(pendingRecord);
|
|
2957
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(dlg.id, existingPending);
|
|
2958
|
+
});
|
|
2959
|
+
const task = (async () => {
|
|
2960
|
+
try {
|
|
2961
|
+
const initPrompt = {
|
|
2962
|
+
content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
|
|
2963
|
+
fromAgentId: dlg.agentId,
|
|
2964
|
+
toAgentId: sub.agentId,
|
|
2965
|
+
headLine,
|
|
2966
|
+
callBody: body,
|
|
2967
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2968
|
+
collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
|
|
2969
|
+
}),
|
|
2970
|
+
msgId: (0, id_1.generateShortId)(),
|
|
2971
|
+
grammar: 'markdown',
|
|
2972
|
+
};
|
|
2973
|
+
await driveDialogStream(sub, initPrompt, true);
|
|
2974
|
+
}
|
|
2975
|
+
catch (err) {
|
|
2976
|
+
log_1.log.warn('Type B fallback subdialog processing error:', err);
|
|
2977
|
+
}
|
|
2978
|
+
})();
|
|
2979
|
+
void task;
|
|
2980
|
+
subdialogsCreated.push(sub.id);
|
|
2981
|
+
suspend = true;
|
|
2982
|
+
}
|
|
2983
|
+
catch (err) {
|
|
2984
|
+
log_1.log.warn('Type B fallback subdialog creation error:', err);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
else {
|
|
2988
|
+
const originMemberId = dlg.agentId;
|
|
2989
|
+
const assignment = {
|
|
2990
|
+
headLine,
|
|
2991
|
+
callBody: body,
|
|
2992
|
+
originMemberId,
|
|
2993
|
+
callerDialogId: callerDialog.id.selfId,
|
|
2994
|
+
callId,
|
|
2995
|
+
collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
|
|
2996
|
+
};
|
|
2997
|
+
const existingSubdialog = rootDialog.lookupSubdialog(parseResult.agentId, parseResult.tellaskSession);
|
|
2998
|
+
const pendingOwner = callerDialog;
|
|
2999
|
+
if (existingSubdialog) {
|
|
3000
|
+
const resumePrompt = {
|
|
3001
|
+
content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
|
|
3002
|
+
fromAgentId: dlg.agentId,
|
|
3003
|
+
toAgentId: existingSubdialog.agentId,
|
|
3004
|
+
headLine,
|
|
3005
|
+
callBody: body,
|
|
3006
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
3007
|
+
collectiveTargets: options?.collectiveTargets ?? [existingSubdialog.agentId],
|
|
3008
|
+
}),
|
|
3009
|
+
msgId: (0, id_1.generateShortId)(),
|
|
3010
|
+
grammar: 'markdown',
|
|
3011
|
+
};
|
|
3012
|
+
try {
|
|
3013
|
+
await updateSubdialogAssignment(existingSubdialog, assignment);
|
|
3014
|
+
}
|
|
3015
|
+
catch (err) {
|
|
3016
|
+
log_1.log.warn('Failed to update registered subdialog assignment', err);
|
|
3017
|
+
}
|
|
3018
|
+
const pendingRecord = {
|
|
3019
|
+
subdialogId: existingSubdialog.id.selfId,
|
|
3020
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3021
|
+
headLine,
|
|
3022
|
+
targetAgentId: parseResult.agentId,
|
|
3023
|
+
callType: 'B',
|
|
3024
|
+
tellaskSession: parseResult.tellaskSession,
|
|
3025
|
+
};
|
|
3026
|
+
await withSuspensionStateLock(pendingOwner.id, async () => {
|
|
3027
|
+
const existingPending = await persistence_1.DialogPersistence.loadPendingSubdialogs(pendingOwner.id);
|
|
3028
|
+
existingPending.push(pendingRecord);
|
|
3029
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(pendingOwner.id, existingPending);
|
|
3030
|
+
});
|
|
3031
|
+
const task = (async () => {
|
|
3032
|
+
try {
|
|
3033
|
+
await driveDialogStream(existingSubdialog, resumePrompt, true);
|
|
3034
|
+
}
|
|
3035
|
+
catch (err) {
|
|
3036
|
+
log_1.log.warn('Type B registered subdialog resumption error:', err);
|
|
3037
|
+
}
|
|
3038
|
+
})();
|
|
3039
|
+
void task;
|
|
3040
|
+
subdialogsCreated.push(existingSubdialog.id);
|
|
3041
|
+
suspend = true;
|
|
3042
|
+
}
|
|
3043
|
+
else {
|
|
3044
|
+
const sub = await rootDialog.createSubDialog(parseResult.agentId, headLine, body, {
|
|
3045
|
+
originMemberId,
|
|
3046
|
+
callerDialogId: callerDialog.id.selfId,
|
|
3047
|
+
callId,
|
|
3048
|
+
tellaskSession: parseResult.tellaskSession,
|
|
3049
|
+
collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
|
|
3050
|
+
});
|
|
3051
|
+
rootDialog.registerSubdialog(sub);
|
|
3052
|
+
await rootDialog.saveSubdialogRegistry();
|
|
3053
|
+
const pendingRecord = {
|
|
3054
|
+
subdialogId: sub.id.selfId,
|
|
3055
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3056
|
+
headLine,
|
|
3057
|
+
targetAgentId: parseResult.agentId,
|
|
3058
|
+
callType: 'B',
|
|
3059
|
+
tellaskSession: parseResult.tellaskSession,
|
|
3060
|
+
};
|
|
3061
|
+
await withSuspensionStateLock(pendingOwner.id, async () => {
|
|
3062
|
+
const existingPending = await persistence_1.DialogPersistence.loadPendingSubdialogs(pendingOwner.id);
|
|
3063
|
+
existingPending.push(pendingRecord);
|
|
3064
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(pendingOwner.id, existingPending);
|
|
3065
|
+
});
|
|
3066
|
+
const task = (async () => {
|
|
3067
|
+
try {
|
|
3068
|
+
const initPrompt = {
|
|
3069
|
+
content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
|
|
3070
|
+
fromAgentId: rootDialog.agentId,
|
|
3071
|
+
toAgentId: sub.agentId,
|
|
3072
|
+
headLine,
|
|
3073
|
+
callBody: body,
|
|
3074
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
3075
|
+
collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
|
|
3076
|
+
}),
|
|
3077
|
+
msgId: (0, id_1.generateShortId)(),
|
|
3078
|
+
grammar: 'markdown',
|
|
3079
|
+
};
|
|
3080
|
+
await driveDialogStream(sub, initPrompt, true);
|
|
3081
|
+
}
|
|
3082
|
+
catch (err) {
|
|
3083
|
+
log_1.log.warn('Type B subdialog processing error:', err);
|
|
3084
|
+
}
|
|
3085
|
+
})();
|
|
3086
|
+
void task;
|
|
3087
|
+
subdialogsCreated.push(sub.id);
|
|
3088
|
+
suspend = true;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
// Type C: Transient subdialog (unregistered)
|
|
3093
|
+
if (parseResult.type === 'C') {
|
|
3094
|
+
try {
|
|
3095
|
+
const sub = await dlg.createSubDialog(parseResult.agentId, headLine, body, {
|
|
3096
|
+
originMemberId: dlg.agentId,
|
|
3097
|
+
callerDialogId: dlg.id.selfId,
|
|
3098
|
+
callId,
|
|
3099
|
+
collectiveTargets: options?.collectiveTargets ?? [parseResult.agentId],
|
|
3100
|
+
});
|
|
3101
|
+
const pendingRecord = {
|
|
3102
|
+
subdialogId: sub.id.selfId,
|
|
3103
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3104
|
+
headLine,
|
|
3105
|
+
targetAgentId: parseResult.agentId,
|
|
3106
|
+
callType: 'C',
|
|
3107
|
+
};
|
|
3108
|
+
await withSuspensionStateLock(dlg.id, async () => {
|
|
3109
|
+
const existingPending = await persistence_1.DialogPersistence.loadPendingSubdialogs(dlg.id);
|
|
3110
|
+
existingPending.push(pendingRecord);
|
|
3111
|
+
await persistence_1.DialogPersistence.savePendingSubdialogs(dlg.id, existingPending);
|
|
3112
|
+
});
|
|
3113
|
+
const task = (async () => {
|
|
3114
|
+
try {
|
|
3115
|
+
const initPrompt = {
|
|
3116
|
+
content: (0, inter_dialog_format_1.formatAssignmentFromSupdialog)({
|
|
3117
|
+
fromAgentId: dlg.agentId,
|
|
3118
|
+
toAgentId: sub.agentId,
|
|
3119
|
+
headLine,
|
|
3120
|
+
callBody: body,
|
|
3121
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
3122
|
+
collectiveTargets: options?.collectiveTargets ?? [sub.agentId],
|
|
3123
|
+
}),
|
|
3124
|
+
msgId: (0, id_1.generateShortId)(),
|
|
3125
|
+
grammar: 'markdown',
|
|
3126
|
+
};
|
|
3127
|
+
// Type C: Move to done/ on completion (handled by subdialog completion)
|
|
3128
|
+
await driveDialogStream(sub, initPrompt, true);
|
|
3129
|
+
}
|
|
3130
|
+
catch (err) {
|
|
3131
|
+
log_1.log.warn('Type C subdialog processing error:', err);
|
|
3132
|
+
}
|
|
3133
|
+
})();
|
|
3134
|
+
void task;
|
|
3135
|
+
subdialogsCreated.push(sub.id);
|
|
3136
|
+
suspend = true;
|
|
3137
|
+
}
|
|
3138
|
+
catch (err) {
|
|
3139
|
+
log_1.log.warn('Subdialog creation error:', err);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
else {
|
|
3144
|
+
// Not a team member: tellask is reserved for teammate tellasks.
|
|
3145
|
+
// All tools (including dialog control tools) must use native function-calling.
|
|
3146
|
+
const msg = (0, driver_messages_1.formatDomindsNoteTellaskForTeammatesOnly)((0, runtime_language_1.getWorkLanguage)(), { firstMention });
|
|
3147
|
+
toolOutputs.push({ type: 'environment_msg', role: 'user', content: msg });
|
|
3148
|
+
toolOutputs.push({
|
|
3149
|
+
type: 'call_result_msg',
|
|
3150
|
+
role: 'tool',
|
|
3151
|
+
responderId: 'dominds',
|
|
3152
|
+
headLine,
|
|
3153
|
+
status: 'failed',
|
|
3154
|
+
content: msg,
|
|
3155
|
+
});
|
|
3156
|
+
await dlg.receiveToolResponse('dominds', headLine, msg, 'failed', callId);
|
|
3157
|
+
dlg.clearCurrentCallId();
|
|
3158
|
+
}
|
|
3159
|
+
return { toolOutputs, suspend, subdialogsCreated };
|
|
3160
|
+
}
|
|
3161
|
+
function isValidMentionChar(char) {
|
|
3162
|
+
const charCode = char.charCodeAt(0);
|
|
3163
|
+
return ((charCode >= 48 && charCode <= 57) ||
|
|
3164
|
+
(charCode >= 65 && charCode <= 90) ||
|
|
3165
|
+
(charCode >= 97 && charCode <= 122) ||
|
|
3166
|
+
char === '_' ||
|
|
3167
|
+
char === '-' ||
|
|
3168
|
+
char === '.' ||
|
|
3169
|
+
/\p{L}/u.test(char) ||
|
|
3170
|
+
/\p{N}/u.test(char));
|
|
3171
|
+
}
|
|
3172
|
+
function trimTrailingDots(value) {
|
|
3173
|
+
let out = value;
|
|
3174
|
+
while (out.endsWith('.'))
|
|
3175
|
+
out = out.slice(0, -1);
|
|
3176
|
+
return out;
|
|
3177
|
+
}
|
|
3178
|
+
function isMentionBoundaryChar(char) {
|
|
3179
|
+
if (char === '' || char === '\n' || char === '\t' || char === ' ')
|
|
3180
|
+
return true;
|
|
3181
|
+
return !isValidMentionChar(char);
|
|
3182
|
+
}
|
|
3183
|
+
function extractMentionIdsFromHeadline(headLine) {
|
|
3184
|
+
const out = [];
|
|
3185
|
+
const seen = new Set();
|
|
3186
|
+
for (let i = 0; i < headLine.length; i++) {
|
|
3187
|
+
const ch = headLine[i] ?? '';
|
|
3188
|
+
if (ch !== '@')
|
|
3189
|
+
continue;
|
|
3190
|
+
const prev = i === 0 ? '' : (headLine[i - 1] ?? '');
|
|
3191
|
+
if (!isMentionBoundaryChar(prev))
|
|
3192
|
+
continue;
|
|
3193
|
+
let j = i + 1;
|
|
3194
|
+
let raw = '';
|
|
3195
|
+
while (j < headLine.length) {
|
|
3196
|
+
const c = headLine[j] ?? '';
|
|
3197
|
+
if (c !== '' && isValidMentionChar(c)) {
|
|
3198
|
+
raw += c;
|
|
3199
|
+
j += 1;
|
|
3200
|
+
continue;
|
|
3201
|
+
}
|
|
3202
|
+
break;
|
|
3203
|
+
}
|
|
3204
|
+
const id = trimTrailingDots(raw);
|
|
3205
|
+
if (id === '')
|
|
3206
|
+
continue;
|
|
3207
|
+
if (!seen.has(id)) {
|
|
3208
|
+
seen.add(id);
|
|
3209
|
+
out.push(id);
|
|
3210
|
+
}
|
|
3211
|
+
i = j - 1;
|
|
3212
|
+
}
|
|
3213
|
+
return out;
|
|
3214
|
+
}
|