@qwen-code/qwen-code 0.19.11 → 0.19.12-preview.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 (304) hide show
  1. package/bundled/batch/SKILL.md +6 -0
  2. package/bundled/qc-helper/docs/configuration/model-providers.md +11 -0
  3. package/bundled/qc-helper/docs/configuration/settings.md +11 -10
  4. package/bundled/qc-helper/docs/features/channels/overview.md +66 -11
  5. package/bundled/qc-helper/docs/features/sub-agents.md +8 -8
  6. package/bundled/qc-helper/docs/features/worktree.md +2 -2
  7. package/bundled/qc-helper/docs/qwen-serve-deploy-local.md +42 -7
  8. package/bundled/qc-helper/docs/qwen-serve.md +102 -40
  9. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  10. package/bundled/review/SKILL.md +60 -49
  11. package/bundled/simplify/SKILL.md +1 -1
  12. package/chunks/{MaxSizedBox-L6TGYDM3.js → MaxSizedBox-3KIWAU6Z.js} +31 -32
  13. package/chunks/{StandaloneSessionPicker-AKEQHU5P.js → StandaloneSessionPicker-TF626NO7.js} +45 -46
  14. package/chunks/acp-startup-profiler-N3RHHXJ3.js +25 -0
  15. package/chunks/{acpAgent-PTNBGJDN.js → acpAgent-JDVJXVBO.js} +1863 -507
  16. package/chunks/{agent-AJOMOZPJ.js → agent-BONWC4VY.js} +26 -27
  17. package/chunks/{agent-headless-CYKOMAXS.js → agent-headless-H7HKMGLZ.js} +26 -27
  18. package/chunks/{anthropicContentGenerator-HBRXJSP4.js → anthropicContentGenerator-ZZRGFEES.js} +35 -12
  19. package/chunks/{artifact-tool-MIZLXBLF.js → artifact-tool-PHO5XEGZ.js} +5 -5
  20. package/chunks/{askUserQuestion-XMJVPNWM.js → askUserQuestion-CEPBT47X.js} +2 -47
  21. package/chunks/{bridge-QKISWPCI.js → bridge-ATN2TLWJ.js} +31 -32
  22. package/chunks/{ca-CODEN7TD.js → ca-2SL2BXTK.js} +3 -2
  23. package/chunks/{channel-worker-group-SOUWTFHC.js → channel-worker-group-S3H5QHTI.js} +4 -4
  24. package/chunks/{channel-worker-manager-LOVSV3WW.js → channel-worker-manager-ZJ4HTMO2.js} +4 -4
  25. package/chunks/{channel-worker-supervisor-XW6MHJBQ.js → channel-worker-supervisor-JA6VZ5V5.js} +3 -3
  26. package/chunks/{chunk-ZIXOTAPZ.js → chunk-2DZ7E27L.js} +92 -3
  27. package/chunks/{chunk-L5U3ELZX.js → chunk-335ECZTA.js} +1 -1
  28. package/chunks/{chunk-2FO5GFKZ.js → chunk-3DJY4IFO.js} +1 -1
  29. package/chunks/{chunk-4AGTGMCQ.js → chunk-3PE7OY2G.js} +12 -12
  30. package/chunks/{chunk-QOTQH4CB.js → chunk-4AGBO6RB.js} +27 -10
  31. package/chunks/{chunk-Y66VOBRP.js → chunk-4CA4WUMS.js} +2 -2
  32. package/chunks/{chunk-LQIDP6K5.js → chunk-4CHDYDG3.js} +1 -1
  33. package/chunks/{chunk-574PTLMU.js → chunk-4OVECYBM.js} +407 -92
  34. package/chunks/{chunk-TV6K2FB4.js → chunk-4RPARQ5D.js} +2 -2
  35. package/chunks/{chunk-TL4R5TRK.js → chunk-4W55BHOC.js} +2 -2
  36. package/chunks/{chunk-RCEEXIOO.js → chunk-4XCVGEEX.js} +3 -3
  37. package/chunks/{chunk-3DWIFU7L.js → chunk-53UMEONK.js} +1 -1
  38. package/chunks/{chunk-AU3MFJPL.js → chunk-54KPEDM2.js} +8 -8
  39. package/chunks/{chunk-T76DGHGG.js → chunk-66F4IJ4L.js} +5 -5
  40. package/chunks/{chunk-YLSSEUAM.js → chunk-675SVDMD.js} +354 -278
  41. package/chunks/{chunk-NVTQZXR7.js → chunk-6QGZVKOO.js} +91 -52
  42. package/chunks/{chunk-NJOFRXTM.js → chunk-6TDA4EVC.js} +4200 -3450
  43. package/chunks/{chunk-BYH2JRK3.js → chunk-6WZZHJSZ.js} +5 -5
  44. package/chunks/{chunk-7X7TEQNX.js → chunk-76BFY7AF.js} +13 -0
  45. package/chunks/chunk-7KS72E2N.js +883 -0
  46. package/chunks/{chunk-CF5VJRGC.js → chunk-7LMAKIWE.js} +1 -1
  47. package/chunks/{chunk-WMNXXRGL.js → chunk-7O7WZH7N.js} +31 -31
  48. package/chunks/{chunk-7ZYHLN67.js → chunk-7OZGMFHC.js} +26 -75
  49. package/chunks/{chunk-6WPJRYLZ.js → chunk-7SX6WZNO.js} +3 -3
  50. package/chunks/{chunk-JGUT3LWZ.js → chunk-ANRANWVE.js} +4 -1
  51. package/chunks/{chunk-JD44PNGJ.js → chunk-AWOU24F6.js} +10 -5
  52. package/chunks/{chunk-6WGZDD67.js → chunk-BEFQGC6H.js} +2 -2
  53. package/chunks/{chunk-KXYJPRT5.js → chunk-BEZPRKD3.js} +28 -6
  54. package/chunks/{chunk-FYQ22K62.js → chunk-CHVH4DVN.js} +6 -5
  55. package/chunks/{chunk-LS56TX3M.js → chunk-CMHJEUVB.js} +1 -1
  56. package/chunks/{chunk-FXKOAEXP.js → chunk-CXWUXRD5.js} +14 -14
  57. package/chunks/{chunk-CAHY5ABC.js → chunk-DULSE2SH.js} +1 -1
  58. package/chunks/{chunk-SQ5BK3GO.js → chunk-DYUPBU7H.js} +1 -1
  59. package/chunks/{chunk-FNX3LGAK.js → chunk-E655RDRL.js} +1 -1
  60. package/chunks/{chunk-N5VIZTND.js → chunk-EWPLL4OT.js} +1 -1
  61. package/chunks/{chunk-FSA7ERJ2.js → chunk-F2RR4HO2.js} +1 -1
  62. package/chunks/{chunk-S63Z5V42.js → chunk-F2YCCUXI.js} +4 -4
  63. package/chunks/{chunk-3AYLDB6B.js → chunk-FEZXQXW2.js} +1 -1
  64. package/chunks/{chunk-OWT2CAEQ.js → chunk-GEHSF7MS.js} +105 -12
  65. package/chunks/{chunk-LXLTBIDP.js → chunk-HCCR43AY.js} +1 -1
  66. package/chunks/{chunk-7NMU22Q3.js → chunk-HQ7MDVBO.js} +18 -7
  67. package/chunks/{chunk-KSZKSE23.js → chunk-HVWPSGDY.js} +1 -1
  68. package/chunks/{chunk-YFVO2HOR.js → chunk-HZXU6YZW.js} +5 -5
  69. package/chunks/{chunk-HASK4LMI.js → chunk-IKLWU6YS.js} +13 -6
  70. package/chunks/{chunk-GNBBAQ6J.js → chunk-ILQ42UG6.js} +111 -40
  71. package/chunks/{chunk-THXDQSTA.js → chunk-IQRQVPGN.js} +49 -154
  72. package/chunks/{chunk-SPJQO2CE.js → chunk-J2MKYJKZ.js} +2 -2
  73. package/chunks/{chunk-SUARMD4Q.js → chunk-J6NT5YTN.js} +28 -2
  74. package/chunks/{chunk-TYLOFD2U.js → chunk-JCJPZMJV.js} +1 -1
  75. package/chunks/{chunk-BYB3GF2T.js → chunk-JCYMQU5F.js} +133 -6
  76. package/chunks/{chunk-6EDDYPI3.js → chunk-JUNVFQVY.js} +4 -4
  77. package/chunks/{chunk-CH2XVB47.js → chunk-K7P34F2J.js} +3 -3
  78. package/chunks/{chunk-5VWYKIMW.js → chunk-KANTXY7B.js} +2 -2
  79. package/chunks/{chunk-K7VD4XLT.js → chunk-LHEQDKAR.js} +1 -1
  80. package/chunks/{chunk-A2HWMJVO.js → chunk-LKR7QQ63.js} +3 -3
  81. package/chunks/{chunk-4HUDDSO5.js → chunk-LLIGYAQU.js} +4 -4
  82. package/chunks/{chunk-5Z452MMD.js → chunk-LODZMRXC.js} +154 -83
  83. package/chunks/{chunk-FBU7WRZI.js → chunk-LPHGXXZX.js} +6 -1
  84. package/chunks/{chunk-CPFIQVZJ.js → chunk-LYFDJTXE.js} +1 -1
  85. package/chunks/{chunk-TIOE2OPE.js → chunk-MCYZTKBV.js} +2 -2
  86. package/chunks/{chunk-ZQIYGQCG.js → chunk-MHEZV3OY.js} +506 -143
  87. package/chunks/{chunk-JWICTOXS.js → chunk-NGTQA57Y.js} +2 -2
  88. package/chunks/{chunk-5CNYZ7TN.js → chunk-NJQPQ4IO.js} +7 -7
  89. package/chunks/chunk-NSZJQACR.js +45 -0
  90. package/chunks/{chunk-DOPUYNXO.js → chunk-NUHSLBJZ.js} +7 -7
  91. package/chunks/{chunk-CITWNIXJ.js → chunk-NVUGS23Y.js} +5 -5
  92. package/chunks/{chunk-2Z5UX4KI.js → chunk-OSSDHJDU.js} +1 -1
  93. package/chunks/{chunk-YUZI3WAC.js → chunk-OWAK7LMW.js} +7 -0
  94. package/chunks/{chunk-Y3KFTWVX.js → chunk-OZWJAFFE.js} +6 -6
  95. package/chunks/{chunk-YGYSMY2R.js → chunk-PSISZNIT.js} +1629 -1142
  96. package/chunks/{chunk-4ROQY23C.js → chunk-PTXVHS2O.js} +7 -7
  97. package/chunks/{chunk-OKAIGAYW.js → chunk-QGONRHUS.js} +3 -3
  98. package/chunks/{chunk-PZL23GTM.js → chunk-QR6U5QPP.js} +8 -0
  99. package/chunks/chunk-QSG4ZFG4.js +257 -0
  100. package/chunks/{chunk-BXBDC476.js → chunk-R32BTEZY.js} +5 -5
  101. package/chunks/{chunk-AVEOWVOM.js → chunk-R3VWUU2E.js} +1 -1
  102. package/chunks/{chunk-RP5R7MB6.js → chunk-R4RIMOJL.js} +12 -12
  103. package/chunks/{chunk-ARKANCNX.js → chunk-SGPFJMRP.js} +1 -1
  104. package/chunks/{chunk-3M75MSVA.js → chunk-SMASS3CY.js} +1 -1
  105. package/chunks/{chunk-4IJMTYCB.js → chunk-SNONG66P.js} +818 -29
  106. package/chunks/chunk-TDKQP3M6.js +201 -0
  107. package/chunks/{chunk-VFX7EZIR.js → chunk-TFV3XVAZ.js} +8 -1
  108. package/chunks/{chunk-4LD5APFL.js → chunk-TYQMGACW.js} +1 -0
  109. package/chunks/{chunk-7NFB7LBQ.js → chunk-UE5TZ55T.js} +8 -0
  110. package/chunks/{chunk-F333XEAU.js → chunk-UOA2IHRI.js} +4 -3
  111. package/chunks/{chunk-VRR65QYW.js → chunk-V4PCIS25.js} +5 -1
  112. package/chunks/{chunk-WIEO4CWB.js → chunk-VAJS2E2D.js} +1 -1
  113. package/chunks/{chunk-GLNHPRZT.js → chunk-W4DFAUAO.js} +1 -1
  114. package/chunks/{chunk-STZQCNN3.js → chunk-W7H223ZZ.js} +1 -1
  115. package/chunks/{chunk-6Y6ORE2G.js → chunk-WACI4VES.js} +288 -20
  116. package/chunks/{chunk-WTNZ6DXB.js → chunk-WEPXQKGP.js} +1 -1
  117. package/chunks/{chunk-4BJVSIS3.js → chunk-WHQNBBWW.js} +2 -2
  118. package/chunks/{chunk-QDJ2R4CM.js → chunk-WTHRMVKB.js} +3 -3
  119. package/chunks/{chunk-L4Y55KIS.js → chunk-X6YQWCIJ.js} +1 -1
  120. package/chunks/{chunk-Q6TUALBE.js → chunk-XAAEDML2.js} +1 -1
  121. package/chunks/{chunk-BUAXJVNP.js → chunk-XACBIZJB.js} +3 -3
  122. package/chunks/{chunk-2OG646IN.js → chunk-XFJU7SVP.js} +4 -4
  123. package/chunks/{chunk-KKRTYONM.js → chunk-XSPFQQRE.js} +3 -3
  124. package/chunks/{chunk-V2DXTLUE.js → chunk-Y5UDBQYF.js} +3 -3
  125. package/chunks/{chunk-MQOVXZAF.js → chunk-YN2I5I42.js} +529 -61
  126. package/chunks/{chunk-FMNYGWOD.js → chunk-Z4ZCVYJ5.js} +14 -8
  127. package/chunks/{chunk-SY7NEYX4.js → chunk-ZFHHBRRA.js} +2 -2
  128. package/chunks/{chunk-QYG4BY6Q.js → chunk-ZPVGEMVC.js} +1 -1
  129. package/chunks/{computer-use-VZZMJEQ7.js → computer-use-UY4A72X7.js} +26 -27
  130. package/chunks/{config-utils-IQQT3GCI.js → config-utils-KWTRFP7D.js} +3 -3
  131. package/chunks/{contextCommand-PORKIZSY.js → contextCommand-3DZ2SVH6.js} +30 -31
  132. package/chunks/{create-sub-session-EAB2U5XW.js → create-sub-session-DIJOV6HV.js} +2 -2
  133. package/chunks/{create-sub-session-XVFKHAFW.js → create-sub-session-OT7WXLDO.js} +27 -28
  134. package/chunks/{cron-create-GMVKSXZT.js → cron-create-X462ERYA.js} +4 -4
  135. package/chunks/{cron-delete-VDZKUAVK.js → cron-delete-SL5UJUM3.js} +4 -4
  136. package/chunks/{cron-list-UG7C7RAR.js → cron-list-3MD6SVXI.js} +4 -4
  137. package/chunks/{daemon-IHR6WSPB.js → daemon-ZW6LHOXT.js} +91 -14
  138. package/chunks/{daemon-status-provider-ABLBA3UJ.js → daemon-status-provider-GTZOYSBO.js} +35 -36
  139. package/chunks/{de-NOXPCSYN.js → de-I7ZIYXZC.js} +3 -2
  140. package/chunks/{dist-PX2ERZ6I.js → dist-HCUUS6IH.js} +2 -2
  141. package/chunks/{dist-VYWUKURL.js → dist-O2IP3G5D.js} +1 -1
  142. package/chunks/{dist-GXJVCOS7.js → dist-RZBJT5O2.js} +2 -1
  143. package/chunks/{dist-V2YVLYOW.js → dist-Y54RURGT.js} +3 -1
  144. package/chunks/{dist-DOPL5LSQ.js → dist-YTPTCUAD.js} +1 -1
  145. package/chunks/{dist-4WCQEZIN.js → dist-ZJHKKWUD.js} +1 -1
  146. package/chunks/{earlyInputCapture-DJCU5SPU.js → earlyInputCapture-GGWVGBZ7.js} +27 -28
  147. package/chunks/{edit-O4VV5AO6.js → edit-JN467GIR.js} +27 -28
  148. package/chunks/{en-JFVULHTB.js → en-XE34GKAA.js} +7 -3
  149. package/chunks/{enter-worktree-XQYDDI2I.js → enter-worktree-MMO4VBQE.js} +26 -27
  150. package/chunks/{enterPlanMode-CESVMQBV.js → enterPlanMode-BRZOG4DB.js} +27 -30
  151. package/chunks/{environment-5KXOLZNR.js → environment-LOCDCWMZ.js} +29 -30
  152. package/chunks/{errors-EBUTCBAQ.js → errors-XXSL5HGG.js} +29 -30
  153. package/chunks/{exit-worktree-MWGNSPNX.js → exit-worktree-UD7PI5H5.js} +26 -27
  154. package/chunks/exitPlanMode-EIUETQI6.js +389 -0
  155. package/chunks/{fast-path-6V73F2T2.js → fast-path-ULGAEUVK.js} +2 -2
  156. package/chunks/{fr-HVFNU42H.js → fr-X7KDVZIY.js} +3 -2
  157. package/chunks/{gemini-TEFJZCIZ.js → gemini-KW3APZ4P.js} +133 -80
  158. package/chunks/{geminiContentGenerator-M56BEXWI.js → geminiContentGenerator-4BQ5QHPR.js} +5 -5
  159. package/chunks/{glob-DMYFEEAZ.js → glob-FPKAMR6B.js} +32 -28
  160. package/chunks/{grep-GYE47MD4.js → grep-MOGRCYB4.js} +32 -28
  161. package/chunks/{handleAutoUpdate-OLUY4L7L.js → handleAutoUpdate-QUMRZZOS.js} +33 -34
  162. package/chunks/{i18n-462JC7IY.js → i18n-F3W6YUYP.js} +28 -29
  163. package/chunks/{initializer-Y7WD4D2I.js → initializer-KLU2KAIZ.js} +33 -34
  164. package/chunks/{installationInfo-I2Z3BKCE.js → installationInfo-P3ZP2SIZ.js} +27 -28
  165. package/chunks/{ja-PY5AF544.js → ja-2ICT3KXF.js} +3 -2
  166. package/chunks/{keychain-token-storage-AU22CQI6.js → keychain-token-storage-K23CFKGN.js} +2 -2
  167. package/chunks/{list-XQ5MSAXP.js → list-4X6D5JD2.js} +35 -36
  168. package/chunks/{loadedSettingsAdapter-TQOWYE7C.js → loadedSettingsAdapter-446B6WKA.js} +32 -33
  169. package/chunks/{loop-wakeup-QXWZSCYM.js → loop-wakeup-6H7OENTB.js} +5 -5
  170. package/chunks/{ls-2OUTG3IZ.js → ls-APTSOOS5.js} +4 -4
  171. package/chunks/{lsp-4VMNXZW6.js → lsp-HPACPJAN.js} +2 -2
  172. package/chunks/{mcp-22LLEEVM.js → mcp-JYVOJZOD.js} +32 -33
  173. package/chunks/{monitor-VKG324X3.js → monitor-KY7YH7TL.js} +26 -27
  174. package/chunks/nonInteractiveCli-EK35TLVJ.js +129 -0
  175. package/chunks/{notebook-edit-FJJRBQ3S.js → notebook-edit-IHGW42QK.js} +27 -28
  176. package/chunks/{openaiContentGenerator-4T2OGTR5.js → openaiContentGenerator-FURUCKQJ.js} +13 -14
  177. package/chunks/{pidfile-XL3MDCBM.js → pidfile-WKUVXDMR.js} +27 -28
  178. package/chunks/{processUtils-7UCFN34D.js → processUtils-LHSRN76X.js} +2 -2
  179. package/chunks/{pt-A3PRQXMU.js → pt-JJ4XPIBP.js} +3 -2
  180. package/chunks/{qwenContentGenerator-PQUXJDIZ.js → qwenContentGenerator-NRLM7R36.js} +30 -31
  181. package/chunks/{qwenOAuth2-LEYOU7RU.js → qwenOAuth2-5FNX3AWH.js} +22 -5
  182. package/chunks/{read-file-3EYQML7E.js → read-file-VLEAEXFP.js} +9 -10
  183. package/chunks/{read-mcp-resource-JUFHCKSC.js → read-mcp-resource-C6NT52T6.js} +2 -2
  184. package/chunks/{record-artifact-DWSMBXYY.js → record-artifact-ENCU7QCL.js} +2 -2
  185. package/chunks/{ripGrep-S6WPGQNA.js → ripGrep-MT23EIDD.js} +26 -27
  186. package/chunks/{ru-IBCWUTOC.js → ru-STR4KTW7.js} +3 -2
  187. package/chunks/{run-qwen-serve-NSKBT3HU.js → run-qwen-serve-5HWOQFLI.js} +63 -47
  188. package/chunks/{runtime-LG5ZZ3VH.js → runtime-6IVKJSLW.js} +38 -37
  189. package/chunks/{scheduler-PLX3VVTB.js → scheduler-63GJG75U.js} +26 -27
  190. package/chunks/{send-message-JZ752NWD.js → send-message-5GZ4FX2V.js} +3 -3
  191. package/chunks/{serve-S2ZLRZZR.js → serve-3LL7RRDP.js} +32 -33
  192. package/chunks/{server-5ECMV4TM.js → server-AA24QLPA.js} +1672 -615
  193. package/chunks/{session-ILSONWG4.js → session-LRVCFQQQ.js} +102 -75
  194. package/chunks/{settings-BOE7WWSA.js → settings-ZX3OKUT4.js} +36 -37
  195. package/chunks/{shell-VW664JIZ.js → shell-KR3IMYH6.js} +26 -27
  196. package/chunks/{skill-COHV6625.js → skill-QLM2LSF7.js} +11 -12
  197. package/chunks/{spawnChannel-5UUR5XOZ.js → spawnChannel-6WFOK3SA.js} +28 -29
  198. package/chunks/{src-XPKFZ3RN.js → src-ADLNIPHD.js} +101 -72
  199. package/chunks/{standalone-update-IOOJ2NZQ.js → standalone-update-C54OTJWJ.js} +29 -30
  200. package/chunks/{startInteractiveUI-3NGJGXXS.js → startInteractiveUI-KFAOS6HC.js} +2342 -1796
  201. package/chunks/{stdioHelpers-UMY72QRP.js → stdioHelpers-HGIYTWAY.js} +3 -1
  202. package/chunks/{syntheticOutput-LELY6HAI.js → syntheticOutput-ULOVVLY4.js} +3 -3
  203. package/chunks/{task-create-QY3BIMNM.js → task-create-DUE2PMNI.js} +7 -7
  204. package/chunks/{task-list-D2U47WBJ.js → task-list-WVPNX7BZ.js} +5 -5
  205. package/chunks/{task-stop-N6E5IYLH.js → task-stop-CPEQWOT6.js} +2 -2
  206. package/chunks/{task-update-HJCQILLB.js → task-update-HY2LNFFB.js} +7 -7
  207. package/chunks/{team-create-UPLVZQFX.js → team-create-Q5B5M7UD.js} +26 -27
  208. package/chunks/{team-delete-RCWYDRF4.js → team-delete-JJV6IUOA.js} +5 -5
  209. package/chunks/{team-plan-approval-F5F432RO.js → team-plan-approval-J55PXNY2.js} +26 -27
  210. package/chunks/{theme-manager-JJE4BJ67.js → theme-manager-QWTDRPHV.js} +27 -28
  211. package/chunks/todoWrite-JHHIDOIV.js +358 -0
  212. package/chunks/{tool-search-LRXI4JLZ.js → tool-search-LSSKWZKH.js} +10 -11
  213. package/chunks/{total-session-admission-I4DHN6UP.js → total-session-admission-S6ATY5HY.js} +32 -33
  214. package/chunks/{trustedFolders-SEH7LNX2.js → trustedFolders-CY4TUPDJ.js} +28 -29
  215. package/chunks/turndown.cjs-FZHGMUTY.js +17670 -0
  216. package/chunks/{update-relaunch-T6ZKBUA6.js → update-relaunch-IOKOUV3K.js} +6 -6
  217. package/chunks/{updateCheck-FWLXZPRO.js → updateCheck-YNRFE4V7.js} +30 -31
  218. package/chunks/{validateNonInterActiveAuth-X6K2VWYT.js → validateNonInterActiveAuth-OXTECS4B.js} +68 -68
  219. package/chunks/{version-LIFSOGSE.js → version-2CZQQDL5.js} +1 -1
  220. package/chunks/web-fetch-7QTEGJI5.js +938 -0
  221. package/chunks/{workflow-NX2DFWGD.js → workflow-G3CZU256.js} +29 -30
  222. package/chunks/{workspace-providers-status-VYD6M2NE.js → workspace-providers-status-UHQOIPAZ.js} +35 -36
  223. package/chunks/{workspace-registration-store-2Y6PWOEL.js → workspace-registration-store-5WPCMX4F.js} +1 -1
  224. package/chunks/{workspace-registry-PKBDBWCA.js → workspace-registry-LAQCRQTH.js} +32 -33
  225. package/chunks/{workspace-service-HHORUHTS.js → workspace-service-6ZEUJY2Z.js} +38 -39
  226. package/chunks/{workspace-skills-status-O5Q26LIY.js → workspace-skills-status-YG6OITSM.js} +33 -34
  227. package/chunks/{write-file-NVAEZ3J2.js → write-file-E7T3PU3B.js} +60 -28
  228. package/chunks/{zh-PAUNIMHQ.js → zh-6ANG2HTL.js} +7 -3
  229. package/chunks/{zh-TW-GNEUIUFF.js → zh-TW-4RR3YHNN.js} +7 -3
  230. package/cli-entry.js +37 -2
  231. package/cli.js +17 -11
  232. package/locales/ca.js +3 -2
  233. package/locales/de.js +3 -2
  234. package/locales/en.js +10 -5
  235. package/locales/fr.js +3 -2
  236. package/locales/ja.js +3 -2
  237. package/locales/pt.js +3 -2
  238. package/locales/ru.js +3 -2
  239. package/locales/zh-TW.js +9 -3
  240. package/locales/zh.js +9 -3
  241. package/package.json +3 -3
  242. package/web-shell/assets/{arc-H74fE8UR.js → arc-_dVIKMdB.js} +1 -1
  243. package/web-shell/assets/{architectureDiagram-3BPJPVTR-DN3kEq-I.js → architectureDiagram-3BPJPVTR-MuN3nKpL.js} +1 -1
  244. package/web-shell/assets/{blockDiagram-GPEHLZMM-BrqbsFUF.js → blockDiagram-GPEHLZMM-BtmvJjzx.js} +1 -1
  245. package/web-shell/assets/{c4Diagram-AAUBKEIU-ClFhU1P-.js → c4Diagram-AAUBKEIU-Dvi58_BU.js} +1 -1
  246. package/web-shell/assets/channel-Bi5TehaT.js +1 -0
  247. package/web-shell/assets/{chunk-2J33WTMH-jCIcG0_M.js → chunk-2J33WTMH-YqxOVhS1.js} +1 -1
  248. package/web-shell/assets/{chunk-4BX2VUAB-DSkGe62-.js → chunk-4BX2VUAB-DoBgbC9z.js} +1 -1
  249. package/web-shell/assets/{chunk-55IACEB6-BxBHOGTd.js → chunk-55IACEB6-3NEQrBAr.js} +1 -1
  250. package/web-shell/assets/{chunk-727SXJPM-v_DS7dH7.js → chunk-727SXJPM-DrCZQxp0.js} +1 -1
  251. package/web-shell/assets/{chunk-AQP2D5EJ-1RzEKRPq.js → chunk-AQP2D5EJ-m_g3zjgv.js} +1 -1
  252. package/web-shell/assets/{chunk-FMBD7UC4-DyrSbLOX.js → chunk-FMBD7UC4-D1_crABJ.js} +1 -1
  253. package/web-shell/assets/{chunk-ND2GUHAM-BCmdsZiy.js → chunk-ND2GUHAM-BdhgVtBx.js} +1 -1
  254. package/web-shell/assets/{chunk-QZHKN3VN-CphHGW2D.js → chunk-QZHKN3VN-BlmThhm-.js} +1 -1
  255. package/web-shell/assets/classDiagram-4FO5ZUOK-BKhhRdd1.js +1 -0
  256. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BKhhRdd1.js +1 -0
  257. package/web-shell/assets/{cose-bilkent-S5V4N54A-D5kRC7Fi.js → cose-bilkent-S5V4N54A-Cbjm_00Z.js} +1 -1
  258. package/web-shell/assets/{dagre-BM42HDAG-DIiyJJ0U.js → dagre-BM42HDAG-C39QNVgC.js} +1 -1
  259. package/web-shell/assets/{diagram-2AECGRRQ-BOcuB8xU.js → diagram-2AECGRRQ-DNXSAixC.js} +1 -1
  260. package/web-shell/assets/{diagram-5GNKFQAL-CMgiZ4d3.js → diagram-5GNKFQAL-BCbGUPEA.js} +1 -1
  261. package/web-shell/assets/{diagram-KO2AKTUF-Ctx8pfN0.js → diagram-KO2AKTUF-1eujKSDC.js} +1 -1
  262. package/web-shell/assets/{diagram-LMA3HP47-BT5REAWC.js → diagram-LMA3HP47-DUMKXgql.js} +1 -1
  263. package/web-shell/assets/{diagram-OG6HWLK6-CJkUqXcp.js → diagram-OG6HWLK6-BFktU3_u.js} +1 -1
  264. package/web-shell/assets/{erDiagram-TEJ5UH35-DyvNNX6v.js → erDiagram-TEJ5UH35-BvJC7DLk.js} +1 -1
  265. package/web-shell/assets/{flowDiagram-I6XJVG4X-VBf8_My8.js → flowDiagram-I6XJVG4X-Cgu3PlOt.js} +1 -1
  266. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CviJnR7B.js → ganttDiagram-6RSMTGT7-DLNXN5Xh.js} +1 -1
  267. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-BY66_Oze.js → gitGraphDiagram-PVQCEYII-BQFRDv6R.js} +1 -1
  268. package/web-shell/assets/index-C_ouplMQ.js +1187 -0
  269. package/web-shell/assets/{index-DhYuvi3j.js → index-CdC3pjP3.js} +1 -1
  270. package/web-shell/assets/index-XGDFJE-X.css +5 -0
  271. package/web-shell/assets/{infoDiagram-5YYISTIA-D2jaewWZ.js → infoDiagram-5YYISTIA-DT2mOSvp.js} +1 -1
  272. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-BpnNTyi5.js → ishikawaDiagram-YF4QCWOH-9fy_K4bd.js} +1 -1
  273. package/web-shell/assets/{journeyDiagram-JHISSGLW-CA55GNZF.js → journeyDiagram-JHISSGLW-CoGSEuhE.js} +1 -1
  274. package/web-shell/assets/{kanban-definition-UN3LZRKU-DyiSl_Ts.js → kanban-definition-UN3LZRKU-BP9hHYIj.js} +1 -1
  275. package/web-shell/assets/{linear-B-sN3dkV.js → linear-uIroDsmp.js} +1 -1
  276. package/web-shell/assets/{mermaid.core-xf9SXDdb.js → mermaid.core-BUwoStsA.js} +5 -5
  277. package/web-shell/assets/{mindmap-definition-RKZ34NQL-DXhr2kRk.js → mindmap-definition-RKZ34NQL-DZB-FWim.js} +1 -1
  278. package/web-shell/assets/{pieDiagram-4H26LBE5-DUXpbfGQ.js → pieDiagram-4H26LBE5-YPptloKl.js} +1 -1
  279. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-NeDQYeWw.js → quadrantDiagram-W4KKPZXB-dgk3DSzm.js} +1 -1
  280. package/web-shell/assets/{requirementDiagram-4Y6WPE33-BSbFRL1A.js → requirementDiagram-4Y6WPE33-BH2En2i9.js} +1 -1
  281. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-gg27cAaN.js → sankeyDiagram-5OEKKPKP-BB98feeI.js} +1 -1
  282. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-Bi_1TSs4.js → sequenceDiagram-3UESZ5HK-CunAIGh8.js} +1 -1
  283. package/web-shell/assets/{stateDiagram-AJRCARHV-CboCCRaf.js → stateDiagram-AJRCARHV-DTJs_-I2.js} +1 -1
  284. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DrfFMYta.js +1 -0
  285. package/web-shell/assets/{timeline-definition-PNZ67QCA-DrGpF_jn.js → timeline-definition-PNZ67QCA-Cd4AN7O0.js} +1 -1
  286. package/web-shell/assets/{vennDiagram-CIIHVFJN-DCaJU67x.js → vennDiagram-CIIHVFJN-xIG-rUU9.js} +1 -1
  287. package/web-shell/assets/{wardley-L42UT6IY-p0QcW1aP.js → wardley-L42UT6IY-D1UyjIcG.js} +1 -1
  288. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-DiEVxj1W.js → wardleyDiagram-YWT4CUSO-BVM0Mb99.js} +1 -1
  289. package/web-shell/assets/{xychartDiagram-2RQKCTM6-iQB8nSlA.js → xychartDiagram-2RQKCTM6-BX31JDSz.js} +1 -1
  290. package/web-shell/index.html +2 -2
  291. package/chunks/chunk-2HRYPZT5.js +0 -25
  292. package/chunks/chunk-QAW7PIHT.js +0 -168
  293. package/chunks/chunk-RTV5TUYB.js +0 -143
  294. package/chunks/chunk-ZPAHLFZU.js +0 -172
  295. package/chunks/exitPlanMode-HYQL4XMB.js +0 -909
  296. package/chunks/nonInteractiveCli-UMYUT643.js +0 -129
  297. package/chunks/todoWrite-FKCGATPJ.js +0 -531
  298. package/chunks/web-fetch-CFCWTCAP.js +0 -5673
  299. package/web-shell/assets/channel-NFC5ADAa.js +0 -1
  300. package/web-shell/assets/classDiagram-4FO5ZUOK-DHgwjz3X.js +0 -1
  301. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-DHgwjz3X.js +0 -1
  302. package/web-shell/assets/index-8xIBbHV3.js +0 -1143
  303. package/web-shell/assets/index-DLyhlZZO.css +0 -5
  304. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CXRqms6P.js +0 -1
