dominds 1.18.2 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-control.js +6 -6
- package/dist/apps/runtime.d.ts +2 -2
- package/dist/apps/runtime.js +28 -28
- package/dist/apps-host/client.d.ts +1 -1
- package/dist/apps-host/host.js +7 -7
- package/dist/apps-host/ipc-types.d.ts +2 -2
- package/dist/apps-host/ipc-types.js +10 -10
- package/dist/cli/read.d.ts +0 -1
- package/dist/cli/read.js +1 -6
- package/dist/dialog-display-state.d.ts +6 -6
- package/dist/dialog-display-state.js +46 -46
- package/dist/dialog-factory.d.ts +12 -12
- package/dist/dialog-factory.js +33 -30
- package/dist/dialog-fork.d.ts +2 -2
- package/dist/dialog-fork.js +140 -115
- package/dist/dialog-global-registry.d.ts +5 -5
- package/dist/dialog-global-registry.js +11 -11
- package/dist/dialog-instance-registry.d.ts +3 -3
- package/dist/dialog-instance-registry.js +52 -41
- package/dist/dialog.d.ts +100 -113
- package/dist/dialog.js +274 -229
- package/dist/docs/agent-priming.md +5 -5
- package/dist/docs/agent-priming.zh.md +5 -5
- package/dist/docs/app-constitution.md +1 -1
- package/dist/docs/app-constitution.zh.md +1 -1
- package/dist/docs/cli-usage.md +1 -1
- package/dist/docs/cli-usage.zh.md +1 -1
- package/dist/docs/design.md +14 -14
- package/dist/docs/design.zh.md +14 -14
- package/dist/docs/dialog-persistence.md +58 -58
- package/dist/docs/dialog-persistence.zh.md +61 -61
- package/dist/docs/dialog-system.md +363 -367
- package/dist/docs/dialog-system.zh.md +355 -357
- package/dist/docs/diligence-push.md +18 -18
- package/dist/docs/diligence-push.zh.md +17 -17
- package/dist/docs/dominds-agent-collaboration.zh.md +3 -3
- package/dist/docs/dominds-terminology.md +46 -47
- package/dist/docs/encapsulated-taskdoc.md +4 -4
- package/dist/docs/encapsulated-taskdoc.zh.md +3 -3
- package/dist/docs/fbr.md +30 -30
- package/dist/docs/fbr.zh.md +15 -15
- package/dist/docs/i18n.md +2 -2
- package/dist/docs/i18n.zh.md +2 -2
- package/dist/docs/mcp-support.md +5 -4
- package/dist/docs/mcp-support.zh.md +3 -2
- package/dist/docs/memory-system.md +4 -4
- package/dist/docs/memory-system.zh.md +1 -1
- package/dist/docs/mottos.md +1 -1
- package/dist/docs/mottos.zh.md +1 -1
- package/dist/docs/q4h.md +3 -3
- package/dist/docs/q4h.zh.md +1 -1
- package/dist/docs/roadmap.md +2 -2
- package/dist/docs/team_mgmt-toolset.md +11 -3
- package/dist/docs/team_mgmt-toolset.zh.md +9 -2
- package/dist/docs/tellask-collab.md +18 -18
- package/dist/docs/tellask-collab.zh.md +8 -8
- package/dist/docs/tellask-revive-context-refactor.zh.md +591 -0
- package/dist/evt-registry.d.ts +1 -2
- package/dist/evt-registry.js +2 -7
- package/dist/llm/gen/mock.js +9 -0
- package/dist/llm/kernel-driver/context.d.ts +1 -2
- package/dist/llm/kernel-driver/context.js +12 -26
- package/dist/llm/kernel-driver/drive.js +99 -75
- package/dist/llm/kernel-driver/engine.d.ts +2 -2
- package/dist/llm/kernel-driver/engine.js +10 -10
- package/dist/llm/kernel-driver/fbr.js +6 -6
- package/dist/llm/kernel-driver/flow.d.ts +1 -1
- package/dist/llm/kernel-driver/flow.js +129 -93
- package/dist/llm/kernel-driver/guardrails.js +4 -4
- package/dist/llm/kernel-driver/index.d.ts +1 -1
- package/dist/llm/kernel-driver/index.js +2 -2
- package/dist/llm/kernel-driver/loop.js +30 -30
- package/dist/llm/kernel-driver/reply-guidance.js +47 -52
- package/dist/llm/kernel-driver/restore.d.ts +3 -3
- package/dist/llm/kernel-driver/restore.js +23 -28
- package/dist/llm/kernel-driver/runtime.d.ts +1 -1
- package/dist/llm/kernel-driver/runtime.js +1 -1
- package/dist/llm/kernel-driver/sideDialog-txn.d.ts +8 -0
- package/dist/llm/kernel-driver/{subdialog-txn.js → sideDialog-txn.js} +13 -13
- package/dist/llm/kernel-driver/{subdialog.d.ts → sideDialog.d.ts} +13 -13
- package/dist/llm/kernel-driver/{subdialog.js → sideDialog.js} +203 -170
- package/dist/llm/kernel-driver/tellask-special.d.ts +7 -3
- package/dist/llm/kernel-driver/tellask-special.js +474 -497
- package/dist/llm/kernel-driver/types.d.ts +25 -9
- package/dist/mcp/config.d.ts +1 -0
- package/dist/mcp/config.js +7 -2
- package/dist/mcp/supervisor.d.ts +2 -0
- package/dist/mcp/supervisor.js +2 -1
- package/dist/minds/builtin/pangu/persona.en.md +4 -4
- package/dist/minds/load.js +6 -6
- package/dist/minds/system-prompt-parts.d.ts +1 -1
- package/dist/minds/system-prompt-parts.js +12 -12
- package/dist/minds/system-prompt.d.ts +1 -1
- package/dist/minds/system-prompt.js +56 -56
- package/dist/persistence-errors.d.ts +1 -1
- package/dist/persistence.d.ts +126 -121
- package/dist/persistence.js +1190 -786
- package/dist/priming.d.ts +3 -3
- package/dist/priming.js +62 -61
- package/dist/recovery/reply-special.js +5 -5
- package/dist/runtime/driver-messages.d.ts +3 -2
- package/dist/runtime/driver-messages.js +68 -57
- package/dist/runtime/inter-dialog-format.d.ts +12 -10
- package/dist/runtime/inter-dialog-format.js +80 -35
- package/dist/runtime/interjection-pause-stop.js +1 -1
- package/dist/runtime/reply-prompt-copy.d.ts +7 -3
- package/dist/runtime/reply-prompt-copy.js +39 -14
- package/dist/server/api-routes.js +87 -83
- package/dist/server/static-server.js +1 -1
- package/dist/server/websocket-handler.js +163 -153
- package/dist/tool-availability.js +1 -1
- package/dist/tools/app-reminders.js +17 -4
- package/dist/tools/ctrl.js +5 -5
- package/dist/tools/os.js +16 -16
- package/dist/tools/pending-tellask-reminder.js +20 -14
- package/dist/tools/prompts/control/en/index.md +1 -1
- package/dist/tools/prompts/control/en/principles.md +8 -8
- package/dist/tools/prompts/control/en/scenarios.md +7 -7
- package/dist/tools/prompts/control/en/tools.md +7 -7
- package/dist/tools/prompts/control/zh/principles.md +4 -4
- package/dist/tools/prompts/control/zh/scenarios.md +5 -5
- package/dist/tools/prompts/control/zh/tools.md +3 -3
- package/dist/tools/prompts/team_memory/en/scenarios.md +1 -1
- package/dist/tools/prompts/team_memory/zh/scenarios.md +1 -1
- package/dist/tools/team_mgmt-manual.js +2 -2
- package/dist/tools/team_mgmt-mcp-manual.js +10 -0
- package/dist/tools/team_mgmt.js +4 -4
- package/dist/utils/taskdoc.js +12 -12
- package/package.json +3 -3
- package/webapp/dist/assets/{_basePickBy-BPJaiZdW.js → _basePickBy-B7M9Q0Fa.js} +3 -3
- package/webapp/dist/assets/_basePickBy-B7M9Q0Fa.js.map +1 -0
- package/webapp/dist/assets/{_baseUniq-BEetT15i.js → _baseUniq-DAeYoL6j.js} +2 -2
- package/webapp/dist/assets/_baseUniq-DAeYoL6j.js.map +1 -0
- package/webapp/dist/assets/{arc-Dm7Zf36f.js → arc-Bh4nDbNR.js} +2 -2
- package/webapp/dist/assets/arc-Bh4nDbNR.js.map +1 -0
- package/webapp/dist/assets/{architectureDiagram-VXUJARFQ-BpTPtkuo.js → architectureDiagram-2XIMDMQ5-CxqmdsIm.js} +26 -8
- package/webapp/dist/assets/architectureDiagram-2XIMDMQ5-CxqmdsIm.js.map +1 -0
- package/webapp/dist/assets/{blockDiagram-VD42YOAC-C8fLN0iu.js → blockDiagram-WCTKOSBZ-CxIWLtpt.js} +187 -170
- package/webapp/dist/assets/blockDiagram-WCTKOSBZ-CxIWLtpt.js.map +1 -0
- package/webapp/dist/assets/{c4Diagram-YG6GDRKO-BpPr62CH.js → c4Diagram-IC4MRINW-1qErOIgG.js} +4 -4
- package/webapp/dist/assets/c4Diagram-IC4MRINW-1qErOIgG.js.map +1 -0
- package/webapp/dist/assets/{channel-EMYoPjW3.js → channel-DkgZHNUe.js} +2 -2
- package/webapp/dist/assets/channel-DkgZHNUe.js.map +1 -0
- package/webapp/dist/assets/{chunk-4BX2VUAB-CefNtjWG.js → chunk-4BX2VUAB-BmdMbU9v.js} +2 -2
- package/webapp/dist/assets/chunk-4BX2VUAB-BmdMbU9v.js.map +1 -0
- package/webapp/dist/assets/{chunk-55IACEB6-C_X7T43V.js → chunk-55IACEB6-D6LDTDBy.js} +2 -2
- package/webapp/dist/assets/chunk-55IACEB6-D6LDTDBy.js.map +1 -0
- package/webapp/dist/assets/{chunk-FMBD7UC4-ORmtkrtS.js → chunk-FMBD7UC4-C-BdCe4C.js} +2 -2
- package/webapp/dist/assets/chunk-FMBD7UC4-C-BdCe4C.js.map +1 -0
- package/webapp/dist/assets/{chunk-TZMSLE5B-Gao4qrq7.js → chunk-JSJVCQXG-WA_BLIm9.js} +14 -6
- package/webapp/dist/assets/chunk-JSJVCQXG-WA_BLIm9.js.map +1 -0
- package/webapp/dist/assets/{chunk-QN33PNHL-LTAOVhWu.js → chunk-KX2RTZJC-CA7sDJO5.js} +2 -2
- package/webapp/dist/assets/chunk-KX2RTZJC-CA7sDJO5.js.map +1 -0
- package/webapp/dist/assets/{chunk-DI55MBZ5-CbvrsI_w.js → chunk-NQ4KR5QH-wlvxalE3.js} +9 -7
- package/webapp/dist/assets/chunk-NQ4KR5QH-wlvxalE3.js.map +1 -0
- package/webapp/dist/assets/{chunk-QZHKN3VN-ZoUM_4u5.js → chunk-QZHKN3VN-Bo1VMcph.js} +2 -2
- package/webapp/dist/assets/chunk-QZHKN3VN-Bo1VMcph.js.map +1 -0
- package/webapp/dist/assets/{chunk-B4BG7PRW-BRe3_2oA.js → chunk-WL4C6EOR-B-Pk44be.js} +171 -121
- package/webapp/dist/assets/chunk-WL4C6EOR-B-Pk44be.js.map +1 -0
- package/webapp/dist/assets/{classDiagram-2ON5EDUG-uha1vIGN.js → classDiagram-VBA2DB6C-BqKuyb49.js} +7 -6
- package/webapp/dist/assets/classDiagram-VBA2DB6C-BqKuyb49.js.map +1 -0
- package/webapp/dist/assets/{classDiagram-v2-WZHVMYZB-uha1vIGN.js → classDiagram-v2-RAHNMMFH-BqKuyb49.js} +7 -6
- package/webapp/dist/assets/classDiagram-v2-RAHNMMFH-BqKuyb49.js.map +1 -0
- package/webapp/dist/assets/{clone-_9Ayb1Gp.js → clone-BX5z8WVZ.js} +2 -2
- package/webapp/dist/assets/clone-BX5z8WVZ.js.map +1 -0
- package/webapp/dist/assets/{cose-bilkent-S5V4N54A-C8wDw3NY.js → cose-bilkent-S5V4N54A-B-s11SgN.js} +2 -2
- package/webapp/dist/assets/cose-bilkent-S5V4N54A-B-s11SgN.js.map +1 -0
- package/webapp/dist/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -1
- package/webapp/dist/assets/{dagre-6UL2VRFP-BUSeNot0.js → dagre-KLK3FWXG-DmQFV2qK.js} +7 -7
- package/webapp/dist/assets/dagre-KLK3FWXG-DmQFV2qK.js.map +1 -0
- package/webapp/dist/assets/defaultLocale-B2RvLBDe.js.map +1 -1
- package/webapp/dist/assets/{diagram-PSM6KHXK-CMZAksVC.js → diagram-E7M64L7V-QRaBfST8.js} +10 -10
- package/webapp/dist/assets/diagram-E7M64L7V-QRaBfST8.js.map +1 -0
- package/webapp/dist/assets/{diagram-QEK2KX5R-BQKoRtwy.js → diagram-IFDJBPK2-lrWn1Obo.js} +9 -8
- package/webapp/dist/assets/diagram-IFDJBPK2-lrWn1Obo.js.map +1 -0
- package/webapp/dist/assets/{diagram-S2PKOQOG-DjMG97kd.js → diagram-P4PSJMXO-sTU7Hh-Y.js} +8 -8
- package/webapp/dist/assets/diagram-P4PSJMXO-sTU7Hh-Y.js.map +1 -0
- package/webapp/dist/assets/{erDiagram-Q2GNP2WA-BujwA137.js → erDiagram-INFDFZHY-Cx6jc9Wq.js} +96 -75
- package/webapp/dist/assets/erDiagram-INFDFZHY-Cx6jc9Wq.js.map +1 -0
- package/webapp/dist/assets/{flowDiagram-NV44I4VS-DgwPjg4y.js → flowDiagram-PKNHOUZH-DfGI49Dz.js} +98 -81
- package/webapp/dist/assets/flowDiagram-PKNHOUZH-DfGI49Dz.js.map +1 -0
- package/webapp/dist/assets/{ganttDiagram-JELNMOA3-Db2ykf3E.js → ganttDiagram-A5KZAMGK-nrcHWWaM.js} +28 -3
- package/webapp/dist/assets/ganttDiagram-A5KZAMGK-nrcHWWaM.js.map +1 -0
- package/webapp/dist/assets/{gitGraphDiagram-V2S2FVAM-D_gSifkv.js → gitGraphDiagram-K3NZZRJ6-D8ivAqd6.js} +38 -46
- package/webapp/dist/assets/gitGraphDiagram-K3NZZRJ6-D8ivAqd6.js.map +1 -0
- package/webapp/dist/assets/graph-R5G-y8tB.js +782 -0
- package/webapp/dist/assets/graph-R5G-y8tB.js.map +1 -0
- package/webapp/dist/assets/{index-DLajsIDJ.js → index--fy89xGh.js} +2214 -2023
- package/webapp/dist/assets/index--fy89xGh.js.map +1 -0
- package/webapp/dist/assets/{index-xvYYeHuy.css → index-DZFkLLVz.css} +18 -10
- package/webapp/dist/assets/{infoDiagram-HS3SLOUP-BDba5pKs.js → infoDiagram-LFFYTUFH-PIoZHr7s.js} +7 -7
- package/webapp/dist/assets/infoDiagram-LFFYTUFH-PIoZHr7s.js.map +1 -0
- package/webapp/dist/assets/init-ZxktEp_H.js.map +1 -1
- package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-oCM-LYk1.js +966 -0
- package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-oCM-LYk1.js.map +1 -0
- package/webapp/dist/assets/{journeyDiagram-XKPGCS4Q-CmJAbmlm.js → journeyDiagram-4ABVD52K-C2qidjQ5.js} +5 -5
- package/webapp/dist/assets/journeyDiagram-4ABVD52K-C2qidjQ5.js.map +1 -0
- package/webapp/dist/assets/{kanban-definition-3W4ZIXB7-DxQeBTDk.js → kanban-definition-K7BYSVSG-Du0TC8WS.js} +5 -3
- package/webapp/dist/assets/kanban-definition-K7BYSVSG-Du0TC8WS.js.map +1 -0
- package/webapp/dist/assets/{layout-DteV_yE8.js → layout-VmEo1OEB.js} +5 -5
- package/webapp/dist/assets/layout-VmEo1OEB.js.map +1 -0
- package/webapp/dist/assets/{linear-zItbPrND.js → linear-B662YHAc.js} +2 -2
- package/webapp/dist/assets/linear-B662YHAc.js.map +1 -0
- package/webapp/dist/assets/{mindmap-definition-VGOIOE7T-BJXI7UqO.js → mindmap-definition-YRQLILUH-D7arZj95.js} +7 -5
- package/webapp/dist/assets/mindmap-definition-YRQLILUH-D7arZj95.js.map +1 -0
- package/webapp/dist/assets/ordinal-CxptdPJm.js.map +1 -1
- package/webapp/dist/assets/{pieDiagram-ADFJNKIX-BpM-aH2p.js → pieDiagram-SKSYHLDU-DvjPP4PA.js} +8 -8
- package/webapp/dist/assets/pieDiagram-SKSYHLDU-DvjPP4PA.js.map +1 -0
- package/webapp/dist/assets/{quadrantDiagram-AYHSOK5B-NXdIpA15.js → quadrantDiagram-337W2JSQ-B_JUGMj_.js} +3 -3
- package/webapp/dist/assets/quadrantDiagram-337W2JSQ-B_JUGMj_.js.map +1 -0
- package/webapp/dist/assets/{requirementDiagram-UZGBJVZJ-D1AICAA0.js → requirementDiagram-Z7DCOOCP-DF0mpvE3.js} +16 -6
- package/webapp/dist/assets/requirementDiagram-Z7DCOOCP-DF0mpvE3.js.map +1 -0
- package/webapp/dist/assets/{sankeyDiagram-TZEHDZUN-WiReDPfo.js → sankeyDiagram-WA2Y5GQK-CoXlxv00.js} +2 -2
- package/webapp/dist/assets/sankeyDiagram-WA2Y5GQK-CoXlxv00.js.map +1 -0
- package/webapp/dist/assets/{sequenceDiagram-WL72ISMW-Cw76oP8t.js → sequenceDiagram-2WXFIKYE-DYqT5Pg7.js} +601 -201
- package/webapp/dist/assets/sequenceDiagram-2WXFIKYE-DYqT5Pg7.js.map +1 -0
- package/webapp/dist/assets/{stateDiagram-FKZM4ZOC-QjCeRczs.js → stateDiagram-RAJIS63D-D9b1mN8-.js} +9 -9
- package/webapp/dist/assets/stateDiagram-RAJIS63D-D9b1mN8-.js.map +1 -0
- package/webapp/dist/assets/{stateDiagram-v2-4FDKWEC3-IClqxQ4s.js → stateDiagram-v2-FVOUBMTO-DNzgudL_.js} +5 -5
- package/webapp/dist/assets/stateDiagram-v2-FVOUBMTO-DNzgudL_.js.map +1 -0
- package/webapp/dist/assets/{timeline-definition-IT6M3QCI-BfyfTY7m.js → timeline-definition-YZTLITO2-CkyKUY7A.js} +3 -3
- package/webapp/dist/assets/timeline-definition-YZTLITO2-CkyKUY7A.js.map +1 -0
- package/webapp/dist/assets/{treemap-GDKQZRPO-C5MiL6--.js → treemap-KZPCXAKY-CZd09kF-.js} +37 -24
- package/webapp/dist/assets/treemap-KZPCXAKY-CZd09kF-.js.map +1 -0
- package/webapp/dist/assets/vennDiagram-LZ73GAT5-BxVF5Olo.js +2487 -0
- package/webapp/dist/assets/vennDiagram-LZ73GAT5-BxVF5Olo.js.map +1 -0
- package/webapp/dist/assets/{xychartDiagram-PRI3JC2R-ybaJrSry.js → xychartDiagram-JWTSCODW-BRwRloPc.js} +4 -4
- package/webapp/dist/assets/xychartDiagram-JWTSCODW-BRwRloPc.js.map +1 -0
- package/webapp/dist/index.html +2 -2
- package/dist/docs/issues/global-dialog-event-broadcaster-missing.md +0 -128
- package/dist/llm/kernel-driver/subdialog-txn.d.ts +0 -8
- package/webapp/dist/assets/_basePickBy-BPJaiZdW.js.map +0 -1
- package/webapp/dist/assets/_baseUniq-BEetT15i.js.map +0 -1
- package/webapp/dist/assets/arc-Dm7Zf36f.js.map +0 -1
- package/webapp/dist/assets/architectureDiagram-VXUJARFQ-BpTPtkuo.js.map +0 -1
- package/webapp/dist/assets/blockDiagram-VD42YOAC-C8fLN0iu.js.map +0 -1
- package/webapp/dist/assets/c4Diagram-YG6GDRKO-BpPr62CH.js.map +0 -1
- package/webapp/dist/assets/channel-EMYoPjW3.js.map +0 -1
- package/webapp/dist/assets/chunk-4BX2VUAB-CefNtjWG.js.map +0 -1
- package/webapp/dist/assets/chunk-55IACEB6-C_X7T43V.js.map +0 -1
- package/webapp/dist/assets/chunk-B4BG7PRW-BRe3_2oA.js.map +0 -1
- package/webapp/dist/assets/chunk-DI55MBZ5-CbvrsI_w.js.map +0 -1
- package/webapp/dist/assets/chunk-FMBD7UC4-ORmtkrtS.js.map +0 -1
- package/webapp/dist/assets/chunk-QN33PNHL-LTAOVhWu.js.map +0 -1
- package/webapp/dist/assets/chunk-QZHKN3VN-ZoUM_4u5.js.map +0 -1
- package/webapp/dist/assets/chunk-TZMSLE5B-Gao4qrq7.js.map +0 -1
- package/webapp/dist/assets/classDiagram-2ON5EDUG-uha1vIGN.js.map +0 -1
- package/webapp/dist/assets/classDiagram-v2-WZHVMYZB-uha1vIGN.js.map +0 -1
- package/webapp/dist/assets/clone-_9Ayb1Gp.js.map +0 -1
- package/webapp/dist/assets/cose-bilkent-S5V4N54A-C8wDw3NY.js.map +0 -1
- package/webapp/dist/assets/dagre-6UL2VRFP-BUSeNot0.js.map +0 -1
- package/webapp/dist/assets/diagram-PSM6KHXK-CMZAksVC.js.map +0 -1
- package/webapp/dist/assets/diagram-QEK2KX5R-BQKoRtwy.js.map +0 -1
- package/webapp/dist/assets/diagram-S2PKOQOG-DjMG97kd.js.map +0 -1
- package/webapp/dist/assets/erDiagram-Q2GNP2WA-BujwA137.js.map +0 -1
- package/webapp/dist/assets/flowDiagram-NV44I4VS-DgwPjg4y.js.map +0 -1
- package/webapp/dist/assets/ganttDiagram-JELNMOA3-Db2ykf3E.js.map +0 -1
- package/webapp/dist/assets/gitGraphDiagram-V2S2FVAM-D_gSifkv.js.map +0 -1
- package/webapp/dist/assets/graph-BHjCU5xP.js +0 -425
- package/webapp/dist/assets/graph-BHjCU5xP.js.map +0 -1
- package/webapp/dist/assets/index-DLajsIDJ.js.map +0 -1
- package/webapp/dist/assets/infoDiagram-HS3SLOUP-BDba5pKs.js.map +0 -1
- package/webapp/dist/assets/journeyDiagram-XKPGCS4Q-CmJAbmlm.js.map +0 -1
- package/webapp/dist/assets/kanban-definition-3W4ZIXB7-DxQeBTDk.js.map +0 -1
- package/webapp/dist/assets/layout-DteV_yE8.js.map +0 -1
- package/webapp/dist/assets/linear-zItbPrND.js.map +0 -1
- package/webapp/dist/assets/mindmap-definition-VGOIOE7T-BJXI7UqO.js.map +0 -1
- package/webapp/dist/assets/pieDiagram-ADFJNKIX-BpM-aH2p.js.map +0 -1
- package/webapp/dist/assets/quadrantDiagram-AYHSOK5B-NXdIpA15.js.map +0 -1
- package/webapp/dist/assets/requirementDiagram-UZGBJVZJ-D1AICAA0.js.map +0 -1
- package/webapp/dist/assets/sankeyDiagram-TZEHDZUN-WiReDPfo.js.map +0 -1
- package/webapp/dist/assets/sequenceDiagram-WL72ISMW-Cw76oP8t.js.map +0 -1
- package/webapp/dist/assets/stateDiagram-FKZM4ZOC-QjCeRczs.js.map +0 -1
- package/webapp/dist/assets/stateDiagram-v2-4FDKWEC3-IClqxQ4s.js.map +0 -1
- package/webapp/dist/assets/timeline-definition-IT6M3QCI-BfyfTY7m.js.map +0 -1
- package/webapp/dist/assets/treemap-GDKQZRPO-C5MiL6--.js.map +0 -1
- package/webapp/dist/assets/xychartDiagram-PRI3JC2R-ybaJrSry.js.map +0 -1
package/dist/persistence.js
CHANGED
|
@@ -89,7 +89,7 @@ function summarizeLatestProjectionState(latest) {
|
|
|
89
89
|
status: latest.status,
|
|
90
90
|
messageCount: latest.messageCount ?? null,
|
|
91
91
|
functionCallCount: latest.functionCallCount ?? null,
|
|
92
|
-
|
|
92
|
+
sideDialogCount: latest.sideDialogCount ?? null,
|
|
93
93
|
generating: latest.generating ?? false,
|
|
94
94
|
needsDrive: latest.needsDrive ?? false,
|
|
95
95
|
disableDiligencePush: latest.disableDiligencePush ?? false,
|
|
@@ -101,8 +101,8 @@ function summarizeLatestProjectionState(latest) {
|
|
|
101
101
|
pendingCourseStartPromptGrammar: latest.pendingCourseStartPrompt?.grammar ?? null,
|
|
102
102
|
pendingCourseStartPromptUserLanguageCode: latest.pendingCourseStartPrompt?.userLanguageCode ?? null,
|
|
103
103
|
pendingCourseStartPromptContentLength: latest.pendingCourseStartPrompt?.content.length ?? null,
|
|
104
|
-
pendingCourseStartPromptReplyTargetCallId: latest.pendingCourseStartPrompt?.
|
|
105
|
-
pendingCourseStartPromptReplyTargetOwnerDialogId: latest.pendingCourseStartPrompt?.
|
|
104
|
+
pendingCourseStartPromptReplyTargetCallId: latest.pendingCourseStartPrompt?.sideDialogReplyTarget?.callId ?? null,
|
|
105
|
+
pendingCourseStartPromptReplyTargetOwnerDialogId: latest.pendingCourseStartPrompt?.sideDialogReplyTarget?.ownerDialogId ?? null,
|
|
106
106
|
pendingCourseStartPromptExpectedReplyCallName: latest.pendingCourseStartPrompt?.tellaskReplyDirective?.expectedReplyCallName ?? null,
|
|
107
107
|
pendingCourseStartPromptTargetCallId: latest.pendingCourseStartPrompt?.tellaskReplyDirective?.targetCallId ?? null,
|
|
108
108
|
};
|
|
@@ -119,7 +119,7 @@ function summarizeLatestMutationPatch(patch) {
|
|
|
119
119
|
status: patch.status ?? null,
|
|
120
120
|
messageCount: patch.messageCount ?? null,
|
|
121
121
|
functionCallCount: patch.functionCallCount ?? null,
|
|
122
|
-
|
|
122
|
+
sideDialogCount: patch.sideDialogCount ?? null,
|
|
123
123
|
generating: patch.generating ?? null,
|
|
124
124
|
needsDrive: patch.needsDrive ?? null,
|
|
125
125
|
disableDiligencePush: patch.disableDiligencePush ?? null,
|
|
@@ -131,8 +131,8 @@ function summarizeLatestMutationPatch(patch) {
|
|
|
131
131
|
pendingCourseStartPromptGrammar: patch.pendingCourseStartPrompt?.grammar ?? null,
|
|
132
132
|
pendingCourseStartPromptUserLanguageCode: patch.pendingCourseStartPrompt?.userLanguageCode ?? null,
|
|
133
133
|
pendingCourseStartPromptContentLength: patch.pendingCourseStartPrompt?.content.length ?? null,
|
|
134
|
-
pendingCourseStartPromptReplyTargetOwnerDialogId: patch.pendingCourseStartPrompt?.
|
|
135
|
-
pendingCourseStartPromptReplyTargetCallId: patch.pendingCourseStartPrompt?.
|
|
134
|
+
pendingCourseStartPromptReplyTargetOwnerDialogId: patch.pendingCourseStartPrompt?.sideDialogReplyTarget?.ownerDialogId ?? null,
|
|
135
|
+
pendingCourseStartPromptReplyTargetCallId: patch.pendingCourseStartPrompt?.sideDialogReplyTarget?.callId ?? null,
|
|
136
136
|
pendingCourseStartPromptExpectedReplyCallName: patch.pendingCourseStartPrompt?.tellaskReplyDirective?.expectedReplyCallName ?? null,
|
|
137
137
|
pendingCourseStartPromptTargetCallId: patch.pendingCourseStartPrompt?.tellaskReplyDirective?.targetCallId ?? null,
|
|
138
138
|
};
|
|
@@ -220,7 +220,7 @@ function normalizeGeneratingDisplayStateMismatch(dialogId, status, previous, lat
|
|
|
220
220
|
executionMarker: hasInterruptedExecutionMarker ? undefined : latest.executionMarker,
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
|
-
const
|
|
223
|
+
const quarantiningMainDialogs = new Set();
|
|
224
224
|
const PERSISTABLE_DIALOG_STATUSES = ['running', 'completed', 'archived'];
|
|
225
225
|
const RUN_STATUS_DIR = 'run';
|
|
226
226
|
const DONE_STATUS_DIR = 'done';
|
|
@@ -272,7 +272,11 @@ function isSuppressedTellaskPlaceholderFuncResult(args) {
|
|
|
272
272
|
return true;
|
|
273
273
|
}
|
|
274
274
|
if (raw.startsWith('支线对话仍在进行中,已持续 ') ||
|
|
275
|
-
raw.startsWith('
|
|
275
|
+
raw.startsWith('Side Dialog is still running (elapsed ')) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
if ((raw.startsWith('[Dominds 诉请状态]') || raw.startsWith('[Dominds tellask status]')) &&
|
|
279
|
+
(raw.includes('当前仍在等待') || raw.includes('is still waiting'))) {
|
|
276
280
|
return true;
|
|
277
281
|
}
|
|
278
282
|
return false;
|
|
@@ -619,11 +623,11 @@ function buildTellaskResultRecord(result) {
|
|
|
619
623
|
status: result.status,
|
|
620
624
|
content: result.content,
|
|
621
625
|
contentItems: result.contentItems,
|
|
622
|
-
...(typeof result.
|
|
623
|
-
? {
|
|
626
|
+
...(typeof result.callSiteCourse === 'number'
|
|
627
|
+
? { callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse) }
|
|
624
628
|
: {}),
|
|
625
|
-
...(typeof result.
|
|
626
|
-
? {
|
|
629
|
+
...(typeof result.callSiteGenseq === 'number'
|
|
630
|
+
? { callSiteGenseq: (0, storage_1.toCallSiteGenseqNo)(result.callSiteGenseq) }
|
|
627
631
|
: {}),
|
|
628
632
|
responder: {
|
|
629
633
|
responderId,
|
|
@@ -673,7 +677,7 @@ function buildTellaskCarryoverRecord(result, genseq) {
|
|
|
673
677
|
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
674
678
|
type: 'tellask_carryover_record',
|
|
675
679
|
genseq,
|
|
676
|
-
|
|
680
|
+
callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse),
|
|
677
681
|
carryoverCourse: (0, storage_1.toDialogCourseNumber)(result.carryoverCourse),
|
|
678
682
|
responderId: result.responderId,
|
|
679
683
|
callName: result.callName,
|
|
@@ -716,11 +720,11 @@ function buildTellaskResultEvent(result, course) {
|
|
|
716
720
|
return {
|
|
717
721
|
type: 'tellask_result_evt',
|
|
718
722
|
course,
|
|
719
|
-
...(typeof result.
|
|
720
|
-
? {
|
|
723
|
+
...(typeof result.callSiteCourse === 'number'
|
|
724
|
+
? { callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse) }
|
|
721
725
|
: {}),
|
|
722
|
-
|
|
723
|
-
? (0, storage_1.
|
|
726
|
+
callSiteGenseq: typeof result.callSiteGenseq === 'number'
|
|
727
|
+
? (0, storage_1.toCallSiteGenseqNo)(result.callSiteGenseq)
|
|
724
728
|
: undefined,
|
|
725
729
|
callId: result.callId,
|
|
726
730
|
callName: result.callName,
|
|
@@ -747,11 +751,11 @@ function buildTellaskResultEvent(result, course) {
|
|
|
747
751
|
return {
|
|
748
752
|
type: 'tellask_result_evt',
|
|
749
753
|
course,
|
|
750
|
-
...(typeof result.
|
|
751
|
-
? {
|
|
754
|
+
...(typeof result.callSiteCourse === 'number'
|
|
755
|
+
? { callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse) }
|
|
752
756
|
: {}),
|
|
753
|
-
|
|
754
|
-
? (0, storage_1.
|
|
757
|
+
callSiteGenseq: typeof result.callSiteGenseq === 'number'
|
|
758
|
+
? (0, storage_1.toCallSiteGenseqNo)(result.callSiteGenseq)
|
|
755
759
|
: undefined,
|
|
756
760
|
callId: result.callId,
|
|
757
761
|
callName: result.callName,
|
|
@@ -776,11 +780,11 @@ function buildTellaskResultEvent(result, course) {
|
|
|
776
780
|
return {
|
|
777
781
|
type: 'tellask_result_evt',
|
|
778
782
|
course,
|
|
779
|
-
...(typeof result.
|
|
780
|
-
? {
|
|
783
|
+
...(typeof result.callSiteCourse === 'number'
|
|
784
|
+
? { callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse) }
|
|
781
785
|
: {}),
|
|
782
|
-
|
|
783
|
-
? (0, storage_1.
|
|
786
|
+
callSiteGenseq: typeof result.callSiteGenseq === 'number'
|
|
787
|
+
? (0, storage_1.toCallSiteGenseqNo)(result.callSiteGenseq)
|
|
784
788
|
: undefined,
|
|
785
789
|
callId: result.callId,
|
|
786
790
|
callName: result.callName,
|
|
@@ -811,7 +815,7 @@ function buildTellaskCarryoverEvent(result, course) {
|
|
|
811
815
|
type: 'tellask_carryover_evt',
|
|
812
816
|
course,
|
|
813
817
|
genseq: result.genseq,
|
|
814
|
-
|
|
818
|
+
callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse),
|
|
815
819
|
carryoverCourse: (0, storage_1.toDialogCourseNumber)(result.carryoverCourse),
|
|
816
820
|
responderId: result.responderId,
|
|
817
821
|
callName: result.callName,
|
|
@@ -838,7 +842,7 @@ function buildTellaskCarryoverEvent(result, course) {
|
|
|
838
842
|
type: 'tellask_carryover_evt',
|
|
839
843
|
course,
|
|
840
844
|
genseq: result.genseq,
|
|
841
|
-
|
|
845
|
+
callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse),
|
|
842
846
|
carryoverCourse: (0, storage_1.toDialogCourseNumber)(result.carryoverCourse),
|
|
843
847
|
responderId: result.responderId,
|
|
844
848
|
callName: result.callName,
|
|
@@ -863,7 +867,7 @@ function buildTellaskCarryoverEvent(result, course) {
|
|
|
863
867
|
type: 'tellask_carryover_evt',
|
|
864
868
|
course,
|
|
865
869
|
genseq: result.genseq,
|
|
866
|
-
|
|
870
|
+
callSiteCourse: (0, storage_1.toCallSiteCourseNo)(result.callSiteCourse),
|
|
867
871
|
carryoverCourse: (0, storage_1.toDialogCourseNumber)(result.carryoverCourse),
|
|
868
872
|
responderId: result.responderId,
|
|
869
873
|
callName: result.callName,
|
|
@@ -890,10 +894,10 @@ function isReplyTellaskCallRecordName(name) {
|
|
|
890
894
|
return (name === 'replyTellask' || name === 'replyTellaskSessionless' || name === 'replyTellaskBack');
|
|
891
895
|
}
|
|
892
896
|
function resolveRootGenerationAnchor(dialog) {
|
|
893
|
-
const
|
|
897
|
+
const mainDialog = dialog instanceof dialog_1.SideDialog ? dialog.mainDialog : dialog;
|
|
894
898
|
return (0, storage_1.toRootGenerationAnchor)({
|
|
895
|
-
rootCourse:
|
|
896
|
-
rootGenseq:
|
|
899
|
+
rootCourse: mainDialog.currentCourse,
|
|
900
|
+
rootGenseq: mainDialog.activeGenSeqOrUndefined ?? 0,
|
|
897
901
|
});
|
|
898
902
|
}
|
|
899
903
|
function resolveReconciledRecordWriteTarget(dialog) {
|
|
@@ -923,7 +927,7 @@ function attachRootGenerationRef(dialog, record) {
|
|
|
923
927
|
rootGenseq: anchor.rootGenseq,
|
|
924
928
|
};
|
|
925
929
|
}
|
|
926
|
-
function
|
|
930
|
+
function isMainDialogMetadataFile(value) {
|
|
927
931
|
if (!isRecord(value))
|
|
928
932
|
return false;
|
|
929
933
|
if (typeof value.id !== 'string')
|
|
@@ -934,11 +938,11 @@ function isRootDialogMetadataFile(value) {
|
|
|
934
938
|
return false;
|
|
935
939
|
if (typeof value.createdAt !== 'string')
|
|
936
940
|
return false;
|
|
937
|
-
if (value.
|
|
941
|
+
if (value.askerDialogId !== undefined)
|
|
938
942
|
return false;
|
|
939
943
|
if (value.sessionSlug !== undefined)
|
|
940
944
|
return false;
|
|
941
|
-
if (value.
|
|
945
|
+
if (value.assignmentFromAsker !== undefined)
|
|
942
946
|
return false;
|
|
943
947
|
if (value.priming !== undefined) {
|
|
944
948
|
if (!isRecord(value.priming))
|
|
@@ -952,66 +956,49 @@ function isRootDialogMetadataFile(value) {
|
|
|
952
956
|
}
|
|
953
957
|
return true;
|
|
954
958
|
}
|
|
955
|
-
function
|
|
959
|
+
function isSideDialogAssignmentFromAsker(value) {
|
|
956
960
|
if (!isRecord(value))
|
|
957
961
|
return false;
|
|
958
|
-
if (typeof value.
|
|
959
|
-
return false;
|
|
960
|
-
if (typeof value.agentId !== 'string')
|
|
961
|
-
return false;
|
|
962
|
-
if (typeof value.taskDocPath !== 'string')
|
|
963
|
-
return false;
|
|
964
|
-
if (typeof value.createdAt !== 'string')
|
|
965
|
-
return false;
|
|
966
|
-
if (typeof value.supdialogId !== 'string')
|
|
967
|
-
return false;
|
|
968
|
-
if (value.priming !== undefined)
|
|
969
|
-
return false;
|
|
970
|
-
if (value.sessionSlug !== undefined && typeof value.sessionSlug !== 'string')
|
|
971
|
-
return false;
|
|
972
|
-
const assignment = value.assignmentFromSup;
|
|
973
|
-
if (!isRecord(assignment))
|
|
974
|
-
return false;
|
|
975
|
-
if (typeof assignment.tellaskContent !== 'string')
|
|
962
|
+
if (typeof value.tellaskContent !== 'string')
|
|
976
963
|
return false;
|
|
977
|
-
if (typeof
|
|
964
|
+
if (typeof value.originMemberId !== 'string')
|
|
978
965
|
return false;
|
|
979
|
-
if (typeof
|
|
966
|
+
if (typeof value.askerDialogId !== 'string')
|
|
980
967
|
return false;
|
|
981
|
-
if (typeof
|
|
968
|
+
if (typeof value.callId !== 'string')
|
|
982
969
|
return false;
|
|
983
|
-
if (
|
|
984
|
-
if (!Array.isArray(
|
|
970
|
+
if (value.collectiveTargets !== undefined) {
|
|
971
|
+
if (!Array.isArray(value.collectiveTargets))
|
|
985
972
|
return false;
|
|
986
|
-
if (!
|
|
973
|
+
if (!value.collectiveTargets.every((item) => typeof item === 'string'))
|
|
987
974
|
return false;
|
|
988
975
|
}
|
|
989
|
-
if (
|
|
990
|
-
if (typeof
|
|
991
|
-
!Number.isInteger(
|
|
976
|
+
if (value.effectiveFbrEffort !== undefined) {
|
|
977
|
+
if (typeof value.effectiveFbrEffort !== 'number' ||
|
|
978
|
+
!Number.isInteger(value.effectiveFbrEffort)) {
|
|
992
979
|
return false;
|
|
993
980
|
}
|
|
994
|
-
if (
|
|
981
|
+
if (value.effectiveFbrEffort < 1 || value.effectiveFbrEffort > 100) {
|
|
995
982
|
return false;
|
|
996
983
|
}
|
|
997
984
|
}
|
|
998
|
-
switch (
|
|
985
|
+
switch (value.callName) {
|
|
999
986
|
case 'tellask':
|
|
1000
987
|
case 'tellaskSessionless': {
|
|
1001
|
-
if (!Array.isArray(
|
|
988
|
+
if (!Array.isArray(value.mentionList))
|
|
1002
989
|
return false;
|
|
1003
|
-
if (
|
|
990
|
+
if (value.mentionList.length < 1)
|
|
1004
991
|
return false;
|
|
1005
|
-
if (!
|
|
992
|
+
if (!value.mentionList.every((item) => typeof item === 'string'))
|
|
1006
993
|
return false;
|
|
1007
|
-
if (
|
|
994
|
+
if (value.effectiveFbrEffort !== undefined)
|
|
1008
995
|
return false;
|
|
1009
996
|
break;
|
|
1010
997
|
}
|
|
1011
998
|
case 'freshBootsReasoning': {
|
|
1012
|
-
if (
|
|
999
|
+
if (value.mentionList !== undefined)
|
|
1013
1000
|
return false;
|
|
1014
|
-
if (
|
|
1001
|
+
if (value.effectiveFbrEffort === undefined)
|
|
1015
1002
|
return false;
|
|
1016
1003
|
break;
|
|
1017
1004
|
}
|
|
@@ -1020,8 +1007,26 @@ function isSubdialogMetadataFile(value) {
|
|
|
1020
1007
|
}
|
|
1021
1008
|
return true;
|
|
1022
1009
|
}
|
|
1023
|
-
function
|
|
1024
|
-
|
|
1010
|
+
function isSideDialogMetadataFile(value) {
|
|
1011
|
+
if (!isRecord(value))
|
|
1012
|
+
return false;
|
|
1013
|
+
if (typeof value.id !== 'string')
|
|
1014
|
+
return false;
|
|
1015
|
+
if (typeof value.agentId !== 'string')
|
|
1016
|
+
return false;
|
|
1017
|
+
if (typeof value.taskDocPath !== 'string')
|
|
1018
|
+
return false;
|
|
1019
|
+
if (typeof value.createdAt !== 'string')
|
|
1020
|
+
return false;
|
|
1021
|
+
if (value.askerDialogId !== undefined)
|
|
1022
|
+
return false;
|
|
1023
|
+
if (value.assignmentFromAsker !== undefined)
|
|
1024
|
+
return false;
|
|
1025
|
+
if (value.priming !== undefined)
|
|
1026
|
+
return false;
|
|
1027
|
+
if (value.sessionSlug !== undefined && typeof value.sessionSlug !== 'string')
|
|
1028
|
+
return false;
|
|
1029
|
+
return true;
|
|
1025
1030
|
}
|
|
1026
1031
|
function parseTellaskReplyDirective(value) {
|
|
1027
1032
|
if (!isRecord(value))
|
|
@@ -1037,24 +1042,116 @@ function parseTellaskReplyDirective(value) {
|
|
|
1037
1042
|
if (typeof targetCallId !== 'string' || typeof tellaskContent !== 'string') {
|
|
1038
1043
|
return null;
|
|
1039
1044
|
}
|
|
1045
|
+
const targetDialogId = value.targetDialogId;
|
|
1046
|
+
if (typeof targetDialogId !== 'string')
|
|
1047
|
+
return null;
|
|
1040
1048
|
if (expectedReplyCallName === 'replyTellaskBack') {
|
|
1041
|
-
const targetDialogId = value.targetDialogId;
|
|
1042
|
-
if (typeof targetDialogId !== 'string')
|
|
1043
|
-
return null;
|
|
1044
1049
|
return {
|
|
1045
1050
|
expectedReplyCallName,
|
|
1046
|
-
targetCallId,
|
|
1047
1051
|
targetDialogId,
|
|
1052
|
+
targetCallId,
|
|
1048
1053
|
tellaskContent,
|
|
1049
1054
|
};
|
|
1050
1055
|
}
|
|
1051
1056
|
return {
|
|
1052
1057
|
expectedReplyCallName,
|
|
1058
|
+
targetDialogId,
|
|
1053
1059
|
targetCallId,
|
|
1054
1060
|
tellaskContent,
|
|
1055
1061
|
};
|
|
1056
1062
|
}
|
|
1057
|
-
function
|
|
1063
|
+
function isAskerDialogStackFrame(value) {
|
|
1064
|
+
if (!isRecord(value))
|
|
1065
|
+
return false;
|
|
1066
|
+
if (value.kind !== 'asker_dialog_stack_frame')
|
|
1067
|
+
return false;
|
|
1068
|
+
if (typeof value.askerDialogId !== 'string')
|
|
1069
|
+
return false;
|
|
1070
|
+
if (value.assignmentFromAsker !== undefined &&
|
|
1071
|
+
!isSideDialogAssignmentFromAsker(value.assignmentFromAsker)) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
if (value.assignmentFromAsker !== undefined &&
|
|
1075
|
+
value.assignmentFromAsker.askerDialogId !== value.askerDialogId) {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
if (value.tellaskReplyObligation !== undefined) {
|
|
1079
|
+
const directive = parseTellaskReplyDirective(value.tellaskReplyObligation);
|
|
1080
|
+
if (directive === null)
|
|
1081
|
+
return false;
|
|
1082
|
+
if (value.assignmentFromAsker !== undefined) {
|
|
1083
|
+
const expectedReplyCallName = value.assignmentFromAsker.callName === 'tellask'
|
|
1084
|
+
? 'replyTellask'
|
|
1085
|
+
: 'replyTellaskSessionless';
|
|
1086
|
+
if (directive.expectedReplyCallName !== expectedReplyCallName)
|
|
1087
|
+
return false;
|
|
1088
|
+
if (directive.targetDialogId !== value.askerDialogId)
|
|
1089
|
+
return false;
|
|
1090
|
+
if (directive.targetCallId !== value.assignmentFromAsker.callId)
|
|
1091
|
+
return false;
|
|
1092
|
+
if (directive.tellaskContent !== value.assignmentFromAsker.tellaskContent)
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
else if (directive.targetDialogId !== value.askerDialogId) {
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1101
|
+
function isDialogAskerStackState(value) {
|
|
1102
|
+
if (!isRecord(value))
|
|
1103
|
+
return false;
|
|
1104
|
+
if (!Array.isArray(value.askerStack))
|
|
1105
|
+
return false;
|
|
1106
|
+
return value.askerStack.every(isAskerDialogStackFrame);
|
|
1107
|
+
}
|
|
1108
|
+
function getDialogAskerStackTop(state) {
|
|
1109
|
+
const top = state.askerStack[state.askerStack.length - 1];
|
|
1110
|
+
if (!top) {
|
|
1111
|
+
throw new Error('asker stack invariant violation: empty stack');
|
|
1112
|
+
}
|
|
1113
|
+
return top;
|
|
1114
|
+
}
|
|
1115
|
+
function getDialogAskerStackCurrentAssignment(state) {
|
|
1116
|
+
for (let index = state.askerStack.length - 1; index >= 0; index -= 1) {
|
|
1117
|
+
const frame = state.askerStack[index];
|
|
1118
|
+
if (frame?.assignmentFromAsker !== undefined) {
|
|
1119
|
+
return frame.assignmentFromAsker;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
throw new Error('asker stack invariant violation: missing assignment frame');
|
|
1123
|
+
}
|
|
1124
|
+
function buildAssignmentTellaskReplyObligation(args) {
|
|
1125
|
+
switch (args.assignment.callName) {
|
|
1126
|
+
case 'tellask':
|
|
1127
|
+
return {
|
|
1128
|
+
expectedReplyCallName: 'replyTellask',
|
|
1129
|
+
targetDialogId: args.targetDialogId,
|
|
1130
|
+
targetCallId: args.assignment.callId,
|
|
1131
|
+
tellaskContent: args.assignment.tellaskContent,
|
|
1132
|
+
};
|
|
1133
|
+
case 'tellaskSessionless':
|
|
1134
|
+
case 'freshBootsReasoning':
|
|
1135
|
+
return {
|
|
1136
|
+
expectedReplyCallName: 'replyTellaskSessionless',
|
|
1137
|
+
targetDialogId: args.targetDialogId,
|
|
1138
|
+
targetCallId: args.assignment.callId,
|
|
1139
|
+
tellaskContent: args.assignment.tellaskContent,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function buildAssignmentAskerStackFrame(args) {
|
|
1144
|
+
return {
|
|
1145
|
+
kind: 'asker_dialog_stack_frame',
|
|
1146
|
+
askerDialogId: args.askerDialogId,
|
|
1147
|
+
assignmentFromAsker: args.assignment,
|
|
1148
|
+
tellaskReplyObligation: buildAssignmentTellaskReplyObligation({
|
|
1149
|
+
targetDialogId: args.assignment.askerDialogId,
|
|
1150
|
+
assignment: args.assignment,
|
|
1151
|
+
}),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function parseDialogSideDialogReplyTarget(value) {
|
|
1058
1155
|
if (!isRecord(value))
|
|
1059
1156
|
return null;
|
|
1060
1157
|
const ownerDialogId = value.ownerDialogId;
|
|
@@ -1096,22 +1193,22 @@ function parseDialogPendingCourseStartPrompt(value) {
|
|
|
1096
1193
|
if (value.tellaskReplyDirective !== undefined && tellaskReplyDirective === null) {
|
|
1097
1194
|
return null;
|
|
1098
1195
|
}
|
|
1099
|
-
const
|
|
1196
|
+
const sideDialogReplyTarget = value.sideDialogReplyTarget === undefined
|
|
1100
1197
|
? undefined
|
|
1101
|
-
:
|
|
1102
|
-
if (value.
|
|
1198
|
+
: parseDialogSideDialogReplyTarget(value.sideDialogReplyTarget);
|
|
1199
|
+
if (value.sideDialogReplyTarget !== undefined && sideDialogReplyTarget === null) {
|
|
1103
1200
|
return null;
|
|
1104
1201
|
}
|
|
1105
1202
|
const userLanguageCode = userLanguageCodeRaw;
|
|
1106
1203
|
const skipTaskdoc = skipTaskdocRaw;
|
|
1107
1204
|
const normalizedTellaskReplyDirective = tellaskReplyDirective === null ? undefined : tellaskReplyDirective;
|
|
1108
|
-
const
|
|
1109
|
-
if (
|
|
1205
|
+
const normalizedSideDialogReplyTarget = sideDialogReplyTarget === null ? undefined : sideDialogReplyTarget;
|
|
1206
|
+
if (normalizedSideDialogReplyTarget !== undefined &&
|
|
1110
1207
|
normalizedTellaskReplyDirective === undefined) {
|
|
1111
1208
|
return null;
|
|
1112
1209
|
}
|
|
1113
1210
|
if (normalizedTellaskReplyDirective !== undefined &&
|
|
1114
|
-
|
|
1211
|
+
normalizedSideDialogReplyTarget !== undefined) {
|
|
1115
1212
|
return {
|
|
1116
1213
|
content: value.content,
|
|
1117
1214
|
msgId: value.msgId,
|
|
@@ -1120,7 +1217,7 @@ function parseDialogPendingCourseStartPrompt(value) {
|
|
|
1120
1217
|
...(userLanguageCode === undefined ? {} : { userLanguageCode }),
|
|
1121
1218
|
...(skipTaskdoc === undefined ? {} : { skipTaskdoc }),
|
|
1122
1219
|
tellaskReplyDirective: normalizedTellaskReplyDirective,
|
|
1123
|
-
|
|
1220
|
+
sideDialogReplyTarget: normalizedSideDialogReplyTarget,
|
|
1124
1221
|
};
|
|
1125
1222
|
}
|
|
1126
1223
|
if (normalizedTellaskReplyDirective !== undefined) {
|
|
@@ -1162,7 +1259,7 @@ function parseDialogLatestFile(value) {
|
|
|
1162
1259
|
return null;
|
|
1163
1260
|
if (value.functionCallCount !== undefined && typeof value.functionCallCount !== 'number')
|
|
1164
1261
|
return null;
|
|
1165
|
-
if (value.
|
|
1262
|
+
if (value.sideDialogCount !== undefined && typeof value.sideDialogCount !== 'number')
|
|
1166
1263
|
return null;
|
|
1167
1264
|
if (value.generating !== undefined && typeof value.generating !== 'boolean')
|
|
1168
1265
|
return null;
|
|
@@ -1203,10 +1300,13 @@ function parseDialogLatestFile(value) {
|
|
|
1203
1300
|
switch (reason.kind) {
|
|
1204
1301
|
case 'needs_human_input':
|
|
1205
1302
|
return { kind: 'blocked', reason: { kind: 'needs_human_input' } };
|
|
1206
|
-
case '
|
|
1207
|
-
return { kind: 'blocked', reason: { kind: '
|
|
1208
|
-
case '
|
|
1209
|
-
return {
|
|
1303
|
+
case 'waiting_for_sideDialogs':
|
|
1304
|
+
return { kind: 'blocked', reason: { kind: 'waiting_for_sideDialogs' } };
|
|
1305
|
+
case 'needs_human_input_and_sideDialogs':
|
|
1306
|
+
return {
|
|
1307
|
+
kind: 'blocked',
|
|
1308
|
+
reason: { kind: 'needs_human_input_and_sideDialogs' },
|
|
1309
|
+
};
|
|
1210
1310
|
default:
|
|
1211
1311
|
return null;
|
|
1212
1312
|
}
|
|
@@ -1342,7 +1442,7 @@ function parseDialogLatestFile(value) {
|
|
|
1342
1442
|
lastModified: value.lastModified,
|
|
1343
1443
|
messageCount: value.messageCount,
|
|
1344
1444
|
functionCallCount: value.functionCallCount,
|
|
1345
|
-
|
|
1445
|
+
sideDialogCount: value.sideDialogCount,
|
|
1346
1446
|
status: value.status,
|
|
1347
1447
|
generating: value.generating,
|
|
1348
1448
|
needsDrive: value.needsDrive,
|
|
@@ -1355,12 +1455,14 @@ function parseDialogLatestFile(value) {
|
|
|
1355
1455
|
diligencePushRemainingBudget: value.diligencePushRemainingBudget,
|
|
1356
1456
|
};
|
|
1357
1457
|
}
|
|
1358
|
-
function
|
|
1458
|
+
function isSideDialogResponseRecord(value) {
|
|
1359
1459
|
if (!isRecord(value))
|
|
1360
1460
|
return false;
|
|
1361
1461
|
if (typeof value.responseId !== 'string')
|
|
1362
1462
|
return false;
|
|
1363
|
-
if (
|
|
1463
|
+
if (value.responseId.trim() === '')
|
|
1464
|
+
return false;
|
|
1465
|
+
if (typeof value.sideDialogId !== 'string')
|
|
1364
1466
|
return false;
|
|
1365
1467
|
if (typeof value.response !== 'string')
|
|
1366
1468
|
return false;
|
|
@@ -1402,6 +1504,19 @@ function isSubdialogResponseRecord(value) {
|
|
|
1402
1504
|
return false;
|
|
1403
1505
|
return true;
|
|
1404
1506
|
}
|
|
1507
|
+
function assertUniqueSideDialogResponseIds(records, context) {
|
|
1508
|
+
const seen = new Set();
|
|
1509
|
+
for (const record of records) {
|
|
1510
|
+
const responseId = record.responseId.trim();
|
|
1511
|
+
if (responseId === '') {
|
|
1512
|
+
throw new Error(`sideDialog responses empty responseId invariant violation: ${context}`);
|
|
1513
|
+
}
|
|
1514
|
+
if (seen.has(responseId)) {
|
|
1515
|
+
throw new Error(`sideDialog responses duplicate responseId invariant violation: ${context} responseId=${responseId}`);
|
|
1516
|
+
}
|
|
1517
|
+
seen.add(responseId);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1405
1520
|
function isReminderPriority(value) {
|
|
1406
1521
|
return value === 'high' || value === 'medium' || value === 'low';
|
|
1407
1522
|
}
|
|
@@ -1457,14 +1572,14 @@ function isHumanQuestion(value) {
|
|
|
1457
1572
|
return false;
|
|
1458
1573
|
if (typeof value.callSiteRef.messageIndex !== 'number')
|
|
1459
1574
|
return false;
|
|
1460
|
-
if ('
|
|
1461
|
-
const
|
|
1462
|
-
if (
|
|
1463
|
-
if (typeof
|
|
1575
|
+
if ('callSiteGenseq' in value.callSiteRef) {
|
|
1576
|
+
const callSiteGenseq = value.callSiteRef.callSiteGenseq;
|
|
1577
|
+
if (callSiteGenseq !== undefined) {
|
|
1578
|
+
if (typeof callSiteGenseq !== 'number')
|
|
1464
1579
|
return false;
|
|
1465
|
-
if (!Number.isFinite(
|
|
1580
|
+
if (!Number.isFinite(callSiteGenseq))
|
|
1466
1581
|
return false;
|
|
1467
|
-
if (Math.floor(
|
|
1582
|
+
if (Math.floor(callSiteGenseq) <= 0)
|
|
1468
1583
|
return false;
|
|
1469
1584
|
}
|
|
1470
1585
|
}
|
|
@@ -1597,85 +1712,78 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
1597
1712
|
this.thinkingReasoning = undefined;
|
|
1598
1713
|
this.dialogId = dialogId;
|
|
1599
1714
|
}
|
|
1600
|
-
// === DialogStore interface methods
|
|
1715
|
+
// === DialogStore interface methods ===
|
|
1601
1716
|
/**
|
|
1602
|
-
* Create
|
|
1717
|
+
* Create sideDialog with automatic persistence
|
|
1603
1718
|
*/
|
|
1604
|
-
async
|
|
1719
|
+
async createSideDialog(askerDialog, targetAgentId, mentionList, tellaskContent, options) {
|
|
1605
1720
|
const generatedId = (0, id_1.generateDialogID)();
|
|
1606
1721
|
const nowTs = (0, time_1.formatUnifiedTimestamp)(new Date());
|
|
1607
|
-
const
|
|
1608
|
-
?
|
|
1609
|
-
:
|
|
1610
|
-
?
|
|
1722
|
+
const mainDialog = askerDialog instanceof dialog_1.MainDialog
|
|
1723
|
+
? askerDialog
|
|
1724
|
+
: askerDialog instanceof dialog_1.SideDialog
|
|
1725
|
+
? askerDialog.mainDialog
|
|
1611
1726
|
: (() => {
|
|
1612
|
-
throw new Error(`
|
|
1727
|
+
throw new Error(`createSideDialog invariant violation: unsupported asker type (${askerDialog.constructor.name})`);
|
|
1613
1728
|
})();
|
|
1614
|
-
const rootStatus =
|
|
1729
|
+
const rootStatus = mainDialog.status;
|
|
1615
1730
|
if (rootStatus !== 'running') {
|
|
1616
|
-
throw new Error(`
|
|
1617
|
-
}
|
|
1618
|
-
const
|
|
1619
|
-
// Prepare
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
tellaskContent,
|
|
1625
|
-
originMemberId: options.originMemberId,
|
|
1626
|
-
callerDialogId: options.callerDialogId,
|
|
1627
|
-
callId: options.callId,
|
|
1628
|
-
collectiveTargets: options.collectiveTargets,
|
|
1629
|
-
effectiveFbrEffort: options.effectiveFbrEffort,
|
|
1630
|
-
}, options.sessionSlug);
|
|
1631
|
-
// Initial subdialog user prompt is now persisted at first drive (driver.ts)
|
|
1632
|
-
// Ensure subdialog directory and persist metadata under supdialog/.subdialogs/
|
|
1633
|
-
await this.ensureSubdialogDirectory(subdialogId);
|
|
1634
|
-
const metadata = {
|
|
1635
|
-
id: subdialogId.selfId,
|
|
1636
|
-
agentId: targetAgentId,
|
|
1637
|
-
taskDocPath: callerDialog.taskDocPath,
|
|
1638
|
-
createdAt: nowTs,
|
|
1639
|
-
supdialogId: callerDialog.id.selfId,
|
|
1640
|
-
sessionSlug: options.sessionSlug,
|
|
1641
|
-
assignmentFromSup: {
|
|
1731
|
+
throw new Error(`createSideDialog invariant violation: main dialog must be running (rootId=${mainDialog.id.rootId}, status=${rootStatus})`);
|
|
1732
|
+
}
|
|
1733
|
+
const sideDialogId = new dialog_1.DialogID(generatedId, mainDialog.id.rootId);
|
|
1734
|
+
// Prepare sideDialog store
|
|
1735
|
+
const sideDialogStore = new DiskFileDialogStore(sideDialogId);
|
|
1736
|
+
const sideDialog = new dialog_1.SideDialog(sideDialogStore, mainDialog, askerDialog.taskDocPath, sideDialogId, targetAgentId, (0, dialog_1.buildSideDialogAskerStack)({
|
|
1737
|
+
askerDialogId: options.askerDialogId,
|
|
1738
|
+
assignment: {
|
|
1642
1739
|
callName: options.callName,
|
|
1643
1740
|
mentionList,
|
|
1644
1741
|
tellaskContent,
|
|
1645
1742
|
originMemberId: options.originMemberId,
|
|
1646
|
-
|
|
1743
|
+
askerDialogId: options.askerDialogId,
|
|
1647
1744
|
callId: options.callId,
|
|
1648
1745
|
collectiveTargets: options.collectiveTargets,
|
|
1649
1746
|
effectiveFbrEffort: options.effectiveFbrEffort,
|
|
1650
1747
|
},
|
|
1748
|
+
}), options.sessionSlug);
|
|
1749
|
+
// Initial sideDialog user prompt is now persisted at first drive (driver.ts)
|
|
1750
|
+
// Ensure sideDialog directory and persist metadata under askerDialog/.sideDialogs/
|
|
1751
|
+
await this.ensureSideDialogDirectory(sideDialogId);
|
|
1752
|
+
const metadata = {
|
|
1753
|
+
id: sideDialogId.selfId,
|
|
1754
|
+
agentId: targetAgentId,
|
|
1755
|
+
taskDocPath: askerDialog.taskDocPath,
|
|
1756
|
+
createdAt: nowTs,
|
|
1757
|
+
sessionSlug: options.sessionSlug,
|
|
1651
1758
|
};
|
|
1652
|
-
await DialogPersistence.
|
|
1653
|
-
|
|
1654
|
-
const
|
|
1655
|
-
const
|
|
1759
|
+
await DialogPersistence.saveSideDialogAskerStackState(sideDialogId, sideDialog.askerStack);
|
|
1760
|
+
await DialogPersistence.saveSideDialogMetadata(sideDialogId, metadata);
|
|
1761
|
+
const rootAnchor = resolveRootGenerationAnchor(askerDialog);
|
|
1762
|
+
const parentCourse = askerDialog.activeGenCourseOrUndefined ?? askerDialog.currentCourse;
|
|
1763
|
+
const sideDialogCreatedRecord = {
|
|
1656
1764
|
ts: nowTs,
|
|
1657
|
-
type: '
|
|
1765
|
+
type: 'sideDialog_created_record',
|
|
1658
1766
|
...cloneRootGenerationAnchor(rootAnchor),
|
|
1659
|
-
|
|
1660
|
-
|
|
1767
|
+
sideDialogId: sideDialogId.selfId,
|
|
1768
|
+
askerDialogId: askerDialog.id.selfId,
|
|
1661
1769
|
agentId: targetAgentId,
|
|
1662
|
-
taskDocPath:
|
|
1770
|
+
taskDocPath: askerDialog.taskDocPath,
|
|
1663
1771
|
createdAt: nowTs,
|
|
1664
1772
|
sessionSlug: options.sessionSlug,
|
|
1665
|
-
|
|
1773
|
+
assignmentFromAsker: {
|
|
1666
1774
|
callName: options.callName,
|
|
1667
1775
|
mentionList,
|
|
1668
1776
|
tellaskContent,
|
|
1669
1777
|
originMemberId: options.originMemberId,
|
|
1670
|
-
|
|
1778
|
+
askerDialogId: options.askerDialogId,
|
|
1671
1779
|
callId: options.callId,
|
|
1672
1780
|
collectiveTargets: options.collectiveTargets,
|
|
1673
1781
|
effectiveFbrEffort: options.effectiveFbrEffort,
|
|
1674
1782
|
},
|
|
1675
1783
|
};
|
|
1676
|
-
await this.appendEvent(
|
|
1784
|
+
await this.appendEvent(askerDialog, parentCourse, sideDialogCreatedRecord);
|
|
1677
1785
|
// Initialize latest.yaml via the mutation API (write-back will flush).
|
|
1678
|
-
await DialogPersistence.mutateDialogLatest(
|
|
1786
|
+
await DialogPersistence.mutateDialogLatest(sideDialogId, () => ({
|
|
1679
1787
|
kind: 'replace',
|
|
1680
1788
|
next: {
|
|
1681
1789
|
currentCourse: 1,
|
|
@@ -1683,61 +1791,61 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
1683
1791
|
status: 'active',
|
|
1684
1792
|
messageCount: 0,
|
|
1685
1793
|
functionCallCount: 0,
|
|
1686
|
-
|
|
1794
|
+
sideDialogCount: 0,
|
|
1687
1795
|
displayState: { kind: 'idle_waiting_user' },
|
|
1688
1796
|
disableDiligencePush: false,
|
|
1689
1797
|
},
|
|
1690
1798
|
}));
|
|
1691
|
-
//
|
|
1692
|
-
const
|
|
1693
|
-
const
|
|
1694
|
-
type: '
|
|
1799
|
+
// AskerDialog clarification context is persisted in sideDialog metadata (askerDialogCall)
|
|
1800
|
+
const rootSideDialogCount = await DialogPersistence.countAllSideDialogsUnderRoot(mainDialog.id, rootStatus);
|
|
1801
|
+
const sideDialogCreatedEvt = {
|
|
1802
|
+
type: 'sideDialog_created_evt',
|
|
1695
1803
|
dialog: {
|
|
1696
|
-
selfId:
|
|
1697
|
-
rootId:
|
|
1804
|
+
selfId: sideDialogId.selfId,
|
|
1805
|
+
rootId: sideDialogId.rootId,
|
|
1698
1806
|
},
|
|
1699
1807
|
timestamp: new Date().toISOString(),
|
|
1700
1808
|
course: parentCourse,
|
|
1701
1809
|
parentDialog: {
|
|
1702
|
-
selfId:
|
|
1703
|
-
rootId:
|
|
1810
|
+
selfId: askerDialog.id.selfId,
|
|
1811
|
+
rootId: askerDialog.id.rootId,
|
|
1704
1812
|
},
|
|
1705
|
-
|
|
1706
|
-
selfId:
|
|
1707
|
-
rootId:
|
|
1813
|
+
sideDialog: {
|
|
1814
|
+
selfId: sideDialogId.selfId,
|
|
1815
|
+
rootId: sideDialogId.rootId,
|
|
1708
1816
|
},
|
|
1709
1817
|
targetAgentId,
|
|
1710
1818
|
callName: options.callName,
|
|
1711
1819
|
mentionList,
|
|
1712
1820
|
tellaskContent,
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
selfId:
|
|
1716
|
-
rootId:
|
|
1717
|
-
|
|
1821
|
+
rootSideDialogCount,
|
|
1822
|
+
sideDialogNode: {
|
|
1823
|
+
selfId: sideDialogId.selfId,
|
|
1824
|
+
rootId: sideDialogId.rootId,
|
|
1825
|
+
askerDialogId: askerDialog.id.selfId,
|
|
1718
1826
|
agentId: targetAgentId,
|
|
1719
|
-
taskDocPath:
|
|
1827
|
+
taskDocPath: askerDialog.taskDocPath,
|
|
1720
1828
|
status: rootStatus,
|
|
1721
1829
|
currentCourse: 1,
|
|
1722
1830
|
createdAt: nowTs,
|
|
1723
1831
|
lastModified: nowTs,
|
|
1724
1832
|
displayState: { kind: 'idle_waiting_user' },
|
|
1725
1833
|
sessionSlug: options.sessionSlug,
|
|
1726
|
-
|
|
1834
|
+
assignmentFromAsker: {
|
|
1727
1835
|
callName: options.callName,
|
|
1728
1836
|
mentionList,
|
|
1729
1837
|
tellaskContent,
|
|
1730
1838
|
originMemberId: options.originMemberId,
|
|
1731
|
-
|
|
1839
|
+
askerDialogId: options.askerDialogId,
|
|
1732
1840
|
callId: options.callId,
|
|
1733
1841
|
effectiveFbrEffort: options.effectiveFbrEffort,
|
|
1734
1842
|
},
|
|
1735
1843
|
},
|
|
1736
1844
|
};
|
|
1737
|
-
// Post
|
|
1738
|
-
// The frontend subscribes to the parent's events, not the
|
|
1739
|
-
(0, evt_registry_1.postDialogEvent)(
|
|
1740
|
-
return
|
|
1845
|
+
// Post sideDialog_created_evt to PARENT's PubChan so frontend can receive it
|
|
1846
|
+
// The frontend subscribes to the parent's events, not the sideDialog's
|
|
1847
|
+
(0, evt_registry_1.postDialogEvent)(askerDialog, sideDialogCreatedEvt);
|
|
1848
|
+
return sideDialog;
|
|
1741
1849
|
}
|
|
1742
1850
|
/**
|
|
1743
1851
|
* Receive and handle function call results (includes logging)
|
|
@@ -1815,10 +1923,10 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
1815
1923
|
(0, evt_registry_1.postDialogEvent)(dialog, buildTellaskCarryoverEvent(normalizedResult, course));
|
|
1816
1924
|
}
|
|
1817
1925
|
/**
|
|
1818
|
-
* Ensure
|
|
1926
|
+
* Ensure sideDialog directory exists (delegate to DialogPersistence)
|
|
1819
1927
|
*/
|
|
1820
|
-
async
|
|
1821
|
-
return await DialogPersistence.
|
|
1928
|
+
async ensureSideDialogDirectory(dialogId) {
|
|
1929
|
+
return await DialogPersistence.ensureSideDialogDirectory(dialogId);
|
|
1822
1930
|
}
|
|
1823
1931
|
async findExistingFuncResultRecord(dialog, callId) {
|
|
1824
1932
|
const latest = await DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
|
|
@@ -1837,6 +1945,22 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
1837
1945
|
}
|
|
1838
1946
|
return undefined;
|
|
1839
1947
|
}
|
|
1948
|
+
async findExistingCallRecord(dialog, callId) {
|
|
1949
|
+
const latest = await DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
|
|
1950
|
+
const maxCourse = latest?.currentCourse ?? dialog.currentCourse;
|
|
1951
|
+
for (let course = 1; course <= maxCourse; course += 1) {
|
|
1952
|
+
const events = await DialogPersistence.loadCourseEvents(dialog.id, course, dialog.status);
|
|
1953
|
+
for (const event of events) {
|
|
1954
|
+
if (event.type === 'func_call_record' && event.id === callId) {
|
|
1955
|
+
return { course, record: event };
|
|
1956
|
+
}
|
|
1957
|
+
if (event.type === 'tellask_call_record' && event.id === callId) {
|
|
1958
|
+
return { course, record: event };
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
return undefined;
|
|
1963
|
+
}
|
|
1840
1964
|
async findExistingTellaskResultRecord(dialog, callId) {
|
|
1841
1965
|
const latest = await DialogPersistence.loadDialogLatest(dialog.id, dialog.status);
|
|
1842
1966
|
const maxCourse = latest?.currentCourse ?? dialog.currentCourse;
|
|
@@ -1854,10 +1978,41 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
1854
1978
|
}
|
|
1855
1979
|
return undefined;
|
|
1856
1980
|
}
|
|
1981
|
+
async raiseDuplicateCallInvariantViolation(args) {
|
|
1982
|
+
const err = new Error(`${args.kind} duplicate callId invariant violation: rootId=${args.dialog.id.rootId} selfId=${args.dialog.id.selfId} ` +
|
|
1983
|
+
`callId=${args.callId} callName=${args.callName} existingName=${args.existingName} ` +
|
|
1984
|
+
`existingCourse=${args.existingCourse} existingGenseq=${args.existingGenseq} ` +
|
|
1985
|
+
`incomingCourse=${args.incomingCourse} incomingGenseq=${args.incomingGenseq}`);
|
|
1986
|
+
log_1.log.error('Duplicate call detected; rejecting second write', err, {
|
|
1987
|
+
rootId: args.dialog.id.rootId,
|
|
1988
|
+
selfId: args.dialog.id.selfId,
|
|
1989
|
+
callId: args.callId,
|
|
1990
|
+
callName: args.callName,
|
|
1991
|
+
kind: args.kind,
|
|
1992
|
+
existingName: args.existingName,
|
|
1993
|
+
existingCourse: args.existingCourse,
|
|
1994
|
+
existingGenseq: args.existingGenseq,
|
|
1995
|
+
incomingCourse: args.incomingCourse,
|
|
1996
|
+
incomingGenseq: args.incomingGenseq,
|
|
1997
|
+
});
|
|
1998
|
+
try {
|
|
1999
|
+
await this.streamError(args.dialog, err.message);
|
|
2000
|
+
}
|
|
2001
|
+
catch (streamErr) {
|
|
2002
|
+
log_1.log.warn('Failed to emit stream_error_evt for duplicate call', streamErr, {
|
|
2003
|
+
rootId: args.dialog.id.rootId,
|
|
2004
|
+
selfId: args.dialog.id.selfId,
|
|
2005
|
+
callId: args.callId,
|
|
2006
|
+
callName: args.callName,
|
|
2007
|
+
kind: args.kind,
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
throw err;
|
|
2011
|
+
}
|
|
1857
2012
|
async raiseDuplicateCallResultInvariantViolation(args) {
|
|
1858
2013
|
// Duplicate final results are not harmless transcript noise. They mean two different program
|
|
1859
2014
|
// paths both believed they owned the same business-level completion fact for one callId.
|
|
1860
|
-
// In ask-back flows this usually points to identity confusion between
|
|
2015
|
+
// In ask-back flows this usually points to identity confusion between asker/tellaskee or
|
|
1861
2016
|
// canonical reply-tool delivery versus another mistaken write path. We fail fast here so the
|
|
1862
2017
|
// second writer keeps its own stack trace instead of silently corrupting the dialog transcript.
|
|
1863
2018
|
const err = new Error(`${args.kind} duplicate callId invariant violation: rootId=${args.dialog.id.rootId} selfId=${args.dialog.id.selfId} ` +
|
|
@@ -2141,19 +2296,6 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
2141
2296
|
})();
|
|
2142
2297
|
(0, evt_registry_1.postDialogEvent)(dialog, evt);
|
|
2143
2298
|
}
|
|
2144
|
-
// Function call events (non-streaming mode - single event captures entire call)
|
|
2145
|
-
async funcCallRequested(dialog, funcId, funcName, argumentsStr) {
|
|
2146
|
-
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2147
|
-
const funcCallEvt = {
|
|
2148
|
-
type: 'func_call_requested_evt',
|
|
2149
|
-
funcId,
|
|
2150
|
-
funcName,
|
|
2151
|
-
arguments: argumentsStr,
|
|
2152
|
-
course,
|
|
2153
|
-
genseq: dialog.activeGenSeq,
|
|
2154
|
-
};
|
|
2155
|
-
(0, evt_registry_1.postDialogEvent)(dialog, funcCallEvt);
|
|
2156
|
-
}
|
|
2157
2299
|
async webSearchCall(dialog, payload) {
|
|
2158
2300
|
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2159
2301
|
const itemId = payload.itemId.trim();
|
|
@@ -2545,6 +2687,10 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
2545
2687
|
if (deferredReplyReassertion?.directive.targetCallId === payload.targetCallId) {
|
|
2546
2688
|
await DialogPersistence.setDeferredReplyReassertion(dialog.id, undefined, dialog.status);
|
|
2547
2689
|
}
|
|
2690
|
+
const activeObligation = await DialogPersistence.loadActiveTellaskReplyObligation(dialog.id, dialog.status);
|
|
2691
|
+
if (activeObligation?.targetCallId === payload.targetCallId) {
|
|
2692
|
+
await DialogPersistence.setActiveTellaskReplyObligation(dialog.id, undefined, dialog.status);
|
|
2693
|
+
}
|
|
2548
2694
|
}
|
|
2549
2695
|
/**
|
|
2550
2696
|
* Persist an assistant message to storage
|
|
@@ -2593,13 +2739,54 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
2593
2739
|
*/
|
|
2594
2740
|
async persistFunctionCall(dialog, id, name, rawArgumentsText, genseq) {
|
|
2595
2741
|
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2742
|
+
if (!Number.isFinite(genseq) || genseq <= 0) {
|
|
2743
|
+
throw new Error(`persistFunctionCall invariant violation: missing valid genseq for func call ${id}`);
|
|
2744
|
+
}
|
|
2745
|
+
const existingCall = await this.findExistingCallRecord(dialog, id);
|
|
2746
|
+
if (existingCall) {
|
|
2747
|
+
await this.raiseDuplicateCallInvariantViolation({
|
|
2748
|
+
dialog,
|
|
2749
|
+
kind: 'func_call',
|
|
2750
|
+
callId: id,
|
|
2751
|
+
callName: name,
|
|
2752
|
+
incomingCourse: course,
|
|
2753
|
+
incomingGenseq: genseq,
|
|
2754
|
+
existingCourse: existingCall.course,
|
|
2755
|
+
existingGenseq: existingCall.record.genseq,
|
|
2756
|
+
existingName: existingCall.record.name,
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2596
2759
|
const funcCallEvent = buildFuncCallRecord({ id, name, rawArgumentsText, genseq });
|
|
2597
2760
|
await this.appendEvent(dialog, course, funcCallEvent);
|
|
2598
|
-
|
|
2599
|
-
|
|
2761
|
+
const funcCallEvt = {
|
|
2762
|
+
type: 'func_call_requested_evt',
|
|
2763
|
+
funcId: id,
|
|
2764
|
+
funcName: name,
|
|
2765
|
+
arguments: rawArgumentsText,
|
|
2766
|
+
course,
|
|
2767
|
+
genseq,
|
|
2768
|
+
};
|
|
2769
|
+
(0, evt_registry_1.postDialogEvent)(dialog, funcCallEvt);
|
|
2600
2770
|
}
|
|
2601
2771
|
async persistTellaskCall(dialog, id, name, rawArgumentsText, genseq, options) {
|
|
2602
2772
|
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2773
|
+
if (!Number.isFinite(genseq) || genseq <= 0) {
|
|
2774
|
+
throw new Error(`persistTellaskCall invariant violation: missing valid genseq for tellask call ${id}`);
|
|
2775
|
+
}
|
|
2776
|
+
const existingCall = await this.findExistingCallRecord(dialog, id);
|
|
2777
|
+
if (existingCall) {
|
|
2778
|
+
await this.raiseDuplicateCallInvariantViolation({
|
|
2779
|
+
dialog,
|
|
2780
|
+
kind: 'tellask_call',
|
|
2781
|
+
callId: id,
|
|
2782
|
+
callName: name,
|
|
2783
|
+
incomingCourse: course,
|
|
2784
|
+
incomingGenseq: genseq,
|
|
2785
|
+
existingCourse: existingCall.course,
|
|
2786
|
+
existingGenseq: existingCall.record.genseq,
|
|
2787
|
+
existingName: existingCall.record.name,
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2603
2790
|
const tellaskCallEvent = buildTellaskCallRecord({
|
|
2604
2791
|
id,
|
|
2605
2792
|
name,
|
|
@@ -2609,81 +2796,17 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
2609
2796
|
(isReplyTellaskCallRecordName(name) ? 'func_call_requested' : 'tellask_call_start'),
|
|
2610
2797
|
});
|
|
2611
2798
|
await this.appendEvent(dialog, course, tellaskCallEvent);
|
|
2612
|
-
if (
|
|
2799
|
+
if (tellaskCallEvent.deliveryMode === 'func_call_requested') {
|
|
2613
2800
|
const funcCallEvt = {
|
|
2614
2801
|
type: 'func_call_requested_evt',
|
|
2615
2802
|
funcId: id,
|
|
2616
2803
|
funcName: name,
|
|
2617
2804
|
arguments: formatTellaskCallArguments(tellaskCallEvent),
|
|
2618
2805
|
course,
|
|
2619
|
-
genseq: dialog.activeGenSeqOrUndefined ?? genseq,
|
|
2620
|
-
};
|
|
2621
|
-
(0, evt_registry_1.postDialogEvent)(dialog, funcCallEvt);
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
async persistFunctionCallResultPair(dialog, id, name, rawArgumentsText, genseq, result) {
|
|
2625
|
-
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2626
|
-
const resultGenseq = dialog.activeGenSeqOrUndefined ?? result.genseq;
|
|
2627
|
-
if (!Number.isFinite(resultGenseq) || resultGenseq <= 0) {
|
|
2628
|
-
throw new Error(`persistFunctionCallResultPair invariant violation: missing valid genseq for func result ${result.id}`);
|
|
2629
|
-
}
|
|
2630
|
-
await this.appendEvents(dialog, course, [
|
|
2631
|
-
buildFuncCallRecord({ id, name, rawArgumentsText, genseq }),
|
|
2632
|
-
buildFuncResultRecord(result, resultGenseq),
|
|
2633
|
-
]);
|
|
2634
|
-
if (!isSuppressedTellaskPlaceholderFuncResult({
|
|
2635
|
-
name: result.name,
|
|
2636
|
-
content: result.content,
|
|
2637
|
-
})) {
|
|
2638
|
-
const funcResultEvt = {
|
|
2639
|
-
type: 'func_result_evt',
|
|
2640
|
-
id: result.id,
|
|
2641
|
-
name: result.name,
|
|
2642
|
-
content: result.content,
|
|
2643
|
-
contentItems: result.contentItems,
|
|
2644
|
-
course,
|
|
2645
2806
|
genseq,
|
|
2646
2807
|
};
|
|
2647
|
-
(0, evt_registry_1.postDialogEvent)(dialog,
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
async persistTellaskCallResultPair(dialog, args) {
|
|
2651
|
-
const course = dialog.activeGenCourseOrUndefined ?? dialog.currentCourse;
|
|
2652
|
-
const callRecord = buildTellaskCallRecord({
|
|
2653
|
-
id: args.id,
|
|
2654
|
-
name: args.name,
|
|
2655
|
-
rawArgumentsText: args.rawArgumentsText,
|
|
2656
|
-
genseq: args.genseq,
|
|
2657
|
-
deliveryMode: args.deliveryMode,
|
|
2658
|
-
});
|
|
2659
|
-
if (args.result.type === 'func_result_msg') {
|
|
2660
|
-
const resultGenseq = dialog.activeGenSeqOrUndefined ?? args.result.genseq;
|
|
2661
|
-
if (!Number.isFinite(resultGenseq) || resultGenseq <= 0) {
|
|
2662
|
-
throw new Error(`persistTellaskCallResultPair invariant violation: missing valid genseq for func result ${args.result.id}`);
|
|
2663
|
-
}
|
|
2664
|
-
await this.appendEvents(dialog, course, [
|
|
2665
|
-
callRecord,
|
|
2666
|
-
buildFuncResultRecord(args.result, resultGenseq),
|
|
2667
|
-
]);
|
|
2668
|
-
if (!isSuppressedTellaskPlaceholderFuncResult({
|
|
2669
|
-
name: args.result.name,
|
|
2670
|
-
content: args.result.content,
|
|
2671
|
-
})) {
|
|
2672
|
-
const funcResultEvt = {
|
|
2673
|
-
type: 'func_result_evt',
|
|
2674
|
-
id: args.result.id,
|
|
2675
|
-
name: args.result.name,
|
|
2676
|
-
content: args.result.content,
|
|
2677
|
-
contentItems: args.result.contentItems,
|
|
2678
|
-
course,
|
|
2679
|
-
genseq: resultGenseq,
|
|
2680
|
-
};
|
|
2681
|
-
(0, evt_registry_1.postDialogEvent)(dialog, funcResultEvt);
|
|
2682
|
-
}
|
|
2683
|
-
return;
|
|
2808
|
+
(0, evt_registry_1.postDialogEvent)(dialog, funcCallEvt);
|
|
2684
2809
|
}
|
|
2685
|
-
await this.appendEvents(dialog, course, [callRecord, buildTellaskResultRecord(args.result)]);
|
|
2686
|
-
(0, evt_registry_1.postDialogEvent)(dialog, buildTellaskResultEvent(args.result, course));
|
|
2687
2810
|
}
|
|
2688
2811
|
/**
|
|
2689
2812
|
* Update questions for human state (exceptional overwrite pattern)
|
|
@@ -2701,162 +2824,168 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
2701
2824
|
async loadDialogMetadata(dialogId, status) {
|
|
2702
2825
|
return await DialogPersistence.loadDialogMetadata(dialogId, status);
|
|
2703
2826
|
}
|
|
2704
|
-
async
|
|
2705
|
-
|
|
2827
|
+
async loadSideDialogAssignmentFromAsker(dialogId, status) {
|
|
2828
|
+
if (dialogId.rootId === dialogId.selfId)
|
|
2829
|
+
return null;
|
|
2830
|
+
return await DialogPersistence.loadSideDialogAssignmentFromAsker(dialogId, status);
|
|
2831
|
+
}
|
|
2832
|
+
async loadPendingSideDialogs(mainDialogId, status) {
|
|
2833
|
+
const records = await DialogPersistence.loadPendingSideDialogs(mainDialogId, status);
|
|
2706
2834
|
return records.map((record) => ({
|
|
2707
|
-
|
|
2835
|
+
sideDialogId: new dialog_1.DialogID(record.sideDialogId, mainDialogId.rootId),
|
|
2708
2836
|
createdAt: record.createdAt,
|
|
2709
2837
|
mentionList: record.mentionList,
|
|
2710
2838
|
tellaskContent: record.tellaskContent,
|
|
2711
2839
|
targetAgentId: record.targetAgentId,
|
|
2712
2840
|
callId: record.callId,
|
|
2713
|
-
|
|
2841
|
+
callSiteCourse: record.callSiteCourse,
|
|
2842
|
+
callSiteGenseq: record.callSiteGenseq,
|
|
2714
2843
|
callType: record.callType,
|
|
2715
2844
|
sessionSlug: record.sessionSlug,
|
|
2716
2845
|
}));
|
|
2717
2846
|
}
|
|
2718
|
-
async
|
|
2719
|
-
await DialogPersistence.
|
|
2720
|
-
await DialogPersistence.
|
|
2847
|
+
async saveSideDialogRegistry(dialog, mainDialogId, entries, status) {
|
|
2848
|
+
await DialogPersistence.saveSideDialogRegistry(mainDialogId, entries, status);
|
|
2849
|
+
await DialogPersistence.appendSideDialogRegistryReconciledRecord(mainDialogId, entries.map((entry) => ({
|
|
2721
2850
|
key: entry.key,
|
|
2722
|
-
|
|
2851
|
+
sideDialogId: entry.sideDialogId.selfId,
|
|
2723
2852
|
agentId: entry.agentId,
|
|
2724
2853
|
sessionSlug: entry.sessionSlug,
|
|
2725
2854
|
})), resolveReconciledRecordWriteTarget(dialog), status);
|
|
2726
2855
|
}
|
|
2727
|
-
async
|
|
2728
|
-
const entries = await DialogPersistence.
|
|
2856
|
+
async loadSideDialogRegistry(mainDialog, status) {
|
|
2857
|
+
const entries = await DialogPersistence.loadSideDialogRegistry(mainDialog.id, status);
|
|
2729
2858
|
const shouldPruneDead = status === 'running';
|
|
2730
2859
|
let prunedDeadRegistryEntries = false;
|
|
2731
|
-
const
|
|
2732
|
-
const
|
|
2733
|
-
if (ancestry.has(
|
|
2734
|
-
throw new Error(`
|
|
2735
|
-
`(rootId=${
|
|
2860
|
+
const restoringSideDialogs = new Map();
|
|
2861
|
+
const ensureSideDialogLoaded = async (sideDialogId, ancestry = new Set()) => {
|
|
2862
|
+
if (ancestry.has(sideDialogId.selfId)) {
|
|
2863
|
+
throw new Error(`SideDialog registry restore invariant violation: cyclic parent chain ` +
|
|
2864
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${sideDialogId.selfId})`);
|
|
2736
2865
|
}
|
|
2737
|
-
const existing =
|
|
2866
|
+
const existing = mainDialog.lookupDialog(sideDialogId.selfId);
|
|
2738
2867
|
if (existing) {
|
|
2739
|
-
if (!(existing instanceof dialog_1.
|
|
2740
|
-
throw new Error(`Dialog registry type invariant violation: expected
|
|
2741
|
-
`(rootId=${
|
|
2868
|
+
if (!(existing instanceof dialog_1.SideDialog)) {
|
|
2869
|
+
throw new Error(`Dialog registry type invariant violation: expected SideDialog ` +
|
|
2870
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${sideDialogId.selfId})`);
|
|
2742
2871
|
}
|
|
2743
2872
|
return existing;
|
|
2744
2873
|
}
|
|
2745
|
-
const inFlight =
|
|
2874
|
+
const inFlight = restoringSideDialogs.get(sideDialogId.selfId);
|
|
2746
2875
|
if (inFlight) {
|
|
2747
2876
|
return await inFlight;
|
|
2748
2877
|
}
|
|
2749
2878
|
const task = (async () => {
|
|
2750
2879
|
const nextAncestry = new Set(ancestry);
|
|
2751
|
-
nextAncestry.add(
|
|
2752
|
-
const
|
|
2753
|
-
if (!
|
|
2754
|
-
throw new Error(`
|
|
2755
|
-
`(rootId=${
|
|
2880
|
+
nextAncestry.add(sideDialogId.selfId);
|
|
2881
|
+
const sideDialogState = await DialogPersistence.restoreDialog(sideDialogId, status);
|
|
2882
|
+
if (!sideDialogState) {
|
|
2883
|
+
throw new Error(`SideDialog registry restore invariant violation: missing dialog state ` +
|
|
2884
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${sideDialogId.selfId})`);
|
|
2756
2885
|
}
|
|
2757
|
-
const metadata =
|
|
2758
|
-
if (!
|
|
2759
|
-
throw new Error(`
|
|
2760
|
-
`(rootId=${
|
|
2886
|
+
const metadata = sideDialogState.metadata;
|
|
2887
|
+
if (!isSideDialogMetadataFile(metadata)) {
|
|
2888
|
+
throw new Error(`SideDialog registry restore invariant violation: expected sideDialog metadata ` +
|
|
2889
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${sideDialogId.selfId})`);
|
|
2761
2890
|
}
|
|
2762
|
-
const
|
|
2763
|
-
if (!
|
|
2764
|
-
throw new Error(`
|
|
2765
|
-
`(rootId=${
|
|
2891
|
+
const askerStack = await DialogPersistence.loadSideDialogAskerStackState(sideDialogId, status);
|
|
2892
|
+
if (!askerStack) {
|
|
2893
|
+
throw new Error(`SideDialog registry restore invariant violation: missing asker stack ` +
|
|
2894
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${sideDialogId.selfId})`);
|
|
2766
2895
|
}
|
|
2896
|
+
const assignmentFromAsker = getDialogAskerStackCurrentAssignment(askerStack);
|
|
2767
2897
|
const parentIds = [];
|
|
2768
2898
|
const maybePushParentId = (candidate) => {
|
|
2769
2899
|
if (!candidate)
|
|
2770
2900
|
return;
|
|
2771
|
-
if (candidate ===
|
|
2901
|
+
if (candidate === mainDialog.id.rootId)
|
|
2772
2902
|
return;
|
|
2773
|
-
if (candidate ===
|
|
2903
|
+
if (candidate === sideDialogId.selfId)
|
|
2774
2904
|
return;
|
|
2775
2905
|
if (parentIds.includes(candidate))
|
|
2776
2906
|
return;
|
|
2777
2907
|
parentIds.push(candidate);
|
|
2778
2908
|
};
|
|
2779
|
-
maybePushParentId(
|
|
2780
|
-
maybePushParentId(assignmentFromSup.callerDialogId);
|
|
2909
|
+
maybePushParentId(assignmentFromAsker.askerDialogId);
|
|
2781
2910
|
for (const parentId of parentIds) {
|
|
2782
|
-
if (
|
|
2911
|
+
if (mainDialog.lookupDialog(parentId)) {
|
|
2783
2912
|
continue;
|
|
2784
2913
|
}
|
|
2785
|
-
const parentDialogId = new dialog_1.DialogID(parentId,
|
|
2914
|
+
const parentDialogId = new dialog_1.DialogID(parentId, mainDialog.id.rootId);
|
|
2786
2915
|
const parentMeta = await DialogPersistence.loadDialogMetadata(parentDialogId, status);
|
|
2787
2916
|
if (!parentMeta) {
|
|
2788
|
-
throw new Error(`
|
|
2789
|
-
`(rootId=${
|
|
2917
|
+
throw new Error(`SideDialog registry restore invariant violation: missing parent metadata ` +
|
|
2918
|
+
`(rootId=${mainDialog.id.rootId}, childId=${sideDialogId.selfId}, parentId=${parentId})`);
|
|
2790
2919
|
}
|
|
2791
|
-
if (!
|
|
2792
|
-
throw new Error(`
|
|
2793
|
-
`(rootId=${
|
|
2920
|
+
if (!isSideDialogMetadataFile(parentMeta)) {
|
|
2921
|
+
throw new Error(`SideDialog registry restore invariant violation: parent is not a sideDialog ` +
|
|
2922
|
+
`(rootId=${mainDialog.id.rootId}, childId=${sideDialogId.selfId}, parentId=${parentId})`);
|
|
2794
2923
|
}
|
|
2795
|
-
await
|
|
2796
|
-
if (!
|
|
2797
|
-
throw new Error(`
|
|
2798
|
-
`(rootId=${
|
|
2924
|
+
await ensureSideDialogLoaded(parentDialogId, nextAncestry);
|
|
2925
|
+
if (!mainDialog.lookupDialog(parentId)) {
|
|
2926
|
+
throw new Error(`SideDialog registry restore invariant violation: parent restore failed ` +
|
|
2927
|
+
`(rootId=${mainDialog.id.rootId}, childId=${sideDialogId.selfId}, parentId=${parentId})`);
|
|
2799
2928
|
}
|
|
2800
2929
|
}
|
|
2801
|
-
const
|
|
2802
|
-
const
|
|
2803
|
-
messages:
|
|
2804
|
-
reminders:
|
|
2805
|
-
currentCourse:
|
|
2806
|
-
contextHealth:
|
|
2930
|
+
const sideDialogStore = new DiskFileDialogStore(sideDialogId);
|
|
2931
|
+
const sideDialog = new dialog_1.SideDialog(sideDialogStore, mainDialog, metadata.taskDocPath, new dialog_1.DialogID(sideDialogId.selfId, mainDialog.id.rootId), metadata.agentId, askerStack, metadata.sessionSlug, {
|
|
2932
|
+
messages: sideDialogState.messages,
|
|
2933
|
+
reminders: sideDialogState.reminders,
|
|
2934
|
+
currentCourse: sideDialogState.currentCourse,
|
|
2935
|
+
contextHealth: sideDialogState.contextHealth,
|
|
2807
2936
|
});
|
|
2808
|
-
const latest = await DialogPersistence.loadDialogLatest(
|
|
2809
|
-
|
|
2810
|
-
if (
|
|
2811
|
-
|
|
2937
|
+
const latest = await DialogPersistence.loadDialogLatest(sideDialogId, status);
|
|
2938
|
+
sideDialog.disableDiligencePush = latest?.disableDiligencePush ?? false;
|
|
2939
|
+
if (sideDialog.sessionSlug) {
|
|
2940
|
+
mainDialog.registerSideDialog(sideDialog);
|
|
2812
2941
|
}
|
|
2813
|
-
return
|
|
2942
|
+
return sideDialog;
|
|
2814
2943
|
})();
|
|
2815
|
-
|
|
2944
|
+
restoringSideDialogs.set(sideDialogId.selfId, task);
|
|
2816
2945
|
try {
|
|
2817
2946
|
return await task;
|
|
2818
2947
|
}
|
|
2819
2948
|
finally {
|
|
2820
|
-
|
|
2949
|
+
restoringSideDialogs.delete(sideDialogId.selfId);
|
|
2821
2950
|
}
|
|
2822
2951
|
};
|
|
2823
2952
|
for (const entry of entries) {
|
|
2824
2953
|
if (!entry.sessionSlug)
|
|
2825
2954
|
continue;
|
|
2826
2955
|
if (shouldPruneDead) {
|
|
2827
|
-
const latest = await DialogPersistence.loadDialogLatest(entry.
|
|
2956
|
+
const latest = await DialogPersistence.loadDialogLatest(entry.sideDialogId, status);
|
|
2828
2957
|
const executionMarker = latest?.executionMarker;
|
|
2829
2958
|
if (executionMarker && executionMarker.kind === 'dead') {
|
|
2830
2959
|
prunedDeadRegistryEntries = true;
|
|
2831
|
-
|
|
2832
|
-
log_1.log.debug('Skip dead
|
|
2833
|
-
rootId:
|
|
2834
|
-
|
|
2960
|
+
mainDialog.unregisterSideDialog(entry.agentId, entry.sessionSlug);
|
|
2961
|
+
log_1.log.debug('Skip dead sideDialog while loading Type B registry', undefined, {
|
|
2962
|
+
rootId: mainDialog.id.rootId,
|
|
2963
|
+
sideDialogId: entry.sideDialogId.selfId,
|
|
2835
2964
|
agentId: entry.agentId,
|
|
2836
2965
|
sessionSlug: entry.sessionSlug,
|
|
2837
2966
|
});
|
|
2838
2967
|
continue;
|
|
2839
2968
|
}
|
|
2840
2969
|
}
|
|
2841
|
-
const
|
|
2842
|
-
if (!
|
|
2843
|
-
throw new Error(`
|
|
2844
|
-
`(rootId=${
|
|
2970
|
+
const sideDialog = await ensureSideDialogLoaded(entry.sideDialogId);
|
|
2971
|
+
if (!sideDialog.sessionSlug) {
|
|
2972
|
+
throw new Error(`SideDialog registry invariant violation: missing sessionSlug on loaded sideDialog ` +
|
|
2973
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${entry.sideDialogId.selfId}, expectedSessionSlug=${entry.sessionSlug})`);
|
|
2845
2974
|
}
|
|
2846
|
-
if (
|
|
2847
|
-
throw new Error(`
|
|
2848
|
-
`(rootId=${
|
|
2849
|
-
`expected=${entry.sessionSlug}, actual=${
|
|
2975
|
+
if (sideDialog.sessionSlug !== entry.sessionSlug) {
|
|
2976
|
+
throw new Error(`SideDialog registry invariant violation: sessionSlug mismatch ` +
|
|
2977
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${entry.sideDialogId.selfId}, ` +
|
|
2978
|
+
`expected=${entry.sessionSlug}, actual=${sideDialog.sessionSlug})`);
|
|
2850
2979
|
}
|
|
2851
|
-
if (
|
|
2852
|
-
throw new Error(`
|
|
2853
|
-
`(rootId=${
|
|
2854
|
-
`expected=${entry.agentId}, actual=${
|
|
2980
|
+
if (sideDialog.agentId !== entry.agentId) {
|
|
2981
|
+
throw new Error(`SideDialog registry invariant violation: agentId mismatch ` +
|
|
2982
|
+
`(rootId=${mainDialog.id.rootId}, selfId=${entry.sideDialogId.selfId}, ` +
|
|
2983
|
+
`expected=${entry.agentId}, actual=${sideDialog.agentId})`);
|
|
2855
2984
|
}
|
|
2856
|
-
|
|
2985
|
+
mainDialog.registerSideDialog(sideDialog);
|
|
2857
2986
|
}
|
|
2858
2987
|
if (prunedDeadRegistryEntries) {
|
|
2859
|
-
await
|
|
2988
|
+
await mainDialog.saveSideDialogRegistry();
|
|
2860
2989
|
}
|
|
2861
2990
|
}
|
|
2862
2991
|
/**
|
|
@@ -3514,8 +3643,8 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
3514
3643
|
callId: event.callId,
|
|
3515
3644
|
status: event.status,
|
|
3516
3645
|
content: event.content,
|
|
3517
|
-
...(event.
|
|
3518
|
-
...(event.
|
|
3646
|
+
...(event.callSiteCourse !== undefined ? { callSiteCourse: event.callSiteCourse } : {}),
|
|
3647
|
+
...(event.callSiteGenseq !== undefined ? { callSiteGenseq: event.callSiteGenseq } : {}),
|
|
3519
3648
|
responder: event.responder,
|
|
3520
3649
|
...(event.route ? { route: event.route } : {}),
|
|
3521
3650
|
dialog: {
|
|
@@ -3546,68 +3675,64 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
3546
3675
|
}
|
|
3547
3676
|
break;
|
|
3548
3677
|
}
|
|
3549
|
-
case '
|
|
3550
|
-
// Handle
|
|
3551
|
-
const persistedStatus = assertPersistableDialogStatus(status, 'sendEventDirectlyToWebSocket:
|
|
3552
|
-
const
|
|
3553
|
-
const metadata = await DialogPersistence.loadDialogMetadata(
|
|
3554
|
-
if (!metadata || !
|
|
3555
|
-
throw new Error(`
|
|
3678
|
+
case 'sideDialog_request_record': {
|
|
3679
|
+
// Handle sideDialog creation requests
|
|
3680
|
+
const persistedStatus = assertPersistableDialogStatus(status, 'sendEventDirectlyToWebSocket:sideDialog_request_record');
|
|
3681
|
+
const sideDialogId = new dialog_1.DialogID(event.sideDialogId, dialog.id.rootId);
|
|
3682
|
+
const metadata = await DialogPersistence.loadDialogMetadata(sideDialogId, status);
|
|
3683
|
+
if (!metadata || !isSideDialogMetadataFile(metadata)) {
|
|
3684
|
+
throw new Error(`sideDialog_created_evt replay invariant violation: metadata missing for ${sideDialogId.valueOf()} in ${status}`);
|
|
3556
3685
|
}
|
|
3557
|
-
const
|
|
3558
|
-
const
|
|
3559
|
-
const
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
: typeof subMeta.supdialogId === 'string' && subMeta.supdialogId.trim() !== ''
|
|
3563
|
-
? subMeta.supdialogId
|
|
3564
|
-
: dialog.id.selfId;
|
|
3565
|
-
const callName = subMeta.assignmentFromSup?.callName;
|
|
3686
|
+
const sideMeta = metadata;
|
|
3687
|
+
const sideLatest = await DialogPersistence.loadDialogLatest(sideDialogId, status);
|
|
3688
|
+
const assignmentFromAsker = await DialogPersistence.loadSideDialogAssignmentFromAsker(sideDialogId, status);
|
|
3689
|
+
const derivedAskerDialogId = assignmentFromAsker.askerDialogId.trim();
|
|
3690
|
+
const callName = assignmentFromAsker.callName;
|
|
3566
3691
|
if (callName !== 'tellask' &&
|
|
3567
3692
|
callName !== 'tellaskSessionless' &&
|
|
3568
3693
|
callName !== 'freshBootsReasoning') {
|
|
3569
|
-
throw new Error(`
|
|
3694
|
+
throw new Error(`sideDialog_created_evt replay invariant violation: missing assignment callName for ${sideDialogId.valueOf()} in ${status}`);
|
|
3570
3695
|
}
|
|
3571
|
-
const
|
|
3572
|
-
const
|
|
3573
|
-
type: '
|
|
3696
|
+
const rootSideDialogCount = await DialogPersistence.countAllSideDialogsUnderRoot(new dialog_1.DialogID(sideDialogId.rootId), persistedStatus);
|
|
3697
|
+
const sideDialogCreatedEvent = {
|
|
3698
|
+
type: 'sideDialog_created_evt',
|
|
3574
3699
|
course,
|
|
3575
3700
|
dialog: {
|
|
3576
3701
|
// Add dialog field for proper event routing
|
|
3577
|
-
selfId:
|
|
3578
|
-
rootId:
|
|
3702
|
+
selfId: sideDialogId.selfId,
|
|
3703
|
+
rootId: sideDialogId.rootId,
|
|
3579
3704
|
},
|
|
3580
3705
|
parentDialog: {
|
|
3581
3706
|
selfId: dialog.id.selfId,
|
|
3582
3707
|
rootId: dialog.id.rootId,
|
|
3583
3708
|
},
|
|
3584
|
-
|
|
3585
|
-
selfId:
|
|
3586
|
-
rootId:
|
|
3709
|
+
sideDialog: {
|
|
3710
|
+
selfId: sideDialogId.selfId,
|
|
3711
|
+
rootId: sideDialogId.rootId,
|
|
3587
3712
|
},
|
|
3588
|
-
targetAgentId:
|
|
3713
|
+
targetAgentId: sideMeta.agentId,
|
|
3589
3714
|
callName,
|
|
3590
3715
|
mentionList: event.mentionList,
|
|
3591
3716
|
tellaskContent: event.tellaskContent,
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
selfId:
|
|
3595
|
-
rootId:
|
|
3596
|
-
|
|
3597
|
-
agentId:
|
|
3598
|
-
taskDocPath:
|
|
3717
|
+
rootSideDialogCount,
|
|
3718
|
+
sideDialogNode: {
|
|
3719
|
+
selfId: sideMeta.id,
|
|
3720
|
+
rootId: sideDialogId.rootId,
|
|
3721
|
+
askerDialogId: derivedAskerDialogId,
|
|
3722
|
+
agentId: sideMeta.agentId,
|
|
3723
|
+
taskDocPath: sideMeta.taskDocPath,
|
|
3599
3724
|
status: persistedStatus,
|
|
3600
|
-
currentCourse:
|
|
3601
|
-
createdAt:
|
|
3602
|
-
lastModified:
|
|
3603
|
-
displayState:
|
|
3604
|
-
sessionSlug:
|
|
3605
|
-
|
|
3725
|
+
currentCourse: sideLatest?.currentCourse || 1,
|
|
3726
|
+
createdAt: sideMeta.createdAt,
|
|
3727
|
+
lastModified: sideLatest?.lastModified || sideMeta.createdAt,
|
|
3728
|
+
displayState: sideLatest?.displayState,
|
|
3729
|
+
sessionSlug: sideMeta.sessionSlug,
|
|
3730
|
+
assignmentFromAsker,
|
|
3606
3731
|
},
|
|
3607
3732
|
timestamp: event.ts,
|
|
3608
3733
|
};
|
|
3609
3734
|
if (ws.readyState === 1) {
|
|
3610
|
-
ws.send(JSON.stringify(
|
|
3735
|
+
ws.send(JSON.stringify(sideDialogCreatedEvent));
|
|
3611
3736
|
}
|
|
3612
3737
|
break;
|
|
3613
3738
|
}
|
|
@@ -3635,8 +3760,8 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
3635
3760
|
callId: event.callId,
|
|
3636
3761
|
assignmentCourse: event.assignmentCourse,
|
|
3637
3762
|
assignmentGenseq: event.assignmentGenseq,
|
|
3638
|
-
|
|
3639
|
-
|
|
3763
|
+
askerDialogId: event.askerDialogId,
|
|
3764
|
+
askerCourse: event.askerCourse,
|
|
3640
3765
|
dialog: {
|
|
3641
3766
|
selfId: dialog.id.selfId,
|
|
3642
3767
|
rootId: dialog.id.rootId,
|
|
@@ -3648,19 +3773,19 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
3648
3773
|
}
|
|
3649
3774
|
break;
|
|
3650
3775
|
}
|
|
3651
|
-
case '
|
|
3776
|
+
case 'sideDialog_created_record':
|
|
3652
3777
|
case 'reminders_reconciled_record':
|
|
3653
3778
|
case 'questions4human_reconciled_record':
|
|
3654
|
-
case '
|
|
3655
|
-
case '
|
|
3656
|
-
case '
|
|
3779
|
+
case 'pending_sideDialogs_reconciled_record':
|
|
3780
|
+
case 'sideDialog_registry_reconciled_record':
|
|
3781
|
+
case 'sideDialog_responses_reconciled_record':
|
|
3657
3782
|
break;
|
|
3658
3783
|
case 'tellask_carryover_record': {
|
|
3659
3784
|
const base = {
|
|
3660
3785
|
type: 'tellask_carryover_evt',
|
|
3661
3786
|
course,
|
|
3662
3787
|
genseq: event.genseq,
|
|
3663
|
-
|
|
3788
|
+
callSiteCourse: event.callSiteCourse,
|
|
3664
3789
|
carryoverCourse: event.carryoverCourse,
|
|
3665
3790
|
responderId: event.responderId,
|
|
3666
3791
|
tellaskContent: event.tellaskContent,
|
|
@@ -3727,7 +3852,7 @@ class DiskFileDialogStore extends dialog_1.DialogStore {
|
|
|
3727
3852
|
exports.DiskFileDialogStore = DiskFileDialogStore;
|
|
3728
3853
|
class DialogWriteBackCanceledError extends Error {
|
|
3729
3854
|
constructor(token, phase) {
|
|
3730
|
-
super(`Dialog writeback canceled for ${token.
|
|
3855
|
+
super(`Dialog writeback canceled for ${token.mainDialogId} (${token.status}) during ${phase}`);
|
|
3731
3856
|
this.name = 'DialogWriteBackCanceledError';
|
|
3732
3857
|
}
|
|
3733
3858
|
}
|
|
@@ -3765,12 +3890,12 @@ class DialogPersistence {
|
|
|
3765
3890
|
static getCourseAppendMutexKey(dialogId, course, status) {
|
|
3766
3891
|
return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|course:${course}`;
|
|
3767
3892
|
}
|
|
3768
|
-
static
|
|
3769
|
-
const existing = this.
|
|
3893
|
+
static getPendingSideDialogsWriteBackMutex(key) {
|
|
3894
|
+
const existing = this.pendingSideDialogsWriteBackMutexes.get(key);
|
|
3770
3895
|
if (existing)
|
|
3771
3896
|
return existing;
|
|
3772
3897
|
const created = new async_fifo_mutex_1.AsyncFifoMutex();
|
|
3773
|
-
this.
|
|
3898
|
+
this.pendingSideDialogsWriteBackMutexes.set(key, created);
|
|
3774
3899
|
return created;
|
|
3775
3900
|
}
|
|
3776
3901
|
static getLatestWriteBackKey(dialogId, status) {
|
|
@@ -3781,27 +3906,27 @@ class DialogPersistence {
|
|
|
3781
3906
|
// Include dialogs root dir to avoid cross-test/process.cwd collisions.
|
|
3782
3907
|
return `${this.getDialogsRootDir()}|${status}|${dialogId.valueOf()}|q4h`;
|
|
3783
3908
|
}
|
|
3784
|
-
static
|
|
3785
|
-
return `${this.getDialogsRootDir()}|${status}|${
|
|
3909
|
+
static getPendingSideDialogsWriteBackKey(mainDialogId, status) {
|
|
3910
|
+
return `${this.getDialogsRootDir()}|${status}|${mainDialogId.valueOf()}|pending-sideDialogs`;
|
|
3786
3911
|
}
|
|
3787
|
-
static
|
|
3788
|
-
return `${this.getDialogsRootDir()}|${status}|${
|
|
3912
|
+
static getMainDialogWriteBackCancelScopeKey(mainDialogId, status) {
|
|
3913
|
+
return `${this.getDialogsRootDir()}|${status}|${mainDialogId.selfId}|writeback-cancel`;
|
|
3789
3914
|
}
|
|
3790
|
-
static
|
|
3791
|
-
const
|
|
3792
|
-
const scopeKey = this.
|
|
3915
|
+
static createMainDialogWriteBackCancellationToken(dialogId, status) {
|
|
3916
|
+
const mainDialogId = dialogId.rootId === dialogId.selfId ? dialogId : new dialog_1.DialogID(dialogId.rootId);
|
|
3917
|
+
const scopeKey = this.getMainDialogWriteBackCancelScopeKey(mainDialogId, status);
|
|
3793
3918
|
return {
|
|
3794
3919
|
scopeKey,
|
|
3795
|
-
generation: this.
|
|
3796
|
-
|
|
3920
|
+
generation: this.mainDialogWriteBackCancelGenerations.get(scopeKey) ?? 0,
|
|
3921
|
+
mainDialogId: mainDialogId.selfId,
|
|
3797
3922
|
status,
|
|
3798
3923
|
};
|
|
3799
3924
|
}
|
|
3800
|
-
static
|
|
3801
|
-
if (this.
|
|
3925
|
+
static assertMainDialogWriteBackNotCanceled(token, phase) {
|
|
3926
|
+
if (this.quarantinedMainDialogScopes.has(token.scopeKey)) {
|
|
3802
3927
|
throw new DialogWriteBackCanceledError(token, phase);
|
|
3803
3928
|
}
|
|
3804
|
-
const currentGeneration = this.
|
|
3929
|
+
const currentGeneration = this.mainDialogWriteBackCancelGenerations.get(token.scopeKey) ?? 0;
|
|
3805
3930
|
if (currentGeneration !== token.generation) {
|
|
3806
3931
|
throw new DialogWriteBackCanceledError(token, phase);
|
|
3807
3932
|
}
|
|
@@ -3811,45 +3936,51 @@ class DialogPersistence {
|
|
|
3811
3936
|
throw error;
|
|
3812
3937
|
}
|
|
3813
3938
|
if (cancellationToken) {
|
|
3814
|
-
this.
|
|
3939
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, phase);
|
|
3815
3940
|
if (!(await this.pathExists(dialogPath))) {
|
|
3816
3941
|
throw new DialogWriteBackCanceledError(cancellationToken, `${phase}:dialog-path-missing`);
|
|
3817
3942
|
}
|
|
3818
3943
|
}
|
|
3819
3944
|
throw error;
|
|
3820
3945
|
}
|
|
3821
|
-
static
|
|
3822
|
-
const scopeKey = this.
|
|
3823
|
-
const nextGeneration = (this.
|
|
3824
|
-
this.
|
|
3825
|
-
this.
|
|
3946
|
+
static cancelMainDialogWriteBacks(mainDialogId, status) {
|
|
3947
|
+
const scopeKey = this.getMainDialogWriteBackCancelScopeKey(mainDialogId, status);
|
|
3948
|
+
const nextGeneration = (this.mainDialogWriteBackCancelGenerations.get(scopeKey) ?? 0) + 1;
|
|
3949
|
+
this.mainDialogWriteBackCancelGenerations.set(scopeKey, nextGeneration);
|
|
3950
|
+
this.clearWriteBackEntriesForMainDialog(mainDialogId, status);
|
|
3826
3951
|
}
|
|
3827
3952
|
static getDialogMetadataPath(dialogId, status) {
|
|
3828
3953
|
const dialogPath = dialogId.rootId === dialogId.selfId
|
|
3829
|
-
? this.
|
|
3830
|
-
: this.
|
|
3954
|
+
? this.getMainDialogPath(dialogId, status)
|
|
3955
|
+
: this.getSideDialogPath(dialogId, status);
|
|
3831
3956
|
return path.join(dialogPath, 'dialog.yaml');
|
|
3832
3957
|
}
|
|
3958
|
+
static getDialogAskerStackPath(dialogId, status) {
|
|
3959
|
+
const dialogPath = dialogId.rootId === dialogId.selfId
|
|
3960
|
+
? this.getMainDialogPath(dialogId, status)
|
|
3961
|
+
: this.getSideDialogPath(dialogId, status);
|
|
3962
|
+
return path.join(dialogPath, 'asker-stack.jsonl');
|
|
3963
|
+
}
|
|
3833
3964
|
static async assertDialogMetadataExistsForAppend(dialogId, status, cancellationToken, phase) {
|
|
3834
|
-
this.
|
|
3965
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, phase);
|
|
3835
3966
|
const metadataPath = this.getDialogMetadataPath(dialogId, status);
|
|
3836
3967
|
try {
|
|
3837
3968
|
await fs.promises.access(metadataPath);
|
|
3838
3969
|
}
|
|
3839
3970
|
catch (error) {
|
|
3840
3971
|
if (getErrorCode(error) === 'ENOENT') {
|
|
3841
|
-
this.
|
|
3972
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, `${phase}:metadata-missing`);
|
|
3842
3973
|
throw new Error(`Refusing to append events for dialog ${dialogId.valueOf()}: missing dialog metadata at ${metadataPath}`);
|
|
3843
3974
|
}
|
|
3844
3975
|
throw error;
|
|
3845
3976
|
}
|
|
3846
3977
|
}
|
|
3847
3978
|
static async cleanupCanceledAppendPlaceholder(dialogId, status) {
|
|
3848
|
-
const
|
|
3849
|
-
const rootPath = this.
|
|
3979
|
+
const mainDialogId = dialogId.rootId === dialogId.selfId ? dialogId : new dialog_1.DialogID(dialogId.rootId);
|
|
3980
|
+
const rootPath = this.getMainDialogPath(mainDialogId, status);
|
|
3850
3981
|
await fs.promises.rm(rootPath, { recursive: true, force: true });
|
|
3851
3982
|
}
|
|
3852
|
-
static
|
|
3983
|
+
static clonePendingSideDialogRecords(records) {
|
|
3853
3984
|
return records.map((record) => ({
|
|
3854
3985
|
...record,
|
|
3855
3986
|
mentionList: record.mentionList ? [...record.mentionList] : undefined,
|
|
@@ -3866,7 +3997,7 @@ class DialogPersistence {
|
|
|
3866
3997
|
...entry,
|
|
3867
3998
|
}));
|
|
3868
3999
|
}
|
|
3869
|
-
static
|
|
4000
|
+
static cloneSideDialogResponses(responses) {
|
|
3870
4001
|
return responses.map((response) => ({
|
|
3871
4002
|
...response,
|
|
3872
4003
|
mentionList: response.mentionList ? [...response.mentionList] : undefined,
|
|
@@ -3890,30 +4021,30 @@ class DialogPersistence {
|
|
|
3890
4021
|
};
|
|
3891
4022
|
await this.appendEvent(dialogId, resolveTargetCourseFromWriteTarget(writeTarget), record, status);
|
|
3892
4023
|
}
|
|
3893
|
-
static async
|
|
4024
|
+
static async appendPendingSideDialogsReconciledRecord(dialogId, pendingSideDialogs, writeTarget, status) {
|
|
3894
4025
|
const record = {
|
|
3895
4026
|
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3896
|
-
type: '
|
|
4027
|
+
type: 'pending_sideDialogs_reconciled_record',
|
|
3897
4028
|
...cloneRootGenerationAnchor(writeTarget.rootAnchor),
|
|
3898
|
-
|
|
4029
|
+
pendingSideDialogs: this.clonePendingSideDialogRecords(pendingSideDialogs),
|
|
3899
4030
|
};
|
|
3900
4031
|
await this.appendEvent(dialogId, resolveTargetCourseFromWriteTarget(writeTarget), record, status);
|
|
3901
4032
|
}
|
|
3902
|
-
static async
|
|
4033
|
+
static async appendSideDialogRegistryReconciledRecord(dialogId, entries, writeTarget, status) {
|
|
3903
4034
|
const record = {
|
|
3904
4035
|
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3905
|
-
type: '
|
|
4036
|
+
type: 'sideDialog_registry_reconciled_record',
|
|
3906
4037
|
...cloneRootGenerationAnchor(writeTarget.rootAnchor),
|
|
3907
4038
|
entries: this.cloneRegistryEntries(entries),
|
|
3908
4039
|
};
|
|
3909
4040
|
await this.appendEvent(dialogId, resolveTargetCourseFromWriteTarget(writeTarget), record, status);
|
|
3910
4041
|
}
|
|
3911
|
-
static async
|
|
4042
|
+
static async appendSideDialogResponsesReconciledRecord(dialogId, responses, writeTarget, status) {
|
|
3912
4043
|
const record = {
|
|
3913
4044
|
ts: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
3914
|
-
type: '
|
|
4045
|
+
type: 'sideDialog_responses_reconciled_record',
|
|
3915
4046
|
...cloneRootGenerationAnchor(writeTarget.rootAnchor),
|
|
3916
|
-
responses: this.
|
|
4047
|
+
responses: this.cloneSideDialogResponses(responses),
|
|
3917
4048
|
};
|
|
3918
4049
|
await this.appendEvent(dialogId, resolveTargetCourseFromWriteTarget(writeTarget), record, status);
|
|
3919
4050
|
}
|
|
@@ -3926,42 +4057,42 @@ class DialogPersistence {
|
|
|
3926
4057
|
/**
|
|
3927
4058
|
* Get the full path for a dialog directory
|
|
3928
4059
|
*/
|
|
3929
|
-
static
|
|
4060
|
+
static getMainDialogPath(dialogId, status = 'running') {
|
|
3930
4061
|
if (dialogId.rootId !== dialogId.selfId) {
|
|
3931
|
-
throw new Error('Expected
|
|
4062
|
+
throw new Error('Expected main dialog id');
|
|
3932
4063
|
}
|
|
3933
|
-
const statusDir = getPersistableStatusDirName(status, 'DialogPersistence.
|
|
4064
|
+
const statusDir = getPersistableStatusDirName(status, 'DialogPersistence.getMainDialogPath');
|
|
3934
4065
|
return path.join(this.getDialogsRootDir(), statusDir, dialogId.selfId);
|
|
3935
4066
|
}
|
|
3936
4067
|
/**
|
|
3937
|
-
* Get the events/state directory for a dialog (composite ID for
|
|
4068
|
+
* Get the events/state directory for a dialog (composite ID for sideDialogs)
|
|
3938
4069
|
*/
|
|
3939
4070
|
static getDialogEventsPath(dialogId, status = 'running') {
|
|
3940
|
-
//
|
|
3941
|
-
//
|
|
4071
|
+
// Main dialogs store events under their own directory.
|
|
4072
|
+
// SideDialogs store events under the root's sideDialogs/<self> directory.
|
|
3942
4073
|
if (dialogId.rootId === dialogId.selfId) {
|
|
3943
|
-
return this.
|
|
4074
|
+
return this.getMainDialogPath(dialogId, status);
|
|
3944
4075
|
}
|
|
3945
|
-
return this.
|
|
4076
|
+
return this.getSideDialogPath(dialogId, status);
|
|
3946
4077
|
}
|
|
3947
4078
|
/**
|
|
3948
|
-
* Get the path for a
|
|
4079
|
+
* Get the path for a sideDialog within an askerDialog
|
|
3949
4080
|
*/
|
|
3950
|
-
static
|
|
4081
|
+
static getSideDialogPath(dialogId, status = 'running') {
|
|
3951
4082
|
if (dialogId.rootId === dialogId.selfId) {
|
|
3952
|
-
throw new Error('Expected
|
|
4083
|
+
throw new Error('Expected sideDialog id (self differs from root)');
|
|
3953
4084
|
}
|
|
3954
|
-
const rootPath = this.
|
|
3955
|
-
return path.join(rootPath, this.
|
|
4085
|
+
const rootPath = this.getMainDialogPath(new dialog_1.DialogID(dialogId.rootId), status);
|
|
4086
|
+
return path.join(rootPath, this.SIDE_DIALOGS_DIR, dialogId.selfId);
|
|
3956
4087
|
}
|
|
3957
|
-
static
|
|
4088
|
+
static getMalformedMainDialogPath(dialogId, status) {
|
|
3958
4089
|
if (dialogId.rootId !== dialogId.selfId) {
|
|
3959
|
-
throw new Error('Expected
|
|
4090
|
+
throw new Error('Expected main dialog id');
|
|
3960
4091
|
}
|
|
3961
4092
|
void status;
|
|
3962
4093
|
return path.join(this.getDialogsRootDir(), this.MALFORMED_DIR, dialogId.selfId);
|
|
3963
4094
|
}
|
|
3964
|
-
static
|
|
4095
|
+
static inferMainDialogIdFromMetadataRelativeDir(relativeDir) {
|
|
3965
4096
|
const dir = relativeDir.trim();
|
|
3966
4097
|
if (dir === '' || dir === '.' || dir === path.sep) {
|
|
3967
4098
|
return null;
|
|
@@ -3970,8 +4101,8 @@ class DialogPersistence {
|
|
|
3970
4101
|
if (segments.length === 0) {
|
|
3971
4102
|
return null;
|
|
3972
4103
|
}
|
|
3973
|
-
const
|
|
3974
|
-
const rootSegments =
|
|
4104
|
+
const sideDialogsIndex = segments.indexOf(this.SIDE_DIALOGS_DIR);
|
|
4105
|
+
const rootSegments = sideDialogsIndex === -1 ? segments : segments.slice(0, sideDialogsIndex);
|
|
3975
4106
|
if (rootSegments.length === 0) {
|
|
3976
4107
|
return null;
|
|
3977
4108
|
}
|
|
@@ -3986,19 +4117,19 @@ class DialogPersistence {
|
|
|
3986
4117
|
if (segments.length === 0) {
|
|
3987
4118
|
return null;
|
|
3988
4119
|
}
|
|
3989
|
-
const
|
|
3990
|
-
if (
|
|
4120
|
+
const sideDialogsIndex = segments.indexOf(this.SIDE_DIALOGS_DIR);
|
|
4121
|
+
if (sideDialogsIndex === -1) {
|
|
3991
4122
|
return segments.join('/');
|
|
3992
4123
|
}
|
|
3993
|
-
const
|
|
3994
|
-
if (
|
|
4124
|
+
const sideDialogSegments = segments.slice(sideDialogsIndex + 1);
|
|
4125
|
+
if (sideDialogSegments.length === 0) {
|
|
3995
4126
|
return null;
|
|
3996
4127
|
}
|
|
3997
|
-
return
|
|
4128
|
+
return sideDialogSegments.join('/');
|
|
3998
4129
|
}
|
|
3999
|
-
static async
|
|
4000
|
-
const
|
|
4001
|
-
const
|
|
4130
|
+
static async listSideDialogIdsUnderRoot(mainDialogId, status) {
|
|
4131
|
+
const sideDialogsPath = path.join(this.getMainDialogPath(mainDialogId, status), this.SIDE_DIALOGS_DIR);
|
|
4132
|
+
const sideDialogIds = new Set();
|
|
4002
4133
|
const visit = async (dirPath, relativePath = '') => {
|
|
4003
4134
|
let entries;
|
|
4004
4135
|
try {
|
|
@@ -4019,11 +4150,11 @@ class DialogPersistence {
|
|
|
4019
4150
|
const dialogYamlPath = path.join(fullPath, 'dialog.yaml');
|
|
4020
4151
|
try {
|
|
4021
4152
|
await fs.promises.access(dialogYamlPath);
|
|
4022
|
-
const inferredId = this.inferExpectedDialogIdFromMetadataRelativeDir(path.join(this.
|
|
4153
|
+
const inferredId = this.inferExpectedDialogIdFromMetadataRelativeDir(path.join(this.SIDE_DIALOGS_DIR, entryRelativePath));
|
|
4023
4154
|
if (!inferredId) {
|
|
4024
|
-
throw new Error(`Failed to infer
|
|
4155
|
+
throw new Error(`Failed to infer sideDialog id from relative path ${entryRelativePath} under root ${mainDialogId.selfId}`);
|
|
4025
4156
|
}
|
|
4026
|
-
|
|
4157
|
+
sideDialogIds.add(inferredId);
|
|
4027
4158
|
continue;
|
|
4028
4159
|
}
|
|
4029
4160
|
catch (error) {
|
|
@@ -4034,8 +4165,8 @@ class DialogPersistence {
|
|
|
4034
4165
|
await visit(fullPath, entryRelativePath);
|
|
4035
4166
|
}
|
|
4036
4167
|
};
|
|
4037
|
-
await visit(
|
|
4038
|
-
return [...
|
|
4168
|
+
await visit(sideDialogsPath);
|
|
4169
|
+
return [...sideDialogIds];
|
|
4039
4170
|
}
|
|
4040
4171
|
static async pathExists(targetPath) {
|
|
4041
4172
|
try {
|
|
@@ -4058,11 +4189,11 @@ class DialogPersistence {
|
|
|
4058
4189
|
this.latestWriteBack.delete(key);
|
|
4059
4190
|
this.latestWriteBackMutexes.delete(key);
|
|
4060
4191
|
}
|
|
4061
|
-
static
|
|
4062
|
-
const basePrefix = `${this.getDialogsRootDir()}|${status}|${
|
|
4063
|
-
const
|
|
4192
|
+
static clearWriteBackEntriesForMainDialog(mainDialogId, status) {
|
|
4193
|
+
const basePrefix = `${this.getDialogsRootDir()}|${status}|${mainDialogId.selfId}`;
|
|
4194
|
+
const matchesMainDialogKey = (key) => key === basePrefix || key.startsWith(`${basePrefix}#`) || key.startsWith(`${basePrefix}|`);
|
|
4064
4195
|
for (const [key, entry] of this.latestWriteBack.entries()) {
|
|
4065
|
-
if (!
|
|
4196
|
+
if (!matchesMainDialogKey(key))
|
|
4066
4197
|
continue;
|
|
4067
4198
|
if (entry.kind === 'scheduled') {
|
|
4068
4199
|
clearTimeout(entry.timer);
|
|
@@ -4070,12 +4201,12 @@ class DialogPersistence {
|
|
|
4070
4201
|
this.latestWriteBack.delete(key);
|
|
4071
4202
|
}
|
|
4072
4203
|
for (const key of this.latestWriteBackMutexes.keys()) {
|
|
4073
|
-
if (
|
|
4204
|
+
if (matchesMainDialogKey(key)) {
|
|
4074
4205
|
this.latestWriteBackMutexes.delete(key);
|
|
4075
4206
|
}
|
|
4076
4207
|
}
|
|
4077
4208
|
for (const [key, entry] of this.q4hWriteBack.entries()) {
|
|
4078
|
-
if (!
|
|
4209
|
+
if (!matchesMainDialogKey(key))
|
|
4079
4210
|
continue;
|
|
4080
4211
|
if (entry.kind === 'scheduled') {
|
|
4081
4212
|
clearTimeout(entry.timer);
|
|
@@ -4083,72 +4214,72 @@ class DialogPersistence {
|
|
|
4083
4214
|
this.q4hWriteBack.delete(key);
|
|
4084
4215
|
}
|
|
4085
4216
|
for (const key of this.q4hWriteBackMutexes.keys()) {
|
|
4086
|
-
if (
|
|
4217
|
+
if (matchesMainDialogKey(key)) {
|
|
4087
4218
|
this.q4hWriteBackMutexes.delete(key);
|
|
4088
4219
|
}
|
|
4089
4220
|
}
|
|
4090
|
-
for (const [key, entry] of this.
|
|
4091
|
-
if (!
|
|
4221
|
+
for (const [key, entry] of this.pendingSideDialogsWriteBack.entries()) {
|
|
4222
|
+
if (!matchesMainDialogKey(key))
|
|
4092
4223
|
continue;
|
|
4093
4224
|
if (entry.kind === 'scheduled') {
|
|
4094
4225
|
clearTimeout(entry.timer);
|
|
4095
4226
|
}
|
|
4096
|
-
this.
|
|
4227
|
+
this.pendingSideDialogsWriteBack.delete(key);
|
|
4097
4228
|
}
|
|
4098
|
-
for (const key of this.
|
|
4099
|
-
if (
|
|
4100
|
-
this.
|
|
4229
|
+
for (const key of this.pendingSideDialogsWriteBackMutexes.keys()) {
|
|
4230
|
+
if (matchesMainDialogKey(key)) {
|
|
4231
|
+
this.pendingSideDialogsWriteBackMutexes.delete(key);
|
|
4101
4232
|
}
|
|
4102
4233
|
}
|
|
4103
4234
|
for (const key of this.courseAppendMutexes.keys()) {
|
|
4104
|
-
if (
|
|
4235
|
+
if (matchesMainDialogKey(key)) {
|
|
4105
4236
|
this.courseAppendMutexes.delete(key);
|
|
4106
4237
|
}
|
|
4107
4238
|
}
|
|
4108
4239
|
}
|
|
4109
4240
|
static async quarantineMalformedDialog(dialogId, status, reason, error) {
|
|
4110
|
-
const
|
|
4111
|
-
const quarantineKey = `${status}|${
|
|
4112
|
-
if (
|
|
4241
|
+
const mainDialogId = dialogId.rootId === dialogId.selfId ? dialogId : new dialog_1.DialogID(dialogId.rootId);
|
|
4242
|
+
const quarantineKey = `${status}|${mainDialogId.selfId}`;
|
|
4243
|
+
if (quarantiningMainDialogs.has(quarantineKey)) {
|
|
4113
4244
|
return;
|
|
4114
4245
|
}
|
|
4115
|
-
|
|
4246
|
+
quarantiningMainDialogs.add(quarantineKey);
|
|
4116
4247
|
let quarantined = false;
|
|
4117
4248
|
try {
|
|
4118
4249
|
await prepareDialogQuarantineHook?.({
|
|
4119
4250
|
dialogId,
|
|
4120
|
-
|
|
4251
|
+
mainDialogId,
|
|
4121
4252
|
status,
|
|
4122
4253
|
reason,
|
|
4123
4254
|
error,
|
|
4124
4255
|
});
|
|
4125
|
-
this.
|
|
4126
|
-
this.
|
|
4127
|
-
const sourcePath = this.
|
|
4256
|
+
this.quarantinedMainDialogScopes.add(this.getMainDialogWriteBackCancelScopeKey(mainDialogId, status));
|
|
4257
|
+
this.cancelMainDialogWriteBacks(mainDialogId, status);
|
|
4258
|
+
const sourcePath = this.getMainDialogPath(mainDialogId, status);
|
|
4128
4259
|
if (!(await this.pathExists(sourcePath))) {
|
|
4129
4260
|
return;
|
|
4130
4261
|
}
|
|
4131
|
-
let destinationPath = this.
|
|
4262
|
+
let destinationPath = this.getMalformedMainDialogPath(mainDialogId, status);
|
|
4132
4263
|
if (await this.pathExists(destinationPath)) {
|
|
4133
|
-
destinationPath = path.join(this.getDialogsRootDir(), this.MALFORMED_DIR, `${
|
|
4264
|
+
destinationPath = path.join(this.getDialogsRootDir(), this.MALFORMED_DIR, `${mainDialogId.selfId}__${(0, node_crypto_1.randomUUID)()}`);
|
|
4134
4265
|
}
|
|
4135
4266
|
await fs.promises.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
4136
4267
|
await fs.promises.rename(sourcePath, destinationPath);
|
|
4137
4268
|
quarantined = true;
|
|
4138
|
-
log_1.log.warn(`Quarantined malformed dialog ${
|
|
4269
|
+
log_1.log.warn(`Quarantined malformed dialog ${mainDialogId.selfId}`, undefined, {
|
|
4139
4270
|
status,
|
|
4140
4271
|
reason,
|
|
4141
4272
|
sourcePath,
|
|
4142
4273
|
destinationPath,
|
|
4143
4274
|
errorMessage: error.message,
|
|
4144
4275
|
dialogId: dialogId.valueOf(),
|
|
4145
|
-
|
|
4276
|
+
mainDialogId: mainDialogId.valueOf(),
|
|
4146
4277
|
});
|
|
4147
4278
|
dialogsQuarantinedBroadcaster?.({
|
|
4148
4279
|
type: 'dialogs_quarantined',
|
|
4149
4280
|
status: 'quarantining',
|
|
4150
4281
|
fromStatus: assertPersistableDialogStatus(status, 'DialogPersistence.quarantineMalformedDialog(fromStatus)'),
|
|
4151
|
-
rootId:
|
|
4282
|
+
rootId: mainDialogId.selfId,
|
|
4152
4283
|
dialogId: dialogId.selfId,
|
|
4153
4284
|
reason,
|
|
4154
4285
|
timestamp: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
@@ -4158,7 +4289,7 @@ class DialogPersistence {
|
|
|
4158
4289
|
try {
|
|
4159
4290
|
await finalizeDialogQuarantineHook?.({
|
|
4160
4291
|
dialogId,
|
|
4161
|
-
|
|
4292
|
+
mainDialogId,
|
|
4162
4293
|
status,
|
|
4163
4294
|
reason,
|
|
4164
4295
|
error,
|
|
@@ -4166,7 +4297,7 @@ class DialogPersistence {
|
|
|
4166
4297
|
});
|
|
4167
4298
|
}
|
|
4168
4299
|
finally {
|
|
4169
|
-
|
|
4300
|
+
quarantiningMainDialogs.delete(quarantineKey);
|
|
4170
4301
|
}
|
|
4171
4302
|
}
|
|
4172
4303
|
}
|
|
@@ -4197,8 +4328,8 @@ class DialogPersistence {
|
|
|
4197
4328
|
/**
|
|
4198
4329
|
* Ensure dialog directory structure exists
|
|
4199
4330
|
*/
|
|
4200
|
-
static async
|
|
4201
|
-
const dialogPath = this.
|
|
4331
|
+
static async ensureMainDialogDirectory(dialogId, status = 'running') {
|
|
4332
|
+
const dialogPath = this.getMainDialogPath(dialogId, status);
|
|
4202
4333
|
try {
|
|
4203
4334
|
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
4204
4335
|
return dialogPath;
|
|
@@ -4209,16 +4340,16 @@ class DialogPersistence {
|
|
|
4209
4340
|
}
|
|
4210
4341
|
}
|
|
4211
4342
|
/**
|
|
4212
|
-
* Ensure
|
|
4343
|
+
* Ensure sideDialog directory structure exists
|
|
4213
4344
|
*/
|
|
4214
|
-
static async
|
|
4215
|
-
const
|
|
4345
|
+
static async ensureSideDialogDirectory(dialogId, status = 'running') {
|
|
4346
|
+
const sideDialogPath = this.getSideDialogPath(dialogId, status);
|
|
4216
4347
|
try {
|
|
4217
|
-
await fs.promises.mkdir(
|
|
4218
|
-
return
|
|
4348
|
+
await fs.promises.mkdir(sideDialogPath, { recursive: true });
|
|
4349
|
+
return sideDialogPath;
|
|
4219
4350
|
}
|
|
4220
4351
|
catch (error) {
|
|
4221
|
-
log_1.log.error(`Failed to create
|
|
4352
|
+
log_1.log.error(`Failed to create sideDialog directory ${sideDialogPath}:`, error);
|
|
4222
4353
|
throw error;
|
|
4223
4354
|
}
|
|
4224
4355
|
}
|
|
@@ -4227,8 +4358,8 @@ class DialogPersistence {
|
|
|
4227
4358
|
*/
|
|
4228
4359
|
static async markDialogCompleted(dialogId) {
|
|
4229
4360
|
try {
|
|
4230
|
-
const dialogPath = this.
|
|
4231
|
-
const completedPath = this.
|
|
4361
|
+
const dialogPath = this.getMainDialogPath(dialogId, 'running');
|
|
4362
|
+
const completedPath = this.getMainDialogPath(dialogId, 'completed');
|
|
4232
4363
|
await fs.promises.mkdir(completedPath, { recursive: true });
|
|
4233
4364
|
// Move files from current to completed
|
|
4234
4365
|
const files = await fs.promises.readdir(dialogPath);
|
|
@@ -4244,7 +4375,7 @@ class DialogPersistence {
|
|
|
4244
4375
|
}
|
|
4245
4376
|
}
|
|
4246
4377
|
/**
|
|
4247
|
-
* List candidate
|
|
4378
|
+
* List candidate main dialog IDs by scanning `dialog.yaml`.
|
|
4248
4379
|
*
|
|
4249
4380
|
* This scanner intentionally stays lightweight: it only validates the path<->id identity needed
|
|
4250
4381
|
* for safe enumeration, and leaves full metadata shape validation to the subsequent lazy-load
|
|
@@ -4301,9 +4432,9 @@ class DialogPersistence {
|
|
|
4301
4432
|
catch (yamlError) {
|
|
4302
4433
|
const persistenceError = (0, persistence_errors_1.findDomindsPersistenceFileError)(yamlError);
|
|
4303
4434
|
if (persistenceError) {
|
|
4304
|
-
const
|
|
4305
|
-
if (
|
|
4306
|
-
await this.quarantineMalformedDialog(
|
|
4435
|
+
const mainDialogId = this.inferMainDialogIdFromMetadataRelativeDir(path.dirname(entryRelativePath));
|
|
4436
|
+
if (mainDialogId) {
|
|
4437
|
+
await this.quarantineMalformedDialog(mainDialogId, status, 'listDialogs', persistenceError);
|
|
4307
4438
|
}
|
|
4308
4439
|
}
|
|
4309
4440
|
log_1.log.warn(`🔍 listDialogs: Failed to parse dialog.yaml at ${fullPath}:`, yamlError);
|
|
@@ -4347,8 +4478,8 @@ class DialogPersistence {
|
|
|
4347
4478
|
}
|
|
4348
4479
|
}
|
|
4349
4480
|
/**
|
|
4350
|
-
* List all dialog IDs (
|
|
4351
|
-
* This is the only safe way to enumerate
|
|
4481
|
+
* List all dialog IDs (main dialogs + sideDialogs) together with their root IDs.
|
|
4482
|
+
* This is the only safe way to enumerate sideDialogs because their directory names
|
|
4352
4483
|
* are not guaranteed to be their selfId.
|
|
4353
4484
|
*
|
|
4354
4485
|
* Like `listDialogs()`, this is a candidate scanner rather than a full metadata validator.
|
|
@@ -4359,9 +4490,9 @@ class DialogPersistence {
|
|
|
4359
4490
|
const statusDir = this.getDialogsRootDir();
|
|
4360
4491
|
const specificDir = path.join(statusDir, getPersistableStatusDirName(status, 'DialogPersistence.listAllDialogIds'));
|
|
4361
4492
|
const result = [];
|
|
4362
|
-
const
|
|
4493
|
+
const mainDialogIdByDialogYamlPath = new Map();
|
|
4363
4494
|
const readDialogYamlId = async (dialogYamlPath) => {
|
|
4364
|
-
const cached =
|
|
4495
|
+
const cached = mainDialogIdByDialogYamlPath.get(dialogYamlPath);
|
|
4365
4496
|
if (cached !== undefined)
|
|
4366
4497
|
return cached;
|
|
4367
4498
|
try {
|
|
@@ -4391,19 +4522,19 @@ class DialogPersistence {
|
|
|
4391
4522
|
});
|
|
4392
4523
|
}
|
|
4393
4524
|
const normalized = idValue.trim();
|
|
4394
|
-
|
|
4525
|
+
mainDialogIdByDialogYamlPath.set(dialogYamlPath, normalized);
|
|
4395
4526
|
return normalized;
|
|
4396
4527
|
}
|
|
4397
4528
|
catch (error) {
|
|
4398
4529
|
const persistenceError = (0, persistence_errors_1.findDomindsPersistenceFileError)(error);
|
|
4399
4530
|
if (persistenceError) {
|
|
4400
4531
|
const relativeDir = path.relative(specificDir, path.dirname(dialogYamlPath));
|
|
4401
|
-
const
|
|
4402
|
-
if (
|
|
4403
|
-
await this.quarantineMalformedDialog(
|
|
4532
|
+
const mainDialogId = this.inferMainDialogIdFromMetadataRelativeDir(relativeDir);
|
|
4533
|
+
if (mainDialogId) {
|
|
4534
|
+
await this.quarantineMalformedDialog(mainDialogId, status, 'listAllDialogIds:readDialogYamlId', persistenceError);
|
|
4404
4535
|
}
|
|
4405
4536
|
}
|
|
4406
|
-
|
|
4537
|
+
mainDialogIdByDialogYamlPath.set(dialogYamlPath, null);
|
|
4407
4538
|
return null;
|
|
4408
4539
|
}
|
|
4409
4540
|
};
|
|
@@ -4414,11 +4545,11 @@ class DialogPersistence {
|
|
|
4414
4545
|
const segments = dir.split(path.sep).filter((seg) => seg.length > 0 && seg !== '.');
|
|
4415
4546
|
if (segments.length === 0)
|
|
4416
4547
|
return null;
|
|
4417
|
-
//
|
|
4418
|
-
// The
|
|
4548
|
+
// Main dialog IDs in this repo can contain path separators (e.g. "f4/44/cd85c4e2").
|
|
4549
|
+
// The main dialog directory is therefore nested (RUN_DIR/<rootId>/dialog.yaml).
|
|
4419
4550
|
//
|
|
4420
|
-
// To infer the rootId for any dialog.yaml we find (
|
|
4421
|
-
// directory path and pick the first prefix that is itself a valid
|
|
4551
|
+
// To infer the rootId for any dialog.yaml we find (main dialog or sideDialog), scan prefixes of the
|
|
4552
|
+
// directory path and pick the first prefix that is itself a valid main dialog directory:
|
|
4422
4553
|
// - it has a dialog.yaml
|
|
4423
4554
|
// - its dialog.yaml id matches the prefix joined with '/'
|
|
4424
4555
|
for (let i = 1; i <= segments.length; i++) {
|
|
@@ -4495,9 +4626,9 @@ class DialogPersistence {
|
|
|
4495
4626
|
catch (yamlError) {
|
|
4496
4627
|
const persistenceError = (0, persistence_errors_1.findDomindsPersistenceFileError)(yamlError);
|
|
4497
4628
|
if (persistenceError) {
|
|
4498
|
-
const
|
|
4499
|
-
if (
|
|
4500
|
-
await this.quarantineMalformedDialog(
|
|
4629
|
+
const mainDialogId = this.inferMainDialogIdFromMetadataRelativeDir(relDir);
|
|
4630
|
+
if (mainDialogId) {
|
|
4631
|
+
await this.quarantineMalformedDialog(mainDialogId, status, 'listAllDialogIds', persistenceError);
|
|
4501
4632
|
}
|
|
4502
4633
|
}
|
|
4503
4634
|
log_1.log.warn(`🔍 listAllDialogIds: Failed to parse dialog.yaml at ${fullPath}:`, yamlError);
|
|
@@ -4525,7 +4656,7 @@ class DialogPersistence {
|
|
|
4525
4656
|
static async appendEvents(dialogId, course, events, status = 'running') {
|
|
4526
4657
|
const appendMutexKey = this.getCourseAppendMutexKey(dialogId, course, status);
|
|
4527
4658
|
const release = await this.getCourseAppendMutex(appendMutexKey).acquire();
|
|
4528
|
-
const cancellationToken = this.
|
|
4659
|
+
const cancellationToken = this.createMainDialogWriteBackCancellationToken(dialogId, status);
|
|
4529
4660
|
try {
|
|
4530
4661
|
if (events.length === 0) {
|
|
4531
4662
|
return;
|
|
@@ -5021,7 +5152,7 @@ class DialogPersistence {
|
|
|
5021
5152
|
if (entry.kind !== 'scheduled')
|
|
5022
5153
|
return;
|
|
5023
5154
|
clearTimeout(entry.timer);
|
|
5024
|
-
const cancellationToken = this.
|
|
5155
|
+
const cancellationToken = this.createMainDialogWriteBackCancellationToken(entry.dialogId, entry.status);
|
|
5025
5156
|
const inFlight = this.writeQ4HStateToDisk(entry.dialogId, entry.state, entry.status, cancellationToken);
|
|
5026
5157
|
captured = {
|
|
5027
5158
|
dialogId: entry.dialogId,
|
|
@@ -5107,7 +5238,7 @@ class DialogPersistence {
|
|
|
5107
5238
|
}
|
|
5108
5239
|
static async writeQ4HStateToDisk(dialogId, state, status, cancellationToken) {
|
|
5109
5240
|
if (cancellationToken) {
|
|
5110
|
-
this.
|
|
5241
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, 'writeQ4HStateToDisk:start');
|
|
5111
5242
|
}
|
|
5112
5243
|
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
5113
5244
|
const questionsFilePath = path.join(dialogPath, 'q4h.yaml');
|
|
@@ -5132,7 +5263,7 @@ class DialogPersistence {
|
|
|
5132
5263
|
*/
|
|
5133
5264
|
static async loadAllQ4HState() {
|
|
5134
5265
|
try {
|
|
5135
|
-
// Get all running dialogs (
|
|
5266
|
+
// Get all running dialogs (main dialogs + sideDialogs) with correct rootId association.
|
|
5136
5267
|
const dialogIds = await this.listAllDialogIds('running');
|
|
5137
5268
|
const allQuestions = [];
|
|
5138
5269
|
for (const dialogIdObj of dialogIds) {
|
|
@@ -5183,36 +5314,35 @@ class DialogPersistence {
|
|
|
5183
5314
|
log_1.log.error(`Failed to clear q4h.yaml for dialog ${dialogId}:`, error);
|
|
5184
5315
|
}
|
|
5185
5316
|
}
|
|
5186
|
-
// === PHASE 6:
|
|
5317
|
+
// === PHASE 6: SIDE DIALOG PENDING PERSISTENCE ===
|
|
5187
5318
|
/**
|
|
5188
|
-
* Save pending
|
|
5189
|
-
* Tracks subdialogs that were created but not yet completed.
|
|
5319
|
+
* Save pending sideDialogs that have an outstanding tellask/reply delivery.
|
|
5190
5320
|
*/
|
|
5191
|
-
static async
|
|
5192
|
-
const next =
|
|
5193
|
-
await this.
|
|
5321
|
+
static async savePendingSideDialogs(mainDialogId, pendingSideDialogs, rootAnchor, status = 'running') {
|
|
5322
|
+
const next = pendingSideDialogs.map((r) => ({ ...r }));
|
|
5323
|
+
await this.mutatePendingSideDialogs(mainDialogId, () => ({ kind: 'replace', records: next }), rootAnchor, status);
|
|
5194
5324
|
}
|
|
5195
5325
|
/**
|
|
5196
|
-
* Load pending
|
|
5326
|
+
* Load pending sideDialogs that have an outstanding tellask/reply delivery.
|
|
5197
5327
|
*/
|
|
5198
|
-
static async
|
|
5199
|
-
const key = this.
|
|
5200
|
-
const staged = this.
|
|
5328
|
+
static async loadPendingSideDialogs(mainDialogId, status = 'running') {
|
|
5329
|
+
const key = this.getPendingSideDialogsWriteBackKey(mainDialogId, status);
|
|
5330
|
+
const staged = this.pendingSideDialogsWriteBack.get(key);
|
|
5201
5331
|
if (staged) {
|
|
5202
5332
|
return staged.state.kind === 'deleted' ? [] : staged.state.records;
|
|
5203
5333
|
}
|
|
5204
5334
|
try {
|
|
5205
|
-
return await this.
|
|
5335
|
+
return await this.loadPendingSideDialogsFromDisk(mainDialogId, status);
|
|
5206
5336
|
}
|
|
5207
5337
|
catch (error) {
|
|
5208
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(
|
|
5209
|
-
throw new Error('unreachable after
|
|
5338
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(mainDialogId, status, 'loadPendingSideDialogs', error);
|
|
5339
|
+
throw new Error('unreachable after loadPendingSideDialogs persistence rethrow');
|
|
5210
5340
|
}
|
|
5211
5341
|
}
|
|
5212
|
-
static
|
|
5342
|
+
static isPendingSideDialogRecord(value) {
|
|
5213
5343
|
if (!isRecord(value))
|
|
5214
5344
|
return false;
|
|
5215
|
-
if (typeof value.
|
|
5345
|
+
if (typeof value.sideDialogId !== 'string')
|
|
5216
5346
|
return false;
|
|
5217
5347
|
if (typeof value.createdAt !== 'string')
|
|
5218
5348
|
return false;
|
|
@@ -5242,28 +5372,18 @@ class DialogPersistence {
|
|
|
5242
5372
|
return false;
|
|
5243
5373
|
if (typeof value.callId !== 'string')
|
|
5244
5374
|
return false;
|
|
5245
|
-
if ('
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
const callingGenseq = value.callingGenseq;
|
|
5258
|
-
if (callingGenseq !== undefined) {
|
|
5259
|
-
if (typeof callingGenseq !== 'number')
|
|
5260
|
-
return false;
|
|
5261
|
-
if (!Number.isFinite(callingGenseq))
|
|
5262
|
-
return false;
|
|
5263
|
-
if (Math.floor(callingGenseq) <= 0)
|
|
5264
|
-
return false;
|
|
5265
|
-
}
|
|
5266
|
-
}
|
|
5375
|
+
if (typeof value.callSiteCourse !== 'number')
|
|
5376
|
+
return false;
|
|
5377
|
+
if (!Number.isInteger(value.callSiteCourse))
|
|
5378
|
+
return false;
|
|
5379
|
+
if (value.callSiteCourse <= 0)
|
|
5380
|
+
return false;
|
|
5381
|
+
if (typeof value.callSiteGenseq !== 'number')
|
|
5382
|
+
return false;
|
|
5383
|
+
if (!Number.isInteger(value.callSiteGenseq))
|
|
5384
|
+
return false;
|
|
5385
|
+
if (value.callSiteGenseq <= 0)
|
|
5386
|
+
return false;
|
|
5267
5387
|
if (value.callType !== 'A' && value.callType !== 'B' && value.callType !== 'C')
|
|
5268
5388
|
return false;
|
|
5269
5389
|
if ('sessionSlug' in value) {
|
|
@@ -5273,27 +5393,56 @@ class DialogPersistence {
|
|
|
5273
5393
|
}
|
|
5274
5394
|
return true;
|
|
5275
5395
|
}
|
|
5276
|
-
static
|
|
5277
|
-
const
|
|
5278
|
-
const
|
|
5396
|
+
static assertNoDuplicateSessionedTellaskPendingRecords(records, context) {
|
|
5397
|
+
const seen = new Map();
|
|
5398
|
+
for (const record of records) {
|
|
5399
|
+
if (record.callType !== 'B')
|
|
5400
|
+
continue;
|
|
5401
|
+
if (record.callName !== 'tellask')
|
|
5402
|
+
continue;
|
|
5403
|
+
if (record.sessionSlug === undefined)
|
|
5404
|
+
continue;
|
|
5405
|
+
const sessionSlug = record.sessionSlug.trim();
|
|
5406
|
+
if (sessionSlug === '')
|
|
5407
|
+
continue;
|
|
5408
|
+
const key = `${record.targetAgentId}\0${sessionSlug}`;
|
|
5409
|
+
const previous = seen.get(key);
|
|
5410
|
+
if (previous) {
|
|
5411
|
+
throw new Error(`pending-sideDialogs invariant violation: duplicate sessioned tellask pending record ` +
|
|
5412
|
+
`(rootId=${context.rootId}, selfId=${context.selfId}, status=${context.status}, ` +
|
|
5413
|
+
`targetAgentId=${record.targetAgentId}, sessionSlug=${sessionSlug}, ` +
|
|
5414
|
+
`previousSideDialogId=${previous.sideDialogId}, previousCallId=${previous.callId}, ` +
|
|
5415
|
+
`duplicateSideDialogId=${record.sideDialogId}, duplicateCallId=${record.callId})`);
|
|
5416
|
+
}
|
|
5417
|
+
seen.set(key, record);
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
static async loadPendingSideDialogsFromDisk(mainDialogId, status) {
|
|
5421
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
5422
|
+
const filePath = path.join(dialogPath, 'pending-sideDialogs.json');
|
|
5279
5423
|
try {
|
|
5280
5424
|
const content = await readPersistenceTextFile({
|
|
5281
5425
|
filePath,
|
|
5282
|
-
source: '
|
|
5426
|
+
source: 'pending_sideDialogs',
|
|
5283
5427
|
format: 'json',
|
|
5284
5428
|
});
|
|
5285
5429
|
const parsed = parsePersistenceJson({
|
|
5286
5430
|
content,
|
|
5287
5431
|
filePath,
|
|
5288
|
-
source: '
|
|
5432
|
+
source: 'pending_sideDialogs',
|
|
5289
5433
|
});
|
|
5290
|
-
if (!Array.isArray(parsed) || !parsed.every((item) => this.
|
|
5434
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => this.isPendingSideDialogRecord(item))) {
|
|
5291
5435
|
throw buildInvalidPersistenceFileError({
|
|
5292
|
-
source: '
|
|
5436
|
+
source: 'pending_sideDialogs',
|
|
5293
5437
|
format: 'json',
|
|
5294
5438
|
filePath,
|
|
5295
5439
|
});
|
|
5296
5440
|
}
|
|
5441
|
+
this.assertNoDuplicateSessionedTellaskPendingRecords(parsed, {
|
|
5442
|
+
rootId: mainDialogId.rootId,
|
|
5443
|
+
selfId: mainDialogId.selfId,
|
|
5444
|
+
status,
|
|
5445
|
+
});
|
|
5297
5446
|
return parsed;
|
|
5298
5447
|
}
|
|
5299
5448
|
catch (error) {
|
|
@@ -5302,17 +5451,17 @@ class DialogPersistence {
|
|
|
5302
5451
|
throw error;
|
|
5303
5452
|
}
|
|
5304
5453
|
}
|
|
5305
|
-
static async
|
|
5306
|
-
const key = this.
|
|
5307
|
-
const mutex = this.
|
|
5454
|
+
static async mutatePendingSideDialogs(mainDialogId, mutator, rootAnchor, status = 'running') {
|
|
5455
|
+
const key = this.getPendingSideDialogsWriteBackKey(mainDialogId, status);
|
|
5456
|
+
const mutex = this.getPendingSideDialogsWriteBackMutex(key);
|
|
5308
5457
|
const release = await mutex.acquire();
|
|
5309
5458
|
try {
|
|
5310
|
-
const staged = this.
|
|
5459
|
+
const staged = this.pendingSideDialogsWriteBack.get(key);
|
|
5311
5460
|
const previousRecords = staged && staged.state.kind === 'file'
|
|
5312
5461
|
? staged.state.records
|
|
5313
5462
|
: staged && staged.state.kind === 'deleted'
|
|
5314
5463
|
? []
|
|
5315
|
-
: await this.
|
|
5464
|
+
: await this.loadPendingSideDialogsFromDisk(mainDialogId, status);
|
|
5316
5465
|
const mutation = mutator(previousRecords);
|
|
5317
5466
|
let nextRecords = previousRecords;
|
|
5318
5467
|
const removedRecords = [];
|
|
@@ -5322,20 +5471,20 @@ class DialogPersistence {
|
|
|
5322
5471
|
else if (mutation.kind === 'append') {
|
|
5323
5472
|
nextRecords = [...previousRecords, mutation.record];
|
|
5324
5473
|
}
|
|
5325
|
-
else if (mutation.kind === '
|
|
5474
|
+
else if (mutation.kind === 'removeBySideDialogId') {
|
|
5326
5475
|
for (const r of previousRecords) {
|
|
5327
|
-
if (r.
|
|
5476
|
+
if (r.sideDialogId === mutation.sideDialogId)
|
|
5328
5477
|
removedRecords.push(r);
|
|
5329
5478
|
}
|
|
5330
|
-
nextRecords = previousRecords.filter((r) => r.
|
|
5479
|
+
nextRecords = previousRecords.filter((r) => r.sideDialogId !== mutation.sideDialogId);
|
|
5331
5480
|
}
|
|
5332
|
-
else if (mutation.kind === '
|
|
5333
|
-
const remove = new Set(mutation.
|
|
5481
|
+
else if (mutation.kind === 'removeBySideDialogIds') {
|
|
5482
|
+
const remove = new Set(mutation.sideDialogIds);
|
|
5334
5483
|
for (const r of previousRecords) {
|
|
5335
|
-
if (remove.has(r.
|
|
5484
|
+
if (remove.has(r.sideDialogId))
|
|
5336
5485
|
removedRecords.push(r);
|
|
5337
5486
|
}
|
|
5338
|
-
nextRecords = previousRecords.filter((r) => !remove.has(r.
|
|
5487
|
+
nextRecords = previousRecords.filter((r) => !remove.has(r.sideDialogId));
|
|
5339
5488
|
}
|
|
5340
5489
|
else if (mutation.kind === 'replace') {
|
|
5341
5490
|
nextRecords = [...mutation.records];
|
|
@@ -5346,17 +5495,28 @@ class DialogPersistence {
|
|
|
5346
5495
|
}
|
|
5347
5496
|
else {
|
|
5348
5497
|
const _exhaustive = mutation;
|
|
5349
|
-
throw new Error(`Unhandled pending-
|
|
5498
|
+
throw new Error(`Unhandled pending-sideDialogs mutation: ${String(_exhaustive)}`);
|
|
5499
|
+
}
|
|
5500
|
+
for (let index = 0; index < nextRecords.length; index += 1) {
|
|
5501
|
+
if (!this.isPendingSideDialogRecord(nextRecords[index])) {
|
|
5502
|
+
throw new Error(`pending-sideDialogs write invariant violation: malformed record at index ${index} ` +
|
|
5503
|
+
`(rootId=${mainDialogId.rootId}, selfId=${mainDialogId.selfId}, status=${status})`);
|
|
5504
|
+
}
|
|
5350
5505
|
}
|
|
5506
|
+
this.assertNoDuplicateSessionedTellaskPendingRecords(nextRecords, {
|
|
5507
|
+
rootId: mainDialogId.rootId,
|
|
5508
|
+
selfId: mainDialogId.selfId,
|
|
5509
|
+
status,
|
|
5510
|
+
});
|
|
5351
5511
|
const nextState = nextRecords.length === 0 ? { kind: 'deleted' } : { kind: 'file', records: nextRecords };
|
|
5352
|
-
const pending = this.
|
|
5512
|
+
const pending = this.pendingSideDialogsWriteBack.get(key);
|
|
5353
5513
|
if (!pending) {
|
|
5354
5514
|
const timer = setTimeout(() => {
|
|
5355
|
-
void this.
|
|
5356
|
-
}, this.
|
|
5357
|
-
this.
|
|
5515
|
+
void this.flushPendingSideDialogsWriteBack(key);
|
|
5516
|
+
}, this.PENDING_SIDE_DIALOGS_WRITEBACK_WINDOW_MS);
|
|
5517
|
+
this.pendingSideDialogsWriteBack.set(key, {
|
|
5358
5518
|
kind: 'scheduled',
|
|
5359
|
-
dialogId:
|
|
5519
|
+
dialogId: mainDialogId,
|
|
5360
5520
|
status,
|
|
5361
5521
|
state: nextState,
|
|
5362
5522
|
timer,
|
|
@@ -5368,7 +5528,7 @@ class DialogPersistence {
|
|
|
5368
5528
|
pending.dirty = true;
|
|
5369
5529
|
}
|
|
5370
5530
|
if (rootAnchor) {
|
|
5371
|
-
await this.
|
|
5531
|
+
await this.appendPendingSideDialogsReconciledRecord(mainDialogId, nextRecords, rootAnchorWriteTarget(rootAnchor), status);
|
|
5372
5532
|
}
|
|
5373
5533
|
return { previousRecords, records: nextRecords, removedRecords };
|
|
5374
5534
|
}
|
|
@@ -5376,22 +5536,22 @@ class DialogPersistence {
|
|
|
5376
5536
|
release();
|
|
5377
5537
|
}
|
|
5378
5538
|
}
|
|
5379
|
-
static async
|
|
5380
|
-
await this.
|
|
5539
|
+
static async appendPendingSideDialog(mainDialogId, record, rootAnchor, status = 'running') {
|
|
5540
|
+
await this.mutatePendingSideDialogs(mainDialogId, () => ({ kind: 'append', record }), rootAnchor, status);
|
|
5381
5541
|
}
|
|
5382
|
-
static async
|
|
5383
|
-
await this.
|
|
5542
|
+
static async removePendingSideDialog(mainDialogId, sideDialogId, rootAnchor, status = 'running') {
|
|
5543
|
+
await this.mutatePendingSideDialogs(mainDialogId, () => ({ kind: 'removeBySideDialogId', sideDialogId }), rootAnchor, status);
|
|
5384
5544
|
}
|
|
5385
|
-
static async
|
|
5386
|
-
await this.
|
|
5545
|
+
static async clearPendingSideDialogs(mainDialogId, rootAnchor, status = 'running') {
|
|
5546
|
+
await this.mutatePendingSideDialogs(mainDialogId, () => ({ kind: 'clear' }), rootAnchor, status);
|
|
5387
5547
|
}
|
|
5388
|
-
static async
|
|
5389
|
-
const mutex = this.
|
|
5548
|
+
static async flushPendingSideDialogsWriteBack(key) {
|
|
5549
|
+
const mutex = this.getPendingSideDialogsWriteBackMutex(key);
|
|
5390
5550
|
let captured;
|
|
5391
5551
|
{
|
|
5392
5552
|
const release = await mutex.acquire();
|
|
5393
5553
|
try {
|
|
5394
|
-
const entry = this.
|
|
5554
|
+
const entry = this.pendingSideDialogsWriteBack.get(key);
|
|
5395
5555
|
if (!entry)
|
|
5396
5556
|
return;
|
|
5397
5557
|
if (entry.kind === 'flushing')
|
|
@@ -5399,15 +5559,15 @@ class DialogPersistence {
|
|
|
5399
5559
|
if (entry.kind !== 'scheduled')
|
|
5400
5560
|
return;
|
|
5401
5561
|
clearTimeout(entry.timer);
|
|
5402
|
-
const cancellationToken = this.
|
|
5403
|
-
const inFlight = this.
|
|
5562
|
+
const cancellationToken = this.createMainDialogWriteBackCancellationToken(entry.dialogId, entry.status);
|
|
5563
|
+
const inFlight = this.writePendingSideDialogsToDisk(entry.dialogId, entry.state, entry.status, cancellationToken);
|
|
5404
5564
|
captured = {
|
|
5405
5565
|
dialogId: entry.dialogId,
|
|
5406
5566
|
status: entry.status,
|
|
5407
5567
|
stateToWrite: entry.state,
|
|
5408
5568
|
inFlight,
|
|
5409
5569
|
};
|
|
5410
|
-
this.
|
|
5570
|
+
this.pendingSideDialogsWriteBack.set(key, {
|
|
5411
5571
|
kind: 'flushing',
|
|
5412
5572
|
dialogId: entry.dialogId,
|
|
5413
5573
|
status: entry.status,
|
|
@@ -5428,7 +5588,7 @@ class DialogPersistence {
|
|
|
5428
5588
|
catch (error) {
|
|
5429
5589
|
const release = await mutex.acquire();
|
|
5430
5590
|
try {
|
|
5431
|
-
const entry = this.
|
|
5591
|
+
const entry = this.pendingSideDialogsWriteBack.get(key);
|
|
5432
5592
|
if (!entry)
|
|
5433
5593
|
return;
|
|
5434
5594
|
if (entry.kind !== 'flushing')
|
|
@@ -5436,13 +5596,13 @@ class DialogPersistence {
|
|
|
5436
5596
|
if (entry.inFlight !== captured.inFlight)
|
|
5437
5597
|
return;
|
|
5438
5598
|
if (isDialogWriteBackCanceledError(error)) {
|
|
5439
|
-
this.
|
|
5599
|
+
this.pendingSideDialogsWriteBack.delete(key);
|
|
5440
5600
|
return;
|
|
5441
5601
|
}
|
|
5442
5602
|
const timer = setTimeout(() => {
|
|
5443
|
-
void this.
|
|
5444
|
-
}, this.
|
|
5445
|
-
this.
|
|
5603
|
+
void this.flushPendingSideDialogsWriteBack(key);
|
|
5604
|
+
}, this.PENDING_SIDE_DIALOGS_WRITEBACK_WINDOW_MS);
|
|
5605
|
+
this.pendingSideDialogsWriteBack.set(key, {
|
|
5446
5606
|
kind: 'scheduled',
|
|
5447
5607
|
dialogId: entry.dialogId,
|
|
5448
5608
|
status: entry.status,
|
|
@@ -5457,7 +5617,7 @@ class DialogPersistence {
|
|
|
5457
5617
|
}
|
|
5458
5618
|
const release = await mutex.acquire();
|
|
5459
5619
|
try {
|
|
5460
|
-
const entry = this.
|
|
5620
|
+
const entry = this.pendingSideDialogsWriteBack.get(key);
|
|
5461
5621
|
if (!entry)
|
|
5462
5622
|
return;
|
|
5463
5623
|
if (entry.kind !== 'flushing')
|
|
@@ -5465,13 +5625,13 @@ class DialogPersistence {
|
|
|
5465
5625
|
if (entry.inFlight !== captured.inFlight)
|
|
5466
5626
|
return;
|
|
5467
5627
|
if (!entry.dirty) {
|
|
5468
|
-
this.
|
|
5628
|
+
this.pendingSideDialogsWriteBack.delete(key);
|
|
5469
5629
|
return;
|
|
5470
5630
|
}
|
|
5471
5631
|
const timer = setTimeout(() => {
|
|
5472
|
-
void this.
|
|
5473
|
-
}, this.
|
|
5474
|
-
this.
|
|
5632
|
+
void this.flushPendingSideDialogsWriteBack(key);
|
|
5633
|
+
}, this.PENDING_SIDE_DIALOGS_WRITEBACK_WINDOW_MS);
|
|
5634
|
+
this.pendingSideDialogsWriteBack.set(key, {
|
|
5475
5635
|
kind: 'scheduled',
|
|
5476
5636
|
dialogId: entry.dialogId,
|
|
5477
5637
|
status: entry.status,
|
|
@@ -5483,12 +5643,12 @@ class DialogPersistence {
|
|
|
5483
5643
|
release();
|
|
5484
5644
|
}
|
|
5485
5645
|
}
|
|
5486
|
-
static async
|
|
5646
|
+
static async writePendingSideDialogsToDisk(mainDialogId, state, status, cancellationToken) {
|
|
5487
5647
|
if (cancellationToken) {
|
|
5488
|
-
this.
|
|
5648
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, 'writePendingSideDialogsToDisk:start');
|
|
5489
5649
|
}
|
|
5490
|
-
const dialogPath = this.getDialogResponsesPath(
|
|
5491
|
-
const filePath = path.join(dialogPath, 'pending-
|
|
5650
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
5651
|
+
const filePath = path.join(dialogPath, 'pending-sideDialogs.json');
|
|
5492
5652
|
if (state.kind === 'deleted') {
|
|
5493
5653
|
await fs.promises.rm(filePath, { force: true });
|
|
5494
5654
|
return;
|
|
@@ -5499,75 +5659,75 @@ class DialogPersistence {
|
|
|
5499
5659
|
await fs.promises.writeFile(tempFile, jsonContent, 'utf-8');
|
|
5500
5660
|
}
|
|
5501
5661
|
catch (error) {
|
|
5502
|
-
await this.rethrowWriteBackPathMissingAsCanceled(error, dialogPath, cancellationToken, '
|
|
5662
|
+
await this.rethrowWriteBackPathMissingAsCanceled(error, dialogPath, cancellationToken, 'writePendingSideDialogsToDisk:write-temp');
|
|
5503
5663
|
throw error;
|
|
5504
5664
|
}
|
|
5505
5665
|
await this.renameWithRetry(tempFile, filePath, 5, cancellationToken);
|
|
5506
5666
|
}
|
|
5507
5667
|
/**
|
|
5508
|
-
* Get the path for storing
|
|
5509
|
-
* For Type C
|
|
5668
|
+
* Get the path for storing sideDialog responses (supports both main dialog and sideDialog tellaskers).
|
|
5669
|
+
* For Type C sideDialogs created inside another sideDialog, responses are stored at the parent's level.
|
|
5510
5670
|
*/
|
|
5511
5671
|
static getDialogResponsesPath(dialogId, status = 'running') {
|
|
5512
|
-
//
|
|
5513
|
-
//
|
|
5672
|
+
// Main dialogs store responses in their own directory.
|
|
5673
|
+
// SideDialogs store responses in the tellasker's location (main dialog or sideDialog).
|
|
5514
5674
|
if (dialogId.rootId === dialogId.selfId) {
|
|
5515
|
-
//
|
|
5516
|
-
return this.
|
|
5675
|
+
// Main dialog: use root's directory
|
|
5676
|
+
return this.getMainDialogPath(dialogId, status);
|
|
5517
5677
|
}
|
|
5518
|
-
//
|
|
5519
|
-
// The parent is always identified by rootId (could be root or parent
|
|
5678
|
+
// SideDialog: store in parent's sideDialogs directory
|
|
5679
|
+
// The parent is always identified by rootId (could be root or parent sideDialog)
|
|
5520
5680
|
const parentSelfId = dialogId.rootId;
|
|
5521
|
-
const rootPath = this.
|
|
5522
|
-
return path.join(rootPath, this.
|
|
5681
|
+
const rootPath = this.getMainDialogPath(new dialog_1.DialogID(parentSelfId), status);
|
|
5682
|
+
return path.join(rootPath, this.SIDE_DIALOGS_DIR, dialogId.selfId);
|
|
5523
5683
|
}
|
|
5524
5684
|
/**
|
|
5525
|
-
* Save
|
|
5526
|
-
* Tracks responses from completed subdialogs.
|
|
5685
|
+
* Save responses delivered back from completed sideDialogs.
|
|
5527
5686
|
*/
|
|
5528
|
-
static async
|
|
5687
|
+
static async saveSideDialogResponses(mainDialogId, responses, rootAnchor, status = 'running') {
|
|
5529
5688
|
try {
|
|
5530
|
-
|
|
5531
|
-
const
|
|
5689
|
+
assertUniqueSideDialogResponseIds(responses, `save rootId=${mainDialogId.rootId}`);
|
|
5690
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
5691
|
+
const filePath = path.join(dialogPath, 'sideDialog-responses.json');
|
|
5532
5692
|
// Atomic write operation
|
|
5533
5693
|
const jsonContent = JSON.stringify(responses, null, 2);
|
|
5534
5694
|
const tempFile = path.join(dialogPath, `.${path.basename(filePath)}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
5535
5695
|
await fs.promises.writeFile(tempFile, jsonContent, 'utf-8');
|
|
5536
5696
|
await this.renameWithRetry(tempFile, filePath);
|
|
5537
5697
|
if (rootAnchor) {
|
|
5538
|
-
await this.
|
|
5698
|
+
await this.appendSideDialogResponsesReconciledRecord(mainDialogId, responses, rootAnchorWriteTarget(rootAnchor), status);
|
|
5539
5699
|
}
|
|
5540
5700
|
}
|
|
5541
5701
|
catch (error) {
|
|
5542
|
-
log_1.log.error(`Failed to save
|
|
5702
|
+
log_1.log.error(`Failed to save sideDialog responses for dialog ${mainDialogId}:`, error);
|
|
5543
5703
|
throw error;
|
|
5544
5704
|
}
|
|
5545
5705
|
}
|
|
5546
5706
|
/**
|
|
5547
|
-
* Load
|
|
5707
|
+
* Load responses delivered back from completed sideDialogs.
|
|
5548
5708
|
*/
|
|
5549
|
-
static async
|
|
5709
|
+
static async loadSideDialogResponses(mainDialogId, status = 'running') {
|
|
5550
5710
|
try {
|
|
5551
|
-
const dialogPath = this.getDialogResponsesPath(
|
|
5552
|
-
const filePath = path.join(dialogPath, '
|
|
5553
|
-
const inflightPath = path.join(dialogPath, '
|
|
5711
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
5712
|
+
const filePath = path.join(dialogPath, 'sideDialog-responses.json');
|
|
5713
|
+
const inflightPath = path.join(dialogPath, 'sideDialog-responses.processing.json');
|
|
5554
5714
|
try {
|
|
5555
5715
|
const results = [];
|
|
5556
5716
|
const tryReadArray = async (p) => {
|
|
5557
5717
|
try {
|
|
5558
5718
|
const content = await readPersistenceTextFile({
|
|
5559
5719
|
filePath: p,
|
|
5560
|
-
source: '
|
|
5720
|
+
source: 'sideDialog_responses',
|
|
5561
5721
|
format: 'json',
|
|
5562
5722
|
});
|
|
5563
5723
|
const parsed = parsePersistenceJson({
|
|
5564
5724
|
content,
|
|
5565
5725
|
filePath: p,
|
|
5566
|
-
source: '
|
|
5726
|
+
source: 'sideDialog_responses',
|
|
5567
5727
|
});
|
|
5568
5728
|
if (!Array.isArray(parsed)) {
|
|
5569
5729
|
throw buildInvalidPersistenceFileError({
|
|
5570
|
-
source: '
|
|
5730
|
+
source: 'sideDialog_responses',
|
|
5571
5731
|
format: 'json',
|
|
5572
5732
|
filePath: p,
|
|
5573
5733
|
});
|
|
@@ -5584,21 +5744,17 @@ class DialogPersistence {
|
|
|
5584
5744
|
const primary = await tryReadArray(filePath);
|
|
5585
5745
|
const inflight = await tryReadArray(inflightPath);
|
|
5586
5746
|
for (const item of [...primary, ...inflight]) {
|
|
5587
|
-
if (!
|
|
5747
|
+
if (!isSideDialogResponseRecord(item)) {
|
|
5588
5748
|
throw buildInvalidPersistenceFileError({
|
|
5589
|
-
source: '
|
|
5749
|
+
source: 'sideDialog_responses',
|
|
5590
5750
|
format: 'json',
|
|
5591
5751
|
filePath,
|
|
5592
5752
|
});
|
|
5593
5753
|
}
|
|
5594
5754
|
results.push(item);
|
|
5595
5755
|
}
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
for (const r of results) {
|
|
5599
|
-
byId.set(r.responseId, r);
|
|
5600
|
-
}
|
|
5601
|
-
return Array.from(byId.values());
|
|
5756
|
+
assertUniqueSideDialogResponseIds(results, `load rootId=${mainDialogId.rootId} status=${status}`);
|
|
5757
|
+
return results;
|
|
5602
5758
|
}
|
|
5603
5759
|
catch (error) {
|
|
5604
5760
|
if (getErrorCode(error) === 'ENOENT') {
|
|
@@ -5608,51 +5764,52 @@ class DialogPersistence {
|
|
|
5608
5764
|
}
|
|
5609
5765
|
}
|
|
5610
5766
|
catch (error) {
|
|
5611
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(
|
|
5612
|
-
throw new Error('unreachable after
|
|
5767
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(mainDialogId, status, 'loadSideDialogResponses', error);
|
|
5768
|
+
throw new Error('unreachable after loadSideDialogResponses persistence rethrow');
|
|
5613
5769
|
}
|
|
5614
5770
|
}
|
|
5615
|
-
static async
|
|
5771
|
+
static async loadSideDialogResponsesQueue(dialogId, status = 'running') {
|
|
5616
5772
|
try {
|
|
5617
5773
|
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
5618
|
-
const filePath = path.join(dialogPath, '
|
|
5774
|
+
const filePath = path.join(dialogPath, 'sideDialog-responses.json');
|
|
5619
5775
|
const content = await readPersistenceTextFile({
|
|
5620
5776
|
filePath,
|
|
5621
|
-
source: '
|
|
5777
|
+
source: 'sideDialog_responses',
|
|
5622
5778
|
format: 'json',
|
|
5623
5779
|
});
|
|
5624
5780
|
const parsed = parsePersistenceJson({
|
|
5625
5781
|
content,
|
|
5626
5782
|
filePath,
|
|
5627
|
-
source: '
|
|
5783
|
+
source: 'sideDialog_responses',
|
|
5628
5784
|
});
|
|
5629
|
-
if (!Array.isArray(parsed) || !parsed.every((item) =>
|
|
5785
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => isSideDialogResponseRecord(item))) {
|
|
5630
5786
|
throw buildInvalidPersistenceFileError({
|
|
5631
|
-
source: '
|
|
5787
|
+
source: 'sideDialog_responses',
|
|
5632
5788
|
format: 'json',
|
|
5633
5789
|
filePath,
|
|
5634
5790
|
});
|
|
5635
5791
|
}
|
|
5792
|
+
assertUniqueSideDialogResponseIds(parsed, `load queue rootId=${dialogId.rootId} selfId=${dialogId.selfId} status=${status}`);
|
|
5636
5793
|
return parsed;
|
|
5637
5794
|
}
|
|
5638
5795
|
catch (error) {
|
|
5639
5796
|
if (getErrorCode(error) === 'ENOENT') {
|
|
5640
5797
|
return [];
|
|
5641
5798
|
}
|
|
5642
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, '
|
|
5643
|
-
throw new Error('unreachable after
|
|
5799
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, 'loadSideDialogResponsesQueue', error);
|
|
5800
|
+
throw new Error('unreachable after loadSideDialogResponsesQueue persistence rethrow');
|
|
5644
5801
|
}
|
|
5645
5802
|
}
|
|
5646
|
-
static async
|
|
5647
|
-
const existing = await this.
|
|
5803
|
+
static async appendSideDialogResponse(dialogId, response, rootAnchor, status = 'running') {
|
|
5804
|
+
const existing = await this.loadSideDialogResponsesQueue(dialogId, status);
|
|
5648
5805
|
existing.push(response);
|
|
5649
|
-
await this.
|
|
5806
|
+
await this.saveSideDialogResponses(dialogId, existing, rootAnchor, status);
|
|
5650
5807
|
}
|
|
5651
|
-
static async
|
|
5808
|
+
static async takeSideDialogResponses(dialogId, status = 'running') {
|
|
5652
5809
|
try {
|
|
5653
5810
|
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
5654
|
-
const filePath = path.join(dialogPath, '
|
|
5655
|
-
const inflightPath = path.join(dialogPath, '
|
|
5811
|
+
const filePath = path.join(dialogPath, 'sideDialog-responses.json');
|
|
5812
|
+
const inflightPath = path.join(dialogPath, 'sideDialog-responses.processing.json');
|
|
5656
5813
|
// If a previous processing file exists, merge it back so it will be re-processed.
|
|
5657
5814
|
try {
|
|
5658
5815
|
await fs.promises.access(inflightPath);
|
|
@@ -5663,7 +5820,7 @@ class DialogPersistence {
|
|
|
5663
5820
|
}
|
|
5664
5821
|
}
|
|
5665
5822
|
if (await this.pathExists(inflightPath)) {
|
|
5666
|
-
await this.
|
|
5823
|
+
await this.rollbackTakenSideDialogResponses(dialogId, status);
|
|
5667
5824
|
}
|
|
5668
5825
|
try {
|
|
5669
5826
|
await fs.promises.rename(filePath, inflightPath);
|
|
@@ -5676,56 +5833,57 @@ class DialogPersistence {
|
|
|
5676
5833
|
}
|
|
5677
5834
|
const raw = await readPersistenceTextFile({
|
|
5678
5835
|
filePath: inflightPath,
|
|
5679
|
-
source: '
|
|
5836
|
+
source: 'sideDialog_responses',
|
|
5680
5837
|
format: 'json',
|
|
5681
5838
|
});
|
|
5682
5839
|
const parsed = parsePersistenceJson({
|
|
5683
5840
|
content: raw,
|
|
5684
5841
|
filePath: inflightPath,
|
|
5685
|
-
source: '
|
|
5842
|
+
source: 'sideDialog_responses',
|
|
5686
5843
|
});
|
|
5687
|
-
if (!Array.isArray(parsed) || !parsed.every((item) =>
|
|
5844
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => isSideDialogResponseRecord(item))) {
|
|
5688
5845
|
throw buildInvalidPersistenceFileError({
|
|
5689
|
-
source: '
|
|
5846
|
+
source: 'sideDialog_responses',
|
|
5690
5847
|
format: 'json',
|
|
5691
5848
|
filePath: inflightPath,
|
|
5692
5849
|
});
|
|
5693
5850
|
}
|
|
5851
|
+
assertUniqueSideDialogResponseIds(parsed, `take rootId=${dialogId.rootId} selfId=${dialogId.selfId} status=${status}`);
|
|
5694
5852
|
return parsed;
|
|
5695
5853
|
}
|
|
5696
5854
|
catch (error) {
|
|
5697
5855
|
if (getErrorCode(error) === 'ENOENT') {
|
|
5698
5856
|
return [];
|
|
5699
5857
|
}
|
|
5700
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, '
|
|
5701
|
-
throw new Error('unreachable after
|
|
5858
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, 'takeSideDialogResponses', error);
|
|
5859
|
+
throw new Error('unreachable after takeSideDialogResponses persistence rethrow');
|
|
5702
5860
|
}
|
|
5703
5861
|
}
|
|
5704
|
-
static async
|
|
5862
|
+
static async commitTakenSideDialogResponses(dialogId, status = 'running') {
|
|
5705
5863
|
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
5706
|
-
const inflightPath = path.join(dialogPath, '
|
|
5864
|
+
const inflightPath = path.join(dialogPath, 'sideDialog-responses.processing.json');
|
|
5707
5865
|
await fs.promises.rm(inflightPath, { force: true });
|
|
5708
5866
|
}
|
|
5709
|
-
static async
|
|
5867
|
+
static async rollbackTakenSideDialogResponses(dialogId, status = 'running') {
|
|
5710
5868
|
try {
|
|
5711
5869
|
const dialogPath = this.getDialogResponsesPath(dialogId, status);
|
|
5712
|
-
const filePath = path.join(dialogPath, '
|
|
5713
|
-
const inflightPath = path.join(dialogPath, '
|
|
5870
|
+
const filePath = path.join(dialogPath, 'sideDialog-responses.json');
|
|
5871
|
+
const inflightPath = path.join(dialogPath, 'sideDialog-responses.processing.json');
|
|
5714
5872
|
let inflight = [];
|
|
5715
5873
|
try {
|
|
5716
5874
|
const raw = await readPersistenceTextFile({
|
|
5717
5875
|
filePath: inflightPath,
|
|
5718
|
-
source: '
|
|
5876
|
+
source: 'sideDialog_responses',
|
|
5719
5877
|
format: 'json',
|
|
5720
5878
|
});
|
|
5721
5879
|
const parsed = parsePersistenceJson({
|
|
5722
5880
|
content: raw,
|
|
5723
5881
|
filePath: inflightPath,
|
|
5724
|
-
source: '
|
|
5882
|
+
source: 'sideDialog_responses',
|
|
5725
5883
|
});
|
|
5726
|
-
if (!Array.isArray(parsed) || !parsed.every((item) =>
|
|
5884
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => isSideDialogResponseRecord(item))) {
|
|
5727
5885
|
throw buildInvalidPersistenceFileError({
|
|
5728
|
-
source: '
|
|
5886
|
+
source: 'sideDialog_responses',
|
|
5729
5887
|
format: 'json',
|
|
5730
5888
|
filePath: inflightPath,
|
|
5731
5889
|
});
|
|
@@ -5742,17 +5900,17 @@ class DialogPersistence {
|
|
|
5742
5900
|
try {
|
|
5743
5901
|
const raw = await readPersistenceTextFile({
|
|
5744
5902
|
filePath,
|
|
5745
|
-
source: '
|
|
5903
|
+
source: 'sideDialog_responses',
|
|
5746
5904
|
format: 'json',
|
|
5747
5905
|
});
|
|
5748
5906
|
const parsed = parsePersistenceJson({
|
|
5749
5907
|
content: raw,
|
|
5750
5908
|
filePath,
|
|
5751
|
-
source: '
|
|
5909
|
+
source: 'sideDialog_responses',
|
|
5752
5910
|
});
|
|
5753
|
-
if (!Array.isArray(parsed) || !parsed.every((item) =>
|
|
5911
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => isSideDialogResponseRecord(item))) {
|
|
5754
5912
|
throw buildInvalidPersistenceFileError({
|
|
5755
|
-
source: '
|
|
5913
|
+
source: 'sideDialog_responses',
|
|
5756
5914
|
format: 'json',
|
|
5757
5915
|
filePath,
|
|
5758
5916
|
});
|
|
@@ -5764,12 +5922,8 @@ class DialogPersistence {
|
|
|
5764
5922
|
throw error;
|
|
5765
5923
|
}
|
|
5766
5924
|
}
|
|
5767
|
-
const
|
|
5768
|
-
|
|
5769
|
-
for (const r of merged) {
|
|
5770
|
-
byId.set(r.responseId, r);
|
|
5771
|
-
}
|
|
5772
|
-
const result = Array.from(byId.values());
|
|
5925
|
+
const result = [...inflight, ...primary];
|
|
5926
|
+
assertUniqueSideDialogResponseIds(result, `rollback rootId=${dialogId.rootId} selfId=${dialogId.selfId} status=${status}`);
|
|
5773
5927
|
const jsonContent = JSON.stringify(result, null, 2);
|
|
5774
5928
|
const tempFile = path.join(dialogPath, `.${path.basename(filePath)}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
5775
5929
|
await fs.promises.writeFile(tempFile, jsonContent, 'utf-8');
|
|
@@ -5777,16 +5931,25 @@ class DialogPersistence {
|
|
|
5777
5931
|
await fs.promises.rm(inflightPath, { force: true });
|
|
5778
5932
|
}
|
|
5779
5933
|
catch (error) {
|
|
5780
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, '
|
|
5781
|
-
throw new Error('unreachable after
|
|
5934
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, 'rollbackTakenSideDialogResponses', error);
|
|
5935
|
+
throw new Error('unreachable after rollbackTakenSideDialogResponses persistence rethrow');
|
|
5782
5936
|
}
|
|
5783
5937
|
}
|
|
5784
5938
|
/**
|
|
5785
|
-
* Save
|
|
5939
|
+
* Save main dialog metadata (write-once pattern)
|
|
5786
5940
|
*/
|
|
5787
|
-
static async
|
|
5941
|
+
static async saveMainDialogMetadata(dialogId, metadata, status = 'running') {
|
|
5788
5942
|
try {
|
|
5789
|
-
|
|
5943
|
+
if (dialogId.rootId !== dialogId.selfId) {
|
|
5944
|
+
throw new Error(`saveMainDialogMetadata expects a main dialog id: ${dialogId.valueOf()}`);
|
|
5945
|
+
}
|
|
5946
|
+
if (!isMainDialogMetadataFile(metadata)) {
|
|
5947
|
+
throw new Error(`Invalid main dialog metadata for ${dialogId.selfId}`);
|
|
5948
|
+
}
|
|
5949
|
+
if (metadata.id !== dialogId.selfId) {
|
|
5950
|
+
throw new Error(`Main dialog metadata id mismatch: dialogId=${dialogId.selfId} metadataId=${metadata.id}`);
|
|
5951
|
+
}
|
|
5952
|
+
const dialogPath = this.getMainDialogPath(dialogId, status);
|
|
5790
5953
|
// Ensure dialog directory exists first
|
|
5791
5954
|
await fs.promises.mkdir(dialogPath, { recursive: true });
|
|
5792
5955
|
// Atomic write operation
|
|
@@ -5806,30 +5969,32 @@ class DialogPersistence {
|
|
|
5806
5969
|
*/
|
|
5807
5970
|
static async saveDialogMetadata(dialogId, metadata, status = 'running') {
|
|
5808
5971
|
if (dialogId.rootId === dialogId.selfId) {
|
|
5809
|
-
if (!
|
|
5810
|
-
throw new Error(`Expected
|
|
5972
|
+
if (!isMainDialogMetadataFile(metadata)) {
|
|
5973
|
+
throw new Error(`Expected main dialog metadata for ${dialogId.selfId}`);
|
|
5811
5974
|
}
|
|
5812
|
-
return this.
|
|
5975
|
+
return this.saveMainDialogMetadata(dialogId, metadata, status);
|
|
5813
5976
|
}
|
|
5814
|
-
// For
|
|
5815
|
-
if (!
|
|
5816
|
-
throw new Error(`Expected
|
|
5977
|
+
// For sideDialogs, delegate to saveSideDialogMetadata
|
|
5978
|
+
if (!isSideDialogMetadataFile(metadata)) {
|
|
5979
|
+
throw new Error(`Expected sideDialog metadata for ${dialogId.selfId}`);
|
|
5817
5980
|
}
|
|
5818
|
-
return this.
|
|
5819
|
-
}
|
|
5820
|
-
/**
|
|
5821
|
-
* Save dialog metadata (legacy - use saveRootDialogMetadata instead)
|
|
5822
|
-
* @deprecated
|
|
5823
|
-
*/
|
|
5824
|
-
static async _saveDialogMetadata(dialogId, metadata, status = 'running') {
|
|
5825
|
-
return this.saveRootDialogMetadata(dialogId, metadata, status);
|
|
5981
|
+
return this.saveSideDialogMetadata(dialogId, metadata, status);
|
|
5826
5982
|
}
|
|
5827
5983
|
/**
|
|
5828
|
-
* Save
|
|
5984
|
+
* Save sideDialog metadata under the root dialog's sideDialogs directory.
|
|
5829
5985
|
*/
|
|
5830
|
-
static async
|
|
5986
|
+
static async saveSideDialogMetadata(dialogId, metadata, status = 'running') {
|
|
5831
5987
|
try {
|
|
5832
|
-
|
|
5988
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
5989
|
+
throw new Error(`saveSideDialogMetadata expects a sideDialog id: ${dialogId.valueOf()}`);
|
|
5990
|
+
}
|
|
5991
|
+
if (!isSideDialogMetadataFile(metadata)) {
|
|
5992
|
+
throw new Error(`Invalid sideDialog metadata for ${dialogId.selfId}`);
|
|
5993
|
+
}
|
|
5994
|
+
if (metadata.id !== dialogId.selfId) {
|
|
5995
|
+
throw new Error(`sideDialog metadata id mismatch: dialogId=${dialogId.selfId} metadataId=${metadata.id}`);
|
|
5996
|
+
}
|
|
5997
|
+
const subPath = this.getSideDialogPath(dialogId, status);
|
|
5833
5998
|
const metadataFilePath = path.join(subPath, 'dialog.yaml');
|
|
5834
5999
|
// Creation sites must ensure the directory exists first. Update paths intentionally do not
|
|
5835
6000
|
// recreate missing directories so quarantine can use "directory disappeared" as cancellation.
|
|
@@ -5839,33 +6004,259 @@ class DialogPersistence {
|
|
|
5839
6004
|
await this.renameWithRetry(tempFile, metadataFilePath);
|
|
5840
6005
|
}
|
|
5841
6006
|
catch (error) {
|
|
5842
|
-
log_1.log.error(`Failed to save
|
|
6007
|
+
log_1.log.error(`Failed to save sideDialog YAML for ${dialogId.selfId} under main dialog ${dialogId.rootId}:`, error);
|
|
5843
6008
|
throw error;
|
|
5844
6009
|
}
|
|
5845
6010
|
}
|
|
6011
|
+
static async saveSideDialogAskerStackState(dialogId, state, status = 'running') {
|
|
6012
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6013
|
+
throw new Error('saveSideDialogAskerStackState expects a sideDialog id');
|
|
6014
|
+
}
|
|
6015
|
+
if (!isDialogAskerStackState(state)) {
|
|
6016
|
+
throw new Error(`Invalid asker stack for dialog ${dialogId.selfId}`);
|
|
6017
|
+
}
|
|
6018
|
+
await this.saveDialogAskerStack(dialogId, state, status);
|
|
6019
|
+
}
|
|
6020
|
+
static async loadSideDialogAskerStackState(dialogId, status = 'running') {
|
|
6021
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6022
|
+
return null;
|
|
6023
|
+
}
|
|
6024
|
+
const stack = await this.loadDialogAskerStack(dialogId, status);
|
|
6025
|
+
return stack.askerStack.length === 0 ? null : stack;
|
|
6026
|
+
}
|
|
6027
|
+
static async loadSideDialogAssignmentFromAsker(dialogId, status = 'running') {
|
|
6028
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6029
|
+
throw new Error('loadSideDialogAssignmentFromAsker expects a sideDialog id');
|
|
6030
|
+
}
|
|
6031
|
+
const stack = await this.loadSideDialogAskerStackState(dialogId, status);
|
|
6032
|
+
if (!stack) {
|
|
6033
|
+
throw new Error(`Missing asker stack for sideDialog ${dialogId.selfId}`);
|
|
6034
|
+
}
|
|
6035
|
+
return getDialogAskerStackCurrentAssignment(stack);
|
|
6036
|
+
}
|
|
6037
|
+
static parseDialogAskerStackJsonlRows(args) {
|
|
6038
|
+
const askerStack = [];
|
|
6039
|
+
const rows = [];
|
|
6040
|
+
const rawLines = args.content.split(/(?<=\n)/u);
|
|
6041
|
+
let byteOffset = 0;
|
|
6042
|
+
for (let index = 0; index < rawLines.length; index += 1) {
|
|
6043
|
+
const rawLine = rawLines[index];
|
|
6044
|
+
const line = rawLine.endsWith('\n') ? rawLine.slice(0, -1) : rawLine;
|
|
6045
|
+
const startOffset = byteOffset;
|
|
6046
|
+
const endOffset = startOffset + Buffer.byteLength(rawLine, 'utf-8');
|
|
6047
|
+
byteOffset = endOffset;
|
|
6048
|
+
if (line.trim() === '')
|
|
6049
|
+
continue;
|
|
6050
|
+
const parsed = parsePersistenceJson({
|
|
6051
|
+
content: line,
|
|
6052
|
+
filePath: args.filePath,
|
|
6053
|
+
source: 'dialog_asker_stack',
|
|
6054
|
+
lineNumber: index + 1,
|
|
6055
|
+
});
|
|
6056
|
+
if (!isAskerDialogStackFrame(parsed)) {
|
|
6057
|
+
throw buildInvalidPersistenceFileError({
|
|
6058
|
+
source: 'dialog_asker_stack',
|
|
6059
|
+
format: 'jsonl',
|
|
6060
|
+
filePath: args.filePath,
|
|
6061
|
+
lineNumber: index + 1,
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
6064
|
+
askerStack.push(parsed);
|
|
6065
|
+
rows.push({
|
|
6066
|
+
frame: parsed,
|
|
6067
|
+
startOffset,
|
|
6068
|
+
endOffset,
|
|
6069
|
+
});
|
|
6070
|
+
}
|
|
6071
|
+
return { state: { askerStack }, rows };
|
|
6072
|
+
}
|
|
6073
|
+
static async appendDialogAskerStackFrames(dialogId, frames, status) {
|
|
6074
|
+
if (frames.length === 0)
|
|
6075
|
+
return;
|
|
6076
|
+
const filePath = this.getDialogAskerStackPath(dialogId, status);
|
|
6077
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
6078
|
+
const content = frames.map((frame) => `${JSON.stringify(frame)}\n`).join('');
|
|
6079
|
+
await fs.promises.appendFile(filePath, content, 'utf-8');
|
|
6080
|
+
}
|
|
6081
|
+
static async saveDialogAskerStack(dialogId, state, status = 'running') {
|
|
6082
|
+
if (!isDialogAskerStackState(state)) {
|
|
6083
|
+
throw new Error(`Invalid asker stack for dialog ${dialogId.selfId}`);
|
|
6084
|
+
}
|
|
6085
|
+
const filePath = this.getDialogAskerStackPath(dialogId, status);
|
|
6086
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
6087
|
+
await fs.promises.truncate(filePath, 0).catch(async (error) => {
|
|
6088
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
6089
|
+
await fs.promises.writeFile(filePath, '', 'utf-8');
|
|
6090
|
+
return;
|
|
6091
|
+
}
|
|
6092
|
+
throw error;
|
|
6093
|
+
});
|
|
6094
|
+
await this.appendDialogAskerStackFrames(dialogId, state.askerStack, status);
|
|
6095
|
+
}
|
|
6096
|
+
static async loadDialogAskerStack(dialogId, status = 'running') {
|
|
6097
|
+
return (await this.loadDialogAskerStackRows(dialogId, status)).state;
|
|
6098
|
+
}
|
|
6099
|
+
static async loadDialogAskerStackRows(dialogId, status) {
|
|
6100
|
+
const filePath = this.getDialogAskerStackPath(dialogId, status);
|
|
6101
|
+
try {
|
|
6102
|
+
const content = await readPersistenceTextFile({
|
|
6103
|
+
filePath,
|
|
6104
|
+
source: 'dialog_asker_stack',
|
|
6105
|
+
format: 'jsonl',
|
|
6106
|
+
});
|
|
6107
|
+
return { filePath, ...this.parseDialogAskerStackJsonlRows({ content, filePath }) };
|
|
6108
|
+
}
|
|
6109
|
+
catch (error) {
|
|
6110
|
+
if (getErrorCode(error) === 'ENOENT') {
|
|
6111
|
+
return { filePath, state: { askerStack: [] }, rows: [] };
|
|
6112
|
+
}
|
|
6113
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, 'loadDialogAskerStack', error);
|
|
6114
|
+
throw new Error('unreachable after loadDialogAskerStack persistence rethrow');
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
static async truncateDialogAskerStackToDepth(dialogId, depth, status) {
|
|
6118
|
+
const loaded = await this.loadDialogAskerStackRows(dialogId, status);
|
|
6119
|
+
if (!Number.isInteger(depth) || depth < 0 || depth > loaded.rows.length) {
|
|
6120
|
+
throw new Error(`asker stack truncate invariant violation: invalid depth ` +
|
|
6121
|
+
`(rootId=${dialogId.rootId}, selfId=${dialogId.selfId}, depth=${String(depth)}, size=${String(loaded.rows.length)})`);
|
|
6122
|
+
}
|
|
6123
|
+
const truncateOffset = depth === loaded.rows.length
|
|
6124
|
+
? (loaded.rows[loaded.rows.length - 1]?.endOffset ?? 0)
|
|
6125
|
+
: (loaded.rows[depth]?.startOffset ?? 0);
|
|
6126
|
+
await fs.promises.mkdir(path.dirname(loaded.filePath), { recursive: true });
|
|
6127
|
+
await fs.promises.truncate(loaded.filePath, truncateOffset).catch(async (error) => {
|
|
6128
|
+
if (getErrorCode(error) === 'ENOENT' && truncateOffset === 0) {
|
|
6129
|
+
await fs.promises.writeFile(loaded.filePath, '', 'utf-8');
|
|
6130
|
+
return;
|
|
6131
|
+
}
|
|
6132
|
+
throw error;
|
|
6133
|
+
});
|
|
6134
|
+
return { askerStack: loaded.rows.slice(0, depth).map((row) => row.frame) };
|
|
6135
|
+
}
|
|
6136
|
+
static async replaceDialogAskerStackFrameAndAppend(args) {
|
|
6137
|
+
const loaded = await this.loadDialogAskerStackRows(args.dialogId, args.status);
|
|
6138
|
+
const matchingIndexes = loaded.rows
|
|
6139
|
+
.map((row, index) => (args.findFrame(row.frame) ? index : -1))
|
|
6140
|
+
.filter((index) => index >= 0);
|
|
6141
|
+
if (matchingIndexes.length === 0) {
|
|
6142
|
+
throw new Error(args.missingFrameMessage);
|
|
6143
|
+
}
|
|
6144
|
+
if (matchingIndexes.length > 1) {
|
|
6145
|
+
throw new Error(`replace pending asker stack invariant violation: duplicate old frames ` +
|
|
6146
|
+
`(rootId=${args.dialogId.rootId}, selfId=${args.dialogId.selfId}, matches=${String(matchingIndexes.length)})`);
|
|
6147
|
+
}
|
|
6148
|
+
const replaceIndex = matchingIndexes[0];
|
|
6149
|
+
const replacedRow = loaded.rows[replaceIndex];
|
|
6150
|
+
await fs.promises.truncate(loaded.filePath, replacedRow.startOffset);
|
|
6151
|
+
const retainedBefore = loaded.rows.slice(0, replaceIndex).map((row) => row.frame);
|
|
6152
|
+
const retainedAfter = loaded.rows.slice(replaceIndex + 1).map((row) => row.frame);
|
|
6153
|
+
await this.appendDialogAskerStackFrames(args.dialogId, [args.appendFrame, ...retainedAfter], args.status);
|
|
6154
|
+
return {
|
|
6155
|
+
askerStack: [...retainedBefore, args.appendFrame, ...retainedAfter],
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
6158
|
+
static async pushTellaskReplyObligation(dialogId, obligation, status = 'running') {
|
|
6159
|
+
const frame = {
|
|
6160
|
+
kind: 'asker_dialog_stack_frame',
|
|
6161
|
+
askerDialogId: obligation.targetDialogId,
|
|
6162
|
+
tellaskReplyObligation: obligation,
|
|
6163
|
+
};
|
|
6164
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6165
|
+
await this.appendDialogAskerStackFrames(dialogId, [frame], status);
|
|
6166
|
+
return;
|
|
6167
|
+
}
|
|
6168
|
+
const state = await this.loadSideDialogAskerStackState(dialogId, status);
|
|
6169
|
+
if (!state) {
|
|
6170
|
+
throw new Error(`Missing asker stack for sideDialog ${dialogId.selfId}`);
|
|
6171
|
+
}
|
|
6172
|
+
await this.appendDialogAskerStackFrames(dialogId, [frame], status);
|
|
6173
|
+
}
|
|
6174
|
+
static async setActiveTellaskReplyObligation(dialogId, obligation, status = 'running') {
|
|
6175
|
+
if (obligation !== undefined) {
|
|
6176
|
+
await this.pushTellaskReplyObligation(dialogId, obligation, status);
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6180
|
+
const stackFile = await this.loadDialogAskerStack(dialogId, status);
|
|
6181
|
+
if (stackFile.askerStack.length === 0)
|
|
6182
|
+
return;
|
|
6183
|
+
await this.truncateDialogAskerStackToDepth(dialogId, stackFile.askerStack.length - 1, status);
|
|
6184
|
+
return;
|
|
6185
|
+
}
|
|
6186
|
+
const state = await this.loadSideDialogAskerStackState(dialogId, status);
|
|
6187
|
+
if (!state) {
|
|
6188
|
+
throw new Error(`Missing asker stack for sideDialog ${dialogId.selfId}`);
|
|
6189
|
+
}
|
|
6190
|
+
if (state.askerStack.length > 1) {
|
|
6191
|
+
await this.truncateDialogAskerStackToDepth(dialogId, state.askerStack.length - 1, status);
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6194
|
+
const top = getDialogAskerStackTop(state);
|
|
6195
|
+
await this.truncateDialogAskerStackToDepth(dialogId, 0, status);
|
|
6196
|
+
if (top.assignmentFromAsker === undefined) {
|
|
6197
|
+
return;
|
|
6198
|
+
}
|
|
6199
|
+
await this.appendDialogAskerStackFrames(dialogId, [
|
|
6200
|
+
{
|
|
6201
|
+
kind: 'asker_dialog_stack_frame',
|
|
6202
|
+
askerDialogId: top.askerDialogId,
|
|
6203
|
+
assignmentFromAsker: top.assignmentFromAsker,
|
|
6204
|
+
},
|
|
6205
|
+
], status);
|
|
6206
|
+
}
|
|
6207
|
+
static async loadActiveTellaskReplyObligation(dialogId, status = 'running') {
|
|
6208
|
+
if (dialogId.rootId === dialogId.selfId) {
|
|
6209
|
+
const stack = (await this.loadDialogAskerStack(dialogId, status)).askerStack;
|
|
6210
|
+
return stack[stack.length - 1]?.tellaskReplyObligation;
|
|
6211
|
+
}
|
|
6212
|
+
return getDialogAskerStackTop(await this.requireSideDialogAskerStackState(dialogId, status))
|
|
6213
|
+
.tellaskReplyObligation;
|
|
6214
|
+
}
|
|
6215
|
+
static async requireSideDialogAskerStackState(dialogId, status) {
|
|
6216
|
+
const state = await this.loadSideDialogAskerStackState(dialogId, status);
|
|
6217
|
+
if (!state) {
|
|
6218
|
+
throw new Error(`Missing asker stack for sideDialog ${dialogId.selfId}`);
|
|
6219
|
+
}
|
|
6220
|
+
return state;
|
|
6221
|
+
}
|
|
5846
6222
|
/**
|
|
5847
|
-
* Update
|
|
6223
|
+
* Update assignmentFromAsker for an existing sideDialog.
|
|
5848
6224
|
*/
|
|
5849
|
-
static async
|
|
6225
|
+
static async updateSideDialogAssignment(dialogId, assignment, status = 'running', options) {
|
|
5850
6226
|
if (dialogId.rootId === dialogId.selfId) {
|
|
5851
|
-
throw new Error('
|
|
6227
|
+
throw new Error('updateSideDialogAssignment expects a sideDialog id');
|
|
5852
6228
|
}
|
|
5853
6229
|
const metadata = await this.loadDialogMetadata(dialogId, status);
|
|
5854
|
-
if (!metadata || !
|
|
5855
|
-
throw new Error(`Missing dialog metadata for
|
|
6230
|
+
if (!metadata || !isSideDialogMetadataFile(metadata)) {
|
|
6231
|
+
throw new Error(`Missing dialog metadata for sideDialog ${dialogId.selfId}`);
|
|
6232
|
+
}
|
|
6233
|
+
const nextAssignmentFrame = buildAssignmentAskerStackFrame({
|
|
6234
|
+
askerDialogId: assignment.askerDialogId,
|
|
6235
|
+
assignment,
|
|
6236
|
+
});
|
|
6237
|
+
if (options?.replacePendingCallId === undefined) {
|
|
6238
|
+
await this.requireSideDialogAskerStackState(dialogId, status);
|
|
6239
|
+
await this.appendDialogAskerStackFrames(dialogId, [nextAssignmentFrame], status);
|
|
6240
|
+
}
|
|
6241
|
+
else {
|
|
6242
|
+
const replacePendingAskerDialogId = options.replacePendingAskerDialogId ?? assignment.askerDialogId;
|
|
6243
|
+
await this.replaceDialogAskerStackFrameAndAppend({
|
|
6244
|
+
dialogId,
|
|
6245
|
+
status,
|
|
6246
|
+
findFrame: (frame) => frame.assignmentFromAsker?.askerDialogId === replacePendingAskerDialogId &&
|
|
6247
|
+
frame.assignmentFromAsker.callId === options.replacePendingCallId,
|
|
6248
|
+
missingFrameMessage: `replace pending asker stack invariant violation: missing old frame ` +
|
|
6249
|
+
`(rootId=${dialogId.rootId}, selfId=${dialogId.selfId}, askerDialogId=${replacePendingAskerDialogId}, callId=${options.replacePendingCallId})`,
|
|
6250
|
+
appendFrame: nextAssignmentFrame,
|
|
6251
|
+
});
|
|
5856
6252
|
}
|
|
5857
|
-
const next = {
|
|
5858
|
-
...metadata,
|
|
5859
|
-
assignmentFromSup: assignment,
|
|
5860
|
-
};
|
|
5861
|
-
await this.saveSubdialogMetadata(dialogId, next, status);
|
|
5862
6253
|
}
|
|
5863
6254
|
/**
|
|
5864
|
-
* Load
|
|
6255
|
+
* Load main dialog metadata
|
|
5865
6256
|
*/
|
|
5866
|
-
static async
|
|
6257
|
+
static async loadMainDialogMetadata(dialogId, status = 'running') {
|
|
5867
6258
|
try {
|
|
5868
|
-
const dialogPath = this.
|
|
6259
|
+
const dialogPath = this.getMainDialogPath(dialogId, status);
|
|
5869
6260
|
const metadataFilePath = path.join(dialogPath, 'dialog.yaml');
|
|
5870
6261
|
try {
|
|
5871
6262
|
const content = await readPersistenceTextFile({
|
|
@@ -5878,7 +6269,7 @@ class DialogPersistence {
|
|
|
5878
6269
|
filePath: metadataFilePath,
|
|
5879
6270
|
source: 'dialog_metadata',
|
|
5880
6271
|
});
|
|
5881
|
-
if (!
|
|
6272
|
+
if (!isMainDialogMetadataFile(parsed)) {
|
|
5882
6273
|
throw buildInvalidPersistenceFileError({
|
|
5883
6274
|
source: 'dialog_metadata',
|
|
5884
6275
|
format: 'yaml',
|
|
@@ -5899,8 +6290,8 @@ class DialogPersistence {
|
|
|
5899
6290
|
if (getErrorCode(error) === 'ENOENT') {
|
|
5900
6291
|
return null;
|
|
5901
6292
|
}
|
|
5902
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, '
|
|
5903
|
-
throw new Error('unreachable after
|
|
6293
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(dialogId, status, 'loadMainDialogMetadata', error);
|
|
6294
|
+
throw new Error('unreachable after loadMainDialogMetadata persistence rethrow');
|
|
5904
6295
|
}
|
|
5905
6296
|
}
|
|
5906
6297
|
catch (error) {
|
|
@@ -5909,17 +6300,14 @@ class DialogPersistence {
|
|
|
5909
6300
|
}
|
|
5910
6301
|
}
|
|
5911
6302
|
/**
|
|
5912
|
-
* Load dialog metadata
|
|
6303
|
+
* Load dialog metadata from the path implied by the DialogID.
|
|
5913
6304
|
*/
|
|
5914
6305
|
static async loadDialogMetadata(dialogId, status = 'running') {
|
|
5915
|
-
// For root dialogs, use the selfId
|
|
5916
|
-
// For subdialogs, this is more complex - we need to find the root metadata
|
|
5917
6306
|
if (dialogId.rootId === dialogId.selfId) {
|
|
5918
|
-
return this.
|
|
6307
|
+
return this.loadMainDialogMetadata(dialogId, status);
|
|
5919
6308
|
}
|
|
5920
|
-
|
|
5921
|
-
const
|
|
5922
|
-
const metadataFilePath = path.join(subdialogPath, 'dialog.yaml');
|
|
6309
|
+
const sideDialogPath = this.getSideDialogPath(dialogId, status);
|
|
6310
|
+
const metadataFilePath = path.join(sideDialogPath, 'dialog.yaml');
|
|
5923
6311
|
try {
|
|
5924
6312
|
const content = await readPersistenceTextFile({
|
|
5925
6313
|
filePath: metadataFilePath,
|
|
@@ -5931,13 +6319,29 @@ class DialogPersistence {
|
|
|
5931
6319
|
filePath: metadataFilePath,
|
|
5932
6320
|
source: 'dialog_metadata',
|
|
5933
6321
|
});
|
|
5934
|
-
if (!
|
|
6322
|
+
if (!isSideDialogMetadataFile(parsed)) {
|
|
5935
6323
|
throw buildInvalidPersistenceFileError({
|
|
5936
6324
|
source: 'dialog_metadata',
|
|
5937
6325
|
format: 'yaml',
|
|
5938
6326
|
filePath: metadataFilePath,
|
|
5939
6327
|
});
|
|
5940
6328
|
}
|
|
6329
|
+
if (parsed.id !== dialogId.selfId) {
|
|
6330
|
+
throw buildInvalidPersistenceFileError({
|
|
6331
|
+
source: 'dialog_metadata',
|
|
6332
|
+
format: 'yaml',
|
|
6333
|
+
filePath: metadataFilePath,
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
const askerStack = await this.loadSideDialogAskerStackState(dialogId, status);
|
|
6337
|
+
if (!askerStack) {
|
|
6338
|
+
throw buildInvalidPersistenceFileError({
|
|
6339
|
+
source: 'dialog_asker_stack',
|
|
6340
|
+
format: 'jsonl',
|
|
6341
|
+
filePath: this.getDialogAskerStackPath(dialogId, status),
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
getDialogAskerStackCurrentAssignment(askerStack);
|
|
5941
6345
|
return parsed;
|
|
5942
6346
|
}
|
|
5943
6347
|
catch (error) {
|
|
@@ -5954,7 +6358,7 @@ class DialogPersistence {
|
|
|
5954
6358
|
static async writeDialogLatestToDisk(dialogId, latest, status = 'running', cancellationToken) {
|
|
5955
6359
|
try {
|
|
5956
6360
|
if (cancellationToken) {
|
|
5957
|
-
this.
|
|
6361
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, 'writeDialogLatestToDisk:start');
|
|
5958
6362
|
}
|
|
5959
6363
|
const dialogPath = this.getDialogEventsPath(dialogId, status);
|
|
5960
6364
|
const latestFilePath = path.join(dialogPath, 'latest.yaml');
|
|
@@ -5989,7 +6393,7 @@ class DialogPersistence {
|
|
|
5989
6393
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
5990
6394
|
try {
|
|
5991
6395
|
if (cancellationToken) {
|
|
5992
|
-
this.
|
|
6396
|
+
this.assertMainDialogWriteBackNotCanceled(cancellationToken, `renameWithRetry:${path.basename(destination)}:before-rename`);
|
|
5993
6397
|
}
|
|
5994
6398
|
await fs.promises.rename(source, destination);
|
|
5995
6399
|
return;
|
|
@@ -6045,10 +6449,10 @@ class DialogPersistence {
|
|
|
6045
6449
|
static async mutateDialogLatest(dialogId, mutator, status = 'running', cancellationToken) {
|
|
6046
6450
|
const key = this.getLatestWriteBackKey(dialogId, status);
|
|
6047
6451
|
const mutex = this.getLatestWriteBackMutex(key);
|
|
6048
|
-
const effectiveCancellationToken = cancellationToken ?? this.
|
|
6452
|
+
const effectiveCancellationToken = cancellationToken ?? this.createMainDialogWriteBackCancellationToken(dialogId, status);
|
|
6049
6453
|
const release = await mutex.acquire();
|
|
6050
6454
|
try {
|
|
6051
|
-
this.
|
|
6455
|
+
this.assertMainDialogWriteBackNotCanceled(effectiveCancellationToken, 'mutateDialogLatest');
|
|
6052
6456
|
const staged = this.latestWriteBack.get(key);
|
|
6053
6457
|
const latestFromDisk = staged ? null : await this.loadDialogLatestFromDisk(dialogId, status);
|
|
6054
6458
|
const existing = (staged ? staged.latest : latestFromDisk) || {
|
|
@@ -6089,7 +6493,7 @@ class DialogPersistence {
|
|
|
6089
6493
|
latestSource: staged ? 'staged' : latestFromDisk ? 'disk' : 'default_bootstrap',
|
|
6090
6494
|
latestWriteBackKey: key,
|
|
6091
6495
|
});
|
|
6092
|
-
this.
|
|
6496
|
+
this.assertMainDialogWriteBackNotCanceled(effectiveCancellationToken, 'mutateDialogLatest:before-stage');
|
|
6093
6497
|
const pending = this.latestWriteBack.get(key);
|
|
6094
6498
|
if (!pending) {
|
|
6095
6499
|
const timer = setTimeout(() => {
|
|
@@ -6149,7 +6553,7 @@ class DialogPersistence {
|
|
|
6149
6553
|
return;
|
|
6150
6554
|
clearTimeout(entry.timer);
|
|
6151
6555
|
const latestToWrite = entry.latest;
|
|
6152
|
-
const cancellationToken = this.
|
|
6556
|
+
const cancellationToken = this.createMainDialogWriteBackCancellationToken(entry.dialogId, entry.status);
|
|
6153
6557
|
const inFlight = this.writeDialogLatestToDisk(entry.dialogId, latestToWrite, entry.status, cancellationToken);
|
|
6154
6558
|
captured = {
|
|
6155
6559
|
dialogId: entry.dialogId,
|
|
@@ -6340,18 +6744,18 @@ class DialogPersistence {
|
|
|
6340
6744
|
};
|
|
6341
6745
|
}
|
|
6342
6746
|
/**
|
|
6343
|
-
* Count
|
|
6747
|
+
* Count sideDialogs under a main dialog (no single-layer listing exposed)
|
|
6344
6748
|
*/
|
|
6345
|
-
static async
|
|
6346
|
-
if (
|
|
6347
|
-
throw new Error(`
|
|
6749
|
+
static async countAllSideDialogsUnderRoot(mainDialogId, status = 'running') {
|
|
6750
|
+
if (mainDialogId.rootId !== mainDialogId.selfId) {
|
|
6751
|
+
throw new Error(`countAllSideDialogsUnderRoot invariant violation: expected main dialog id, got ${mainDialogId.valueOf()}`);
|
|
6348
6752
|
}
|
|
6349
6753
|
try {
|
|
6350
6754
|
const dialogIds = await this.listAllDialogIds(status);
|
|
6351
|
-
return dialogIds.filter((dialogId) => dialogId.rootId ===
|
|
6755
|
+
return dialogIds.filter((dialogId) => dialogId.rootId === mainDialogId.rootId && dialogId.selfId !== dialogId.rootId).length;
|
|
6352
6756
|
}
|
|
6353
6757
|
catch (error) {
|
|
6354
|
-
log_1.log.error(`Failed to count all
|
|
6758
|
+
log_1.log.error(`Failed to count all sideDialogs under root ${mainDialogId.selfId}:`, error);
|
|
6355
6759
|
throw error;
|
|
6356
6760
|
}
|
|
6357
6761
|
}
|
|
@@ -6359,22 +6763,22 @@ class DialogPersistence {
|
|
|
6359
6763
|
/**
|
|
6360
6764
|
* Restore complete dialog tree from disk
|
|
6361
6765
|
*/
|
|
6362
|
-
static async restoreDialogTree(
|
|
6766
|
+
static async restoreDialogTree(mainDialogId, status = 'running') {
|
|
6363
6767
|
try {
|
|
6364
|
-
// First restore the
|
|
6365
|
-
const rootState = await this.restoreDialog(
|
|
6768
|
+
// First restore the main dialog
|
|
6769
|
+
const rootState = await this.restoreDialog(mainDialogId, status);
|
|
6366
6770
|
if (!rootState) {
|
|
6367
6771
|
return null;
|
|
6368
6772
|
}
|
|
6369
|
-
// Recursively restore
|
|
6370
|
-
const
|
|
6371
|
-
for (const
|
|
6372
|
-
await this.restoreDialogTree(new dialog_1.DialogID(
|
|
6773
|
+
// Recursively restore sideDialogs
|
|
6774
|
+
const sideDialogIds = await this.listSideDialogIdsUnderRoot(mainDialogId, status);
|
|
6775
|
+
for (const sideDialogId of sideDialogIds) {
|
|
6776
|
+
await this.restoreDialogTree(new dialog_1.DialogID(sideDialogId, mainDialogId.rootId), status);
|
|
6373
6777
|
}
|
|
6374
6778
|
return rootState;
|
|
6375
6779
|
}
|
|
6376
6780
|
catch (error) {
|
|
6377
|
-
log_1.log.error(`Failed to restore dialog tree for ${
|
|
6781
|
+
log_1.log.error(`Failed to restore dialog tree for ${mainDialogId.valueOf()}:`, error);
|
|
6378
6782
|
return null;
|
|
6379
6783
|
}
|
|
6380
6784
|
}
|
|
@@ -6532,8 +6936,8 @@ class DialogPersistence {
|
|
|
6532
6936
|
status: event.status,
|
|
6533
6937
|
content: event.content,
|
|
6534
6938
|
contentItems: event.contentItems,
|
|
6535
|
-
...(event.
|
|
6536
|
-
...(event.
|
|
6939
|
+
...(event.callSiteCourse !== undefined ? { callSiteCourse: event.callSiteCourse } : {}),
|
|
6940
|
+
...(event.callSiteGenseq !== undefined ? { callSiteGenseq: event.callSiteGenseq } : {}),
|
|
6537
6941
|
call: event.call,
|
|
6538
6942
|
responder: event.responder,
|
|
6539
6943
|
...(event.route ? { route: event.route } : {}),
|
|
@@ -6564,7 +6968,7 @@ class DialogPersistence {
|
|
|
6564
6968
|
genseq: event.genseq,
|
|
6565
6969
|
content: event.content,
|
|
6566
6970
|
contentItems: event.contentItems,
|
|
6567
|
-
|
|
6971
|
+
callSiteCourse: event.callSiteCourse,
|
|
6568
6972
|
carryoverCourse: event.carryoverCourse,
|
|
6569
6973
|
responderId: event.responderId,
|
|
6570
6974
|
callName: event.callName,
|
|
@@ -6599,23 +7003,23 @@ class DialogPersistence {
|
|
|
6599
7003
|
contextHealth = event.contextHealth;
|
|
6600
7004
|
}
|
|
6601
7005
|
break;
|
|
6602
|
-
case '
|
|
7006
|
+
case 'sideDialog_request_record':
|
|
6603
7007
|
// These events are handled separately in dialog restoration
|
|
6604
7008
|
// Skip them for message reconstruction
|
|
6605
7009
|
break;
|
|
6606
7010
|
case 'tellask_call_anchor_record':
|
|
6607
|
-
// This record is UI navigation metadata for deep links in
|
|
7011
|
+
// This record is UI navigation metadata for deep links in tellaskee dialogs.
|
|
6608
7012
|
// It does not contribute to model context or chat transcript reconstruction.
|
|
6609
7013
|
break;
|
|
6610
7014
|
case 'ui_only_markdown_record':
|
|
6611
7015
|
// UI-only records are replay-only rendering facts. They do not enter dialog messages or ctx.
|
|
6612
7016
|
break;
|
|
6613
|
-
case '
|
|
7017
|
+
case 'sideDialog_created_record':
|
|
6614
7018
|
case 'reminders_reconciled_record':
|
|
6615
7019
|
case 'questions4human_reconciled_record':
|
|
6616
|
-
case '
|
|
6617
|
-
case '
|
|
6618
|
-
case '
|
|
7020
|
+
case 'pending_sideDialogs_reconciled_record':
|
|
7021
|
+
case 'sideDialog_registry_reconciled_record':
|
|
7022
|
+
case 'sideDialog_responses_reconciled_record':
|
|
6619
7023
|
break;
|
|
6620
7024
|
default:
|
|
6621
7025
|
log_1.log.warn(`Unknown event type in rebuildFromEvents`, undefined, { event });
|
|
@@ -6655,36 +7059,36 @@ class DialogPersistence {
|
|
|
6655
7059
|
}
|
|
6656
7060
|
}
|
|
6657
7061
|
/**
|
|
6658
|
-
* Delete a
|
|
7062
|
+
* Delete a main dialog directory (including sideDialogs) from disk.
|
|
6659
7063
|
* Caller must provide the source status explicitly.
|
|
6660
7064
|
*/
|
|
6661
|
-
static async
|
|
6662
|
-
if (
|
|
6663
|
-
throw new Error('
|
|
7065
|
+
static async deleteMainDialog(mainDialogId, fromStatus) {
|
|
7066
|
+
if (mainDialogId.selfId !== mainDialogId.rootId) {
|
|
7067
|
+
throw new Error('deleteMainDialog expects a main dialog id');
|
|
6664
7068
|
}
|
|
6665
|
-
const exists = await this.
|
|
7069
|
+
const exists = await this.loadMainDialogMetadata(mainDialogId, fromStatus);
|
|
6666
7070
|
if (!exists)
|
|
6667
7071
|
return false;
|
|
6668
7072
|
// Best-effort cleanup: remove the dialog from all status directories to avoid leaving behind
|
|
6669
7073
|
// orphaned placeholder paths (e.g. `run/<id>/latest.yaml`) after a delete.
|
|
6670
7074
|
for (const candidate of PERSISTABLE_DIALOG_STATUSES) {
|
|
6671
|
-
this.
|
|
6672
|
-
const candidatePath = this.
|
|
7075
|
+
this.cancelMainDialogWriteBacks(mainDialogId, candidate);
|
|
7076
|
+
const candidatePath = this.getMainDialogPath(mainDialogId, candidate);
|
|
6673
7077
|
await fs.promises.rm(candidatePath, { recursive: true, force: true });
|
|
6674
7078
|
}
|
|
6675
7079
|
return true;
|
|
6676
7080
|
}
|
|
6677
7081
|
// === REGISTRY PERSISTENCE ===
|
|
6678
7082
|
/**
|
|
6679
|
-
* Save
|
|
7083
|
+
* Save sideDialog registry (TYPE B entries).
|
|
6680
7084
|
*/
|
|
6681
|
-
static async
|
|
7085
|
+
static async saveSideDialogRegistry(mainDialogId, entries, status = 'running') {
|
|
6682
7086
|
try {
|
|
6683
|
-
const dialogPath = this.getDialogResponsesPath(
|
|
7087
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
6684
7088
|
const registryFilePath = path.join(dialogPath, 'registry.yaml');
|
|
6685
7089
|
const serializableEntries = entries.map((entry) => ({
|
|
6686
7090
|
key: entry.key,
|
|
6687
|
-
|
|
7091
|
+
sideDialogId: entry.sideDialogId.selfId,
|
|
6688
7092
|
agentId: entry.agentId,
|
|
6689
7093
|
sessionSlug: entry.sessionSlug,
|
|
6690
7094
|
}));
|
|
@@ -6694,30 +7098,30 @@ class DialogPersistence {
|
|
|
6694
7098
|
await this.renameWithRetry(tempFile, registryFilePath);
|
|
6695
7099
|
}
|
|
6696
7100
|
catch (error) {
|
|
6697
|
-
log_1.log.error(`Failed to save
|
|
7101
|
+
log_1.log.error(`Failed to save sideDialog registry for dialog ${mainDialogId}:`, error);
|
|
6698
7102
|
throw error;
|
|
6699
7103
|
}
|
|
6700
7104
|
}
|
|
6701
7105
|
/**
|
|
6702
|
-
* Load
|
|
7106
|
+
* Load sideDialog registry.
|
|
6703
7107
|
*/
|
|
6704
|
-
static async
|
|
7108
|
+
static async loadSideDialogRegistry(mainDialogId, status = 'running') {
|
|
6705
7109
|
try {
|
|
6706
|
-
const dialogPath = this.getDialogResponsesPath(
|
|
7110
|
+
const dialogPath = this.getDialogResponsesPath(mainDialogId, status);
|
|
6707
7111
|
const registryFilePath = path.join(dialogPath, 'registry.yaml');
|
|
6708
7112
|
const content = await readPersistenceTextFile({
|
|
6709
7113
|
filePath: registryFilePath,
|
|
6710
|
-
source: '
|
|
7114
|
+
source: 'sideDialog_registry',
|
|
6711
7115
|
format: 'yaml',
|
|
6712
7116
|
});
|
|
6713
7117
|
const parsed = parsePersistenceYaml({
|
|
6714
7118
|
content,
|
|
6715
7119
|
filePath: registryFilePath,
|
|
6716
|
-
source: '
|
|
7120
|
+
source: 'sideDialog_registry',
|
|
6717
7121
|
});
|
|
6718
7122
|
if (!isRecord(parsed) || !Array.isArray(parsed.entries)) {
|
|
6719
7123
|
throw buildInvalidPersistenceFileError({
|
|
6720
|
-
source: '
|
|
7124
|
+
source: 'sideDialog_registry',
|
|
6721
7125
|
format: 'yaml',
|
|
6722
7126
|
filePath: registryFilePath,
|
|
6723
7127
|
});
|
|
@@ -6725,18 +7129,18 @@ class DialogPersistence {
|
|
|
6725
7129
|
const entries = parsed.entries.map((entry) => {
|
|
6726
7130
|
if (!isRecord(entry) ||
|
|
6727
7131
|
typeof entry.key !== 'string' ||
|
|
6728
|
-
typeof entry.
|
|
7132
|
+
typeof entry.sideDialogId !== 'string' ||
|
|
6729
7133
|
typeof entry.agentId !== 'string' ||
|
|
6730
7134
|
(entry.sessionSlug !== undefined && typeof entry.sessionSlug !== 'string')) {
|
|
6731
7135
|
throw buildInvalidPersistenceFileError({
|
|
6732
|
-
source: '
|
|
7136
|
+
source: 'sideDialog_registry',
|
|
6733
7137
|
format: 'yaml',
|
|
6734
7138
|
filePath: registryFilePath,
|
|
6735
7139
|
});
|
|
6736
7140
|
}
|
|
6737
7141
|
return {
|
|
6738
7142
|
key: entry.key,
|
|
6739
|
-
|
|
7143
|
+
sideDialogId: new dialog_1.DialogID(entry.sideDialogId, mainDialogId.rootId),
|
|
6740
7144
|
agentId: entry.agentId,
|
|
6741
7145
|
sessionSlug: entry.sessionSlug,
|
|
6742
7146
|
};
|
|
@@ -6747,8 +7151,8 @@ class DialogPersistence {
|
|
|
6747
7151
|
if (getErrorCode(error) === 'ENOENT') {
|
|
6748
7152
|
return [];
|
|
6749
7153
|
}
|
|
6750
|
-
await this.rethrowAfterQuarantiningDialogPersistenceProblem(
|
|
6751
|
-
throw new Error('unreachable after
|
|
7154
|
+
await this.rethrowAfterQuarantiningDialogPersistenceProblem(mainDialogId, status, 'loadSideDialogRegistry', error);
|
|
7155
|
+
throw new Error('unreachable after loadSideDialogRegistry persistence rethrow');
|
|
6752
7156
|
}
|
|
6753
7157
|
}
|
|
6754
7158
|
}
|
|
@@ -6758,16 +7162,16 @@ DialogPersistence.MALFORMED_DIR = 'malformed';
|
|
|
6758
7162
|
DialogPersistence.RUN_DIR = 'run';
|
|
6759
7163
|
DialogPersistence.DONE_DIR = 'done';
|
|
6760
7164
|
DialogPersistence.ARCHIVE_DIR = 'archive';
|
|
6761
|
-
DialogPersistence.
|
|
6762
|
-
DialogPersistence.
|
|
7165
|
+
DialogPersistence.SIDE_DIALOGS_DIR = 'sideDialogs';
|
|
7166
|
+
DialogPersistence.quarantinedMainDialogScopes = new Set();
|
|
6763
7167
|
DialogPersistence.LATEST_WRITEBACK_WINDOW_MS = 300;
|
|
6764
7168
|
DialogPersistence.Q4H_WRITEBACK_WINDOW_MS = 300;
|
|
6765
|
-
DialogPersistence.
|
|
7169
|
+
DialogPersistence.PENDING_SIDE_DIALOGS_WRITEBACK_WINDOW_MS = 300;
|
|
6766
7170
|
DialogPersistence.latestWriteBackMutexes = new Map();
|
|
6767
7171
|
DialogPersistence.latestWriteBack = new Map();
|
|
6768
7172
|
DialogPersistence.q4hWriteBackMutexes = new Map();
|
|
6769
7173
|
DialogPersistence.q4hWriteBack = new Map();
|
|
6770
|
-
DialogPersistence.
|
|
6771
|
-
DialogPersistence.
|
|
7174
|
+
DialogPersistence.pendingSideDialogsWriteBackMutexes = new Map();
|
|
7175
|
+
DialogPersistence.pendingSideDialogsWriteBack = new Map();
|
|
6772
7176
|
DialogPersistence.courseAppendMutexes = new Map();
|
|
6773
|
-
DialogPersistence.
|
|
7177
|
+
DialogPersistence.mainDialogWriteBackCancelGenerations = new Map();
|