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,3128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Module: persistence
|
|
4
|
+
*
|
|
5
|
+
* Modern dialog persistence with strong typing and latest.yaml support.
|
|
6
|
+
* Provides file-based storage with append-only events and atomic operations.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.DialogPersistence = exports.DiskFileDialogStore = void 0;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const yaml = __importStar(require("yaml"));
|
|
47
|
+
const dialog_1 = require("./dialog");
|
|
48
|
+
const evt_registry_1 = require("./evt-registry");
|
|
49
|
+
const log_1 = require("./log");
|
|
50
|
+
const async_fifo_mutex_1 = require("./shared/async-fifo-mutex");
|
|
51
|
+
const runtime_language_1 = require("./shared/runtime-language");
|
|
52
|
+
const inter_dialog_format_1 = require("./shared/utils/inter-dialog-format");
|
|
53
|
+
const time_1 = require("./shared/utils/time");
|
|
54
|
+
const registry_1 = require("./tools/registry");
|
|
55
|
+
function getErrorCode(error) {
|
|
56
|
+
if (typeof error !== 'object' || error === null)
|
|
57
|
+
return undefined;
|
|
58
|
+
const maybeCode = error.code;
|
|
59
|
+
return typeof maybeCode === 'string' ? maybeCode : undefined;
|
|
60
|
+
}
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
return typeof value === 'object' && value !== null;
|
|
63
|
+
}
|
|
64
|
+
function normalizeQ4HKind(value) {
|
|
65
|
+
return value === 'keep_going_budget_exhausted' ||
|
|
66
|
+
value === 'context_health_critical' ||
|
|
67
|
+
value === 'generic'
|
|
68
|
+
? value
|
|
69
|
+
: 'generic';
|
|
70
|
+
}
|
|
71
|
+
function isAssignmentFromSup(value) {
|
|
72
|
+
if (!isRecord(value))
|
|
73
|
+
return false;
|
|
74
|
+
if (typeof value.headLine !== 'string')
|
|
75
|
+
return false;
|
|
76
|
+
if (typeof value.callBody !== 'string')
|
|
77
|
+
return false;
|
|
78
|
+
if (typeof value.originMemberId !== 'string')
|
|
79
|
+
return false;
|
|
80
|
+
if (typeof value.callerDialogId !== 'string')
|
|
81
|
+
return false;
|
|
82
|
+
if (typeof value.callId !== 'string')
|
|
83
|
+
return false;
|
|
84
|
+
if ('collectiveTargets' in value) {
|
|
85
|
+
const collectiveTargets = value.collectiveTargets;
|
|
86
|
+
if (collectiveTargets === undefined)
|
|
87
|
+
return true;
|
|
88
|
+
if (!Array.isArray(collectiveTargets))
|
|
89
|
+
return false;
|
|
90
|
+
if (!collectiveTargets.every((item) => typeof item === 'string'))
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function isRootDialogMetadataFile(value) {
|
|
96
|
+
if (!isRecord(value))
|
|
97
|
+
return false;
|
|
98
|
+
if (typeof value.id !== 'string')
|
|
99
|
+
return false;
|
|
100
|
+
if (typeof value.agentId !== 'string')
|
|
101
|
+
return false;
|
|
102
|
+
if (typeof value.taskDocPath !== 'string')
|
|
103
|
+
return false;
|
|
104
|
+
if (typeof value.createdAt !== 'string')
|
|
105
|
+
return false;
|
|
106
|
+
if (value.supdialogId !== undefined)
|
|
107
|
+
return false;
|
|
108
|
+
if (value.tellaskSession !== undefined)
|
|
109
|
+
return false;
|
|
110
|
+
if (value.assignmentFromSup !== undefined)
|
|
111
|
+
return false;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function isSubdialogMetadataFile(value) {
|
|
115
|
+
if (!isRecord(value))
|
|
116
|
+
return false;
|
|
117
|
+
if (typeof value.id !== 'string')
|
|
118
|
+
return false;
|
|
119
|
+
if (typeof value.agentId !== 'string')
|
|
120
|
+
return false;
|
|
121
|
+
if (typeof value.taskDocPath !== 'string')
|
|
122
|
+
return false;
|
|
123
|
+
if (typeof value.createdAt !== 'string')
|
|
124
|
+
return false;
|
|
125
|
+
if (typeof value.supdialogId !== 'string')
|
|
126
|
+
return false;
|
|
127
|
+
if (value.tellaskSession !== undefined && typeof value.tellaskSession !== 'string')
|
|
128
|
+
return false;
|
|
129
|
+
if (!isAssignmentFromSup(value.assignmentFromSup))
|
|
130
|
+
return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function isDialogMetadataFile(value) {
|
|
134
|
+
return isRootDialogMetadataFile(value) || isSubdialogMetadataFile(value);
|
|
135
|
+
}
|
|
136
|
+
function isDialogLatestFile(value) {
|
|
137
|
+
if (!isRecord(value))
|
|
138
|
+
return false;
|
|
139
|
+
return (typeof value.currentRound === 'number' &&
|
|
140
|
+
typeof value.lastModified === 'string' &&
|
|
141
|
+
(value.status === 'active' || value.status === 'completed' || value.status === 'archived') &&
|
|
142
|
+
(value.disableDiligencePush === undefined || typeof value.disableDiligencePush === 'boolean'));
|
|
143
|
+
}
|
|
144
|
+
function isSubdialogResponseRecord(value) {
|
|
145
|
+
if (!isRecord(value))
|
|
146
|
+
return false;
|
|
147
|
+
if (typeof value.responseId !== 'string')
|
|
148
|
+
return false;
|
|
149
|
+
if (typeof value.subdialogId !== 'string')
|
|
150
|
+
return false;
|
|
151
|
+
if (typeof value.response !== 'string')
|
|
152
|
+
return false;
|
|
153
|
+
if (typeof value.completedAt !== 'string')
|
|
154
|
+
return false;
|
|
155
|
+
if (value.callType !== 'A' && value.callType !== 'B' && value.callType !== 'C')
|
|
156
|
+
return false;
|
|
157
|
+
if (typeof value.headLine !== 'string')
|
|
158
|
+
return false;
|
|
159
|
+
if (typeof value.responderId !== 'string')
|
|
160
|
+
return false;
|
|
161
|
+
if (typeof value.originMemberId !== 'string')
|
|
162
|
+
return false;
|
|
163
|
+
if (typeof value.callId !== 'string')
|
|
164
|
+
return false;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
// Remove old type definitions - now using shared/types/storage.ts
|
|
168
|
+
const tellask_1 = require("./tellask");
|
|
169
|
+
const id_1 = require("./utils/id");
|
|
170
|
+
/**
|
|
171
|
+
* Uses append-only pattern for events, exceptional overwrite for reminders
|
|
172
|
+
*/
|
|
173
|
+
class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
174
|
+
constructor(dialogId) {
|
|
175
|
+
super();
|
|
176
|
+
// Track saying/thinking content for persistence
|
|
177
|
+
this.sayingContent = '';
|
|
178
|
+
this.thinkingContent = '';
|
|
179
|
+
this.dialogId = dialogId;
|
|
180
|
+
}
|
|
181
|
+
// === DialogStore interface methods (for compatibility) ===
|
|
182
|
+
/**
|
|
183
|
+
* Create subdialog with automatic persistence
|
|
184
|
+
*/
|
|
185
|
+
async createSubDialog(supdialog, targetAgentId, headLine, callBody, options) {
|
|
186
|
+
const generatedId = (0, id_1.generateDialogID)();
|
|
187
|
+
// For subdialogs, use the supdialog's root dialog ID as the root
|
|
188
|
+
const subdialogId = new dialog_1.DialogID(generatedId, supdialog.id.rootId);
|
|
189
|
+
// Prepare subdialog store
|
|
190
|
+
const subdialogStore = new DiskFileDialogStore(subdialogId);
|
|
191
|
+
const subdialog = new dialog_1.SubDialog(subdialogStore, supdialog, supdialog.taskDocPath, subdialogId, targetAgentId, {
|
|
192
|
+
headLine,
|
|
193
|
+
callBody,
|
|
194
|
+
originMemberId: options.originMemberId,
|
|
195
|
+
callerDialogId: options.callerDialogId,
|
|
196
|
+
callId: options.callId,
|
|
197
|
+
}, options.tellaskSession);
|
|
198
|
+
// Initial subdialog user prompt is now persisted at first drive (driver.ts)
|
|
199
|
+
// Ensure subdialog directory and persist metadata under supdialog/.subdialogs/
|
|
200
|
+
await this.ensureSubdialogDirectory(subdialogId);
|
|
201
|
+
const metadata = {
|
|
202
|
+
id: subdialogId.selfId,
|
|
203
|
+
agentId: targetAgentId,
|
|
204
|
+
taskDocPath: supdialog.taskDocPath,
|
|
205
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
206
|
+
supdialogId: supdialog.id.selfId,
|
|
207
|
+
tellaskSession: options.tellaskSession,
|
|
208
|
+
assignmentFromSup: {
|
|
209
|
+
headLine,
|
|
210
|
+
callBody,
|
|
211
|
+
originMemberId: options.originMemberId,
|
|
212
|
+
callerDialogId: options.callerDialogId,
|
|
213
|
+
callId: options.callId,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
await DialogPersistence.saveSubdialogMetadata(subdialogId, metadata);
|
|
217
|
+
await DialogPersistence.saveDialogMetadata(subdialogId, metadata);
|
|
218
|
+
// Initialize latest.yaml via the mutation API (write-back will flush).
|
|
219
|
+
await DialogPersistence.mutateDialogLatest(subdialogId, () => ({
|
|
220
|
+
kind: 'replace',
|
|
221
|
+
next: {
|
|
222
|
+
currentRound: 1,
|
|
223
|
+
lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
224
|
+
status: 'active',
|
|
225
|
+
messageCount: 0,
|
|
226
|
+
functionCallCount: 0,
|
|
227
|
+
subdialogCount: 0,
|
|
228
|
+
runState: { kind: 'idle_waiting_user' },
|
|
229
|
+
disableDiligencePush: false,
|
|
230
|
+
},
|
|
231
|
+
}));
|
|
232
|
+
// Supdialog clarification context is persisted in subdialog metadata (supdialogCall)
|
|
233
|
+
const parentRound = await DialogPersistence.getCurrentRoundNumber(supdialog.id);
|
|
234
|
+
const subdialogCreatedEvt = {
|
|
235
|
+
type: 'subdialog_created_evt',
|
|
236
|
+
dialog: {
|
|
237
|
+
selfId: subdialogId.selfId,
|
|
238
|
+
rootId: subdialogId.rootId,
|
|
239
|
+
},
|
|
240
|
+
timestamp: new Date().toISOString(),
|
|
241
|
+
round: parentRound,
|
|
242
|
+
parentDialog: {
|
|
243
|
+
selfId: supdialog.id.selfId,
|
|
244
|
+
rootId: supdialog.id.rootId,
|
|
245
|
+
},
|
|
246
|
+
subDialog: {
|
|
247
|
+
selfId: subdialogId.selfId,
|
|
248
|
+
rootId: subdialogId.rootId,
|
|
249
|
+
},
|
|
250
|
+
targetAgentId,
|
|
251
|
+
headLine,
|
|
252
|
+
callBody,
|
|
253
|
+
};
|
|
254
|
+
// Post subdialog_created_evt to PARENT's PubChan so frontend can receive it
|
|
255
|
+
// The frontend subscribes to the parent's events, not the subdialog's
|
|
256
|
+
(0, evt_registry_1.postDialogEvent)(supdialog, subdialogCreatedEvt);
|
|
257
|
+
return subdialog;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Receive and handle function call results (includes logging)
|
|
261
|
+
*/
|
|
262
|
+
async receiveFuncResult(dialog, funcResult) {
|
|
263
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
264
|
+
// Persist function result record
|
|
265
|
+
const funcResultRecord = {
|
|
266
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
267
|
+
type: 'func_result_record',
|
|
268
|
+
id: funcResult.id,
|
|
269
|
+
name: funcResult.name,
|
|
270
|
+
content: funcResult.content,
|
|
271
|
+
genseq: dialog.activeGenSeq,
|
|
272
|
+
};
|
|
273
|
+
await this.appendEvent(round, funcResultRecord);
|
|
274
|
+
// Send event to frontend
|
|
275
|
+
const funcResultEvt = {
|
|
276
|
+
type: 'func_result_evt',
|
|
277
|
+
id: funcResult.id,
|
|
278
|
+
name: funcResult.name,
|
|
279
|
+
content: funcResult.content,
|
|
280
|
+
round,
|
|
281
|
+
};
|
|
282
|
+
(0, evt_registry_1.postDialogEvent)(dialog, funcResultEvt);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Receive and handle tellask tool responses with callId for inline result display
|
|
286
|
+
*
|
|
287
|
+
* Call Types:
|
|
288
|
+
* - Tellask Call (inline bubble): !?@<mention-id>
|
|
289
|
+
* - Result displays INLINE in the same bubble
|
|
290
|
+
* - Uses callId for correlation between call_start and response
|
|
291
|
+
* - Uses receiveToolResponse() + callId parameter
|
|
292
|
+
*
|
|
293
|
+
* - Teammate Tellask: !?@agentName (e.g., !?@coder, !?@tester)
|
|
294
|
+
* - Result displays in SEPARATE bubble (subdialog response)
|
|
295
|
+
* - Uses calleeDialogId for correlation
|
|
296
|
+
* - Uses receiveTeammateResponse() instead
|
|
297
|
+
*
|
|
298
|
+
* @param dialog - The dialog receiving the response
|
|
299
|
+
* @param responderId - ID of the tool/agent that responded (e.g., "add_reminder")
|
|
300
|
+
* @param headLine - Headline of the original tool call
|
|
301
|
+
* @param result - The result content to display
|
|
302
|
+
* @param status - Response status ('completed' | 'failed')
|
|
303
|
+
* @param callId - Correlation ID from call_start_evt (REQUIRED for inline display)
|
|
304
|
+
*/
|
|
305
|
+
async receiveToolResponse(dialog, responderId, headLine, result, status, callId) {
|
|
306
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
307
|
+
const calling_genseq = dialog.activeGenSeqOrUndefined;
|
|
308
|
+
// Persist record WITH callId for replay correlation
|
|
309
|
+
const ev = {
|
|
310
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
311
|
+
type: 'tool_call_result_record',
|
|
312
|
+
responderId,
|
|
313
|
+
headLine,
|
|
314
|
+
status,
|
|
315
|
+
result,
|
|
316
|
+
calling_genseq,
|
|
317
|
+
callId,
|
|
318
|
+
};
|
|
319
|
+
await this.appendEvent(round, ev);
|
|
320
|
+
// Emit ToolCallResponseEvent WITH callId for UI correlation
|
|
321
|
+
const toolResponseEvt = {
|
|
322
|
+
type: 'tool_call_response_evt',
|
|
323
|
+
responderId,
|
|
324
|
+
headLine,
|
|
325
|
+
status,
|
|
326
|
+
result,
|
|
327
|
+
round,
|
|
328
|
+
calling_genseq,
|
|
329
|
+
callId,
|
|
330
|
+
};
|
|
331
|
+
(0, evt_registry_1.postDialogEvent)(dialog, toolResponseEvt);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Receive and handle TEAMMATE TELLASK responses (separate bubble for @agentName tellasks)
|
|
335
|
+
*
|
|
336
|
+
* Call Types:
|
|
337
|
+
* - Teammate Tellask: !?@agentName (e.g., !?@coder, !?@tester)
|
|
338
|
+
* - Result displays in SEPARATE bubble (subdialog or supdialog response)
|
|
339
|
+
* - Uses calleeDialogId for correlation (not callId)
|
|
340
|
+
* - Uses this method (receiveTeammateResponse)
|
|
341
|
+
*
|
|
342
|
+
* @param dialog - The dialog receiving the response
|
|
343
|
+
* @param responderId - ID of the teammate agent (e.g., "coder")
|
|
344
|
+
* @param headLine - Headline of the original teammate tellask
|
|
345
|
+
* @param status - Response status ('completed' | 'failed')
|
|
346
|
+
* @param calleeDialogId - ID of the callee dialog (subdialog OR supdialog) for navigation links
|
|
347
|
+
*/
|
|
348
|
+
async receiveTeammateResponse(dialog, responderId, headLine, status, calleeDialogId, options) {
|
|
349
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
350
|
+
const calling_genseq = dialog.activeGenSeqOrUndefined;
|
|
351
|
+
const calleeDialogSelfId = calleeDialogId ? calleeDialogId.selfId : undefined;
|
|
352
|
+
const response = options.response;
|
|
353
|
+
const agentId = options.agentId;
|
|
354
|
+
const callId = options.callId;
|
|
355
|
+
const originMemberId = options.originMemberId;
|
|
356
|
+
const result = (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
357
|
+
responderId,
|
|
358
|
+
requesterId: originMemberId,
|
|
359
|
+
originalCallHeadLine: headLine,
|
|
360
|
+
responseBody: response,
|
|
361
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
362
|
+
});
|
|
363
|
+
const ev = {
|
|
364
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
365
|
+
type: 'teammate_response_record',
|
|
366
|
+
responderId,
|
|
367
|
+
calleeDialogId: calleeDialogSelfId,
|
|
368
|
+
headLine,
|
|
369
|
+
status,
|
|
370
|
+
result,
|
|
371
|
+
calling_genseq,
|
|
372
|
+
response,
|
|
373
|
+
agentId,
|
|
374
|
+
callId,
|
|
375
|
+
originMemberId,
|
|
376
|
+
};
|
|
377
|
+
await this.appendEvent(round, ev);
|
|
378
|
+
const teammateResponseEvt = {
|
|
379
|
+
type: 'teammate_response_evt',
|
|
380
|
+
responderId,
|
|
381
|
+
calleeDialogId: calleeDialogSelfId,
|
|
382
|
+
headLine,
|
|
383
|
+
status,
|
|
384
|
+
result,
|
|
385
|
+
round,
|
|
386
|
+
calling_genseq,
|
|
387
|
+
response,
|
|
388
|
+
agentId,
|
|
389
|
+
callId,
|
|
390
|
+
originMemberId,
|
|
391
|
+
};
|
|
392
|
+
(0, evt_registry_1.postDialogEvent)(dialog, teammateResponseEvt);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Ensure subdialog directory exists (delegate to DialogPersistence)
|
|
396
|
+
*/
|
|
397
|
+
async ensureSubdialogDirectory(dialogId) {
|
|
398
|
+
return await DialogPersistence.ensureSubdialogDirectory(dialogId);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Append event to round JSONL file (delegate to DialogPersistence)
|
|
402
|
+
*/
|
|
403
|
+
async appendEvent(round, event) {
|
|
404
|
+
await DialogPersistence.appendEvent(this.dialogId, round, event);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Notify start of LLM generation for frontend bubble management
|
|
408
|
+
* CRITICAL: This must be called BEFORE any substream events (thinking_start, markdown_start, etc.)
|
|
409
|
+
* to ensure proper event ordering on the frontend.
|
|
410
|
+
*/
|
|
411
|
+
async notifyGeneratingStart(dialog) {
|
|
412
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
413
|
+
const genseq = dialog.activeGenSeq;
|
|
414
|
+
try {
|
|
415
|
+
const ev = {
|
|
416
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
417
|
+
type: 'gen_start_record',
|
|
418
|
+
genseq: genseq,
|
|
419
|
+
};
|
|
420
|
+
await this.appendEvent(round, ev);
|
|
421
|
+
// Emit generating_start_evt event
|
|
422
|
+
// This event MUST be emitted and processed before any substream events
|
|
423
|
+
// to ensure the frontend has created the generation bubble before receiving
|
|
424
|
+
// thinking/markdown/calling events
|
|
425
|
+
const genStartEvt = {
|
|
426
|
+
type: 'generating_start_evt',
|
|
427
|
+
round,
|
|
428
|
+
genseq: genseq,
|
|
429
|
+
};
|
|
430
|
+
(0, evt_registry_1.postDialogEvent)(dialog, genStartEvt);
|
|
431
|
+
// Update generating flag in latest.yaml
|
|
432
|
+
await DialogPersistence.mutateDialogLatest(this.dialogId, () => ({
|
|
433
|
+
kind: 'patch',
|
|
434
|
+
patch: { generating: true },
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
log_1.log.warn('Failed to persist gen_start event', err);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Notify end of LLM generation for frontend bubble management
|
|
443
|
+
*/
|
|
444
|
+
async notifyGeneratingFinish(dialog, contextHealth, llmGenModel) {
|
|
445
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
446
|
+
const genseq = dialog.activeGenSeq;
|
|
447
|
+
if (genseq === undefined) {
|
|
448
|
+
throw new Error('Missing active genseq for notifyGeneratingFinish');
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
const ev = {
|
|
452
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
453
|
+
type: 'gen_finish_record',
|
|
454
|
+
genseq: genseq,
|
|
455
|
+
contextHealth,
|
|
456
|
+
llmGenModel,
|
|
457
|
+
};
|
|
458
|
+
await this.appendEvent(round, ev);
|
|
459
|
+
// Emit generating_finish_evt event (this was missing, causing double triggering issue)
|
|
460
|
+
const genFinishEvt = {
|
|
461
|
+
type: 'generating_finish_evt',
|
|
462
|
+
round,
|
|
463
|
+
genseq: genseq,
|
|
464
|
+
llmGenModel,
|
|
465
|
+
};
|
|
466
|
+
(0, evt_registry_1.postDialogEvent)(dialog, genFinishEvt);
|
|
467
|
+
if (contextHealth) {
|
|
468
|
+
const ctxEvt = {
|
|
469
|
+
type: 'context_health_evt',
|
|
470
|
+
round,
|
|
471
|
+
genseq,
|
|
472
|
+
contextHealth,
|
|
473
|
+
};
|
|
474
|
+
(0, evt_registry_1.postDialogEvent)(dialog, ctxEvt);
|
|
475
|
+
}
|
|
476
|
+
// Update generating flag in latest.yaml
|
|
477
|
+
await DialogPersistence.mutateDialogLatest(this.dialogId, () => ({
|
|
478
|
+
kind: 'patch',
|
|
479
|
+
patch: { generating: false },
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
log_1.log.warn('Failed to persist gen_finish event', err);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async sayingStart(dialog) {
|
|
487
|
+
// Reset saying content tracker
|
|
488
|
+
this.sayingContent = '';
|
|
489
|
+
}
|
|
490
|
+
async sayingChunk(dialog, chunk) {
|
|
491
|
+
// Collect saying content for persistence
|
|
492
|
+
this.sayingContent += chunk;
|
|
493
|
+
}
|
|
494
|
+
async sayingFinish(dialog) {
|
|
495
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
496
|
+
const sayingContent = this.sayingContent.trim();
|
|
497
|
+
// Persist saying content as a message event
|
|
498
|
+
if (sayingContent) {
|
|
499
|
+
const sayingMessageEvent = {
|
|
500
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
501
|
+
type: 'agent_words_record',
|
|
502
|
+
genseq: dialog.activeGenSeq,
|
|
503
|
+
content: sayingContent,
|
|
504
|
+
};
|
|
505
|
+
await this.appendEvent(round, sayingMessageEvent);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async thinkingStart(dialog) {
|
|
509
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
510
|
+
// Reset thinking content tracker
|
|
511
|
+
this.thinkingContent = '';
|
|
512
|
+
const thinkingStartEvt = {
|
|
513
|
+
type: 'thinking_start_evt',
|
|
514
|
+
round,
|
|
515
|
+
genseq: dialog.activeGenSeq,
|
|
516
|
+
};
|
|
517
|
+
(0, evt_registry_1.postDialogEvent)(dialog, thinkingStartEvt);
|
|
518
|
+
}
|
|
519
|
+
async thinkingChunk(dialog, chunk) {
|
|
520
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
521
|
+
// Collect thinking content for persistence
|
|
522
|
+
this.thinkingContent += chunk;
|
|
523
|
+
const thinkingChunkEvt = {
|
|
524
|
+
type: 'thinking_chunk_evt',
|
|
525
|
+
chunk,
|
|
526
|
+
round,
|
|
527
|
+
genseq: dialog.activeGenSeq,
|
|
528
|
+
};
|
|
529
|
+
(0, evt_registry_1.postDialogEvent)(dialog, thinkingChunkEvt);
|
|
530
|
+
}
|
|
531
|
+
async thinkingFinish(dialog) {
|
|
532
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
533
|
+
// Persist thinking content as a message event
|
|
534
|
+
if (this.thinkingContent.trim()) {
|
|
535
|
+
const thinkingMessageEvent = {
|
|
536
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
537
|
+
type: 'agent_thought_record',
|
|
538
|
+
genseq: dialog.activeGenSeq,
|
|
539
|
+
content: this.thinkingContent.trim(),
|
|
540
|
+
};
|
|
541
|
+
await this.appendEvent(round, thinkingMessageEvent);
|
|
542
|
+
}
|
|
543
|
+
const thinkingFinishEvt = {
|
|
544
|
+
type: 'thinking_finish_evt',
|
|
545
|
+
round,
|
|
546
|
+
genseq: dialog.activeGenSeq,
|
|
547
|
+
};
|
|
548
|
+
(0, evt_registry_1.postDialogEvent)(dialog, thinkingFinishEvt);
|
|
549
|
+
}
|
|
550
|
+
async markdownStart(dialog) {
|
|
551
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
552
|
+
const markdownStartEvt = {
|
|
553
|
+
type: 'markdown_start_evt',
|
|
554
|
+
round,
|
|
555
|
+
genseq: dialog.activeGenSeq,
|
|
556
|
+
};
|
|
557
|
+
(0, evt_registry_1.postDialogEvent)(dialog, markdownStartEvt);
|
|
558
|
+
}
|
|
559
|
+
async markdownChunk(dialog, chunk) {
|
|
560
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
561
|
+
const evt = {
|
|
562
|
+
type: 'markdown_chunk_evt',
|
|
563
|
+
chunk,
|
|
564
|
+
round,
|
|
565
|
+
genseq: dialog.activeGenSeq,
|
|
566
|
+
};
|
|
567
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
568
|
+
}
|
|
569
|
+
async markdownFinish(dialog) {
|
|
570
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
571
|
+
const evt = {
|
|
572
|
+
type: 'markdown_finish_evt',
|
|
573
|
+
round,
|
|
574
|
+
genseq: dialog.activeGenSeq,
|
|
575
|
+
};
|
|
576
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
577
|
+
}
|
|
578
|
+
// Tellask call streaming methods
|
|
579
|
+
async callingStart(dialog, validation) {
|
|
580
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
581
|
+
const evt = {
|
|
582
|
+
type: 'tool_call_start_evt',
|
|
583
|
+
validation,
|
|
584
|
+
round,
|
|
585
|
+
genseq: dialog.activeGenSeq,
|
|
586
|
+
};
|
|
587
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
588
|
+
}
|
|
589
|
+
async callingHeadlineChunk(dialog, chunk) {
|
|
590
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
591
|
+
const evt = {
|
|
592
|
+
type: 'tool_call_headline_chunk_evt',
|
|
593
|
+
chunk,
|
|
594
|
+
round,
|
|
595
|
+
genseq: dialog.activeGenSeq,
|
|
596
|
+
};
|
|
597
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
598
|
+
}
|
|
599
|
+
async callingHeadlineFinish(dialog) {
|
|
600
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
601
|
+
const evt = {
|
|
602
|
+
type: 'tool_call_headline_finish_evt',
|
|
603
|
+
round,
|
|
604
|
+
genseq: dialog.activeGenSeq,
|
|
605
|
+
};
|
|
606
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
607
|
+
}
|
|
608
|
+
async callingBodyStart(dialog) {
|
|
609
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
610
|
+
const evt = {
|
|
611
|
+
type: 'tool_call_body_start_evt',
|
|
612
|
+
round,
|
|
613
|
+
genseq: dialog.activeGenSeq,
|
|
614
|
+
};
|
|
615
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
616
|
+
}
|
|
617
|
+
async callingBodyChunk(dialog, chunk) {
|
|
618
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
619
|
+
const evt = {
|
|
620
|
+
type: 'tool_call_body_chunk_evt',
|
|
621
|
+
chunk,
|
|
622
|
+
round,
|
|
623
|
+
genseq: dialog.activeGenSeq,
|
|
624
|
+
};
|
|
625
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
626
|
+
}
|
|
627
|
+
async callingBodyFinish(dialog) {
|
|
628
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
629
|
+
const evt = {
|
|
630
|
+
type: 'tool_call_body_finish_evt',
|
|
631
|
+
round,
|
|
632
|
+
genseq: dialog.activeGenSeq,
|
|
633
|
+
};
|
|
634
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
635
|
+
}
|
|
636
|
+
async callingFinish(dialog, callId) {
|
|
637
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
638
|
+
const evt = {
|
|
639
|
+
type: 'tool_call_finish_evt',
|
|
640
|
+
callId,
|
|
641
|
+
round,
|
|
642
|
+
genseq: dialog.activeGenSeq,
|
|
643
|
+
};
|
|
644
|
+
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
645
|
+
}
|
|
646
|
+
// Function call events (non-streaming mode - single event captures entire call)
|
|
647
|
+
async funcCallRequested(dialog, funcId, funcName, argumentsStr) {
|
|
648
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
649
|
+
const funcCallEvt = {
|
|
650
|
+
type: 'func_call_requested_evt',
|
|
651
|
+
funcId,
|
|
652
|
+
funcName,
|
|
653
|
+
arguments: argumentsStr,
|
|
654
|
+
round,
|
|
655
|
+
genseq: dialog.activeGenSeq,
|
|
656
|
+
};
|
|
657
|
+
(0, evt_registry_1.postDialogEvent)(dialog, funcCallEvt);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Emit stream error for current generation lifecycle (uses active genseq when present)
|
|
661
|
+
*/
|
|
662
|
+
async streamError(dialog, error) {
|
|
663
|
+
log_1.log.error(`Dialog stream error '${error}'`, new Error(), { dialog });
|
|
664
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
665
|
+
const genseq = typeof dialog.activeGenSeq === 'number' ? dialog.activeGenSeq : undefined;
|
|
666
|
+
// Enhanced stream error event with better error classification
|
|
667
|
+
const streamErrorEvent = {
|
|
668
|
+
type: 'stream_error_evt',
|
|
669
|
+
round,
|
|
670
|
+
genseq,
|
|
671
|
+
error,
|
|
672
|
+
};
|
|
673
|
+
(0, evt_registry_1.postDialogEvent)(dialog, streamErrorEvent);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Start new round (append-only JSONL + exceptional reminder persistence)
|
|
677
|
+
*/
|
|
678
|
+
async startNewRound(dialog, _newRoundPrompt) {
|
|
679
|
+
const previousRound = dialog.currentRound;
|
|
680
|
+
const newRound = previousRound + 1;
|
|
681
|
+
// Persist reminders state for new round (exceptional overwrite)
|
|
682
|
+
// Use the currently attached dialog's reminders to avoid stale state
|
|
683
|
+
await this.persistReminders(dialog, dialog.reminders || []);
|
|
684
|
+
// Update latest.yaml with new round (lastModified is set by persistence layer)
|
|
685
|
+
await DialogPersistence.mutateDialogLatest(this.dialogId, () => ({
|
|
686
|
+
kind: 'patch',
|
|
687
|
+
patch: { currentRound: newRound },
|
|
688
|
+
}));
|
|
689
|
+
// Post round update event
|
|
690
|
+
const roundUpdateEvt = {
|
|
691
|
+
type: 'round_update',
|
|
692
|
+
round: newRound,
|
|
693
|
+
totalRounds: newRound,
|
|
694
|
+
};
|
|
695
|
+
(0, evt_registry_1.postDialogEvent)(dialog, roundUpdateEvt);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Persist reminder state (exceptional overwrite pattern)
|
|
699
|
+
* Note: Event emission is handled by processReminderUpdates() in Dialog
|
|
700
|
+
*/
|
|
701
|
+
async persistReminders(dialog, reminders) {
|
|
702
|
+
await DialogPersistence._saveReminderState(this.dialogId, reminders);
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Persist a user message to storage
|
|
706
|
+
* Note: The end_of_user_saying_evt is emitted by the driver after user content
|
|
707
|
+
* is rendered and any tellask calls are parsed/executed.
|
|
708
|
+
*/
|
|
709
|
+
async persistUserMessage(dialog, content, msgId, grammar, userLanguageCode) {
|
|
710
|
+
const round = dialog.currentRound;
|
|
711
|
+
// Use activeGenSeqOrUndefined to handle case when genseq hasn't been initialized yet
|
|
712
|
+
const genseq = dialog.activeGenSeqOrUndefined ?? 1;
|
|
713
|
+
const humanEv = {
|
|
714
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
715
|
+
type: 'human_text_record',
|
|
716
|
+
genseq: genseq,
|
|
717
|
+
content: String(content || ''),
|
|
718
|
+
msgId: msgId,
|
|
719
|
+
grammar,
|
|
720
|
+
userLanguageCode,
|
|
721
|
+
};
|
|
722
|
+
await this.appendEvent(round, humanEv);
|
|
723
|
+
// Note: end_of_user_saying_evt is now emitted by llm/driver.ts after tellask calls complete
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Persist an assistant message to storage
|
|
727
|
+
*/
|
|
728
|
+
async persistAgentMessage(dialog, content, genseq, type, provider_data) {
|
|
729
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
730
|
+
const event = type === 'thinking_msg'
|
|
731
|
+
? {
|
|
732
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
733
|
+
type: 'agent_thought_record',
|
|
734
|
+
genseq,
|
|
735
|
+
content: content || '',
|
|
736
|
+
provider_data,
|
|
737
|
+
}
|
|
738
|
+
: {
|
|
739
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
740
|
+
type: 'agent_words_record',
|
|
741
|
+
genseq,
|
|
742
|
+
content: content || '',
|
|
743
|
+
};
|
|
744
|
+
await this.appendEvent(round, event);
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Persist a function call to storage
|
|
748
|
+
*/
|
|
749
|
+
async persistFunctionCall(dialog, id, name, arguments_, genseq) {
|
|
750
|
+
const round = dialog.activeGenRoundOrUndefined ?? dialog.currentRound;
|
|
751
|
+
const funcCallEvent = {
|
|
752
|
+
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
753
|
+
type: 'func_call_record',
|
|
754
|
+
genseq,
|
|
755
|
+
id,
|
|
756
|
+
name,
|
|
757
|
+
arguments: arguments_,
|
|
758
|
+
};
|
|
759
|
+
await this.appendEvent(round, funcCallEvent);
|
|
760
|
+
// NOTE: func_call_evt REMOVED - persistence uses FuncCallRecord directly
|
|
761
|
+
// UI display uses func_call_requested_evt instead
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Update questions for human state (exceptional overwrite pattern)
|
|
765
|
+
*/
|
|
766
|
+
async updateQuestions4Human(dialog, questions) {
|
|
767
|
+
await DialogPersistence._saveQuestions4HumanState(this.dialogId, questions);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Load Questions for Human state from storage
|
|
771
|
+
*/
|
|
772
|
+
async loadQuestions4Human(dialogId, status) {
|
|
773
|
+
return await DialogPersistence.loadQuestions4HumanState(dialogId, status);
|
|
774
|
+
}
|
|
775
|
+
async loadDialogMetadata(dialogId, status) {
|
|
776
|
+
return await DialogPersistence.loadDialogMetadata(dialogId, status);
|
|
777
|
+
}
|
|
778
|
+
async loadPendingSubdialogs(rootDialogId, status) {
|
|
779
|
+
const records = await DialogPersistence.loadPendingSubdialogs(rootDialogId, status);
|
|
780
|
+
return records.map((record) => ({
|
|
781
|
+
subdialogId: new dialog_1.DialogID(record.subdialogId, rootDialogId.rootId),
|
|
782
|
+
createdAt: record.createdAt,
|
|
783
|
+
headLine: record.headLine,
|
|
784
|
+
targetAgentId: record.targetAgentId,
|
|
785
|
+
callType: record.callType,
|
|
786
|
+
tellaskSession: record.tellaskSession,
|
|
787
|
+
}));
|
|
788
|
+
}
|
|
789
|
+
async saveSubdialogRegistry(rootDialogId, entries, status) {
|
|
790
|
+
await DialogPersistence.saveSubdialogRegistry(rootDialogId, entries, status);
|
|
791
|
+
}
|
|
792
|
+
async loadSubdialogRegistry(rootDialog, status) {
|
|
793
|
+
const entries = await DialogPersistence.loadSubdialogRegistry(rootDialog.id, status);
|
|
794
|
+
for (const entry of entries) {
|
|
795
|
+
if (!entry.tellaskSession)
|
|
796
|
+
continue;
|
|
797
|
+
const existing = rootDialog.lookupDialog(entry.subdialogId.selfId);
|
|
798
|
+
if (existing) {
|
|
799
|
+
if (existing instanceof dialog_1.SubDialog && existing.tellaskSession) {
|
|
800
|
+
rootDialog.registerSubdialog(existing);
|
|
801
|
+
}
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const subdialogState = await DialogPersistence.restoreDialog(entry.subdialogId, status);
|
|
805
|
+
if (!subdialogState)
|
|
806
|
+
continue;
|
|
807
|
+
const assignmentFromSup = subdialogState.metadata.assignmentFromSup;
|
|
808
|
+
if (!assignmentFromSup)
|
|
809
|
+
continue;
|
|
810
|
+
const subdialogStore = new DiskFileDialogStore(entry.subdialogId);
|
|
811
|
+
const subdialog = new dialog_1.SubDialog(subdialogStore, rootDialog, subdialogState.metadata.taskDocPath, new dialog_1.DialogID(entry.subdialogId.selfId, rootDialog.id.rootId), subdialogState.metadata.agentId, assignmentFromSup, entry.tellaskSession, {
|
|
812
|
+
messages: subdialogState.messages,
|
|
813
|
+
reminders: subdialogState.reminders,
|
|
814
|
+
currentRound: subdialogState.currentRound,
|
|
815
|
+
});
|
|
816
|
+
rootDialog.registerSubdialog(subdialog);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Clear Questions for Human state in storage
|
|
821
|
+
*/
|
|
822
|
+
async clearQuestions4Human(dialog) {
|
|
823
|
+
const previousQuestions = await DialogPersistence.loadQuestions4HumanState(dialog.id);
|
|
824
|
+
const previousCount = previousQuestions.length;
|
|
825
|
+
if (previousCount > 0) {
|
|
826
|
+
await DialogPersistence.clearQuestions4HumanState(dialog.id);
|
|
827
|
+
// Emit q4h_answered events for each removed question
|
|
828
|
+
for (const q of previousQuestions) {
|
|
829
|
+
const answeredEvent = {
|
|
830
|
+
type: 'q4h_answered',
|
|
831
|
+
questionId: q.id,
|
|
832
|
+
selfId: dialog.id.selfId,
|
|
833
|
+
};
|
|
834
|
+
(0, evt_registry_1.postDialogEvent)(dialog, answeredEvent);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Get current questions for human count for UI decoration
|
|
840
|
+
*/
|
|
841
|
+
async getQuestions4HumanCount() {
|
|
842
|
+
const questions = await DialogPersistence.loadQuestions4HumanState(this.dialogId);
|
|
843
|
+
return questions.length;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Load current round number from persisted metadata
|
|
847
|
+
*/
|
|
848
|
+
async loadCurrentRound(dialogId) {
|
|
849
|
+
return await DialogPersistence.getCurrentRoundNumber(dialogId, 'running');
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Get next sequence number for generation
|
|
853
|
+
*/
|
|
854
|
+
async getNextSeq(dialogId, round) {
|
|
855
|
+
return await DialogPersistence.getNextSeq(dialogId, round, 'running');
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Send dialog events directly to a specific WebSocket connection for dialog restoration
|
|
859
|
+
* CRITICAL: This bypasses PubChan to ensure only the requesting session receives restoration events
|
|
860
|
+
* Unlike replayDialogEvents(), this sends events directly to ws.send() instead of postDialogEvent()
|
|
861
|
+
* @param ws - WebSocket connection to send events to
|
|
862
|
+
* @param dialog - Dialog object containing metadata
|
|
863
|
+
* @param round - Optional round number (uses dialog.currentRound if not provided)
|
|
864
|
+
* @param totalRounds - Optional total rounds count (defaults to round/currentRound)
|
|
865
|
+
*/
|
|
866
|
+
async sendDialogEventsDirectly(ws, dialog, round, totalRounds, status = 'running') {
|
|
867
|
+
try {
|
|
868
|
+
// Use provided round or fallback to dialog.currentRound (which may be stale for new Dialog objects)
|
|
869
|
+
const currentRound = round ?? dialog.currentRound;
|
|
870
|
+
const effectiveTotalRounds = totalRounds ?? currentRound;
|
|
871
|
+
const persistenceEvents = await DialogPersistence.readRoundEvents(dialog.id, currentRound, status);
|
|
872
|
+
// Send round_update event directly to this WebSocket only
|
|
873
|
+
ws.send(JSON.stringify({
|
|
874
|
+
type: 'round_update',
|
|
875
|
+
dialog: {
|
|
876
|
+
selfId: dialog.id.selfId,
|
|
877
|
+
rootId: dialog.id.rootId,
|
|
878
|
+
},
|
|
879
|
+
round: currentRound,
|
|
880
|
+
totalRounds: effectiveTotalRounds,
|
|
881
|
+
}));
|
|
882
|
+
// Events are already in chronological order from JSONL file (append-only pattern)
|
|
883
|
+
// Send each persistence event directly to the requesting WebSocket
|
|
884
|
+
for (const event of persistenceEvents) {
|
|
885
|
+
await this.sendEventDirectlyToWebSocket(ws, dialog, currentRound, event);
|
|
886
|
+
}
|
|
887
|
+
// Rehydrate reminders from dialog state
|
|
888
|
+
const dialogState = await DialogPersistence.restoreDialog(dialog.id, status);
|
|
889
|
+
const rehydrated = (dialogState?.reminders ?? []).map((r) => {
|
|
890
|
+
return { content: r.content, owner: r.owner, meta: r.meta };
|
|
891
|
+
});
|
|
892
|
+
dialog.reminders.length = 0;
|
|
893
|
+
dialog.reminders.push(...rehydrated);
|
|
894
|
+
}
|
|
895
|
+
catch (error) {
|
|
896
|
+
log_1.log.error(`Failed to send dialog events directly for ${dialog.id.selfId}:`, error);
|
|
897
|
+
throw error;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Send a single persistence event directly to a WebSocket connection
|
|
902
|
+
* CRITICAL: Avoid PubChan completely for dialog restoration to the single client's display_dialog request
|
|
903
|
+
*/
|
|
904
|
+
async sendEventDirectlyToWebSocket(ws, dialog, round, event) {
|
|
905
|
+
switch (event.type) {
|
|
906
|
+
case 'human_text_record': {
|
|
907
|
+
const genseq = event.genseq;
|
|
908
|
+
const content = event.content || '';
|
|
909
|
+
const grammar = event.grammar ?? 'tellask';
|
|
910
|
+
const userLanguageCode = event.userLanguageCode;
|
|
911
|
+
if (content) {
|
|
912
|
+
if (grammar === 'tellask') {
|
|
913
|
+
const receiver = {
|
|
914
|
+
markdownStart: async () => {
|
|
915
|
+
if (ws.readyState === 1) {
|
|
916
|
+
ws.send(JSON.stringify({
|
|
917
|
+
type: 'markdown_start_evt',
|
|
918
|
+
round,
|
|
919
|
+
genseq,
|
|
920
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
921
|
+
timestamp: event.ts,
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
markdownChunk: async (chunk) => {
|
|
926
|
+
if (ws.readyState === 1) {
|
|
927
|
+
ws.send(JSON.stringify({
|
|
928
|
+
type: 'markdown_chunk_evt',
|
|
929
|
+
chunk,
|
|
930
|
+
round,
|
|
931
|
+
genseq,
|
|
932
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
933
|
+
timestamp: event.ts,
|
|
934
|
+
}));
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
markdownFinish: async () => {
|
|
938
|
+
if (ws.readyState === 1) {
|
|
939
|
+
ws.send(JSON.stringify({
|
|
940
|
+
type: 'markdown_finish_evt',
|
|
941
|
+
round,
|
|
942
|
+
genseq,
|
|
943
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
944
|
+
timestamp: event.ts,
|
|
945
|
+
}));
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
callStart: async (validation) => {
|
|
949
|
+
if (ws.readyState === 1) {
|
|
950
|
+
ws.send(JSON.stringify({
|
|
951
|
+
type: 'tool_call_start_evt',
|
|
952
|
+
validation,
|
|
953
|
+
round,
|
|
954
|
+
genseq,
|
|
955
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
956
|
+
timestamp: event.ts,
|
|
957
|
+
}));
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
callHeadLineChunk: async (chunk) => {
|
|
961
|
+
if (ws.readyState === 1) {
|
|
962
|
+
ws.send(JSON.stringify({
|
|
963
|
+
type: 'tool_call_headline_chunk_evt',
|
|
964
|
+
chunk,
|
|
965
|
+
round,
|
|
966
|
+
genseq,
|
|
967
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
968
|
+
timestamp: event.ts,
|
|
969
|
+
}));
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
callHeadLineFinish: async () => {
|
|
973
|
+
if (ws.readyState === 1) {
|
|
974
|
+
ws.send(JSON.stringify({
|
|
975
|
+
type: 'tool_call_headline_finish_evt',
|
|
976
|
+
round,
|
|
977
|
+
genseq,
|
|
978
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
979
|
+
timestamp: event.ts,
|
|
980
|
+
}));
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
callBodyStart: async () => {
|
|
984
|
+
if (ws.readyState === 1) {
|
|
985
|
+
ws.send(JSON.stringify({
|
|
986
|
+
type: 'tool_call_body_start_evt',
|
|
987
|
+
round,
|
|
988
|
+
genseq,
|
|
989
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
990
|
+
timestamp: event.ts,
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
callBodyChunk: async (chunk) => {
|
|
995
|
+
if (ws.readyState === 1) {
|
|
996
|
+
ws.send(JSON.stringify({
|
|
997
|
+
type: 'tool_call_body_chunk_evt',
|
|
998
|
+
chunk,
|
|
999
|
+
round,
|
|
1000
|
+
genseq,
|
|
1001
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1002
|
+
timestamp: event.ts,
|
|
1003
|
+
}));
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
callBodyFinish: async () => {
|
|
1007
|
+
if (ws.readyState === 1) {
|
|
1008
|
+
ws.send(JSON.stringify({
|
|
1009
|
+
type: 'tool_call_body_finish_evt',
|
|
1010
|
+
round,
|
|
1011
|
+
genseq,
|
|
1012
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1013
|
+
timestamp: event.ts,
|
|
1014
|
+
}));
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
callFinish: async (_callId) => {
|
|
1018
|
+
if (ws.readyState === 1) {
|
|
1019
|
+
ws.send(JSON.stringify({
|
|
1020
|
+
type: 'tool_call_finish_evt',
|
|
1021
|
+
callId: _callId,
|
|
1022
|
+
round,
|
|
1023
|
+
genseq,
|
|
1024
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1025
|
+
timestamp: event.ts,
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
};
|
|
1030
|
+
// Parse user content through TellaskStreamParser (same as live mode)
|
|
1031
|
+
const streamingParser = new tellask_1.TellaskStreamParser(receiver);
|
|
1032
|
+
await streamingParser.takeUpstreamChunk(content);
|
|
1033
|
+
await streamingParser.finalize();
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
if (ws.readyState === 1) {
|
|
1037
|
+
ws.send(JSON.stringify({
|
|
1038
|
+
type: 'markdown_start_evt',
|
|
1039
|
+
round,
|
|
1040
|
+
genseq,
|
|
1041
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1042
|
+
timestamp: event.ts,
|
|
1043
|
+
}));
|
|
1044
|
+
ws.send(JSON.stringify({
|
|
1045
|
+
type: 'markdown_chunk_evt',
|
|
1046
|
+
chunk: content,
|
|
1047
|
+
round,
|
|
1048
|
+
genseq,
|
|
1049
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1050
|
+
timestamp: event.ts,
|
|
1051
|
+
}));
|
|
1052
|
+
ws.send(JSON.stringify({
|
|
1053
|
+
type: 'markdown_finish_evt',
|
|
1054
|
+
round,
|
|
1055
|
+
genseq,
|
|
1056
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1057
|
+
timestamp: event.ts,
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
// Emit end_of_user_saying_evt to signal frontend to render <hr/> separator
|
|
1063
|
+
if (ws.readyState === 1) {
|
|
1064
|
+
ws.send(JSON.stringify({
|
|
1065
|
+
type: 'end_of_user_saying_evt',
|
|
1066
|
+
round,
|
|
1067
|
+
genseq,
|
|
1068
|
+
msgId: event.msgId,
|
|
1069
|
+
content,
|
|
1070
|
+
grammar,
|
|
1071
|
+
userLanguageCode,
|
|
1072
|
+
dialog: { selfId: dialog.id.selfId, rootId: dialog.id.rootId },
|
|
1073
|
+
timestamp: event.ts,
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
case 'gen_start_record': {
|
|
1079
|
+
// Create generating_start_evt event using persisted genseq directly
|
|
1080
|
+
const genStartWireEvent = {
|
|
1081
|
+
type: 'generating_start_evt',
|
|
1082
|
+
round,
|
|
1083
|
+
genseq: event.genseq,
|
|
1084
|
+
dialog: {
|
|
1085
|
+
selfId: dialog.id.selfId,
|
|
1086
|
+
rootId: dialog.id.rootId,
|
|
1087
|
+
},
|
|
1088
|
+
timestamp: event.ts,
|
|
1089
|
+
};
|
|
1090
|
+
// Send directly to WebSocket (NO PubChan emission)
|
|
1091
|
+
if (ws.readyState === 1) {
|
|
1092
|
+
ws.send(JSON.stringify(genStartWireEvent));
|
|
1093
|
+
}
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
case 'gen_finish_record': {
|
|
1097
|
+
// Create generating_finish_evt event using persisted genseq directly
|
|
1098
|
+
const genFinishWireEvent = {
|
|
1099
|
+
type: 'generating_finish_evt',
|
|
1100
|
+
round,
|
|
1101
|
+
genseq: event.genseq,
|
|
1102
|
+
llmGenModel: typeof event.llmGenModel === 'string' ? event.llmGenModel : undefined,
|
|
1103
|
+
dialog: {
|
|
1104
|
+
selfId: dialog.id.selfId,
|
|
1105
|
+
rootId: dialog.id.rootId,
|
|
1106
|
+
},
|
|
1107
|
+
timestamp: event.ts,
|
|
1108
|
+
};
|
|
1109
|
+
// Send directly to WebSocket (NO PubChan emission)
|
|
1110
|
+
if (ws.readyState === 1) {
|
|
1111
|
+
ws.send(JSON.stringify(genFinishWireEvent));
|
|
1112
|
+
}
|
|
1113
|
+
if (event.contextHealth) {
|
|
1114
|
+
const ctxWireEvent = {
|
|
1115
|
+
type: 'context_health_evt',
|
|
1116
|
+
round,
|
|
1117
|
+
genseq: event.genseq,
|
|
1118
|
+
contextHealth: event.contextHealth,
|
|
1119
|
+
dialog: {
|
|
1120
|
+
selfId: dialog.id.selfId,
|
|
1121
|
+
rootId: dialog.id.rootId,
|
|
1122
|
+
},
|
|
1123
|
+
timestamp: event.ts,
|
|
1124
|
+
};
|
|
1125
|
+
if (ws.readyState === 1) {
|
|
1126
|
+
ws.send(JSON.stringify(ctxWireEvent));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
case 'agent_thought_record': {
|
|
1132
|
+
// Replay thinking content as thinking events
|
|
1133
|
+
const content = event.content || '';
|
|
1134
|
+
if (content) {
|
|
1135
|
+
// Start thinking phase
|
|
1136
|
+
const thinkingStartEvent = {
|
|
1137
|
+
type: 'thinking_start_evt',
|
|
1138
|
+
round,
|
|
1139
|
+
genseq: event.genseq,
|
|
1140
|
+
dialog: {
|
|
1141
|
+
selfId: dialog.id.selfId,
|
|
1142
|
+
rootId: dialog.id.rootId,
|
|
1143
|
+
},
|
|
1144
|
+
timestamp: event.ts,
|
|
1145
|
+
};
|
|
1146
|
+
if (ws.readyState === 1) {
|
|
1147
|
+
ws.send(JSON.stringify(thinkingStartEvent));
|
|
1148
|
+
}
|
|
1149
|
+
const thinkingChunks = this.createOptimalChunks(content);
|
|
1150
|
+
for (const chunk of thinkingChunks) {
|
|
1151
|
+
const thinkingChunkEvent = {
|
|
1152
|
+
type: 'thinking_chunk_evt',
|
|
1153
|
+
chunk,
|
|
1154
|
+
round,
|
|
1155
|
+
genseq: event.genseq,
|
|
1156
|
+
dialog: {
|
|
1157
|
+
selfId: dialog.id.selfId,
|
|
1158
|
+
rootId: dialog.id.rootId,
|
|
1159
|
+
},
|
|
1160
|
+
timestamp: event.ts,
|
|
1161
|
+
};
|
|
1162
|
+
if (ws.readyState === 1) {
|
|
1163
|
+
ws.send(JSON.stringify(thinkingChunkEvent));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
// Finish thinking phase
|
|
1167
|
+
const thinkingFinishEvent = {
|
|
1168
|
+
type: 'thinking_finish_evt',
|
|
1169
|
+
round,
|
|
1170
|
+
genseq: event.genseq,
|
|
1171
|
+
dialog: {
|
|
1172
|
+
selfId: dialog.id.selfId,
|
|
1173
|
+
rootId: dialog.id.rootId,
|
|
1174
|
+
},
|
|
1175
|
+
timestamp: event.ts,
|
|
1176
|
+
};
|
|
1177
|
+
if (ws.readyState === 1) {
|
|
1178
|
+
ws.send(JSON.stringify(thinkingFinishEvent));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case 'agent_words_record': {
|
|
1184
|
+
// Replay assistant text using ad-hoc event receiver with closure-based WebSocket access
|
|
1185
|
+
const content = event.content || '';
|
|
1186
|
+
if (content) {
|
|
1187
|
+
// Create ad-hoc receiver similar to driver pattern with closure-based WebSocket access
|
|
1188
|
+
const receiver = {
|
|
1189
|
+
markdownStart: async () => {
|
|
1190
|
+
if (ws.readyState === 1) {
|
|
1191
|
+
ws.send(JSON.stringify({
|
|
1192
|
+
type: 'markdown_start_evt',
|
|
1193
|
+
round,
|
|
1194
|
+
genseq: event.genseq,
|
|
1195
|
+
dialog: {
|
|
1196
|
+
selfId: dialog.id.selfId,
|
|
1197
|
+
rootId: dialog.id.rootId,
|
|
1198
|
+
},
|
|
1199
|
+
timestamp: event.ts,
|
|
1200
|
+
}));
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
markdownChunk: async (chunk) => {
|
|
1204
|
+
if (ws.readyState === 1) {
|
|
1205
|
+
ws.send(JSON.stringify({
|
|
1206
|
+
type: 'markdown_chunk_evt',
|
|
1207
|
+
chunk,
|
|
1208
|
+
round,
|
|
1209
|
+
genseq: event.genseq,
|
|
1210
|
+
dialog: {
|
|
1211
|
+
selfId: dialog.id.selfId,
|
|
1212
|
+
rootId: dialog.id.rootId,
|
|
1213
|
+
},
|
|
1214
|
+
timestamp: event.ts,
|
|
1215
|
+
}));
|
|
1216
|
+
}
|
|
1217
|
+
},
|
|
1218
|
+
markdownFinish: async () => {
|
|
1219
|
+
if (ws.readyState === 1) {
|
|
1220
|
+
ws.send(JSON.stringify({
|
|
1221
|
+
type: 'markdown_finish_evt',
|
|
1222
|
+
round,
|
|
1223
|
+
genseq: event.genseq,
|
|
1224
|
+
dialog: {
|
|
1225
|
+
selfId: dialog.id.selfId,
|
|
1226
|
+
rootId: dialog.id.rootId,
|
|
1227
|
+
},
|
|
1228
|
+
timestamp: event.ts,
|
|
1229
|
+
}));
|
|
1230
|
+
}
|
|
1231
|
+
},
|
|
1232
|
+
callStart: async (validation) => {
|
|
1233
|
+
if (ws.readyState === 1) {
|
|
1234
|
+
ws.send(JSON.stringify({
|
|
1235
|
+
type: 'tool_call_start_evt',
|
|
1236
|
+
validation,
|
|
1237
|
+
round,
|
|
1238
|
+
genseq: event.genseq,
|
|
1239
|
+
dialog: {
|
|
1240
|
+
selfId: dialog.id.selfId,
|
|
1241
|
+
rootId: dialog.id.rootId,
|
|
1242
|
+
},
|
|
1243
|
+
timestamp: event.ts,
|
|
1244
|
+
}));
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
callHeadLineChunk: async (chunk) => {
|
|
1248
|
+
if (ws.readyState === 1) {
|
|
1249
|
+
ws.send(JSON.stringify({
|
|
1250
|
+
type: 'tool_call_headline_chunk_evt',
|
|
1251
|
+
chunk,
|
|
1252
|
+
round,
|
|
1253
|
+
genseq: event.genseq,
|
|
1254
|
+
dialog: {
|
|
1255
|
+
selfId: dialog.id.selfId,
|
|
1256
|
+
rootId: dialog.id.rootId,
|
|
1257
|
+
},
|
|
1258
|
+
timestamp: event.ts,
|
|
1259
|
+
}));
|
|
1260
|
+
}
|
|
1261
|
+
},
|
|
1262
|
+
callHeadLineFinish: async () => {
|
|
1263
|
+
if (ws.readyState === 1) {
|
|
1264
|
+
ws.send(JSON.stringify({
|
|
1265
|
+
type: 'tool_call_headline_finish_evt',
|
|
1266
|
+
round,
|
|
1267
|
+
genseq: event.genseq,
|
|
1268
|
+
dialog: {
|
|
1269
|
+
selfId: dialog.id.selfId,
|
|
1270
|
+
rootId: dialog.id.rootId,
|
|
1271
|
+
},
|
|
1272
|
+
timestamp: event.ts,
|
|
1273
|
+
}));
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
callBodyStart: async () => {
|
|
1277
|
+
if (ws.readyState === 1) {
|
|
1278
|
+
ws.send(JSON.stringify({
|
|
1279
|
+
type: 'tool_call_body_start_evt',
|
|
1280
|
+
round,
|
|
1281
|
+
genseq: event.genseq,
|
|
1282
|
+
dialog: {
|
|
1283
|
+
selfId: dialog.id.selfId,
|
|
1284
|
+
rootId: dialog.id.rootId,
|
|
1285
|
+
},
|
|
1286
|
+
timestamp: event.ts,
|
|
1287
|
+
}));
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
callBodyChunk: async (chunk) => {
|
|
1291
|
+
if (ws.readyState === 1) {
|
|
1292
|
+
ws.send(JSON.stringify({
|
|
1293
|
+
type: 'tool_call_body_chunk_evt',
|
|
1294
|
+
chunk,
|
|
1295
|
+
round,
|
|
1296
|
+
genseq: event.genseq,
|
|
1297
|
+
dialog: {
|
|
1298
|
+
selfId: dialog.id.selfId,
|
|
1299
|
+
rootId: dialog.id.rootId,
|
|
1300
|
+
},
|
|
1301
|
+
timestamp: event.ts,
|
|
1302
|
+
}));
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
callBodyFinish: async () => {
|
|
1306
|
+
if (ws.readyState === 1) {
|
|
1307
|
+
ws.send(JSON.stringify({
|
|
1308
|
+
type: 'tool_call_body_finish_evt',
|
|
1309
|
+
round,
|
|
1310
|
+
genseq: event.genseq,
|
|
1311
|
+
dialog: {
|
|
1312
|
+
selfId: dialog.id.selfId,
|
|
1313
|
+
rootId: dialog.id.rootId,
|
|
1314
|
+
},
|
|
1315
|
+
timestamp: event.ts,
|
|
1316
|
+
}));
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
callFinish: async (callId) => {
|
|
1320
|
+
if (ws.readyState === 1) {
|
|
1321
|
+
ws.send(JSON.stringify({
|
|
1322
|
+
type: 'tool_call_finish_evt',
|
|
1323
|
+
callId,
|
|
1324
|
+
round,
|
|
1325
|
+
genseq: event.genseq,
|
|
1326
|
+
dialog: {
|
|
1327
|
+
selfId: dialog.id.selfId,
|
|
1328
|
+
rootId: dialog.id.rootId,
|
|
1329
|
+
},
|
|
1330
|
+
timestamp: event.ts,
|
|
1331
|
+
}));
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
};
|
|
1335
|
+
// Use the same TellaskStreamParser that live streaming uses
|
|
1336
|
+
const streamingParser = new tellask_1.TellaskStreamParser(receiver);
|
|
1337
|
+
// Stream the content through the parser to ensure consistent event emission
|
|
1338
|
+
await streamingParser.takeUpstreamChunk(content);
|
|
1339
|
+
await streamingParser.finalize();
|
|
1340
|
+
}
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
case 'func_call_record': {
|
|
1344
|
+
// Handle function call events from persistence
|
|
1345
|
+
// NOTE: func_call_evt REMOVED - emit func_call_requested_evt for UI instead
|
|
1346
|
+
const funcCall = {
|
|
1347
|
+
type: 'func_call_requested_evt',
|
|
1348
|
+
funcId: event.id,
|
|
1349
|
+
funcName: event.name,
|
|
1350
|
+
arguments: JSON.stringify(event.arguments),
|
|
1351
|
+
round,
|
|
1352
|
+
genseq: event.genseq,
|
|
1353
|
+
dialog: {
|
|
1354
|
+
selfId: dialog.id.selfId,
|
|
1355
|
+
rootId: dialog.id.rootId,
|
|
1356
|
+
},
|
|
1357
|
+
timestamp: event.ts,
|
|
1358
|
+
};
|
|
1359
|
+
if (ws.readyState === 1) {
|
|
1360
|
+
ws.send(JSON.stringify(funcCall));
|
|
1361
|
+
}
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
case 'func_result_record': {
|
|
1365
|
+
// Handle function result events from persistence
|
|
1366
|
+
const funcResult = {
|
|
1367
|
+
type: 'func_result_evt',
|
|
1368
|
+
id: event.id,
|
|
1369
|
+
name: event.name,
|
|
1370
|
+
content: event.content,
|
|
1371
|
+
round,
|
|
1372
|
+
dialog: {
|
|
1373
|
+
selfId: dialog.id.selfId,
|
|
1374
|
+
rootId: dialog.id.rootId,
|
|
1375
|
+
},
|
|
1376
|
+
timestamp: event.ts,
|
|
1377
|
+
};
|
|
1378
|
+
if (ws.readyState === 1) {
|
|
1379
|
+
ws.send(JSON.stringify(funcResult));
|
|
1380
|
+
}
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
case 'quest_for_sup_record': {
|
|
1384
|
+
// Handle subdialog creation requests
|
|
1385
|
+
const subdialogCreatedEvent = {
|
|
1386
|
+
type: 'subdialog_created_evt',
|
|
1387
|
+
dialog: {
|
|
1388
|
+
// Add dialog field for proper event routing
|
|
1389
|
+
selfId: event.subDialogId,
|
|
1390
|
+
rootId: dialog.id.rootId,
|
|
1391
|
+
},
|
|
1392
|
+
parentDialog: {
|
|
1393
|
+
selfId: dialog.id.selfId,
|
|
1394
|
+
rootId: dialog.id.rootId,
|
|
1395
|
+
},
|
|
1396
|
+
subDialog: {
|
|
1397
|
+
selfId: event.subDialogId,
|
|
1398
|
+
rootId: dialog.id.rootId, // Use parent's rootId for subdialog's rootId
|
|
1399
|
+
},
|
|
1400
|
+
targetAgentId: 'unknown', // Will be resolved during actual subdialog creation
|
|
1401
|
+
headLine: event.headLine,
|
|
1402
|
+
callBody: event.callBody,
|
|
1403
|
+
timestamp: event.ts,
|
|
1404
|
+
};
|
|
1405
|
+
if (ws.readyState === 1) {
|
|
1406
|
+
ws.send(JSON.stringify(subdialogCreatedEvent));
|
|
1407
|
+
}
|
|
1408
|
+
break;
|
|
1409
|
+
}
|
|
1410
|
+
case 'tool_call_result_record': {
|
|
1411
|
+
// Handle tool call results
|
|
1412
|
+
const responseEvent = {
|
|
1413
|
+
type: 'tool_call_response_evt',
|
|
1414
|
+
responderId: event.responderId,
|
|
1415
|
+
headLine: event.headLine,
|
|
1416
|
+
status: event.status,
|
|
1417
|
+
result: event.result,
|
|
1418
|
+
callId: event.callId || '',
|
|
1419
|
+
round,
|
|
1420
|
+
calling_genseq: event.calling_genseq,
|
|
1421
|
+
dialog: {
|
|
1422
|
+
selfId: dialog.id.selfId,
|
|
1423
|
+
rootId: dialog.id.rootId,
|
|
1424
|
+
},
|
|
1425
|
+
timestamp: event.ts,
|
|
1426
|
+
};
|
|
1427
|
+
if (ws.readyState === 1) {
|
|
1428
|
+
ws.send(JSON.stringify(responseEvent));
|
|
1429
|
+
}
|
|
1430
|
+
break;
|
|
1431
|
+
}
|
|
1432
|
+
case 'teammate_response_record': {
|
|
1433
|
+
// Handle teammate response events (separate bubble for @teammate tellasks)
|
|
1434
|
+
const formattedResult = (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
1435
|
+
responderId: event.responderId,
|
|
1436
|
+
requesterId: event.originMemberId,
|
|
1437
|
+
originalCallHeadLine: event.headLine,
|
|
1438
|
+
responseBody: event.response,
|
|
1439
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
1440
|
+
});
|
|
1441
|
+
const teammateResponseEvent = {
|
|
1442
|
+
type: 'teammate_response_evt',
|
|
1443
|
+
responderId: event.responderId,
|
|
1444
|
+
calleeDialogId: event.calleeDialogId,
|
|
1445
|
+
headLine: event.headLine,
|
|
1446
|
+
status: event.status,
|
|
1447
|
+
result: formattedResult,
|
|
1448
|
+
response: event.response,
|
|
1449
|
+
agentId: event.agentId,
|
|
1450
|
+
callId: event.callId,
|
|
1451
|
+
originMemberId: event.originMemberId,
|
|
1452
|
+
round,
|
|
1453
|
+
calling_genseq: event.calling_genseq,
|
|
1454
|
+
dialog: {
|
|
1455
|
+
selfId: dialog.id.selfId,
|
|
1456
|
+
rootId: dialog.id.rootId,
|
|
1457
|
+
},
|
|
1458
|
+
timestamp: event.ts,
|
|
1459
|
+
};
|
|
1460
|
+
if (ws.readyState === 1) {
|
|
1461
|
+
ws.send(JSON.stringify(teammateResponseEvent));
|
|
1462
|
+
}
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
default:
|
|
1466
|
+
// Unknown event type - log but don't crash
|
|
1467
|
+
log_1.log.warn(`Unknown persistence event type during direct WebSocket send`, undefined, event);
|
|
1468
|
+
break;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Create optimal text chunks for websocket transmission
|
|
1473
|
+
* Splits content into 1MB pieces for efficient websocket streaming
|
|
1474
|
+
*/
|
|
1475
|
+
createOptimalChunks(content, maxChunk = 1000000) {
|
|
1476
|
+
const chunks = [];
|
|
1477
|
+
let remaining = content.trim();
|
|
1478
|
+
while (remaining.length > 0) {
|
|
1479
|
+
// Use 1MB chunks for optimal websocket transmission
|
|
1480
|
+
const targetSize = Math.min(remaining.length, maxChunk);
|
|
1481
|
+
const chunk = remaining.slice(0, targetSize);
|
|
1482
|
+
chunks.push(chunk);
|
|
1483
|
+
remaining = remaining.slice(chunk.length).trim();
|
|
1484
|
+
}
|
|
1485
|
+
return chunks.filter((chunk) => chunk.length > 0);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
exports.DiskFileDialogStore = DiskFileDialogStore;
|
|
1489
|
+
/**
|
|
1490
|
+
* Utility class for managing dialog persistence
|
|
1491
|
+
*/
|
|
1492
|
+
class DialogPersistence {
|
|
1493
|
+
static getLatestWriteBackMutex(key) {
|
|
1494
|
+
const existing = this.latestWriteBackMutexes.get(key);
|
|
1495
|
+
if (existing)
|
|
1496
|
+
return existing;
|
|
1497
|
+
const created = new async_fifo_mutex_1.AsyncFifoMutex();
|
|
1498
|
+
this.latestWriteBackMutexes.set(key, created);
|
|
1499
|
+
return created;
|
|
1500
|
+
}
|
|
1501
|
+
static getLatestWriteBackKey(dialogId, status) {
|
|
1502
|
+
// Include dialogs root dir to avoid cross-test/process.cwd collisions.
|
|
1503
|
+
return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}`;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Get the base dialogs directory path
|
|
1507
|
+
*/
|
|
1508
|
+
static getDialogsRootDir() {
|
|
1509
|
+
return path.join(process.cwd(), this.DIALOGS_DIR);
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Save dialog state to JSON file for persistence (internal use only)
|
|
1513
|
+
*/
|
|
1514
|
+
static async saveDialogState(state) {
|
|
1515
|
+
try {
|
|
1516
|
+
const dialogPath = await this.ensureRootDialogDirectory(new dialog_1.DialogID(state.metadata.id));
|
|
1517
|
+
// Save state as JSON file
|
|
1518
|
+
const stateFile = path.join(dialogPath, 'state.json');
|
|
1519
|
+
await fs.promises.writeFile(stateFile, JSON.stringify({
|
|
1520
|
+
metadata: state.metadata,
|
|
1521
|
+
currentRound: state.currentRound,
|
|
1522
|
+
messages: state.messages,
|
|
1523
|
+
reminders: state.reminders,
|
|
1524
|
+
savedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
1525
|
+
}, null, 2), 'utf-8');
|
|
1526
|
+
}
|
|
1527
|
+
catch (error) {
|
|
1528
|
+
log_1.log.error(`Failed to save dialog state for ${state.metadata.id}:`, error);
|
|
1529
|
+
throw error;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Load dialog state from JSON file
|
|
1534
|
+
*/
|
|
1535
|
+
static async loadDialogState(dialogId) {
|
|
1536
|
+
try {
|
|
1537
|
+
const dialogPath = this.getRootDialogPath(dialogId, 'running');
|
|
1538
|
+
const stateFile = path.join(dialogPath, 'state.json');
|
|
1539
|
+
// Check if state file exists
|
|
1540
|
+
try {
|
|
1541
|
+
await fs.promises.access(stateFile);
|
|
1542
|
+
}
|
|
1543
|
+
catch {
|
|
1544
|
+
log_1.log.warn(`No state file found for dialog ${dialogId.selfId}, returning null`);
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
const stateData = JSON.parse(await fs.promises.readFile(stateFile, 'utf-8'));
|
|
1548
|
+
return {
|
|
1549
|
+
metadata: stateData.metadata,
|
|
1550
|
+
currentRound: stateData.currentRound,
|
|
1551
|
+
messages: stateData.messages,
|
|
1552
|
+
reminders: stateData.reminders || [],
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
catch (error) {
|
|
1556
|
+
log_1.log.error(`Failed to load dialog state for root ${dialogId.selfId}:`, error);
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Get the full path for a dialog directory
|
|
1562
|
+
*/
|
|
1563
|
+
static getRootDialogPath(dialogId, status = 'running') {
|
|
1564
|
+
if (dialogId.rootId !== dialogId.selfId) {
|
|
1565
|
+
throw new Error('Expected root dialog id');
|
|
1566
|
+
}
|
|
1567
|
+
let statusDir;
|
|
1568
|
+
if (status === 'running') {
|
|
1569
|
+
statusDir = this.RUN_DIR;
|
|
1570
|
+
}
|
|
1571
|
+
else if (status === 'completed') {
|
|
1572
|
+
statusDir = this.DONE_DIR;
|
|
1573
|
+
}
|
|
1574
|
+
else {
|
|
1575
|
+
statusDir = this.ARCHIVE_DIR;
|
|
1576
|
+
}
|
|
1577
|
+
return path.join(this.getDialogsRootDir(), statusDir, dialogId.selfId);
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Get the events/state directory for a dialog (composite ID for subdialogs)
|
|
1581
|
+
*/
|
|
1582
|
+
static getDialogEventsPath(dialogId, status = 'running') {
|
|
1583
|
+
// Root dialogs store events under their own directory.
|
|
1584
|
+
// Subdialogs store events under the root's subdialogs/<self> directory.
|
|
1585
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
1586
|
+
return this.getRootDialogPath(dialogId, status);
|
|
1587
|
+
}
|
|
1588
|
+
return this.getSubdialogPath(dialogId, status);
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Get the path for a subdialog within a supdialog
|
|
1592
|
+
*/
|
|
1593
|
+
static getSubdialogPath(dialogId, status = 'running') {
|
|
1594
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
1595
|
+
throw new Error('Expected subdialog id (self differs from root)');
|
|
1596
|
+
}
|
|
1597
|
+
const rootPath = this.getRootDialogPath(new dialog_1.DialogID(dialogId.rootId), status);
|
|
1598
|
+
return path.join(rootPath, this.SUBDIALOGS_DIR, dialogId.selfId);
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Ensure dialog directory structure exists
|
|
1602
|
+
*/
|
|
1603
|
+
static async ensureRootDialogDirectory(dialogId, status = 'running') {
|
|
1604
|
+
const dialogPath = this.getRootDialogPath(dialogId, status);
|
|
1605
|
+
try {
|
|
1606
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
1607
|
+
return dialogPath;
|
|
1608
|
+
}
|
|
1609
|
+
catch (error) {
|
|
1610
|
+
log_1.log.error(`Failed to create dialog directory ${dialogPath}:`, error);
|
|
1611
|
+
throw error;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Ensure subdialog directory structure exists
|
|
1616
|
+
*/
|
|
1617
|
+
static async ensureSubdialogDirectory(dialogId, status = 'running') {
|
|
1618
|
+
const subdialogPath = this.getSubdialogPath(dialogId, status);
|
|
1619
|
+
try {
|
|
1620
|
+
await fs.promises.mkdir(subdialogPath, { recursive: true });
|
|
1621
|
+
return subdialogPath;
|
|
1622
|
+
}
|
|
1623
|
+
catch (error) {
|
|
1624
|
+
log_1.log.error(`Failed to create subdialog directory ${subdialogPath}:`, error);
|
|
1625
|
+
throw error;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Mark a dialog as completed
|
|
1630
|
+
*/
|
|
1631
|
+
static async markDialogCompleted(dialogId) {
|
|
1632
|
+
try {
|
|
1633
|
+
const dialogPath = this.getRootDialogPath(dialogId, 'running');
|
|
1634
|
+
const completedPath = this.getRootDialogPath(dialogId, 'completed');
|
|
1635
|
+
await fs.promises.mkdir(completedPath, { recursive: true });
|
|
1636
|
+
// Move files from current to completed
|
|
1637
|
+
const files = await fs.promises.readdir(dialogPath);
|
|
1638
|
+
for (const file of files) {
|
|
1639
|
+
const src = path.join(dialogPath, file);
|
|
1640
|
+
const dest = path.join(completedPath, file);
|
|
1641
|
+
await fs.promises.rename(src, dest);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
log_1.log.error(`Failed to mark dialog ${dialogId} as completed:`, error);
|
|
1646
|
+
throw error;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* List all dialog IDs by scanning for dialog.yaml files and validating their IDs
|
|
1651
|
+
*/
|
|
1652
|
+
static async listDialogs(status = 'running') {
|
|
1653
|
+
try {
|
|
1654
|
+
const statusDir = this.getDialogsRootDir();
|
|
1655
|
+
const specificDir = path.join(statusDir, status === 'running'
|
|
1656
|
+
? this.RUN_DIR
|
|
1657
|
+
: status === 'completed'
|
|
1658
|
+
? this.DONE_DIR
|
|
1659
|
+
: this.ARCHIVE_DIR);
|
|
1660
|
+
const validDialogIds = [];
|
|
1661
|
+
// Recursively find all dialog.yaml files
|
|
1662
|
+
const findDialogYamls = async (dirPath, relativePath = '') => {
|
|
1663
|
+
try {
|
|
1664
|
+
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1665
|
+
for (const entry of entries) {
|
|
1666
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
1667
|
+
const entryRelativePath = path.join(relativePath, entry.name);
|
|
1668
|
+
if (entry.isDirectory()) {
|
|
1669
|
+
// Recursively search subdirectories
|
|
1670
|
+
await findDialogYamls(fullPath, entryRelativePath);
|
|
1671
|
+
}
|
|
1672
|
+
else if (entry.name === 'dialog.yaml') {
|
|
1673
|
+
// Found a dialog.yaml file, record its ID regardless of nesting structure
|
|
1674
|
+
try {
|
|
1675
|
+
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
1676
|
+
const parsed = yaml.parse(content);
|
|
1677
|
+
if (parsed?.id && typeof parsed.id === 'string') {
|
|
1678
|
+
validDialogIds.push(parsed.id);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
catch (yamlError) {
|
|
1682
|
+
log_1.log.warn(`🔍 listDialogs: Failed to parse dialog.yaml at ${fullPath}:`, yamlError);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
catch (error) {
|
|
1688
|
+
log_1.log.warn(`🔍 listDialogs: Error reading directory ${dirPath}:`, error);
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
try {
|
|
1692
|
+
// Check if directory exists before trying to read it
|
|
1693
|
+
const dirExists = await fs.promises
|
|
1694
|
+
.stat(specificDir)
|
|
1695
|
+
.then(() => true)
|
|
1696
|
+
.catch(() => false);
|
|
1697
|
+
if (dirExists) {
|
|
1698
|
+
await findDialogYamls(specificDir);
|
|
1699
|
+
}
|
|
1700
|
+
return validDialogIds;
|
|
1701
|
+
}
|
|
1702
|
+
catch (error) {
|
|
1703
|
+
log_1.log.warn(`🔍 listDialogs: Error processing directory ${specificDir}:`, error instanceof Error ? error.message : String(error));
|
|
1704
|
+
return [];
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
catch (error) {
|
|
1708
|
+
log_1.log.error('Failed to list dialogs:', error);
|
|
1709
|
+
return [];
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* List all dialog IDs (root + subdialogs) together with their root IDs.
|
|
1714
|
+
* This is the only safe way to enumerate subdialogs because their directory names
|
|
1715
|
+
* are not guaranteed to be their selfId.
|
|
1716
|
+
*/
|
|
1717
|
+
static async listAllDialogIds(status = 'running') {
|
|
1718
|
+
const statusDir = this.getDialogsRootDir();
|
|
1719
|
+
const specificDir = path.join(statusDir, status === 'running'
|
|
1720
|
+
? this.RUN_DIR
|
|
1721
|
+
: status === 'completed'
|
|
1722
|
+
? this.DONE_DIR
|
|
1723
|
+
: this.ARCHIVE_DIR);
|
|
1724
|
+
const result = [];
|
|
1725
|
+
const rootDialogIdByDialogYamlPath = new Map();
|
|
1726
|
+
const readDialogYamlId = async (dialogYamlPath) => {
|
|
1727
|
+
const cached = rootDialogIdByDialogYamlPath.get(dialogYamlPath);
|
|
1728
|
+
if (cached !== undefined)
|
|
1729
|
+
return cached;
|
|
1730
|
+
try {
|
|
1731
|
+
const content = await fs.promises.readFile(dialogYamlPath, 'utf-8');
|
|
1732
|
+
const parsed = yaml.parse(content);
|
|
1733
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
1734
|
+
rootDialogIdByDialogYamlPath.set(dialogYamlPath, null);
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
const idValue = parsed.id;
|
|
1738
|
+
if (typeof idValue !== 'string' || idValue.trim() === '') {
|
|
1739
|
+
rootDialogIdByDialogYamlPath.set(dialogYamlPath, null);
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
const normalized = idValue.trim();
|
|
1743
|
+
rootDialogIdByDialogYamlPath.set(dialogYamlPath, normalized);
|
|
1744
|
+
return normalized;
|
|
1745
|
+
}
|
|
1746
|
+
catch {
|
|
1747
|
+
rootDialogIdByDialogYamlPath.set(dialogYamlPath, null);
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
const inferRootIdFromRelativeDir = async (relativeDir) => {
|
|
1752
|
+
const dir = relativeDir.trim();
|
|
1753
|
+
if (dir === '' || dir === '.' || dir === path.sep)
|
|
1754
|
+
return null;
|
|
1755
|
+
const segments = dir.split(path.sep).filter((seg) => seg.length > 0 && seg !== '.');
|
|
1756
|
+
if (segments.length === 0)
|
|
1757
|
+
return null;
|
|
1758
|
+
// Root dialog IDs in this repo can contain path separators (e.g. "f4/44/cd85c4e2").
|
|
1759
|
+
// The root dialog directory is therefore nested (RUN_DIR/<rootId>/dialog.yaml).
|
|
1760
|
+
//
|
|
1761
|
+
// To infer the rootId for any dialog.yaml we find (root or subdialog), scan prefixes of the
|
|
1762
|
+
// directory path and pick the first prefix that is itself a valid root dialog directory:
|
|
1763
|
+
// - it has a dialog.yaml
|
|
1764
|
+
// - its dialog.yaml id matches the prefix joined with '/'
|
|
1765
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
1766
|
+
const prefixSegs = segments.slice(0, i);
|
|
1767
|
+
const candidateDialogYamlPath = path.join(specificDir, ...prefixSegs, 'dialog.yaml');
|
|
1768
|
+
const id = await readDialogYamlId(candidateDialogYamlPath);
|
|
1769
|
+
const expectedId = prefixSegs.join('/');
|
|
1770
|
+
if (id === expectedId)
|
|
1771
|
+
return expectedId;
|
|
1772
|
+
}
|
|
1773
|
+
return null;
|
|
1774
|
+
};
|
|
1775
|
+
const findDialogYamls = async (dirPath, relativePath = '') => {
|
|
1776
|
+
let entries;
|
|
1777
|
+
try {
|
|
1778
|
+
entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1779
|
+
}
|
|
1780
|
+
catch (err) {
|
|
1781
|
+
log_1.log.warn(`🔍 listAllDialogIds: Error reading directory ${dirPath}:`, err);
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
for (const entry of entries) {
|
|
1785
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
1786
|
+
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
1787
|
+
if (entry.isDirectory()) {
|
|
1788
|
+
await findDialogYamls(fullPath, entryRelativePath);
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
if (entry.name !== 'dialog.yaml')
|
|
1792
|
+
continue;
|
|
1793
|
+
const relDir = path.dirname(entryRelativePath);
|
|
1794
|
+
const rootId = await inferRootIdFromRelativeDir(relDir);
|
|
1795
|
+
if (!rootId)
|
|
1796
|
+
continue;
|
|
1797
|
+
try {
|
|
1798
|
+
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
1799
|
+
const parsed = yaml.parse(content);
|
|
1800
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
1801
|
+
continue;
|
|
1802
|
+
const idValue = parsed.id;
|
|
1803
|
+
if (typeof idValue !== 'string' || idValue.trim() === '')
|
|
1804
|
+
continue;
|
|
1805
|
+
result.push(new dialog_1.DialogID(idValue, rootId));
|
|
1806
|
+
}
|
|
1807
|
+
catch (yamlError) {
|
|
1808
|
+
log_1.log.warn(`🔍 listAllDialogIds: Failed to parse dialog.yaml at ${fullPath}:`, yamlError);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
const dirExists = await fs.promises
|
|
1813
|
+
.stat(specificDir)
|
|
1814
|
+
.then(() => true)
|
|
1815
|
+
.catch(() => false);
|
|
1816
|
+
if (!dirExists)
|
|
1817
|
+
return [];
|
|
1818
|
+
await findDialogYamls(specificDir);
|
|
1819
|
+
return result;
|
|
1820
|
+
}
|
|
1821
|
+
// === NEW JSONL ROUND-BASED METHODS ===
|
|
1822
|
+
/**
|
|
1823
|
+
* Append event to round JSONL file (append-only pattern)
|
|
1824
|
+
*/
|
|
1825
|
+
static async appendEvent(dialogId, round, event, status = 'running') {
|
|
1826
|
+
try {
|
|
1827
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
1828
|
+
const roundFilename = this.getRoundFilename(round);
|
|
1829
|
+
const roundFilePath = path.join(dialogPath, roundFilename);
|
|
1830
|
+
// Ensure directory exists
|
|
1831
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
1832
|
+
// Atomic append operation
|
|
1833
|
+
const eventLine = JSON.stringify(event) + '\n';
|
|
1834
|
+
await fs.promises.appendFile(roundFilePath, eventLine, 'utf-8');
|
|
1835
|
+
// Update latest.yaml with new lastModified timestamp
|
|
1836
|
+
await this.mutateDialogLatest(dialogId, () => ({
|
|
1837
|
+
kind: 'patch',
|
|
1838
|
+
patch: {
|
|
1839
|
+
lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
1840
|
+
currentRound: round,
|
|
1841
|
+
},
|
|
1842
|
+
}), status);
|
|
1843
|
+
}
|
|
1844
|
+
catch (error) {
|
|
1845
|
+
log_1.log.error(`Failed to append event to dialog ${dialogId} round ${round}:`, error);
|
|
1846
|
+
throw error;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Read all events from round JSONL file
|
|
1851
|
+
*/
|
|
1852
|
+
static async readRoundEvents(dialogId, round, status = 'running') {
|
|
1853
|
+
try {
|
|
1854
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
1855
|
+
const roundFilename = this.getRoundFilename(round);
|
|
1856
|
+
const roundFilePath = path.join(dialogPath, roundFilename);
|
|
1857
|
+
try {
|
|
1858
|
+
const content = await fs.promises.readFile(roundFilePath, 'utf-8');
|
|
1859
|
+
const events = [];
|
|
1860
|
+
for (const line of content.trim().split('\n')) {
|
|
1861
|
+
if (line.trim()) {
|
|
1862
|
+
events.push(JSON.parse(line));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return events;
|
|
1866
|
+
}
|
|
1867
|
+
catch (error) {
|
|
1868
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
1869
|
+
// Round file doesn't exist - return empty array
|
|
1870
|
+
return [];
|
|
1871
|
+
}
|
|
1872
|
+
throw error;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
catch (error) {
|
|
1876
|
+
log_1.log.error(`Failed to read round events for dialog ${dialogId} round ${round}:`, error);
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Compute next sequence number for a round by scanning existing events
|
|
1882
|
+
*/
|
|
1883
|
+
static async getNextSeq(dialogId, round, status = 'running') {
|
|
1884
|
+
const events = await this.readRoundEvents(dialogId, round, status);
|
|
1885
|
+
let maxSeq = 0;
|
|
1886
|
+
for (const ev of events) {
|
|
1887
|
+
if ('genseq' in ev && typeof ev.genseq === 'number' && ev.genseq > maxSeq) {
|
|
1888
|
+
maxSeq = ev.genseq;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return maxSeq + 1;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Get current round number from latest.yaml (performance optimization)
|
|
1895
|
+
* UI navigation can assume natural numbering schema back to 1
|
|
1896
|
+
*/
|
|
1897
|
+
static async getCurrentRoundNumber(dialogId, status = 'running') {
|
|
1898
|
+
try {
|
|
1899
|
+
const latest = await this.loadDialogLatest(dialogId, status);
|
|
1900
|
+
return latest?.currentRound || 1;
|
|
1901
|
+
}
|
|
1902
|
+
catch (error) {
|
|
1903
|
+
log_1.log.error(`Failed to get current round for dialog ${dialogId}:`, error);
|
|
1904
|
+
return 1;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Save reminder state (exceptional overwrite pattern) (internal use only)
|
|
1909
|
+
*/
|
|
1910
|
+
static async _saveReminderState(dialogId, reminders, status = 'running') {
|
|
1911
|
+
try {
|
|
1912
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
1913
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
1914
|
+
const remindersFilePath = path.join(dialogPath, 'reminders.json');
|
|
1915
|
+
const reminderState = {
|
|
1916
|
+
reminders: reminders.map((r, index) => ({
|
|
1917
|
+
id: `reminder-${index}`,
|
|
1918
|
+
content: r.content,
|
|
1919
|
+
ownerName: r.owner ? r.owner.name : undefined,
|
|
1920
|
+
meta: r.meta,
|
|
1921
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
1922
|
+
priority: 'medium',
|
|
1923
|
+
})),
|
|
1924
|
+
updatedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
1925
|
+
};
|
|
1926
|
+
// Atomic write operation
|
|
1927
|
+
const tempFile = `${remindersFilePath}.${process.pid}.${Date.now()}.tmp`;
|
|
1928
|
+
await fs.promises.writeFile(tempFile, JSON.stringify(reminderState, null, 2), 'utf-8');
|
|
1929
|
+
await fs.promises.rename(tempFile, remindersFilePath);
|
|
1930
|
+
}
|
|
1931
|
+
catch (error) {
|
|
1932
|
+
log_1.log.error(`Failed to save reminder state for dialog ${dialogId}:`, error);
|
|
1933
|
+
throw error;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Load reminder state
|
|
1938
|
+
*/
|
|
1939
|
+
static async loadReminderState(dialogId, status = 'running') {
|
|
1940
|
+
try {
|
|
1941
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
1942
|
+
const remindersFilePath = path.join(dialogPath, 'reminders.json');
|
|
1943
|
+
try {
|
|
1944
|
+
const content = await fs.promises.readFile(remindersFilePath, 'utf-8');
|
|
1945
|
+
const reminderState = JSON.parse(content);
|
|
1946
|
+
return reminderState.reminders.map((r) => {
|
|
1947
|
+
const ownerNameFromFile = typeof r.ownerName === 'string' ? r.ownerName : undefined;
|
|
1948
|
+
const owner = ownerNameFromFile ? (0, registry_1.getReminderOwner)(ownerNameFromFile) : undefined;
|
|
1949
|
+
return {
|
|
1950
|
+
id: r.id,
|
|
1951
|
+
content: r.content,
|
|
1952
|
+
owner,
|
|
1953
|
+
meta: r.meta,
|
|
1954
|
+
createdAt: r.createdAt,
|
|
1955
|
+
priority: r.priority,
|
|
1956
|
+
};
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
catch (error) {
|
|
1960
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
1961
|
+
// reminders.json doesn't exist - return empty array
|
|
1962
|
+
return [];
|
|
1963
|
+
}
|
|
1964
|
+
throw error;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
catch (error) {
|
|
1968
|
+
log_1.log.error(`Failed to load reminder state for dialog ${dialogId}:`, error);
|
|
1969
|
+
return [];
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Save questions for human state (exceptional overwrite pattern) (internal use only)
|
|
1974
|
+
*/
|
|
1975
|
+
static async _saveQuestions4HumanState(dialogId, questions, status = 'running') {
|
|
1976
|
+
try {
|
|
1977
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
1978
|
+
const questionsFilePath = path.join(dialogPath, 'q4h.yaml');
|
|
1979
|
+
const questionsState = {
|
|
1980
|
+
questions,
|
|
1981
|
+
updatedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
1982
|
+
};
|
|
1983
|
+
// Atomic write operation
|
|
1984
|
+
const tempFile = questionsFilePath + '.tmp';
|
|
1985
|
+
const yamlContent = yaml.stringify(questionsState);
|
|
1986
|
+
await fs.promises.writeFile(tempFile, yamlContent, 'utf-8');
|
|
1987
|
+
await fs.promises.rename(tempFile, questionsFilePath);
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
log_1.log.error(`Failed to save q4h.yaml for dialog ${dialogId}:`, error);
|
|
1991
|
+
throw error;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Load questions for human state
|
|
1996
|
+
*/
|
|
1997
|
+
static async loadQuestions4HumanState(dialogId, status = 'running') {
|
|
1998
|
+
try {
|
|
1999
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
2000
|
+
const questionsFilePath = path.join(dialogPath, 'q4h.yaml');
|
|
2001
|
+
try {
|
|
2002
|
+
const content = await fs.promises.readFile(questionsFilePath, 'utf-8');
|
|
2003
|
+
const questionsState = yaml.parse(content);
|
|
2004
|
+
return questionsState.questions.map((q) => ({
|
|
2005
|
+
...q,
|
|
2006
|
+
kind: normalizeQ4HKind(q.kind),
|
|
2007
|
+
}));
|
|
2008
|
+
}
|
|
2009
|
+
catch (error) {
|
|
2010
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2011
|
+
// q4h.yaml doesn't exist - return empty array
|
|
2012
|
+
return [];
|
|
2013
|
+
}
|
|
2014
|
+
throw error;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
catch (error) {
|
|
2018
|
+
log_1.log.error(`Failed to load q4h.yaml for dialog ${dialogId}:`, error);
|
|
2019
|
+
return [];
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Load all Q4H questions from all running dialogs (for global Q4H display)
|
|
2024
|
+
* Returns array of questions with their dialog context for frontend display
|
|
2025
|
+
*/
|
|
2026
|
+
static async loadAllQ4HState() {
|
|
2027
|
+
try {
|
|
2028
|
+
// Get all running dialogs (root + subdialogs) with correct rootId association.
|
|
2029
|
+
const dialogIds = await this.listAllDialogIds('running');
|
|
2030
|
+
const allQuestions = [];
|
|
2031
|
+
for (const dialogIdObj of dialogIds) {
|
|
2032
|
+
try {
|
|
2033
|
+
const questions = await this.loadQuestions4HumanState(dialogIdObj, 'running');
|
|
2034
|
+
const metadata = await this.loadDialogMetadata(dialogIdObj, 'running');
|
|
2035
|
+
if (metadata && questions.length > 0) {
|
|
2036
|
+
for (const q of questions) {
|
|
2037
|
+
allQuestions.push({
|
|
2038
|
+
...q,
|
|
2039
|
+
selfId: dialogIdObj.selfId,
|
|
2040
|
+
rootId: dialogIdObj.rootId,
|
|
2041
|
+
agentId: metadata.agentId,
|
|
2042
|
+
taskDocPath: metadata.taskDocPath,
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
catch (err) {
|
|
2048
|
+
log_1.log.warn(`Failed to load Q4H for dialog ${dialogIdObj.valueOf()}:`, err);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return allQuestions;
|
|
2052
|
+
}
|
|
2053
|
+
catch (error) {
|
|
2054
|
+
log_1.log.error('Failed to load all Q4H state:', error);
|
|
2055
|
+
return [];
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
static async clearQuestions4HumanState(dialogId, status = 'running') {
|
|
2059
|
+
try {
|
|
2060
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
2061
|
+
const questionsFilePath = path.join(dialogPath, 'q4h.yaml');
|
|
2062
|
+
let previousCount = 0;
|
|
2063
|
+
let existingQuestions = [];
|
|
2064
|
+
try {
|
|
2065
|
+
existingQuestions = await this.loadQuestions4HumanState(dialogId, status);
|
|
2066
|
+
previousCount = existingQuestions.length;
|
|
2067
|
+
}
|
|
2068
|
+
catch (err) {
|
|
2069
|
+
log_1.log.debug('No existing questions state found, using default count', err);
|
|
2070
|
+
}
|
|
2071
|
+
await fs.promises.rm(questionsFilePath, { force: true });
|
|
2072
|
+
// Emit q4h_answered events for each removed question
|
|
2073
|
+
for (const q of existingQuestions) {
|
|
2074
|
+
const answeredEvent = {
|
|
2075
|
+
type: 'q4h_answered',
|
|
2076
|
+
questionId: q.id,
|
|
2077
|
+
selfId: dialogId.selfId,
|
|
2078
|
+
};
|
|
2079
|
+
(0, evt_registry_1.postDialogEventById)(dialogId, answeredEvent);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
catch (error) {
|
|
2083
|
+
log_1.log.error(`Failed to clear q4h.yaml for dialog ${dialogId}:`, error);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
// === PHASE 6: SUBDIALOG SUPPLY PERSISTENCE ===
|
|
2087
|
+
/**
|
|
2088
|
+
* Save pending subdialogs for Type A supply mechanism.
|
|
2089
|
+
* Tracks subdialogs that were created but not yet completed.
|
|
2090
|
+
*/
|
|
2091
|
+
static async savePendingSubdialogs(rootDialogId, pendingSubdialogs, status = 'running') {
|
|
2092
|
+
try {
|
|
2093
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
2094
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2095
|
+
const filePath = path.join(dialogPath, 'pending-subdialogs.json');
|
|
2096
|
+
// Atomic write operation
|
|
2097
|
+
const tempFile = filePath + '.tmp';
|
|
2098
|
+
await fs.promises.writeFile(tempFile, JSON.stringify(pendingSubdialogs, null, 2), 'utf-8');
|
|
2099
|
+
await fs.promises.rename(tempFile, filePath);
|
|
2100
|
+
}
|
|
2101
|
+
catch (error) {
|
|
2102
|
+
log_1.log.error(`Failed to save pending subdialogs for dialog ${rootDialogId}:`, error);
|
|
2103
|
+
throw error;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Load pending subdialogs for Type A supply mechanism.
|
|
2108
|
+
*/
|
|
2109
|
+
static async loadPendingSubdialogs(rootDialogId, status = 'running') {
|
|
2110
|
+
try {
|
|
2111
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
2112
|
+
const filePath = path.join(dialogPath, 'pending-subdialogs.json');
|
|
2113
|
+
try {
|
|
2114
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
2115
|
+
return JSON.parse(content);
|
|
2116
|
+
}
|
|
2117
|
+
catch (error) {
|
|
2118
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2119
|
+
return [];
|
|
2120
|
+
}
|
|
2121
|
+
throw error;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
catch (error) {
|
|
2125
|
+
log_1.log.error(`Failed to load pending subdialogs for dialog ${rootDialogId}:`, error);
|
|
2126
|
+
return [];
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Get the path for storing subdialog responses (supports both root and subdialog parents).
|
|
2131
|
+
* For Type C subdialogs created inside another subdialog, responses are stored at the parent's level.
|
|
2132
|
+
*/
|
|
2133
|
+
static getDialogResponsesPath(dialogId, status = 'running') {
|
|
2134
|
+
// Root dialogs store responses in their own directory.
|
|
2135
|
+
// Subdialogs store responses in the parent's location (root or subdialog).
|
|
2136
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
2137
|
+
// Root dialog: use root's directory
|
|
2138
|
+
return this.getRootDialogPath(dialogId, status);
|
|
2139
|
+
}
|
|
2140
|
+
// Subdialog: store in parent's subdialogs directory
|
|
2141
|
+
// The parent is always identified by rootId (could be root or parent subdialog)
|
|
2142
|
+
const parentSelfId = dialogId.rootId;
|
|
2143
|
+
const rootPath = this.getRootDialogPath(new dialog_1.DialogID(parentSelfId), status);
|
|
2144
|
+
return path.join(rootPath, this.SUBDIALOGS_DIR, dialogId.selfId);
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Save subdialog responses for Type A supply mechanism.
|
|
2148
|
+
* Tracks responses from completed subdialogs.
|
|
2149
|
+
*/
|
|
2150
|
+
static async saveSubdialogResponses(rootDialogId, responses, status = 'running') {
|
|
2151
|
+
try {
|
|
2152
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
2153
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2154
|
+
const filePath = path.join(dialogPath, 'subdialog-responses.json');
|
|
2155
|
+
// Atomic write operation
|
|
2156
|
+
const tempFile = filePath + '.tmp';
|
|
2157
|
+
await fs.promises.writeFile(tempFile, JSON.stringify(responses, null, 2), 'utf-8');
|
|
2158
|
+
await fs.promises.rename(tempFile, filePath);
|
|
2159
|
+
}
|
|
2160
|
+
catch (error) {
|
|
2161
|
+
log_1.log.error(`Failed to save subdialog responses for dialog ${rootDialogId}:`, error);
|
|
2162
|
+
throw error;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Load subdialog responses for Type A supply mechanism.
|
|
2167
|
+
*/
|
|
2168
|
+
static async loadSubdialogResponses(rootDialogId, status = 'running') {
|
|
2169
|
+
try {
|
|
2170
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
2171
|
+
const filePath = path.join(dialogPath, 'subdialog-responses.json');
|
|
2172
|
+
const inflightPath = path.join(dialogPath, 'subdialog-responses.processing.json');
|
|
2173
|
+
try {
|
|
2174
|
+
const results = [];
|
|
2175
|
+
const tryReadArray = async (p) => {
|
|
2176
|
+
try {
|
|
2177
|
+
const content = await fs.promises.readFile(p, 'utf-8');
|
|
2178
|
+
const parsed = JSON.parse(content);
|
|
2179
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
2180
|
+
}
|
|
2181
|
+
catch (error) {
|
|
2182
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2183
|
+
return [];
|
|
2184
|
+
}
|
|
2185
|
+
throw error;
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
const primary = await tryReadArray(filePath);
|
|
2189
|
+
const inflight = await tryReadArray(inflightPath);
|
|
2190
|
+
for (const item of [...primary, ...inflight]) {
|
|
2191
|
+
if (isSubdialogResponseRecord(item)) {
|
|
2192
|
+
results.push(item);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
// Deduplicate by responseId (primary wins over inflight order is irrelevant)
|
|
2196
|
+
const byId = new Map();
|
|
2197
|
+
for (const r of results) {
|
|
2198
|
+
byId.set(r.responseId, r);
|
|
2199
|
+
}
|
|
2200
|
+
return Array.from(byId.values());
|
|
2201
|
+
}
|
|
2202
|
+
catch (error) {
|
|
2203
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2204
|
+
return [];
|
|
2205
|
+
}
|
|
2206
|
+
throw error;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
catch (error) {
|
|
2210
|
+
log_1.log.error(`Failed to load subdialog responses for dialog ${rootDialogId}:`, error);
|
|
2211
|
+
return [];
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
static async loadSubdialogResponsesQueue(dialogId, status = 'running') {
|
|
2215
|
+
try {
|
|
2216
|
+
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
2217
|
+
const filePath = path.join(dialogPath, 'subdialog-responses.json');
|
|
2218
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
2219
|
+
const parsed = JSON.parse(content);
|
|
2220
|
+
if (!Array.isArray(parsed)) {
|
|
2221
|
+
return [];
|
|
2222
|
+
}
|
|
2223
|
+
return parsed.filter(isSubdialogResponseRecord);
|
|
2224
|
+
}
|
|
2225
|
+
catch (error) {
|
|
2226
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2227
|
+
return [];
|
|
2228
|
+
}
|
|
2229
|
+
throw error;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
static async appendSubdialogResponse(dialogId, response, status = 'running') {
|
|
2233
|
+
const existing = await this.loadSubdialogResponsesQueue(dialogId, status);
|
|
2234
|
+
existing.push(response);
|
|
2235
|
+
await this.saveSubdialogResponses(dialogId, existing, status);
|
|
2236
|
+
}
|
|
2237
|
+
static async takeSubdialogResponses(dialogId, status = 'running') {
|
|
2238
|
+
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
2239
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2240
|
+
const filePath = path.join(dialogPath, 'subdialog-responses.json');
|
|
2241
|
+
const inflightPath = path.join(dialogPath, 'subdialog-responses.processing.json');
|
|
2242
|
+
// If a previous processing file exists, merge it back so it will be re-processed.
|
|
2243
|
+
try {
|
|
2244
|
+
await fs.promises.access(inflightPath);
|
|
2245
|
+
await this.rollbackTakenSubdialogResponses(dialogId, status);
|
|
2246
|
+
}
|
|
2247
|
+
catch {
|
|
2248
|
+
// no-op
|
|
2249
|
+
}
|
|
2250
|
+
try {
|
|
2251
|
+
await fs.promises.rename(filePath, inflightPath);
|
|
2252
|
+
}
|
|
2253
|
+
catch (error) {
|
|
2254
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2255
|
+
return [];
|
|
2256
|
+
}
|
|
2257
|
+
throw error;
|
|
2258
|
+
}
|
|
2259
|
+
try {
|
|
2260
|
+
const raw = await fs.promises.readFile(inflightPath, 'utf-8');
|
|
2261
|
+
const parsed = JSON.parse(raw);
|
|
2262
|
+
if (!Array.isArray(parsed)) {
|
|
2263
|
+
return [];
|
|
2264
|
+
}
|
|
2265
|
+
return parsed.filter(isSubdialogResponseRecord);
|
|
2266
|
+
}
|
|
2267
|
+
catch (error) {
|
|
2268
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2269
|
+
return [];
|
|
2270
|
+
}
|
|
2271
|
+
throw error;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
static async commitTakenSubdialogResponses(dialogId, status = 'running') {
|
|
2275
|
+
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
2276
|
+
const inflightPath = path.join(dialogPath, 'subdialog-responses.processing.json');
|
|
2277
|
+
await fs.promises.rm(inflightPath, { force: true });
|
|
2278
|
+
}
|
|
2279
|
+
static async rollbackTakenSubdialogResponses(dialogId, status = 'running') {
|
|
2280
|
+
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
2281
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2282
|
+
const filePath = path.join(dialogPath, 'subdialog-responses.json');
|
|
2283
|
+
const inflightPath = path.join(dialogPath, 'subdialog-responses.processing.json');
|
|
2284
|
+
let inflight = [];
|
|
2285
|
+
try {
|
|
2286
|
+
const raw = await fs.promises.readFile(inflightPath, 'utf-8');
|
|
2287
|
+
const parsed = JSON.parse(raw);
|
|
2288
|
+
inflight = Array.isArray(parsed) ? parsed : [];
|
|
2289
|
+
}
|
|
2290
|
+
catch (error) {
|
|
2291
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
2296
|
+
let primary = [];
|
|
2297
|
+
try {
|
|
2298
|
+
const raw = await fs.promises.readFile(filePath, 'utf-8');
|
|
2299
|
+
const parsed = JSON.parse(raw);
|
|
2300
|
+
primary = Array.isArray(parsed) ? parsed : [];
|
|
2301
|
+
}
|
|
2302
|
+
catch (error) {
|
|
2303
|
+
if (getErrorCode(error) !== 'ENOENT') {
|
|
2304
|
+
throw error;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
const merged = [...inflight, ...primary].filter(isSubdialogResponseRecord);
|
|
2308
|
+
const byId = new Map();
|
|
2309
|
+
for (const r of merged) {
|
|
2310
|
+
byId.set(r.responseId, r);
|
|
2311
|
+
}
|
|
2312
|
+
const result = Array.from(byId.values());
|
|
2313
|
+
const tempFile = filePath + '.tmp';
|
|
2314
|
+
await fs.promises.writeFile(tempFile, JSON.stringify(result, null, 2), 'utf-8');
|
|
2315
|
+
await fs.promises.rename(tempFile, filePath);
|
|
2316
|
+
await fs.promises.rm(inflightPath, { force: true });
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Save root dialog metadata (write-once pattern)
|
|
2320
|
+
*/
|
|
2321
|
+
static async saveRootDialogMetadata(dialogId, metadata, status = 'running') {
|
|
2322
|
+
try {
|
|
2323
|
+
const dialogPath = this.getRootDialogPath(dialogId, status);
|
|
2324
|
+
// Ensure dialog directory exists first
|
|
2325
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2326
|
+
// Atomic write operation
|
|
2327
|
+
const metadataFilePath = path.join(dialogPath, 'dialog.yaml');
|
|
2328
|
+
const tempFile = metadataFilePath + '.tmp';
|
|
2329
|
+
const yamlContent = yaml.stringify(metadata);
|
|
2330
|
+
await fs.promises.writeFile(tempFile, yamlContent, 'utf-8');
|
|
2331
|
+
await fs.promises.rename(tempFile, metadataFilePath);
|
|
2332
|
+
}
|
|
2333
|
+
catch (error) {
|
|
2334
|
+
log_1.log.error(`Failed to save dialog YAML for dialog ${dialogId}:`, error);
|
|
2335
|
+
throw error;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Save dialog metadata (universal - works with any DialogID)
|
|
2340
|
+
*/
|
|
2341
|
+
static async saveDialogMetadata(dialogId, metadata, status = 'running') {
|
|
2342
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
2343
|
+
if (!isRootDialogMetadataFile(metadata)) {
|
|
2344
|
+
throw new Error(`Expected root dialog metadata for ${dialogId.selfId}`);
|
|
2345
|
+
}
|
|
2346
|
+
return this.saveRootDialogMetadata(dialogId, metadata, status);
|
|
2347
|
+
}
|
|
2348
|
+
// For subdialogs, delegate to saveSubdialogMetadata
|
|
2349
|
+
if (!isSubdialogMetadataFile(metadata)) {
|
|
2350
|
+
throw new Error(`Expected subdialog metadata for ${dialogId.selfId}`);
|
|
2351
|
+
}
|
|
2352
|
+
return this.saveSubdialogMetadata(dialogId, metadata, status);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Save dialog metadata (legacy - use saveRootDialogMetadata instead)
|
|
2356
|
+
* @deprecated
|
|
2357
|
+
*/
|
|
2358
|
+
static async _saveDialogMetadata(dialogId, metadata, status = 'running') {
|
|
2359
|
+
return this.saveRootDialogMetadata(dialogId, metadata, status);
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Save subdialog metadata under the supdialog's .subdialogs directory
|
|
2363
|
+
*/
|
|
2364
|
+
static async saveSubdialogMetadata(dialogId, metadata, status = 'running') {
|
|
2365
|
+
try {
|
|
2366
|
+
const subPath = this.getSubdialogPath(dialogId, status);
|
|
2367
|
+
const metadataFilePath = path.join(subPath, 'dialog.yaml');
|
|
2368
|
+
await fs.promises.mkdir(subPath, { recursive: true });
|
|
2369
|
+
const tempFile = metadataFilePath + '.tmp';
|
|
2370
|
+
const yamlContent = yaml.stringify(metadata);
|
|
2371
|
+
await fs.promises.writeFile(tempFile, yamlContent, 'utf-8');
|
|
2372
|
+
await fs.promises.rename(tempFile, metadataFilePath);
|
|
2373
|
+
}
|
|
2374
|
+
catch (error) {
|
|
2375
|
+
log_1.log.error(`Failed to save subdialog YAML for ${dialogId.selfId} under root dialog ${dialogId.rootId}:`, error);
|
|
2376
|
+
throw error;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Update assignmentFromSup for an existing subdialog.
|
|
2381
|
+
* Persists both subdialog metadata locations for consistency.
|
|
2382
|
+
*/
|
|
2383
|
+
static async updateSubdialogAssignment(dialogId, assignment, status = 'running') {
|
|
2384
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
2385
|
+
throw new Error('updateSubdialogAssignment expects a subdialog id');
|
|
2386
|
+
}
|
|
2387
|
+
const metadata = await this.loadDialogMetadata(dialogId, status);
|
|
2388
|
+
if (!metadata || !isSubdialogMetadataFile(metadata)) {
|
|
2389
|
+
throw new Error(`Missing dialog metadata for subdialog ${dialogId.selfId}`);
|
|
2390
|
+
}
|
|
2391
|
+
const next = {
|
|
2392
|
+
...metadata,
|
|
2393
|
+
assignmentFromSup: assignment,
|
|
2394
|
+
};
|
|
2395
|
+
await this.saveSubdialogMetadata(dialogId, next, status);
|
|
2396
|
+
await this.saveDialogMetadata(dialogId, next, status);
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Load root dialog metadata
|
|
2400
|
+
*/
|
|
2401
|
+
static async loadRootDialogMetadata(dialogId, status = 'running') {
|
|
2402
|
+
try {
|
|
2403
|
+
const dialogPath = this.getRootDialogPath(dialogId, status);
|
|
2404
|
+
const metadataFilePath = path.join(dialogPath, 'dialog.yaml');
|
|
2405
|
+
try {
|
|
2406
|
+
const content = await fs.promises.readFile(metadataFilePath, 'utf-8');
|
|
2407
|
+
const parsed = yaml.parse(content);
|
|
2408
|
+
if (!isDialogMetadataFile(parsed)) {
|
|
2409
|
+
throw new Error(`Invalid dialog metadata in ${metadataFilePath}`);
|
|
2410
|
+
}
|
|
2411
|
+
// Validate that the ID in the file matches the expected dialogId
|
|
2412
|
+
if (parsed.id !== dialogId.selfId) {
|
|
2413
|
+
log_1.log.warn(`Dialog ID mismatch in ${metadataFilePath}: expected ${dialogId.selfId}, got ${parsed.id}`);
|
|
2414
|
+
return null;
|
|
2415
|
+
}
|
|
2416
|
+
return parsed;
|
|
2417
|
+
}
|
|
2418
|
+
catch (error) {
|
|
2419
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
throw error;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
log_1.log.error(`Failed to load dialog YAML for dialog ${dialogId.selfId}:`, error);
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Load dialog metadata (universal - works with any DialogID)
|
|
2432
|
+
*/
|
|
2433
|
+
static async loadDialogMetadata(dialogId, status = 'running') {
|
|
2434
|
+
// For root dialogs, use the selfId
|
|
2435
|
+
// For subdialogs, this is more complex - we need to find the root metadata
|
|
2436
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
2437
|
+
return this.loadRootDialogMetadata(dialogId, status);
|
|
2438
|
+
}
|
|
2439
|
+
// For subdialogs, we need to load from the subdialog location
|
|
2440
|
+
const subdialogPath = this.getSubdialogPath(dialogId, status);
|
|
2441
|
+
const metadataFilePath = path.join(subdialogPath, 'dialog.yaml');
|
|
2442
|
+
try {
|
|
2443
|
+
const content = await fs.promises.readFile(metadataFilePath, 'utf-8');
|
|
2444
|
+
const parsed = yaml.parse(content);
|
|
2445
|
+
if (!isDialogMetadataFile(parsed)) {
|
|
2446
|
+
throw new Error(`Invalid dialog metadata in ${metadataFilePath}`);
|
|
2447
|
+
}
|
|
2448
|
+
return parsed;
|
|
2449
|
+
}
|
|
2450
|
+
catch (error) {
|
|
2451
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
throw error;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Save latest.yaml with current round and lastModified info
|
|
2459
|
+
*/
|
|
2460
|
+
static async writeDialogLatestToDisk(dialogId, latest, status = 'running') {
|
|
2461
|
+
try {
|
|
2462
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
2463
|
+
const latestFilePath = path.join(dialogPath, 'latest.yaml');
|
|
2464
|
+
// Ensure directory exists before writing (handles race conditions and new dialogs)
|
|
2465
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
2466
|
+
// NOTE: Use a unique temp file name to avoid collisions when multiple updates
|
|
2467
|
+
// happen concurrently for the same dialog (e.g., parallel tool responses).
|
|
2468
|
+
const tempFile = path.join(dialogPath, `.${path.basename(latestFilePath)}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
2469
|
+
const yamlContent = yaml.stringify(latest);
|
|
2470
|
+
await fs.promises.writeFile(tempFile, yamlContent, 'utf-8');
|
|
2471
|
+
// Rename with retry logic for filesystem sync issues
|
|
2472
|
+
await this.renameWithRetry(tempFile, latestFilePath, yamlContent);
|
|
2473
|
+
// todo: publish RoundEvent here or where more suitable?
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
log_1.log.error(`Failed to save latest.yaml for dialog ${dialogId.selfId}:`, error);
|
|
2477
|
+
throw error;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Rename with retry logic to handle filesystem sync issues
|
|
2482
|
+
*/
|
|
2483
|
+
static async renameWithRetry(source, destination, yamlContent, maxRetries = 5) {
|
|
2484
|
+
let lastError;
|
|
2485
|
+
const destinationDir = path.dirname(destination);
|
|
2486
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
2487
|
+
try {
|
|
2488
|
+
// Ensure directory exists (handles race conditions)
|
|
2489
|
+
await fs.promises.mkdir(destinationDir, { recursive: true });
|
|
2490
|
+
// Check if source file exists, re-create if missing
|
|
2491
|
+
try {
|
|
2492
|
+
await fs.promises.access(source);
|
|
2493
|
+
}
|
|
2494
|
+
catch {
|
|
2495
|
+
// Source file missing - re-create it
|
|
2496
|
+
await fs.promises.writeFile(source, yamlContent, 'utf-8');
|
|
2497
|
+
}
|
|
2498
|
+
await fs.promises.rename(source, destination);
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
catch (error) {
|
|
2502
|
+
lastError = error;
|
|
2503
|
+
if (getErrorCode(error) !== 'ENOENT' || attempt === maxRetries) {
|
|
2504
|
+
throw error;
|
|
2505
|
+
}
|
|
2506
|
+
// Exponential backoff for ENOENT (race condition or sync issue)
|
|
2507
|
+
await new Promise((resolve) => setTimeout(resolve, 20 * attempt));
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
throw lastError;
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Load latest.yaml for current round and lastModified info
|
|
2514
|
+
*/
|
|
2515
|
+
static async loadDialogLatest(dialogId, status = 'running') {
|
|
2516
|
+
try {
|
|
2517
|
+
const key = this.getLatestWriteBackKey(dialogId, status);
|
|
2518
|
+
const staged = this.latestWriteBack.get(key);
|
|
2519
|
+
if (staged) {
|
|
2520
|
+
return staged.latest;
|
|
2521
|
+
}
|
|
2522
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
2523
|
+
const latestFilePath = path.join(dialogPath, 'latest.yaml');
|
|
2524
|
+
const content = await fs.promises.readFile(latestFilePath, 'utf-8');
|
|
2525
|
+
const parsed = yaml.parse(content);
|
|
2526
|
+
if (!isDialogLatestFile(parsed)) {
|
|
2527
|
+
throw new Error(`Invalid latest.yaml in ${latestFilePath}`);
|
|
2528
|
+
}
|
|
2529
|
+
return parsed;
|
|
2530
|
+
}
|
|
2531
|
+
catch (error) {
|
|
2532
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2533
|
+
return null;
|
|
2534
|
+
}
|
|
2535
|
+
throw error;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Delta-only latest.yaml update API.
|
|
2540
|
+
*
|
|
2541
|
+
* Callers provide a mutation callback which is applied against the most recent
|
|
2542
|
+
* staged state (or disk fallback). This avoids read-modify-write races in user code
|
|
2543
|
+
* and allows lock-free-ish coalescing to disk via the write-back buffer.
|
|
2544
|
+
*/
|
|
2545
|
+
static async mutateDialogLatest(dialogId, mutator, status = 'running') {
|
|
2546
|
+
const key = this.getLatestWriteBackKey(dialogId, status);
|
|
2547
|
+
const mutex = this.getLatestWriteBackMutex(key);
|
|
2548
|
+
const release = await mutex.acquire();
|
|
2549
|
+
try {
|
|
2550
|
+
const staged = this.latestWriteBack.get(key);
|
|
2551
|
+
const existing = (staged
|
|
2552
|
+
? staged.latest
|
|
2553
|
+
: await this.loadDialogLatestFromDisk(dialogId, status)) || {
|
|
2554
|
+
currentRound: 1,
|
|
2555
|
+
lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2556
|
+
status: 'active',
|
|
2557
|
+
};
|
|
2558
|
+
const mutation = mutator(existing);
|
|
2559
|
+
let updated;
|
|
2560
|
+
if (mutation.kind === 'noop') {
|
|
2561
|
+
return existing;
|
|
2562
|
+
}
|
|
2563
|
+
else if (mutation.kind === 'replace') {
|
|
2564
|
+
updated = {
|
|
2565
|
+
...mutation.next,
|
|
2566
|
+
lastModified: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
else if (mutation.kind === 'patch') {
|
|
2570
|
+
updated = {
|
|
2571
|
+
...existing,
|
|
2572
|
+
...mutation.patch,
|
|
2573
|
+
lastModified: mutation.patch.lastModified || (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
else {
|
|
2577
|
+
const _exhaustive = mutation;
|
|
2578
|
+
throw new Error(`Unhandled dialog latest mutation: ${String(_exhaustive)}`);
|
|
2579
|
+
}
|
|
2580
|
+
const pending = this.latestWriteBack.get(key);
|
|
2581
|
+
if (!pending) {
|
|
2582
|
+
const timer = setTimeout(() => {
|
|
2583
|
+
void this.flushLatestWriteBack(key);
|
|
2584
|
+
}, this.LATEST_WRITEBACK_WINDOW_MS);
|
|
2585
|
+
this.latestWriteBack.set(key, {
|
|
2586
|
+
kind: 'scheduled',
|
|
2587
|
+
dialogId,
|
|
2588
|
+
status,
|
|
2589
|
+
latest: updated,
|
|
2590
|
+
timer,
|
|
2591
|
+
});
|
|
2592
|
+
return updated;
|
|
2593
|
+
}
|
|
2594
|
+
pending.latest = updated;
|
|
2595
|
+
if (pending.kind === 'flushing') {
|
|
2596
|
+
pending.dirty = true;
|
|
2597
|
+
}
|
|
2598
|
+
// Keep the existing timer to ensure a bounded flush window.
|
|
2599
|
+
return updated;
|
|
2600
|
+
}
|
|
2601
|
+
finally {
|
|
2602
|
+
release();
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
static async loadDialogLatestFromDisk(dialogId, status) {
|
|
2606
|
+
try {
|
|
2607
|
+
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
2608
|
+
const latestFilePath = path.join(dialogPath, 'latest.yaml');
|
|
2609
|
+
const content = await fs.promises.readFile(latestFilePath, 'utf-8');
|
|
2610
|
+
const parsed = yaml.parse(content);
|
|
2611
|
+
if (!isDialogLatestFile(parsed)) {
|
|
2612
|
+
throw new Error(`Invalid latest.yaml in ${latestFilePath}`);
|
|
2613
|
+
}
|
|
2614
|
+
return parsed;
|
|
2615
|
+
}
|
|
2616
|
+
catch (error) {
|
|
2617
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2618
|
+
return null;
|
|
2619
|
+
}
|
|
2620
|
+
throw error;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
static async flushLatestWriteBack(key) {
|
|
2624
|
+
const mutex = this.getLatestWriteBackMutex(key);
|
|
2625
|
+
let captured;
|
|
2626
|
+
{
|
|
2627
|
+
const release = await mutex.acquire();
|
|
2628
|
+
try {
|
|
2629
|
+
const entry = this.latestWriteBack.get(key);
|
|
2630
|
+
if (!entry)
|
|
2631
|
+
return;
|
|
2632
|
+
if (entry.kind === 'flushing')
|
|
2633
|
+
return;
|
|
2634
|
+
if (entry.kind !== 'scheduled')
|
|
2635
|
+
return;
|
|
2636
|
+
clearTimeout(entry.timer);
|
|
2637
|
+
const latestToWrite = entry.latest;
|
|
2638
|
+
const inFlight = this.writeDialogLatestToDisk(entry.dialogId, latestToWrite, entry.status);
|
|
2639
|
+
captured = {
|
|
2640
|
+
dialogId: entry.dialogId,
|
|
2641
|
+
status: entry.status,
|
|
2642
|
+
latestToWrite,
|
|
2643
|
+
inFlight,
|
|
2644
|
+
};
|
|
2645
|
+
this.latestWriteBack.set(key, {
|
|
2646
|
+
kind: 'flushing',
|
|
2647
|
+
dialogId: entry.dialogId,
|
|
2648
|
+
status: entry.status,
|
|
2649
|
+
latest: entry.latest,
|
|
2650
|
+
dirty: false,
|
|
2651
|
+
inFlight,
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
finally {
|
|
2655
|
+
release();
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
if (!captured)
|
|
2659
|
+
return;
|
|
2660
|
+
try {
|
|
2661
|
+
await captured.inFlight;
|
|
2662
|
+
}
|
|
2663
|
+
catch (error) {
|
|
2664
|
+
const release = await mutex.acquire();
|
|
2665
|
+
try {
|
|
2666
|
+
const entry = this.latestWriteBack.get(key);
|
|
2667
|
+
if (!entry)
|
|
2668
|
+
return;
|
|
2669
|
+
if (entry.kind !== 'flushing')
|
|
2670
|
+
return;
|
|
2671
|
+
if (entry.inFlight !== captured.inFlight)
|
|
2672
|
+
return;
|
|
2673
|
+
const timer = setTimeout(() => {
|
|
2674
|
+
void this.flushLatestWriteBack(key);
|
|
2675
|
+
}, this.LATEST_WRITEBACK_WINDOW_MS);
|
|
2676
|
+
this.latestWriteBack.set(key, {
|
|
2677
|
+
kind: 'scheduled',
|
|
2678
|
+
dialogId: entry.dialogId,
|
|
2679
|
+
status: entry.status,
|
|
2680
|
+
latest: entry.latest,
|
|
2681
|
+
timer,
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
finally {
|
|
2685
|
+
release();
|
|
2686
|
+
}
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
const release = await mutex.acquire();
|
|
2690
|
+
try {
|
|
2691
|
+
const entry = this.latestWriteBack.get(key);
|
|
2692
|
+
if (!entry)
|
|
2693
|
+
return;
|
|
2694
|
+
if (entry.kind !== 'flushing')
|
|
2695
|
+
return;
|
|
2696
|
+
if (entry.inFlight !== captured.inFlight)
|
|
2697
|
+
return;
|
|
2698
|
+
if (!entry.dirty) {
|
|
2699
|
+
this.latestWriteBack.delete(key);
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const timer = setTimeout(() => {
|
|
2703
|
+
void this.flushLatestWriteBack(key);
|
|
2704
|
+
}, this.LATEST_WRITEBACK_WINDOW_MS);
|
|
2705
|
+
this.latestWriteBack.set(key, {
|
|
2706
|
+
kind: 'scheduled',
|
|
2707
|
+
dialogId: entry.dialogId,
|
|
2708
|
+
status: entry.status,
|
|
2709
|
+
latest: entry.latest,
|
|
2710
|
+
timer,
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
finally {
|
|
2714
|
+
release();
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
static async setNeedsDrive(dialogId, needsDrive, status = 'running') {
|
|
2718
|
+
await this.mutateDialogLatest(dialogId, () => ({ kind: 'patch', patch: { needsDrive } }), status);
|
|
2719
|
+
}
|
|
2720
|
+
static async getNeedsDrive(dialogId, status = 'running') {
|
|
2721
|
+
const latest = await this.loadDialogLatest(dialogId, status);
|
|
2722
|
+
return latest?.needsDrive === true;
|
|
2723
|
+
}
|
|
2724
|
+
// === FILE SYSTEM UTILITIES ===
|
|
2725
|
+
/**
|
|
2726
|
+
* Get round filename from round number
|
|
2727
|
+
*/
|
|
2728
|
+
static getRoundFilename(round) {
|
|
2729
|
+
return `round-${round.toString().padStart(3, '0')}.jsonl`;
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Extract round number from filename
|
|
2733
|
+
*/
|
|
2734
|
+
static getRoundFromFilename(filename) {
|
|
2735
|
+
const match = filename.match(/^round-(\d+)\.jsonl$/);
|
|
2736
|
+
if (!match) {
|
|
2737
|
+
throw new Error(`Invalid round filename: ${filename}`);
|
|
2738
|
+
}
|
|
2739
|
+
return parseInt(match[1], 10);
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* Get dialog status from file system path
|
|
2743
|
+
*/
|
|
2744
|
+
static getStatusFromPath(dialogPath) {
|
|
2745
|
+
const parentDir = path.basename(path.dirname(dialogPath));
|
|
2746
|
+
if (parentDir === this.RUN_DIR)
|
|
2747
|
+
return 'running';
|
|
2748
|
+
if (parentDir === this.DONE_DIR)
|
|
2749
|
+
return 'completed';
|
|
2750
|
+
if (parentDir === this.ARCHIVE_DIR)
|
|
2751
|
+
return 'archived';
|
|
2752
|
+
throw new Error(`Unknown dialog status from path: ${parentDir}`);
|
|
2753
|
+
}
|
|
2754
|
+
static async loadQuestions4Human(dialogId, round, status = 'running') {
|
|
2755
|
+
const questions = await this.loadQuestions4HumanState(dialogId, status);
|
|
2756
|
+
return {
|
|
2757
|
+
round,
|
|
2758
|
+
questions,
|
|
2759
|
+
createdAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2760
|
+
updatedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Count subdialogs under a root dialog (no single-layer listing exposed)
|
|
2765
|
+
*/
|
|
2766
|
+
static async countAllSubdialogsUnderRoot(rootDialogId, status = 'running') {
|
|
2767
|
+
try {
|
|
2768
|
+
const rootPath = this.getRootDialogPath(rootDialogId, status);
|
|
2769
|
+
const subdialogsPath = path.join(rootPath, this.SUBDIALOGS_DIR);
|
|
2770
|
+
try {
|
|
2771
|
+
const entries = await fs.promises.readdir(subdialogsPath, { withFileTypes: true });
|
|
2772
|
+
return entries.filter((entry) => entry.isDirectory()).length;
|
|
2773
|
+
}
|
|
2774
|
+
catch (error) {
|
|
2775
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
2776
|
+
return 0;
|
|
2777
|
+
}
|
|
2778
|
+
throw error;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
catch (error) {
|
|
2782
|
+
log_1.log.error(`Failed to count all subdialogs under root ${rootDialogId.selfId}:`, error);
|
|
2783
|
+
return 0;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
// === HIERARCHICAL DIALOG RESTORATION ===
|
|
2787
|
+
/**
|
|
2788
|
+
* Restore complete dialog tree from disk
|
|
2789
|
+
*/
|
|
2790
|
+
static async restoreDialogTree(rootDialogId, status = 'running') {
|
|
2791
|
+
try {
|
|
2792
|
+
// First restore the root dialog
|
|
2793
|
+
const rootState = await this.restoreDialog(rootDialogId, status);
|
|
2794
|
+
if (!rootState) {
|
|
2795
|
+
return null;
|
|
2796
|
+
}
|
|
2797
|
+
// Recursively restore subdialogs
|
|
2798
|
+
const rootPath = this.getRootDialogPath(rootDialogId, status);
|
|
2799
|
+
const subdialogsPath = path.join(rootPath, this.SUBDIALOGS_DIR);
|
|
2800
|
+
let subdialogIds = [];
|
|
2801
|
+
try {
|
|
2802
|
+
const entries = await fs.promises.readdir(subdialogsPath, { withFileTypes: true });
|
|
2803
|
+
subdialogIds = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2804
|
+
}
|
|
2805
|
+
catch (err) {
|
|
2806
|
+
if (getErrorCode(err) !== 'ENOENT') {
|
|
2807
|
+
throw err;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
for (const subdialogId of subdialogIds) {
|
|
2811
|
+
await this.restoreDialogTree(new dialog_1.DialogID(subdialogId, rootDialogId.rootId), status);
|
|
2812
|
+
}
|
|
2813
|
+
return rootState;
|
|
2814
|
+
}
|
|
2815
|
+
catch (error) {
|
|
2816
|
+
log_1.log.error(`Failed to restore dialog tree for ${rootDialogId.valueOf()}:`, error);
|
|
2817
|
+
return null;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Restore dialog from disk using JSONL events (optimized: only latest round loaded)
|
|
2822
|
+
* For historical rounds, use loadRoundEvents() on-demand for UI navigation
|
|
2823
|
+
*/
|
|
2824
|
+
static async restoreDialog(dialogId, status = 'running') {
|
|
2825
|
+
try {
|
|
2826
|
+
const metadata = await this.loadDialogMetadata(dialogId, status);
|
|
2827
|
+
if (!metadata) {
|
|
2828
|
+
log_1.log.debug(`No metadata found for dialog ${dialogId}`);
|
|
2829
|
+
return null;
|
|
2830
|
+
}
|
|
2831
|
+
const reminders = await this.loadReminderState(dialogId, status);
|
|
2832
|
+
// Only load latest round for dialog state restoration
|
|
2833
|
+
const currentRound = await this.getCurrentRoundNumber(dialogId, status);
|
|
2834
|
+
const latestEvents = await this.readRoundEvents(dialogId, currentRound, status);
|
|
2835
|
+
const reconstructedState = await this.rebuildFromEvents(latestEvents, metadata, reminders, currentRound);
|
|
2836
|
+
return reconstructedState;
|
|
2837
|
+
}
|
|
2838
|
+
catch (error) {
|
|
2839
|
+
log_1.log.error(`Failed to restore dialog ${dialogId}:`, error);
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Load specific round events for UI navigation (on-demand)
|
|
2845
|
+
*/
|
|
2846
|
+
static async loadRoundEvents(dialogId, round, status = 'running') {
|
|
2847
|
+
return await this.readRoundEvents(dialogId, round, status);
|
|
2848
|
+
}
|
|
2849
|
+
/**
|
|
2850
|
+
* Reconstruct dialog state from JSONL events (optimized: only latest round needed)
|
|
2851
|
+
*/
|
|
2852
|
+
static async rebuildFromEvents(events, metadata, reminders, currentRound) {
|
|
2853
|
+
// Events are already in chronological order from JSONL file (append-only pattern)
|
|
2854
|
+
const messages = [];
|
|
2855
|
+
let contextHealth;
|
|
2856
|
+
// Simple, straightforward mapping to reconstruct messages from persisted events
|
|
2857
|
+
for (const event of events) {
|
|
2858
|
+
switch (event.type) {
|
|
2859
|
+
case 'agent_thought_record': {
|
|
2860
|
+
// Convert agent thought to ChatMessage
|
|
2861
|
+
messages.push({
|
|
2862
|
+
type: 'thinking_msg',
|
|
2863
|
+
role: 'assistant',
|
|
2864
|
+
genseq: event.genseq,
|
|
2865
|
+
content: event.content,
|
|
2866
|
+
provider_data: event.provider_data,
|
|
2867
|
+
});
|
|
2868
|
+
break;
|
|
2869
|
+
}
|
|
2870
|
+
case 'agent_words_record': {
|
|
2871
|
+
// Convert agent words to ChatMessage
|
|
2872
|
+
messages.push({
|
|
2873
|
+
type: 'saying_msg',
|
|
2874
|
+
role: 'assistant',
|
|
2875
|
+
genseq: event.genseq,
|
|
2876
|
+
content: event.content,
|
|
2877
|
+
});
|
|
2878
|
+
break;
|
|
2879
|
+
}
|
|
2880
|
+
case 'human_text_record': {
|
|
2881
|
+
// Convert human text to prompting message
|
|
2882
|
+
messages.push({
|
|
2883
|
+
type: 'prompting_msg',
|
|
2884
|
+
role: 'user',
|
|
2885
|
+
genseq: event.genseq,
|
|
2886
|
+
msgId: event.msgId,
|
|
2887
|
+
content: event.content,
|
|
2888
|
+
grammar: event.grammar ?? 'tellask',
|
|
2889
|
+
});
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
case 'func_call_record': {
|
|
2893
|
+
// Convert function call to ChatMessage
|
|
2894
|
+
messages.push({
|
|
2895
|
+
type: 'func_call_msg',
|
|
2896
|
+
role: 'assistant',
|
|
2897
|
+
genseq: event.genseq,
|
|
2898
|
+
id: event.id,
|
|
2899
|
+
name: event.name,
|
|
2900
|
+
arguments: event.arguments ? JSON.stringify(event.arguments) : '{}',
|
|
2901
|
+
});
|
|
2902
|
+
break;
|
|
2903
|
+
}
|
|
2904
|
+
case 'func_result_record': {
|
|
2905
|
+
// Convert function result to ChatMessage
|
|
2906
|
+
messages.push({
|
|
2907
|
+
type: 'func_result_msg',
|
|
2908
|
+
role: 'tool',
|
|
2909
|
+
genseq: event.genseq,
|
|
2910
|
+
id: event.id,
|
|
2911
|
+
name: event.name,
|
|
2912
|
+
content: event.content,
|
|
2913
|
+
});
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
case 'tool_call_result_record': {
|
|
2917
|
+
// Convert tool call result to ChatMessage
|
|
2918
|
+
messages.push({
|
|
2919
|
+
type: 'call_result_msg',
|
|
2920
|
+
role: 'tool',
|
|
2921
|
+
responderId: event.responderId,
|
|
2922
|
+
headLine: event.headLine,
|
|
2923
|
+
status: event.status,
|
|
2924
|
+
content: event.result,
|
|
2925
|
+
});
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
case 'teammate_response_record': {
|
|
2929
|
+
// Convert teammate response to ChatMessage (teammate - separate bubble)
|
|
2930
|
+
// Note: Teammate responses are stored as separate records but use same message type
|
|
2931
|
+
const formattedResult = (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
2932
|
+
responderId: event.responderId,
|
|
2933
|
+
requesterId: event.originMemberId,
|
|
2934
|
+
originalCallHeadLine: event.headLine,
|
|
2935
|
+
responseBody: event.response,
|
|
2936
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2937
|
+
});
|
|
2938
|
+
messages.push({
|
|
2939
|
+
type: 'call_result_msg',
|
|
2940
|
+
role: 'tool',
|
|
2941
|
+
responderId: event.responderId,
|
|
2942
|
+
headLine: event.headLine,
|
|
2943
|
+
status: event.status,
|
|
2944
|
+
content: formattedResult,
|
|
2945
|
+
});
|
|
2946
|
+
break;
|
|
2947
|
+
}
|
|
2948
|
+
// gen_start_record and gen_finish_record are control events, not message content
|
|
2949
|
+
// They don't need to be converted to ChatMessage objects
|
|
2950
|
+
case 'gen_start_record':
|
|
2951
|
+
break;
|
|
2952
|
+
case 'gen_finish_record':
|
|
2953
|
+
if (event.contextHealth) {
|
|
2954
|
+
contextHealth = event.contextHealth;
|
|
2955
|
+
}
|
|
2956
|
+
break;
|
|
2957
|
+
case 'quest_for_sup_record':
|
|
2958
|
+
// These events are handled separately in dialog restoration
|
|
2959
|
+
// Skip them for message reconstruction
|
|
2960
|
+
break;
|
|
2961
|
+
default:
|
|
2962
|
+
log_1.log.warn(`Unknown event type in rebuildFromEvents`, undefined, { event });
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
return {
|
|
2967
|
+
metadata,
|
|
2968
|
+
currentRound,
|
|
2969
|
+
messages,
|
|
2970
|
+
reminders,
|
|
2971
|
+
contextHealth,
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
/**
|
|
2975
|
+
* Move dialog between status directories (run/done/archive)
|
|
2976
|
+
*/
|
|
2977
|
+
static async moveDialogStatus(dialogId, fromStatus, toStatus) {
|
|
2978
|
+
try {
|
|
2979
|
+
const fromPath = path.join(this.getDialogsRootDir(), fromStatus === 'running'
|
|
2980
|
+
? this.RUN_DIR
|
|
2981
|
+
: fromStatus === 'completed'
|
|
2982
|
+
? this.DONE_DIR
|
|
2983
|
+
: this.ARCHIVE_DIR, dialogId.selfId);
|
|
2984
|
+
const toPath = path.join(this.getDialogsRootDir(), toStatus === 'running'
|
|
2985
|
+
? this.RUN_DIR
|
|
2986
|
+
: toStatus === 'completed'
|
|
2987
|
+
? this.DONE_DIR
|
|
2988
|
+
: this.ARCHIVE_DIR, dialogId.selfId);
|
|
2989
|
+
// Ensure destination directory exists
|
|
2990
|
+
await fs.promises.mkdir(toPath, { recursive: true });
|
|
2991
|
+
// Move all files and directories
|
|
2992
|
+
const entries = await fs.promises.readdir(fromPath, { withFileTypes: true });
|
|
2993
|
+
for (const entry of entries) {
|
|
2994
|
+
const srcPath = path.join(fromPath, entry.name);
|
|
2995
|
+
const destPath = path.join(toPath, entry.name);
|
|
2996
|
+
await fs.promises.rename(srcPath, destPath);
|
|
2997
|
+
}
|
|
2998
|
+
// Remove the (now-empty) source directory so the dialog is not detected under both statuses.
|
|
2999
|
+
await fs.promises.rm(fromPath, { recursive: true, force: true });
|
|
3000
|
+
}
|
|
3001
|
+
catch (error) {
|
|
3002
|
+
log_1.log.error(`Failed to move dialog ${dialogId} from ${fromStatus} to ${toStatus}:`, error);
|
|
3003
|
+
throw error;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Find the current persistence status directory for a root dialog.
|
|
3008
|
+
* Root dialogs are stored under exactly one of: run/ | done/ | archive/
|
|
3009
|
+
*/
|
|
3010
|
+
static async findRootDialogStatus(rootDialogId) {
|
|
3011
|
+
if (rootDialogId.selfId !== rootDialogId.rootId) {
|
|
3012
|
+
throw new Error('Expected root dialog id (selfId must equal rootId)');
|
|
3013
|
+
}
|
|
3014
|
+
const candidates = [
|
|
3015
|
+
{ status: 'running', dirName: this.RUN_DIR },
|
|
3016
|
+
{ status: 'completed', dirName: this.DONE_DIR },
|
|
3017
|
+
{ status: 'archived', dirName: this.ARCHIVE_DIR },
|
|
3018
|
+
];
|
|
3019
|
+
for (const candidate of candidates) {
|
|
3020
|
+
const candidatePath = path.join(this.getDialogsRootDir(), candidate.dirName, rootDialogId.selfId);
|
|
3021
|
+
try {
|
|
3022
|
+
// `run/` can contain stray placeholder directories (e.g. created by status-agnostic metadata updates).
|
|
3023
|
+
// Treat a status as valid only if it contains the root dialog's dialog.yaml.
|
|
3024
|
+
const dialogYamlPath = path.join(candidatePath, 'dialog.yaml');
|
|
3025
|
+
const st = await fs.promises.stat(dialogYamlPath);
|
|
3026
|
+
if (st.isFile()) {
|
|
3027
|
+
return candidate.status;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
catch (error) {
|
|
3031
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
throw error;
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Delete a root dialog directory (including subdialogs) from disk.
|
|
3041
|
+
* Returns the status directory the dialog was deleted from, or null if not found.
|
|
3042
|
+
*/
|
|
3043
|
+
static async deleteRootDialog(rootDialogId) {
|
|
3044
|
+
const status = await this.findRootDialogStatus(rootDialogId);
|
|
3045
|
+
if (!status)
|
|
3046
|
+
return null;
|
|
3047
|
+
// Best-effort cleanup: remove the dialog from all status directories to avoid leaving behind
|
|
3048
|
+
// orphaned placeholder paths (e.g. `run/<id>/latest.yaml`) after a delete.
|
|
3049
|
+
const allStatuses = [
|
|
3050
|
+
'running',
|
|
3051
|
+
'completed',
|
|
3052
|
+
'archived',
|
|
3053
|
+
];
|
|
3054
|
+
for (const candidate of allStatuses) {
|
|
3055
|
+
const candidatePath = this.getRootDialogPath(rootDialogId, candidate);
|
|
3056
|
+
await fs.promises.rm(candidatePath, { recursive: true, force: true });
|
|
3057
|
+
}
|
|
3058
|
+
return status;
|
|
3059
|
+
}
|
|
3060
|
+
// === REGISTRY PERSISTENCE ===
|
|
3061
|
+
/**
|
|
3062
|
+
* Save subdialog registry (TYPE B entries).
|
|
3063
|
+
*/
|
|
3064
|
+
static async saveSubdialogRegistry(rootDialogId, entries, status = 'running') {
|
|
3065
|
+
try {
|
|
3066
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
3067
|
+
const registryFilePath = path.join(dialogPath, 'registry.yaml');
|
|
3068
|
+
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
3069
|
+
const serializableEntries = entries.map((entry) => ({
|
|
3070
|
+
key: entry.key,
|
|
3071
|
+
subdialogId: entry.subdialogId.selfId,
|
|
3072
|
+
agentId: entry.agentId,
|
|
3073
|
+
tellaskSession: entry.tellaskSession,
|
|
3074
|
+
}));
|
|
3075
|
+
const tempFile = registryFilePath + '.tmp';
|
|
3076
|
+
const yamlContent = yaml.stringify({ entries: serializableEntries });
|
|
3077
|
+
await fs.promises.writeFile(tempFile, yamlContent, 'utf-8');
|
|
3078
|
+
await fs.promises.rename(tempFile, registryFilePath);
|
|
3079
|
+
}
|
|
3080
|
+
catch (error) {
|
|
3081
|
+
log_1.log.error(`Failed to save subdialog registry for dialog ${rootDialogId}:`, error);
|
|
3082
|
+
throw error;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Load subdialog registry.
|
|
3087
|
+
*/
|
|
3088
|
+
static async loadSubdialogRegistry(rootDialogId, status = 'running') {
|
|
3089
|
+
try {
|
|
3090
|
+
const dialogPath = this.getDialogResponsesPath(rootDialogId, status);
|
|
3091
|
+
const registryFilePath = path.join(dialogPath, 'registry.yaml');
|
|
3092
|
+
const content = await fs.promises.readFile(registryFilePath, 'utf-8');
|
|
3093
|
+
const parsed = yaml.parse(content);
|
|
3094
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.entries)) {
|
|
3095
|
+
log_1.log.warn(`Invalid registry.yaml format for dialog ${rootDialogId}`);
|
|
3096
|
+
return [];
|
|
3097
|
+
}
|
|
3098
|
+
const entries = parsed.entries.map((entry) => {
|
|
3099
|
+
if (!isRecord(entry)) {
|
|
3100
|
+
throw new Error('Invalid registry entry');
|
|
3101
|
+
}
|
|
3102
|
+
return {
|
|
3103
|
+
key: entry.key,
|
|
3104
|
+
subdialogId: new dialog_1.DialogID(entry.subdialogId, rootDialogId.rootId),
|
|
3105
|
+
agentId: entry.agentId,
|
|
3106
|
+
tellaskSession: entry.tellaskSession,
|
|
3107
|
+
};
|
|
3108
|
+
});
|
|
3109
|
+
return entries;
|
|
3110
|
+
}
|
|
3111
|
+
catch (error) {
|
|
3112
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
3113
|
+
return [];
|
|
3114
|
+
}
|
|
3115
|
+
log_1.log.error(`Failed to load subdialog registry for dialog ${rootDialogId}:`, error);
|
|
3116
|
+
return [];
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
exports.DialogPersistence = DialogPersistence;
|
|
3121
|
+
DialogPersistence.DIALOGS_DIR = '.dialogs';
|
|
3122
|
+
DialogPersistence.RUN_DIR = 'run';
|
|
3123
|
+
DialogPersistence.DONE_DIR = 'done';
|
|
3124
|
+
DialogPersistence.ARCHIVE_DIR = 'archive';
|
|
3125
|
+
DialogPersistence.SUBDIALOGS_DIR = 'subdialogs';
|
|
3126
|
+
DialogPersistence.LATEST_WRITEBACK_WINDOW_MS = 300;
|
|
3127
|
+
DialogPersistence.latestWriteBackMutexes = new Map();
|
|
3128
|
+
DialogPersistence.latestWriteBack = new Map();
|