@@ -1,58 +1,55 @@
1
1
  // Force strict mode and setup for ESM
2
2
  "use strict";
3
3
  import {
4
- classifyApiError,
5
- prefixMidTurnUserMessageParts
6
- } from "./chunk-YLSSEUAM.js";
4
+ recomputeMcpGating
5
+ } from "./chunk-6WZZHJSZ.js";
6
+ import {
7
+ classifyApiError
8
+ } from "./chunk-675SVDMD.js";
7
9
  import {
8
10
  EXTENSION_CONTEXT_BUDGET,
9
11
  buildExtensionMentionContext,
10
12
  buildMcpServerContextText,
13
+ extractAtPathCommands,
11
14
  matchExtensionByRef,
12
15
  matchMcpServerByRef,
13
16
  parseExtensionRef,
14
17
  parseMcpServerRef
15
- } from "./chunk-HASK4LMI.js";
18
+ } from "./chunk-IKLWU6YS.js";
16
19
  import {
17
20
  getAvailableCommands,
18
21
  handleSlashCommand,
19
22
  insertAfterFunctionResponses,
20
23
  isSlashCommand,
21
24
  normalizePartList
22
- } from "./chunk-THXDQSTA.js";
23
- import {
24
- collectContextData,
25
- formatContextUsageText
26
- } from "./chunk-FMNYGWOD.js";
27
- import {
28
- recomputeMcpGating
29
- } from "./chunk-BYH2JRK3.js";
25
+ } from "./chunk-IQRQVPGN.js";
30
26
  import {
31
27
  appEvents,
32
28
  buildDisabledSkillNamesProvider,
33
29
  loadCliConfig,
34
30
  normalizeDisabledToolList
35
- } from "./chunk-YGYSMY2R.js";
31
+ } from "./chunk-PSISZNIT.js";
32
+ import "./chunk-5DTTYNB7.js";
33
+ import {
34
+ collectContextData,
35
+ formatContextUsageText
36
+ } from "./chunk-Z4ZCVYJ5.js";
36
37
  import {
37
38
  getCommandSubcommandNames
38
- } from "./chunk-BYB3GF2T.js";
39
- import "./chunk-FYQ22K62.js";
39
+ } from "./chunk-JCYMQU5F.js";
40
+ import "./chunk-CHVH4DVN.js";
40
41
  import {
41
42
  getEffectiveSupportedModes
42
- } from "./chunk-NVTQZXR7.js";
43
+ } from "./chunk-6QGZVKOO.js";
43
44
  import "./chunk-4WGODMVR.js";
44
45
  import "./chunk-KW7NOTN6.js";
45
- import "./chunk-OWT2CAEQ.js";
46
- import "./chunk-5DTTYNB7.js";
46
+ import "./chunk-GEHSF7MS.js";
47
+ import "./chunk-XAAEDML2.js";
48
+ import "./chunk-NVUGS23Y.js";
47
49
  import "./chunk-NHDKSOEY.js";
48
- import {
49
- isTerminalGoalStatusKind
50
- } from "./chunk-7X7TEQNX.js";
51
- import "./chunk-HTHUX2T5.js";
52
- import "./chunk-TL4R5TRK.js";
53
- import "./chunk-V2DXTLUE.js";
50
+ import "./chunk-4W55BHOC.js";
51
+ import "./chunk-Y5UDBQYF.js";
54
52
  import "./chunk-YAF2MQZ6.js";
55
- import "./chunk-Q6TUALBE.js";
56
53
  import {
57
54
  GENERATION_MAX_PROMPT_BYTES,
58
55
  GENERATION_TIMEOUT_MS,
@@ -71,34 +68,51 @@ import {
71
68
  shouldSuppressRememberErrorDetails,
72
69
  workspaceMemoryFailureCode,
73
70
  workspaceMemoryFailureDiagnostics
74
- } from "./chunk-QOTQH4CB.js";
71
+ } from "./chunk-4AGBO6RB.js";
75
72
  import {
76
73
  HistoryReplayer,
77
74
  MessageEmitter,
78
75
  PlanEmitter,
79
- ToolCallEmitter
80
- } from "./chunk-6Y6ORE2G.js";
81
- import "./chunk-CF5VJRGC.js";
82
- import "./chunk-SUARMD4Q.js";
76
+ ToolCallEmitter,
77
+ collectGoalStatusItemsFromRecords,
78
+ goalTerminalEventToHistoryItem,
79
+ recordGoalStatusItem,
80
+ restoreGoalFromHistory
81
+ } from "./chunk-WACI4VES.js";
82
+ import "./chunk-QSG4ZFG4.js";
83
+ import "./chunk-7LMAKIWE.js";
84
+ import "./chunk-J6NT5YTN.js";
83
85
  import "./chunk-QHTIBUWB.js";
86
+ import {
87
+ isTerminalGoalStatusKind
88
+ } from "./chunk-76BFY7AF.js";
89
+ import "./chunk-HTHUX2T5.js";
84
90
  import "./chunk-L573Z2QZ.js";
85
91
  import {
86
92
  computeCpuPercent
87
93
  } from "./chunk-HGT6JR3U.js";
88
94
  import "./chunk-RXFQM6FQ.js";
89
- import "./chunk-Y3KFTWVX.js";
95
+ import "./chunk-OZWJAFFE.js";
90
96
  import "./chunk-6H67XLET.js";
91
- import "./chunk-CITWNIXJ.js";
92
- import "./chunk-4HUDDSO5.js";
97
+ import {
98
+ beginAcpBootstrapConfigProfiling,
99
+ buildAndFreezeAcpStartupProfile,
100
+ endAcpBootstrapConfigProfiling,
101
+ markAcpStartup
102
+ } from "./chunk-TDKQP3M6.js";
103
+ import "./chunk-LLIGYAQU.js";
93
104
  import "./chunk-LXXMOYPL.js";
94
- import "./chunk-S63Z5V42.js";
105
+ import "./chunk-F2YCCUXI.js";
95
106
  import "./chunk-E5Z2AVNV.js";
96
107
  import {
97
- formatAcpModelId,
108
+ ACP_ROUTE_ID_PREFIX,
109
+ buildAcpModelOptions,
110
+ getCurrentAcpModelId,
98
111
  parseAcpBaseModelId,
99
112
  parseAcpModelOption,
113
+ resolveAcpModelOption,
100
114
  sanitizeProviderBaseUrl
101
- } from "./chunk-ZIXOTAPZ.js";
115
+ } from "./chunk-2DZ7E27L.js";
102
116
  import {
103
117
  mapSkillConfigToStatus
104
118
  } from "./chunk-2W3OOD4W.js";
@@ -108,44 +122,39 @@ import {
108
122
  buildPermissionSettings,
109
123
  normalizePermissionRules,
110
124
  readPermissionRuleSet
111
- } from "./chunk-K7VD4XLT.js";
125
+ } from "./chunk-LHEQDKAR.js";
112
126
  import {
113
127
  MAX_AUDIO_BYTES,
114
128
  sanitizeVoiceErrorMessage,
115
129
  transcribeVoiceAudio
116
- } from "./chunk-BUAXJVNP.js";
130
+ } from "./chunk-XACBIZJB.js";
117
131
  import {
118
132
  readVoiceModel
119
- } from "./chunk-3M75MSVA.js";
120
- import "./chunk-FSA7ERJ2.js";
133
+ } from "./chunk-SMASS3CY.js";
134
+ import "./chunk-F2RR4HO2.js";
121
135
  import {
122
136
  parseSessionSource
123
- } from "./chunk-574PTLMU.js";
137
+ } from "./chunk-4OVECYBM.js";
138
+ import "./chunk-6RLROE6G.js";
139
+ import "./chunk-COH43MRJ.js";
124
140
  import {
141
+ CHANNEL_STARTUP_PROFILE_META_KEY,
142
+ CHANNEL_STARTUP_PROFILE_VERSION,
125
143
  CLIENT_MCP_OVER_WS_CONFIG_FLAG,
126
144
  LOAD_REPLAY_BULK_MODE,
127
145
  LOAD_REPLAY_META_KEY,
128
146
  LOAD_REPLAY_MODE_META_KEY,
147
+ LOAD_REPLAY_PAGE_SIZE_META_KEY,
129
148
  LOAD_REPLAY_VERSION,
130
- MID_TURN_QUEUE_DRAIN_METHOD
131
- } from "./chunk-PZL23GTM.js";
132
- import "./chunk-6RLROE6G.js";
133
- import "./chunk-COH43MRJ.js";
134
- import {
135
- NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE
136
- } from "./chunk-L4Y55KIS.js";
137
- import "./chunk-7NMU22Q3.js";
138
- import "./chunk-YFVO2HOR.js";
139
- import "./chunk-57OAHC2Y.js";
140
- import "./chunk-5CNYZ7TN.js";
141
- import "./chunk-FNX3LGAK.js";
142
- import "./chunk-V6WHFGST.js";
143
- import {
144
- createLoadedSettingsAdapter
145
- } from "./chunk-SQ5BK3GO.js";
149
+ MID_TURN_QUEUE_DRAIN_METHOD,
150
+ TODO_STOP_GUARD_QUEUE_RELEASE_METHOD
151
+ } from "./chunk-QR6U5QPP.js";
146
152
  import {
147
153
  ndJsonStream
148
- } from "./chunk-3AYLDB6B.js";
154
+ } from "./chunk-FEZXQXW2.js";
155
+ import {
156
+ NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE
157
+ } from "./chunk-X6YQWCIJ.js";
149
158
  import {
150
159
  ACP_PREFLIGHT_KINDS,
151
160
  IDLE_HOOK_EVENTS,
@@ -153,14 +162,23 @@ import {
153
162
  SERVE_STATUS_EXT_METHODS,
154
163
  STATUS_SCHEMA_VERSION,
155
164
  mapDomainErrorToErrorKind
156
- } from "./chunk-VFX7EZIR.js";
165
+ } from "./chunk-TFV3XVAZ.js";
166
+ import "./chunk-HQ7MDVBO.js";
167
+ import "./chunk-HZXU6YZW.js";
168
+ import "./chunk-57OAHC2Y.js";
169
+ import "./chunk-NJQPQ4IO.js";
170
+ import "./chunk-E655RDRL.js";
171
+ import "./chunk-V6WHFGST.js";
172
+ import {
173
+ createLoadedSettingsAdapter
174
+ } from "./chunk-DYUPBU7H.js";
157
175
  import "./chunk-W7FJ3N32.js";
158
- import "./chunk-KSZKSE23.js";
176
+ import "./chunk-HVWPSGDY.js";
159
177
  import "./chunk-TJTKSVIV.js";
160
178
  import "./chunk-M5GB774H.js";
161
- import "./chunk-LXLTBIDP.js";
162
- import "./chunk-3DWIFU7L.js";
163
- import "./chunk-ZQIYGQCG.js";
179
+ import "./chunk-HCCR43AY.js";
180
+ import "./chunk-53UMEONK.js";
181
+ import "./chunk-MHEZV3OY.js";
164
182
  import {
165
183
  AgentSideConnection,
166
184
  PROTOCOL_VERSION,
@@ -169,48 +187,47 @@ import {
169
187
  import {
170
188
  assembleMcpServers,
171
189
  loadMcpApprovals
172
- } from "./chunk-4ROQY23C.js";
190
+ } from "./chunk-PTXVHS2O.js";
173
191
  import {
174
- OUTPUT_LANGUAGE_AUTO,
175
192
  getOutputLanguageFilePath,
176
193
  getPersistScopeForModelSelection,
177
194
  getUserSettingsPath,
178
- isAutoLanguage,
179
195
  loadSettings,
180
- resolveOutputLanguage,
196
+ resolveOutputLanguageOrPreserveAuto,
181
197
  updateOutputLanguageFile,
182
198
  writeOutputLanguageAndRegisterPath
183
- } from "./chunk-GNBBAQ6J.js";
199
+ } from "./chunk-ILQ42UG6.js";
184
200
  import "./chunk-RKUWKYED.js";
201
+ import "./chunk-GOXKNSDZ.js";
202
+ import {
203
+ getSystemDefaultsPath,
204
+ getSystemSettingsPath
205
+ } from "./chunk-IDS7MSUP.js";
185
206
  import {
186
207
  SUPPORTED_LANGUAGES,
187
208
  getCurrentLanguage,
188
209
  setLanguageAsync
189
- } from "./chunk-4AGTGMCQ.js";
210
+ } from "./chunk-3PE7OY2G.js";
190
211
  import {
191
212
  findEnvFiles,
192
213
  preResolveHomeEnvOverrides,
193
214
  reloadEnvironment
194
- } from "./chunk-JWICTOXS.js";
195
- import "./chunk-GOXKNSDZ.js";
196
- import {
197
- getSystemDefaultsPath,
198
- getSystemSettingsPath
199
- } from "./chunk-IDS7MSUP.js";
215
+ } from "./chunk-NGTQA57Y.js";
200
216
  import "./chunk-QRUIS3DN.js";
217
+ import "./chunk-W4DFAUAO.js";
218
+ import "./chunk-VJNBRRZG.js";
201
219
  import {
202
220
  isFolderTrustEnabled,
203
221
  isWorkspaceTrusted,
204
222
  loadTrustedFolders
205
- } from "./chunk-2OG646IN.js";
223
+ } from "./chunk-XFJU7SVP.js";
206
224
  import "./chunk-ALGGS7UH.js";
207
225
  import "./chunk-MR3PXB6E.js";
208
226
  import {
209
- writeStderrLine
210
- } from "./chunk-YUZI3WAC.js";
227
+ writeStderrLine,
228
+ writeStderrLineSafe
229
+ } from "./chunk-OWAK7LMW.js";
211
230
  import "./chunk-XFSFZCM5.js";
212
- import "./chunk-GLNHPRZT.js";
213
- import "./chunk-VJNBRRZG.js";
214
231
  import {
215
232
  ALL_PROVIDERS,
216
233
  APPROVAL_MODE_INFO,
@@ -233,7 +250,9 @@ import {
233
250
  SESSION_ARTIFACT_PERSISTENCE_VERSION,
234
251
  SESSION_TITLE_MAX_LENGTH,
235
252
  SESSION_TRANSCRIPT_MAX_LIMIT,
253
+ SESSION_TRANSCRIPT_MAX_PAGE_BYTES,
236
254
  SessionService,
255
+ SessionTranscriptPageTooLargeError,
237
256
  SessionTranscriptReader,
238
257
  SessionTranscriptSnapshotUnavailableError,
239
258
  SessionTranscriptTooLargeError,
@@ -275,6 +294,7 @@ import {
275
294
  generatePromptSuggestion,
276
295
  generateSessionRecap,
277
296
  generateToolUseId,
297
+ getActiveGoal,
278
298
  getArenaSystemReminder,
279
299
  getAutoModePermissionDeniedReason,
280
300
  getDefaultBaseUrlForProtocol,
@@ -321,35 +341,42 @@ import {
321
341
  subagentGenerator,
322
342
  unregisterGoalHook,
323
343
  updateSetting
324
- } from "./chunk-NJOFRXTM.js";
344
+ } from "./chunk-6TDA4EVC.js";
345
+ import "./chunk-5M6IDOMF.js";
325
346
  import "./chunk-T4TZQJ7W.js";
326
347
  import {
327
348
  external_exports
328
349
  } from "./chunk-H6XPXXMH.js";
329
- import "./chunk-5M6IDOMF.js";
330
- import "./chunk-A2HWMJVO.js";
331
- import "./chunk-RCEEXIOO.js";
332
- import "./chunk-KKRTYONM.js";
333
- import "./chunk-TV6K2FB4.js";
350
+ import "./chunk-LKR7QQ63.js";
351
+ import "./chunk-4XCVGEEX.js";
352
+ import "./chunk-XSPFQQRE.js";
353
+ import "./chunk-4RPARQ5D.js";
334
354
  import "./chunk-E2UVIFI2.js";
335
355
  import "./chunk-XU457J4V.js";
336
356
  import "./chunk-VDDGDZMA.js";
337
- import "./chunk-SPJQO2CE.js";
357
+ import "./chunk-J2MKYJKZ.js";
338
358
  import "./chunk-SZRN75WQ.js";
339
359
  import "./chunk-HTO4JFDZ.js";
340
360
  import "./chunk-CPBF7KYF.js";
341
361
  import {
342
362
  APPROVAL_MODES,
343
- FORK_SUBAGENT_TYPE
344
- } from "./chunk-F333XEAU.js";
363
+ FORK_SUBAGENT_TYPE,
364
+ runWithRuntimeContentGenerator
365
+ } from "./chunk-UOA2IHRI.js";
345
366
  import "./chunk-6AM5D4ME.js";
346
- import "./chunk-2Z5UX4KI.js";
367
+ import "./chunk-OSSDHJDU.js";
347
368
  import {
348
369
  HookEventName,
349
370
  createHookOutput
350
- } from "./chunk-ARKANCNX.js";
351
- import "./chunk-L5U3ELZX.js";
352
- import "./chunk-TYLOFD2U.js";
371
+ } from "./chunk-SGPFJMRP.js";
372
+ import "./chunk-335ECZTA.js";
373
+ import "./chunk-JCJPZMJV.js";
374
+ import {
375
+ clearCachedCredentialFile,
376
+ qwenOAuth2Events
377
+ } from "./chunk-QGONRHUS.js";
378
+ import "./chunk-7KS72E2N.js";
379
+ import "./chunk-JESCGQM3.js";
353
380
  import {
354
381
  AuthType,
355
382
  ConversationFinishedEvent,
@@ -360,9 +387,12 @@ import {
360
387
  endToolExecutionSpan,
361
388
  endToolSpan,
362
389
  extractDaemonTraceContext,
390
+ formatFullTurnVisionNotice,
363
391
  formatVisionBridgeNotice,
364
392
  getAllGeminiMdFilenames,
393
+ getFullTurnVisionModelSelector,
365
394
  getMCPServerStatus,
395
+ getSpecificMimeType,
366
396
  hasImageParts,
367
397
  logConversationFinishedEvent,
368
398
  logLoopDetected,
@@ -372,6 +402,7 @@ import {
372
402
  normalizeParts,
373
403
  registerAcpEventLoopLagGauge,
374
404
  runInToolSpanContext,
405
+ runSideQuery,
375
406
  runVisionBridge,
376
407
  shouldRunVisionBridge,
377
408
  splitImageParts,
@@ -379,57 +410,49 @@ import {
379
410
  startToolExecutionSpan,
380
411
  startToolSpan,
381
412
  uiTelemetryService,
413
+ withDaemonSpan,
382
414
  withInteractionSpan
383
- } from "./chunk-MQOVXZAF.js";
415
+ } from "./chunk-YN2I5I42.js";
384
416
  import "./chunk-4WKAA4KS.js";
385
417
  import "./chunk-CR3C7WXL.js";
386
- import {
387
- runSideQuery
388
- } from "./chunk-RTV5TUYB.js";
389
418
  import "./chunk-VGC4I5JJ.js";
390
419
  import {
391
420
  getAutoMemoryRoot,
392
421
  getUserAutoMemoryRoot
393
- } from "./chunk-FBU7WRZI.js";
394
- import "./chunk-Y66VOBRP.js";
395
- import "./chunk-WTNZ6DXB.js";
422
+ } from "./chunk-LPHGXXZX.js";
423
+ import "./chunk-4CA4WUMS.js";
424
+ import "./chunk-VAJS2E2D.js";
425
+ import "./chunk-WEPXQKGP.js";
396
426
  import {
397
427
  ToolConfirmationOutcome,
398
428
  ToolNames,
399
429
  isShellProgressData
400
- } from "./chunk-JGUT3LWZ.js";
401
- import {
402
- clearCachedCredentialFile,
403
- qwenOAuth2Events
404
- } from "./chunk-OKAIGAYW.js";
405
- import "./chunk-ZPAHLFZU.js";
406
- import "./chunk-JESCGQM3.js";
407
- import "./chunk-WIEO4CWB.js";
430
+ } from "./chunk-ANRANWVE.js";
408
431
  import "./chunk-XVNQMZ2I.js";
