dominds 1.23.10 → 1.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/dist/apps/runtime.js +2 -2
  2. package/dist/dialog-display-state.d.ts +2 -3
  3. package/dist/dialog-display-state.js +53 -145
  4. package/dist/dialog-drive-work.d.ts +4 -0
  5. package/dist/dialog-drive-work.js +40 -0
  6. package/dist/dialog-fork.d.ts +0 -1
  7. package/dist/dialog-fork.js +28 -32
  8. package/dist/dialog-generation-run.d.ts +4 -0
  9. package/dist/dialog-generation-run.js +24 -0
  10. package/dist/dialog-global-registry.d.ts +7 -8
  11. package/dist/dialog-global-registry.js +39 -33
  12. package/dist/dialog-instance-registry.js +12 -12
  13. package/dist/dialog-interruption.js +2 -2
  14. package/dist/dialog-latest-state.d.ts +4 -0
  15. package/dist/dialog-latest-state.js +14 -0
  16. package/dist/dialog.d.ts +31 -30
  17. package/dist/dialog.js +146 -56
  18. package/dist/docs/agent-priming.md +1 -1
  19. package/dist/docs/agent-priming.zh.md +1 -1
  20. package/dist/docs/app-constitution.md +1 -1
  21. package/dist/docs/app-constitution.zh.md +1 -1
  22. package/dist/docs/dialog-persistence.md +2 -2
  23. package/dist/docs/dialog-persistence.zh.md +2 -2
  24. package/dist/docs/dialog-system.md +72 -75
  25. package/dist/docs/dialog-system.zh.md +79 -82
  26. package/dist/docs/diligence-push.md +30 -40
  27. package/dist/docs/diligence-push.zh.md +15 -20
  28. package/dist/docs/dominds-agent-collaboration.zh.md +12 -10
  29. package/dist/docs/dominds-terminology.md +5 -2
  30. package/dist/docs/idle-reminder-wake.md +1 -1
  31. package/dist/docs/idle-reminder-wake.zh.md +1 -1
  32. package/dist/docs/issues/tellask-background-continuation-live-bugs-2026-05-17.zh.md +101 -0
  33. package/dist/docs/mcp-prompts-resources.md +13 -6
  34. package/dist/docs/mcp-support.md +18 -9
  35. package/dist/docs/mcp-support.zh.md +13 -8
  36. package/dist/docs/memory-system.md +51 -1
  37. package/dist/docs/memory-system.zh.md +51 -1
  38. package/dist/docs/team_mgmt-toolset.md +27 -10
  39. package/dist/docs/team_mgmt-toolset.zh.md +20 -9
  40. package/dist/docs/tellask-background-continuation-refactor.zh.md +1017 -0
  41. package/dist/docs/tellask-collab.md +4 -4
  42. package/dist/docs/tellask-collab.zh.md +4 -4
  43. package/dist/evt-registry.js +1 -0
  44. package/dist/llm/defaults.yaml +12 -9
  45. package/dist/llm/kernel-driver/drive.js +315 -67
  46. package/dist/llm/kernel-driver/engine.d.ts +1 -1
  47. package/dist/llm/kernel-driver/engine.js +4 -4
  48. package/dist/llm/kernel-driver/flow.js +203 -131
  49. package/dist/llm/kernel-driver/idle-reminder-wake.js +2 -4
  50. package/dist/llm/kernel-driver/loop.d.ts +4 -1
  51. package/dist/llm/kernel-driver/loop.js +268 -62
  52. package/dist/llm/kernel-driver/reply-guidance.js +29 -50
  53. package/dist/llm/kernel-driver/runtime.d.ts +10 -0
  54. package/dist/llm/kernel-driver/runtime.js +29 -84
  55. package/dist/llm/kernel-driver/sideDialog-txn.js +3 -0
  56. package/dist/llm/kernel-driver/sideDialog.d.ts +4 -13
  57. package/dist/llm/kernel-driver/sideDialog.js +165 -161
  58. package/dist/llm/kernel-driver/tellask-special.d.ts +5 -2
  59. package/dist/llm/kernel-driver/tellask-special.js +166 -184
  60. package/dist/llm/kernel-driver/types.d.ts +12 -8
  61. package/dist/mcp/config.d.ts +13 -5
  62. package/dist/mcp/config.js +37 -24
  63. package/dist/mcp/resources.d.ts +14 -3
  64. package/dist/mcp/resources.js +9 -6
  65. package/dist/mcp/supervisor.js +6 -1
  66. package/dist/mcp/tool-names.d.ts +2 -2
  67. package/dist/mcp/tool-names.js +4 -7
  68. package/dist/minds/system-prompt-parts.js +3 -0
  69. package/dist/persistence-errors.d.ts +1 -1
  70. package/dist/persistence.d.ts +81 -51
  71. package/dist/persistence.js +1747 -427
  72. package/dist/priming.d.ts +1 -1
  73. package/dist/priming.js +2 -2
  74. package/dist/recovery/proceeding-drive.js +46 -11
  75. package/dist/recovery/reply-special.js +42 -35
  76. package/dist/runtime/background-callee-summary.d.ts +2 -0
  77. package/dist/runtime/background-callee-summary.js +14 -0
  78. package/dist/runtime/driver-messages.d.ts +0 -8
  79. package/dist/runtime/driver-messages.js +0 -59
  80. package/dist/server/api-routes.js +40 -27
  81. package/dist/server/websocket-handler.js +39 -78
  82. package/dist/server.js +2 -2
  83. package/dist/shared-reminders.js +14 -3
  84. package/dist/skills/load.d.ts +7 -0
  85. package/dist/tools/cmd-runner.js +2 -0
  86. package/dist/tools/ctrl.js +6 -6
  87. package/dist/tools/os.js +2 -0
  88. package/dist/tools/pending-tellask-reminder.js +9 -9
  89. package/dist/tools/prompts/control/en/index.md +2 -0
  90. package/dist/tools/prompts/control/en/principles.md +1 -0
  91. package/dist/tools/prompts/control/en/tools.md +3 -0
  92. package/dist/tools/prompts/control/zh/index.md +2 -0
  93. package/dist/tools/prompts/control/zh/principles.md +1 -0
  94. package/dist/tools/prompts/control/zh/tools.md +3 -0
  95. package/dist/tools/prompts/personal_memory/en/principles.md +4 -1
  96. package/dist/tools/prompts/personal_memory/zh/principles.md +4 -1
  97. package/dist/tools/prompts/skills/en/index.md +1 -1
  98. package/dist/tools/prompts/skills/en/principles.md +5 -1
  99. package/dist/tools/prompts/skills/zh/index.md +1 -1
  100. package/dist/tools/prompts/skills/zh/principles.md +5 -1
  101. package/dist/tools/prompts/team_memory/en/principles.md +3 -0
  102. package/dist/tools/prompts/team_memory/en/scenarios.md +1 -1
  103. package/dist/tools/prompts/team_memory/zh/principles.md +3 -0
  104. package/dist/tools/prompts/team_memory/zh/scenarios.md +1 -1
  105. package/dist/tools/shell-capture-env.d.ts +1 -0
  106. package/dist/tools/shell-capture-env.js +44 -0
  107. package/dist/tools/skills.js +2 -0
  108. package/dist/tools/team_mgmt-mcp-manual.js +8 -8
  109. package/dist/tools/team_mgmt.js +34 -12
  110. package/dist/utils/task-package.js +9 -1
  111. package/package.json +3 -3
  112. package/webapp/dist/assets/{_basePickBy-CBh9Agsi.js → _basePickBy-DBvh4H3k.js} +3 -3
  113. package/webapp/dist/assets/_basePickBy-DBvh4H3k.js.map +1 -0
  114. package/webapp/dist/assets/{_baseUniq-D2UXV506.js → _baseUniq-CQmc9B-o.js} +2 -2
  115. package/webapp/dist/assets/_baseUniq-CQmc9B-o.js.map +1 -0
  116. package/webapp/dist/assets/{arc-CmeRUuzC.js → arc-DfLiOX_4.js} +2 -2
  117. package/webapp/dist/assets/arc-DfLiOX_4.js.map +1 -0
  118. package/webapp/dist/assets/{architectureDiagram-VXUJARFQ-D5hfsb4A.js → architectureDiagram-2XIMDMQ5-CaTVJ5ev.js} +26 -8
  119. package/webapp/dist/assets/architectureDiagram-2XIMDMQ5-CaTVJ5ev.js.map +1 -0
  120. package/webapp/dist/assets/{blockDiagram-VD42YOAC-BVswEa9D.js → blockDiagram-WCTKOSBZ-DlmAtUca.js} +187 -170
  121. package/webapp/dist/assets/blockDiagram-WCTKOSBZ-DlmAtUca.js.map +1 -0
  122. package/webapp/dist/assets/{c4Diagram-YG6GDRKO-Bgm6yzGX.js → c4Diagram-IC4MRINW-CNHKD5Sl.js} +4 -4
  123. package/webapp/dist/assets/c4Diagram-IC4MRINW-CNHKD5Sl.js.map +1 -0
  124. package/webapp/dist/assets/{channel-CDo0v82C.js → channel-BTWhZYd5.js} +2 -2
  125. package/webapp/dist/assets/channel-BTWhZYd5.js.map +1 -0
  126. package/webapp/dist/assets/{chunk-4BX2VUAB-DMqSdYxu.js → chunk-4BX2VUAB-CV1n0Uhy.js} +2 -2
  127. package/webapp/dist/assets/chunk-4BX2VUAB-CV1n0Uhy.js.map +1 -0
  128. package/webapp/dist/assets/{chunk-55IACEB6-CudmE3Fx.js → chunk-55IACEB6-C5r77i_p.js} +2 -2
  129. package/webapp/dist/assets/chunk-55IACEB6-C5r77i_p.js.map +1 -0
  130. package/webapp/dist/assets/{chunk-FMBD7UC4-BSVSuNxy.js → chunk-FMBD7UC4-DJ1yHxrH.js} +2 -2
  131. package/webapp/dist/assets/chunk-FMBD7UC4-DJ1yHxrH.js.map +1 -0
  132. package/webapp/dist/assets/{chunk-TZMSLE5B-aW2uEdtS.js → chunk-JSJVCQXG-Ahg9hCCm.js} +14 -6
  133. package/webapp/dist/assets/chunk-JSJVCQXG-Ahg9hCCm.js.map +1 -0
  134. package/webapp/dist/assets/{chunk-QN33PNHL-Cbf-pIxI.js → chunk-KX2RTZJC-qBrewKt0.js} +2 -2
  135. package/webapp/dist/assets/chunk-KX2RTZJC-qBrewKt0.js.map +1 -0
  136. package/webapp/dist/assets/{chunk-DI55MBZ5-CZCetJxI.js → chunk-NQ4KR5QH-1lEdM6Wi.js} +9 -7
  137. package/webapp/dist/assets/chunk-NQ4KR5QH-1lEdM6Wi.js.map +1 -0
  138. package/webapp/dist/assets/{chunk-QZHKN3VN-DYyIY8_q.js → chunk-QZHKN3VN-ChVR749G.js} +2 -2
  139. package/webapp/dist/assets/chunk-QZHKN3VN-ChVR749G.js.map +1 -0
  140. package/webapp/dist/assets/{chunk-B4BG7PRW-d5dfiagO.js → chunk-WL4C6EOR-BAUXgk0K.js} +171 -121
  141. package/webapp/dist/assets/chunk-WL4C6EOR-BAUXgk0K.js.map +1 -0
  142. package/webapp/dist/assets/{classDiagram-2ON5EDUG-C4-5PgVL.js → classDiagram-VBA2DB6C-DlqyhKve.js} +7 -6
  143. package/webapp/dist/assets/classDiagram-VBA2DB6C-DlqyhKve.js.map +1 -0
  144. package/webapp/dist/assets/{classDiagram-v2-WZHVMYZB-C4-5PgVL.js → classDiagram-v2-RAHNMMFH-DlqyhKve.js} +7 -6
  145. package/webapp/dist/assets/classDiagram-v2-RAHNMMFH-DlqyhKve.js.map +1 -0
  146. package/webapp/dist/assets/{clone-DivPByZ0.js → clone-BFiIqUsc.js} +2 -2
  147. package/webapp/dist/assets/clone-BFiIqUsc.js.map +1 -0
  148. package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CwBAjMT3.js → cose-bilkent-S5V4N54A-JYvhtd6J.js} +2 -2
  149. package/webapp/dist/assets/cose-bilkent-S5V4N54A-JYvhtd6J.js.map +1 -0
  150. package/webapp/dist/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -1
  151. package/webapp/dist/assets/{dagre-6UL2VRFP-tC56AIio.js → dagre-KLK3FWXG-CCGcQh6w.js} +7 -7
  152. package/webapp/dist/assets/dagre-KLK3FWXG-CCGcQh6w.js.map +1 -0
  153. package/webapp/dist/assets/defaultLocale-B2RvLBDe.js.map +1 -1
  154. package/webapp/dist/assets/{diagram-PSM6KHXK-dEF_O6uj.js → diagram-E7M64L7V-BXC4AxAd.js} +10 -10
  155. package/webapp/dist/assets/diagram-E7M64L7V-BXC4AxAd.js.map +1 -0
  156. package/webapp/dist/assets/{diagram-QEK2KX5R-BC3CyB81.js → diagram-IFDJBPK2-B--Sb3XT.js} +9 -8
  157. package/webapp/dist/assets/diagram-IFDJBPK2-B--Sb3XT.js.map +1 -0
  158. package/webapp/dist/assets/{diagram-S2PKOQOG-BV-YMbA_.js → diagram-P4PSJMXO-CVqgtrh3.js} +8 -8
  159. package/webapp/dist/assets/diagram-P4PSJMXO-CVqgtrh3.js.map +1 -0
  160. package/webapp/dist/assets/{erDiagram-Q2GNP2WA-DIgdtwce.js → erDiagram-INFDFZHY-BniHaRTt.js} +96 -75
  161. package/webapp/dist/assets/erDiagram-INFDFZHY-BniHaRTt.js.map +1 -0
  162. package/webapp/dist/assets/{flowDiagram-NV44I4VS-C7Mawlld.js → flowDiagram-PKNHOUZH-wLKFBWTR.js} +98 -81
  163. package/webapp/dist/assets/flowDiagram-PKNHOUZH-wLKFBWTR.js.map +1 -0
  164. package/webapp/dist/assets/{ganttDiagram-JELNMOA3-DgaYLOeL.js → ganttDiagram-A5KZAMGK-DrptcitZ.js} +28 -3
  165. package/webapp/dist/assets/ganttDiagram-A5KZAMGK-DrptcitZ.js.map +1 -0
  166. package/webapp/dist/assets/{gitGraphDiagram-V2S2FVAM-Dwv1ZLFB.js → gitGraphDiagram-K3NZZRJ6-C6l5aP44.js} +38 -46
  167. package/webapp/dist/assets/gitGraphDiagram-K3NZZRJ6-C6l5aP44.js.map +1 -0
  168. package/webapp/dist/assets/graph-DXuQGYQN.js +782 -0
  169. package/webapp/dist/assets/graph-DXuQGYQN.js.map +1 -0
  170. package/webapp/dist/assets/{index-BGdI3lWA.css → index-BQoNJEGT.css} +19 -27
  171. package/webapp/dist/assets/{index-BsSFGqVX.js → index-DuQ1OCMG.js} +1435 -1348
  172. package/webapp/dist/assets/{index-BsSFGqVX.js.map → index-DuQ1OCMG.js.map} +1 -1
  173. package/webapp/dist/assets/{infoDiagram-HS3SLOUP-PDKRqD6y.js → infoDiagram-LFFYTUFH-BbleCSjW.js} +7 -7
  174. package/webapp/dist/assets/infoDiagram-LFFYTUFH-BbleCSjW.js.map +1 -0
  175. package/webapp/dist/assets/init-ZxktEp_H.js.map +1 -1
  176. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-DmV-LZuk.js +966 -0
  177. package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-DmV-LZuk.js.map +1 -0
  178. package/webapp/dist/assets/{journeyDiagram-XKPGCS4Q-DDD6YlTa.js → journeyDiagram-4ABVD52K-D3sQFfac.js} +5 -5
  179. package/webapp/dist/assets/journeyDiagram-4ABVD52K-D3sQFfac.js.map +1 -0
  180. package/webapp/dist/assets/{kanban-definition-3W4ZIXB7-CK-CJzXm.js → kanban-definition-K7BYSVSG-BEeBlBtM.js} +5 -3
  181. package/webapp/dist/assets/kanban-definition-K7BYSVSG-BEeBlBtM.js.map +1 -0
  182. package/webapp/dist/assets/{layout-DRs2ltCp.js → layout-g7jjgV-W.js} +5 -5
  183. package/webapp/dist/assets/layout-g7jjgV-W.js.map +1 -0
  184. package/webapp/dist/assets/{linear-CiMu0dYF.js → linear-D_X91Yek.js} +2 -2
  185. package/webapp/dist/assets/linear-D_X91Yek.js.map +1 -0
  186. package/webapp/dist/assets/{mindmap-definition-VGOIOE7T-C8yEoHXx.js → mindmap-definition-YRQLILUH-NkMAIgRY.js} +7 -5
  187. package/webapp/dist/assets/mindmap-definition-YRQLILUH-NkMAIgRY.js.map +1 -0
  188. package/webapp/dist/assets/ordinal-CxptdPJm.js.map +1 -1
  189. package/webapp/dist/assets/{pieDiagram-ADFJNKIX-BhtVILXQ.js → pieDiagram-SKSYHLDU-Z6E4GEPC.js} +8 -8
  190. package/webapp/dist/assets/pieDiagram-SKSYHLDU-Z6E4GEPC.js.map +1 -0
  191. package/webapp/dist/assets/{quadrantDiagram-AYHSOK5B-B7Xuuv_G.js → quadrantDiagram-337W2JSQ-BH8hfOuU.js} +3 -3
  192. package/webapp/dist/assets/quadrantDiagram-337W2JSQ-BH8hfOuU.js.map +1 -0
  193. package/webapp/dist/assets/{requirementDiagram-UZGBJVZJ-DY6Q4U6l.js → requirementDiagram-Z7DCOOCP-DRJkvoQI.js} +16 -6
  194. package/webapp/dist/assets/requirementDiagram-Z7DCOOCP-DRJkvoQI.js.map +1 -0
  195. package/webapp/dist/assets/{sankeyDiagram-TZEHDZUN-DZPox4PX.js → sankeyDiagram-WA2Y5GQK-D2VwjtJo.js} +2 -2
  196. package/webapp/dist/assets/sankeyDiagram-WA2Y5GQK-D2VwjtJo.js.map +1 -0
  197. package/webapp/dist/assets/{sequenceDiagram-WL72ISMW-Bg6GPP0w.js → sequenceDiagram-2WXFIKYE-Cq-gEPOw.js} +601 -201
  198. package/webapp/dist/assets/sequenceDiagram-2WXFIKYE-Cq-gEPOw.js.map +1 -0
  199. package/webapp/dist/assets/{stateDiagram-FKZM4ZOC-CcskYNpn.js → stateDiagram-RAJIS63D-CFM8Jqke.js} +9 -9
  200. package/webapp/dist/assets/stateDiagram-RAJIS63D-CFM8Jqke.js.map +1 -0
  201. package/webapp/dist/assets/{stateDiagram-v2-4FDKWEC3-Dja5UwuW.js → stateDiagram-v2-FVOUBMTO-Dip5iGX_.js} +5 -5
  202. package/webapp/dist/assets/stateDiagram-v2-FVOUBMTO-Dip5iGX_.js.map +1 -0
  203. package/webapp/dist/assets/{timeline-definition-IT6M3QCI-CA-Wvxg8.js → timeline-definition-YZTLITO2-OI9JzMjX.js} +3 -3
  204. package/webapp/dist/assets/timeline-definition-YZTLITO2-OI9JzMjX.js.map +1 -0
  205. package/webapp/dist/assets/{treemap-GDKQZRPO-CF_Fur4n.js → treemap-KZPCXAKY-CtNF416A.js} +37 -24
  206. package/webapp/dist/assets/treemap-KZPCXAKY-CtNF416A.js.map +1 -0
  207. package/webapp/dist/assets/vennDiagram-LZ73GAT5-CjaPj4FZ.js +2487 -0
  208. package/webapp/dist/assets/vennDiagram-LZ73GAT5-CjaPj4FZ.js.map +1 -0
  209. package/webapp/dist/assets/{xychartDiagram-PRI3JC2R-CYc3vOkZ.js → xychartDiagram-JWTSCODW-21mBt9iu.js} +4 -4
  210. package/webapp/dist/assets/xychartDiagram-JWTSCODW-21mBt9iu.js.map +1 -0
  211. package/webapp/dist/index.html +2 -2
  212. package/dist/docs/tellask-revive-context-refactor.zh.md +0 -604
  213. package/webapp/dist/assets/_basePickBy-CBh9Agsi.js.map +0 -1
  214. package/webapp/dist/assets/_baseUniq-D2UXV506.js.map +0 -1
  215. package/webapp/dist/assets/arc-CmeRUuzC.js.map +0 -1
  216. package/webapp/dist/assets/architectureDiagram-VXUJARFQ-D5hfsb4A.js.map +0 -1
  217. package/webapp/dist/assets/blockDiagram-VD42YOAC-BVswEa9D.js.map +0 -1
  218. package/webapp/dist/assets/c4Diagram-YG6GDRKO-Bgm6yzGX.js.map +0 -1
  219. package/webapp/dist/assets/channel-CDo0v82C.js.map +0 -1
  220. package/webapp/dist/assets/chunk-4BX2VUAB-DMqSdYxu.js.map +0 -1
  221. package/webapp/dist/assets/chunk-55IACEB6-CudmE3Fx.js.map +0 -1
  222. package/webapp/dist/assets/chunk-B4BG7PRW-d5dfiagO.js.map +0 -1
  223. package/webapp/dist/assets/chunk-DI55MBZ5-CZCetJxI.js.map +0 -1
  224. package/webapp/dist/assets/chunk-FMBD7UC4-BSVSuNxy.js.map +0 -1
  225. package/webapp/dist/assets/chunk-QN33PNHL-Cbf-pIxI.js.map +0 -1
  226. package/webapp/dist/assets/chunk-QZHKN3VN-DYyIY8_q.js.map +0 -1
  227. package/webapp/dist/assets/chunk-TZMSLE5B-aW2uEdtS.js.map +0 -1
  228. package/webapp/dist/assets/classDiagram-2ON5EDUG-C4-5PgVL.js.map +0 -1
  229. package/webapp/dist/assets/classDiagram-v2-WZHVMYZB-C4-5PgVL.js.map +0 -1
  230. package/webapp/dist/assets/clone-DivPByZ0.js.map +0 -1
  231. package/webapp/dist/assets/cose-bilkent-S5V4N54A-CwBAjMT3.js.map +0 -1
  232. package/webapp/dist/assets/dagre-6UL2VRFP-tC56AIio.js.map +0 -1
  233. package/webapp/dist/assets/diagram-PSM6KHXK-dEF_O6uj.js.map +0 -1
  234. package/webapp/dist/assets/diagram-QEK2KX5R-BC3CyB81.js.map +0 -1
  235. package/webapp/dist/assets/diagram-S2PKOQOG-BV-YMbA_.js.map +0 -1
  236. package/webapp/dist/assets/erDiagram-Q2GNP2WA-DIgdtwce.js.map +0 -1
  237. package/webapp/dist/assets/flowDiagram-NV44I4VS-C7Mawlld.js.map +0 -1
  238. package/webapp/dist/assets/ganttDiagram-JELNMOA3-DgaYLOeL.js.map +0 -1
  239. package/webapp/dist/assets/gitGraphDiagram-V2S2FVAM-Dwv1ZLFB.js.map +0 -1
  240. package/webapp/dist/assets/graph-saC_350a.js +0 -425
  241. package/webapp/dist/assets/graph-saC_350a.js.map +0 -1
  242. package/webapp/dist/assets/infoDiagram-HS3SLOUP-PDKRqD6y.js.map +0 -1
  243. package/webapp/dist/assets/journeyDiagram-XKPGCS4Q-DDD6YlTa.js.map +0 -1
  244. package/webapp/dist/assets/kanban-definition-3W4ZIXB7-CK-CJzXm.js.map +0 -1
  245. package/webapp/dist/assets/layout-DRs2ltCp.js.map +0 -1
  246. package/webapp/dist/assets/linear-CiMu0dYF.js.map +0 -1
  247. package/webapp/dist/assets/mindmap-definition-VGOIOE7T-C8yEoHXx.js.map +0 -1
  248. package/webapp/dist/assets/pieDiagram-ADFJNKIX-BhtVILXQ.js.map +0 -1
  249. package/webapp/dist/assets/quadrantDiagram-AYHSOK5B-B7Xuuv_G.js.map +0 -1
  250. package/webapp/dist/assets/requirementDiagram-UZGBJVZJ-DY6Q4U6l.js.map +0 -1
  251. package/webapp/dist/assets/sankeyDiagram-TZEHDZUN-DZPox4PX.js.map +0 -1
  252. package/webapp/dist/assets/sequenceDiagram-WL72ISMW-Bg6GPP0w.js.map +0 -1
  253. package/webapp/dist/assets/stateDiagram-FKZM4ZOC-CcskYNpn.js.map +0 -1
  254. package/webapp/dist/assets/stateDiagram-v2-4FDKWEC3-Dja5UwuW.js.map +0 -1
  255. package/webapp/dist/assets/timeline-definition-IT6M3QCI-CA-Wvxg8.js.map +0 -1
  256. package/webapp/dist/assets/treemap-GDKQZRPO-CF_Fur4n.js.map +0 -1
  257. package/webapp/dist/assets/xychartDiagram-PRI3JC2R-CYc3vOkZ.js.map +0 -1
