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,1011 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleWebSocketMessage = handleWebSocketMessage;
|
|
4
|
+
exports.setupWebSocketServer = setupWebSocketServer;
|
|
5
|
+
exports.cleanupEventSystems = cleanupEventSystems;
|
|
6
|
+
const ws_1 = require("ws");
|
|
7
|
+
const dialog_1 = require("../dialog");
|
|
8
|
+
const dialog_global_registry_1 = require("../dialog-global-registry");
|
|
9
|
+
const dialog_instance_registry_1 = require("../dialog-instance-registry");
|
|
10
|
+
const dialog_run_state_1 = require("../dialog-run-state");
|
|
11
|
+
const evt_registry_1 = require("../evt-registry");
|
|
12
|
+
const driver_1 = require("../llm/driver");
|
|
13
|
+
const log_1 = require("../log");
|
|
14
|
+
const persistence_1 = require("../persistence");
|
|
15
|
+
const problems_1 = require("../problems");
|
|
16
|
+
const diligence_1 = require("../shared/diligence");
|
|
17
|
+
const evt_1 = require("../shared/evt");
|
|
18
|
+
const runtime_language_1 = require("../shared/runtime-language");
|
|
19
|
+
const language_1 = require("../shared/types/language");
|
|
20
|
+
const time_1 = require("../shared/utils/time");
|
|
21
|
+
const team_1 = require("../team");
|
|
22
|
+
const id_1 = require("../utils/id");
|
|
23
|
+
const task_package_1 = require("../utils/task-package");
|
|
24
|
+
const auth_1 = require("./auth");
|
|
25
|
+
function resolveMemberDiligencePushMax(team, agentId) {
|
|
26
|
+
const member = team.getMember(agentId);
|
|
27
|
+
if (member && member.diligence_push_max !== undefined) {
|
|
28
|
+
return member.diligence_push_max;
|
|
29
|
+
}
|
|
30
|
+
return diligence_1.DEFAULT_DILIGENCE_PUSH_MAX;
|
|
31
|
+
}
|
|
32
|
+
const log = (0, log_1.createLogger)('websocket-handler');
|
|
33
|
+
const wsLiveDlg = new WeakMap();
|
|
34
|
+
const wsSub = new WeakMap();
|
|
35
|
+
const wsUiLanguage = new WeakMap();
|
|
36
|
+
let broadcastDialogsIndexMessage = null;
|
|
37
|
+
function resolveUserLanguageCode(ws, raw, fallbackDialog) {
|
|
38
|
+
if (typeof raw === 'string') {
|
|
39
|
+
const parsed = (0, language_1.normalizeLanguageCode)(raw);
|
|
40
|
+
if (parsed)
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
const fromWs = wsUiLanguage.get(ws);
|
|
44
|
+
if (fromWs)
|
|
45
|
+
return fromWs;
|
|
46
|
+
if (fallbackDialog)
|
|
47
|
+
return fallbackDialog.getLastUserLanguageCode();
|
|
48
|
+
return (0, runtime_language_1.getWorkLanguage)();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get error code from unknown error
|
|
52
|
+
*/
|
|
53
|
+
function getErrorCode(error) {
|
|
54
|
+
if (typeof error !== 'object' || error === null)
|
|
55
|
+
return undefined;
|
|
56
|
+
const maybeCode = error.code;
|
|
57
|
+
return typeof maybeCode === 'string' ? maybeCode : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cleanup WebSocket client: cancel active forwarder and clear live dialog state
|
|
61
|
+
*/
|
|
62
|
+
function cleanupWsClient(ws) {
|
|
63
|
+
const existingSub = wsSub.get(ws);
|
|
64
|
+
if (existingSub) {
|
|
65
|
+
try {
|
|
66
|
+
existingSub.subChan.cancel();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
log.warn('Failed to cancel forwarder on cleanupWsClient', err);
|
|
70
|
+
}
|
|
71
|
+
wsSub.delete(ws);
|
|
72
|
+
}
|
|
73
|
+
wsLiveDlg.delete(ws);
|
|
74
|
+
wsUiLanguage.delete(ws);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Setup WebSocket subscription for real-time dialog events
|
|
78
|
+
* Ensures only one subscription per WebSocket connection
|
|
79
|
+
*/
|
|
80
|
+
async function setupWebSocketSubscription(ws, dialog) {
|
|
81
|
+
// Cancel any existing subscription
|
|
82
|
+
const existingSub = wsSub.get(ws);
|
|
83
|
+
if (existingSub) {
|
|
84
|
+
try {
|
|
85
|
+
existingSub.subChan.cancel();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
log.warn('Failed to cancel existing subscription', undefined, err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Store dialog in wsLiveDlg
|
|
92
|
+
wsLiveDlg.set(ws, dialog);
|
|
93
|
+
// Create new subscription for real-time events
|
|
94
|
+
const subChan = evt_registry_1.dialogEventRegistry.createSubChan(dialog.id);
|
|
95
|
+
wsSub.set(ws, { dialogKey: dialog.id.valueOf(), subChan });
|
|
96
|
+
// Forward events from SubChan to WebSocket
|
|
97
|
+
(async () => {
|
|
98
|
+
try {
|
|
99
|
+
for await (const event of subChan.stream()) {
|
|
100
|
+
if (ws.readyState === 1) {
|
|
101
|
+
ws.send(JSON.stringify(event));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (err !== evt_1.EndOfStream) {
|
|
110
|
+
log.warn(`Event forwarding error for dialog ${dialog.id.selfId}:`, err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})().catch((err) => {
|
|
114
|
+
log.warn(`Event forwarding task failed for dialog ${dialog.id.selfId}:`, err);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Handle incoming WebSocket messages
|
|
119
|
+
*/
|
|
120
|
+
async function handleWebSocketMessage(ws, packet) {
|
|
121
|
+
try {
|
|
122
|
+
switch (packet.type) {
|
|
123
|
+
case 'set_ui_language':
|
|
124
|
+
await handleSetUiLanguage(ws, packet);
|
|
125
|
+
break;
|
|
126
|
+
case 'get_problems':
|
|
127
|
+
await handleGetProblems(ws, packet);
|
|
128
|
+
break;
|
|
129
|
+
case 'create_dialog':
|
|
130
|
+
await handleCreateDialog(ws, packet);
|
|
131
|
+
break;
|
|
132
|
+
case 'display_dialog':
|
|
133
|
+
await handleDisplayDialog(ws, packet);
|
|
134
|
+
break;
|
|
135
|
+
case 'set_diligence_push':
|
|
136
|
+
await handleSetDiligencePush(ws, packet);
|
|
137
|
+
break;
|
|
138
|
+
case 'get_q4h_state':
|
|
139
|
+
await handleGetQ4HState(ws, packet);
|
|
140
|
+
break;
|
|
141
|
+
case 'display_reminders':
|
|
142
|
+
await handleDisplayReminders(ws, packet);
|
|
143
|
+
break;
|
|
144
|
+
case 'display_round':
|
|
145
|
+
await handleDisplayRound(ws, packet);
|
|
146
|
+
break;
|
|
147
|
+
case 'drive_dlg_by_user_msg':
|
|
148
|
+
await handleUserMsg2Dlg(ws, packet);
|
|
149
|
+
break;
|
|
150
|
+
case 'drive_dialog_by_user_answer':
|
|
151
|
+
await handleUserAnswer2Q4H(ws, packet);
|
|
152
|
+
break;
|
|
153
|
+
case 'interrupt_dialog':
|
|
154
|
+
await handleInterruptDialog(ws, packet);
|
|
155
|
+
break;
|
|
156
|
+
case 'emergency_stop':
|
|
157
|
+
await handleEmergencyStop(ws, packet);
|
|
158
|
+
break;
|
|
159
|
+
case 'resume_dialog':
|
|
160
|
+
await handleResumeDialog(ws, packet);
|
|
161
|
+
break;
|
|
162
|
+
case 'resume_all':
|
|
163
|
+
await handleResumeAll(ws, packet);
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
log.warn('Unknown WebSocket packet type:', undefined, packet.type);
|
|
167
|
+
ws.send(JSON.stringify({
|
|
168
|
+
type: 'error',
|
|
169
|
+
message: `Unknown packet type: ${packet.type}`,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
log.error('Error processing WebSocket packet:', error);
|
|
175
|
+
ws.send(JSON.stringify({
|
|
176
|
+
type: 'error',
|
|
177
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function handleSetDiligencePush(ws, packet) {
|
|
182
|
+
try {
|
|
183
|
+
const { dialog, disableDiligencePush } = packet;
|
|
184
|
+
if (!isRecord(dialog)) {
|
|
185
|
+
ws.send(JSON.stringify({ type: 'error', message: 'dialog is required' }));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const selfId = typeof dialog.selfId === 'string' ? dialog.selfId : null;
|
|
189
|
+
const rootId = typeof dialog.rootId === 'string' ? dialog.rootId : null;
|
|
190
|
+
if (!selfId || !rootId) {
|
|
191
|
+
ws.send(JSON.stringify({
|
|
192
|
+
type: 'error',
|
|
193
|
+
message: 'Invalid dialog identifiers for set_diligence_push: selfId/rootId must be strings',
|
|
194
|
+
}));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (typeof disableDiligencePush !== 'boolean') {
|
|
198
|
+
ws.send(JSON.stringify({ type: 'error', message: 'disableDiligencePush must be a boolean' }));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const dialogIdObj = new dialog_1.DialogID(selfId, rootId);
|
|
202
|
+
// Locate dialog status (running/completed/archived) for persistence.
|
|
203
|
+
const statuses = [
|
|
204
|
+
'running',
|
|
205
|
+
'completed',
|
|
206
|
+
'archived',
|
|
207
|
+
];
|
|
208
|
+
let foundStatus = null;
|
|
209
|
+
for (const status of statuses) {
|
|
210
|
+
const meta = await persistence_1.DialogPersistence.loadDialogMetadata(dialogIdObj, status);
|
|
211
|
+
if (!meta)
|
|
212
|
+
continue;
|
|
213
|
+
foundStatus = status;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
if (!foundStatus) {
|
|
217
|
+
ws.send(JSON.stringify({ type: 'error', message: `Dialog ${dialogIdObj.valueOf()} not found` }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
await persistence_1.DialogPersistence.mutateDialogLatest(dialogIdObj, (previous) => ({
|
|
221
|
+
kind: 'patch',
|
|
222
|
+
patch: { disableDiligencePush },
|
|
223
|
+
}), foundStatus);
|
|
224
|
+
// Update live in-memory instance if it's loaded.
|
|
225
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogIdObj.rootId, foundStatus);
|
|
226
|
+
if (rootDialog) {
|
|
227
|
+
const target = dialogIdObj.selfId === dialogIdObj.rootId
|
|
228
|
+
? rootDialog
|
|
229
|
+
: await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogIdObj, foundStatus);
|
|
230
|
+
if (target) {
|
|
231
|
+
target.disableDiligencePush = disableDiligencePush;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const msg = {
|
|
235
|
+
type: 'diligence_push_updated',
|
|
236
|
+
dialog: { selfId, rootId },
|
|
237
|
+
disableDiligencePush,
|
|
238
|
+
timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
239
|
+
};
|
|
240
|
+
ws.send(JSON.stringify(msg));
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
log.warn('Failed to handle set_diligence_push', error);
|
|
244
|
+
ws.send(JSON.stringify({
|
|
245
|
+
type: 'error',
|
|
246
|
+
message: error instanceof Error ? error.message : 'Unknown error updating diligence push setting',
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function handleGetProblems(ws, packet) {
|
|
251
|
+
if (packet.type !== 'get_problems') {
|
|
252
|
+
throw new Error('Internal error: handleGetProblems called with non get_problems packet');
|
|
253
|
+
}
|
|
254
|
+
const _req = packet;
|
|
255
|
+
ws.send(JSON.stringify((0, problems_1.createProblemsSnapshotMessage)()));
|
|
256
|
+
}
|
|
257
|
+
async function handleSetUiLanguage(ws, packet) {
|
|
258
|
+
if (packet.type !== 'set_ui_language') {
|
|
259
|
+
throw new Error('Internal error: handleSetUiLanguage called with non set_ui_language packet');
|
|
260
|
+
}
|
|
261
|
+
const raw = packet.uiLanguage;
|
|
262
|
+
if (typeof raw !== 'string') {
|
|
263
|
+
ws.send(JSON.stringify({ type: 'error', message: 'uiLanguage must be a string' }));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const parsed = (0, language_1.normalizeLanguageCode)(raw);
|
|
267
|
+
if (!parsed) {
|
|
268
|
+
ws.send(JSON.stringify({
|
|
269
|
+
type: 'error',
|
|
270
|
+
message: `Unsupported uiLanguage '${raw}'. Supported: ${language_1.supportedLanguageCodes.join(', ')}`,
|
|
271
|
+
}));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
wsUiLanguage.set(ws, parsed);
|
|
275
|
+
ws.send(JSON.stringify({ type: 'ui_language_set', uiLanguage: parsed }));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handle dialog creation via WebSocket
|
|
279
|
+
*/
|
|
280
|
+
async function handleCreateDialog(ws, packet) {
|
|
281
|
+
try {
|
|
282
|
+
const { agentId, taskDocPath } = packet;
|
|
283
|
+
// Validate that taskDocPath is provided (it's now mandatory)
|
|
284
|
+
if (!taskDocPath || taskDocPath.trim() === '') {
|
|
285
|
+
throw new Error('Task Doc path is required for creating a dialog');
|
|
286
|
+
}
|
|
287
|
+
if (!(0, task_package_1.isTaskPackagePath)(taskDocPath)) {
|
|
288
|
+
throw new Error(`Task Doc must be a directory ending in '.tsk' (got: '${taskDocPath}')`);
|
|
289
|
+
}
|
|
290
|
+
// Auto-fill default_responder if no agentId provided
|
|
291
|
+
let finalAgentId = agentId;
|
|
292
|
+
if (!finalAgentId) {
|
|
293
|
+
try {
|
|
294
|
+
const teamConfig = await team_1.Team.load();
|
|
295
|
+
const def = teamConfig.getDefaultResponder();
|
|
296
|
+
finalAgentId = def ? def.id : undefined;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
throw new Error(`Failed to load team configuration: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!finalAgentId) {
|
|
303
|
+
throw new Error('No team members available to create a dialog');
|
|
304
|
+
}
|
|
305
|
+
const generatedId = (0, id_1.generateDialogID)();
|
|
306
|
+
// For root dialogs, self and root are the same
|
|
307
|
+
const dialogId = new dialog_1.DialogID(generatedId);
|
|
308
|
+
// Import Dialog and DiskFileDialogStore
|
|
309
|
+
// Create DiskFileDialogStore for file-based persistence
|
|
310
|
+
const dialogUI = new persistence_1.DiskFileDialogStore(dialogId);
|
|
311
|
+
// Create RootDialog instance with the new store
|
|
312
|
+
const dialog = new dialog_1.RootDialog(dialogUI, taskDocPath, dialogId, finalAgentId);
|
|
313
|
+
dialog_global_registry_1.globalDialogRegistry.register(dialog);
|
|
314
|
+
// Setup WebSocket subscription for real-time events
|
|
315
|
+
await setupWebSocketSubscription(ws, dialog);
|
|
316
|
+
// Persist dialog metadata and latest.yaml (write-once pattern)
|
|
317
|
+
const metadata = {
|
|
318
|
+
id: dialogId.selfId,
|
|
319
|
+
agentId: finalAgentId,
|
|
320
|
+
taskDocPath: taskDocPath,
|
|
321
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
322
|
+
};
|
|
323
|
+
await persistence_1.DialogPersistence.saveDialogMetadata(new dialog_1.DialogID(dialogId.selfId), metadata);
|
|
324
|
+
// Initialize latest.yaml via the mutation API (write-back will flush).
|
|
325
|
+
await persistence_1.DialogPersistence.mutateDialogLatest(new dialog_1.DialogID(dialogId.selfId), () => ({
|
|
326
|
+
kind: 'replace',
|
|
327
|
+
next: {
|
|
328
|
+
currentRound: 1,
|
|
329
|
+
lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
330
|
+
status: 'active',
|
|
331
|
+
messageCount: 0,
|
|
332
|
+
functionCallCount: 0,
|
|
333
|
+
subdialogCount: 0,
|
|
334
|
+
runState: { kind: 'idle_waiting_user' },
|
|
335
|
+
disableDiligencePush: false,
|
|
336
|
+
},
|
|
337
|
+
}));
|
|
338
|
+
// Send dialog_ready with full info so frontend can track the active dialog
|
|
339
|
+
const team = await team_1.Team.load();
|
|
340
|
+
const response = {
|
|
341
|
+
type: 'dialog_ready',
|
|
342
|
+
dialog: {
|
|
343
|
+
selfId: dialogId.selfId,
|
|
344
|
+
rootId: dialogId.rootId,
|
|
345
|
+
},
|
|
346
|
+
agentId: finalAgentId,
|
|
347
|
+
taskDocPath: taskDocPath,
|
|
348
|
+
disableDiligencePush: false,
|
|
349
|
+
diligencePushMax: resolveMemberDiligencePushMax(team, finalAgentId),
|
|
350
|
+
};
|
|
351
|
+
ws.send(JSON.stringify(response));
|
|
352
|
+
broadcastDialogsIndexMessage?.({
|
|
353
|
+
type: 'dialogs_created',
|
|
354
|
+
scope: { kind: 'root', rootId: dialogId.selfId },
|
|
355
|
+
status: 'running',
|
|
356
|
+
createdRootIds: [dialogId.selfId],
|
|
357
|
+
timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
log.warn('Failed to create dialog', undefined, error);
|
|
362
|
+
ws.send(JSON.stringify({
|
|
363
|
+
type: 'error',
|
|
364
|
+
message: error instanceof Error ? error.message : 'Unknown error creating dialog',
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Handle dialog retrieval via WebSocket
|
|
370
|
+
*/
|
|
371
|
+
async function handleDisplayDialog(ws, packet) {
|
|
372
|
+
try {
|
|
373
|
+
const { dialog: dialogIdent } = packet;
|
|
374
|
+
if (!dialogIdent) {
|
|
375
|
+
throw new Error('dialog is required');
|
|
376
|
+
}
|
|
377
|
+
// Extract dialog ID from DialogIdent
|
|
378
|
+
let dialogId = dialogIdent.selfId;
|
|
379
|
+
let rootDialogId = dialogIdent.rootId;
|
|
380
|
+
// Handle case where dialogIdent properties might be objects instead of strings
|
|
381
|
+
if (typeof dialogId !== 'string' || typeof rootDialogId !== 'string') {
|
|
382
|
+
ws.send(JSON.stringify({
|
|
383
|
+
type: 'error',
|
|
384
|
+
message: 'Invalid dialog identifiers for display_dialog: selfId/rootId must be strings',
|
|
385
|
+
}));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
// IMPORTANT: cancel any existing event forwarder before emitting restoration events.
|
|
389
|
+
// Otherwise, the same client can receive overlapping "replay" and "live" streams,
|
|
390
|
+
// which surfaces as duplicate generation lifecycle events on the frontend.
|
|
391
|
+
const existing = wsLiveDlg.get(ws);
|
|
392
|
+
if (existing) {
|
|
393
|
+
const existingId = existing.id;
|
|
394
|
+
const isSameDialog = existingId.selfId === dialogId && existingId.rootId === rootDialogId;
|
|
395
|
+
if (isSameDialog) {
|
|
396
|
+
log.warn('display_dialog: refreshing the same dialog; cancelling existing subscription to prevent duplicate stream events', undefined, { dialogId, rootDialogId });
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
log.debug('display_dialog: switching dialogs; cancelling previous subscription', undefined, {
|
|
400
|
+
previousDialogId: existingId.valueOf(),
|
|
401
|
+
nextDialogId: new dialog_1.DialogID(dialogId, rootDialogId).valueOf(),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
cleanupWsClient(ws);
|
|
405
|
+
}
|
|
406
|
+
const dialogIdObj = new dialog_1.DialogID(dialogId, rootDialogId);
|
|
407
|
+
const statuses = [
|
|
408
|
+
'running',
|
|
409
|
+
'completed',
|
|
410
|
+
'archived',
|
|
411
|
+
];
|
|
412
|
+
let foundStatus = null;
|
|
413
|
+
let dialogState = null;
|
|
414
|
+
let metadata = null;
|
|
415
|
+
for (const status of statuses) {
|
|
416
|
+
const state = await persistence_1.DialogPersistence.restoreDialog(dialogIdObj, status);
|
|
417
|
+
if (!state)
|
|
418
|
+
continue;
|
|
419
|
+
const meta = await persistence_1.DialogPersistence.loadDialogMetadata(dialogIdObj, status);
|
|
420
|
+
if (!meta)
|
|
421
|
+
continue;
|
|
422
|
+
foundStatus = status;
|
|
423
|
+
dialogState = state;
|
|
424
|
+
metadata = meta;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
if (!foundStatus || !dialogState || !metadata) {
|
|
428
|
+
throw new Error('Dialog not found');
|
|
429
|
+
}
|
|
430
|
+
const decidedRound = (await persistence_1.DialogPersistence.getCurrentRoundNumber(dialogIdObj, foundStatus)) ||
|
|
431
|
+
(dialogState.currentRound ?? 1);
|
|
432
|
+
const enableLive = foundStatus === 'running';
|
|
433
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogIdObj.rootId, foundStatus);
|
|
434
|
+
if (!rootDialog) {
|
|
435
|
+
throw new Error('Root dialog not found');
|
|
436
|
+
}
|
|
437
|
+
if (enableLive) {
|
|
438
|
+
dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
|
|
439
|
+
}
|
|
440
|
+
let dialog;
|
|
441
|
+
if (dialogIdObj.selfId === dialogIdObj.rootId) {
|
|
442
|
+
dialog = rootDialog;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
const loaded = await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogIdObj, foundStatus);
|
|
446
|
+
if (!loaded) {
|
|
447
|
+
throw new Error('Dialog not found');
|
|
448
|
+
}
|
|
449
|
+
dialog = loaded;
|
|
450
|
+
}
|
|
451
|
+
// CRITICAL FIX: Send dialog events directly to requesting WebSocket only
|
|
452
|
+
// This bypasses PubChan to ensure only the requesting session receives restoration events
|
|
453
|
+
// Pass decidedRound explicitly since dialog.currentRound defaults to 1 for new Dialog objects
|
|
454
|
+
try {
|
|
455
|
+
const dialogStore = dialog.dlgStore;
|
|
456
|
+
if (dialogStore instanceof persistence_1.DiskFileDialogStore) {
|
|
457
|
+
await dialogStore.sendDialogEventsDirectly(ws, dialog, decidedRound, decidedRound, foundStatus);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
throw new Error('Unexpected dialog store type for sendDialogEventsDirectly');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
log.warn(`Failed to send dialog events directly for ${dialogId}:`, err);
|
|
465
|
+
}
|
|
466
|
+
// Always subscribe for future realtime events (including cross-client revival + continued drive).
|
|
467
|
+
// Live generation is still gated by dialog.status ('running') in drive handlers.
|
|
468
|
+
await setupWebSocketSubscription(ws, dialog);
|
|
469
|
+
// Send dialog_ready with full info so frontend knows the current dialog ID
|
|
470
|
+
const team = await team_1.Team.load();
|
|
471
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogIdObj, foundStatus);
|
|
472
|
+
const dialogReadyResponse = {
|
|
473
|
+
type: 'dialog_ready',
|
|
474
|
+
dialog: {
|
|
475
|
+
selfId: dialogId,
|
|
476
|
+
rootId: rootDialogId,
|
|
477
|
+
},
|
|
478
|
+
agentId: metadata.agentId,
|
|
479
|
+
taskDocPath: metadata.taskDocPath,
|
|
480
|
+
supdialogId: metadata.supdialogId,
|
|
481
|
+
tellaskSession: metadata.tellaskSession,
|
|
482
|
+
assignmentFromSup: metadata.assignmentFromSup,
|
|
483
|
+
disableDiligencePush: latest?.disableDiligencePush ?? false,
|
|
484
|
+
diligencePushMax: resolveMemberDiligencePushMax(team, metadata.agentId),
|
|
485
|
+
};
|
|
486
|
+
ws.send(JSON.stringify(dialogReadyResponse));
|
|
487
|
+
// Send authoritative run state for this dialog so the client can render Send↔Stop and Continue.
|
|
488
|
+
try {
|
|
489
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogIdObj, foundStatus);
|
|
490
|
+
const runState = latest?.runState ??
|
|
491
|
+
(foundStatus === 'running'
|
|
492
|
+
? { kind: 'idle_waiting_user' }
|
|
493
|
+
: foundStatus === 'completed'
|
|
494
|
+
? { kind: 'terminal', status: 'completed' }
|
|
495
|
+
: { kind: 'terminal', status: 'archived' });
|
|
496
|
+
const runStateEvt = evt_registry_1.dialogEventRegistry.createTypedEvent(dialogIdObj, {
|
|
497
|
+
type: 'dlg_run_state_evt',
|
|
498
|
+
runState,
|
|
499
|
+
});
|
|
500
|
+
ws.send(JSON.stringify(runStateEvt));
|
|
501
|
+
}
|
|
502
|
+
catch (err) {
|
|
503
|
+
log.warn(`Failed to send dlg_run_state_evt for ${dialogIdObj.valueOf()}:`, err);
|
|
504
|
+
}
|
|
505
|
+
// Emit Q4H state to ensure frontend has current questions count
|
|
506
|
+
// Load Q4H from ALL running dialogs for global display (not just this dialog)
|
|
507
|
+
try {
|
|
508
|
+
const allQuestions = await persistence_1.DialogPersistence.loadAllQ4HState();
|
|
509
|
+
// Emit new_q4h_asked events for each question (best-effort sync on dialog display).
|
|
510
|
+
// Include full dialog context (selfId/rootId/agentId/taskDocPath) so the frontend can
|
|
511
|
+
// render origin info without relying on additional lookups.
|
|
512
|
+
for (const q of allQuestions) {
|
|
513
|
+
const newQ4HEvent = {
|
|
514
|
+
type: 'new_q4h_asked',
|
|
515
|
+
question: {
|
|
516
|
+
id: q.id,
|
|
517
|
+
kind: q.kind,
|
|
518
|
+
selfId: q.selfId,
|
|
519
|
+
rootId: q.rootId,
|
|
520
|
+
agentId: q.agentId,
|
|
521
|
+
taskDocPath: q.taskDocPath,
|
|
522
|
+
headLine: q.headLine,
|
|
523
|
+
bodyContent: q.bodyContent,
|
|
524
|
+
askedAt: q.askedAt,
|
|
525
|
+
callSiteRef: q.callSiteRef,
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
ws.send(JSON.stringify(newQ4HEvent));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (err) {
|
|
532
|
+
log.warn(`Failed to emit Q4H state for ${dialogIdObj}:`, err);
|
|
533
|
+
}
|
|
534
|
+
// Proactively emit reminders for the newly active dialog
|
|
535
|
+
// todo: maybe emit only to the requestiong websocket, not publish via PubChan as curr impl
|
|
536
|
+
try {
|
|
537
|
+
await dialog.processReminderUpdates();
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
log.warn(`Failed to emit proactive reminders for ${dialogIdObj}:`, err);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
log.warn('Failed to handle display_dialog', error);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Handle Q4H state request via WebSocket
|
|
549
|
+
* Fetches Q4H questions from ALL running dialogs for global display
|
|
550
|
+
*/
|
|
551
|
+
async function handleGetQ4HState(ws, _packet) {
|
|
552
|
+
try {
|
|
553
|
+
// Load Q4H from all running dialogs
|
|
554
|
+
const allQuestions = await persistence_1.DialogPersistence.loadAllQ4HState();
|
|
555
|
+
// Transform to wire `Q4HStateResponse` question entries.
|
|
556
|
+
// `selfId` + `rootId` uniquely identify the originating dialog (including subdialogs).
|
|
557
|
+
const questions = allQuestions.map((q) => ({
|
|
558
|
+
id: q.id,
|
|
559
|
+
kind: q.kind,
|
|
560
|
+
selfId: q.selfId,
|
|
561
|
+
rootId: q.rootId,
|
|
562
|
+
agentId: q.agentId,
|
|
563
|
+
taskDocPath: q.taskDocPath,
|
|
564
|
+
headLine: q.headLine,
|
|
565
|
+
bodyContent: q.bodyContent,
|
|
566
|
+
askedAt: q.askedAt,
|
|
567
|
+
callSiteRef: q.callSiteRef,
|
|
568
|
+
}));
|
|
569
|
+
// Send single response packet with all questions (not PubChan events)
|
|
570
|
+
const response = {
|
|
571
|
+
type: 'q4h_state_response',
|
|
572
|
+
questions,
|
|
573
|
+
};
|
|
574
|
+
ws.send(JSON.stringify(response));
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
log.warn('Failed to handle get_q4h_state', error);
|
|
578
|
+
ws.send(JSON.stringify({
|
|
579
|
+
type: 'error',
|
|
580
|
+
message: error instanceof Error ? error.message : 'Unknown error getting Q4H state',
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
async function handleDisplayReminders(ws, packet) {
|
|
585
|
+
try {
|
|
586
|
+
const live = wsLiveDlg.get(ws);
|
|
587
|
+
if (!live) {
|
|
588
|
+
log.warn('No live dialog found for display_reminders');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (live.id.selfId !== packet.dialog.selfId) {
|
|
592
|
+
log.warn(`Dialog ${packet.dialog} for reminders is not current live (live dialog is ${live.id})`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
await live.processReminderUpdates();
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
log.warn('Failed to display reminders', error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function handleDisplayRound(ws, packet) {
|
|
602
|
+
try {
|
|
603
|
+
const { dialog, round } = packet;
|
|
604
|
+
if (!dialog || typeof round !== 'number') {
|
|
605
|
+
throw new Error('dialog and round are required');
|
|
606
|
+
}
|
|
607
|
+
// Extract dialog ID from DialogIdent
|
|
608
|
+
let dialogIdStr = dialog.selfId;
|
|
609
|
+
let rootDialogIdStr = dialog.rootId;
|
|
610
|
+
// Handle case where dialog properties might be objects instead of strings
|
|
611
|
+
if (typeof dialogIdStr !== 'string' || typeof rootDialogIdStr !== 'string') {
|
|
612
|
+
ws.send(JSON.stringify({
|
|
613
|
+
type: 'error',
|
|
614
|
+
message: 'Invalid dialog identifiers for display_round: selfId/rootId must be strings',
|
|
615
|
+
}));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const dialogId = new dialog_1.DialogID(dialogIdStr, rootDialogIdStr);
|
|
619
|
+
try {
|
|
620
|
+
const statuses = [
|
|
621
|
+
'running',
|
|
622
|
+
'completed',
|
|
623
|
+
'archived',
|
|
624
|
+
];
|
|
625
|
+
let foundStatus = null;
|
|
626
|
+
let metadata = null;
|
|
627
|
+
for (const status of statuses) {
|
|
628
|
+
const meta = await persistence_1.DialogPersistence.loadDialogMetadata(dialogId, status);
|
|
629
|
+
if (!meta)
|
|
630
|
+
continue;
|
|
631
|
+
foundStatus = status;
|
|
632
|
+
metadata = meta;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
if (!foundStatus || !metadata) {
|
|
636
|
+
log.warn('Metadata not found for display_round', undefined, { dialogId: dialogId.selfId });
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const totalRounds = (await persistence_1.DialogPersistence.getCurrentRoundNumber(dialogId, foundStatus)) || round;
|
|
640
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogId.rootId, foundStatus);
|
|
641
|
+
if (!rootDialog)
|
|
642
|
+
return;
|
|
643
|
+
const dialog = dialogId.selfId === dialogId.rootId
|
|
644
|
+
? rootDialog
|
|
645
|
+
: await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogId, foundStatus);
|
|
646
|
+
if (!dialog)
|
|
647
|
+
return;
|
|
648
|
+
const store = dialog.dlgStore;
|
|
649
|
+
if (!(store instanceof persistence_1.DiskFileDialogStore)) {
|
|
650
|
+
throw new Error('Unexpected dialog store type for display_round');
|
|
651
|
+
}
|
|
652
|
+
// Send the requested round's persisted events directly to this WebSocket.
|
|
653
|
+
// This is a UI navigation operation; do not emit via PubChan.
|
|
654
|
+
await store.sendDialogEventsDirectly(ws, dialog, round, totalRounds, foundStatus);
|
|
655
|
+
}
|
|
656
|
+
catch (err) {
|
|
657
|
+
log.warn('Failed to send dialog events for display_round', err);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
log.warn('Failed to handle display_round', error);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Handle message sending via WebSocket
|
|
666
|
+
*/
|
|
667
|
+
async function handleUserMsg2Dlg(ws, packet) {
|
|
668
|
+
try {
|
|
669
|
+
const { dialog: dialogIdent, content, msgId } = packet;
|
|
670
|
+
const userLanguageCode = resolveUserLanguageCode(ws, packet.userLanguageCode);
|
|
671
|
+
// Basic validation
|
|
672
|
+
if (!dialogIdent || !content || !msgId) {
|
|
673
|
+
ws.send(JSON.stringify({
|
|
674
|
+
type: 'error',
|
|
675
|
+
message: 'dialog, content, and msgId are required',
|
|
676
|
+
}));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
// Extract dialog ID from DialogIdent
|
|
680
|
+
const dialogId = dialogIdent.selfId;
|
|
681
|
+
const rootDialogId = dialogIdent.rootId;
|
|
682
|
+
// Validate dialog identifiers
|
|
683
|
+
if (typeof dialogId !== 'string' || typeof rootDialogId !== 'string') {
|
|
684
|
+
ws.send(JSON.stringify({
|
|
685
|
+
type: 'error',
|
|
686
|
+
message: 'Invalid dialog identifiers for drive_dlg_by_user_msg: selfId/rootId must be strings',
|
|
687
|
+
}));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
// If the dialog is already active for this WebSocket, runnable (status === 'running'),
|
|
691
|
+
// and has an event forwarder (subChan),
|
|
692
|
+
// drive it directly to preserve in-memory state (pending subdialogs, teammate tellask tracking, etc).
|
|
693
|
+
//
|
|
694
|
+
// IMPORTANT: do not drive a view-only dialog instance here. When users browse a completed/archived
|
|
695
|
+
// dialog, handleDisplayDialog restores it with dialog.status set to completed/archived. If that
|
|
696
|
+
// dialog is later revived to running by another client, the UI may re-enable input without
|
|
697
|
+
// re-issuing display_dialog. In that case, we must restore from running rather than driving the
|
|
698
|
+
// cached view-only dialog (stale state, wrong hydration, etc).
|
|
699
|
+
const existingDialog = wsLiveDlg.get(ws);
|
|
700
|
+
const existingSub = wsSub.get(ws);
|
|
701
|
+
if (existingDialog &&
|
|
702
|
+
existingDialog.id.selfId === dialogId &&
|
|
703
|
+
existingDialog.id.rootId === rootDialogId &&
|
|
704
|
+
existingDialog.status === 'running' &&
|
|
705
|
+
existingSub &&
|
|
706
|
+
existingSub.dialogKey === existingDialog.id.valueOf()) {
|
|
707
|
+
await (0, driver_1.driveDialogStream)(existingDialog, { content, msgId, grammar: 'tellask', userLanguageCode }, true);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
// Dialog not found in wsLiveDlg - drive using the canonical root/subdialog instances.
|
|
711
|
+
// This supports driving subdialogs and cross-client revival without creating duplicate dialog objects.
|
|
712
|
+
try {
|
|
713
|
+
const dialogIdObj = new dialog_1.DialogID(dialogId, rootDialogId);
|
|
714
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogIdObj.rootId, 'running');
|
|
715
|
+
if (!rootDialog) {
|
|
716
|
+
ws.send(JSON.stringify({ type: 'error', message: `Dialog ${dialogId} not found` }));
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
|
|
720
|
+
const dialog = dialogIdObj.selfId === dialogIdObj.rootId
|
|
721
|
+
? rootDialog
|
|
722
|
+
: await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogIdObj, 'running');
|
|
723
|
+
if (!dialog) {
|
|
724
|
+
ws.send(JSON.stringify({ type: 'error', message: `Dialog ${dialogId} not found` }));
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
await setupWebSocketSubscription(ws, dialog);
|
|
728
|
+
await (0, driver_1.driveDialogStream)(dialog, { content, msgId, grammar: 'tellask', userLanguageCode }, true);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
catch (restoreError) {
|
|
732
|
+
log.warn('Failed to restore dialog for message:', restoreError);
|
|
733
|
+
ws.send(JSON.stringify({
|
|
734
|
+
type: 'error',
|
|
735
|
+
message: `Cannot send message to dialog ${dialogId}: dialog is not the currently active dialog and could not be restored`,
|
|
736
|
+
}));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
// Log the error at warning level in the backend console
|
|
742
|
+
log.warn(`Failed to drive dialog ${packet?.dialog?.selfId} with user message: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
|
|
743
|
+
// Send error response to client
|
|
744
|
+
ws.send(JSON.stringify({
|
|
745
|
+
type: 'error',
|
|
746
|
+
message: `Failed to process message: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
async function restoreDialogForDrive(dialogIdObj, status) {
|
|
751
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogIdObj.rootId, status);
|
|
752
|
+
if (!rootDialog) {
|
|
753
|
+
throw new Error(`Dialog ${dialogIdObj.valueOf()} not found`);
|
|
754
|
+
}
|
|
755
|
+
dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
|
|
756
|
+
if (dialogIdObj.selfId === dialogIdObj.rootId) {
|
|
757
|
+
return rootDialog;
|
|
758
|
+
}
|
|
759
|
+
const sub = await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogIdObj, status);
|
|
760
|
+
if (!sub) {
|
|
761
|
+
throw new Error(`Dialog ${dialogIdObj.valueOf()} not found`);
|
|
762
|
+
}
|
|
763
|
+
return sub;
|
|
764
|
+
}
|
|
765
|
+
async function handleInterruptDialog(ws, packet) {
|
|
766
|
+
if (packet.type !== 'interrupt_dialog') {
|
|
767
|
+
throw new Error('Internal error: handleInterruptDialog called with non interrupt_dialog packet');
|
|
768
|
+
}
|
|
769
|
+
const dialog = packet.dialog;
|
|
770
|
+
if (!dialog || typeof dialog.selfId !== 'string' || typeof dialog.rootId !== 'string') {
|
|
771
|
+
ws.send(JSON.stringify({ type: 'error', message: 'interrupt_dialog requires dialog.selfId/rootId' }));
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const dialogIdObj = new dialog_1.DialogID(dialog.selfId, dialog.rootId);
|
|
775
|
+
const res = await (0, dialog_run_state_1.requestInterruptDialog)(dialogIdObj, 'user_stop');
|
|
776
|
+
if (!res.applied) {
|
|
777
|
+
// Stop should be idempotent: a double-click (or concurrent stop) must not surface as an error.
|
|
778
|
+
// If the dialog is already not proceeding, treat as a no-op.
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async function handleEmergencyStop(ws, packet) {
|
|
783
|
+
if (packet.type !== 'emergency_stop') {
|
|
784
|
+
throw new Error('Internal error: handleEmergencyStop called with non emergency_stop packet');
|
|
785
|
+
}
|
|
786
|
+
await (0, dialog_run_state_1.requestEmergencyStopAll)();
|
|
787
|
+
}
|
|
788
|
+
async function handleResumeDialog(ws, packet) {
|
|
789
|
+
if (packet.type !== 'resume_dialog') {
|
|
790
|
+
throw new Error('Internal error: handleResumeDialog called with non resume_dialog packet');
|
|
791
|
+
}
|
|
792
|
+
const dialog = packet.dialog;
|
|
793
|
+
if (!dialog || typeof dialog.selfId !== 'string' || typeof dialog.rootId !== 'string') {
|
|
794
|
+
ws.send(JSON.stringify({ type: 'error', message: 'resume_dialog requires dialog.selfId/rootId' }));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const dialogIdObj = new dialog_1.DialogID(dialog.selfId, dialog.rootId);
|
|
798
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(dialogIdObj, 'running');
|
|
799
|
+
const runState = latest?.runState;
|
|
800
|
+
if (!runState || runState.kind !== 'interrupted') {
|
|
801
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Dialog is not eligible for resumption.' }));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const restored = await restoreDialogForDrive(dialogIdObj, 'running');
|
|
805
|
+
await (0, driver_1.driveDialogStream)(restored, undefined, true);
|
|
806
|
+
}
|
|
807
|
+
async function handleResumeAll(ws, packet) {
|
|
808
|
+
if (packet.type !== 'resume_all') {
|
|
809
|
+
throw new Error('Internal error: handleResumeAll called with non resume_all packet');
|
|
810
|
+
}
|
|
811
|
+
const dialogIds = await persistence_1.DialogPersistence.listAllDialogIds('running');
|
|
812
|
+
for (const id of dialogIds) {
|
|
813
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(id, 'running');
|
|
814
|
+
const runState = latest?.runState;
|
|
815
|
+
if (!runState || runState.kind !== 'interrupted')
|
|
816
|
+
continue;
|
|
817
|
+
void (async () => {
|
|
818
|
+
try {
|
|
819
|
+
const dlg = await restoreDialogForDrive(id, 'running');
|
|
820
|
+
await (0, driver_1.driveDialogStream)(dlg, undefined, true);
|
|
821
|
+
}
|
|
822
|
+
catch (err) {
|
|
823
|
+
log.warn('resume_all: failed to resume dialog', err, { dialogId: id.valueOf() });
|
|
824
|
+
}
|
|
825
|
+
})();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Handle user answer to a Q4H (Questions for Human) question
|
|
830
|
+
* Validates questionId, clears q4h.yaml entry, and resumes dialog with user's answer
|
|
831
|
+
*/
|
|
832
|
+
async function handleUserAnswer2Q4H(ws, packet) {
|
|
833
|
+
try {
|
|
834
|
+
const { dialog: dialogIdent, content, msgId, questionId } = packet;
|
|
835
|
+
const userLanguageCode = resolveUserLanguageCode(ws, packet.userLanguageCode);
|
|
836
|
+
// Basic validation
|
|
837
|
+
if (!dialogIdent || !content || !msgId || !questionId) {
|
|
838
|
+
ws.send(JSON.stringify({
|
|
839
|
+
type: 'error',
|
|
840
|
+
message: 'dialog, content, msgId, and questionId are required',
|
|
841
|
+
}));
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Extract dialog ID from DialogIdent
|
|
845
|
+
const dialogId = dialogIdent.selfId;
|
|
846
|
+
const rootDialogId = dialogIdent.rootId;
|
|
847
|
+
// Validate dialog identifiers
|
|
848
|
+
if (typeof dialogId !== 'string' || typeof rootDialogId !== 'string') {
|
|
849
|
+
ws.send(JSON.stringify({
|
|
850
|
+
type: 'error',
|
|
851
|
+
message: 'Invalid dialog identifiers for drive_dialog_by_user_answer: selfId/rootId must be strings',
|
|
852
|
+
}));
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const dialogIdObj = new dialog_1.DialogID(dialogId, rootDialogId);
|
|
856
|
+
// Load current questions from q4h.yaml
|
|
857
|
+
const questions = await persistence_1.DialogPersistence.loadQuestions4HumanState(dialogIdObj);
|
|
858
|
+
// Validate questionId exists
|
|
859
|
+
const questionIndex = questions.findIndex((q) => q.id === questionId);
|
|
860
|
+
if (questionIndex === -1) {
|
|
861
|
+
ws.send(JSON.stringify({
|
|
862
|
+
type: 'error',
|
|
863
|
+
message: `Question ${questionId} not found in dialog ${dialogId}`,
|
|
864
|
+
}));
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
// Remove answered question from the list
|
|
868
|
+
questions.splice(questionIndex, 1);
|
|
869
|
+
// Save updated questions to q4h.yaml
|
|
870
|
+
if (questions.length > 0) {
|
|
871
|
+
await persistence_1.DialogPersistence._saveQuestions4HumanState(dialogIdObj, questions);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
// No more questions - remove the q4h.yaml file
|
|
875
|
+
await persistence_1.DialogPersistence.clearQuestions4HumanState(dialogIdObj);
|
|
876
|
+
}
|
|
877
|
+
// Restore the canonical dialog instances (root + subdialogs) to avoid duplicates.
|
|
878
|
+
const rootDialog = await (0, dialog_instance_registry_1.getOrRestoreRootDialog)(dialogIdObj.rootId, 'running');
|
|
879
|
+
if (!rootDialog) {
|
|
880
|
+
ws.send(JSON.stringify({ type: 'error', message: `Dialog ${dialogId} not found` }));
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
dialog_global_registry_1.globalDialogRegistry.register(rootDialog);
|
|
884
|
+
const dialog = dialogIdObj.selfId === dialogIdObj.rootId
|
|
885
|
+
? rootDialog
|
|
886
|
+
: await (0, dialog_instance_registry_1.ensureDialogLoaded)(rootDialog, dialogIdObj, 'running');
|
|
887
|
+
if (!dialog) {
|
|
888
|
+
ws.send(JSON.stringify({ type: 'error', message: `Dialog ${dialogId} not found` }));
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
// Ensure the requesting WebSocket receives q4h_answered and subsequent resume stream events.
|
|
892
|
+
await setupWebSocketSubscription(ws, dialog);
|
|
893
|
+
// Emit q4h_answered event for answered question
|
|
894
|
+
const answeredEvent = {
|
|
895
|
+
type: 'q4h_answered',
|
|
896
|
+
questionId,
|
|
897
|
+
selfId: dialogId,
|
|
898
|
+
};
|
|
899
|
+
(0, evt_registry_1.postDialogEvent)(dialog, answeredEvent);
|
|
900
|
+
// Resume the dialog with the user's answer.
|
|
901
|
+
await (0, driver_1.driveDialogStream)(dialog, { content, msgId, grammar: 'tellask', userLanguageCode }, true);
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
log.error('Error processing Q4H user answer:', error);
|
|
905
|
+
ws.send(JSON.stringify({
|
|
906
|
+
type: 'error',
|
|
907
|
+
message: `Failed to process Q4H answer: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
908
|
+
}));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Setup WebSocket server with dialog handling
|
|
913
|
+
*/
|
|
914
|
+
function setupWebSocketServer(httpServer, clients, auth, serverWorkLanguage) {
|
|
915
|
+
const wss = new ws_1.WebSocketServer({ server: httpServer });
|
|
916
|
+
// Broadcast dialog run-state changes to all connected clients so multi-tab views converge.
|
|
917
|
+
(0, dialog_run_state_1.setRunStateBroadcaster)((msg) => {
|
|
918
|
+
const data = JSON.stringify(msg);
|
|
919
|
+
for (const ws of clients) {
|
|
920
|
+
if (ws.readyState === 1) {
|
|
921
|
+
ws.send(data);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
// Broadcast dialog index changes (create/move/delete) so other tabs refresh their lists.
|
|
926
|
+
// This ensures multi-tab/multi-browser updates stay consistent without polling.
|
|
927
|
+
broadcastDialogsIndexMessage = (msg) => {
|
|
928
|
+
const data = JSON.stringify(msg);
|
|
929
|
+
for (const ws of clients) {
|
|
930
|
+
if (ws.readyState === 1) {
|
|
931
|
+
ws.send(data);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
// Broadcast workspace Problems snapshots to all connected clients.
|
|
936
|
+
(0, problems_1.setProblemsBroadcaster)((msg) => {
|
|
937
|
+
const data = JSON.stringify(msg);
|
|
938
|
+
for (const ws of clients) {
|
|
939
|
+
if (ws.readyState === 1) {
|
|
940
|
+
ws.send(data);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
wss.on('connection', (ws, req) => {
|
|
945
|
+
const authCheck = (0, auth_1.getWebSocketAuthCheck)(req, auth);
|
|
946
|
+
if (authCheck.kind !== 'ok') {
|
|
947
|
+
ws.close(4401, 'unauthorized');
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
clients.add(ws);
|
|
951
|
+
wsUiLanguage.set(ws, serverWorkLanguage);
|
|
952
|
+
// Send welcome message
|
|
953
|
+
ws.send(JSON.stringify({
|
|
954
|
+
type: 'welcome',
|
|
955
|
+
message: 'Connected to dialog server',
|
|
956
|
+
serverWorkLanguage,
|
|
957
|
+
supportedLanguageCodes: [...language_1.supportedLanguageCodes],
|
|
958
|
+
timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
959
|
+
}));
|
|
960
|
+
// Send an initial snapshot so the UI can render a stable Problems indicator immediately.
|
|
961
|
+
ws.send(JSON.stringify((0, problems_1.createProblemsSnapshotMessage)()));
|
|
962
|
+
ws.on('message', async (data) => {
|
|
963
|
+
try {
|
|
964
|
+
const packet = JSON.parse(data.toString());
|
|
965
|
+
if (!isRecord(packet) || typeof packet.type !== 'string') {
|
|
966
|
+
throw new Error('Invalid packet format');
|
|
967
|
+
}
|
|
968
|
+
await handleWebSocketMessage(ws, packet);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
log.error('Error handling WebSocket packet:', error);
|
|
972
|
+
ws.send(JSON.stringify({
|
|
973
|
+
type: 'error',
|
|
974
|
+
message: 'Invalid packet format',
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
ws.on('close', () => {
|
|
979
|
+
clients.delete(ws);
|
|
980
|
+
// Clean up client subscriptions
|
|
981
|
+
cleanupWsClient(ws);
|
|
982
|
+
});
|
|
983
|
+
ws.on('error', (error) => {
|
|
984
|
+
log.error('WebSocket error:', error);
|
|
985
|
+
clients.delete(ws);
|
|
986
|
+
// Clean up client subscriptions on error
|
|
987
|
+
cleanupWsClient(ws);
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
return wss;
|
|
991
|
+
}
|
|
992
|
+
function isRecord(value) {
|
|
993
|
+
return typeof value === 'object' && value !== null;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Clean up all event channels and subscriptions
|
|
997
|
+
*/
|
|
998
|
+
function cleanupEventSystems() {
|
|
999
|
+
// Clear all WebSocket subscriptions (WeakMap will be garbage collected)
|
|
1000
|
+
// Just trigger cleanup of the event channel registry
|
|
1001
|
+
evt_registry_1.dialogEventRegistry.cleanup();
|
|
1002
|
+
}
|
|
1003
|
+
// Register cleanup on process exit
|
|
1004
|
+
process.on('SIGINT', () => {
|
|
1005
|
+
cleanupEventSystems();
|
|
1006
|
+
process.exit(0);
|
|
1007
|
+
});
|
|
1008
|
+
process.on('SIGTERM', () => {
|
|
1009
|
+
cleanupEventSystems();
|
|
1010
|
+
process.exit(0);
|
|
1011
|
+
});
|