409
432
  import "./chunk-FEOZBUMT.js";
433
+ import "./chunk-DJ2GSRLV.js";
434
+ import "./chunk-4CHDYDG3.js";
410
435
  import {
411
436
  DEFAULT_TOKEN_LIMIT,
412
437
  getToolCallPreparations,
413
438
  tokenLimit
414
- } from "./chunk-VRR65QYW.js";
439
+ } from "./chunk-V4PCIS25.js";
415
440
  import "./chunk-A4UCTEB5.js";
416
441
  import "./chunk-XWRJCPHC.js";
417
- import "./chunk-4LD5APFL.js";
442
+ import "./chunk-TYQMGACW.js";
418
443
  import "./chunk-TYAMGABM.js";
419
444
  import "./chunk-FCMNKVXT.js";
420
- import "./chunk-DJ2GSRLV.js";
421
- import "./chunk-LQIDP6K5.js";
422
445
  import {
423
446
  Storage,
424
447
  createDebugLogger,
425
448
  getErrorMessage,
426
449
  getErrorStatus,
427
450
  isSubpath
428
- } from "./chunk-7NFB7LBQ.js";
429
- import "./chunk-QYG4BY6Q.js";
451
+ } from "./chunk-UE5TZ55T.js";
452
+ import "./chunk-ZPVGEMVC.js";
430
453
  import {
431
454
  runExitCleanup
432
- } from "./chunk-CAHY5ABC.js";
455
+ } from "./chunk-DULSE2SH.js";
433
456
  import "./chunk-75DOP5OR.js";
434
457
  import "./chunk-DMTGGOSA.js";
435
458
  import "./chunk-YQ3U5MUC.js";