@@ -0,0 +1,1017 @@
1
+ # Tellask 后台继续语义与崩溃恢复对齐重构计划
2
+
3
+ ## 背景
4
+
5
+ 本计划记录一次围绕 `tellask` 后台诉请续推行为的语义收敛,并把它扩展到后台进程崩溃/重启后的自动恢复逻辑。
6
+
7
+ 触发案例来自一个真实运行时工作区:
8
+
9
+ - rtws:某真实运行时工作区(具体路径已脱敏)
10
+ - 对话标识:某主线对话(具体 root/self id 已脱敏)
11
+ - 课程:`course=30`
12
+ - 关注气泡:`genseq=1845`
13
+
14
+ 现场关键序列:
15
+
16
+ 1. `genseq=1842`:LLM 只调用了一次 `tellask`,向 `@fullstack` 发起 `network-diagnostic-impl` 长线诉请。
17
+ 2. runtime 创建 Side Dialog,登记 pending sideDialog,并写入 pending `func_result_record`。
18
+ 3. `genseq=1843`:主线无新用户输入地继续生成。
19
+ 4. `genseq=1844`:主线再次调用 `tellask`,向 `@doc-editor` 发起 handbook 草稿诉请。
20
+ 5. `genseq=1845`:主线继续执行本地工具。
21
+
22
+ 业务直觉上,单个 pending `tellask` 后可以自然停在当前边界;后续是否保活应由 Diligence Push 独立决定,而不是由 pending tellask 的 tool result 自动驱动下一轮 LLM generation。
23
+
24
+ ### 现场证据锚点
25
+
26
+ `course-030.jsonl` 中的 durable events 支持以下判断:
27
+
28
+ - `genseq=1842` 已写入 `tellask_call_record`、`sideDialog_created_record`、pending `func_result_record`,且该 generation 后续已有 `gen_finish_record`。这说明“诉请发出”这一轮本身已经闭合。
29
+ - `genseq=1844` 再次出现单个 `tellask` dispatch ack,并在 `gen_finish_record` 后约 3 秒出现 `genseq=1845` 的新 `gen_start_record`。因此 `genseq=1845` 不是同一 LLM generation 内自然延续,而是外层 drive 又把主线调起。
30
+ - 同一 rtws 的 `latest.yaml` 后续落在 `generating: false`、`needsDrive: true`、`displayState: blocked/waiting_for_sideDialogs`。这说明当前系统把“后台诉请仍 pending”误投影成了“诉请者正在等待被诉请者”的运行状态;大多数场景看起来会停,靠的是这个旧误投影,而不是最终原则。
31
+
32
+ 这三个事实共同说明:最终原则不是要修复一个单点 if,而是要把 `tellask` background fact、下一步动作判定、Diligence Push、restart recovery 四套语义拆清楚。这里的 dialog 不是只指主线;任意对话都可能作为 caller 发起后台诉请,也都可能通过 `tellaskBack` / ask-back 路径成为 callee 并承担回贴义务。
33
+
34
+ ## 最终原则
35
+
36
+ ### 核心语义
37
+
38
+ `tellask` 的业务语义固定为后台并行委派:
39
+
40
+ - `tellask` / `tellaskSessionless` / registered Type-B assignment 只产生后台诉请事实。
41
+ - pending tellask 不表示 caller dialog 必须停下;caller 可以是主线,也可以是支线。
42
+ - pending tellask 不要求 caller dialog 等待回贴。
43
+ - pending tellask 不等价于 `waiting_for_sideDialogs` 显示/运行状态。
44
+ - caller dialog 可以继续,也可以自然 idle;这由智能体后续动作、用户输入、queued prompt、Diligence Push 等通用机制决定。
45
+ - callee dialog 的 active reply obligation 是另一种事实:它表示“当前 dialog 最终必须交付回贴”,但不妨碍该 dialog 自己发起下游 pending tellask。
46
+
47
+ ### 角色相对性
48
+
49
+ 同一个 dialog 可能同时处在多个相对角色中,不能用“主线/支线”静态划分控制流:
50
+
51
+ - 作为 caller:它发起 `tellask` / `tellaskSessionless` / `tellaskBack`。它发出的 pending tellask 对它自己是后台事实,不决定“现在必须停下”。
52
+ - 作为 callee:它因为上游诉请或 ask-back 而承担 active reply obligation。这个义务用于完成判定、reply tool 引导、direct-fallback 交付和崩溃恢复。
53
+ - 作为 reply result owner:它等待某个 reply tool 写回 canonical result。`replyTellaskBack` 尤其要区分“执行 reply 的 dialog”“等待 tellaskBack result 的 dialog”“最终接收业务回贴的上游 dialog”。
54
+ - 作为 dispatch batch caller:它在同一次 LLM move 中一次性发出多个 `tellask` 时,负责按这组派发批次的整体结果边界收口。单个 `tellask` 只是 dispatch batch size = 1 的特例。
55
+
56
+ 因此规则必须按“当前 dialog 相对于某条诉请/回贴链路的角色”判定,而不能写成“主线停、支线停”或“所有 sideDialogs 都等待”。主线被 `tellaskBack` 时同样是 callee;支线发起下游 `tellask` 时同样是 caller。
57
+
58
+ ### 主线 / 支线差异边界
59
+
60
+ 业务上,主线 / 支线差异应收敛为白名单,而不是散落在 run-control 语义里:
61
+
62
+ - 主线承担编排责任:维护全局推进叙事、拆分/合并工作、协调多路诉请。
63
+ - 主线与其它主线共同维护差遣牒;支线只读差遣牒,不能把自己的局部视角写成全局事实。
64
+ - 支线承担被委派事项的交付责任;如果需要影响全局计划,应通过 reply / tellaskBack / 明确回贴把信息交给具备编排责任的 dialog。
65
+ - 技术上,主线是归档单位:由人类用户手工标记完成/归档,归档时定义一组运行记录的生命周期边界。
66
+
67
+ 除此之外,主线和支线不应在以下机制中拥有业务语义差异:
68
+
69
+ - `tellask` background delegation。
70
+ - active reply obligation / reply recovery。
71
+ - 派发批次回贴齐备后的 result-arrival handling。
72
+ - Q4H:等待用户回答。
73
+ - crash/restart recovery。
74
+ - post-tool immediate continuation。
75
+ - displayState / run-control 状态投影。
76
+
77
+ Diligence Push 是例外:它只作用于主线对话,因为它是编排保活机制。callee 不被运行时鞭策;callee 如果出现非本意的直接回复并停止驱动,应通过 direct-fallback 把事实交回 caller,由 caller 智能判断是续推原诉请、发起 `tellaskBack`,还是另开其它诉请对话。
78
+
79
+ 实现上存在入口形态差异,例如主线通常由用户创建,支线通常由诉请创建;WebUI 把主线作为归档、导航、课程聚合入口。这些是 lifecycle / presentation 差异,不影响 dialog drive 语义。
80
+
81
+ ### 三层边界
82
+
83
+ 1. `tellask` 层:只登记后台工作事实和回贴义务。
84
+ 2. post-tool auto-continue 层:只处理真正需要当前 LLM 立刻消化的 tool result、queued prompt、错误恢复、reply 工具收口等通用驱动事实。
85
+ 3. Diligence Push 层:主线编排保活机制;可把 pending tellask 作为判断输入,但不改变 `tellask` 的业务语义,也不作用于支线。
86
+
87
+ ### 不再抽象 `blocker`
88
+
89
+ 目标设计不需要把业务状态先反向抽象成 `blocker`,再由 `blocker` 推导行动。更顺直的表达是:
90
+
91
+ - 现在是否有用户问题要等:有则等待用户。
92
+ - 现在是否有 runtime prompt / queued prompt 要消费:有则消费。
93
+ - 当前工具轮是否产生了需要模型立刻消化的结果:有则继续本轮后续 generation。
94
+ - 是否有后台诉请回贴结果到达:有则按派发批次 complete 触发等待方处理。
95
+ - 是否还有后台诉请 pending:只更新可观测状态;主线可被 Diligence Push 保活,callee 不由运行时鞭策。
96
+ - 当前 dialog 是否欠上游交付:记录 completion obligation,并引导 reply/direct-fallback 收口;它不改写后台诉请的语义。
97
+
98
+ 因此 `blocker` 只应作为旧代码命名或旧 displayState 的迁移对象被提及,不应成为新设计里的业务概念。
99
+
100
+ ### 同类概念也要降级
101
+
102
+ 类似 `blocker`,下面几个词也容易把技术症状误升级成业务机制:
103
+
104
+ - `needsDrive`:业务上不是“这个 dialog 需要被驱动”的抽象状态,而是有一个具体下一步动作来源:用户输入、queued prompt、主线 Diligence Push、回贴结果到达、open generation 续跑等。目标实现不再保留 boolean 投影;只保留具体 `nextStep.triggers` 和 registry wake signal。
105
+ - `entitlement` / `revive`:业务上不是某个 dialog 获得“恢复权”,而是“有新事实到达,caller 应处理”。目标实现以 `result_arrival` trigger 表达这件事;若底层仍临时保留 entitlement token,只能作为防重入/防误续推的内部令牌,并必须携带同一个 `batchId`。
106
+ - `wait-group`:业务上不是 caller 正在等待一组 callee,而是“同一次派发批次的回贴结果要成组收口”。因此设计用 dispatch batch / 派发批次描述,不再新增 `waitGroupId` 这类等待语义字段名。
107
+ - `proceeding recovery`:业务上不是恢复 proceeding 状态,而是“上一次 generation 确认未闭合,所以继续同一轮”。已闭合 generation 只能从状态快照重新投影状态。
108
+ - `latest` / `displayState`:业务事实的来源应是状态机转移时维护的结构化元信息;`displayState`、run-control counts 都只是投影,不能反向决定业务事实。
109
+
110
+ ### 状态机携带判定元信息,杜绝历史回扫
111
+
112
+ 这次重构要一并清理旧技术债:以前为了做某些判断,会重新扫描聊天记录、course JSONL、历史 tool calls 或 pending records 来推导当前状态。目标态禁止这种设计。
113
+
114
+ 原则:
115
+
116
+ - 每一次状态机转移都必须同步写入后续判断所需的最小充分元信息。
117
+ - 状态事件写入和状态快照更新必须在同一个事务/临界区完成;不能出现 event 已落盘但判定元信息未更新的半状态。
118
+ - runtime 下一步判定、Diligence Push、result-arrival handling、restart recovery、display projection 都只读当前状态快照和显式 pending records。
119
+ - 不用“回扫历史聊天记录”来推导 open generation、dispatch batch completion、reply delivery 是否完成、active reply obligation、next-step 来源等运行判定。
120
+ - 历史事件日志只保留为审计、调试、人类追查、上下文展示/组装,以及 dialog fork 复制输入;它不是常态业务判定的数据源。
121
+ - Dominds 永远不制作“历史数据一次性迁移 / repair 工具”作为软件功能。对话历史是低价值数据;正常处理流程是依赖工作区文件、差遣牒进展记录和必要的人类判断,开新对话继续推进。遇到旧对话运行态缺失或损坏时,系统应 loud diagnostic + 停止不安全路径,而不是提供历史迁移/修补软件去抢救旧对话。最接近“复用历史”的能力是 dialog fork:它可以扫描并复制对话历史,但只到复制新分支为止,不承担修复旧运行态的职责。
122
+ - 如果当前状态快照缺少必要元信息,应按 loud error policy 报错并停止不安全路径,而不是静默扫描历史补猜。
123
+
124
+ 状态机至少需要携带:
125
+
126
+ - active generation 元信息:`course`、`genseq`、`phase`、是否已写入 finish、最后一次 tool round 分类。
127
+ - user wait 元信息:是否正在等待 Q4H / askHuman 的用户回答、对应 questionId、等待来源 course/genseq。
128
+ - next-step trigger 元信息:下一步动作的具体来源、来源 course/genseq/callId、是否一次性消费。
129
+ - active callee dispatch 元信息:存于 caller 对话目录的 `active-callees.json`;路径已经表达 caller,不在文件内容重复记录自身 dialog id;每个 batch 携带稳定 `batchId`、call site、callee 成员 callIds、每个 call 的 pending/resolved/final 状态、batch 是否 complete。
130
+ - reply delivery 元信息:active reply obligation、expected reply tool、target dialog/callId、delivery status、tool-result status。
131
+ - background work 元信息:pending tellask 摘要、与 dispatch batch 的关联、用于主线 Diligence 的只读上下文。
132
+
133
+ next-step trigger 使用显式 union,而不是裸 boolean:
134
+
135
+ ```ts
136
+ type NextStepTrigger =
137
+ | (NextStepTriggerBase & { kind: 'user_input'; course: number; genseq: number })
138
+ | (NextStepTriggerBase & { kind: 'queued_prompt'; promptId: string; course: number })
139
+ | (NextStepTriggerBase & { kind: 'root_drive_wake'; reason: string; course: number })
140
+ | ({
141
+ kind: 'followup';
142
+ sourceGeneration: { course: number; genseq: number };
143
+ reasons: FollowupReason[];
144
+ } & NextStepTriggerBase)
145
+ | ({
146
+ kind: 'mainline_diligence';
147
+ diligenceId: string;
148
+ pendingTellaskCount: number;
149
+ } & NextStepTriggerBase)
150
+ | (NextStepTriggerBase & { kind: 'result_arrival'; batchId: string })
151
+ | (NextStepTriggerBase & { kind: 'open_generation_recovery'; course: number; genseq: number })
152
+ | ({
153
+ kind: 'reply_delivery_recovery';
154
+ replyDeliveryId: string;
155
+ targetDialogId: string;
156
+ } & NextStepTriggerBase);
157
+
158
+ type NextStepTriggerBase = {
159
+ triggerId: string;
160
+ createdAt: string;
161
+ seq: number;
162
+ };
163
+
164
+ type NextStepTriggerState = {
165
+ nextSeq: number;
166
+ triggers: NextStepTrigger[];
167
+ };
168
+
169
+ type FollowupReason =
170
+ | { kind: 'ordinary_tool_result'; callIds: string[] }
171
+ | { kind: 'invalid_tool_recovery'; callIds: string[] }
172
+ | { kind: 'reply_delivery_result'; replyDeliveryId: string; replyCallId: string }
173
+ | { kind: 'result_arrival'; batchId: string }
174
+ | { kind: 'runtime_guidance'; msgId: string };
175
+ ```
176
+
177
+ `nextStep.triggers.length > 0` 表示“有未消费的新事实”,不等价于“此刻允许启动 drive”。backend loop / run-control 必须同时读取 user wait 等先决等待事实;只有先决等待为空时,未消费 trigger 才能进入 runnable drive。
178
+
179
+ `nextStep.triggers` 是一次性铃声,不是业务事实账本:
180
+
181
+ - trigger 默认只携带稳定引用,不复制大型业务账本;真实账本在对应状态文件中,例如 `active-callees.json`、`replyDelivery`、`pendingRuntimePrompt`、`generationRunState`。
182
+ - `followup` 是例外但仍属于 trigger 层:它表达“这轮已产生必须立刻交给 LLM 继续处理的结果集合”,原因集合本身就是一次性驱动事实,不另开 `followup.json`。这样避免用文件存在/不存在表达业务语义,也减少一次额外文件访问。
183
+ - 消费点是对应业务事实已成功注入或处理,而不是 drive start。若启动 drive 后崩溃但事实尚未处理,trigger 仍应保留以便重启后重试。
184
+ - `followup` 的消费点是下一轮 generation 已成功完成 durable handoff:`gen_start_record` 写入、`generationRunState.kind='open'` 写入,且 open state 记录 `acceptedTriggerIds`。之后若新一轮 generation 中途崩溃,由 `generationRunState.open` + `open_generation_recovery` 恢复同一轮,不再保留 `followup` 等到 `gen_finish`。否则 `followup` 会同时表达“需要启动下一轮”和“下一轮尚未结束”,与 open-generation recovery 重叠。
185
+ - 一轮 generation 可以合并消费多个 runnable triggers。只要这些 triggers 的正式内容已经进入该轮 LLM context,并完成同一个 durable handoff,就可以把它们一起记录到 `acceptedTriggerIds` 并一起消费;这对用户插话、多个 batch 近同时回贴、queued prompt 与 result-arrival 并存等业务场景是必要能力。
186
+ - 合并进入 LLM context 的 triggers 按持久化到达顺序排列:先按 `seq`,再按 `createdAt`,最后按 `triggerId` 稳定打平。`seq` 是 dialog-local 单调序号,由 `NextStepTriggerState.nextSeq` 分配,不依赖数组当前顺序,也不通过扫描现有 trigger 反推;若旧状态没有 `seq/nextSeq`,该 dialog 进入 `malformed/`。
187
+ - recovery 派生 trigger 的到达顺序锚点来自它的 durable source,而不是重启时刻:`open_generation_recovery` 使用 `generationRunState.open.openedAt`,`reply_delivery_recovery` 使用 `replyDelivery.createdAt` 或对应 pending delivery 的创建时间。
188
+ - `open_generation_recovery` 和 `reply_delivery_recovery` 不强制独占一轮。若恢复动作与其它 runnable triggers 一起进入同一轮 LLM context,可以把 recovery trigger 隐形合并消费;业务语义是“上次未完成/未交付的事实和新到事实被同一轮处理”,不要求为 recovery 单独生成一轮。
189
+ - recovery trigger 是从 durable recovery source 派生的服务铃声:`open_generation_recovery` 来自 `generationRunState.open`,`reply_delivery_recovery` 来自 `replyDelivery`。即使它的 trigger id 已记录在 `acceptedTriggerIds`,只要 durable recovery source 仍表示未完成,重启后仍可重新生成 recovery trigger;`acceptedTriggerIds` 不能被用来压制必要恢复。
190
+ - 消费后直接从 `triggers` 删除;不保留 consumed tombstone。审计、排查和历史追溯使用 event log 与业务状态,不让运行态文件累积历史触发记录。
191
+ - 幂等防重由业务状态或 trigger identity 承担:例如 `active-callees.json` batch 消费后删除、`replyDelivery.status/toolResultStatus` 防重复交付、`pendingRuntimePrompt.msgId` 防重复 prompt、`generationRunState` 防 closed generation 再恢复;`followup` 使用 source generation 生成稳定 `triggerId`,同一 generation 的多次原因更新应合并到同一 trigger。
192
+
193
+ `userWait` 使用独立状态表达先决等待:
194
+
195
+ ```ts
196
+ type UserWaitState = {
197
+ kind: 'awaiting_user_answer';
198
+ questionId: string;
199
+ callId: string;
200
+ course: number;
201
+ genseq?: number;
202
+ askedAt: string;
203
+ };
204
+ ```
205
+
206
+ `userWait.kind === 'awaiting_user_answer'` 是先决等待事实,不是 trigger。等待用户期间到达的 `result_arrival` 等新事实仍要写入 `NextStepTriggerState`,但 backend driver 不能越过 user wait 自动启动;用户回答到达时,清除 user wait,并把用户回答作为 `user_input` trigger 写入同一个 next-step state。随后按未消费 triggers 的持久到达顺序合并进入下一轮;这里没有额外权衡。
207
+
208
+ 若运行态元信息缺失或结构不合法,不在 runtime 回扫历史补猜;该 dialog 直接转移到 `malformed/`,记录 warning / structured diagnostic,之后不做第二次自动处理。历史对话在 Dominds 中是低价值资产;真正需要恢复时,由人类手工分析 `malformed/` 下记录并修正后续跑。
209
+
210
+ 这条规则可以作为后续重构的命名约束:若一个新概念不能回答“哪个业务事实到了、下一步应做什么”,就不要把它放进目标设计;最多把它留在模块级迁移说明里。
211
+
212
+ ### 规约句
213
+
214
+ `active-callees` is an observability fact and, for mainline dialogs only, a Diligence Push input. It must not decide whether the caller dialog can keep working.
215
+
216
+ 中文表达:
217
+
218
+ > pending tellask 只是后台进行中事实;它只参与 observability、主线 Diligence Push 是否保活、以及回贴到达后的 result-arrival handling,不决定 caller dialog 是否继续工作。
219
+
220
+ ## 当前实现事实
221
+
222
+ ### 为什么大部分场景看起来符合原则
223
+
224
+ 当前实现中,普通单个 `tellask` 后多数情况下会停住,但原因不是最终原则,而是旧“pending sideDialog 等同诉请者等待被诉请者”的投影机制。
225
+
226
+ 路径:
227
+
228
+ 1. `tellask-special.ts` 创建/更新 Side Dialog,并把后台 callee dispatch 写入 `active-callees.json`。
229
+ 2. 如果没有立即获得回贴,`processTellaskFunctionRound` 会为 callId 生成 background `func_result_msg`。
230
+ 3. `drive.ts` 在工具轮结束后执行:
231
+
232
+ ```ts
233
+ const suspensionAfterToolRound = await dlg.getSuspensionStatus({
234
+ allowPendingSideDialogs: routed.hasImmediateFollowupToolCalls,
235
+ });
236
+ if (!suspensionAfterToolRound.canDrive) {
237
+ await preserveDiligenceBudgetAcrossQ4H(dlg);
238
+ break;
239
+ }
240
+ ```
241
+
242
+ 4. `Dialog.getSuspensionStatus()` 当前把后台 callee dispatch 当成不能继续驱动的原因:
243
+
244
+ ```ts
245
+ const blockingSideDialogs = hasSideDialogs && options?.allowPendingSideDialogs !== true;
246
+ ```
247
+
248
+ 因此多数单个 pending `tellask` 后会因为 `canDrive=false` break。这种结果表面接近“发完诉请自然停”,但底层语义是“诉请者等待被诉请者”,与最终原则不一致。
249
+
250
+ ### 为什么触发案例不符合原则
251
+
252
+ 触发案例中,`genseq=1842` 后仍出现 `genseq=1843` 无 prompt 续推,说明不是同一 LLM generation 内自然继续,而是外层调度又启动了主线 drive。
253
+
254
+ 当前可解释路径:
255
+
256
+ 1. 旧 backend loop 会驱动 `globalDialogRegistry.needsDrive=true` 的主线对话。
257
+ 2. `loop.ts` 对 `latest.generating === true` 的主线对话使用 `resumeInProgressGeneration`:
258
+
259
+ ```ts
260
+ const resumeInProgressGeneration = latest?.generating === true;
261
+ if (!resumeInProgressGeneration && !(await mainDialog.canDrive())) {
262
+ continue;
263
+ }
264
+ ```
265
+
266
+ 3. 这会绕过 `mainDialog.canDrive()`。
267
+ 4. `flow.ts` 在 no-prompt resume 路径中又允许 `resumeInProgressGeneration` 越过 pending sideDialogs:
268
+
269
+ ```ts
270
+ allowPendingSideDialogs: driveOptions?.resumeInProgressGeneration === true,
271
+ ```
272
+
273
+ 所以当前实现出现了两种不一致:
274
+
275
+ - 普通路径:pending tellask 被当成“需要等待”,诉请者停。
276
+ - proceeding/recovery 路径:这个“需要等待”的判断被绕过,诉请者续推。
277
+
278
+ 这解释了“多数场景符合,但该现场不符合”:多数场景靠旧误投影恰好停住;该现场被 queued/proceeding recovery 绕过了旧误投影。
279
+
280
+ 更精确地说,当前实现存在一个“结果偶然正确、控制语义错误”的组合:
281
+
282
+ - 普通路径用 `pendingSideDialogs -> canDrive=false` 让诉请者停,这是错因对果。
283
+ - resume/proceeding 路径用 `resumeInProgressGeneration -> allowPendingSideDialogs=true` 绕过同一判断,让诉请者继续,这是错因错果。
284
+ - 最终重构后,二者都不应依赖 pending tellask:普通路径停,是因为 pending dispatch ack 不构成 immediate post-tool generation;resume/proceeding 路径不应重放已经 `gen_finish` 的 generation。
285
+
286
+ ## 与最终原则的 Gap
287
+
288
+ ### Gap 1:pending sideDialog 被误当成“caller 正在等待”
289
+
290
+ 位置:
291
+
292
+ - `Dialog.getSuspensionStatus()`
293
+ - `computeIdleDisplayState()`
294
+ - `computeIdleDisplayStateFromPersistence()`
295
+ - `blockerDisplayState()` 及相关显示投影
296
+ - run-control snapshot 计数
297
+ - restart reconciliation projection
298
+
299
+ 问题:
300
+
301
+ - 诉请者的 pending tellask 被显示成 `blocked/waiting_for_sideDialogs`。
302
+ - 任意 callee 如果自己再作为 caller 发起下游诉请,也会被相同的 pending sideDialog 机制当成“正在等待下游被诉请者”,而这同样不符合 background delegation 语义。
303
+ - Continue / resumable 计数把 pending tellask 当成暂停原因,且无法区分“caller 后台诉请 pending”和“callee active reply obligation 未完成”。
304
+ - backend loop 用 `canDrive()` 把 pending tellask 当作不可驱动条件。
305
+
306
+ 目标:
307
+
308
+ - 对任意 caller dialog,pending tellask 不应导致 `canDrive=false`。
309
+ - 对任意 caller dialog,pending tellask 不应投影为 `blocked/waiting_for_sideDialogs`。
310
+ - 任意 dialog 作为 callee 的 active reply obligation 需独立建模为 completion obligation;它说明当前 dialog 还欠上游交付,但不说明当前 dialog 不能继续工作。
311
+ - Q4H 等真正需要外部输入的状态应表达为“等待用户回答”,而不是混进 pending tellask。
312
+
313
+ ### Gap 1.5:主线/支线推进规则没有统一到角色模型
314
+
315
+ 当前代码里主线/支线推进逻辑散落在:
316
+
317
+ - `flow.ts` 的 fresh suspension reload、interjection pause resume、result revive entitlement。
318
+ - `runtime.ts` 中旧 sideDialog diligence recovery 路径已按最终原则移除;不要重新引入支线鞭策注入。
319
+ - `tellask-special.ts` 的 reply obligation、replyTellask\*、tellaskBack 处理。
320
+ - `recovery/proceeding-drive.ts` 和 `recovery/reply-special.ts`。
321
+
322
+ 问题:
323
+
324
+ - 任意 dialog 作为 callee 时,受 active reply obligation 约束最终交付;但它仍能用普通工具、向下游 `tellask`,并在非本意 direct response 时把 fallback 交给 caller 判断。
325
+ - 任意 dialog 作为 caller 时,它发出的 pending tellask 是后台事实,不应让自身 `canDrive=false`。
326
+ - 主线被 `tellaskBack` 时也可能是 callee;这时它需要按 active reply obligation 完成 ask-back 回答,而不是按“主线永远只接收最终结果”的假设处理。
327
+ - 直接自然语言回复、thinking-only、函数调用后继续、replyTellask\* 成功后的 stop-current-round,这些完成判定依赖 active reply obligation 和最新 generation 状态,不能被 pendingSideDialogs 混淆。
328
+ - result revive entitlement 是当前实现里表达“回贴结果到达”的技术令牌,不是“仍有 pending sideDialog”驱动;二者现在容易在 display/run-control 里混到一起。
329
+
330
+ 目标:
331
+
332
+ - 建一个所有 dialog 共用的“业务事实 -> 下一步动作”分类:等待用户回答、处理已到达用户输入、欠上游交付、后台诉请进行中、已有新结果可处理、可继续本轮工具结果消化、自然 idle。
333
+ - active reply obligation 属于 completion obligation;pending downstream tellask 属于 background work。
334
+ - 移除 callee 运行时鞭策语义;callee direct-fallback 交由 caller 决策,不由 runtime 代替 callee 续推。
335
+ - 任何 dialog 的 pending background work 都不进入暂停/可恢复计数。
336
+
337
+ ### Gap 2:pending tellask 的 tool result 语义不够显式
338
+
339
+ 当前 pending `tellask` 会成为 `func_result_record`,内容为“诉请已发出,当前仍在等待回贴”。这条记录是事实回执,不是需要 LLM 立刻推理的业务输出。
340
+
341
+ 目标:
342
+
343
+ - pending tellask result 应归类为 background dispatch acknowledgment。
344
+ - 单个 pending tellask result 不触发 immediate post-tool generation。
345
+ - 如果同一轮还有普通 tool result、invalid tool call、queued prompt、reply recovery,则仍按对应机制继续。
346
+
347
+ ### Gap 2.5:多路 `tellask` 的派发批次边界不够显式
348
+
349
+ 业务上,同一次 LLM move 可以发出多个 `tellask`。这时 caller 的“后台结果到达后自动恢复”应按整组 callee 回复收口:
350
+
351
+ - 同一 dispatch batch 内任意单个 callee 回贴,只更新该 call 的结果事实和 pending 状态。
352
+ - 只有该批次所有 callee 都已有最终结果,caller 才获得 result-arrival trigger。
353
+ - 单个 `tellask` 是 dispatch batch size = 1 的特殊情况,因此看起来像“单个回贴即触发”,但实现上仍应走同一批次收口规则。
354
+ - 如果同一 generation 里同时有多组不同语义的 dispatch,需要 durable batch identity 区分;不能只靠“当前是否有 active callee”粗判。
355
+
356
+ 目标态不再沿用 `pending-sideDialogs.json` 作为运行判定源,也不改名续命为 `pending-calleeDialogs.json`。caller 对话目录下使用 `active-callees.json` 承载还未被消费的 callee 派发批次:
357
+
358
+ ```ts
359
+ type ActiveCalleesFile = {
360
+ batches: ActiveCalleeBatch[];
361
+ };
362
+
363
+ type ActiveCalleeBatch = {
364
+ batchId: string;
365
+ callSite: { course: number; genseq: number };
366
+ status: 'open' | 'resolved';
367
+ callees: Array<{
368
+ callId: string;
369
+ calleeDialogId: string;
370
+ callName: 'tellask' | 'tellaskSessionless' | 'tellaskBack' | 'freshBootsReasoning';
371
+ status: 'pending' | 'resolved' | 'final';
372
+ targetAgentId: string;
373
+ tellaskContent: string;
374
+ callType: 'A' | 'B' | 'C';
375
+ mentionList?: string[];
376
+ sessionSlug?: string;
377
+ completion?: ActiveCalleeCompletion;
378
+ createdAt: string;
379
+ resolvedAt?: string;
380
+ }>;
381
+ createdAt: string;
382
+ resolvedAt?: string;
383
+ };
384
+
385
+ type ActiveCalleeCompletion =
386
+ | { kind: 'reply_tool'; resultRecordId: string }
387
+ | { kind: 'direct_fallback'; memo: string; resultRecordId: string };
388
+ ```
389
+
390
+ 原则:
391
+
392
+ - 文件所在目录已经表达 caller,不在文件内容重复记录自身 `callerDialogId`。这也让 fork dialog 时少一类路径与内容不一致的问题。
393
+ - `batchId` 必须保留,用于 result-arrival trigger、日志、崩溃恢复、去重和并发写入稳定引用;不使用数组下标或 `{ course, genseq }` 作为隐式 identity。
394
+ - 同一 caller 可以同时存在多个 active batch。最重要的业务场景是用户插话:前一批 callee 还在后台运行时,用户可能继续补充要求,caller 因用户输入再次推进并派发新的 callee batch。
395
+ - 每个 batch 独立 open/resolved/consumed;某个 batch complete 只生成该 batch 的 `result_arrival` trigger,不等待其它 batch,也不被其它 batch pending 投影成“caller 正在等待”。
396
+ - caller 启动或恢复一轮时,可以合并消费当前所有 runnable `result_arrival` / `followup` / `queued_prompt` / recovery 等 triggers。前提是这些 trigger 对应的正式内容已经进入同一轮 LLM context,并在 `generationRunState.open.acceptedTriggerIds` 中完成 durable handoff。
397
+ - callee 非本意直接回复停止驱动时,不新增 `direct_fallback` trigger;它作为该 callee member 的 `completion.kind='direct_fallback'` 写入 batch facts,最多携带 `memo` 说明来源。batch complete 后仍只生成 `result_arrival` trigger,让 caller 像人类一样综合判断是续推原诉请、发起 `tellaskBack`,还是另开诉请。
398
+ - `result_arrival` 被消费后,删除对应 batch;`active-callees.json` 只保存仍需判断的运行态事实,不累积历史诉请记录。
399
+ - 不再新增 `waitGroupId` 这类等待语义字段名。
400
+
401
+ ### Gap 3:Diligence Push 与“等待被诉请者”投影混杂
402
+
403
+ Diligence Push 是主线编排保活机制。它可以参考 pending tellask,但不应复用“等待被诉请者”投影,也不应扩展成 callee reply-recovery 机制。
404
+
405
+ 当前:
406
+
407
+ - `maybeContinueWithDiligencePrompt()` 先调用 `getSuspensionStatus()`。
408
+ - pending sideDialog 会让 Diligence Push break。
409
+ - idle reminder wake 也要求 pending sideDialogs 为空。
410
+
411
+ 目标:
412
+
413
+ - Diligence Push 只在主线对话触发,独立读取 pending tellask 事实,判断是否保活。
414
+ - pending tellask 可影响主线 Diligence prompt 文案,例如提醒“仍有后台诉请未回贴,不要宣布全局完成;如无本地可做事项,可以等待”。
415
+ - pending tellask 不应通过 `canDrive=false` 间接影响主线 Diligence Push。
416
+ - 支线不触发 Diligence Push;支线直接回复或停驱动时,按带 direct-fallback memo 的 result-arrival fact 交回 caller。
417
+
418
+ ### Gap 4:崩溃/重启恢复把 `generating=true` 视为可自动续跑
419
+
420
+ 启动顺序:
421
+
422
+ 1. `reconcileDisplayStatesAfterRestart()`
423
+ 2. `recoverProceedingDrivesAfterRestart()`
424
+ 3. `recoverPendingReplyTellaskCallsAfterRestart()`
425
+ 4. `runBackendDriver()`
426
+
427
+ 当前规则:
428
+
429
+ - `isRecoverableGeneratingLatest(latest)` 对 `generating=true` 且没有强 interrupted/dead marker 的 dialog 返回 true。
430
+ - 旧 restart reconciliation 把它设为 `needsDrive=true`、`displayState=proceeding`。
431
+ - 旧主线 proceeding recovery 将主线对话标记为 needsDrive。
432
+ - backend loop 发现 `latest.generating=true` 时用 `resumeInProgressGeneration=true` 自动 drive。
433
+
434
+ 风险:
435
+
436
+ - 如果进程在单个 pending tellask 刚发出后崩溃,重启后可能把这个“已完成 dispatch 的边界”误判为 in-progress generation,并继续主线。
437
+ - 如果 latest 中 `generating` 未及时清 false,普通 runtime 也可能出现类似 proceeding resume 绕过。
438
+
439
+ 目标:
440
+
441
+ - 重启恢复要区分“LLM 正在真正未完成 streaming/tool round”与“上一轮已经持久化 gen_finish / pending tellask dispatch boundary”。
442
+ - 对已存在 `gen_finish_record` 的 latest generation,不应用 `resumeInProgressGeneration` 继续。
443
+ - 对最后一轮只有 background `tellask` dispatch ack 且已 finished 的 dialog,恢复后应进入 idle + pending tellask observability,而不是 proceeding。
444
+
445
+ ### Gap 5:`needsDrive` 队列含义过宽
446
+
447
+ 当前 `latest.needsDrive` / `globalDialogRegistry.needsDrive` 既可能表示:
448
+
449
+ - 有 queued prompt / pending runtime prompt 需要消费;
450
+ - 有回贴结果到达,需要等待方 dialog 处理结果;
451
+ - 有 restart recovery 需要恢复真正 open generation;
452
+ - 也可能只是 pending sideDialog 被误投影后导致 backend loop 保持 queued。
453
+
454
+ 最后一种不应继续存在。pending tellask 是后台事实,不是“待驱动队列”的理由。重构后 `needsDrive` boolean 投影应删除,真实调度来源必须是 `NextStepTriggerState.triggers.length > 0` 或 open `generationRunState`,且每个 trigger 必须是显式 union variant:`user_input`、`queued_prompt`、`mainline_diligence`、`result_arrival`、`open_generation_recovery`、`reply_delivery_recovery`。
455
+
456
+ ## 重构目标
457
+
458
+ ### 行为目标
459
+
460
+ 1. 单个 pending `tellask` 后当前 drive 停在边界,不自动开下一轮 LLM generation。
461
+ 2. UI 显示诉请者自身的 idle 或 proceeding/保活状态,不显示 `waiting_for_sideDialogs`。
462
+ 3. pending tellask 状态通过 pendingTellask reminder / pending sideDialog panel / call bubble / 后台被诉请者 badge 展示。
463
+ 4. 主线 Diligence Push 只因 budget 允许而保活;pending tellask 只作为 prompt context。支线不触发 Diligence。
464
+ 5. 崩溃重启后,已完成的 dispatch boundary 不被误恢复成 proceeding auto-drive。
465
+ 6. replyTellask\*、askHuman、tellaskBack、active reply obligation 等真正交付/恢复语义不退化。
466
+
467
+ ### 非目标
468
+
469
+ - 不引入 `tellask` continuation policy 字段。
470
+ - 不把 nextAction 做成协议字段。
471
+ - 不改变 replyTellask\* 的正式交付机制。
472
+ - 不把 pending tellask 静默丢弃或降级为不可观测状态。
473
+ - 不引入“主线/支线各一套 drive 规则”;除编排/差遣牒/归档生命周期外,按统一 dialog 语义处理。
474
+
475
+ ## 设计方案
476
+
477
+ ### 1. 按业务事实决定下一步动作
478
+
479
+ 新增或重命名内部查询,避免一个 `getSuspensionStatus()` 同时服务所有业务:
480
+
481
+ ```ts
482
+ type DialogNextStepFacts = {
483
+ userWait: DialogUserWaitState;
484
+ nextStep: NextStepTriggerState;
485
+ completionObligation: DialogCompletionObligationState;
486
+ backgroundWork: PendingBackgroundWork;
487
+ };
488
+
489
+ type DialogUserWaitState =
490
+ | { kind: 'none' }
491
+ | { kind: 'awaiting_user_answer'; questionId: string; course: number; genseq: number };
492
+
493
+ type DialogCompletionObligationState =
494
+ | { kind: 'none' }
495
+ | {
496
+ kind: 'active_reply_obligation';
497
+ expectedReplyCallName: 'replyTellask' | 'replyTellaskSessionless' | 'replyTellaskBack';
498
+ targetDialogId: string;
499
+ targetCallId: string;
500
+ };
501
+
502
+ type PendingBackgroundWork = {
503
+ pendingTellaskCount: number;
504
+ activeCallees: ActiveCalleesFile;
505
+ };
506
+
507
+ type ActiveCalleesFile = { batches: ActiveCalleeBatch[] };
508
+ ```
509
+
510
+ 下一步动作按事实直接判定:
511
+
512
+ - `userWait.kind === 'awaiting_user_answer'`:等待用户回答;不触发 Diligence,也不把 pending tellask 混进等待原因。
513
+ - `nextStep.triggers` 中有 `user_input`:处理已经到达的用户输入;Q4H 仍由独立的等待用户回答状态表达。
514
+ - `nextStep.triggers` 中有 `queued_prompt`:消费 runtime prompt / queued prompt。
515
+ - `nextStep.triggers` 中有 `mainline_diligence`:注入主线 Diligence prompt。
516
+ - `nextStep.triggers` 中有 `result_arrival`:让等待方处理 dispatch batch 回贴结果。
517
+ - `nextStep.triggers` 中有 `open_generation_recovery`:继续未闭合 generation;若同时存在其它 runnable triggers,可以合并进入同一轮上下文。
518
+ - `nextStep.triggers` 中有 `reply_delivery_recovery`:完成未交付 reply;若交付事实和其它 runnable triggers 都已正式进入同一轮上下文,可以合并处理。
519
+ - 有 immediate tool result:继续当前 drive 让 LLM 消化结果。
520
+ - 只有 background pending tellask:更新 observability;主线可由 Diligence Push 保活;否则自然 idle。
521
+ - 有 active reply obligation:在已有驱动中引导 reply/direct-fallback 收口;它不是“必须暂停”或“必须立刻开新一轮”的事实。
522
+
523
+ completion 判断使用 `DialogCompletionObligationState`:
524
+
525
+ - active reply obligation:对任意需要 reply 工具收口的 callee 上下文生效,包括主线被 `tellaskBack` 的场景。
526
+ - 它要求当前 dialog 最终交付,但不阻止当前 dialog 使用工具、发起下游 tellask、或继续推理。
527
+
528
+ pending tellask observability 使用 `PendingBackgroundWork`。四者不可混用:
529
+
530
+ - user wait facts 决定“是否正在等用户回答”。
531
+ - next-step facts 决定“已经有哪个新事实需要处理”。
532
+ - completion obligations 决定“这个 dialog 是否还欠上游交付”。
533
+ - background work 决定“有哪些后台诉请仍在进行中”。
534
+
535
+ ### 2. 调整 dialog display projection
536
+
537
+ `computeIdleDisplayState()` 对任意 dialog:
538
+
539
+ - 有 Q4H:显示等待用户回答。
540
+ - 没 Q4H,仅有 pending tellask:`idle_waiting_user`。
541
+ - 有 active reply obligation:不把它显示成 `waiting_for_sideDialogs`;dialog 仍可 idle/proceeding,completion obligation 通过 reply guide / call bubble / direct-fallback path 表达。
542
+ - UI 表达“后台进行中”时,走 pendingTellask reminder / panel,不复用暂停态。
543
+
544
+ 本轮不新增 `background_work_pending`;后台进行中只通过 pendingTellask reminder / panel 表达。
545
+
546
+ ### 3. 调整 post-tool continuation 判断
547
+
548
+ 在 `drive.ts` 工具轮后:
549
+
550
+ - `replyTellask*` 成功:仍 stop current round。
551
+ - `dlg.hasUpNext()`:仍消费 queued prompt。
552
+ - `routed.hasImmediateFollowupToolCalls`:只由普通工具、invalid call、真正需要模型消化的结果决定;目标态写入 `followup` trigger 表达该轮需要立即继续。
553
+ - pending tellask ack 不计入 immediate followup。
554
+ - pending tellask ack 不让 `allowPendingSideDialogs` 变成 true,也不作为是否继续的判断输入。
555
+
556
+ 新增显式命名:
557
+
558
+ ```ts
559
+ const shouldStartImmediatePostToolGeneration =
560
+ routed.hasImmediateFollowupToolCalls ||
561
+ routed.hasTellaskResolvedToolOutputs ||
562
+ invalidFuncCallCount > 0;
563
+ ```
564
+
565
+ 其中 pending dispatch ack 不属于 `hasTellaskResolvedToolOutputs`。
566
+
567
+ ### 4. Diligence Push 接入 pending tellask
568
+
569
+ 主线 Diligence Push 是很薄的保活功能,目标只是确保智能体对话只停在合法边界:确实需要人类输入时应调用 askHuman / Q4H 等等待答案,而不是像普通聊天应用那样自然停下。它只作用于主线,只看 budget 是否允许;pending tellask 可作为 prompt context,但不参与“是否应该鞭策”的判定。支线不触发 Diligence Push。
570
+
571
+ 新增 Diligence context:
572
+
573
+ ```ts
574
+ type MainlineDiligenceContext = {
575
+ budget: { remainingPushes: number };
576
+ pendingTellaskCount: number;
577
+ pendingTellaskSummaries: string[];
578
+ };
579
+ ```
580
+
581
+ prompt 文案原则:
582
+
583
+ - pending tellask 存在时,不催促“必须继续本地工作”。
584
+ - Diligence Push 不分析 local runnable work、不看 pending tellask 数量来决定是否触发;触发条件只来自 budget。
585
+ - 应提醒:
586
+ - 后台还有诉请未回贴;
587
+ - 不要声称全局已完成;
588
+ - 若有独立本地事项,继续推进;
589
+ - 若没有本地可做事项,可以等待回贴或向用户报告当前后台状态。
590
+ - callee 非本意直接回复停止驱动时,不注入鞭策 prompt;direct-fallback 交给 caller 处理。
591
+
592
+ ### 5. 后台进程崩溃/重启恢复
593
+
594
+ 恢复逻辑必须基于状态机维护的 generation 元信息,而不是只看 `latest.generating`,也不是重启时回扫 course events 推导。
595
+
596
+ #### 5.1 判定 latest generation 是否已经完整结束
597
+
598
+ 新增状态字段和 helper:
599
+
600
+ ```ts
601
+ type GenerationRunState =
602
+ | {
603
+ kind: 'closed';
604
+ course: number;
605
+ genseq: number;
606
+ closedAt: string;
607
+ }
608
+ | {
609
+ kind: 'open';
610
+ course: number;
611
+ genseq: number;
612
+ phase: 'streaming' | 'tool_round' | 'finishing';
613
+ acceptedTriggerIds: string[];
614
+ openedAt: string;
615
+ };
616
+ ```
617
+
618
+ 判定方式:
619
+
620
+ - `gen_start_record` 写入时,同步把 `generationRunState` 设为 `open`,记录 course/genseq/phase/acceptedTriggerIds;若本轮由 trigger 触发,完成该 durable handoff 后消费对应 triggers。
621
+ - recovery 同一 open generation 时,不新增 `gen_start_record`;若本次恢复同时接纳了新的 runnable triggers,则在它们的正式内容进入恢复上下文后,把 trigger ids 追加/合并到 `generationRunState.open.acceptedTriggerIds`,再消费对应 triggers。
622
+ - streaming/tool round/finishing 每个阶段推进时,只更新当前 genseq 的 phase;若 genseq 不匹配,按 loud error policy 失败。
623
+ - `gen_finish_record` 写入时,同步把同一 genseq 的 `generationRunState` 设为 `closed`,记录关闭时间;`finishRecordId` 不进入 runtime snapshot,它只属于 event log 审计信息。
624
+ - `func_result_record`、`tellask_callee_record`、`reminders_reconciled_record` 等后续事件不能改变 closed/open 判定;若需要影响下一步动作,必须写独立 next-step trigger 元信息。
625
+ - 如果 latest 中缺失 `generationRunState`、genseq 回退、重复 open、finish 与 active genseq 不一致,按 loud error policy 转移到 `malformed/`,记录结构化错误并停止该 dialog 的 unsafe recovery,而不是静默 fallback 到 proceeding 或回扫历史补猜。
626
+
627
+ 注意:`DialogLatestFile` 当前没有保存 active genseq 是需要偿还的技术债。目标运行路径不能每次重启都扫描历史来推导 latest generation closure;缺失必要 generation 元信息时进入 `malformed/`。
628
+
629
+ #### 5.2 closed / missing generation state 不进入 proceeding recovery
630
+
631
+ `generating=true` 是投影,不是恢复事实。当前代码已把 open-generation recovery 的判定收敛到 `getRecoverableGenerationRunState(latest)`:只有 `generationRunState.kind === 'open'` 且 interruption marker 允许自动恢复时,才写入 `open_generation_recovery` trigger 并进入 proceeding recovery。
632
+
633
+ 目标态仍可把该 helper 扩展为带诊断返回值的 decision helper:
634
+
635
+ ```ts
636
+ async function resolveRecoverableGenerationAfterRestart(
637
+ dialogId,
638
+ latest,
639
+ ): Promise<RestartGenerationRecoveryDecision>;
640
+ ```
641
+
642
+ 规则:
643
+
644
+ - `generationRunState.kind === 'open'`:recover proceeding。
645
+ - `generationRunState.kind === 'closed'`:不 recover proceeding;清 stale generating,并按状态快照投影 displayState。
646
+ - `generationRunState` 缺失:转移到 `malformed/`,记录 warning / structured diagnostic,不 recover proceeding,也不扫描历史补猜。
647
+ - latest.generating 与 `generationRunState` 冲突:转移到 `malformed/`,记录 warning / structured diagnostic,停止该 dialog 的 unsafe recovery;runtime 不回扫补猜,也不尝试第二次自动修复。
648
+
649
+ 返回结构携带诊断字段,便于日志与测试断言:
650
+
651
+ ```ts
652
+ type RestartGenerationRecoveryDecision =
653
+ | {
654
+ kind: 'recover_open_generation';
655
+ course: number;
656
+ genseq: number;
657
+ reason: 'generation_state_open';
658
+ }
659
+ | {
660
+ kind: 'clear_stale_generating';
661
+ course: number;
662
+ genseq: number;
663
+ reason: 'generation_state_closed';
664
+ }
665
+ | {
666
+ kind: 'no_generation_recovery';
667
+ reason: 'not_generating' | 'dead_marker' | 'interrupted_non_recoverable';
668
+ };
669
+ ```
670
+
671
+ 结构化日志字段至少包含 `rootId`、`selfId`、`course`、`genseq`、`latestGenerating`、`nextStepTriggerCount`、`decision.kind`、`decision.reason`。
672
+
673
+ #### 5.3 closed background tellask boundary 的恢复投影
674
+
675
+ 若最后 closed generation 的 tool calls 全部是 pending background tellask dispatch ack:
676
+
677
+ - 清 `generating=false`。
678
+ - 清不必要 queued state;除非存在 queued prompt、pending course start 转换出的 `queued_prompt` trigger、或 reply recovery trigger,否则不写 next-step trigger。
679
+ - displayState 对普通 idle dialog 为 `idle_waiting_user`;若同一 dialog 仍有 active reply obligation,则按 callee completion obligation 投影。
680
+ - pending tellask reminder 保留并在下一次 reminder reconcile 中显示。
681
+
682
+ #### 5.4 pending replyTellask\* 恢复仍优先
683
+
684
+ 第一阶段时 `recoverPendingReplyTellaskCallsAfterRestart()` 通过扫描历史 call-without-result 推导未交付 reply;第二阶段已改为读取 `latest.replyDelivery`。目标态继续要求 reply delivery 元信息记录完整 `deliveryStatus`,重启后直接读取状态快照恢复未完成交付。
685
+
686
+ 要求:
687
+
688
+ - reply recovery 可以完成交付并触发等待方处理新事实。
689
+ - 但普通 pending `tellask` dispatch 不应走 reply recovery。
690
+ - `replyTellaskBack` recovery 必须按 reply delivery 元信息中的 target dialog/callId 写回 canonical `tellaskBack` result;不能假设 target 一定是支线对话,也不能假设主线只会作为最终接收者。
691
+ - 启动顺序中,closed-generation reconciliation 应先清掉 stale generating,再做 reply recovery,避免 backend loop 抢先误续推。
692
+
693
+ 启动顺序调整为:
694
+
695
+ 1. load/quarantine persistence
696
+ 2. project closed/stale generation flags from state snapshot
697
+ 3. recover pending replyTellask\* deliveries from reply delivery state
698
+ 4. recover genuinely open generations from generationRunState
699
+ 5. start backend driver
700
+
701
+ 当前实现已把 reply recovery 调整到 proceeding/open-generation recovery 之前;closed/stale generation projection 仍需继续收敛为更明确的 decision helper。
702
+
703
+ #### 5.5 restart 后 next-step 的收敛规则
704
+
705
+ closed-generation projection 清掉 stale `generating` 后,要同步处理 `nextStep.triggers`:
706
+
707
+ - 如果存在 `pendingCourseStartPrompt`:转成 `queued_prompt` trigger;display 为对应 stopped/resumable 状态。
708
+ - 如果存在 pending reply delivery:由 `recoverPendingReplyTellaskCallsAfterRestart()` 负责产生/保留 `reply_delivery_recovery` trigger。
709
+ - 如果只是 pending tellask / pending sideDialogs:不写 next-step trigger,display 投影为 `idle_waiting_user`,pending tellask 通过 reminder/panel 表达。
710
+ - 如果状态快照缺少必要元信息:转移到 `malformed/` 并发 warning / structured diagnostic,不要把 next-step trigger 当兜底保活,也不要回扫历史补猜。
711
+
712
+ ### 6. result-arrival handling 语义
713
+
714
+ 回贴完成后,等待该结果的 dialog 应处理新事实,这是另一条正当自动继续路径。常见情况是 callee 回贴后让 caller 处理结果;`tellaskBack` 中也可能是主线或其它上游 dialog 等待 ask-back result。
715
+
716
+ 目标态 `supplyResponseToAskerDialog()`:
717
+
718
+ - 更新 `active-callees.json` 中对应 batch member 的 resolved/final 状态。
719
+ - 若同 batch 已全部完成,则把 batch 标记为 `resolved`,写入 `result_arrival` trigger,并由 backend/直接调度路径启动 caller 处理新事实。
720
+ - schedule 等待方 drive,带当前实现所需的 `noPromptSideDialogResumeEntitlement`。
721
+
722
+ 保留原则:
723
+
724
+ - 回贴完成后的自动继续是“后台结果到达后的新事实处理”,不是 pending tellask 自身要求等待。
725
+ - result-arrival handling 的粒度是 dispatch batch:整组 dispatch 的 callee 全部完成后通知 caller 处理;单个 tellask 是 dispatch batch size = 1。
726
+ - direct-fallback 不另设下一步动作类型。它只是 batch completion facts 中的一种 result source;`result_arrival` prompt/context 可用 memo 标明“此结果来自 callee direct-fallback”,但运行时不尝试结构化分析该 memo。
727
+ - 多路同组 reply 中间态不得触发 caller LLM generation,但要更新 observability、pending reminder 和对应 call bubble。
728
+ - 自动继续 prompt/drive 应明确携带“有新回贴事实可处理”的原因。
729
+ - 若等待方正在 Q4H / askHuman user wait 中,batch complete 仍写入 `result_arrival` trigger,但 driver 停在 user wait;用户回答清除 user wait 后再消费该 trigger。
730
+ - 当前实现里的 revive entitlement 应携带 `batchId` / batch completion facts,并对应生成 `result_arrival` trigger;目标态移除“全局 pending sideDialogs 是否为空”这类猜测。
731
+ - 若等待方 dialog 已经 idle,收到回贴可以自动 drive 处理结果;这不违反 background_continue。
732
+
733
+ ## 模块级改造清单
734
+
735
+ ### `main/dialog.ts`
736
+
737
+ - 将 `canDrive()` 收敛为“当前是否有先决事实要求先停在边界”,例如 Q4H 等待用户回答;pending course start 转为 `queued_prompt` trigger,不再作为先决等待事实。
738
+ - 新增/拆出 completion obligation 查询,用于读取 active reply obligation;不要让它参与“是否可以开始下一步”的判定。
739
+ - `getSuspensionStatus()` 若保留,应只暴露 `backgroundCalleeDialogs` 这类观测字段,避免 `sideDialogs` 与 `blockingSideDialogs` 被调用方误解为同一件事。
740
+ - 后台被诉请者观测统一读取 `active-callees.json` / `loadActiveCalleeDispatches()`;不再保留 `hasPendingSideDialogs()` 这类旧语义 helper。
741
+ - 避免新增主线专属 next-step 判定;主线/支线差异只应由编排、差遣牒权限、归档生命周期层表达。
742
+
743
+ ### `main/dialog-display-state.ts`
744
+
745
+ - 移除/改名 `blockerDisplayState()` 这类以 blocker 命名的投影;pending sideDialogs 不再映射为 `waiting_for_sideDialogs`。
746
+ - `computeIdleDisplayStateFromPersistence()` 对任意 dialog 使用 next-step facts、background facts、completion obligations 分层投影。
747
+ - `getRecoverableGenerationRunState()` 已只读取 `generationRunState.kind === 'open'`;后续可扩展为结构化 recovery decision。调用方不能只凭 `generating=true` 判断 open generation,也不能回扫历史补猜。
748
+ - restart reconciliation 在 patch latest 时同时处理 `generating`、`nextStep`、`displayState`、`executionMarker`,避免留下 stale queued 状态。
749
+
750
+ ### `main/llm/kernel-driver/drive.ts`
751
+
752
+ - 工具轮后不再用 pending sideDialogs 作为停止当前 drive 的原因。
753
+ - `shouldStartImmediatePostToolGeneration` 排除 pending tellask dispatch ack,只接纳普通 tool outputs、resolved tellask outputs、invalid call recovery 等真正需要模型消化的结果。
754
+ - `maybeContinueWithDiligencePrompt()` 只对主线生效;前置判断只看需要先处理的业务事实;pending tellask 作为主线 Diligence context 传入 prompt formatter。
755
+ - callee direct-fallback 不走 Diligence prompt,也不形成独立 trigger;它写入对应 active callee completion fact,随后通过 batch `result_arrival` 交给 caller 判断。
756
+
757
+ ### `main/llm/kernel-driver/loop.ts`
758
+
759
+ - `resumeInProgressGeneration` 必须来自 restart/open-generation decision 或 active run state,而不是直接等价于 `latest.generating === true`。
760
+ - backend loop drive 前后更新 `nextStep` / drive wake 时,不得因 pending tellask 保持 queued;调度 runnable drive 时必须同时检查 `userWait.kind === 'none'`。
761
+
762
+ ### `main/recovery/proceeding-drive.ts`
763
+
764
+ - 只恢复 `generationRunState.kind === 'open'` 且 recovery decision 为 `recover_open_generation` 的 dialog。
765
+ - 主线/支线 dialog recovery 均记录 `course/genseq/reason`。
766
+ - 不再在 proceeding recovery 中抢在 reply recovery 前误驱动 closed generation。
767
+
768
+ ### `main/recovery/reply-special.ts`
769
+
770
+ - 旧实现扫描 `replyTellask*` call-without-result;目标态改为读取 reply delivery 元信息。扫描历史不作为 runtime recovery,也不作为迁移/repair 工具入口。
771
+ - 明确排除普通 `tellask` pending dispatch;它不是 reply delivery recovery。
772
+ - 成功交付后仍可触发等待方 dialog 处理新事实,这是 result arrival trigger;`replyTellaskBack` 的等待方不一定是支线对话。
773
+
774
+ ### `main/llm/kernel-driver/tellask-special.ts`
775
+
776
+ - pending dispatch ack 的结果内容可保留,但需要在内部分类为 `background_dispatch_ack`。
777
+ - resolved reply result 与 pending ack 在 routing metadata 中分开;需要 immediate followup 时写入 `followup` trigger,pending dispatch ack 不写入。
778
+ - `tellaskBack` / `replyTellaskBack` 的 directive 必须显式保留 caller、callee、result owner 三类角色字段,避免用主线/支线形态推断业务角色。
779
+ - `tellask` dispatch 时写入 `active-callees.json` durable batch identity;reply delivery 时按 batch 判断是否全部完成,只有 batch complete 才触发 result-arrival handling。
780
+
781
+ ### `main/persistence.ts`
782
+
783
+ - `active-callees.json` 是后台 callee dispatch 的 durable 运行源;`ActiveCalleeDispatchRecord` 只作为从 batch 结构投影出来的局部视图,供 UI/background observability 和回贴匹配使用。
784
+ - 旧 `removePendingSideDialog()` / pending-sideDialogs reconciliation 已收敛为 active-callee batch member transition,返回 batch completion outcome。
785
+ - crash recovery 从 `active-callees.json` 读取每个 batch 的 pending/resolved/final 状态;不从 durable events 重建运行态,也不提供历史迁移/repair 工具。
786
+
787
+ ### `main/dialog-fork.ts`
788
+
789
+ - fork 时继续复制 active callee dispatches 作为 background facts。
790
+ - fork 目标 display/run-control 不再因为 copied active callees 进入等待被诉请者状态。
791
+
792
+ ### WebUI / run-control
793
+
794
+ - 暂停/可恢复 counts 不计入 pending tellask。
795
+ - pending tellask 展示继续走 reminder / side dialog panel / call bubble。
796
+ - 主线归档、导航、差遣牒维护入口保留特殊 UI;这些 UI 差异不能反向改变 drive/revive/下一步动作规则。
797
+
798
+ UI 业务状态投影结论:
799
+
800
+ 1. `userWait.kind === 'awaiting_user_answer'`:沿用现有等待用户/Q4H run badge。这是强等待状态,run-control 不自动越过。
801
+ 2. `active-callees.json` 存在 open batch:沿用现有后台被诉请者 / 后台 FBR 被诉请者 badge,可展示数量。它不是暂停态,不显示“等待被诉请者”。
802
+ 3. `nextStep.triggers` 有 runnable trigger 且无 userWait:沿用现有可调度 / proceeding / stopped-resumable 等 run-control 视觉入口,不新增专门的 trigger badge。它可与后台进行中 badge 并存。
803
+ 4. `active-callees.json` 存在 resolved-but-unconsumed batch / 未消费 `result_arrival` trigger:通常是瞬时状态,不加长期 badge;它通过第 3 类可调度状态体现。
804
+ 5. `replyDelivery` pending / delivered-but-tool-result-not-recorded:通常是瞬时交付过程,不加长期 badge;必要信息留在 call bubble / 日志 / 当前 dialog 上下文中。
805
+ 6. `generationRunState.kind === 'open'` 或 recovery trigger 存在:通常是瞬时恢复过程,不加长期 badge;恢复中可沿用 running/proceeding 视觉。
806
+ 7. 只有 active callee open batch、没有 runnable trigger、没有 userWait:这不是独立状态,而是普通 idle + 第 2 类后台 badge。典型路径是当前 generation 只发出后台 tellask 后闭合;callee 还未回贴,所以没有 `result_arrival`,也没有其它 next-step trigger。主线不应仅因 Diligence Push 预算存在而继续空转;active callee 仍 pending 时,主线自然 idle,等待结果到达或其它具体驱动来源。支线自然 idle。
807
+ 8. 元信息缺失、冲突或解析失败:移入 `malformed/` 后技术上相当于当前运行视图不存在;UI 从普通列表撤掉,不显示 badge。需要排查时从归档/诊断入口或文件系统查看 `malformed/`。
808
+
809
+ ## 测试计划
810
+
811
+ ### 单元 / kernel-driver 测试
812
+
813
+ 1. caller dialog 单个 `tellask` 后不 immediate post-tool continue。
814
+ - 输入:LLM 输出一个 valid `tellask`。
815
+ - 期望:有 pending record,有 pending func result,无下一轮 gen_start。
816
+ - 期望:不产生 `result_arrival` trigger;pending tellask dispatch ack 不写入 `nextStep` / `root_drive_wake`。
817
+ - 期望:诉请者 displayState 不为 `waiting_for_sideDialogs`。
818
+
819
+ 2. 同轮 `tellask` + 普通 immediate tool result。
820
+ - 期望:普通工具结果仍可触发 immediate followup。
821
+ - pending tellask 不影响该 followup。
822
+
823
+ 3. callee 有 active reply obligation 但继续使用工具/下游 `tellask`。
824
+ - 期望:active reply obligation 不参与“是否可以开始下一步”的判定;完成判定仍要求最终 reply/direct-fallback 收口。
825
+
826
+ 4. pending tellask + Diligence Push enabled。
827
+ - 期望:进入 idle 边界后 Diligence 不按预算注入 prompt。
828
+ - 期望:不产生 `mainline_diligence` trigger;不消耗 Diligence 预算。
829
+ - 期望:只保留 active callee / pending tellask 可观察状态,等待结果到达、用户输入、queued prompt 或其它具体驱动来源。
830
+
831
+ 5. pending tellask + Diligence Push disabled。
832
+ - 期望:不自动继续,不产生 next-step trigger,只保留 observability。
833
+
834
+ 6. 支线对话 `replyTellask` 成功。
835
+ - 期望:仍 stop current round,并 supply caller。
836
+
837
+ 7. 回贴完成 result-arrival handling。
838
+ - 期望:dispatch batch complete 后,等待方收到 `tellask_result_record` 并自动 drive 处理结果。
839
+
840
+ 8. 多路同组 `tellask` 部分回贴。
841
+ - 输入:同一 generation 发出 3 路 tellask,先收到其中 1 路回贴。
842
+ - 期望:写入该 call 的 result,pending reminder/call bubble 更新,但不触发 caller 处理,不新增 caller gen_start。
843
+
844
+ 9. 多路同组 `tellask` 全部回贴。
845
+ - 输入:同一 dispatch batch 的最后一路 callee 回贴。
846
+ - 期望:batch completion facts 写入,caller 的 `NextStepTriggerState` 出现 `result_arrival` trigger;单路 tellask 走同一逻辑且 dispatch batch size = 1。
847
+
848
+ 10. 同一 caller 同一 course 中存在不同 dispatch batch。
849
+ - 期望:A 组完成只触发 A 组对应事实处理,不把 B 组 pending 错当成暂停原因,也不等 B 组完成才处理 A 组结果。
850
+
851
+ 11. 用户插话导致多个 active batch 并行。
852
+ - 输入:A batch 尚未全部回贴时,用户补充要求触发 caller 新一轮推进并派发 B batch。
853
+ - 期望:`active-callees.json` 同时保存 A/B 两个 batch;任一 batch complete 都只生成自身 `result_arrival` trigger,不等待另一批。
854
+
855
+ 12. 一轮 generation 合并消费多个 runnable triggers。
856
+ - 输入:caller 同时有用户输入、A batch 的 `result_arrival`、B batch 的 `result_arrival`。
857
+ - 期望:这三个正式事实可以进入同一轮 LLM context;`generationRunState.open.acceptedTriggerIds` 记录全部 trigger ids;durable handoff 后一起消费这些 triggers。
858
+
859
+ 13. recovery trigger 与其它 runnable triggers 合并。
860
+ - 输入:`open_generation_recovery` 或 `reply_delivery_recovery` 存在时,又有用户输入 / result-arrival / queued prompt。
861
+ - 期望:若恢复事实和其它正式事实都进入同一轮 LLM context,可以同轮处理并消费对应 triggers;不要求 recovery 独占一轮,也不额外生成空的 recovery generation。
862
+
863
+ 14. result-arrival trigger 消费幂等。
864
+ - 输入:等待方消费一个 `result_arrival` trigger 并完成 generation。
865
+ - 期望:该 trigger 从状态快照删除;崩溃重启后不重复处理同一 dispatch batch。
866
+
867
+ 15. result-arrival 到达时等待方正在 Q4H。
868
+ - 输入:dispatch batch complete,同时等待方 `userWait.kind='awaiting_user_answer'`。
869
+ - 期望:写入 `result_arrival` trigger 和 batch completion facts,但不新增 gen_start;用户回答清除 user wait 后再消费该 trigger。
870
+
871
+ 16. 主线被 `tellaskBack` 时作为 callee。
872
+ - 输入:上游 ask-back directive 指向主线对话。
873
+ - 期望:主线按 active reply obligation 使用 `replyTellaskBack` 收口;该场景不依赖支线式 Diligence recovery。
874
+
875
+ 17. 支线作为 callee 同时作为 caller 发起下游 `tellask`。
876
+ - 期望:active reply obligation 仍保留;下游 pending tellask 是 background work,不让该支线停止推进;若出现非本意直接回复停止驱动,direct-fallback 交给 caller 判断。
877
+
878
+ 18. callee direct-fallback 作为 result-arrival 事实。
879
+ - 输入:callee 未按 reply 工具收口而直接回复停止驱动。
880
+ - 期望:对应 active callee member 写入 `completion.kind='direct_fallback'` 和 memo;batch complete 后只生成 `result_arrival` trigger,不生成独立 direct-fallback trigger。
881
+
882
+ 19. `replyTellaskBack` 写回等待方。
883
+ - 期望:按 directive 的 target dialog/callId 写入 canonical `tellaskBack` result,并触发等待方处理新事实;不按主线/支线形态猜测 target。
884
+
885
+ 20. Q4H 等待用户回答。
886
+ - 期望:状态快照写入 `DialogUserWaitState.kind='awaiting_user_answer'`,Q4H 仍表示等待用户回答,并且不触发 Diligence。
887
+ - 期望:不产生 `mainline_diligence` trigger,也不把 pending tellask 混入等待原因。
888
+
889
+ 21. 状态事件与状态快照更新的原子性。
890
+ - 输入:模拟 event 写入成功但状态快照元信息未更新的异常路径。
891
+ - 期望:runtime 发 warning / structured diagnostic,把该 dialog 转移到 `malformed/`,停止 unsafe drive;不回扫历史补猜。
892
+
893
+ ### 重启恢复测试
894
+
895
+ 1. closed single pending tellask generation + stale `generating=true`。
896
+ - 构造:状态快照有 `generationRunState.kind=closed`、无 `followup` trigger、pending dispatch batch,latest 仍有 stale `generating=true` 或旧 `needsDrive=true` 投影。
897
+ - 期望:restart reconciliation 清 stale generating,不 recover proceeding,不新增 gen_start。
898
+
899
+ 2. open streaming generation crash。
900
+ - 构造:状态快照有 `generationRunState.kind=open`、phase=`streaming`。
901
+ - 期望:recover proceeding,并使用 `resumeInProgressGeneration=true`。
902
+
903
+ 3. pending replyTellask\* crash。
904
+ - 构造:reply delivery 元信息为 pending,包含 expected reply tool、target dialog/callId。
905
+ - 期望:reply recovery 完成交付,result-arrival handling 正常。
906
+
907
+ 4. pending replyTellaskBack crash。
908
+ - 构造:reply delivery 元信息为 pending,target dialog 可以是主线对话。
909
+ - 期望:reply recovery 按 directive 写回 `tellaskBack` result,触发等待方处理新事实,不误判为 ordinary pending tellask。
910
+
911
+ 5. closed pending tellask + pending background records。
912
+ - 期望:displayState 为 `idle_waiting_user`,pendingTellask reminder 保留;若仍有 active reply obligation,则按 callee completion obligation 投影。
913
+
914
+ 6. partial dispatch batch replies before crash。
915
+ - 构造:dispatch batch 元信息里部分 call resolved,仍有 pending call。
916
+ - 期望:重启后不触发 caller 处理,只恢复 batch pending/observability。
917
+
918
+ 7. complete dispatch batch reply delivery before crash。
919
+ - 构造:dispatch batch 元信息显示全部 resolved 且 result-arrival trigger 未消费,latest stale。
920
+ - 期望:重启后可恢复/保留一次 result-arrival trigger,且不因其它 dispatch batch pending 而等待。
921
+
922
+ 8. closed generation with normal immediate tool result but stale latest。
923
+ - 构造:状态快照有 `generationRunState.kind=closed`、`followup` trigger 未消费,latest stale。
924
+ - 期望:不重复 replay tool round;只按 `followup` trigger 启动下一轮,并在 `gen_start_record` + `generationRunState.open.acceptedTriggerIds` 完成 durable handoff 后消费 trigger。
925
+
926
+ 9. stale sideDialog after final response anchor。
927
+ - 期望:最终回复锚点之后的 dead/final sideDialog 不进入 restart recovery,也不生成 drive trigger。
928
+
929
+ 10. missing state metadata after restart。
930
+ - 构造:latest.generationRunState 缺失,但历史 course events 可被扫描补猜。
931
+ - 期望:runtime 发 warning / structured diagnostic,把该 dialog 转移到 `malformed/` 并停止 unsafe recovery;不回扫补齐,也不做第二次自动处理。
932
+
933
+ 11. awaiting user answer after restart。
934
+ - 构造:状态快照有 `DialogUserWaitState.kind='awaiting_user_answer'`,同时存在 pending tellask background work。
935
+ - 期望:重启后仍显示等待用户回答,不产生 `mainline_diligence` / `result_arrival` / `open_generation_recovery` trigger,也不回扫历史补猜等待原因。
936
+
937
+ 12. complete dispatch batch + awaiting user answer after restart。
938
+ - 构造:状态快照已有未消费 `result_arrival` trigger,同时 `DialogUserWaitState.kind='awaiting_user_answer'`。
939
+ - 期望:重启后保留 `result_arrival` trigger,但不启动 gen_start;用户回答清除 user wait 后再消费该 trigger。
940
+
941
+ ### 回归测试关注点
942
+
943
+ - `interruption-resumption.ts` 中依赖 `waiting_for_sideDialogs` 的断言需要按新语义拆分 caller background work / callee completion obligation。
944
+ - `sideDialog-mixed-tool-round-honors-suspension` 重命名为正向业务语义测试:pending sideDialog 不让 caller dialog 暂停,active reply obligation 仍参与完成/恢复判定。
945
+ - run-control visual / counts 需要更新 pending tellask 不计入暂停/可恢复计数。
946
+ - fork snapshot 中 active callee dispatches 仍要复制为 background facts,但不投影为等待被诉请者状态。
947
+
948
+ ## 当前状态
949
+
950
+ 后台 callee dispatch 已收敛到 `active-callees.json`,旧 `pending-sideDialogs.json` 已从运行源里移除。当前运行事实只来自状态快照和显式 pending records,常态业务判定不再回扫 course JSONL 补猜运行事实;course JSONL 只保留为 transcript/context/审计/调试/人类追查/dialog fork 复制输入。
951
+
952
+ 运行与恢复的核心状态已全部显式化:
953
+
954
+ - `generationRunState` 已写入 `latest.yaml`,generation start 标记 `open`,generation finish 标记 `closed`,restart open-generation recovery 只认 `generationRunState.kind === 'open'`。
955
+ - `NextStepTriggerState` 已落地,`queued_prompt`、`root_drive_wake`、`followup`、`mainline_diligence`、`result_arrival`、`open_generation_recovery`、`reply_delivery_recovery` 都是 durable trigger;generation start 会消费 accepted trigger,不保留 consumed tombstone。
956
+ - `nextStep` 已成为必要字段,`latest` 缺失它或 trigger 结构不合法就直接进入 `malformed/`,不再自动补猜。
957
+ - `latest.tellaskCalls` 和 `latest.tellaskResults` 都已落地,tellask call/result duplicate 判定读取 latest 索引,不再扫描当前 course JSONL。
958
+ - `replyDelivery`、`latest.userWait`、`latestAssignmentAnchor`、`sideDialogFinalResponse` 等状态字段都已补齐,恢复/尾部判定优先读状态快照。
959
+
960
+ 调度和唤醒也已收敛:
961
+
962
+ - Root backend wake 只通过显式 next-step API 表达:`root_drive_wake` trigger + registry wake signal;不再存在 `backend_queue` bridge 或 `needsDrive` runtime/API 调用面。
963
+ - `globalDialogRegistry` 只承载 live registry 与 wake signal,`wakeDrive` / `clearDriveWake` / `isDriveWakeQueued` 是唯一公开语义。
964
+ - backend loop 只扫描 live root dialog 本身 + root-local `drive-watch.json` 中的支线子集;支线只在 `latest.nextStep.triggers`、open `generationRunState`、pending/delivered-but-tool-result-pending `replyDelivery` 等 durable work 存在时进入 watch。
965
+ - `root_drive_wake` 若在 generation start 被 accepted 后遇到 core/tail failure,会重新写回 durable wake,避免失败收尾误清队列。
966
+
967
+ UI 和投影语义也已对齐:
968
+
969
+ - pending tellask / active callee dispatch 不再作为 caller 的暂停条件;`getSuspensionStatus()` 只把 Q4H 视为 `canDrive=false` 的用户等待事实。
970
+ - pending tellask 不再投影为 `waiting_for_sideDialogs`;前端以后台被诉请者数量 / FBR 被诉请者数量表达可观测状态。
971
+ - `dlg_background_callee_summary_evt` 已落地,active callee 增加、resolve、registered tellask 替换旧 owner 等路径会广播后台被诉请者数量,前端 dialog list 会及时更新电话听筒 / FBR badge。
972
+ - Diligence Push 已统一通过 `evaluateDiligenceAutoContinueGate()` 判断:active callee / Q4H / stop/interrupted 等边界会阻止鞭策 prompt;若 root 仍有 pending active callee,不消耗预算、不插入鞭策语、不维持主线空转。
973
+
974
+ 重启和异常边界也已收口:
975
+
976
+ - `generating=true` 但缺少 `generationRunState` 的 dialog 会进入 `malformed/`,不再静默停成 `server_restart` 或回扫历史补猜。
977
+ - `reply-special` restart recovery 已改为读取 `latest.replyDelivery`,不再扫描当前 course events 查找 call-without-result。
978
+ - duplicate pending `replyDelivery` 采用保活路径:成功交付后同步 delivered;遇到 stale pending delivery 且新 reply call 对应当前有效 reply obligation 时,记录 loud warn 并替换 pending delivery,避免可恢复对话被 hard stop。
979
+ - runtime reason / error 已收敛到 result-arrival / dispatch-batch 语义;旧 `tellask-revive-context-refactor` 历史设计文档已删除。
980
+
981
+ ## 迁移步骤
982
+
983
+ 1. 新增 generationRunState、DialogUserWaitState、NextStepTriggerState、dispatch batch、reply delivery status 等状态机元信息,并在每次状态转移时同步维护。
984
+ 2. 移除 runtime 历史回扫补猜路径;旧 dialog 缺必要元信息时进入 `malformed/`。如确有业务价值,由人类手工分析 `malformed/` 记录并修正后续跑。
985
+ 3. 新增 fact helpers:user wait、next-step facts、completion obligations、background pending work,全部读取状态快照。
986
+ 4. 调整 dialog `getSuspensionStatus()` 或新增 next-step facts API,先让 drive 使用新 API;API 必须按角色区分 caller background work 与 callee completion obligation。
987
+ 5. 调整 display projection,移除 pending tellask -> `waiting_for_sideDialogs`,覆盖 caller/callee 角色组合。
988
+ 6. 调整 post-tool continuation,把 pending tellask ack 从 immediate followup 中排除并加测试。
989
+ 7. 新增 `active-callees.json`,以 `batchId` 保证多路同组回复齐后才触发 result-arrival handling;消费后删除 batch,不累积历史。
990
+ 8. 调整 Diligence Push,显式读取状态快照里的 pending tellask context 并改文案。
991
+ 9. 调整 restart recovery,仅读取状态快照判断 open/closed generation;缺元信息或冲突时转移到 `malformed/`。
992
+ 10. 调整 proceeding recovery,只恢复真正 open generation。
993
+ 11. 调整 backend loop,避免 `resumeInProgressGeneration` 绕过已 closed background boundary。
994
+ 12. 更新 tests / docs / UI run-control copy。
995
+
996
+ ## 风险与判定
997
+
998
+ ### 风险
999
+
1000
+ - 一些旧测试把 pending sideDialog 当暂停原因;重构后需要明确它们测试的是“后台进行中”还是“等待用户/等待 runtime prompt”。
1001
+ - 主线 Diligence Push 文案或 gate 如果过强,可能又把 pending tellask 表达成必须继续,重新引入后台等待期间的主线空转。
1002
+ - restart recovery 如果过保守,可能导致真实 open generation 崩溃后不恢复。
1003
+ - result-arrival handling 如果过弱,可能导致回贴到了但等待方 dialog 不处理。
1004
+ - active callee batch identity 如果不 durable,崩溃恢复后可能出现部分回贴误触发、整组回贴不触发,或不同批次互相等待。
1005
+ - 历史主线/支线形态特判如果保留过多,会把归档/编排差异误扩散成控制流差异,导致支线推进或 tellaskBack 场景继续出例外。
1006
+ - 状态机元信息如果没有随转移同步维护,开发者可能重新引入历史回扫补猜;这必须作为架构回归处理。
1007
+
1008
+ ### 判定原则
1009
+
1010
+ - pending tellask 存在时,caller dialog 可被驱动,但不是因为 pending tellask 必须驱动。
1011
+ - pending tellask 存在时,caller dialog 可 idle,但 idle 不代表后台完成。
1012
+ - active reply obligation 存在时,当前 dialog 是 callee;它需要最终通过对应 reply path 收口,但它自己的下游 pending tellask 不决定它是否继续推进。
1013
+ - caller 处理 callee 回复时,触发粒度是 dispatch batch complete;单个 `tellask` 是 size = 1 的特例。
1014
+ - 只有不存在 `awaiting_user_answer` 等先决等待事实,且存在未消费 `NextStepTriggerState` trigger 或当前工具轮产生 immediate tool result,才能启动新一轮。
1015
+ - 已有 `gen_finish_record` 的 generation 不应被 proceeding recovery 重放。
1016
+ - runtime 运行路径不得通过回扫聊天记录、course JSONL 或历史 tool calls 补猜当前状态;缺少必要元信息或元信息冲突时转移到 `malformed/`,记录 warning / structured diagnostic,之后不做第二次自动处理。
1017
+ - 主线/支线差异只允许来自编排责任、差遣牒读写责任、归档生命周期;其它 run-control 行为应按统一 dialog 规则解释。