dominds 0.1.0

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