@@ -444,6 +467,7 @@ import {
444
467
  // packages/cli/src/acp-integration/acpAgent.ts
445
468
  init_esbuild_shims();
446
469
  import { randomUUID } from "node:crypto";
470
+ import { performance } from "node:perf_hooks";
447
471
 
448
472
  // packages/cli/src/acp-integration/authMethods.ts
449
473
  init_esbuild_shims();
@@ -820,9 +844,35 @@ __name(isInactiveExtensionSkill, "isInactiveExtensionSkill");
820
844
  // packages/cli/src/acp-integration/session/Session.ts
821
845
  init_esbuild_shims();
822
846
  import { Buffer as Buffer2 } from "node:buffer";
847
+ import { existsSync as existsSync2, statSync as statSync2 } from "node:fs";
823
848
  import * as os2 from "node:os";
824
849
  import * as path3 from "node:path";
825
850
 
851
+ // packages/cli/src/utils/midTurnUserMessage.ts
852
+ init_esbuild_shims();
853
+ var MID_TURN_USER_MESSAGE_PREFIX = "\n[User message received during tool execution]: ";
854
+ function prefixMidTurnUserMessageParts(parts, displayText) {
855
+ const partArray = normalizePartList(parts);
856
+ if (partArray.length === 0) {
857
+ return [{ text: `${MID_TURN_USER_MESSAGE_PREFIX}${displayText}` }];
858
+ }
859
+ const [firstPart, ...rest] = partArray;
860
+ if ("text" in firstPart && typeof firstPart.text === "string") {
861
+ return [
862
+ {
863
+ ...firstPart,
864
+ text: `${MID_TURN_USER_MESSAGE_PREFIX}${firstPart.text}`
865
+ },
866
+ ...rest
867
+ ];
868
+ }
869
+ return [
870
+ { text: `${MID_TURN_USER_MESSAGE_PREFIX}${displayText}` },
871
+ ...partArray
872
+ ];
873
+ }
874
+ __name(prefixMidTurnUserMessageParts, "prefixMidTurnUserMessageParts");
875
+
826
876
  // packages/cli/src/acp-integration/session/tool-call-preparation-tracker.ts
827
877
  init_esbuild_shims();
828
878
  var ToolCallPreparationTracker = class {
@@ -1601,11 +1651,196 @@ function loadRewriteConfig(settings) {
1601
1651
  }
1602
1652
  __name(loadRewriteConfig, "loadRewriteConfig");
1603
1653
 
1654
+ // packages/cli/src/acp-integration/session/daemon-todo-stop-guard.ts
1655
+ init_esbuild_shims();
1656
+ var TODO_STOP_GUARD_MAX_ATTEMPTS = 2;
1657
+ function parseStructuredTodos(value) {
1658
+ if (typeof value !== "object" || value === null) return null;
1659
+ const record = value;
1660
+ if (record["type"] !== "todo_list" || !Array.isArray(record["todos"])) {
1661
+ return null;
1662
+ }
1663
+ for (const item of record["todos"]) {
1664
+ if (typeof item !== "object" || item === null) return null;
1665
+ const todo = item;
1666
+ if (typeof todo["id"] !== "string" || typeof todo["content"] !== "string" || todo["status"] !== "pending" && todo["status"] !== "in_progress" && todo["status"] !== "completed") {
1667
+ return null;
1668
+ }
1669
+ }
1670
+ return record["todos"];
1671
+ }
1672
+ __name(parseStructuredTodos, "parseStructuredTodos");
1673
+ var DaemonTodoStopGuard = class {
1674
+ constructor(enabled) {
1675
+ this.enabled = enabled;
1676
+ }
1677
+ static {
1678
+ __name(this, "DaemonTodoStopGuard");
1679
+ }
1680
+ #armed = false;
1681
+ #unfinishedCount = 0;
1682
+ #attempts = 0;
1683
+ #suspended = false;
1684
+ #retryPaused = false;
1685
+ #awaitingQueuedPrompt = false;
1686
+ #exhaustionReported = false;
1687
+ get hasTrustedUnfinishedState() {
1688
+ return this.enabled && this.#armed && !this.#suspended && this.#unfinishedCount > 0;
1689
+ }
1690
+ get isHardSuspended() {
1691
+ return this.enabled && this.#suspended;
1692
+ }
1693
+ get hasCommittedContinuation() {
1694
+ return this.enabled && this.#attempts > 0;
1695
+ }
1696
+ get blocksUnrelatedAutomaticTurns() {
1697
+ return this.enabled && this.#armed && !this.#suspended && this.#unfinishedCount > 0;
1698
+ }
1699
+ get needsStopInspection() {
1700
+ return this.enabled && this.#armed && !this.#suspended && !this.#retryPaused && !this.#awaitingQueuedPrompt && this.#unfinishedCount > 0;
1701
+ }
1702
+ clearTrust() {
1703
+ this.#armed = false;
1704
+ this.#unfinishedCount = 0;
1705
+ this.#attempts = 0;
1706
+ this.#suspended = false;
1707
+ this.#retryPaused = false;
1708
+ this.#awaitingQueuedPrompt = false;
1709
+ this.#exhaustionReported = false;
1710
+ }
1711
+ startOrdinaryPrompt() {
1712
+ this.clearTrust();
1713
+ }
1714
+ resumeTrustedPrompt() {
1715
+ this.#awaitingQueuedPrompt = false;
1716
+ this.#retryPaused = false;
1717
+ }
1718
+ blockUntilOrdinaryPromptStarts() {
1719
+ this.clearTrust();
1720
+ this.#suspended = true;
1721
+ }
1722
+ acceptMidTurnUserInput() {
1723
+ if (!this.enabled || this.#suspended) return;
1724
+ if (!this.#armed && this.#attempts === 0) return;
1725
+ this.#attempts = 0;
1726
+ this.#retryPaused = false;
1727
+ this.#awaitingQueuedPrompt = false;
1728
+ this.#exhaustionReported = false;
1729
+ }
1730
+ observeTodoWrite(resultDisplay, allowArm) {
1731
+ if (!this.enabled) return false;
1732
+ const todos = parseStructuredTodos(resultDisplay);
1733
+ if (todos === null) return false;
1734
+ if (!allowArm) {
1735
+ this.blockUntilOrdinaryPromptStarts();
1736
+ return true;
1737
+ }
1738
+ this.#unfinishedCount = todos.filter(
1739
+ (todo) => todo.status === "pending" || todo.status === "in_progress"
1740
+ ).length;
1741
+ this.#exhaustionReported = false;
1742
+ if (this.#unfinishedCount === 0) {
1743
+ this.#armed = false;
1744
+ return true;
1745
+ }
1746
+ if (!this.#suspended) this.#armed = true;
1747
+ return true;
1748
+ }
1749
+ suspend() {
1750
+ if (!this.enabled) return;
1751
+ this.#suspended = true;
1752
+ this.#awaitingQueuedPrompt = false;
1753
+ }
1754
+ pauseForTrustedRetry() {
1755
+ if (!this.#armed) return;
1756
+ this.#retryPaused = true;
1757
+ this.#awaitingQueuedPrompt = false;
1758
+ }
1759
+ awaitQueuedPrompt() {
1760
+ if (!this.#armed && this.#attempts === 0 || this.#suspended) return false;
1761
+ this.#awaitingQueuedPrompt = true;
1762
+ return true;
1763
+ }
1764
+ decide(hasRelevantBackgroundInput) {
1765
+ if (!this.needsStopInspection) return { kind: "inactive" };
1766
+ if (hasRelevantBackgroundInput) return { kind: "deferred" };
1767
+ if (this.#attempts >= TODO_STOP_GUARD_MAX_ATTEMPTS) {
1768
+ return {
1769
+ kind: "exhausted",
1770
+ attempt: this.#attempts,
1771
+ maxAttempts: TODO_STOP_GUARD_MAX_ATTEMPTS,
1772
+ unfinishedCount: this.#unfinishedCount
1773
+ };
1774
+ }
1775
+ return {
1776
+ kind: "continue",
1777
+ attempt: this.#attempts + 1,
1778
+ maxAttempts: TODO_STOP_GUARD_MAX_ATTEMPTS,
1779
+ unfinishedCount: this.#unfinishedCount
1780
+ };
1781
+ }
1782
+ decideToolClosure(currentAttempt, hasRelevantBackgroundInput) {
1783
+ if (this.#unfinishedCount > 0) {
1784
+ return this.decide(hasRelevantBackgroundInput);
1785
+ }
1786
+ if (!this.enabled || this.#suspended || this.#retryPaused || this.#awaitingQueuedPrompt || this.#attempts !== currentAttempt) {
1787
+ return { kind: "inactive" };
1788
+ }
1789
+ if (hasRelevantBackgroundInput) return { kind: "deferred" };
1790
+ if (currentAttempt >= TODO_STOP_GUARD_MAX_ATTEMPTS) {
1791
+ return { kind: "inactive" };
1792
+ }
1793
+ return {
1794
+ kind: "continue",
1795
+ attempt: currentAttempt + 1,
1796
+ maxAttempts: TODO_STOP_GUARD_MAX_ATTEMPTS,
1797
+ unfinishedCount: 0,
1798
+ toolClosure: true
1799
+ };
1800
+ }
1801
+ commitContinuation(attempt) {
1802
+ const canCloseTools = this.enabled && this.#unfinishedCount === 0 && this.#attempts > 0 && !this.#suspended && !this.#retryPaused && !this.#awaitingQueuedPrompt;
1803
+ if (!this.needsStopInspection && !canCloseTools || attempt !== this.#attempts + 1 || attempt > TODO_STOP_GUARD_MAX_ATTEMPTS) {
1804
+ return false;
1805
+ }
1806
+ this.#attempts = attempt;
1807
+ return true;
1808
+ }
1809
+ markExhaustionReported() {
1810
+ if (this.#exhaustionReported) return false;
1811
+ this.#exhaustionReported = true;
1812
+ this.#suspended = true;
1813
+ return true;
1814
+ }
1815
+ };
1816
+
1604
1817
  // packages/cli/src/acp-integration/session/Session.ts
1605
1818
  var debugLogger6 = createDebugLogger("SESSION");
1606
1819
  var USER_CANCEL_ABORT_REASON = "qwen:user-cancel";
1607
1820
  var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
1608
1821
  var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
1822
+ var TODO_STOP_GUARD_PROMPT_PREFIX = "[Todo Stop Guard] ";
1823
+ var TODO_STOP_GUARD_PROMPT_BODY_SUFFIX = " todo item(s) are still pending or in progress. Continue executing the current task now. Do not ask the user whether to continue. If progress requires user input, use the structured question or permission flow. If progress depends on external state, report the blocker explicitly.";
1824
+ var TODO_STOP_GUARD_FINAL_PROMPT_SUFFIX = " This is the final automatic continuation. Before ending, either complete/update the todos or report the completed progress and the exact blocker.";
1825
+ function isTodoStopGuardPromptText(text) {
1826
+ if (typeof text !== "string") return false;
1827
+ if (!text.startsWith(TODO_STOP_GUARD_PROMPT_PREFIX)) return false;
1828
+ const remainder = text.slice(TODO_STOP_GUARD_PROMPT_PREFIX.length);
1829
+ const separator = remainder.indexOf(" ");
1830
+ if (separator <= 0) return false;
1831
+ const countText = remainder.slice(0, separator);
1832
+ const count = Number(countText);
1833
+ if (!Number.isSafeInteger(count) || count <= 0 || String(count) !== countText) {
1834
+ return false;
1835
+ }
1836
+ const body = `${countText}${TODO_STOP_GUARD_PROMPT_BODY_SUFFIX}`;
1837
+ return remainder === body || remainder === body + TODO_STOP_GUARD_FINAL_PROMPT_SUFFIX;
1838
+ }
1839
+ __name(isTodoStopGuardPromptText, "isTodoStopGuardPromptText");
1840
+ function isCompressionFailureStatus(status) {
1841
+ return status === 2 /* COMPRESSION_FAILED_INFLATED_TOKEN_COUNT */ || status === 3 /* COMPRESSION_FAILED_TOKEN_COUNT_ERROR */ || status === 4 /* COMPRESSION_FAILED_EMPTY_SUMMARY */ || status === 6 /* COMPRESSION_FAILED_OUTPUT_TRUNCATED */;
1842
+ }
1843
+ __name(isCompressionFailureStatus, "isCompressionFailureStatus");
1609
1844
  async function finalizeToolCallPreparations(tracker, includeResolved, streamName) {
1610
1845
  try {
1611
1846
  await tracker.discard(includeResolved);
@@ -1860,6 +2095,15 @@ var MidTurnDrainTimeoutError = class extends Error {
1860
2095
  }
1861
2096
  };
1862
2097
  var MAX_NOTIFICATION_QUEUE = 20;
2098
+ var MAX_DEFERRED_UNRELATED_CRON_QUEUE = 20;
2099
+ function isExistingFile(resolved, fileExists = existsSync2, statFile = statSync2) {
2100
+ try {
2101
+ return fileExists(resolved) && statFile(resolved).isFile();
2102
+ } catch {
2103
+ return false;
2104
+ }
2105
+ }
2106
+ __name(isExistingFile, "isExistingFile");
1863
2107
  function resolveHomeLoopResolverRoots({
1864
2108
  homeQwenDir = Storage.getGlobalQwenDir(),
1865
2109
  homeDir = os2.homedir(),
@@ -2067,11 +2311,21 @@ var Session = class {
2067
2311
  this.settings = settings;
2068
2312
  this.sessionId = id;
2069
2313
  this.runtimeBaseDir = Storage.getRuntimeBaseDir();
2314
+ const todoStopGuardEnabled = this.settings.merged.experimental?.todoStopGuard === true && !this.config.getBareMode() && !this.config.isSafeMode();
2315
+ this.todoStopGuard = new DaemonTodoStopGuard(todoStopGuardEnabled);
2316
+ this.todoStopGuardBackgroundBaseline = todoStopGuardEnabled ? this.#captureTodoStopGuardBackgroundBaseline() : {
2317
+ agents: /* @__PURE__ */ new Set(),
2318
+ shells: /* @__PURE__ */ new Set(),
2319
+ monitors: /* @__PURE__ */ new Set(),
2320
+ wakeups: /* @__PURE__ */ new Set()
2321
+ };
2070
2322
  this.toolCallEmitter = new ToolCallEmitter(this);
2071
2323
  this.planEmitter = new PlanEmitter(this);
2072
- this.historyReplayer = new HistoryReplayer(this);
2324
+ this.historyReplayer = new HistoryReplayer(this, {
2325
+ supersedeUnrestorableGoal: true
2326
+ });
2073
2327
  this.messageEmitter = new MessageEmitter(this);
2074
- this.#installGoalTerminalObserver();
2328
+ this.installGoalTerminalObserver();
2075
2329
  this.#registerBackgroundNotificationCallbacks();
2076
2330
  this.#registerSubSessionSpawner();
2077
2331
  }
@@ -2133,6 +2387,10 @@ var Session = class {
2133
2387
  // batch so a transient stall can't silently lose them. See
2134
2388
  // `#drainMidTurnUserMessages`.
2135
2389
  midTurnRecoveredMessages = [];
2390
+ todoStopGuard;
2391
+ todoStopGuardBackgroundBaseline;
2392
+ todoStopGuardQueuedPromptPriority = false;
2393
+ todoStopGuardDrainAutomaticQueuesWhenIdle = false;
2136
2394
  // Background notification drain state. ACP does not have the TUI's idle
2137
2395
  // hook, so the session serializes registry callbacks through this queue.
2138
2396
  notificationQueue = [];
@@ -2169,6 +2427,169 @@ var Session = class {
2169
2427
  pendingWorktreeNotice = null;
2170
2428
  // Implement SessionContext interface
2171
2429
  sessionId;
2430
+ #prepareTodoStopGuardForPrompt(params) {
2431
+ if (!this.todoStopGuard.enabled) {
2432
+ return {
2433
+ startsWorkChain: false,
2434
+ drainSupersededAutomaticQueues: false
2435
+ };
2436
+ }
2437
+ const drainSupersededAutomaticQueues = this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuard.hasCommittedContinuation || this.todoStopGuardQueuedPromptPriority;
2438
+ if (this.config.getApprovalMode() === "plan" /* PLAN */) {
2439
+ this.todoStopGuardQueuedPromptPriority = false;
2440
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2441
+ return {
2442
+ startsWorkChain: false,
2443
+ drainSupersededAutomaticQueues
2444
+ };
2445
+ }
2446
+ const metadata = params._meta;
2447
+ const isRetry = params.retry === true || metadata?.[DAEMON_RETRY_META_KEY] === true;
2448
+ const isContinue = metadata?.[DAEMON_CONTINUE_META_KEY] === true;
2449
+ if (isRetry || isContinue) {
2450
+ this.todoStopGuardQueuedPromptPriority = false;
2451
+ if (this.todoStopGuard.hasTrustedUnfinishedState) {
2452
+ this.todoStopGuard.resumeTrustedPrompt();
2453
+ return {
2454
+ startsWorkChain: false,
2455
+ drainSupersededAutomaticQueues: false
2456
+ };
2457
+ }
2458
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2459
+ return {
2460
+ startsWorkChain: true,
2461
+ drainSupersededAutomaticQueues
2462
+ };
2463
+ }
2464
+ this.todoStopGuardQueuedPromptPriority = false;
2465
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2466
+ return {
2467
+ startsWorkChain: true,
2468
+ drainSupersededAutomaticQueues
2469
+ };
2470
+ }
2471
+ #prepareTodoStopGuardForAutomaticTurn(continuesCurrentWorkChain) {
2472
+ if (!this.todoStopGuard.enabled) return;
2473
+ if (this.config.getApprovalMode() === "plan" /* PLAN */) {
2474
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2475
+ return;
2476
+ }
2477
+ if (continuesCurrentWorkChain && this.todoStopGuard.isHardSuspended) {
2478
+ return;
2479
+ }
2480
+ if (continuesCurrentWorkChain && this.todoStopGuard.hasTrustedUnfinishedState) {
2481
+ this.todoStopGuard.resumeTrustedPrompt();
2482
+ return;
2483
+ }
2484
+ this.todoStopGuard.clearTrust();
2485
+ this.todoStopGuardBackgroundBaseline = this.#captureTodoStopGuardBackgroundBaseline();
2486
+ }
2487
+ #clearTodoStopGuardTrustAndDrainAutomaticQueues() {
2488
+ const preserveQueuedPromptPriority = this.todoStopGuardQueuedPromptPriority;
2489
+ const shouldDrain = (this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuard.hasCommittedContinuation) && !preserveQueuedPromptPriority;
2490
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2491
+ if (preserveQueuedPromptPriority || !shouldDrain) return;
2492
+ if (this.pendingPrompt) {
2493
+ this.todoStopGuardDrainAutomaticQueuesWhenIdle = true;
2494
+ return;
2495
+ }
2496
+ void this.#drainCronQueue();
2497
+ void this.#drainNotificationQueue();
2498
+ }
2499
+ releaseTodoStopGuardQueuedPromptWait() {
2500
+ if (!this.todoStopGuardQueuedPromptPriority) return false;
2501
+ this.todoStopGuardQueuedPromptPriority = false;
2502
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2503
+ if (this.pendingPrompt) {
2504
+ this.todoStopGuardDrainAutomaticQueuesWhenIdle = true;
2505
+ return true;
2506
+ }
2507
+ void this.#drainCronQueue();
2508
+ void this.#drainNotificationQueue();
2509
+ return true;
2510
+ }
2511
+ clearTodoStopGuardTrust() {
2512
+ this.#clearTodoStopGuardTrustAndDrainAutomaticQueues();
2513
+ }
2514
+ #beginTodoStopGuardQueuedPromptCheck() {
2515
+ this.todoStopGuardQueuedPromptPriority = this.todoStopGuard.awaitQueuedPrompt();
2516
+ }
2517
+ #finishTodoStopGuardQueuedPromptCheck(hasQueuedPrompt) {
2518
+ const shouldWait = hasQueuedPrompt && this.todoStopGuardQueuedPromptPriority;
2519
+ this.todoStopGuardQueuedPromptPriority = shouldWait;
2520
+ if (!shouldWait) this.todoStopGuard.resumeTrustedPrompt();
2521
+ return shouldWait;
2522
+ }
2523
+ #notificationContinuesTodoStopGuardWorkChain(item) {
2524
+ const baseline = this.todoStopGuardBackgroundBaseline;
2525
+ if (item.kind === "agent") return !baseline.agents.has(item.taskId);
2526
+ if (item.kind === "shell") return !baseline.shells.has(item.taskId);
2527
+ return !baseline.monitors.has(item.taskId);
2528
+ }
2529
+ #cronContinuesTodoStopGuardWorkChain(item) {
2530
+ return item.source === "loop" && item.taskId !== void 0 && !this.todoStopGuardBackgroundBaseline.wakeups.has(item.taskId);
2531
+ }
2532
+ #captureTodoStopGuardBackgroundBaseline() {
2533
+ const agents = this.config.getBackgroundTaskRegistry?.()?.getAll?.() ?? [];
2534
+ const shells = this.config.getBackgroundShellRegistry?.()?.getAll?.() ?? [];
2535
+ const monitors = this.config.getMonitorRegistry?.()?.getAll?.() ?? [];
2536
+ const wakeups = this.config.isCronEnabled?.() ? (this.config.getCronScheduler?.()?.list?.() ?? []).filter(
2537
+ (job) => job.cronExpr === "@wakeup"
2538
+ ) : [];
2539
+ return {
2540
+ agents: /* @__PURE__ */ new Set([
2541
+ ...agents.map((task) => task.id),
2542
+ ...this.notificationQueue.filter((item) => item.kind === "agent").map((item) => item.taskId)
2543
+ ]),
2544
+ shells: /* @__PURE__ */ new Set([
2545
+ ...shells.map((task) => task.id),
2546
+ ...this.notificationQueue.filter((item) => item.kind === "shell").map((item) => item.taskId)
2547
+ ]),
2548
+ monitors: /* @__PURE__ */ new Set([
2549
+ ...monitors.map((task) => task.id),
2550
+ ...this.notificationQueue.filter((item) => item.kind === "monitor").map((item) => item.taskId)
2551
+ ]),
2552
+ wakeups: /* @__PURE__ */ new Set([
2553
+ ...wakeups.map((job) => job.id),
2554
+ ...this.cronQueue.flatMap(
2555
+ (item) => item.source === "loop" && item.taskId ? [item.taskId] : []
2556
+ )
2557
+ ])
2558
+ };
2559
+ }
2560
+ #hasRelevantTodoStopGuardBackgroundInput() {
2561
+ if (this.notificationQueue.some(
2562
+ (item) => this.#notificationContinuesTodoStopGuardWorkChain(item)
2563
+ ) || this.cronQueue.some(
2564
+ (item) => this.#cronContinuesTodoStopGuardWorkChain(item)
2565
+ )) {
2566
+ return true;
2567
+ }
2568
+ const baseline = this.todoStopGuardBackgroundBaseline;
2569
+ const agents = this.config.getBackgroundTaskRegistry?.()?.getAll?.() ?? [];
2570
+ if (agents.some(
2571
+ (task) => !baseline.agents.has(task.id) && task.isBackgrounded && (task.status === "running" || task.status === "paused" || task.status === "cancelled" && !task.notified)
2572
+ )) {
2573
+ return true;
2574
+ }
2575
+ const shells = this.config.getBackgroundShellRegistry?.()?.getAll?.() ?? [];
2576
+ if (shells.some(
2577
+ (task) => !baseline.shells.has(task.id) && task.status === "running"
2578
+ )) {
2579
+ return true;
2580
+ }
2581
+ const monitors = this.config.getMonitorRegistry?.()?.getAll?.() ?? [];
2582
+ if (monitors.some(
2583
+ (task) => !baseline.monitors.has(task.id) && task.status === "running"
2584
+ )) {
2585
+ return true;
2586
+ }
2587
+ if (!this.config.isCronEnabled?.()) return false;
2588
+ const wakeups = this.config.getCronScheduler?.()?.list?.() ?? [];
2589
+ return wakeups.some(
2590
+ (job) => job.cronExpr === "@wakeup" && !baseline.wakeups.has(job.id)
2591
+ );
2592
+ }
2172
2593
  /**
2173
2594
  * Wire the sub-session spawner to the daemon over the ACP `extMethod` request
2174
2595
  * channel. The `create_sub_session` tool (model-initiated) is its caller. ONLY
@@ -2236,6 +2657,9 @@ var Session = class {
2236
2657
  }
2237
2658
  dispose() {
2238
2659
  this.disposed = true;
2660
+ this.todoStopGuardQueuedPromptPriority = false;
2661
+ this.todoStopGuardDrainAutomaticQueuesWhenIdle = false;
2662
+ this.todoStopGuard.clearTrust();
2239
2663
  this.notificationQueue = [];
2240
2664
  this.cronQueue = [];
2241
2665
  this.notificationAbortController?.abort();
@@ -2275,21 +2699,42 @@ var Session = class {
2275
2699
  );
2276
2700
  }
2277
2701
  }
2278
- #installGoalTerminalObserver() {
2702
+ /**
2703
+ * Installs (or replaces) this session's goal-terminal observer.
2704
+ *
2705
+ * Public because it does not stay installed: `registerGoalHook` and
2706
+ * `unregisterGoalHook` both clear the observer table for the session, so any
2707
+ * caller that (re-)registers a goal outside `#processSlashCommandResult` —
2708
+ * notably goal restore on resume — has to put it back. Idempotent.
2709
+ */
2710
+ installGoalTerminalObserver() {
2279
2711
  setGoalTerminalObserver(this.sessionId, (event) => {
2280
2712
  void this.messageEmitter.emitGoalTerminal(event).catch((error) => {
2281
2713
  debugLogger6.warn(
2282
2714
  `Failed to emit goal terminal update: ${this.#formatError(error)}`
2283
2715
  );
2284
2716
  });
2717
+ recordGoalStatusItem(this.config, goalTerminalEventToHistoryItem(event));
2285
2718
  });
2286
2719
  }
2720
+ /**
2721
+ * Emits a goal card and persists it to the transcript. Both `set` and
2722
+ * `cleared` reach the client this way — from `#emitGoalStatusItems` for a
2723
+ * `/goal` prompt, and from the `sessionGoalClear` ext method — so recording
2724
+ * here (rather than at each call site) keeps the transcript in step with the
2725
+ * hook. Replay goes through `messageEmitter.emitGoalStatus` directly and so
2726
+ * does not re-record.
2727
+ */
2287
2728
  emitGoalStatus(status) {
2288
2729
  void this.messageEmitter.emitGoalStatus(status).catch((error) => {
2289
2730
  debugLogger6.warn(
2290
2731
  `Failed to emit goal status update: ${this.#formatError(error)}`
2291
2732
  );
2292
2733
  });
2734
+ recordGoalStatusItem(this.config, {
2735
+ type: "goal_status" /* GOAL_STATUS */,
2736
+ ...status
2737
+ });
2293
2738
  }
2294
2739
  /**
2295
2740
  * Replays conversation history to the client using modular components.
@@ -2332,6 +2777,9 @@ var Session = class {
2332
2777
  }
2333
2778
  chat.truncateHistory(apiTruncateIndex);
2334
2779
  chat.stripThoughtsFromHistory();
2780
+ const preserveQueuedPromptPriority = this.todoStopGuardQueuedPromptPriority;
2781
+ const shouldDrainAutomaticQueues = (this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuard.hasCommittedContinuation) && !preserveQueuedPromptPriority;
2782
+ this.todoStopGuard.blockUntilOrdinaryPromptStarts();
2335
2783
  const rewindFiles = opts?.rewindFiles !== false;
2336
2784
  const fileHistoryService = this.config.getFileHistoryService();
2337
2785
  const survivingSnapshots = rewindFiles ? fileHistoryService.getSnapshots().slice(0, targetTurnIndex + 1) : void 0;
@@ -2343,6 +2791,10 @@ var Session = class {
2343
2791
  { truncatedCount: Math.max(0, apiHistory.length - apiTruncateIndex) },
2344
2792
  survivingSnapshots
2345
2793
  );
2794
+ if (shouldDrainAutomaticQueues) {
2795
+ void this.#drainCronQueue();
2796
+ void this.#drainNotificationQueue();
2797
+ }
2346
2798
  return { targetTurnIndex, apiTruncateIndex };
2347
2799
  }
2348
2800
  captureHistorySnapshot() {
@@ -2369,6 +2821,7 @@ var Session = class {
2369
2821
  );
2370
2822
  }
2371
2823
  this.config.getGeminiClient().getChat().setHistory(structuredClone(history));
2824
+ this.#clearTodoStopGuardTrustAndDrainAutomaticQueues();
2372
2825
  }
2373
2826
  #computeApiTruncationIndexForUserTurn(apiHistory, targetTurnIndex) {
2374
2827
  const startIndex = getStartupContextLength(apiHistory, {
@@ -2397,6 +2850,11 @@ var Session = class {
2397
2850
  );
2398
2851
  if (hasFunctionResponse) return false;
2399
2852
  if (isSystemReminderContent(content)) return false;
2853
+ if (content.parts.some(
2854
+ (part) => "text" in part && isTodoStopGuardPromptText(part.text)
2855
+ )) {
2856
+ return false;
2857
+ }
2400
2858
  return content.parts.some((part) => "text" in part && part.text);
2401
2859
  }
2402
2860
  async cancelPendingPrompt() {
@@ -2410,6 +2868,7 @@ var Session = class {
2410
2868
  if (!hadPrompt && !hadCron && !hadNotification) {
2411
2869
  throw new Error(NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE);
2412
2870
  }
2871
+ this.todoStopGuard.suspend();
2413
2872
  if (this.pendingPrompt) {
2414
2873
  this.pendingPrompt.abort(USER_CANCEL_ABORT_REASON);
2415
2874
  this.pendingPrompt = null;
@@ -2436,6 +2895,7 @@ var Session = class {
2436
2895
  }
2437
2896
  }
2438
2897
  async prompt(params) {
2898
+ const todoStopGuardPreparation = this.#prepareTodoStopGuardForPrompt(params);
2439
2899
  this.pendingPrompt?.abort();
2440
2900
  const pendingSend = new AbortController();
2441
2901
  this.pendingPrompt = pendingSend;
@@ -2477,10 +2937,15 @@ var Session = class {
2477
2937
  if (pendingSend.signal.aborted) {
2478
2938
  return { stopReason: "cancelled" };
2479
2939
  }
2940
+ if (todoStopGuardPreparation.startsWorkChain) {
2941
+ this.todoStopGuardQueuedPromptPriority = false;
2942
+ this.todoStopGuard.startOrdinaryPrompt();
2943
+ this.todoStopGuardBackgroundBaseline = this.#captureTodoStopGuardBackgroundBaseline();
2944
+ }
2480
2945
  this.duplicateProviderToolCallResponseIds.clear();
2481
2946
  let resolveCompletion;
2482
- this.pendingPromptCompletion = new Promise((resolve4) => {
2483
- resolveCompletion = resolve4;
2947
+ this.pendingPromptCompletion = new Promise((resolve5) => {
2948
+ resolveCompletion = resolve5;
2484
2949
  });
2485
2950
  try {
2486
2951
  const result = await this.#executePrompt(params, pendingSend);
@@ -2491,6 +2956,12 @@ var Session = class {
2491
2956
  return result;
2492
2957
  } finally {
2493
2958
  this.pendingPrompt = null;
2959
+ const shouldDrainAutomaticQueues = todoStopGuardPreparation.drainSupersededAutomaticQueues || this.todoStopGuardDrainAutomaticQueuesWhenIdle || this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuard.hasCommittedContinuation || this.todoStopGuardQueuedPromptPriority;
2960
+ this.todoStopGuardDrainAutomaticQueuesWhenIdle = false;
2961
+ if (shouldDrainAutomaticQueues) {
2962
+ void this.#drainCronQueue();
2963
+ void this.#drainNotificationQueue();
2964
+ }
2494
2965
  void this.#startCronSchedulerIfNeeded();
2495
2966
  resolveCompletion();
2496
2967
  this.pendingPromptCompletion = null;
@@ -2552,6 +3023,9 @@ var Session = class {
2552
3023
  */
2553
3024
  #maybeEmitFollowupSuggestion(result) {
2554
3025
  if (result.stopReason !== "end_turn") return;
3026
+ if (this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuardQueuedPromptPriority) {
3027
+ return;
3028
+ }
2555
3029
  if (this.settings.merged.ui?.enableFollowupSuggestions === false) return;
2556
3030
  if (this.config.getApprovalMode() === "plan" /* PLAN */) return;
2557
3031
  const chat = this.config.getGeminiClient()?.getChat();
@@ -2686,6 +3160,14 @@ var Session = class {
2686
3160
  );
2687
3161
  const inputText = firstTextBlock?.text || "";
2688
3162
  let parts;
3163
+ let fullTurnModelOverride;
3164
+ const onFullTurnModel = /* @__PURE__ */ __name((model) => {
3165
+ if (fullTurnModelOverride) {
3166
+ return false;
3167
+ }
3168
+ fullTurnModelOverride = model;
3169
+ return true;
3170
+ }, "onFullTurnModel");
2689
3171
  if (isContinue) {
2690
3172
  parts = continuationParts;
2691
3173
  } else if (isSlashCommand(inputText)) {
@@ -2697,7 +3179,9 @@ var Session = class {
2697
3179
  );
2698
3180
  parts = await this.#processSlashCommandResult(
2699
3181
  slashCommandResult,
2700
- params.prompt
3182
+ params.prompt,
3183
+ pendingSend.signal,
3184
+ onFullTurnModel
2701
3185
  );
2702
3186
  if (parts === null) {
2703
3187
  return { stopReason: "end_turn" };
@@ -2706,7 +3190,7 @@ var Session = class {
2706
3190
  parts = await this.#resolvePrompt(
2707
3191
  params.prompt,
2708
3192
  pendingSend.signal,
2709
- { promptLast: true }
3193
+ { promptLast: true, onFullTurnModel }
2710
3194
  );
2711
3195
  }
2712
3196
  const hooksEnabled = !this.config.getDisableAllHooks?.();
@@ -2777,6 +3261,7 @@ ${this.pendingWorktreeNotice}
2777
3261
  while (nextMessage !== null) {
2778
3262
  turnCount++;
2779
3263
  if (pendingSend.signal.aborted) {
3264
+ this.todoStopGuard.suspend();
2780
3265
  this.#getCurrentChat().addHistory(nextMessage);
2781
3266
  return { stopReason: "cancelled" };
2782
3267
  }
@@ -2793,9 +3278,11 @@ ${this.pendingWorktreeNotice}
2793
3278
  const sendResult = await this.#sendMessageStreamWithAutoCompression(
2794
3279
  promptId,
2795
3280
  nextMessage?.parts ?? [],
2796
- pendingSend.signal
3281
+ pendingSend.signal,
3282
+ { modelOverride: fullTurnModelOverride }
2797
3283
  );
2798
3284
  if (!sendResult.responseStream) {
3285
+ this.todoStopGuard.suspend();
2799
3286
  this.#preserveUnsentMessageHistory(
2800
3287
  nextMessage,
2801
3288
  isContinue || sendResult.stopReason === "cancelled"
@@ -2808,6 +3295,7 @@ ${this.pendingWorktreeNotice}
2808
3295
  try {
2809
3296
  for await (const resp of responseStream) {
2810
3297
  if (pendingSend.signal.aborted) {
3298
+ this.todoStopGuard.suspend();
2811
3299
  return { stopReason: "cancelled" };
2812
3300
  }
2813
3301
  if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
@@ -2863,8 +3351,10 @@ ${this.pendingWorktreeNotice}
2863
3351
  strippedOrphanEntries = null;
2864
3352
  }
2865
3353
  if (pendingSend.signal.aborted && pendingSend.signal.reason === USER_CANCEL_ABORT_REASON && this.#isAbortError(error)) {
3354
+ this.todoStopGuard.suspend();
2866
3355
  return { stopReason: "cancelled" };
2867
3356
  }
3357
+ this.todoStopGuard.pauseForTrustedRetry();
2868
3358
  const errorStatus = getErrorStatus(error);
2869
3359
  const errorMessage = error instanceof Error ? error.message : String(error);
2870
3360
  const errorType = classifyApiError({
@@ -2901,24 +3391,31 @@ ${this.pendingWorktreeNotice}
2901
3391
  );
2902
3392
  }
2903
3393
  if (functionCalls.length > 0) {
2904
- const toolRun = await this.runToolCalls(
2905
- pendingSend.signal,
2906
- promptId,
2907
- functionCalls,
2908
- toolLoopState
3394
+ const toolRun = await this.#runWithFullTurnModel(
3395
+ fullTurnModelOverride,
3396
+ () => this.runToolCalls(
3397
+ pendingSend.signal,
3398
+ promptId,
3399
+ functionCalls,
3400
+ toolLoopState
3401
+ )
2909
3402
  );
2910
3403
  if (toolRun.stopAfterPermissionCancel) {
3404
+ this.todoStopGuard.suspend();
2911
3405
  await this.#preserveStoppedToolRun(
2912
3406
  toolRun,
2913
3407
  pendingSend.signal
2914
3408
  );
2915
3409
  return { stopReason: "end_turn" };
2916
3410
  }
2917
- nextMessage = await this.#buildNextMessageAfterToolRun(
3411
+ const nextAfterTools = await this.#buildNextMessageAfterToolRun(
2918
3412
  toolRun,
2919
- pendingSend.signal
3413
+ pendingSend.signal,
3414
+ onFullTurnModel
2920
3415
  );
3416
+ nextMessage = nextAfterTools.message;
2921
3417
  if (toolRun.loopDetected) {
3418
+ this.todoStopGuard.suspend();
2922
3419
  await this.#preserveStoppedToolRun(
2923
3420
  toolRun,
2924
3421
  pendingSend.signal
@@ -2934,7 +3431,9 @@ ${this.pendingWorktreeNotice}
2934
3431
  pendingSend,
2935
3432
  promptId,
2936
3433
  hooksEnabled,
2937
- messageBus
3434
+ messageBus,
3435
+ true,
3436
+ fullTurnModelOverride
2938
3437
  );
2939
3438
  } finally {
2940
3439
  logConversationFinishedEvent(
@@ -2951,220 +3450,558 @@ ${this.pendingWorktreeNotice}
2951
3450
  }
2952
3451
  );
2953
3452
  }
2954
- /**
2955
- * Handles the Stop hook iteration loop.
2956
- * This method processes Stop hooks after a model response completes with no pending tool calls.
2957
- * If a Stop hook requests continuation, it sends a follow-up message and loops back.
2958
- * Maximum iterations (100) prevent infinite loops.
2959
- *
2960
- * @param pendingSend - The abort controller for the current prompt
2961
- * @param promptId - The prompt ID for tracking
2962
- * @param hooksEnabled - Whether hooks are enabled
2963
- * @param messageBus - The MessageBus for hook communication (may be undefined)
2964
- * @returns The ACP stop reason for the prompt.
2965
- */
2966
- async #handleStopHookLoop(pendingSend, promptId, hooksEnabled, messageBus) {
3453
+ async #handleStopHookLoop(pendingSend, promptId, hooksEnabled, messageBus, allowExternalHooks = true, modelOverride) {
2967
3454
  const stopHookBlockingCap = this.config.getStopHookBlockingCap();
2968
3455
  let stopHookIterationCount = 0;
2969
3456
  let stopHookReasons = [];
2970
- while (stopHookIterationCount < stopHookBlockingCap) {
2971
- if (!hooksEnabled || !messageBus || pendingSend.signal.aborted || !this.config.hasHooksForEvent?.("Stop")) {
3457
+ const onFullTurnModel = /* @__PURE__ */ __name((model) => {
3458
+ if (modelOverride) {
3459
+ return false;
3460
+ }
3461
+ modelOverride = model;
3462
+ return true;
3463
+ }, "onFullTurnModel");
3464
+ let midTurnContinuationCount = 0;
3465
+ while (true) {
3466
+ if (pendingSend.signal.aborted) {
3467
+ this.todoStopGuard.suspend();
2972
3468
  return { stopReason: "end_turn" };
2973
3469
  }
2974
- const responseText = this.#getCurrentChat().getLastModelMessageText?.() || "[no response text]";
2975
- const contextUsage = buildContextUsage(
2976
- this.config.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT,
2977
- this.lastPromptTokenCount
2978
- );
2979
- const response = await messageBus.request(
2980
- {
2981
- type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
2982
- eventName: "Stop",
2983
- input: {
2984
- stop_hook_active: true,
2985
- last_assistant_message: responseText,
2986
- ...contextUsage
2987
- },
2988
- signal: pendingSend.signal
2989
- },
2990
- "hook-execution-response" /* HOOK_EXECUTION_RESPONSE */
2991
- );
2992
- if (pendingSend.signal.aborted) {
2993
- return { stopReason: "cancelled" };
2994
- }
2995
- const hookOutput = response.output ? createHookOutput("Stop", response.output) : void 0;
2996
- const stopOutput = hookOutput;
2997
- if (stopOutput?.systemMessage) {
2998
- await this.messageEmitter.emitAgentMessage(stopOutput.systemMessage);
2999
- }
3000
- if (stopOutput?.isBlockingDecision() || stopOutput?.shouldStopExecution()) {
3001
- const continueReason = stopOutput.getEffectiveReason();
3002
- stopHookIterationCount++;
3003
- stopHookReasons = [...stopHookReasons, continueReason];
3004
- if (stopHookIterationCount >= stopHookBlockingCap) {
3005
- const warning = formatStopHookBlockingCapWarning(
3006
- "Stop",
3007
- stopHookBlockingCap
3008
- );
3009
- abortGoalForStopHookCap(
3010
- this.config,
3011
- this.config.getSessionId(),
3012
- warning
3470
+ if (this.config.getApprovalMode() === "plan" /* PLAN */) {
3471
+ this.#clearTodoStopGuardTrustAndDrainAutomaticQueues();
3472
+ }
3473
+ if (this.todoStopGuardQueuedPromptPriority) {
3474
+ return { stopReason: "end_turn" };
3475
+ }
3476
+ if (this.todoStopGuard.needsStopInspection) {
3477
+ this.#beginTodoStopGuardQueuedPromptCheck();
3478
+ const drained = await this.#drainMidTurnInput(pendingSend.signal, {
3479
+ watchQueuedPromptForTodoStopGuard: true,
3480
+ onFullTurnModel
3481
+ });
3482
+ const waitsForQueuedPrompt = this.#finishTodoStopGuardQueuedPromptCheck(
3483
+ drained.hasQueuedPrompt
3484
+ );
3485
+ if (drained.parts.length > 0) {
3486
+ this.todoStopGuard.acceptMidTurnUserInput();
3487
+ const continuation2 = await this.#runStopContinuation(
3488
+ pendingSend,
3489
+ promptId + "_mid_turn_" + ++midTurnContinuationCount,
3490
+ promptId,
3491
+ drained.parts,
3492
+ false,
3493
+ {
3494
+ onFullTurnModel,
3495
+ getModelOverride: /* @__PURE__ */ __name(() => modelOverride, "getModelOverride")
3496
+ }
3013
3497
  );
3014
- await this.messageEmitter.emitAgentMessage(warning);
3015
- debugLogger6.warn(warning);
3498
+ if (continuation2.kind === "terminal") {
3499
+ return { stopReason: continuation2.stopReason };
3500
+ }
3501
+ continue;
3502
+ }
3503
+ if (waitsForQueuedPrompt) {
3016
3504
  return { stopReason: "end_turn" };
3017
3505
  }
3018
- if (stopHookIterationCount > 1) {
3019
- await this.messageEmitter.emitStopHookLoop(
3020
- stopHookIterationCount,
3021
- stopHookReasons,
3022
- response.stopHookCount ?? 1
3506
+ }
3507
+ let externalReason = null;
3508
+ let stopHookCount = 1;
3509
+ let queuedPromptArrivedDuringStopHook = false;
3510
+ if (allowExternalHooks && hooksEnabled && messageBus && stopHookIterationCount < stopHookBlockingCap && this.config.hasHooksForEvent?.("Stop")) {
3511
+ const responseText = this.#getCurrentChat().getLastModelMessageText?.() || "[no response text]";
3512
+ const contextUsage = buildContextUsage(
3513
+ this.config.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT,
3514
+ this.lastPromptTokenCount
3515
+ );
3516
+ let response;
3517
+ try {
3518
+ response = await messageBus.request(
3519
+ {
3520
+ type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
3521
+ eventName: "Stop",
3522
+ input: {
3523
+ stop_hook_active: true,
3524
+ last_assistant_message: responseText,
3525
+ ...contextUsage
3526
+ },
3527
+ signal: pendingSend.signal
3528
+ },
3529
+ "hook-execution-response" /* HOOK_EXECUTION_RESPONSE */
3023
3530
  );
3531
+ } catch (error) {
3532
+ this.todoStopGuard.pauseForTrustedRetry();
3533
+ throw error;
3024
3534
  }
3025
- const continueParts = [{ text: continueReason }];
3026
- let nextMessage = {
3027
- role: "user",
3028
- parts: continueParts
3029
- };
3030
- const toolLoopState = createDaemonToolLoopState();
3031
- while (nextMessage !== null) {
3032
- if (pendingSend.signal.aborted) {
3033
- return { stopReason: "cancelled" };
3034
- }
3035
- const functionCalls = [];
3036
- const preparationTracker = new ToolCallPreparationTracker(
3037
- this.toolCallEmitter
3038
- );
3039
- let usageMetadata = null;
3040
- const streamStartTime = Date.now();
3041
- const messageDisplay = this.#createMessageDisplayDispatcher(
3042
- pendingSend.signal
3043
- );
3044
- try {
3045
- const continueSendResult = await this.#sendMessageStreamWithAutoCompression(
3046
- promptId + "_stop_hook_" + stopHookIterationCount,
3047
- nextMessage?.parts ?? [],
3048
- pendingSend.signal,
3049
- { skipCompression: stopHookIterationCount > 1 }
3535
+ if (pendingSend.signal.aborted) {
3536
+ this.todoStopGuard.suspend();
3537
+ return { stopReason: "cancelled" };
3538
+ }
3539
+ if (this.todoStopGuard.needsStopInspection) {
3540
+ this.#beginTodoStopGuardQueuedPromptCheck();
3541
+ const drained = await this.#drainMidTurnInput(pendingSend.signal, {
3542
+ watchQueuedPromptForTodoStopGuard: true,
3543
+ onFullTurnModel
3544
+ });
3545
+ const waitsForQueuedPrompt = this.#finishTodoStopGuardQueuedPromptCheck(drained.hasQueuedPrompt);
3546
+ queuedPromptArrivedDuringStopHook = waitsForQueuedPrompt;
3547
+ if (drained.parts.length > 0) {
3548
+ this.todoStopGuard.acceptMidTurnUserInput();
3549
+ const continuation2 = await this.#runStopContinuation(
3550
+ pendingSend,
3551
+ promptId + "_mid_turn_" + ++midTurnContinuationCount,
3552
+ promptId,
3553
+ drained.parts,
3554
+ false,
3555
+ {
3556
+ onFullTurnModel,
3557
+ getModelOverride: /* @__PURE__ */ __name(() => modelOverride, "getModelOverride")
3558
+ }
3050
3559
  );
3051
- if (!continueSendResult.responseStream) {
3052
- this.#preserveUnsentMessageHistory(
3053
- nextMessage,
3054
- continueSendResult.stopReason === "cancelled"
3055
- );
3056
- return { stopReason: continueSendResult.stopReason };
3560
+ if (continuation2.kind === "terminal") {
3561
+ return { stopReason: continuation2.stopReason };
3057
3562
  }
3058
- const continueResponseStream = continueSendResult.responseStream;
3059
- nextMessage = null;
3060
- let streamFailed = false;
3061
- try {
3062
- for await (const resp of continueResponseStream) {
3063
- if (pendingSend.signal.aborted) {
3064
- return { stopReason: "cancelled" };
3065
- }
3066
- if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
3067
- const candidate = resp.value.candidates[0];
3068
- for (const part of candidate.content?.parts ?? []) {
3069
- if (!part.text) continue;
3070
- this.messageEmitter.emitMessage(
3071
- part.text,
3072
- "assistant",
3073
- part.thought
3074
- );
3075
- if (!part.thought) {
3076
- messageDisplay?.addChunk(part.text);
3077
- }
3563
+ }
3564
+ }
3565
+ const hookOutput = response.output ? createHookOutput("Stop", response.output) : void 0;
3566
+ const stopOutput = hookOutput;
3567
+ if (stopOutput?.systemMessage) {
3568
+ await this.messageEmitter.emitAgentMessage(stopOutput.systemMessage);
3569
+ }
3570
+ if (stopOutput?.isBlockingDecision() || stopOutput?.shouldStopExecution()) {
3571
+ externalReason = stopOutput.getEffectiveReason();
3572
+ stopHookIterationCount++;
3573
+ stopHookReasons = [...stopHookReasons, externalReason];
3574
+ stopHookCount = response.stopHookCount ?? 1;
3575
+ }
3576
+ }
3577
+ const guardDecision = queuedPromptArrivedDuringStopHook ? null : this.todoStopGuard.decide(
3578
+ this.todoStopGuard.needsStopInspection ? this.#hasRelevantTodoStopGuardBackgroundInput() : false
3579
+ );
3580
+ const guardContinuation = guardDecision?.kind === "continue" ? guardDecision : null;
3581
+ if (guardDecision?.kind === "exhausted") {
3582
+ await this.#emitTodoStopGuardExhausted(guardDecision);
3583
+ if (!externalReason) return { stopReason: "end_turn" };
3584
+ }
3585
+ if (externalReason && stopHookIterationCount >= stopHookBlockingCap) {
3586
+ const warning = formatStopHookBlockingCapWarning(
3587
+ "Stop",
3588
+ stopHookBlockingCap
3589
+ );
3590
+ abortGoalForStopHookCap(
3591
+ this.config,
3592
+ this.config.getSessionId(),
3593
+ warning
3594
+ );
3595
+ this.todoStopGuard.suspend();
3596
+ await this.messageEmitter.emitAgentMessage(warning);
3597
+ debugLogger6.warn(warning);
3598
+ return { stopReason: "end_turn" };
3599
+ }
3600
+ if (queuedPromptArrivedDuringStopHook) {
3601
+ return { stopReason: "end_turn" };
3602
+ }
3603
+ if (!externalReason && !guardContinuation) {
3604
+ return { stopReason: "end_turn" };
3605
+ }
3606
+ const continueParts = [];
3607
+ if (externalReason) continueParts.push({ text: externalReason });
3608
+ if (guardContinuation) {
3609
+ continueParts.push({
3610
+ text: this.#buildTodoStopGuardPrompt(guardContinuation)
3611
+ });
3612
+ }
3613
+ const continuationPromptId = externalReason ? promptId + "_stop_hook_" + stopHookIterationCount : promptId + "_todo_stop_guard_" + guardContinuation.attempt;
3614
+ if (externalReason && stopHookIterationCount > 1 && !guardContinuation) {
3615
+ await this.messageEmitter.emitStopHookLoop(
3616
+ stopHookIterationCount,
3617
+ stopHookReasons,
3618
+ stopHookCount
3619
+ );
3620
+ }
3621
+ const continuation = await this.#runStopContinuation(
3622
+ pendingSend,
3623
+ continuationPromptId,
3624
+ promptId,
3625
+ continueParts,
3626
+ stopHookIterationCount > 1 || (guardContinuation?.attempt ?? 0) > 1,
3627
+ {
3628
+ ...guardContinuation ? { guardContinuation } : {},
3629
+ ...externalReason ? { externalParts: [{ text: externalReason }] } : {},
3630
+ ...externalReason && stopHookIterationCount > 1 && guardContinuation ? {
3631
+ onAutomaticContinuationValidated: /* @__PURE__ */ __name(() => this.messageEmitter.emitStopHookLoop(
3632
+ stopHookIterationCount,
3633
+ stopHookReasons,
3634
+ stopHookCount
3635
+ ), "onAutomaticContinuationValidated")
3636
+ } : {},
3637
+ onFullTurnModel,
3638
+ getModelOverride: /* @__PURE__ */ __name(() => modelOverride, "getModelOverride")
3639
+ }
3640
+ );
3641
+ if (continuation.supersededAutomaticContinuation && externalReason) {
3642
+ stopHookIterationCount--;
3643
+ stopHookReasons = stopHookReasons.slice(0, -1);
3644
+ }
3645
+ if (continuation.kind === "terminal") {
3646
+ return { stopReason: continuation.stopReason };
3647
+ }
3648
+ }
3649
+ }
3650
+ async #runStopContinuation(pendingSend, streamPromptId, toolPromptId, parts, skipCompression, options = {}) {
3651
+ let nextMessage = { role: "user", parts };
3652
+ let nextGuardContinuation = options.guardContinuation;
3653
+ const toolLoopState = createDaemonToolLoopState();
3654
+ let initialSend = true;
3655
+ let automaticContinuationValidated = false;
3656
+ let supersededAutomaticContinuation = false;
3657
+ while (nextMessage !== null) {
3658
+ if (pendingSend.signal.aborted) {
3659
+ this.todoStopGuard.suspend();
3660
+ return {
3661
+ kind: "terminal",
3662
+ stopReason: "cancelled",
3663
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3664
+ };
3665
+ }
3666
+ const functionCalls = [];
3667
+ const preparationTracker = new ToolCallPreparationTracker(
3668
+ this.toolCallEmitter
3669
+ );
3670
+ let usageMetadata = null;
3671
+ const streamStartTime = Date.now();
3672
+ let streamFailed = false;
3673
+ let guardForThisSend = nextGuardContinuation;
3674
+ let preserveGuardOnSkippedSend = false;
3675
+ let messageForPreservation = nextMessage;
3676
+ const externalParts = initialSend ? options.externalParts : void 0;
3677
+ const promptIdForSend = guardForThisSend && guardForThisSend.attempt !== options.guardContinuation?.attempt ? toolPromptId + "_todo_stop_guard_" + guardForThisSend.attempt : streamPromptId;
3678
+ const messageDisplay = this.#createMessageDisplayDispatcher(
3679
+ pendingSend.signal
3680
+ );
3681
+ try {
3682
+ const sendResult = await this.#sendMessageStreamWithAutoCompression(
3683
+ promptIdForSend,
3684
+ nextMessage.parts ?? [],
3685
+ pendingSend.signal,
3686
+ {
3687
+ skipCompression: skipCompression || (guardForThisSend?.attempt ?? 0) > 1,
3688
+ getModelOverride: options.getModelOverride,
3689
+ beforeSend: guardForThisSend || !automaticContinuationValidated && options.onAutomaticContinuationValidated ? async ({ compressionFailed }) => {
3690
+ const inspectGuardPriority = guardForThisSend !== void 0;
3691
+ const guardCompressionFailed = inspectGuardPriority && compressionFailed;
3692
+ if (inspectGuardPriority) {
3693
+ this.#beginTodoStopGuardQueuedPromptCheck();
3694
+ const drained = await this.#drainMidTurnInput(
3695
+ pendingSend.signal,
3696
+ {
3697
+ watchQueuedPromptForTodoStopGuard: true,
3698
+ onFullTurnModel: options.onFullTurnModel
3699
+ }
3700
+ );
3701
+ const waitsForQueuedPrompt = this.#finishTodoStopGuardQueuedPromptCheck(
3702
+ drained.hasQueuedPrompt
3703
+ );
3704
+ if (drained.parts.length > 0) {
3705
+ this.todoStopGuard.acceptMidTurnUserInput();
3706
+ guardForThisSend = void 0;
3707
+ nextGuardContinuation = void 0;
3708
+ if (initialSend) {
3709
+ supersededAutomaticContinuation = true;
3078
3710
  }
3711
+ const replacementMessage = initialSend ? drained.parts : [
3712
+ ...(nextMessage?.parts ?? []).filter(
3713
+ (part) => !("text" in part && isTodoStopGuardPromptText(part.text))
3714
+ ),
3715
+ ...drained.parts
3716
+ ];
3717
+ messageForPreservation = {
3718
+ role: "user",
3719
+ parts: replacementMessage
3720
+ };
3721
+ return {
3722
+ kind: "send",
3723
+ message: replacementMessage
3724
+ };
3079
3725
  }
3080
- if (resp.type === "chunk" /* CHUNK */ && resp.value.usageMetadata) {
3081
- usageMetadata = resp.value.usageMetadata;
3726
+ if (waitsForQueuedPrompt) {
3727
+ guardForThisSend = void 0;
3728
+ nextGuardContinuation = void 0;
3729
+ preserveGuardOnSkippedSend = true;
3730
+ if (initialSend) {
3731
+ supersededAutomaticContinuation = true;
3732
+ }
3733
+ return { kind: "stop", stopReason: "end_turn" };
3082
3734
  }
3083
- if (resp.type === "chunk" /* CHUNK */) {
3084
- await preparationTracker.observe(resp.value);
3085
- if (resp.value.functionCalls) {
3086
- preparationTracker.resolve(resp.value.functionCalls);
3087
- functionCalls.push(...resp.value.functionCalls);
3735
+ if (guardCompressionFailed) {
3736
+ this.todoStopGuard.suspend();
3737
+ guardForThisSend = void 0;
3738
+ nextGuardContinuation = void 0;
3739
+ if (!externalParts || externalParts.length === 0) {
3740
+ preserveGuardOnSkippedSend = true;
3741
+ return { kind: "stop", stopReason: "end_turn" };
3088
3742
  }
3089
3743
  }
3090
- if (resp.type === "retry" /* RETRY */ || resp.type === "model_fallback" /* MODEL_FALLBACK */) {
3091
- await finalizeToolCallPreparations(
3092
- preparationTracker,
3093
- true,
3094
- `Stop Hook continuation ${resp.type}`
3744
+ if (guardForThisSend && this.config.getApprovalMode() === "plan" /* PLAN */) {
3745
+ this.#clearTodoStopGuardTrustAndDrainAutomaticQueues();
3746
+ }
3747
+ if (guardForThisSend) {
3748
+ const hasRelevantBackgroundInput = this.#hasRelevantTodoStopGuardBackgroundInput();
3749
+ const refreshedDecision = guardForThisSend.toolClosure ? this.todoStopGuard.decideToolClosure(
3750
+ guardForThisSend.attempt - 1,
3751
+ hasRelevantBackgroundInput
3752
+ ) : this.todoStopGuard.decide(
3753
+ hasRelevantBackgroundInput
3095
3754
  );
3096
- functionCalls.length = 0;
3755
+ if (refreshedDecision.kind !== "continue" || refreshedDecision.attempt !== guardForThisSend.attempt) {
3756
+ guardForThisSend = void 0;
3757
+ nextGuardContinuation = void 0;
3758
+ if (!options.externalParts) {
3759
+ preserveGuardOnSkippedSend = true;
3760
+ return { kind: "stop", stopReason: "end_turn" };
3761
+ }
3762
+ if (!initialSend && nextMessage) {
3763
+ nextMessage = {
3764
+ ...nextMessage,
3765
+ parts: (nextMessage.parts ?? []).filter(
3766
+ (part) => !("text" in part && isTodoStopGuardPromptText(part.text))
3767
+ )
3768
+ };
3769
+ }
3770
+ }
3097
3771
  }
3098
3772
  }
3099
- } catch (error) {
3100
- streamFailed = true;
3101
- throw error;
3102
- } finally {
3103
- await finalizeToolCallPreparations(
3104
- preparationTracker,
3105
- streamFailed || pendingSend.signal.aborted,
3106
- "Stop Hook continuation"
3107
- );
3108
- }
3109
- } catch (error) {
3110
- const errorStatus = getErrorStatus(error);
3111
- const errorMessage = error instanceof Error ? error.message : String(error);
3112
- const errorType = classifyApiError({
3113
- message: errorMessage,
3114
- status: errorStatus
3115
- });
3116
- const hookSystem = this.config.getHookSystem?.();
3117
- const hooksEnabledForStopFailure = !this.config.getDisableAllHooks?.();
3118
- if (hooksEnabledForStopFailure && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
3119
- hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
3120
- debugLogger6.warn(`StopFailure hook failed: ${err}`);
3121
- });
3122
- }
3123
- if (errorStatus === 429) {
3124
- throw new RequestError(
3125
- 429,
3126
- "Rate limit exceeded. Try again later."
3773
+ if (!automaticContinuationValidated && options.onAutomaticContinuationValidated) {
3774
+ await options.onAutomaticContinuationValidated();
3775
+ automaticContinuationValidated = true;
3776
+ }
3777
+ const selectedMessage = guardForThisSend || !externalParts ? nextMessage?.parts ?? [] : externalParts;
3778
+ messageForPreservation = {
3779
+ role: "user",
3780
+ parts: selectedMessage
3781
+ };
3782
+ return {
3783
+ kind: "send",
3784
+ message: selectedMessage
3785
+ };
3786
+ } : void 0
3787
+ }
3788
+ );
3789
+ if (!sendResult.responseStream) {
3790
+ if (!automaticContinuationValidated && !supersededAutomaticContinuation && options.onAutomaticContinuationValidated) {
3791
+ await options.onAutomaticContinuationValidated();
3792
+ automaticContinuationValidated = true;
3793
+ }
3794
+ if (!preserveGuardOnSkippedSend) {
3795
+ this.todoStopGuard.suspend();
3796
+ }
3797
+ const preservedParts = (messageForPreservation.parts ?? []).filter(
3798
+ (part) => !("text" in part && isTodoStopGuardPromptText(part.text))
3799
+ );
3800
+ this.#preserveUnsentMessageHistory(
3801
+ preservedParts.length > 0 ? { ...messageForPreservation, parts: preservedParts } : null,
3802
+ sendResult.stopReason === "cancelled"
3803
+ );
3804
+ return {
3805
+ kind: "terminal",
3806
+ stopReason: sendResult.stopReason,
3807
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3808
+ };
3809
+ }
3810
+ const responseStream = sendResult.responseStream;
3811
+ nextMessage = null;
3812
+ initialSend = false;
3813
+ if (guardForThisSend) {
3814
+ const guardCommitted = this.todoStopGuard.commitContinuation(
3815
+ guardForThisSend.attempt
3816
+ );
3817
+ if (guardCommitted) {
3818
+ await this.#emitTodoStopGuardContinuation(guardForThisSend);
3819
+ }
3820
+ if (!guardCommitted && externalParts) {
3821
+ guardForThisSend = void 0;
3822
+ }
3823
+ }
3824
+ for await (const response of responseStream) {
3825
+ if (pendingSend.signal.aborted) {
3826
+ this.todoStopGuard.suspend();
3827
+ return {
3828
+ kind: "terminal",
3829
+ stopReason: "cancelled",
3830
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3831
+ };
3832
+ }
3833
+ if (response.type === "chunk" /* CHUNK */ && response.value.candidates && response.value.candidates.length > 0) {
3834
+ const candidate = response.value.candidates[0];
3835
+ for (const part of candidate.content?.parts ?? []) {
3836
+ if (!part.text) continue;
3837
+ this.messageEmitter.emitMessage(
3838
+ part.text,
3839
+ "assistant",
3840
+ part.thought
3127
3841
  );
3842
+ if (!part.thought) messageDisplay?.addChunk(part.text);
3128
3843
  }
3129
- throw error;
3130
- } finally {
3131
- await messageDisplay?.finish();
3132
3844
  }
3133
- if (usageMetadata) {
3134
- this.#recordPromptTokenCount(usageMetadata);
3135
- const durationMs = Date.now() - streamStartTime;
3136
- await this.messageEmitter.emitUsageMetadata(
3137
- usageMetadata,
3138
- "",
3139
- durationMs
3140
- );
3845
+ if (response.type === "chunk" /* CHUNK */ && response.value.usageMetadata) {
3846
+ usageMetadata = response.value.usageMetadata;
3141
3847
  }
3142
- if (functionCalls.length > 0) {
3143
- const toolRun = await this.runToolCalls(
3144
- pendingSend.signal,
3145
- promptId,
3146
- functionCalls,
3147
- toolLoopState
3148
- );
3149
- if (toolRun.stopAfterPermissionCancel) {
3150
- await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
3151
- return { stopReason: "end_turn" };
3848
+ if (response.type === "chunk" /* CHUNK */) {
3849
+ await preparationTracker.observe(response.value);
3850
+ if (response.value.functionCalls) {
3851
+ preparationTracker.resolve(response.value.functionCalls);
3852
+ functionCalls.push(...response.value.functionCalls);
3152
3853
  }
3153
- nextMessage = await this.#buildNextMessageAfterToolRun(
3154
- toolRun,
3155
- pendingSend.signal
3854
+ }
3855
+ if (response.type === "retry" /* RETRY */ || response.type === "model_fallback" /* MODEL_FALLBACK */) {
3856
+ await finalizeToolCallPreparations(
3857
+ preparationTracker,
3858
+ true,
3859
+ `daemon continuation ${response.type}`
3156
3860
  );
3157
- if (toolRun.loopDetected) {
3158
- await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
3159
- return { stopReason: "end_turn" };
3861
+ functionCalls.length = 0;
3862
+ }
3863
+ }
3864
+ } catch (error) {
3865
+ streamFailed = true;
3866
+ this.todoStopGuard.pauseForTrustedRetry();
3867
+ const errorStatus = getErrorStatus(error);
3868
+ const errorMessage = error instanceof Error ? error.message : String(error);
3869
+ const errorType = classifyApiError({
3870
+ message: errorMessage,
3871
+ status: errorStatus
3872
+ });
3873
+ const hookSystem = this.config.getHookSystem?.();
3874
+ if (!this.config.getDisableAllHooks?.() && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
3875
+ hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
3876
+ debugLogger6.warn(`StopFailure hook failed: ${err}`);
3877
+ });
3878
+ }
3879
+ if (errorStatus === 429) {
3880
+ throw new RequestError(429, "Rate limit exceeded. Try again later.");
3881
+ }
3882
+ throw error;
3883
+ } finally {
3884
+ try {
3885
+ await finalizeToolCallPreparations(
3886
+ preparationTracker,
3887
+ streamFailed || pendingSend.signal.aborted,
3888
+ "daemon continuation"
3889
+ );
3890
+ } finally {
3891
+ await messageDisplay?.finish();
3892
+ }
3893
+ }
3894
+ if (usageMetadata) {
3895
+ this.#recordPromptTokenCount(usageMetadata);
3896
+ const durationMs = Date.now() - streamStartTime;
3897
+ await this.messageEmitter.emitUsageMetadata(
3898
+ usageMetadata,
3899
+ "",
3900
+ durationMs
3901
+ );
3902
+ }
3903
+ if (functionCalls.length > 0) {
3904
+ const toolRun = await this.#runWithFullTurnModel(
3905
+ options.getModelOverride?.(),
3906
+ () => this.runToolCalls(
3907
+ pendingSend.signal,
3908
+ toolPromptId,
3909
+ functionCalls,
3910
+ toolLoopState
3911
+ )
3912
+ );
3913
+ if (toolRun.stopAfterPermissionCancel || toolRun.loopDetected) {
3914
+ this.todoStopGuard.suspend();
3915
+ await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
3916
+ return {
3917
+ kind: "terminal",
3918
+ stopReason: "end_turn",
3919
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3920
+ };
3921
+ }
3922
+ const nextAfterTools = await this.#buildNextMessageAfterToolRun(
3923
+ toolRun,
3924
+ pendingSend.signal,
3925
+ options.onFullTurnModel
3926
+ );
3927
+ nextMessage = nextAfterTools.message;
3928
+ if (nextAfterTools.hadMidTurnUserInput) {
3929
+ nextGuardContinuation = void 0;
3930
+ continue;
3931
+ }
3932
+ if (guardForThisSend && nextMessage) {
3933
+ const nextDecision = this.todoStopGuard.decideToolClosure(
3934
+ guardForThisSend.attempt,
3935
+ this.#hasRelevantTodoStopGuardBackgroundInput()
3936
+ );
3937
+ if (nextDecision.kind === "continue" && nextDecision.attempt > guardForThisSend.attempt) {
3938
+ nextGuardContinuation = nextDecision;
3939
+ if (!nextDecision.toolClosure) {
3940
+ nextMessage = {
3941
+ ...nextMessage,
3942
+ parts: [
3943
+ ...nextMessage.parts ?? [],
3944
+ { text: this.#buildTodoStopGuardPrompt(nextDecision) }
3945
+ ]
3946
+ };
3160
3947
  }
3948
+ } else if (nextDecision.kind === "continue" && nextDecision.attempt <= guardForThisSend.attempt) {
3949
+ nextGuardContinuation = void 0;
3950
+ } else if (options.externalParts) {
3951
+ nextGuardContinuation = void 0;
3952
+ } else {
3953
+ this.#preserveUnsentMessageHistory(nextMessage, true);
3954
+ return {
3955
+ kind: "natural_stop",
3956
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3957
+ };
3161
3958
  }
3959
+ } else {
3960
+ nextGuardContinuation = void 0;
3162
3961
  }
3163
- continue;
3164
3962
  }
3165
- break;
3166
3963
  }
3167
- return { stopReason: "end_turn" };
3964
+ return {
3965
+ kind: "natural_stop",
3966
+ ...supersededAutomaticContinuation ? { supersededAutomaticContinuation: true } : {}
3967
+ };
3968
+ }
3969
+ #buildTodoStopGuardPrompt(state) {
3970
+ const prompt = `${TODO_STOP_GUARD_PROMPT_PREFIX}${state.unfinishedCount}${TODO_STOP_GUARD_PROMPT_BODY_SUFFIX}`;
3971
+ if (state.attempt < state.maxAttempts) return prompt;
3972
+ return prompt + TODO_STOP_GUARD_FINAL_PROMPT_SUFFIX;
3973
+ }
3974
+ async #emitTodoStopGuardContinuation(state) {
3975
+ await this.#emitTodoStopGuardMessageSafely(
3976
+ `[Todo Stop Guard] Automatic continuation ${state.attempt}/${state.maxAttempts} started; ${state.unfinishedCount} todo item(s) remain unfinished.`,
3977
+ state
3978
+ );
3979
+ }
3980
+ async #emitTodoStopGuardExhausted(state) {
3981
+ if (!this.todoStopGuard.markExhaustionReported()) return;
3982
+ await this.#emitTodoStopGuardMessageSafely(
3983
+ `[Todo Stop Guard] Automatic continuation stopped after ${state.maxAttempts} attempts; ${state.unfinishedCount} todo item(s) remain unfinished.`,
3984
+ state
3985
+ );
3986
+ }
3987
+ async #emitTodoStopGuardMessageSafely(text, state) {
3988
+ try {
3989
+ await this.sendUpdate({
3990
+ sessionUpdate: "agent_message_chunk",
3991
+ content: { type: "text", text },
3992
+ _meta: {
3993
+ source: "todo_stop_guard",
3994
+ qwenDiscreteMessage: true,
3995
+ attempt: state.attempt,
3996
+ maxAttempts: state.maxAttempts,
3997
+ unfinishedCount: state.unfinishedCount
3998
+ }
3999
+ });
4000
+ } catch (error) {
4001
+ debugLogger6.warn(
4002
+ `Failed to emit Todo Stop Guard status: ${this.#formatError(error)}`
4003
+ );
4004
+ }
3168
4005
  }
