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