3169
4006
  async sendUpdate(update) {
3170
4007
  const params = {
@@ -3176,14 +4013,13 @@ ${this.pendingWorktreeNotice}
3176
4013
  #getCurrentChat() {
3177
4014
  return this.config.getGeminiClient().getChat();
3178
4015
  }
3179
- /**
3180
- * Mirrors the core send path for ACP model sends.
3181
- *
3182
- * Attempts automatic chat compression first, checks the session token limit,
3183
- * emits an ACP-visible notice when compression succeeds, and returns the ACP
3184
- * stop reason when the provider send should be skipped because the request
3185
- * was cancelled or the session token limit was exceeded.
3186
- */
4016
+ async #runWithFullTurnModel(modelOverride, fn) {
4017
+ if (!modelOverride?.endsWith("\0")) {
4018
+ return fn();
4019
+ }
4020
+ const runtimeView = await this.config.getBaseLlmClient().resolveForModel(modelOverride.slice(0, -1), { failClosed: true });
4021
+ return runWithRuntimeContentGenerator(runtimeView, fn);
4022
+ }
3187
4023
  /**
3188
4024
  * Create the MessageDisplay hook dispatcher for one model call's streamed
3189
4025
  * reply, or null when the hook isn't registered (the common case — keeps
@@ -3206,11 +4042,20 @@ ${this.pendingWorktreeNotice}
3206
4042
  (message) => debugLogger6.warn(message)
3207
4043
  );
3208
4044
  }
4045
+ /**
4046
+ * Mirrors the core send path for ACP model sends.
4047
+ *
4048
+ * Attempts automatic chat compression first, checks the session token limit,
4049
+ * emits an ACP-visible notice when compression succeeds, and returns the ACP
4050
+ * stop reason when the provider send should be skipped because the request
4051
+ * was cancelled or the session token limit was exceeded.
4052
+ */
3209
4053
  async #sendMessageStreamWithAutoCompression(promptId, message, abortSignal, options = {}) {
3210
4054
  const geminiClient = this.config.getGeminiClient();
3211
4055
  let compressionDiagnostic = null;
3212
4056
  let compressionInfo = null;
3213
- if (!options.skipCompression) {
4057
+ let compressionFailed = false;
4058
+ if (!options.skipCompression && !(options.getModelOverride?.() ?? options.modelOverride)) {
3214
4059
  try {
3215
4060
  const compressed = await geminiClient.tryCompressChat(
3216
4061
  promptId,
@@ -3219,6 +4064,9 @@ ${this.pendingWorktreeNotice}
3219
4064
  );
3220
4065
  compressionInfo = compressed;
3221
4066
  this.#recordCompressionTokenCount(compressed);
4067
+ compressionFailed = isCompressionFailureStatus(
4068
+ compressed.compressionStatus
4069
+ );
3222
4070
  if (compressed.compressionStatus === 1 /* COMPRESSED */) {
3223
4071
  this.loopTickResolver?.resetCache();
3224
4072
  const reasonClause = compressed.triggerReason === "image_overflow" ? `accumulated enough tool screenshots to trigger compaction for ${this.config.getModel()}` : `approached the input token limit for ${this.config.getModel()}`;
@@ -3232,6 +4080,7 @@ ${this.pendingWorktreeNotice}
3232
4080
  debugLogger6.warn(
3233
4081
  `Auto-compression failed for prompt ${promptId}; proceeding without compression: ` + this.#formatError(compressionError)
3234
4082
  );
4083
+ compressionFailed = true;
3235
4084
  }
3236
4085
  }
3237
4086
  if (abortSignal.aborted) {
@@ -3267,8 +4116,21 @@ ${this.pendingWorktreeNotice}
3267
4116
  );
3268
4117
  return { responseStream: null, stopReason: "cancelled" };
3269
4118
  }
4119
+ if (options.beforeSend) {
4120
+ const decision = await options.beforeSend({ compressionFailed });
4121
+ if (decision.kind === "stop") {
4122
+ return { responseStream: null, stopReason: decision.stopReason };
4123
+ }
4124
+ message = decision.message;
4125
+ }
4126
+ if (abortSignal.aborted) {
4127
+ debugLogger6.debug(
4128
+ `Send aborted after pre-send validation for prompt ${promptId}`
4129
+ );
4130
+ return { responseStream: null, stopReason: "cancelled" };
4131
+ }
3270
4132
  const responseStream = await this.#getCurrentChat().sendMessageStream(
3271
- this.config.getModel(),
4133
+ options.getModelOverride?.() ?? options.modelOverride ?? this.config.getModel(),
3272
4134
  {
3273
4135
  message,
3274
4136
  config: {
@@ -3315,22 +4177,30 @@ ${this.pendingWorktreeNotice}
3315
4177
  );
3316
4178
  await this.messageRewriter?.waitForPendingRewrites();
3317
4179
  }
3318
- async #buildNextMessageAfterToolRun(toolRun, abortSignal) {
4180
+ async #buildNextMessageAfterToolRun(toolRun, abortSignal, onFullTurnModel) {
3319
4181
  if (toolRun.loopDetected) {
3320
4182
  debugLogger6.debug("Stopping ACP turn after daemon loop detection.");
3321
- return null;
4183
+ return { message: null, hadMidTurnUserInput: false };
3322
4184
  }
3323
4185
  if (toolRun.repeatedDuplicateProviderToolCall) {
4186
+ this.todoStopGuard.suspend();
3324
4187
  debugLogger6.debug(
3325
4188
  "Stopping ACP turn after dropping repeated duplicate provider tool-call response."
3326
4189
  );
3327
- return null;
4190
+ return { message: null, hadMidTurnUserInput: false };
3328
4191
  }
3329
- const parts = [
3330
- ...toolRun.parts,
3331
- ...await this.#drainMidTurnUserMessages(abortSignal)
3332
- ];
3333
- return { role: "user", parts };
4192
+ const drained = await this.#drainMidTurnInput(abortSignal, {
4193
+ onFullTurnModel
4194
+ });
4195
+ const hadMidTurnUserInput = drained.parts.length > 0;
4196
+ if (hadMidTurnUserInput) {
4197
+ this.todoStopGuard.acceptMidTurnUserInput();
4198
+ }
4199
+ const parts = [...toolRun.parts, ...drained.parts];
4200
+ return {
4201
+ message: { role: "user", parts },
4202
+ hadMidTurnUserInput
4203
+ };
3334
4204
  }
3335
4205
  #recordCompressionTokenCount(info) {
3336
4206
  this.#syncPromptTokenCountWithCurrentChat();
@@ -3408,15 +4278,26 @@ ${this.pendingWorktreeNotice}
3408
4278
  content: { type: "text", text }
3409
4279
  });
3410
4280
  }
3411
- async #drainMidTurnUserMessages(abortSignal) {
4281
+ async #drainMidTurnUserMessages(abortSignal, onFullTurnModel) {
4282
+ return (await this.#drainMidTurnInput(abortSignal, { onFullTurnModel })).parts;
4283
+ }
4284
+ async #drainMidTurnInput(abortSignal, options = {}) {
3412
4285
  const recovered = this.#takeRecoveredMidTurnMessages();
3413
4286
  if (this.midTurnDrainUnavailable) {
3414
- return this.#buildMidTurnParts(recovered, abortSignal);
4287
+ return {
4288
+ parts: await this.#buildMidTurnParts(
4289
+ recovered,
4290
+ abortSignal,
4291
+ options.onFullTurnModel
4292
+ ),
4293
+ hasQueuedPrompt: false
4294
+ };
3415
4295
  }
3416
4296
  let drainPromise;
3417
4297
  try {
3418
4298
  drainPromise = this.client.extMethod(MID_TURN_QUEUE_DRAIN_METHOD, {
3419
- sessionId: this.sessionId
4299
+ sessionId: this.sessionId,
4300
+ ...options.watchQueuedPromptForTodoStopGuard ? { todoStopGuardWatchQueuedPrompt: true } : {}
3420
4301
  });
3421
4302
  let timeoutHandle;
3422
4303
  const timeoutPromise = new Promise((_, reject) => {
@@ -3432,10 +4313,14 @@ ${this.pendingWorktreeNotice}
3432
4313
  clearTimeout(timeoutHandle);
3433
4314
  }
3434
4315
  this.midTurnDrainTimeoutStrikes = 0;
3435
- return this.#buildMidTurnParts(
3436
- [...recovered, ...parseMidTurnDrainResponse(response)],
3437
- abortSignal
3438
- );
4316
+ return {
4317
+ parts: await this.#buildMidTurnParts(
4318
+ [...recovered, ...parseMidTurnDrainResponse(response)],
4319
+ abortSignal,
4320
+ options.onFullTurnModel
4321
+ ),
4322
+ hasQueuedPrompt: isRecord2(response) && response["hasQueuedPrompt"] === true
4323
+ };
3439
4324
  } catch (error) {
3440
4325
  const errorMessage = error instanceof Error ? error.message : error && typeof error === "object" && "message" in error ? String(error.message) : String(error);
3441
4326
  const errorCode = error && typeof error === "object" && "code" in error ? error.code : void 0;
@@ -3451,7 +4336,14 @@ ${this.pendingWorktreeNotice}
3451
4336
  debugLogger6.warn(
3452
4337
  `Mid-turn queue drain ${isPermanentError ? "permanently " : ""}unavailable [session ${this.sessionId}]: ${errorMessage}`
3453
4338
  );
3454
- return this.#buildMidTurnParts(recovered, abortSignal);
4339
+ return {
4340
+ parts: await this.#buildMidTurnParts(
4341
+ recovered,
4342
+ abortSignal,
4343
+ options.onFullTurnModel
4344
+ ),
4345
+ hasQueuedPrompt: false
4346
+ };
3455
4347
  }
3456
4348
  }
3457
4349
  /** Read and clear the buffer of messages recovered from a timed-out drain. */
@@ -3474,9 +4366,9 @@ ${this.pendingWorktreeNotice}
3474
4366
  });
3475
4367
  const expired = Symbol("mid-turn-recovery-expired");
3476
4368
  let timer;
3477
- const deadline = new Promise((resolve4) => {
4369
+ const deadline = new Promise((resolve5) => {
3478
4370
  timer = setTimeout(
3479
- () => resolve4(expired),
4371
+ () => resolve5(expired),
3480
4372
  MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS
3481
4373
  );
3482
4374
  timer.unref?.();
@@ -3509,7 +4401,7 @@ ${this.pendingWorktreeNotice}
3509
4401
  * happens on injection (here), so a message recovered from an earlier
3510
4402
  * timed-out drain is still recorded exactly once.
3511
4403
  */
3512
- async #buildMidTurnParts(messages, abortSignal) {
4404
+ async #buildMidTurnParts(messages, abortSignal, onFullTurnModel) {
3513
4405
  const parts = [];
3514
4406
  for (const message of messages) {
3515
4407
  const displayText = message.kind === "text" ? message.message : message.displayText;
@@ -3518,7 +4410,9 @@ ${this.pendingWorktreeNotice}
3518
4410
  rawParts = message.kind === "text" ? [{ text: message.message }] : await withTimeoutSignal(
3519
4411
  abortSignal,
3520
4412
  MID_TURN_QUEUE_RESOLVE_TIMEOUT_MS,
3521
- (signal) => this.#resolvePrompt(message.content, signal)
4413
+ (signal) => this.#resolvePrompt(message.content, signal, {
4414
+ onFullTurnModel
4415
+ })
3522
4416
  );
3523
4417
  } catch (messageError) {
3524
4418
  if (abortSignal.aborted) return parts;
@@ -3557,13 +4451,38 @@ ${this.pendingWorktreeNotice}
3557
4451
  scheduler.start((job) => {
3558
4452
  if (this.cronDisabledByTokenLimit) return;
3559
4453
  if (job.missed && detectAutonomousSentinel(job.prompt)) return;
3560
- this.cronQueue.push({
4454
+ this.#enqueueCronPrompt({
3561
4455
  prompt: job.prompt,
3562
- source: job.cronExpr === "@wakeup" ? "loop" : "cron"
4456
+ source: job.cronExpr === "@wakeup" ? "loop" : "cron",
4457
+ ...job.id ? { taskId: job.id } : {}
3563
4458
  });
3564
4459
  void this.#drainCronQueue();
3565
4460
  });
3566
4461
  }
4462
+ #enqueueCronPrompt(item) {
4463
+ if ((this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuardQueuedPromptPriority) && !this.#cronContinuesTodoStopGuardWorkChain(item)) {
4464
+ if (item.taskId) {
4465
+ const duplicateIndex = this.cronQueue.findIndex(
4466
+ (queued) => queued.taskId === item.taskId && !this.#cronContinuesTodoStopGuardWorkChain(queued)
4467
+ );
4468
+ if (duplicateIndex >= 0) {
4469
+ this.cronQueue[duplicateIndex] = item;
4470
+ return;
4471
+ }
4472
+ }
4473
+ const unrelatedIndices = this.cronQueue.map(
4474
+ (queued, index) => this.#cronContinuesTodoStopGuardWorkChain(queued) ? -1 : index
4475
+ ).filter((index) => index >= 0);
4476
+ if (unrelatedIndices.length >= MAX_DEFERRED_UNRELATED_CRON_QUEUE) {
4477
+ const evictedIndex = unrelatedIndices[0];
4478
+ const [evicted] = this.cronQueue.splice(evictedIndex, 1);
4479
+ debugLogger6.warn(
4480
+ `Cron queue overflow while automatic work is deferred: evicting task=${evicted?.taskId ?? "unknown"}`
4481
+ );
4482
+ }
4483
+ }
4484
+ this.cronQueue.push(item);
4485
+ }
3567
4486
  /**
3568
4487
  * Processes queued cron prompts one at a time. Uses `cronProcessing`
3569
4488
  * as a mutex to prevent concurrent access to the chat.
@@ -3573,14 +4492,18 @@ ${this.pendingWorktreeNotice}
3573
4492
  if (this.cronProcessing) return;
3574
4493
  if (this.pendingPrompt) return;
3575
4494
  if (this.notificationProcessing) return;
4495
+ if (this.#nextCronQueueIndex() < 0) return;
3576
4496
  this.cronProcessing = true;
3577
4497
  let resolveCompletion;
3578
- this.cronCompletion = new Promise((resolve4) => {
3579
- resolveCompletion = resolve4;
4498
+ this.cronCompletion = new Promise((resolve5) => {
4499
+ resolveCompletion = resolve5;
3580
4500
  });
3581
4501
  try {
3582
4502
  while (this.cronQueue.length > 0) {
3583
- const item = this.cronQueue.shift();
4503
+ const nextIndex = this.#nextCronQueueIndex();
4504
+ if (nextIndex < 0) break;
4505
+ const [item] = this.cronQueue.splice(nextIndex, 1);
4506
+ if (!item) break;
3584
4507
  await this.#executeCronPrompt(item);
3585
4508
  }
3586
4509
  } finally {
@@ -3596,6 +4519,14 @@ ${this.pendingWorktreeNotice}
3596
4519
  }
3597
4520
  }
3598
4521
  }
4522
+ #nextCronQueueIndex() {
4523
+ if (this.cronQueue.length === 0) return -1;
4524
+ if (this.todoStopGuardQueuedPromptPriority) return -1;
4525
+ if (!this.todoStopGuard.blocksUnrelatedAutomaticTurns) return 0;
4526
+ return this.cronQueue.findIndex(
4527
+ (item) => this.#cronContinuesTodoStopGuardWorkChain(item)
4528
+ );
4529
+ }
3599
4530
  #getLoopTickResolver() {
3600
4531
  const root = this.config.getWorkingDir();
3601
4532
  if (!this.loopTickResolver || this.loopTickResolverRoot !== root) {
@@ -3635,6 +4566,9 @@ ${this.pendingWorktreeNotice}
3635
4566
  async () => {
3636
4567
  const ac = new AbortController();
3637
4568
  this.cronAbortController = ac;
4569
+ this.#prepareTodoStopGuardForAutomaticTurn(
4570
+ this.#cronContinuesTodoStopGuardWorkChain(item)
4571
+ );
3638
4572
  const promptId = this.config.getSessionId() + "########cron" + Date.now();
3639
4573
  let cronHadError = false;
3640
4574
  await withInteractionSpan(
@@ -3708,7 +4642,10 @@ ${this.pendingWorktreeNotice}
3708
4642
  const toolLoopState = createDaemonToolLoopState();
3709
4643
  while (nextMessage !== null) {
3710
4644
  turnCount++;
3711
- if (ac.signal.aborted) return;
4645
+ if (ac.signal.aborted) {
4646
+ this.todoStopGuard.suspend();
4647
+ return;
4648
+ }
3712
4649
  const functionCalls = [];
3713
4650
  const preparationTracker = new ToolCallPreparationTracker(
3714
4651
  this.toolCallEmitter
@@ -3721,6 +4658,7 @@ ${this.pendingWorktreeNotice}
3721
4658
  ac.signal
3722
4659
  );
3723
4660
  if (!sendResult.responseStream) {
4661
+ this.todoStopGuard.suspend();
3724
4662
  this.#preserveUnsentMessageHistory(
3725
4663
  nextMessage,
3726
4664
  sendResult.stopReason === "cancelled"
@@ -3741,7 +4679,10 @@ ${this.pendingWorktreeNotice}
3741
4679
  let streamFailed = false;
3742
4680
  try {
3743
4681
  for await (const resp of responseStream) {
3744
- if (ac.signal.aborted) return;
4682
+ if (ac.signal.aborted) {
4683
+ this.todoStopGuard.suspend();
4684
+ return;
4685
+ }
3745
4686
  if (resp.type === "chunk" /* CHUNK */ && resp.value.candidates && resp.value.candidates.length > 0) {
3746
4687
  const candidate = resp.value.candidates[0];
3747
4688
  for (const part of candidate.content?.parts ?? []) {
@@ -3809,21 +4750,40 @@ ${this.pendingWorktreeNotice}
3809
4750
  toolLoopState
3810
4751
  );
3811
4752
  if (toolRun.stopAfterPermissionCancel) {
4753
+ this.todoStopGuard.suspend();
3812
4754
  await this.#preserveStoppedToolRun(toolRun, ac.signal);
3813
4755
  return;
3814
4756
  }
3815
- nextMessage = await this.#buildNextMessageAfterToolRun(
4757
+ const nextAfterTools = await this.#buildNextMessageAfterToolRun(
3816
4758
  toolRun,
3817
4759
  ac.signal
3818
4760
  );
4761
+ nextMessage = nextAfterTools.message;
3819
4762
  if (toolRun.loopDetected) {
4763
+ this.todoStopGuard.suspend();
3820
4764
  await this.#preserveStoppedToolRun(toolRun, ac.signal);
3821
4765
  return;
3822
4766
  }
3823
4767
  }
3824
4768
  }
4769
+ if (this.todoStopGuard.needsStopInspection) {
4770
+ const guardStop = await this.#handleStopHookLoop(
4771
+ ac,
4772
+ promptId,
4773
+ false,
4774
+ void 0,
4775
+ false
4776
+ );
4777
+ if (guardStop.stopReason === "max_tokens") {
4778
+ this.#stopCronAfterTokenLimit();
4779
+ }
4780
+ }
3825
4781
  } catch (error) {
3826
- if (ac.signal.aborted) return;
4782
+ if (ac.signal.aborted) {
4783
+ this.todoStopGuard.suspend();
4784
+ return;
4785
+ }
4786
+ this.todoStopGuard.pauseForTrustedRetry();
3827
4787
  cronHadError = true;
3828
4788
  debugLogger6.error("Error processing cron prompt:", error);
3829
4789
  const msg = error instanceof Error ? error.message : String(error);
@@ -3849,6 +4809,7 @@ ${this.pendingWorktreeNotice}
3849
4809
  );
3850
4810
  }
3851
4811
  #stopCronAfterTokenLimit() {
4812
+ this.todoStopGuard.suspend();
3852
4813
  this.cronDisabledByTokenLimit = true;
3853
4814
  this.cronQueue = [];
3854
4815
  if (!this.config.isCronEnabled()) return;
@@ -3920,9 +4881,28 @@ ${this.pendingWorktreeNotice}
3920
4881
  }
3921
4882
  #enqueueBackgroundNotification(item) {
3922
4883
  while (this.notificationQueue.length >= MAX_NOTIFICATION_QUEUE) {
3923
- const evicted = this.notificationQueue.shift();
4884
+ let evictedIndex = 0;
4885
+ if (this.todoStopGuard.blocksUnrelatedAutomaticTurns || this.todoStopGuardQueuedPromptPriority) {
4886
+ const incomingIsRelated = this.#notificationContinuesTodoStopGuardWorkChain(item);
4887
+ evictedIndex = this.notificationQueue.findIndex(
4888
+ (queued) => !this.#notificationContinuesTodoStopGuardWorkChain(queued)
4889
+ );
4890
+ if (evictedIndex < 0 && !incomingIsRelated) {
4891
+ debugLogger6.warn(
4892
+ `Notification queue overflow: dropping unrelated task=${item.taskId} kind=${item.kind} while automatic work is deferred`
4893
+ );
4894
+ return;
4895
+ }
4896
+ if (evictedIndex < 0) {
4897
+ debugLogger6.warn(
4898
+ `Notification queue overflow: dropping related task=${item.taskId} kind=${item.kind} because all queued items are related`
4899
+ );
4900
+ return;
4901
+ }
4902
+ }
4903
+ const [evicted] = this.notificationQueue.splice(evictedIndex, 1);
3924
4904
  debugLogger6.warn(
3925
- `Notification queue overflow: evicting task=${evicted.taskId} kind=${evicted.kind}`
4905
+ `Notification queue overflow: evicting task=${evicted?.taskId ?? "unknown"} kind=${evicted?.kind ?? "unknown"}`
3926
4906
  );
3927
4907
  }
3928
4908
  this.notificationQueue.push(item);
@@ -3935,17 +4915,21 @@ ${this.pendingWorktreeNotice}
3935
4915
  return;
3936
4916
  }
3937
4917
  if (this.notificationQueue.length === 0) return;
4918
+ if (this.#nextNotificationQueueIndex() < 0) return;
3938
4919
  this.notificationProcessing = true;
3939
4920
  let resolveCompletion;
3940
- this.notificationCompletion = new Promise((resolve4) => {
3941
- resolveCompletion = resolve4;
4921
+ this.notificationCompletion = new Promise((resolve5) => {
4922
+ resolveCompletion = resolve5;
3942
4923
  });
3943
4924
  try {
3944
4925
  while (this.notificationQueue.length > 0) {
3945
4926
  if (this.pendingPrompt || this.cronProcessing || this.cronAbortController) {
3946
4927
  break;
3947
4928
  }
3948
- const item = this.notificationQueue.shift();
4929
+ const nextIndex = this.#nextNotificationQueueIndex();
4930
+ if (nextIndex < 0) break;
4931
+ const [item] = this.notificationQueue.splice(nextIndex, 1);
4932
+ if (!item) break;
3949
4933
  await sessionIdContext.run(
3950
4934
  this.config.getSessionId(),
3951
4935
  () => this.#executeBackgroundNotificationPromptInner(item)
@@ -3961,6 +4945,14 @@ ${this.pendingWorktreeNotice}
3961
4945
  }
3962
4946
  }
3963
4947
  }
4948
+ #nextNotificationQueueIndex() {
4949
+ if (this.notificationQueue.length === 0) return -1;
4950
+ if (this.todoStopGuardQueuedPromptPriority) return -1;
4951
+ if (!this.todoStopGuard.blocksUnrelatedAutomaticTurns) return 0;
4952
+ return this.notificationQueue.findIndex(
4953
+ (item) => this.#notificationContinuesTodoStopGuardWorkChain(item)
4954
+ );
4955
+ }
3964
4956
  async #executeBackgroundNotificationPromptInner(item) {
3965
4957
  return Storage.runWithRuntimeBaseDir(
3966
4958
  this.runtimeBaseDir,
@@ -3968,6 +4960,9 @@ ${this.pendingWorktreeNotice}
3968
4960
  async () => {
3969
4961
  const ac = new AbortController();
3970
4962
  this.notificationAbortController = ac;
4963
+ this.#prepareTodoStopGuardForAutomaticTurn(
4964
+ this.#notificationContinuesTodoStopGuardWorkChain(item)
4965
+ );
3971
4966
  const promptId = this.config.getSessionId() + "########notification" + Date.now();
3972
4967
  try {
3973
4968
  await this.#emitBackgroundNotificationDisplay(item);
@@ -3981,6 +4976,7 @@ ${this.pendingWorktreeNotice}
3981
4976
  const toolLoopState = createDaemonToolLoopState();
3982
4977
  while (nextMessage !== null) {
3983
4978
  if (ac.signal.aborted) {
4979
+ this.todoStopGuard.suspend();
3984
4980
  await this.#emitBackgroundNotificationEndTurn("cancelled");
3985
4981
  return;
3986
4982
  }
@@ -3997,6 +4993,7 @@ ${this.pendingWorktreeNotice}
3997
4993
  ac.signal
3998
4994
  );
3999
4995
  if (!sendResult.responseStream) {
4996
+ this.todoStopGuard.suspend();
4000
4997
  this.#preserveUnsentMessageHistory(
4001
4998
  nextMessage,
4002
4999
  sendResult.stopReason === "cancelled"
@@ -4015,6 +5012,7 @@ ${this.pendingWorktreeNotice}
4015
5012
  try {
4016
5013
  for await (const resp of responseStream) {
4017
5014
  if (ac.signal.aborted) {
5015
+ this.todoStopGuard.suspend();
4018
5016
  await this.#emitBackgroundNotificationEndTurn("cancelled");
4019
5017
  return;
4020
5018
  }
@@ -4094,15 +5092,18 @@ ${this.pendingWorktreeNotice}
4094
5092
  toolLoopState
4095
5093
  );
4096
5094
  if (toolRun.stopAfterPermissionCancel) {
5095
+ this.todoStopGuard.suspend();
4097
5096
  await this.#preserveStoppedToolRun(toolRun, ac.signal);
4098
5097
  await this.#emitBackgroundNotificationEndTurn("end_turn");
4099
5098
  return;
4100
5099
  }
4101
- nextMessage = await this.#buildNextMessageAfterToolRun(
5100
+ const nextAfterTools = await this.#buildNextMessageAfterToolRun(
4102
5101
  toolRun,
4103
5102
  ac.signal
4104
5103
  );
5104
+ nextMessage = nextAfterTools.message;
4105
5105
  if (toolRun.loopDetected) {
5106
+ this.todoStopGuard.suspend();
4106
5107
  await this.#preserveStoppedToolRun(toolRun, ac.signal);
4107
5108
  await this.#emitBackgroundNotificationEndTurn("end_turn");
4108
5109
  return;
@@ -4112,12 +5113,24 @@ ${this.pendingWorktreeNotice}
4112
5113
  if (this.messageRewriter) {
4113
5114
  await this.messageRewriter.waitForPendingRewrites();
4114
5115
  }
4115
- await this.#emitBackgroundNotificationEndTurn("end_turn");
5116
+ let stopReason = "end_turn";
5117
+ if (this.todoStopGuard.needsStopInspection) {
5118
+ stopReason = (await this.#handleStopHookLoop(
5119
+ ac,
5120
+ promptId,
5121
+ false,
5122
+ void 0,
5123
+ false
5124
+ )).stopReason;
5125
+ }
5126
+ await this.#emitBackgroundNotificationEndTurn(stopReason);
4116
5127
  } catch (error) {
4117
5128
  if (ac.signal.aborted) {
5129
+ this.todoStopGuard.suspend();
4118
5130
  await this.#emitBackgroundNotificationEndTurn("cancelled");
4119
5131
  return;
4120
5132
  }
5133
+ this.todoStopGuard.pauseForTrustedRetry();
4121
5134
  debugLogger6.error("Error processing background notification:", error);
4122
5135
  const msg = error instanceof Error ? error.message : String(error);
4123
5136
  try {
@@ -4267,6 +5280,9 @@ ${this.pendingWorktreeNotice}
4267
5280
  );
4268
5281
  }
4269
5282
  this.config.setApprovalMode(approvalMode);
5283
+ if (approvalMode === "plan" /* PLAN */) {
5284
+ this.clearTodoStopGuardTrust();
5285
+ }
4270
5286
  void this.client.extNotification("qwen/notify/session/mode-update", {
4271
5287
  v: 1,
4272
5288
  sessionId: this.sessionId,
@@ -4284,7 +5300,17 @@ ${this.pendingWorktreeNotice}
4284
5300
  if (!rawModelId) {
4285
5301
  throw RequestError.invalidParams(void 0, "modelId cannot be empty");
4286
5302
  }
4287
- const parsed = parseAcpModelOption(rawModelId);
5303
+ const resolvedRoute = resolveAcpModelOption(
5304
+ rawModelId,
5305
+ this.config.getAllConfiguredModels()
5306
+ );
5307
+ if (!resolvedRoute && rawModelId.startsWith(ACP_ROUTE_ID_PREFIX)) {
5308
+ throw RequestError.invalidParams(
5309
+ void 0,
5310
+ `Unknown or stale model route: "${rawModelId}"`
5311
+ );
5312
+ }
5313
+ const parsed = resolvedRoute ?? parseAcpModelOption(rawModelId);
4288
5314
  const previousAuthType = this.config.getAuthType?.();
4289
5315
  const selectedAuthType = parsed.authType ?? previousAuthType;
4290
5316
  if (!selectedAuthType) {
@@ -4293,29 +5319,49 @@ ${this.pendingWorktreeNotice}
4293
5319
  `authType cannot be determined for modelId "${parsed.modelId}"`
4294
5320
  );
4295
5321
  }
5322
+ const requireCachedCredentials = selectedAuthType !== previousAuthType && selectedAuthType === "qwen-oauth" /* QWEN_OAUTH */;
5323
+ const switchOptions = resolvedRoute?.baseUrl !== void 0 || requireCachedCredentials ? {
5324
+ ...resolvedRoute?.baseUrl !== void 0 ? { baseUrl: resolvedRoute.baseUrl } : {},
5325
+ ...requireCachedCredentials ? { requireCachedCredentials: true } : {}
5326
+ } : void 0;
4296
5327
  await this.config.switchModel(
4297
5328
  selectedAuthType,
4298
5329
  parsed.modelId,
4299
- selectedAuthType !== previousAuthType && selectedAuthType === "qwen-oauth" /* QWEN_OAUTH */ ? { requireCachedCredentials: true } : void 0
5330
+ switchOptions
4300
5331
  );
4301
5332
  const after = this.config.getContentGeneratorConfig?.();
4302
5333
  const effectiveAuthType = after?.authType ?? selectedAuthType;
4303
5334
  const effectiveModelId = after?.model ?? parsed.modelId;
5335
+ const activeRuntimeSnapshot = this.config.getActiveRuntimeModelSnapshot?.();
5336
+ const currentAcpModelId = getCurrentAcpModelId(
5337
+ buildAcpModelOptions(this.config.getAllConfiguredModels()),
5338
+ activeRuntimeSnapshot?.id ?? effectiveModelId,
5339
+ activeRuntimeSnapshot?.authType ?? effectiveAuthType,
5340
+ activeRuntimeSnapshot ? void 0 : resolvedRoute ? resolvedRoute.registryBaseUrl : this.config.getCurrentModelRegistryBaseUrl?.()
5341
+ );
4304
5342
  void this.client.extNotification("qwen/notify/session/model-update", {
4305
5343
  v: 1,
4306
5344
  sessionId: this.sessionId,
4307
- currentModelId: effectiveModelId
5345
+ currentModelId: currentAcpModelId
4308
5346
  }).catch((error) => {
4309
5347
  debugLogger6.debug("model-update extNotification failed", error);
4310
5348
  });
4311
5349
  if (options.persistDefault ?? true) {
4312
5350
  const persistScope = getPersistScopeForModelSelection(this.settings);
4313
- this.settings.setValue(persistScope, "model.name", parsed.modelId);
4314
- this.settings.setValue(persistScope, "model.baseUrl", "");
5351
+ this.settings.setValue(
5352
+ persistScope,
5353
+ "model.name",
5354
+ resolvedRoute?.isRuntime ? resolvedRoute.modelId : effectiveModelId
5355
+ );
5356
+ this.settings.setValue(
5357
+ persistScope,
5358
+ "model.baseUrl",
5359
+ resolvedRoute && !resolvedRoute.isRuntime ? resolvedRoute.baseUrl ?? "" : ""
5360
+ );
4315
5361
  this.settings.setValue(
4316
5362
  persistScope,
4317
5363
  "security.auth.selectedType",
4318
- selectedAuthType
5364
+ effectiveAuthType
4319
5365
  );
4320
5366
  }
4321
5367
  return {
@@ -4325,7 +5371,7 @@ ${this.pendingWorktreeNotice}
4325
5371
  modelId: effectiveModelId,
4326
5372
  baseUrl: after?.baseUrl ?? "(default)",
4327
5373
  apiKey: maskApiKeyForDisplay(after?.apiKey),
4328
- isRuntime: rawModelId.startsWith("$runtime|")
5374
+ isRuntime: resolvedRoute?.isRuntime ?? rawModelId.startsWith("$runtime|")
4329
5375
  }
4330
5376
  }
4331
5377
  };
@@ -4334,33 +5380,29 @@ ${this.pendingWorktreeNotice}
4334
5380
  * Sends a current_mode_update notification to the client.
4335
5381
  * Called after the agent switches modes (e.g., from exit_plan_mode tool).
4336
5382
  */
4337
- async sendCurrentModeUpdateNotification(outcome) {
4338
- let newModeId;
4339
- switch (outcome) {
4340
- case "proceed_always" /* ProceedAlways */:
4341
- newModeId = "auto-edit";
4342
- break;
4343
- case "restore_previous" /* RestorePrevious */:
4344
- newModeId = this.config.getApprovalMode();
4345
- break;
4346
- case "proceed_once" /* ProceedOnce */:
4347
- default:
4348
- newModeId = "default";
4349
- break;
4350
- }
5383
+ async sendCurrentModeUpdateNotification() {
5384
+ const newModeId = this.config.getApprovalMode();
4351
5385
  const update = {
4352
5386
  sessionUpdate: "current_mode_update",
4353
5387
  currentModeId: newModeId
4354
5388
  };
4355
- await this.sendUpdate(update);
4356
- void this.client.extNotification("qwen/notify/session/mode-update", {
4357
- v: 1,
4358
- sessionId: this.sessionId,
4359
- currentModeId: newModeId,
4360
- legacyFrameSent: true
4361
- }).catch((error) => {
5389
+ let legacyFrameSent = false;
5390
+ try {
5391
+ await this.sendUpdate(update);
5392
+ legacyFrameSent = true;
5393
+ } catch (error) {
5394
+ debugLogger6.debug("current_mode_update notification failed", error);
5395
+ }
5396
+ try {
5397
+ await this.client.extNotification("qwen/notify/session/mode-update", {
5398
+ v: 1,
5399
+ sessionId: this.sessionId,
5400
+ currentModeId: newModeId,
5401
+ legacyFrameSent
5402
+ });
5403
+ } catch (error) {
4362
5404
  debugLogger6.debug("mode-update extNotification failed", error);
4363
- });
5405
+ }
4364
5406
  }
4365
5407
  /**
4366
5408
  * Execute a batch of model-returned tool calls, running Agent calls
@@ -4914,6 +5956,7 @@ ${this.pendingWorktreeNotice}
4914
5956
  );
4915
5957
  }
4916
5958
  const isTodoWriteTool = tool.name === ToolNames.TODO_WRITE;
5959
+ const isTrustedTodoWriteTool = isTodoWriteTool && tool.kind === "think" /* Think */ && tool.constructor.name === "TodoWriteTool";
4917
5960
  const isAgentTool = tool.name === ToolNames.AGENT;
4918
5961
  const isExitPlanModeTool = tool.name === ToolNames.EXIT_PLAN_MODE;
4919
5962
  const isEnterPlanModeTool = tool.name === ToolNames.ENTER_PLAN_MODE;
@@ -4968,7 +6011,13 @@ ${this.pendingWorktreeNotice}
4968
6011
  toolName,
4969
6012
  toolParams
4970
6013
  );
4971
- const { finalPermission, pmForcedAsk, pmCtx, denyMessage } = flowResult;
6014
+ const {
6015
+ finalPermission,
6016
+ pmForcedAsk,
6017
+ pmCtx,
6018
+ denyMessage,
6019
+ requiresUserInteraction
6020
+ } = flowResult;
4972
6021
  const isPlanMode = approvalMode === "plan" /* PLAN */;
4973
6022
  if (finalPermission === "deny") {
4974
6023
  return earlyErrorResponse(
@@ -4993,7 +6042,7 @@ ${this.pendingWorktreeNotice}
4993
6042
  );
4994
6043
  }
4995
6044
  let wasAutoModeDenialFallback = false;
4996
- if (!autoModeAllowed && shouldRunAutoModeForCall(approvalMode, toolName)) {
6045
+ if (!autoModeAllowed && !requiresUserInteraction && shouldRunAutoModeForCall(approvalMode, toolName)) {
4997
6046
  const denialState = this.config.getAutoModeDenialState();
4998
6047
  const fallback = shouldFallback(denialState);
4999
6048
  const messages = this.config.getGeminiClient?.()?.getHistoryTail(MAX_TRANSCRIPT_MESSAGES, false) ?? [];
@@ -5066,7 +6115,12 @@ ${this.pendingWorktreeNotice}
5066
6115
  this.config.setAutoModeDenialState(after);
5067
6116
  }
5068
6117
  }, "recordAutoModeFallbackResolution");
5069
- if (!autoModeAllowed && needsConfirmation(confirmationPermission, approvalMode, toolName)) {
6118
+ if (!autoModeAllowed && needsConfirmation(
6119
+ confirmationPermission,
6120
+ approvalMode,
6121
+ toolName,
6122
+ requiresUserInteraction
6123
+ )) {
5070
6124
  confirmationDetails = await invocation.getConfirmationDetails(abortSignal);
5071
6125
  injectPermissionRulesIfMissing(confirmationDetails, pmCtx);
5072
6126
  if (isPlanModeBlocked(
@@ -5093,7 +6147,7 @@ ${this.pendingWorktreeNotice}
5093
6147
  args,
5094
6148
  String(approvalMode)
5095
6149
  );
5096
- if (hookResult.hasDecision) {
6150
+ if (hookResult.hasDecision && (!hookResult.shouldAllow || !requiresUserInteraction)) {
5097
6151
  hookHandled = true;
5098
6152
  if (hookResult.shouldAllow) {
5099
6153
  if (hookResult.updatedInput) {
@@ -5116,7 +6170,7 @@ ${this.pendingWorktreeNotice}
5116
6170
  }
5117
6171
  }
5118
6172
  }
5119
- if (approvalMode === "auto-edit" /* AUTO_EDIT */ && (confirmationDetails.type === "edit" || confirmationDetails.type === "info")) {
6173
+ if (!requiresUserInteraction && approvalMode === "auto-edit" /* AUTO_EDIT */ && (confirmationDetails.type === "edit" || confirmationDetails.type === "info")) {
5120
6174
  } else if (!hookHandled) {
5121
6175
  didRequestPermission = true;
5122
6176
  const content = buildPermissionRequestContent(confirmationDetails);
@@ -5184,12 +6238,11 @@ ${this.pendingWorktreeNotice}
5184
6238
  );
5185
6239
  }
5186
6240
  onStopAfterPermissionCancel?.();
6241
+ const permissionFailureMessage = isExitPlanModeTool ? "The host could not present plan-exit approval. Plan mode remains active; use the host mode selector or /plan exit to leave plan mode." : `Permission request failed for "${toolName}": ${this.#formatError(
6242
+ error
6243
+ )}`;
5187
6244
  return earlyErrorResponse(
5188
- new Error(
5189
- `Permission request failed for "${toolName}": ${this.#formatError(
5190
- error
5191
- )}`
5192
- ),
6245
+ new Error(permissionFailureMessage),
5193
6246
  toolName,
5194
6247
  { stopAfterPermissionCancel: true }
5195
6248
  );
@@ -5218,11 +6271,8 @@ ${this.pendingWorktreeNotice}
5218
6271
  { answers: output.answers }
5219
6272
  );
5220
6273
  }
5221
- if (isExitPlanModeTool && outcome !== "cancel" /* Cancel */) {
5222
- await this.sendCurrentModeUpdateNotification(outcome);
5223
- }
5224
6274
  if (confirmationDetails.type === "edit" && outcome === "proceed_always" /* ProceedAlways */) {
5225
- await this.sendCurrentModeUpdateNotification(outcome);
6275
+ await this.sendCurrentModeUpdateNotification();
5226
6276
  }
5227
6277
  switch (outcome) {
5228
6278
  case "cancel" /* Cancel */:
@@ -5342,11 +6392,11 @@ ${this.pendingWorktreeNotice}
5342
6392
  throw execError;
5343
6393
  }
5344
6394
  cleanupAgentToolResources();
5345
- if ((isEnterPlanModeTool || isExitPlanModeTool) && !didRequestPermission && !toolResult.error && this.config.getApprovalMode() !== approvalMode) {
5346
- await this.sendUpdate({
5347
- sessionUpdate: "current_mode_update",
5348
- currentModeId: this.config.getApprovalMode()
5349
- });
6395
+ if ((isEnterPlanModeTool || isExitPlanModeTool) && !toolResult.error && this.config.getApprovalMode() !== approvalMode) {
6396
+ await this.sendCurrentModeUpdateNotification();
6397
+ if (this.config.getApprovalMode() === "plan" /* PLAN */) {
6398
+ this.#clearTodoStopGuardTrustAndDrainAutomaticQueues();
6399
+ }
5350
6400
  }
5351
6401
  const responseParts = toolResult.error ? convertToFunctionErrorResponse(
5352
6402
  toolName,
@@ -5361,6 +6411,13 @@ ${this.pendingWorktreeNotice}
5361
6411
  const status = aborted ? "cancelled" : toolResult.error ? "error" : "success";
5362
6412
  const succeeded = status === "success";
5363
6413
  const responseError = toolResult.error ? new Error(toolResult.error.message) : aborted ? new Error("Tool execution was cancelled") : void 0;
6414
+ if (isTrustedTodoWriteTool && !toolResult.error) {
6415
+ this.todoStopGuard.observeTodoWrite(
6416
+ toolResult.returnDisplay,
6417
+ this.config.getApprovalMode() !== "plan" /* PLAN */
6418
+ );
6419
+ if (aborted) this.todoStopGuard.suspend();
6420
+ }
5364
6421
  if (hooksEnabledForTool && messageBusForTool && !toolResult.error && !aborted && !nestedPermissionCancelled) {
5365
6422
  const toolResponse = {
5366
6423
  llmContent: toolResult.llmContent,
@@ -5381,6 +6438,7 @@ ${this.pendingWorktreeNotice}
5381
6438
  debugLogger6.info(
5382
6439
  `PostToolUse hook requested stop for ${toolName}: ${stopMessage}`
5383
6440
  );
6441
+ this.todoStopGuard.suspend();
5384
6442
  return earlyErrorResponse(new Error(stopMessage), toolName);
5385
6443
  }
5386
6444
  if (postHookResult.additionalContext) {
@@ -5566,7 +6624,7 @@ ${this.pendingWorktreeNotice}
5566
6624
  }
5567
6625
  }
5568
6626
  if (hasActiveGoalStatus) {
5569
- this.#installGoalTerminalObserver();
6627
+ this.installGoalTerminalObserver();
5570
6628
  }
5571
6629
  }
5572
6630
  /**
@@ -5585,11 +6643,15 @@ ${this.pendingWorktreeNotice}
5585
6643
  * @param originalPrompt The original prompt blocks
5586
6644
  * @returns Parts to use for the prompt, or null if command was handled without needing model interaction
5587
6645
  */
5588
- async #processSlashCommandResult(result, originalPrompt) {
6646
+ async #processSlashCommandResult(result, originalPrompt, abortSignal, onFullTurnModel) {
5589
6647
  this.#emitGoalStatusItems(result);
5590
6648
  switch (result.type) {
5591
6649
  case "submit_prompt":
5592
- return normalizePartList(result.content);
6650
+ return this.#applyBridgeConversionsIfNeeded(
6651
+ normalizePartList(result.content),
6652
+ abortSignal,
6653
+ onFullTurnModel
6654
+ );
5593
6655
  case "message": {
5594
6656
  if (result.messageType === "error") {
5595
6657
  throw new Error(result.content || "Slash command failed.");
@@ -5632,11 +6694,10 @@ ${this.pendingWorktreeNotice}
5632
6694
  throw new Error(unsupportedError);
5633
6695
  }
5634
6696
  case "no_command":
5635
- return this.#resolvePrompt(
5636
- originalPrompt,
5637
- new AbortController().signal,
5638
- { promptLast: true }
5639
- );
6697
+ return this.#resolvePrompt(originalPrompt, abortSignal, {
6698
+ promptLast: true,
6699
+ onFullTurnModel
6700
+ });
5640
6701
  default: {
5641
6702
  const _exhaustive = result;
5642
6703
  const unknownError = `Unknown slash command result type: ${_exhaustive.type}`;
@@ -5649,6 +6710,7 @@ ${this.pendingWorktreeNotice}
5649
6710
  const embeddedContext = [];
5650
6711
  const extensionMentions = /* @__PURE__ */ new Map();
5651
6712
  const mcpServerMentions = /* @__PURE__ */ new Map();
6713
+ const textPathSpecsToRead = /* @__PURE__ */ new Set();
5652
6714
  const preserveUnsupportedImageForBridge = shouldRunVisionBridge(
5653
6715
  this.config
5654
6716
  );
@@ -5657,6 +6719,16 @@ ${this.pendingWorktreeNotice}
5657
6719
  case "text":
5658
6720
  collectExtensionMentionRefs(part.text, extensionMentions);
5659
6721
  collectMcpServerMentionRefs(part.text, mcpServerMentions);
6722
+ for (const pathSpec of extractAtPathCommands(part.text)) {
6723
+ const resolved = path3.resolve(
6724
+ this.config.getProjectRoot(),
6725
+ pathSpec
6726
+ );
6727
+ const filteringOptions = this.config.getFileFilteringOptions();
6728
+ if (path3.isAbsolute(pathSpec) && getSpecificMimeType(resolved)?.startsWith("image/") && isExistingFile(resolved) && this.config.getWorkspaceContext().isPathWithinWorkspace(pathSpec) && !this.config.getFileService().shouldIgnoreFile(pathSpec, filteringOptions)) {
6729
+ textPathSpecsToRead.add(pathSpec);
6730
+ }
6731
+ }
5660
6732
  return { text: part.text };
5661
6733
  case "image":
5662
6734
  if (preserveUnsupportedImageForBridge) {
@@ -5704,23 +6776,31 @@ ${this.pendingWorktreeNotice}
5704
6776
  }
5705
6777
  });
5706
6778
  const atPathCommandParts = parts.filter((part) => "fileData" in part);
6779
+ const pathSpecsToRead = [
6780
+ .../* @__PURE__ */ new Set([
6781
+ ...textPathSpecsToRead,
6782
+ ...atPathCommandParts.map((part) => part.fileData.fileUri)
6783
+ ])
6784
+ ];
5707
6785
  const extensionParts = await this.#resolveExtensionMentionParts(
5708
6786
  extensionMentions,
5709
6787
  abortSignal
5710
6788
  );
5711
6789
  const mcpServerParts = this.#resolveMcpServerMentionParts(mcpServerMentions);
5712
- if (atPathCommandParts.length === 0 && embeddedContext.length === 0 && extensionParts.length === 0 && mcpServerParts.length === 0) {
5713
- return this.#applyBridgeConversionsIfNeeded(parts, abortSignal);
6790
+ if (pathSpecsToRead.length === 0 && embeddedContext.length === 0 && extensionParts.length === 0 && mcpServerParts.length === 0) {
6791
+ return this.#applyBridgeConversionsIfNeeded(
6792
+ parts,
6793
+ abortSignal,
6794
+ options.onFullTurnModel
6795
+ );
5714
6796
  }
5715
- if (atPathCommandParts.length === 0 && embeddedContext.length === 0) {
6797
+ if (pathSpecsToRead.length === 0 && embeddedContext.length === 0) {
5716
6798
  return this.#applyBridgeConversionsIfNeeded(
5717
6799
  [...parts, ...extensionParts, ...mcpServerParts],
5718
- abortSignal
6800
+ abortSignal,
6801
+ options.onFullTurnModel
5719
6802
  );
5720
6803
  }
5721
- const pathSpecsToRead = atPathCommandParts.map(
5722
- (part) => part.fileData.fileUri
5723
- );
5724
6804
  let initialQueryText = "";
5725
6805
  for (let i = 0; i < parts.length; i++) {
5726
6806
  const chunk = parts[i];
@@ -5776,10 +6856,11 @@ ${contextPart.text}`
5776
6856
  const processedQueryParts = options.promptLast ? [...referenceParts, promptPart] : [promptPart, ...referenceParts];
5777
6857
  return this.#applyBridgeConversionsIfNeeded(
5778
6858
  processedQueryParts,
5779
- abortSignal
6859
+ abortSignal,
6860
+ options.onFullTurnModel
5780
6861
  );
5781
6862
  }
5782
- async #applyBridgeConversionsIfNeeded(originalParts, abortSignal) {
6863
+ async #applyBridgeConversionsIfNeeded(originalParts, abortSignal, onFullTurnModel) {
5783
6864
  const parts = await this.#applyVoiceBridgeIfNeeded(
5784
6865
  originalParts,
5785
6866
  abortSignal
@@ -5787,6 +6868,28 @@ ${contextPart.text}`
5787
6868
  if (!hasImageParts(parts) || !shouldRunVisionBridge(this.config)) {
5788
6869
  return parts;
5789
6870
  }
6871
+ const fullTurnModel = this.config.getDefaultVisionBridgeModel();
6872
+ if (onFullTurnModel && fullTurnModel?.agentCapable) {
6873
+ const fullTurnParts = parts.map((part) => clampInlineMediaPart(part));
6874
+ if (!hasImageParts(fullTurnParts)) {
6875
+ return fullTurnParts;
6876
+ }
6877
+ const selected = onFullTurnModel(
6878
+ getFullTurnVisionModelSelector(fullTurnModel)
6879
+ );
6880
+ if (selected) {
6881
+ try {
6882
+ await this.messageEmitter.emitAgentMessage(
6883
+ formatFullTurnVisionNotice(fullTurnModel)
6884
+ );
6885
+ } catch (error) {
6886
+ debugLogger6.debug(
6887
+ `full-turn vision: failed to emit notice; continuing error=${error instanceof Error ? error.message : String(error)}`
6888
+ );
6889
+ }
6890
+ }
6891
+ return fullTurnParts;
6892
+ }
5790
6893
  let bridgeResult;
5791
6894
  try {
5792
6895
  debugLogger6.debug("vision bridge: gate matched, running conversion");
@@ -6126,6 +7229,56 @@ var POSIX_TMP_LOCAL_READ_ROOT = "/tmp";
6126
7229
  var BTW_CHILD_TIMEOUT_MS = 55e3;
6127
7230
  var MCP_OAUTH_START_TIMEOUT_MS = 3e4;
6128
7231
  var WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS = 295e3;
7232
+ function createAcpSessionStartProfiler(span) {
7233
+ let failedStage;
7234
+ const setAttribute = /* @__PURE__ */ __name((name, value) => {
7235
+ try {
7236
+ span?.setAttribute(name, value);
7237
+ } catch {
7238
+ }
7239
+ }, "setAttribute");
7240
+ const recordStage = /* @__PURE__ */ __name((stage, start) => {
7241
+ const durationMs = Math.round((performance.now() - start) * 100) / 100;
7242
+ if (Number.isFinite(durationMs) && durationMs >= 0) {
7243
+ setAttribute(`qwen-code.daemon.session_start.${stage}_ms`, durationMs);
7244
+ }
7245
+ }, "recordStage");
7246
+ const recordFailure = /* @__PURE__ */ __name((stage) => {
7247
+ if (failedStage !== void 0) return;
7248
+ failedStage = stage;
7249
+ setAttribute("qwen-code.daemon.session_start.failed_stage", stage);
7250
+ }, "recordFailure");
7251
+ return {
7252
+ async time(stage, fn) {
7253
+ if (!span) return await fn();
7254
+ const start = performance.now();
7255
+ try {
7256
+ return await fn();
7257
+ } catch (error) {
7258
+ recordFailure(stage);
7259
+ throw error;
7260
+ } finally {
7261
+ recordStage(stage, start);
7262
+ }
7263
+ },
7264
+ timeSync(stage, fn) {
7265
+ if (!span) return fn();
7266
+ const start = performance.now();
7267
+ try {
7268
+ return fn();
7269
+ } catch (error) {
7270
+ recordFailure(stage);
7271
+ throw error;
7272
+ } finally {
7273
+ recordStage(stage, start);
7274
+ }
7275
+ },
7276
+ setSessionId(sessionId) {
7277
+ setAttribute("session.id", sessionId);
7278
+ }
7279
+ };
7280
+ }
7281
+ __name(createAcpSessionStartProfiler, "createAcpSessionStartProfiler");
6129
7282
  function workspaceMemoryErrorData(code, diagnostics) {
6130
7283
  return {
6131
7284
  errorKind: code,
@@ -6206,6 +7359,40 @@ function isBulkLoadReplayRequest(params) {
6206
7359
  return meta?.[LOAD_REPLAY_MODE_META_KEY] === LOAD_REPLAY_BULK_MODE;
6207
7360
  }
6208
7361
  __name(isBulkLoadReplayRequest, "isBulkLoadReplayRequest");
7362
+ function getLoadReplayPageSize(params) {
7363
+ const meta = isObjectRecord(params._meta) ? params._meta : void 0;
7364
+ const value = meta?.[LOAD_REPLAY_PAGE_SIZE_META_KEY];
7365
+ if (value === void 0) return void 0;
7366
+ if (!Number.isSafeInteger(value) || value < 1 || value > SESSION_TRANSCRIPT_MAX_LIMIT) {
7367
+ throw RequestError.invalidParams(
7368
+ void 0,
7369
+ `Invalid load replay page size; expected 1..${SESSION_TRANSCRIPT_MAX_LIMIT}`
7370
+ );
7371
+ }
7372
+ return value;
7373
+ }
7374
+ __name(getLoadReplayPageSize, "getLoadReplayPageSize");
7375
+ function isHistoryTurnStart(record) {
7376
+ return record.type === "user" && record.subtype !== "mid_turn_user_message";
7377
+ }
7378
+ __name(isHistoryTurnStart, "isHistoryTurnStart");
7379
+ function selectRecentHistoryRecords(records, pageSize) {
7380
+ if (pageSize === void 0 || records.length <= pageSize) {
7381
+ return { records, hasMore: false };
7382
+ }
7383
+ let start = records.length - pageSize;
7384
+ for (let i = start; i < records.length; i++) {
7385
+ if (isHistoryTurnStart(records[i])) {
7386
+ start = i;
7387
+ break;
7388
+ }
7389
+ }
7390
+ while (start > 0 && !isHistoryTurnStart(records[start])) {
7391
+ start--;
7392
+ }
7393
+ return { records: records.slice(start), hasMore: start > 0 };
7394
+ }
7395
+ __name(selectRecentHistoryRecords, "selectRecentHistoryRecords");
6209
7396
  function createHiddenWorkspaceMemoryConfig(config) {
6210
7397
  return new Proxy(config, {
6211
7398
  get(target, prop) {
@@ -7604,16 +8791,21 @@ __name(deliverClientMcpMessage, "deliverClientMcpMessage");
7604
8791
  async function runAcpAgent(config, settings, argv) {
7605
8792
  let acpConnection;
7606
8793
  const bootstrapClientMcpSender = /* @__PURE__ */ __name((serverName, message) => deliverClientMcpMessage(acpConnection, serverName, message), "bootstrapClientMcpSender");
7607
- await config.initialize({
7608
- skipGeminiInitialization: true,
7609
- // Bootstrap skips MCP discovery — each session runs its own
7610
- // pool-routed discovery, so bootstrap-level spawns would be
7611
- // redundant subprocess leaks (W119).
7612
- skipMcpDiscovery: true,
7613
- // Bind the workspace-level manager's SDK callback so a runtime-added
7614
- // client-hosted MCP server (#5626) round-trips over the parent WS.
7615
- sendSdkMcpMessage: bootstrapClientMcpSender
7616
- });
8794
+ beginAcpBootstrapConfigProfiling();
8795
+ try {
8796
+ await config.initialize({
8797
+ skipGeminiInitialization: true,
8798
+ // Bootstrap skips MCP discovery — each session runs its own
8799
+ // pool-routed discovery, so bootstrap-level spawns would be
8800
+ // redundant subprocess leaks (W119).
8801
+ skipMcpDiscovery: true,
8802
+ // Bind the workspace-level manager's SDK callback so a runtime-added
8803
+ // client-hosted MCP server (#5626) round-trips over the parent WS.
8804
+ sendSdkMcpMessage: bootstrapClientMcpSender
8805
+ });
8806
+ } finally {
8807
+ endAcpBootstrapConfigProfiling();
8808
+ }
7617
8809
  const eventLoopMonitor = startEventLoopLagMonitor({
7618
8810
  onNewMaxStall: /* @__PURE__ */ __name((maxMs) => {
7619
8811
  console.error(`[perf] acp agent event loop stall: max=${maxMs}ms`);
@@ -7621,6 +8813,7 @@ async function runAcpAgent(config, settings, argv) {
7621
8813
  });
7622
8814
  let agentInstance;
7623
8815
  let connection;
8816
+ markAcpStartup("transportSetupStart");
7624
8817
  try {
7625
8818
  const stdout = Writable.toWeb(process.stdout);
7626
8819
  const stdin = Readable.toWeb(process.stdin);
@@ -7633,6 +8826,7 @@ async function runAcpAgent(config, settings, argv) {
7633
8826
  agentInstance = new QwenAgent(config, settings, argv, conn);
7634
8827
  return agentInstance;
7635
8828
  }, stream);
8829
+ markAcpStartup("transportSetupEnd");
7636
8830
  } catch (err) {
7637
8831
  eventLoopMonitor.dispose();
7638
8832
  throw err;
@@ -8212,10 +9406,11 @@ var QwenAgent = class {
8212
9406
  }
8213
9407
  }
8214
9408
  async initialize(args) {
9409
+ markAcpStartup("initializeHandlerStart");
8215
9410
  this.clientCapabilities = args.clientCapabilities;
8216
9411
  const authMethods = buildAuthMethods();
8217
- const version = "0.19.11";
8218
- return {
9412
+ const version = "0.19.12-preview.0";
9413
+ const response = {
8219
9414
  protocolVersion: PROTOCOL_VERSION,
8220
9415
  agentInfo: {
8221
9416
  name: "qwen-code",
@@ -8243,6 +9438,22 @@ var QwenAgent = class {
8243
9438
  }
8244
9439
  }
8245
9440
  };
9441
+ markAcpStartup("initializeHandlerEnd");
9442
+ markAcpStartup("responseBuilt");
9443
+ let startupProfile;
9444
+ try {
9445
+ startupProfile = buildAndFreezeAcpStartupProfile();
9446
+ } catch {
9447
+ startupProfile = void 0;
9448
+ }
9449
+ const requestedProfile = args._meta?.[CHANNEL_STARTUP_PROFILE_META_KEY];
9450
+ const profileRequested = requestedProfile !== null && typeof requestedProfile === "object" && !Array.isArray(requestedProfile) && requestedProfile["v"] === CHANNEL_STARTUP_PROFILE_VERSION;
9451
+ return profileRequested && startupProfile ? {
9452
+ ...response,
9453
+ _meta: {
9454
+ [CHANNEL_STARTUP_PROFILE_META_KEY]: startupProfile
9455
+ }
9456
+ } : response;
8246
9457
  }
8247
9458
  async authenticate({ methodId }) {
8248
9459
  const method = external_exports.nativeEnum(AuthType).parse(methodId);
@@ -8270,25 +9481,42 @@ var QwenAgent = class {
8270
9481
  }
8271
9482
  }
8272
9483
  }
8273
- async newSession({
8274
- cwd,
8275
- mcpServers
8276
- }) {
8277
- const settings = loadSettingsCached(cwd);
8278
- this.settings = settings;
8279
- const config = await this.newSessionConfig(cwd, mcpServers, settings);
8280
- await this.ensureAuthenticated(config);
8281
- this.setupFileSystem(config);
8282
- const session = await this.createAndStoreSession(config, settings);
8283
- const availableModels = this.buildAvailableModels(config);
8284
- const modesData = this.buildModesData(config);
8285
- const configOptions = this.buildConfigOptions(config);
8286
- return {
8287
- sessionId: session.getId(),
8288
- models: availableModels,
8289
- modes: modesData,
8290
- configOptions
8291
- };
9484
+ async newSession(params) {
9485
+ const { cwd, mcpServers } = params;
9486
+ const parentContext = extractDaemonTraceContext(params);
9487
+ return await withDaemonSpan(
9488
+ "qwen-code.daemon.session_start",
9489
+ { "qwen-code.daemon.operation": "acp_session_new" },
9490
+ async (span) => {
9491
+ const profiler = createAcpSessionStartProfiler(span);
9492
+ const settings = profiler.timeSync(
9493
+ "settings_load",
9494
+ () => loadSettingsCached(cwd)
9495
+ );
9496
+ this.settings = settings;
9497
+ const config = await profiler.time(
9498
+ "config_setup",
9499
+ () => this.newSessionConfig(cwd, mcpServers, settings)
9500
+ );
9501
+ await profiler.time("auth", () => this.ensureAuthenticated(config));
9502
+ profiler.timeSync(
9503
+ "file_system_setup",
9504
+ () => this.setupFileSystem(config)
9505
+ );
9506
+ const session = await profiler.time(
9507
+ "session_register",
9508
+ () => this.createAndStoreSession(config, settings)
9509
+ );
9510
+ profiler.setSessionId(session.getId());
9511
+ return profiler.timeSync("response_build", () => ({
9512
+ sessionId: session.getId(),
9513
+ models: this.buildAvailableModels(config),
9514
+ modes: this.buildModesData(config),
9515
+ configOptions: this.buildConfigOptions(config)
9516
+ }));
9517
+ },
9518
+ parentContext ? { parentContext } : {}
9519
+ );
8292
9520
  }
8293
9521
  async loadSession(params) {
8294
9522
  const settings = loadSettingsCached(params.cwd);
@@ -8319,6 +9547,7 @@ var QwenAgent = class {
8319
9547
  this.setupFileSystem(config);
8320
9548
  const sessionData = config.getResumedSessionData();
8321
9549
  const bulkReplay = isBulkLoadReplayRequest(params);
9550
+ const replayPageSize = bulkReplay ? getLoadReplayPageSize(params) : void 0;
8322
9551
  const session = await this.createAndStoreSession(
8323
9552
  config,
8324
9553
  settings,
@@ -8332,13 +9561,19 @@ var QwenAgent = class {
8332
9561
  let replayUpdates = [];
8333
9562
  if (records) {
8334
9563
  session.primeTurnFromHistory(records);
9564
+ const replayPage = selectRecentHistoryRecords(
9565
+ records,
9566
+ replayPageSize
9567
+ );
8335
9568
  const replayUsage = createReplayCumulativeUsage();
8336
9569
  const replay = await collectHistoryReplayUpdates({
8337
9570
  sessionId: params.sessionId,
8338
9571
  config,
8339
- records,
9572
+ records: replayPage.records,
8340
9573
  gaps: sessionData?.historyGaps,
8341
9574
  cumulativeUsage: replayUsage,
9575
+ // A resume: the goal restore runs right after this.
9576
+ supersedeUnrestorableGoal: true,
8342
9577
  logger: debugLogger7
8343
9578
  });
8344
9579
  replayUpdates = replay.updates;
@@ -8348,9 +9583,15 @@ var QwenAgent = class {
8348
9583
  v: LOAD_REPLAY_VERSION,
8349
9584
  updates: replayUpdates,
8350
9585
  partial: true,
8351
- replayError: replay.replayError
9586
+ replayError: replay.replayError,
9587
+ ...replayPage.hasMore ? { hasMore: true } : {}
8352
9588
  };
8353
9589
  }
9590
+ replayEnvelope ??= {
9591
+ v: LOAD_REPLAY_VERSION,
9592
+ updates: replayUpdates,
9593
+ ...replayPage.hasMore ? { hasMore: true } : {}
9594
+ };
8354
9595
  }
8355
9596
  replayEnvelope ??= {
8356
9597
  v: LOAD_REPLAY_VERSION,
@@ -8364,6 +9605,7 @@ var QwenAgent = class {
8364
9605
  }
8365
9606
  }
8366
9607
  await this.#restoreWorktreeOnResume(config, session);
9608
+ this.#restoreGoalOnResume(config, session);
8367
9609
  const modesData = this.buildModesData(config);
8368
9610
  const availableModels = this.buildAvailableModels(config);
8369
9611
  const configOptions = this.buildConfigOptions(config);
@@ -8413,6 +9655,7 @@ var QwenAgent = class {
8413
9655
  { replayHistory: false }
8414
9656
  );
8415
9657
  await this.#restoreWorktreeOnResume(config, session);
9658
+ this.#restoreGoalOnResume(config, session);
8416
9659
  const modesData = this.buildModesData(config);
8417
9660
  const availableModels = this.buildAvailableModels(config);
8418
9661
  const configOptions = this.buildConfigOptions(config);
@@ -8441,6 +9684,48 @@ var QwenAgent = class {
8441
9684
  debugLogger7.warn(`ACP worktree restore failed: ${error}`);
8442
9685
  }
8443
9686
  }
9687
+ /**
9688
+ * Re-registers the `/goal` Stop hook when a resumed transcript ends on an
9689
+ * unsatisfied goal — the daemon counterpart of the TUI's resume restore.
9690
+ * Without this the goal loop silently dies whenever a session is reloaded or
9691
+ * `qwen serve` restarts, even though the transcript still shows it as active.
9692
+ *
9693
+ * The `addItem` bridge that `restoreGoalFromHistory` takes in the TUI is not
9694
+ * used here — the daemon's terminal card goes out over the wire, not into an
9695
+ * Ink history. But restore reaches `unregisterGoalHook` on every path,
9696
+ * including the one where there was nothing to restore, and that clears the
9697
+ * observer the `Session` constructor installed. So it is put back afterwards,
9698
+ * unconditionally: without it a restored goal that later achieves or fails
9699
+ * emits no terminal update and persists no terminal card, and the next reload
9700
+ * revives a goal that already finished.
9701
+ *
9702
+ * Best-effort: a failed restore must not block session load.
9703
+ */
9704
+ #restoreGoalOnResume(config, session) {
9705
+ try {
9706
+ const records = config.getResumedSessionData()?.conversation.messages;
9707
+ if (!records?.length) return;
9708
+ const restored = restoreGoalFromHistory(
9709
+ collectGoalStatusItemsFromRecords(records),
9710
+ config
9711
+ );
9712
+ if (restored.restored) {
9713
+ debugLogger7.info(
9714
+ `ACP goal restored sessionId=${config.getSessionId()} condition=${restored.condition}`
9715
+ );
9716
+ } else if (restored.blockedBy && restored.blockedBy !== "condition-invalid") {
9717
+ writeStderrLineSafe(
9718
+ `qwen: not restoring the active goal for session ${config.getSessionId()} (${restored.blockedBy}).`
9719
+ );
9720
+ }
9721
+ } catch (error) {
9722
+ writeStderrLineSafe(
9723
+ `qwen: goal restore failed for session ${config.getSessionId()}: ${error}`
9724
+ );
9725
+ } finally {
9726
+ session.installGoalTerminalObserver();
9727
+ }
9728
+ }
8444
9729
  async unstable_listSessions(params) {
8445
9730
  const cwd = params.cwd || process.cwd();
8446
9731
  const numericCursor = parseAcpSessionListCursor(params.cursor);
@@ -9298,9 +10583,18 @@ var QwenAgent = class {
9298
10583
  const currentModelId = activeRuntimeSnapshot ? activeRuntimeSnapshot.id : (config.getModel() || "").trim();
9299
10584
  const hasCurrentModel = currentModelId.length > 0;
9300
10585
  const currentAuth = activeRuntimeSnapshot?.authType ?? currentAuthType;
9301
- const currentAcpModelId = hasCurrentModel && currentAuth ? formatAcpModelId(currentModelId, currentAuth) : currentModelId || void 0;
10586
+ const modelOptions = buildAcpModelOptions(
10587
+ config.getAllConfiguredModels()
10588
+ );
10589
+ const currentAcpModelId = hasCurrentModel ? getCurrentAcpModelId(
10590
+ modelOptions,
10591
+ currentModelId,
10592
+ currentAuth,
10593
+ activeRuntimeSnapshot ? void 0 : config.getCurrentModelRegistryBaseUrl?.()
10594
+ ) : void 0;
9302
10595
  const providers = /* @__PURE__ */ new Map();
9303
- for (const model of config.getAllConfiguredModels().filter(isMainSelectableModel)) {
10596
+ for (const option of modelOptions) {
10597
+ const { model, effectiveModelId, modelId } = option;
9304
10598
  const authType = String(model.authType);
9305
10599
  let provider = providers.get(authType);
9306
10600
  if (!provider) {
@@ -9313,9 +10607,7 @@ var QwenAgent = class {
9313
10607
  };
9314
10608
  providers.set(authType, provider);
9315
10609
  }
9316
- const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
9317
- const modelId = formatAcpModelId(effectiveModelId, model.authType);
9318
- const isCurrent = currentAuth === model.authType && hasCurrentModel && (currentModelId === effectiveModelId || currentModelId === model.id || currentAcpModelId === modelId);
10610
+ const isCurrent = currentAuth === model.authType && currentAcpModelId === modelId;
9319
10611
  const providerModel = {
9320
10612
  modelId,
9321
10613
  baseModelId: parseAcpBaseModelId(effectiveModelId),
@@ -10279,6 +11571,25 @@ var QwenAgent = class {
10279
11571
  const cwd = requestedCwd || process.cwd();
10280
11572
  const SESSION_ID_RE = /^[0-9a-fA-F-]{32,36}$/;
10281
11573
  switch (method) {
11574
+ case TODO_STOP_GUARD_QUEUE_RELEASE_METHOD: {
11575
+ const sessionId = params["sessionId"];
11576
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
11577
+ throw RequestError.invalidParams(
11578
+ void 0,
11579
+ "Invalid or missing sessionId"
11580
+ );
11581
+ }
11582
+ const session = this.sessions.get(sessionId);
11583
+ if (!session) {
11584
+ throw RequestError.invalidParams(
11585
+ void 0,
11586
+ `Session not found for id: ${sessionId}`
11587
+ );
11588
+ }
11589
+ return {
11590
+ released: session.releaseTodoStopGuardQueuedPromptWait()
11591
+ };
11592
+ }
10282
11593
  case "qwen/providers/list": {
10283
11594
  return {
10284
11595
  providers: ALL_PROVIDERS.map(
@@ -10516,6 +11827,19 @@ var QwenAgent = class {
10516
11827
  "Invalid transcript cursor"
10517
11828
  );
10518
11829
  }
11830
+ const rawBeforeRecordId = params["beforeRecordId"];
11831
+ if (rawBeforeRecordId !== void 0 && (typeof rawBeforeRecordId !== "string" || rawBeforeRecordId.length === 0)) {
11832
+ throw RequestError.invalidParams(
11833
+ void 0,
11834
+ "Invalid transcript record boundary"
11835
+ );
11836
+ }
11837
+ if (rawCursor !== void 0 && rawBeforeRecordId !== void 0) {
11838
+ throw RequestError.invalidParams(
11839
+ void 0,
11840
+ "Transcript cursor and record boundary are mutually exclusive"
11841
+ );
11842
+ }
10519
11843
  const rawLimit = params["limit"];
10520
11844
  if (rawLimit !== void 0 && (!Number.isSafeInteger(rawLimit) || rawLimit < 1 || rawLimit > SESSION_TRANSCRIPT_MAX_LIMIT)) {
10521
11845
  throw RequestError.invalidParams(
@@ -10529,7 +11853,9 @@ var QwenAgent = class {
10529
11853
  const reader = new SessionTranscriptReader(cwd);
10530
11854
  const page = await reader.readPage(sessionId, {
10531
11855
  ...typeof rawCursor === "string" ? { cursor: rawCursor } : {},
10532
- ...typeof rawLimit === "number" ? { limit: rawLimit } : {}
11856
+ ...typeof rawBeforeRecordId === "string" ? { beforeRecordId: rawBeforeRecordId } : {},
11857
+ ...typeof rawLimit === "number" ? { limit: rawLimit } : {},
11858
+ maxBytes: SESSION_TRANSCRIPT_MAX_PAGE_BYTES
10533
11859
  });
10534
11860
  const config = await this.getTranscriptReplayConfig(cwd, settings);
10535
11861
  const replay = await replayTranscriptRecordPage({
@@ -10578,6 +11904,14 @@ var QwenAgent = class {
10578
11904
  maxBytes: error.maxBytes
10579
11905
  });
10580
11906
  }
11907
+ if (error instanceof SessionTranscriptPageTooLargeError) {
11908
+ throw new RequestError(-32012, error.message, {
11909
+ errorKind: "transcript_page_too_large",
11910
+ sessionId,
11911
+ pageBytes: error.pageBytes,
11912
+ maxBytes: error.maxBytes
11913
+ });
11914
+ }
10581
11915
  if (error.code === "ENOENT") {
10582
11916
  if (typeof rawCursor === "string") {
10583
11917
  throw new RequestError(
@@ -11222,10 +12556,10 @@ var QwenAgent = class {
11222
12556
  let resolveStarted;
11223
12557
  let rejectStarted;
11224
12558
  let startedTimer;
11225
- const started = new Promise((resolve4, reject) => {
12559
+ const started = new Promise((resolve5, reject) => {
11226
12560
  resolveStarted = /* @__PURE__ */ __name((value) => {
11227
12561
  clearTimeout(startedTimer);
11228
- resolve4(value);
12562
+ resolve5(value);
11229
12563
  }, "resolveStarted");
11230
12564
  rejectStarted = /* @__PURE__ */ __name((reason) => {
11231
12565
  clearTimeout(startedTimer);
@@ -11543,6 +12877,7 @@ var QwenAgent = class {
11543
12877
  `Model context refresh failed: ${error instanceof Error ? error.message : String(error)}`
11544
12878
  );
11545
12879
  }
12880
+ session.clearTodoStopGuardTrust();
11546
12881
  return { previousCwd, newCwd: canonicalPath, warnings };
11547
12882
  }
11548
12883
  case SERVE_CONTROL_EXT_METHODS.sessionApprovalMode: {
@@ -11574,6 +12909,9 @@ var QwenAgent = class {
11574
12909
  throw err;
11575
12910
  }
11576
12911
  const current = config.getApprovalMode();
12912
+ if (current === "plan") {
12913
+ session.clearTodoStopGuardTrust();
12914
+ }
11577
12915
  return { previous, current };
11578
12916
  }
11579
12917
  case SERVE_CONTROL_EXT_METHODS.sessionLanguage: {
@@ -11619,8 +12957,7 @@ var QwenAgent = class {
11619
12957
  let outputLanguage = null;
11620
12958
  let refreshed = false;
11621
12959
  if (syncOutputLanguage) {
11622
- const resolved = resolveOutputLanguage(language);
11623
- const settingValue = isAutoLanguage(language) ? OUTPUT_LANGUAGE_AUTO : resolved;
12960
+ const settingValue = resolveOutputLanguageOrPreserveAuto(language);
11624
12961
  let fileWriteOk = false;
11625
12962
  try {
11626
12963
  writeOutputLanguageAndRegisterPath(
@@ -11678,7 +13015,7 @@ var QwenAgent = class {
11678
13015
  }
11679
13016
  refreshed = results.length === 0 || failedCount === 0;
11680
13017
  }
11681
- outputLanguage = fileWriteOk ? resolved : null;
13018
+ outputLanguage = fileWriteOk ? settingValue : null;
11682
13019
  }
11683
13020
  return { language: resolvedLanguage, outputLanguage, refreshed };
11684
13021
  }
@@ -12020,6 +13357,27 @@ ${outputText}
12020
13357
  condition: cleared?.condition
12021
13358
  };
12022
13359
  }
13360
+ case SERVE_CONTROL_EXT_METHODS.sessionGoalGet: {
13361
+ const sessionId = params["sessionId"];
13362
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
13363
+ throw RequestError.invalidParams(
13364
+ void 0,
13365
+ "Invalid or missing sessionId"
13366
+ );
13367
+ }
13368
+ this.sessionOrThrow(sessionId);
13369
+ const active = getActiveGoal(sessionId);
13370
+ return {
13371
+ // Projected field by field: `ActiveGoal` also carries `hookId` and
13372
+ // `tokensAtStart`, which are this process's business.
13373
+ active: active ? {
13374
+ condition: active.condition,
13375
+ iterations: active.iterations,
13376
+ setAt: active.setAt,
13377
+ ...active.lastReason !== void 0 ? { lastReason: active.lastReason } : {}
13378
+ } : null
13379
+ };
13380
+ }
12023
13381
  case SERVE_CONTROL_EXT_METHODS.sessionContinue: {
12024
13382
  const sessionId = params["sessionId"];
12025
13383
  if (typeof sessionId !== "string" || sessionId.length === 0) {
@@ -12816,6 +14174,9 @@ ${outputText}
12816
14174
  if (newMode && APPROVAL_MODES.includes(newMode) && newMode !== config.getApprovalMode()) {
12817
14175
  try {
12818
14176
  config.setApprovalMode(newMode);
14177
+ if (newMode === "plan") {
14178
+ session.clearTodoStopGuardTrust();
14179
+ }
12819
14180
  } catch (err) {
12820
14181
  debugLogger7.warn(
12821
14182
  `reload: setApprovalMode failed for session ${id}: ${err}`
@@ -13179,23 +14540,22 @@ ${outputText}
13179
14540
  buildAvailableModels(config) {
13180
14541
  const rawCurrentModelId = (config.getModel() || this.config.getModel() || "").trim();
13181
14542
  const currentAuthType = config.getAuthType();
13182
- const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
14543
+ const modelOptions = buildAcpModelOptions(config.getAllConfiguredModels());
13183
14544
  const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
13184
- const currentModelId = activeRuntimeSnapshot ? formatAcpModelId(
13185
- activeRuntimeSnapshot.id,
13186
- activeRuntimeSnapshot.authType
13187
- ) : this.formatCurrentModelId(rawCurrentModelId, currentAuthType);
13188
- const mappedAvailableModels = allConfiguredModels.map((model) => {
13189
- const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
13190
- return {
13191
- modelId: formatAcpModelId(effectiveModelId, model.authType),
13192
- name: model.label,
13193
- description: model.description ?? null,
13194
- _meta: {
13195
- contextLimit: model.contextWindowSize ?? tokenLimit(model.id)
13196
- }
13197
- };
13198
- });
14545
+ const currentModelId = getCurrentAcpModelId(
14546
+ modelOptions,
14547
+ activeRuntimeSnapshot?.id ?? rawCurrentModelId,
14548
+ activeRuntimeSnapshot?.authType ?? currentAuthType,
14549
+ activeRuntimeSnapshot ? void 0 : config.getCurrentModelRegistryBaseUrl?.()
14550
+ );
14551
+ const mappedAvailableModels = modelOptions.map(({ model, modelId }) => ({
14552
+ modelId,
14553
+ name: model.label,
14554
+ description: model.description ?? null,
14555
+ _meta: {
14556
+ contextLimit: model.contextWindowSize ?? tokenLimit(model.id)
14557
+ }
14558
+ }));
13199
14559
  return {
13200
14560
  currentModelId,
13201
14561
  availableModels: mappedAvailableModels
@@ -13215,14 +14575,16 @@ ${outputText}
13215
14575
  }
13216
14576
  buildConfigOptions(config) {
13217
14577
  const currentApprovalMode = config.getApprovalMode();
13218
- const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
14578
+ const modelOptions = buildAcpModelOptions(config.getAllConfiguredModels());
13219
14579
  const rawCurrentModelId = (config.getModel() || "").trim();
13220
14580
  const currentAuthType = config.getAuthType?.();
13221
14581
  const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
13222
- const currentModelId = activeRuntimeSnapshot ? formatAcpModelId(
13223
- activeRuntimeSnapshot.id,
13224
- activeRuntimeSnapshot.authType
13225
- ) : this.formatCurrentModelId(rawCurrentModelId, currentAuthType);
14582
+ const currentModelId = getCurrentAcpModelId(
14583
+ modelOptions,
14584
+ activeRuntimeSnapshot?.id ?? rawCurrentModelId,
14585
+ activeRuntimeSnapshot?.authType ?? currentAuthType,
14586
+ activeRuntimeSnapshot ? void 0 : config.getCurrentModelRegistryBaseUrl?.()
14587
+ );
13226
14588
  const modeOptions = APPROVAL_MODES.map((mode) => ({
13227
14589
  value: mode,
13228
14590
  name: APPROVAL_MODE_INFO[mode].name,
@@ -13237,14 +14599,11 @@ ${outputText}
13237
14599
  currentValue: currentApprovalMode,
13238
14600
  options: modeOptions
13239
14601
  };
13240
- const modelOptions = allConfiguredModels.map((model) => {
13241
- const effectiveModelId = model.isRuntimeModel && model.runtimeSnapshotId ? model.runtimeSnapshotId : model.id;
13242
- return {
13243
- value: formatAcpModelId(effectiveModelId, model.authType),
13244
- name: model.label,
13245
- description: model.description ?? ""
13246
- };
13247
- });
14602
+ const configModelOptions = modelOptions.map(({ model, modelId }) => ({
14603
+ value: modelId,
14604
+ name: model.label,
14605
+ description: model.description ?? ""
14606
+ }));
13248
14607
  const modelConfigOption = {
13249
14608
  id: "model",
13250
14609
  name: "Model",
@@ -13252,19 +14611,11 @@ ${outputText}
13252
14611
  category: "model",
13253
14612
  type: "select",
13254
14613
  currentValue: currentModelId,
13255
- options: modelOptions
14614
+ options: configModelOptions
13256
14615
  };
13257
14616
  return [modeConfigOption, modelConfigOption];
13258
14617
  }
13259
- formatCurrentModelId(baseModelId, authType) {
13260
- if (!baseModelId) return baseModelId;
13261
- return authType ? formatAcpModelId(baseModelId, authType) : baseModelId;
13262
- }
13263
14618
  };
13264
- function isMainSelectableModel(model) {
13265
- return model.fastOnly !== true && model.voiceOnly !== true;
13266
- }
13267
- __name(isMainSelectableModel, "isMainSelectableModel");
13268
14619
  function diffSettingsKeys(oldMerged, newMerged) {
13269
14620
  const changed = /* @__PURE__ */ new Set();
13270
14621
  const allKeys = /* @__PURE__ */ new Set([
@@ -13297,6 +14648,11 @@ export {
13297
14648
  * Copyright 2025 Qwen Team
13298
14649
  * SPDX-License-Identifier: Apache-2.0
13299
14650
  */
14651
+ /**
14652
+ * @license
14653
+ * Copyright 2026 Qwen
14654
+ * SPDX-License-Identifier: Apache-2.0
14655
+ */
13300
14656
  /**
13301
14657
  * @license
13302
14658
  * Copyright 2025 Qwen