@qwen-code/qwen-code 0.19.3 → 0.19.4-nightly.20260702.46814e4f1

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 (308) hide show
  1. package/bundled/loop/SKILL.md +22 -3
  2. package/bundled/loop/SKILL.test.ts +20 -0
  3. package/bundled/loop/autonomous-loop.test.ts +59 -0
  4. package/bundled/loop/autonomous-loop.ts +102 -0
  5. package/bundled/loop/loop-task-file.test.ts +1064 -0
  6. package/bundled/loop/loop-task-file.ts +404 -0
  7. package/bundled/loop/loop-tick-resolver.test.ts +906 -0
  8. package/bundled/loop/loop-tick-resolver.ts +416 -0
  9. package/bundled/qc-helper/SKILL.md +12 -5
  10. package/bundled/qc-helper/docs/configuration/auth.md +1 -1
  11. package/bundled/qc-helper/docs/configuration/settings.md +62 -32
  12. package/bundled/qc-helper/docs/features/approval-mode.md +3 -0
  13. package/bundled/qc-helper/docs/features/auto-mode.md +40 -0
  14. package/bundled/qc-helper/docs/features/channels/overview.md +81 -18
  15. package/bundled/qc-helper/docs/features/channels/plugins.md +1 -1
  16. package/bundled/qc-helper/docs/features/code-review.md +17 -61
  17. package/bundled/qc-helper/docs/features/commands.md +42 -40
  18. package/bundled/qc-helper/docs/features/headless.md +19 -18
  19. package/bundled/qc-helper/docs/features/hooks.md +6 -1
  20. package/bundled/qc-helper/docs/features/mcp.md +3 -1
  21. package/bundled/qc-helper/docs/features/memory.md +1 -1
  22. package/bundled/qc-helper/docs/features/scheduled-tasks.md +10 -0
  23. package/bundled/qc-helper/docs/features/status-line.md +3 -3
  24. package/bundled/qc-helper/docs/ide-integration/ide-integration.md +4 -4
  25. package/bundled/qc-helper/docs/qwen-serve.md +53 -0
  26. package/bundled/qc-helper/docs/support/troubleshooting.md +11 -0
  27. package/bundled/review/DESIGN.md +21 -26
  28. package/bundled/review/SKILL.md +45 -129
  29. package/chunks/MaxSizedBox-GHGRQ2OS.js +69 -0
  30. package/chunks/{StandaloneSessionPicker-Z52JTYST.js → StandaloneSessionPicker-G7YTTM27.js} +49 -42
  31. package/chunks/{acpAgent-7RZUOWOM.js → acpAgent-SDOILBSU.js} +1325 -365
  32. package/chunks/{agent-4FG7WTHJ.js → agent-NTSXGV4L.js} +31 -25
  33. package/chunks/agent-headless-WYURMSAB.js +62 -0
  34. package/chunks/{anthropicContentGenerator-PFITVL7Y.js → anthropicContentGenerator-WL3LJOUB.js} +110 -31
  35. package/chunks/{artifact-tool-D2X7G3C3.js → artifact-tool-W76WC2JX.js} +3 -3
  36. package/chunks/{askUserQuestion-UGETICJQ.js → askUserQuestion-J3P6TH3V.js} +2 -2
  37. package/chunks/bridge-AMOL4W4P.js +73 -0
  38. package/chunks/{ca-IUFWESZL.js → ca-HCMB2I54.js} +10 -9
  39. package/chunks/channel-worker-supervisor-C3IB63V7.js +659 -0
  40. package/chunks/{chunk-STUWFKGD.js → chunk-26JIREA5.js} +466 -543
  41. package/chunks/{chunk-SLNKLLEO.js → chunk-2MRV62AU.js} +37 -8
  42. package/chunks/{chunk-ODLIHNOY.js → chunk-2U4HUMOL.js} +1 -1
  43. package/chunks/{chunk-CKQA6AWF.js → chunk-4D73HUOP.js} +1 -1
  44. package/chunks/{chunk-M2U5NM24.js → chunk-54YZOP7P.js} +309 -72
  45. package/chunks/{chunk-J7FA27RO.js → chunk-5LKL3PAU.js} +3 -3
  46. package/chunks/chunk-5P5XGNYH.js +93 -0
  47. package/chunks/{chunk-UFLGERMB.js → chunk-6JE32BRB.js} +178 -124
  48. package/chunks/{chunk-FKAVULLM.js → chunk-75AVTQJ2.js} +74 -10
  49. package/chunks/{chunk-I2LYBGNI.js → chunk-7IOMIYOE.js} +1 -1
  50. package/chunks/{chunk-4LUZAKMK.js → chunk-7KXAGQJ2.js} +1 -1
  51. package/chunks/{chunk-BXSHL2SF.js → chunk-7LUOQJ3D.js} +1 -1
  52. package/chunks/{chunk-YAINVQ3Q.js → chunk-A5OPLHFM.js} +18693 -17055
  53. package/chunks/chunk-AHNXYU4O.js +24 -0
  54. package/chunks/{chunk-UX4SGCGN.js → chunk-AVQDCKNF.js} +96 -22
  55. package/chunks/{chunk-HYCC3XM3.js → chunk-B236XPGF.js} +1 -1
  56. package/chunks/chunk-B6SZLQAZ.js +3523 -0
  57. package/chunks/{chunk-5DWXLGOM.js → chunk-BJBWRCSK.js} +1 -1
  58. package/chunks/chunk-BPFJQGY7.js +193 -0
  59. package/chunks/{chunk-SKQZZVAV.js → chunk-BZHLWC6T.js} +2 -2
  60. package/chunks/{chunk-U3KSMM7G.js → chunk-D5KISTLW.js} +339 -383
  61. package/chunks/{chunk-GT2FGICD.js → chunk-DE7YXUR7.js} +1 -1
  62. package/chunks/{chunk-BUUQ5HPX.js → chunk-DG573EOO.js} +1 -1
  63. package/chunks/{chunk-VVLOGDB6.js → chunk-DX3TKXNT.js} +2 -2
  64. package/chunks/chunk-E5A7LHNN.js +51 -0
  65. package/chunks/{chunk-3PJXIDKI.js → chunk-E6US47KI.js} +3 -27
  66. package/chunks/{chunk-QBZ22PQX.js → chunk-EWALXUAD.js} +3 -1
  67. package/chunks/{chunk-PDE7CSDS.js → chunk-FXKI7OW6.js} +2 -2
  68. package/chunks/{chunk-F3ZYSEY6.js → chunk-FY4TT4L4.js} +3 -3
  69. package/chunks/{chunk-QLDPRILK.js → chunk-G7OQ3RFJ.js} +3 -3
  70. package/chunks/{chunk-R2BXK7SX.js → chunk-GHOTR7HL.js} +6 -1
  71. package/chunks/{chunk-KMGMKW5Q.js → chunk-GLPV75ES.js} +9 -3
  72. package/chunks/{chunk-LD2XBG6Z.js → chunk-GO6LNQXT.js} +1 -39
  73. package/chunks/{chunk-CNLEDYE3.js → chunk-H6TNLOLM.js} +5 -5
  74. package/chunks/{chunk-JVHEVCAU.js → chunk-HLW4EGKR.js} +31 -6
  75. package/chunks/{chunk-I4H7PRWQ.js → chunk-HTWHAN44.js} +1 -1
  76. package/chunks/{chunk-QE6KJYLT.js → chunk-IPYE5BIS.js} +123 -25
  77. package/chunks/{chunk-EJYJYJZ2.js → chunk-IRJWHZWU.js} +3 -70
  78. package/chunks/{chunk-EDMZZFDU.js → chunk-J3YPTTNL.js} +930 -798
  79. package/chunks/{chunk-GGBIN5K7.js → chunk-JGAJMIJZ.js} +1 -1
  80. package/chunks/{chunk-N4EJFBN5.js → chunk-JVLP5US5.js} +427 -263
  81. package/chunks/{chunk-R5OP2ATH.js → chunk-JZG7VB6C.js} +11 -11
  82. package/chunks/{chunk-QI7GOGJR.js → chunk-KKQWC6WS.js} +1 -1
  83. package/chunks/{chunk-ADCIHOQZ.js → chunk-KX5QZRW2.js} +142 -30
  84. package/chunks/{chunk-SBBN4BWR.js → chunk-L7OJ3ZO3.js} +1 -1
  85. package/chunks/{chunk-NDOC6M5S.js → chunk-L7QPM2YD.js} +2 -2
  86. package/chunks/{chunk-BVSVIN45.js → chunk-LKWAVAB5.js} +3 -3
  87. package/chunks/{chunk-T4T2FNV4.js → chunk-LOW52WYZ.js} +2 -2
  88. package/chunks/{chunk-4QK3O43S.js → chunk-LU7BIVWC.js} +4 -4
  89. package/chunks/{chunk-ZP6UKOPB.js → chunk-LWGLBVWA.js} +1 -1
  90. package/chunks/{chunk-L7OKCI7T.js → chunk-M7BLWURA.js} +15 -1
  91. package/chunks/{chunk-PR32FRT6.js → chunk-MIM62IXS.js} +3 -3
  92. package/chunks/{chunk-RD6YJJJ6.js → chunk-MOUXUCXQ.js} +1 -1
  93. package/chunks/{chunk-VBZ452JT.js → chunk-MSNGFH6L.js} +494 -114
  94. package/chunks/{chunk-BX5YO7UW.js → chunk-MTNTVOJR.js} +542 -370
  95. package/chunks/chunk-MX2YXRER.js +44 -0
  96. package/chunks/chunk-NIOPRZNN.js +27 -0
  97. package/chunks/{chunk-SMYQZVZ3.js → chunk-NOGSPTLJ.js} +1 -1
  98. package/chunks/{chunk-DDMOU62Q.js → chunk-NPQWQYU6.js} +1 -1
  99. package/chunks/{chunk-MNF6S5PR.js → chunk-P5THQV6A.js} +4 -4
  100. package/chunks/{chunk-6V4NZFUS.js → chunk-PCYOT4PK.js} +5 -5
  101. package/chunks/{chunk-CF6BB3LT.js → chunk-PPFWEV7G.js} +10 -1
  102. package/chunks/{chunk-FXKVLFFY.js → chunk-PSFDB5FZ.js} +3 -3
  103. package/chunks/{chunk-XJYUPHRC.js → chunk-QOMAHFYI.js} +3 -3
  104. package/chunks/{chunk-J37FGIOA.js → chunk-QXPI7FEC.js} +38 -4
  105. package/chunks/{chunk-RRFZXMYP.js → chunk-QZDBHNMV.js} +691 -164
  106. package/chunks/{chunk-S7AKE7HL.js → chunk-R6RI2HIC.js} +63 -9
  107. package/chunks/{chunk-HZT4UCFB.js → chunk-RB7S52NJ.js} +4 -4
  108. package/chunks/{chunk-J33ZR6OJ.js → chunk-RK22ACPP.js} +16 -16
  109. package/chunks/chunk-RM3PGSGL.js +66 -0
  110. package/chunks/chunk-RNBYOUGV.js +42 -0
  111. package/chunks/chunk-S6RLAIUR.js +23 -0
  112. package/chunks/{chunk-3TV3HUWZ.js → chunk-SCYK2YLI.js} +81 -30
  113. package/chunks/{chunk-HPZTOP4I.js → chunk-SEKRT4SD.js} +133 -23
  114. package/chunks/chunk-SEZC2725.js +36 -0
  115. package/chunks/chunk-SIUQ3YYX.js +78 -0
  116. package/chunks/{chunk-O2VBQ3Y2.js → chunk-SJBIFVD2.js} +2 -2
  117. package/chunks/chunk-SYCJMSIJ.js +82 -0
  118. package/chunks/{chunk-JE5FVRPC.js → chunk-T22H4V4R.js} +48 -23
  119. package/chunks/{chunk-3227DZO4.js → chunk-T42ACHFI.js} +1 -1
  120. package/chunks/{chunk-UO5GVXQE.js → chunk-TENOUWY2.js} +3 -3
  121. package/chunks/{chunk-JOLIREYR.js → chunk-TOAUDIS7.js} +1 -1
  122. package/chunks/{chunk-QNKBEJO2.js → chunk-UA6XBZT7.js} +2776 -1289
  123. package/chunks/{chunk-O75BHFQW.js → chunk-UQTYK5SS.js} +2 -2
  124. package/chunks/{chunk-Y6YMRS3U.js → chunk-UY2TCIZS.js} +17 -3
  125. package/chunks/{chunk-FNJ7BDPH.js → chunk-UY7ZAYTY.js} +10 -7
  126. package/chunks/{chunk-UYT3MWEB.js → chunk-UYHWM24C.js} +30 -2
  127. package/chunks/{chunk-SMXULRAN.js → chunk-VXGPYF7U.js} +1 -1
  128. package/chunks/{chunk-BTLJ2MF5.js → chunk-W7CG5HPC.js} +3 -3
  129. package/chunks/{chunk-JJXWTVSK.js → chunk-XHZKDN7M.js} +34 -1
  130. package/chunks/{chunk-K5XU6E4T.js → chunk-XKSCTXPK.js} +2 -2
  131. package/chunks/chunk-Y4LITCRB.js +367 -0
  132. package/chunks/chunk-YHTNWGRR.js +239 -0
  133. package/chunks/{chunk-BKXBARJZ.js → chunk-YPCPZSZB.js} +1 -1
  134. package/chunks/{chunk-QWFNTRH4.js → chunk-YSM24SCE.js} +1 -1
  135. package/chunks/chunk-YTFBURQD.js +313 -0
  136. package/chunks/{chunk-NL4C3H6C.js → chunk-YXK4GPYO.js} +1 -1
  137. package/chunks/{chunk-RYLJU2DY.js → chunk-ZN5T4BHI.js} +1 -1
  138. package/chunks/{computer-use-JGB36FDW.js → computer-use-MTSVPLPY.js} +34 -28
  139. package/chunks/contextCommand-VGJ3BPTK.js +67 -0
  140. package/chunks/{cron-create-SKEY55RG.js → cron-create-6YTCOX6P.js} +4 -4
  141. package/chunks/{cron-delete-I3NAT32Z.js → cron-delete-E3NIFSUK.js} +4 -4
  142. package/chunks/{cron-list-6XRRL6TH.js → cron-list-Q64MNQNE.js} +4 -4
  143. package/chunks/daemon-EEB2RYOL.js +8453 -0
  144. package/chunks/daemon-status-provider-H5SNMK42.js +71 -0
  145. package/chunks/{de-EOW5PVHF.js → de-PLLFDERI.js} +10 -9
  146. package/chunks/{dist-NHEYYB2B.js → dist-ADMIQGHI.js} +2 -2
  147. package/chunks/{dist-J37VXPLD.js → dist-BCQ7INM7.js} +40 -15
  148. package/chunks/{dist-663NZ6MI.js → dist-KNZFCY5F.js} +116 -11
  149. package/chunks/{dist-LB6OGPVM.js → dist-LHUWC3K6.js} +26 -5
  150. package/chunks/{dist-PQPZ5Q4N.js → dist-WBBZ76JZ.js} +20 -9
  151. package/chunks/{earlyInputCapture-44RISVVJ.js → earlyInputCapture-U4ZIQSQU.js} +32 -26
  152. package/chunks/{edit-NN6GM5DT.js → edit-SYIQ2KBI.js} +41 -38
  153. package/chunks/{en-HPWEJBTR.js → en-M4EBUBNU.js} +32 -10
  154. package/chunks/{enter-worktree-PB3O2C7D.js → enter-worktree-HPPOO244.js} +31 -25
  155. package/chunks/{enterPlanMode-YQFNNSUA.js → enterPlanMode-56YYO52U.js} +41 -25
  156. package/chunks/{errors-LS7XU5DZ.js → errors-EO4SXCFP.js} +33 -27
  157. package/chunks/{exit-worktree-E2FTNSBK.js → exit-worktree-UDM7E6XH.js} +31 -25
  158. package/chunks/{exitPlanMode-GLYK2BUB.js → exitPlanMode-M6P3AWLJ.js} +149 -33
  159. package/chunks/{fast-path-WQ6M7LI7.js → fast-path-42P4QYWI.js} +33 -14
  160. package/chunks/{fast-path-settings-OAGU3X5H.js → fast-path-settings-UQLRFDQP.js} +2 -2
  161. package/chunks/{fr-BN5NKSAC.js → fr-MBMNQSQ6.js} +10 -9
  162. package/chunks/{gemini-2YFLVA2M.js → gemini-NRLXS5J7.js} +84 -70
  163. package/chunks/{geminiContentGenerator-XV6SV6CD.js → geminiContentGenerator-DLJPYYPA.js} +41 -5
  164. package/chunks/{glob-2H6CDDMK.js → glob-JPY3F74Y.js} +31 -25
  165. package/chunks/{grep-VXIUXM3C.js → grep-HW23YES5.js} +31 -25
  166. package/chunks/initializer-HWOZTC4A.js +67 -0
  167. package/chunks/{ja-7DEUB2TS.js → ja-HFV5QCHR.js} +10 -9
  168. package/chunks/{keychain-token-storage-YLMDLKXT.js → keychain-token-storage-GPAKRDKQ.js} +2 -2
  169. package/chunks/list-OZGAGRDG.js +71 -0
  170. package/chunks/loadedSettingsAdapter-RUTZVVIA.js +66 -0
  171. package/chunks/{loop-wakeup-CITGGQHA.js → loop-wakeup-G2EHBRGR.js} +5 -5
  172. package/chunks/{ls-R5WP4BGZ.js → ls-IDHUVSAG.js} +4 -4
  173. package/chunks/{lsp-GSICJMMW.js → lsp-FSRYXGFT.js} +2 -2
  174. package/chunks/{monitor-HKLRS2HQ.js → monitor-QVWEIXU3.js} +33 -26
  175. package/chunks/nonInteractiveCli-RV2WYPML.js +109 -0
  176. package/chunks/{notebook-edit-Y6SKSGTT.js → notebook-edit-B26W3FFH.js} +37 -34
  177. package/chunks/{openaiContentGenerator-J7G2FYJT.js → openaiContentGenerator-6TH7AZGQ.js} +15 -13
  178. package/chunks/pidfile-KW2QQJZI.js +73 -0
  179. package/chunks/{pt-TNZRNLQB.js → pt-2THT3YHA.js} +10 -9
  180. package/chunks/{qwenContentGenerator-5XIEM5GV.js → qwenContentGenerator-5IQAC7RN.js} +33 -27
  181. package/chunks/{qwenOAuth2-AG3PFFLZ.js → qwenOAuth2-LWLA6MQG.js} +5 -4
  182. package/chunks/{read-file-J7D552ZJ.js → read-file-CWFAHOI2.js} +11 -10
  183. package/chunks/{read-mcp-resource-DQQW7H7T.js → read-mcp-resource-PIJF24RO.js} +2 -2
  184. package/chunks/ripGrep-FTZGDKY4.js +60 -0
  185. package/chunks/{ru-NIAUBKCN.js → ru-Z67YJHBG.js} +10 -9
  186. package/chunks/{run-qwen-serve-SGEEVMXD.js → run-qwen-serve-FN2SW5ZI.js} +545 -97
  187. package/chunks/{scheduler-7CQI3S4Q.js → scheduler-BMS23O3I.js} +31 -25
  188. package/chunks/{send-message-EE4TWKVU.js → send-message-33PYFMCT.js} +20 -4
  189. package/chunks/serve-PXFYKL65.js +71 -0
  190. package/chunks/{server-4QMUWXPW.js → server-BRKALJ4F.js} +4132 -1175
  191. package/chunks/{session-NRO7UFOY.js → session-AQMISBP4.js} +205 -64
  192. package/chunks/{settings-SGWRDQSC.js → settings-JCPVUZMB.js} +35 -29
  193. package/chunks/{shell-62ANGIHL.js → shell-POHCRVN2.js} +31 -25
  194. package/chunks/{skill-VWAQAWW6.js → skill-QPEVHS6H.js} +13 -12
  195. package/chunks/spawnChannel-V3QT62TE.js +69 -0
  196. package/chunks/{src-KUV5J5Q2.js → src-IZSP7L6Y.js} +127 -32
  197. package/chunks/{startInteractiveUI-6EYJN3JV.js → startInteractiveUI-63KATISO.js} +16762 -16075
  198. package/chunks/{syntheticOutput-T5YHQ6S2.js → syntheticOutput-YOSWIHDJ.js} +3 -3
  199. package/chunks/{task-create-BLY7RHXX.js → task-create-NHS2NGB3.js} +9 -7
  200. package/chunks/{task-list-3QUYNVYR.js → task-list-5UIB564R.js} +7 -6
  201. package/chunks/{task-stop-U4RATK2V.js → task-stop-HGXZ2VZ3.js} +2 -2
  202. package/chunks/{task-update-U54O7KJX.js → task-update-IYYWUILG.js} +40 -7
  203. package/chunks/{team-create-LPOGCPM7.js → team-create-E5O2F3CA.js} +31 -25
  204. package/chunks/{team-delete-LKBYPASD.js → team-delete-FEKJCU4P.js} +7 -6
  205. package/chunks/team-plan-approval-2TU4YROB.js +202 -0
  206. package/chunks/theme-manager-37KOGPJL.js +64 -0
  207. package/chunks/{todoWrite-OYXKTVYN.js → todoWrite-TKMVZ2HT.js} +4 -4
  208. package/chunks/{tool-search-YPUFJ3ZQ.js → tool-search-4ZTGNSFI.js} +38 -11
  209. package/chunks/{trustedFolders-5B24TMEA.js → trustedFolders-RJ5WXJ6L.js} +32 -26
  210. package/chunks/{validateNonInterActiveAuth-BTDTYOVT.js → validateNonInterActiveAuth-ZWMHZAOK.js} +66 -55
  211. package/chunks/{web-fetch-EEB5LZYU.js → web-fetch-J4PIW36Q.js} +5 -4
  212. package/chunks/{workflow-LAWIW5LI.js → workflow-6PG2PC5M.js} +68 -39
  213. package/chunks/workspace-providers-status-4QIZN2W4.js +68 -0
  214. package/chunks/workspace-service-CYMCOEIO.js +78 -0
  215. package/chunks/{write-file-PCV226DJ.js → write-file-R354LGQF.js} +41 -40
  216. package/chunks/{zh-5EUYXASN.js → zh-SLZNSSZB.js} +32 -10
  217. package/chunks/{zh-TW-TR3XYGE5.js → zh-TW-P53C2SL5.js} +32 -10
  218. package/cli.js +6 -6
  219. package/locales/ca.js +15 -13
  220. package/locales/de.js +15 -13
  221. package/locales/en.js +52 -15
  222. package/locales/fr.js +15 -14
  223. package/locales/ja.js +15 -14
  224. package/locales/pt.js +15 -13
  225. package/locales/ru.js +15 -13
  226. package/locales/zh-TW.js +50 -14
  227. package/locales/zh.js +50 -14
  228. package/node_modules/@qwen-code/audio-capture/package.json +1 -1
  229. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  230. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  231. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  232. package/package.json +15 -6
  233. package/patches/chrome-devtools-mcp+1.4.0.patch +31 -0
  234. package/postinstall.js +50 -0
  235. package/web-shell/assets/{arc-DA7r8Ueo.js → arc-Bd3048nX.js} +1 -1
  236. package/web-shell/assets/{architectureDiagram-3BPJPVTR-ZDUxPtZ2.js → architectureDiagram-3BPJPVTR-DyzbzABx.js} +1 -1
  237. package/web-shell/assets/{blockDiagram-GPEHLZMM-vrtOf1kJ.js → blockDiagram-GPEHLZMM-CTjxm9Sy.js} +1 -1
  238. package/web-shell/assets/{c4Diagram-AAUBKEIU-BHFjzIi4.js → c4Diagram-AAUBKEIU-DSOFWNU6.js} +1 -1
  239. package/web-shell/assets/channel-oVoup_Ci.js +1 -0
  240. package/web-shell/assets/{chunk-2J33WTMH-mdeQVz59.js → chunk-2J33WTMH-D_PFdjo2.js} +1 -1
  241. package/web-shell/assets/{chunk-4BX2VUAB-BkKMlwBX.js → chunk-4BX2VUAB-52eG6wOT.js} +1 -1
  242. package/web-shell/assets/{chunk-55IACEB6-B7bnXNfu.js → chunk-55IACEB6-CGTZfKkC.js} +1 -1
  243. package/web-shell/assets/{chunk-727SXJPM-DGz8Mm6i.js → chunk-727SXJPM-CZ-qfRUm.js} +1 -1
  244. package/web-shell/assets/{chunk-AQP2D5EJ-BQAydUNk.js → chunk-AQP2D5EJ-B2dSDcqM.js} +1 -1
  245. package/web-shell/assets/{chunk-FMBD7UC4-GHr2LN_p.js → chunk-FMBD7UC4-Cfy3tyA0.js} +1 -1
  246. package/web-shell/assets/{chunk-ND2GUHAM-7R91lnUx.js → chunk-ND2GUHAM-CApNUpUq.js} +1 -1
  247. package/web-shell/assets/{chunk-QZHKN3VN-aTJD3lSQ.js → chunk-QZHKN3VN-CVBG8kgg.js} +1 -1
  248. package/web-shell/assets/classDiagram-4FO5ZUOK-B8WsaT2G.js +1 -0
  249. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B8WsaT2G.js +1 -0
  250. package/web-shell/assets/{cose-bilkent-S5V4N54A-BTf8t1RL.js → cose-bilkent-S5V4N54A-URd77-_-.js} +1 -1
  251. package/web-shell/assets/{dagre-BM42HDAG-_sgp3WuA.js → dagre-BM42HDAG-C07H4G9-.js} +1 -1
  252. package/web-shell/assets/{diagram-2AECGRRQ-CROtRy1w.js → diagram-2AECGRRQ-CYbpXrSn.js} +1 -1
  253. package/web-shell/assets/{diagram-5GNKFQAL-BqGRzwSc.js → diagram-5GNKFQAL-SI8s6fjX.js} +1 -1
  254. package/web-shell/assets/{diagram-KO2AKTUF-CMrcEtA7.js → diagram-KO2AKTUF-mBkC4RTW.js} +1 -1
  255. package/web-shell/assets/{diagram-LMA3HP47-BRydGNW5.js → diagram-LMA3HP47-CyBPDzWJ.js} +1 -1
  256. package/web-shell/assets/{diagram-OG6HWLK6-D0ilgNjV.js → diagram-OG6HWLK6-DwK7kBCu.js} +1 -1
  257. package/web-shell/assets/{erDiagram-TEJ5UH35-2lnQeJUM.js → erDiagram-TEJ5UH35-Ds1StEUd.js} +1 -1
  258. package/web-shell/assets/{flowDiagram-I6XJVG4X-C_ZMdQiC.js → flowDiagram-I6XJVG4X-BDC4-jHO.js} +1 -1
  259. package/web-shell/assets/{ganttDiagram-6RSMTGT7-Dw9V_Ny0.js → ganttDiagram-6RSMTGT7-CV2aHQqf.js} +1 -1
  260. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-HDXI-F1B.js → gitGraphDiagram-PVQCEYII-B8RcuV_L.js} +1 -1
  261. package/web-shell/assets/index-B5WZ0qP1.js +717 -0
  262. package/web-shell/assets/index-CYE1IMd1.css +5 -0
  263. package/web-shell/assets/{infoDiagram-5YYISTIA-CuB8Ebua.js → infoDiagram-5YYISTIA-DUPoTf50.js} +1 -1
  264. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D_tyhVwh.js → ishikawaDiagram-YF4QCWOH-FDYOx9rf.js} +1 -1
  265. package/web-shell/assets/{journeyDiagram-JHISSGLW-CgCwJQNU.js → journeyDiagram-JHISSGLW-C5_ohMRp.js} +1 -1
  266. package/web-shell/assets/{kanban-definition-UN3LZRKU-PZUtcbPx.js → kanban-definition-UN3LZRKU-Bn4UnOMG.js} +1 -1
  267. package/web-shell/assets/{linear-CZqRdJIU.js → linear-Bl0pKQtY.js} +1 -1
  268. package/web-shell/assets/{mermaid.core-CPW_4UEF.js → mermaid.core-sevQFoji.js} +72 -72
  269. package/web-shell/assets/{mindmap-definition-RKZ34NQL-D_Y75ID7.js → mindmap-definition-RKZ34NQL-DSH4JZF9.js} +1 -1
  270. package/web-shell/assets/{pieDiagram-4H26LBE5-Q4dTyLeF.js → pieDiagram-4H26LBE5-acWvdQTD.js} +1 -1
  271. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-ckw329Ft.js → quadrantDiagram-W4KKPZXB-DlggX4Sm.js} +1 -1
  272. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DbVDm4It.js → requirementDiagram-4Y6WPE33--mMqwmV8.js} +1 -1
  273. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CHuZ_ecH.js → sankeyDiagram-5OEKKPKP-BtV9tZ77.js} +1 -1
  274. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DU2YSpq-.js → sequenceDiagram-3UESZ5HK-iDQzPFlj.js} +1 -1
  275. package/web-shell/assets/{stateDiagram-AJRCARHV-CA9PEBUo.js → stateDiagram-AJRCARHV-CsCyQLZ3.js} +1 -1
  276. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CPapECG9.js +1 -0
  277. package/web-shell/assets/{timeline-definition-PNZ67QCA-Bhlo4WVc.js → timeline-definition-PNZ67QCA-Dl0rnKvr.js} +1 -1
  278. package/web-shell/assets/{vennDiagram-CIIHVFJN-6xtPLsyl.js → vennDiagram-CIIHVFJN-qF7nTZzA.js} +1 -1
  279. package/web-shell/assets/{wardley-L42UT6IY-3eozmXPf.js → wardley-L42UT6IY-fWoK9i7F.js} +1 -1
  280. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BOn6hKss.js → wardleyDiagram-YWT4CUSO-pHGfkMnc.js} +1 -1
  281. package/web-shell/assets/{xychartDiagram-2RQKCTM6-BsP4-I_y.js → xychartDiagram-2RQKCTM6-3Doc4Trz.js} +1 -1
  282. package/web-shell/index.html +18 -2
  283. package/chunks/MaxSizedBox-QNF3EIGU.js +0 -63
  284. package/chunks/agent-headless-RQ5BTX3G.js +0 -56
  285. package/chunks/bridge-JL2PMCIW.js +0 -66
  286. package/chunks/chunk-5GOVVXEV.js +0 -73
  287. package/chunks/chunk-BC3C4F3Q.js +0 -40
  288. package/chunks/chunk-BW4KCQFD.js +0 -1103
  289. package/chunks/chunk-RKIZXJMB.js +0 -13
  290. package/chunks/contextCommand-WYU6OQ2F.js +0 -61
  291. package/chunks/daemon-status-provider-HH2EC5FI.js +0 -65
  292. package/chunks/headlessSafetyWarnings-TBE35GJ4.js +0 -55
  293. package/chunks/initializer-2Y7SLOKV.js +0 -61
  294. package/chunks/list-ZAFHZ7OS.js +0 -65
  295. package/chunks/loadedSettingsAdapter-6KJXD6M3.js +0 -60
  296. package/chunks/nonInteractiveCli-5W4GIZQM.js +0 -96
  297. package/chunks/ripGrep-IEIFIWP4.js +0 -54
  298. package/chunks/serve-V6GVZR6U.js +0 -65
  299. package/chunks/spawnChannel-3VHW5EPY.js +0 -63
  300. package/chunks/theme-manager-LYLGAERF.js +0 -58
  301. package/chunks/workspace-providers-status-PIAFVYHA.js +0 -62
  302. package/chunks/workspace-service-YAIOD4HG.js +0 -71
  303. package/web-shell/assets/channel-iRo68q4Q.js +0 -1
  304. package/web-shell/assets/classDiagram-4FO5ZUOK-iHF24a8X.js +0 -1
  305. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-iHF24a8X.js +0 -1
  306. package/web-shell/assets/index-CHmzAWys.css +0 -5
  307. package/web-shell/assets/index-w6GTOHh1.js +0 -713
  308. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-t1LRI0oY.js +0 -1
@@ -3,8 +3,13 @@
3
3
  import {
4
4
  classifyApiError,
5
5
  prefixMidTurnUserMessageParts
6
- } from "./chunk-QE6KJYLT.js";
7
- import "./chunk-UFLGERMB.js";
6
+ } from "./chunk-IPYE5BIS.js";
7
+ import {
8
+ EXTENSION_CONTEXT_BUDGET,
9
+ buildExtensionMentionContext,
10
+ matchExtensionByRef,
11
+ parseExtensionRef
12
+ } from "./chunk-6JE32BRB.js";
8
13
  import {
9
14
  HistoryReplayer,
10
15
  MessageEmitter,
@@ -12,74 +17,91 @@ import {
12
17
  ToolCallEmitter,
13
18
  getAvailableCommands,
14
19
  handleSlashCommand,
20
+ insertAfterFunctionResponses,
15
21
  isSlashCommand,
16
22
  normalizePartList
17
- } from "./chunk-RRFZXMYP.js";
23
+ } from "./chunk-QZDBHNMV.js";
18
24
  import {
19
25
  appEvents,
20
26
  buildDisabledSkillNamesProvider,
21
27
  loadCliConfig,
22
28
  normalizeDisabledToolList
23
- } from "./chunk-EDMZZFDU.js";
24
- import {
25
- collectContextData,
26
- formatContextUsageText
27
- } from "./chunk-FNJ7BDPH.js";
29
+ } from "./chunk-J3YPTTNL.js";
28
30
  import {
29
31
  getCommandSubcommandNames
30
- } from "./chunk-J7FA27RO.js";
31
- import "./chunk-5GOVVXEV.js";
32
+ } from "./chunk-5LKL3PAU.js";
33
+ import "./chunk-Y4LITCRB.js";
32
34
  import {
33
35
  getEffectiveSupportedModes
34
- } from "./chunk-3TV3HUWZ.js";
36
+ } from "./chunk-SCYK2YLI.js";
37
+ import "./chunk-JZSY4WP3.js";
38
+ import {
39
+ collectContextData,
40
+ formatContextUsageText
41
+ } from "./chunk-UY7ZAYTY.js";
35
42
  import {
36
43
  isTerminalGoalStatusKind
37
44
  } from "./chunk-KSPVDFE4.js";
38
45
  import "./chunk-MRO43B25.js";
39
- import "./chunk-SMXULRAN.js";
40
- import "./chunk-T4T2FNV4.js";
46
+ import "./chunk-VXGPYF7U.js";
47
+ import "./chunk-LOW52WYZ.js";
41
48
  import "./chunk-KYMBIKIW.js";
42
- import "./chunk-I4H7PRWQ.js";
43
- import "./chunk-JZSY4WP3.js";
44
- import "./chunk-JVHEVCAU.js";
45
- import "./chunk-CNLEDYE3.js";
49
+ import "./chunk-HTWHAN44.js";
50
+ import {
51
+ MAX_REMEMBER_CONTENT_BYTES,
52
+ extractRememberErrorCode,
53
+ isValidServerName
54
+ } from "./chunk-RM3PGSGL.js";
55
+ import "./chunk-L7QPM2YD.js";
56
+ import "./chunk-54YZOP7P.js";
57
+ import "./chunk-XHZKDN7M.js";
58
+ import "./chunk-AHNXYU4O.js";
59
+ import "./chunk-RNBYOUGV.js";
60
+ import "./chunk-HLW4EGKR.js";
61
+ import "./chunk-BJBWRCSK.js";
62
+ import "./chunk-H6TNLOLM.js";
46
63
  import "./chunk-3GONHQOA.js";
47
- import "./chunk-PR32FRT6.js";
48
- import "./chunk-CKQA6AWF.js";
49
- import "./chunk-BW4KCQFD.js";
64
+ import "./chunk-MIM62IXS.js";
65
+ import "./chunk-4D73HUOP.js";
66
+ import "./chunk-YHTNWGRR.js";
50
67
  import {
51
68
  createLoadedSettingsAdapter
52
- } from "./chunk-BXSHL2SF.js";
53
- import "./chunk-NDOC6M5S.js";
54
- import "./chunk-M2U5NM24.js";
55
- import "./chunk-JJXWTVSK.js";
56
- import "./chunk-R2BXK7SX.js";
57
- import "./chunk-RKIZXJMB.js";
58
- import "./chunk-MNF6S5PR.js";
69
+ } from "./chunk-7LUOQJ3D.js";
70
+ import "./chunk-P5THQV6A.js";
59
71
  import "./chunk-KHDZHZMH.js";
72
+ import "./chunk-DE7YXUR7.js";
73
+ import {
74
+ CLIENT_MCP_OVER_WS_CONFIG_FLAG,
75
+ MID_TURN_QUEUE_DRAIN_METHOD
76
+ } from "./chunk-EWALXUAD.js";
77
+ import "./chunk-ALX6BUYW.js";
60
78
  import {
61
79
  formatAcpModelId,
62
80
  parseAcpBaseModelId,
63
81
  parseAcpModelOption,
64
82
  sanitizeProviderBaseUrl
65
- } from "./chunk-UYT3MWEB.js";
83
+ } from "./chunk-UYHWM24C.js";
66
84
  import {
67
85
  PERMISSION_RULE_TYPES,
68
86
  PermissionRulesValidationError,
69
87
  buildPermissionSettings,
70
88
  normalizePermissionRules,
71
89
  readPermissionRuleSet
72
- } from "./chunk-ODLIHNOY.js";
73
- import "./chunk-4LUZAKMK.js";
74
- import "./chunk-BC3C4F3Q.js";
75
- import "./chunk-GT2FGICD.js";
76
- import {
77
- MID_TURN_QUEUE_DRAIN_METHOD
78
- } from "./chunk-QBZ22PQX.js";
79
- import "./chunk-ALX6BUYW.js";
90
+ } from "./chunk-2U4HUMOL.js";
91
+ import "./chunk-7KXAGQJ2.js";
92
+ import "./chunk-NIOPRZNN.js";
80
93
  import {
81
94
  NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE
82
- } from "./chunk-JE5FVRPC.js";
95
+ } from "./chunk-T22H4V4R.js";
96
+ import "./chunk-SEZC2725.js";
97
+ import {
98
+ ACP_PREFLIGHT_KINDS,
99
+ IDLE_HOOK_EVENTS,
100
+ SERVE_CONTROL_EXT_METHODS,
101
+ SERVE_STATUS_EXT_METHODS,
102
+ STATUS_SCHEMA_VERSION,
103
+ mapDomainErrorToErrorKind
104
+ } from "./chunk-M7BLWURA.js";
83
105
  import {
84
106
  OUTPUT_LANGUAGE_AUTO,
85
107
  getOutputLanguageFilePath,
@@ -90,44 +112,38 @@ import {
90
112
  resolveOutputLanguage,
91
113
  updateOutputLanguageFile,
92
114
  writeOutputLanguageAndRegisterPath
93
- } from "./chunk-FKAVULLM.js";
115
+ } from "./chunk-75AVTQJ2.js";
94
116
  import {
95
117
  SUPPORTED_LANGUAGES,
96
118
  getCurrentLanguage,
97
119
  setLanguageAsync
98
- } from "./chunk-R5OP2ATH.js";
120
+ } from "./chunk-JZG7VB6C.js";
99
121
  import "./chunk-TLRYABYP.js";
100
- import "./chunk-5DWXLGOM.js";
122
+ import "./chunk-S6RLAIUR.js";
123
+ import "./chunk-MX2YXRER.js";
124
+ import "./chunk-B6SZLQAZ.js";
101
125
  import {
102
126
  AgentSideConnection,
103
127
  PROTOCOL_VERSION,
104
128
  RequestError,
105
129
  ndJsonStream
106
130
  } from "./chunk-UFC57OYT.js";
107
- import {
108
- ACP_PREFLIGHT_KINDS,
109
- IDLE_HOOK_EVENTS,
110
- SERVE_CONTROL_EXT_METHODS,
111
- SERVE_STATUS_EXT_METHODS,
112
- STATUS_SCHEMA_VERSION,
113
- mapDomainErrorToErrorKind
114
- } from "./chunk-L7OKCI7T.js";
115
- import "./chunk-CF6BB3LT.js";
131
+ import "./chunk-PPFWEV7G.js";
116
132
  import {
117
133
  isFolderTrustEnabled,
118
134
  isWorkspaceTrusted,
119
135
  loadTrustedFolders
120
- } from "./chunk-F3ZYSEY6.js";
136
+ } from "./chunk-FY4TT4L4.js";
121
137
  import "./chunk-Y6Z2O3WR.js";
122
138
  import "./chunk-QYUE6W3T.js";
123
139
  import {
124
140
  ALL_PROVIDERS,
125
- APPROVAL_MODES,
126
141
  APPROVAL_MODE_INFO,
127
142
  BTW_MAX_INPUT_LENGTH,
128
143
  ExtensionManager,
129
144
  FORK_SUBAGENT_TYPE,
130
145
  InvalidMcpConfigError,
146
+ LoopTickResolver,
131
147
  MAX_TRANSCRIPT_MESSAGES,
132
148
  MCPOAuthProvider,
133
149
  MCPOAuthTokenStorage,
@@ -136,9 +152,11 @@ import {
136
152
  McpBudgetWouldExceedError,
137
153
  McpServerSpawnFailedError,
138
154
  McpTransportPool,
155
+ ORPHAN_TOOL_USE_REPAIR_REASON,
139
156
  POOLED_TRANSPORTS_DEFAULT,
140
157
  SESSION_TITLE_MAX_LENGTH,
141
158
  SessionService,
159
+ TURN_INTERRUPTION_HISTORY_TAIL_COUNT,
142
160
  WorkspaceMcpBudget,
143
161
  abortGoalForStopHookCap,
144
162
  acquireSleepInhibitor,
@@ -147,13 +165,18 @@ import {
147
165
  buildBackgroundEntryLabel,
148
166
  buildBtwCacheSafeParams,
149
167
  buildBtwPrompt,
168
+ buildContextUsage,
150
169
  buildInstallPlan,
170
+ buildSyntheticToolResponseParts,
151
171
  clampInlineMediaPart,
152
172
  clearGoalTerminalObserver,
153
173
  computeUniqueBranchTitle,
154
174
  convertToFunctionResponse,
155
175
  createDuplicateProviderToolCallResponse,
156
176
  dedupeToolCallsById,
177
+ detectAutonomousSentinel,
178
+ detectLoopSentinel,
179
+ detectTurnInterruption,
157
180
  evaluateAutoMode,
158
181
  evaluatePermissionFlow,
159
182
  findExistingProviderModels,
@@ -179,13 +202,16 @@ import {
179
202
  getProviderToolCallId,
180
203
  getScopedEnvContents,
181
204
  getStartupContextLength,
205
+ hasImageParts,
182
206
  injectPermissionRulesIfMissing,
183
207
  isApproveOutcome,
184
208
  isDenialFallbackReason,
185
209
  isPlanModeBlocked,
186
210
  isSystemReminderContent,
187
211
  markDuplicateProviderToolCallResponseSent,
212
+ matchesAnyServerPattern,
188
213
  needsConfirmation,
214
+ normalizeParts,
189
215
  parsePositiveIntegerEnv,
190
216
  persistPermissionOutcome,
191
217
  readManyFiles,
@@ -195,44 +221,55 @@ import {
195
221
  resolveBaseUrl,
196
222
  restoreWorktreeContext,
197
223
  runForkedAgent,
224
+ runManagedRememberByAgent,
225
+ runVisionBridge,
198
226
  sessionIdContext,
199
227
  setGoalTerminalObserver,
228
+ shouldClassifyAllShellForAutoMode,
200
229
  shouldFallback,
201
230
  shouldFirePermissionDeniedForAutoMode,
202
231
  shouldForceAutoModeReviewForAllow,
203
232
  shouldRunAutoModeForCall,
233
+ shouldRunVisionBridge,
234
+ splitImageParts,
204
235
  subagentGenerator,
205
236
  unregisterGoalHook,
206
237
  updateSetting
207
- } from "./chunk-YAINVQ3Q.js";
238
+ } from "./chunk-A5OPLHFM.js";
239
+ import "./chunk-5P5XGNYH.js";
208
240
  import {
209
241
  external_exports
210
242
  } from "./chunk-K5PGHDBN.js";
211
243
  import "./chunk-SKAZWEV5.js";
212
- import "./chunk-XJYUPHRC.js";
213
- import "./chunk-QLDPRILK.js";
214
- import "./chunk-BVSVIN45.js";
244
+ import "./chunk-QOMAHFYI.js";
245
+ import "./chunk-G7OQ3RFJ.js";
246
+ import "./chunk-LKWAVAB5.js";
215
247
  import {
216
248
  runSideQuery
217
- } from "./chunk-SBBN4BWR.js";
218
- import "./chunk-S7AKE7HL.js";
249
+ } from "./chunk-L7OJ3ZO3.js";
250
+ import "./chunk-R6RI2HIC.js";
219
251
  import "./chunk-2MPVVENX.js";
220
252
  import "./chunk-ZTQ26VBE.js";
221
- import "./chunk-O2VBQ3Y2.js";
253
+ import "./chunk-SJBIFVD2.js";
222
254
  import "./chunk-MLZQVCF3.js";
223
255
  import "./chunk-LQ7TMOCE.js";
224
- import "./chunk-LD2XBG6Z.js";
225
- import "./chunk-HYCC3XM3.js";
256
+ import "./chunk-E5A7LHNN.js";
257
+ import {
258
+ APPROVAL_MODES
259
+ } from "./chunk-BPFJQGY7.js";
260
+ import "./chunk-GO6LNQXT.js";
261
+ import "./chunk-B236XPGF.js";
226
262
  import {
227
263
  HookEventName,
228
264
  createHookOutput
229
- } from "./chunk-NL4C3H6C.js";
230
- import "./chunk-JOLIREYR.js";
231
- import "./chunk-DDMOU62Q.js";
265
+ } from "./chunk-YXK4GPYO.js";
266
+ import "./chunk-TOAUDIS7.js";
267
+ import "./chunk-NPQWQYU6.js";
232
268
  import {
233
269
  AuthType,
234
270
  ConversationFinishedEvent,
235
271
  DiscoveredMCPTool,
272
+ LoopDetectedEvent,
236
273
  PromptSuggestionEvent,
237
274
  UserPromptEvent,
238
275
  endToolExecutionSpan,
@@ -241,6 +278,7 @@ import {
241
278
  getAllGeminiMdFilenames,
242
279
  getMCPServerStatus,
243
280
  logConversationFinishedEvent,
281
+ logLoopDetected,
244
282
  logPromptSuggestion,
245
283
  logToolCall,
246
284
  logUserPrompt,
@@ -249,45 +287,52 @@ import {
249
287
  startToolSpan,
250
288
  uiTelemetryService,
251
289
  withInteractionSpan
252
- } from "./chunk-QNKBEJO2.js";
253
- import "./chunk-3PJXIDKI.js";
290
+ } from "./chunk-UA6XBZT7.js";
291
+ import "./chunk-E6US47KI.js";
254
292
  import "./chunk-UWCTAVOD.js";
255
293
  import "./chunk-OFEVLU4C.js";
256
294
  import "./chunk-OMX7CUOE.js";
257
295
  import {
258
- getAutoMemoryRoot
259
- } from "./chunk-RD6YJJJ6.js";
260
- import "./chunk-SKQZZVAV.js";
261
- import "./chunk-BUUQ5HPX.js";
296
+ getAutoMemoryRoot,
297
+ getUserAutoMemoryRoot
298
+ } from "./chunk-MOUXUCXQ.js";
299
+ import "./chunk-BZHLWC6T.js";
300
+ import "./chunk-DG573EOO.js";
262
301
  import {
263
302
  ToolConfirmationOutcome,
264
303
  ToolNames
265
- } from "./chunk-BX5YO7UW.js";
304
+ } from "./chunk-MTNTVOJR.js";
266
305
  import {
267
306
  clearCachedCredentialFile,
268
307
  qwenOAuth2Events
269
- } from "./chunk-UO5GVXQE.js";
270
- import "./chunk-KMGMKW5Q.js";
308
+ } from "./chunk-TENOUWY2.js";
309
+ import "./chunk-GLPV75ES.js";
271
310
  import "./chunk-22IFUCVR.js";
272
- import "./chunk-3227DZO4.js";
311
+ import "./chunk-T42ACHFI.js";
312
+ import "./chunk-SIUQ3YYX.js";
273
313
  import {
314
+ DEFAULT_TOKEN_LIMIT,
274
315
  tokenLimit
275
- } from "./chunk-U3KSMM7G.js";
276
- import "./chunk-BKXBARJZ.js";
277
- import "./chunk-EJYJYJZ2.js";
316
+ } from "./chunk-YTFBURQD.js";
317
+ import "./chunk-YPCPZSZB.js";
318
+ import "./chunk-IRJWHZWU.js";
319
+ import "./chunk-SYCJMSIJ.js";
278
320
  import "./chunk-55ZMG67I.js";
279
321
  import "./chunk-H6BD2ELD.js";
280
322
  import "./chunk-5IFG2VC4.js";
281
323
  import "./chunk-64WXLC72.js";
324
+ import "./chunk-D5KISTLW.js";
282
325
  import {
283
326
  Storage,
284
327
  createDebugLogger,
285
- getErrorStatus
286
- } from "./chunk-UX4SGCGN.js";
287
- import "./chunk-SMYQZVZ3.js";
328
+ getErrorMessage,
329
+ getErrorStatus,
330
+ isSubpath
331
+ } from "./chunk-AVQDCKNF.js";
332
+ import "./chunk-NOGSPTLJ.js";
288
333
  import {
289
334
  runExitCleanup
290
- } from "./chunk-I2LYBGNI.js";
335
+ } from "./chunk-7IOMIYOE.js";
291
336
  import "./chunk-ZERZSAZL.js";
292
337
  import "./chunk-QN5NZ3UQ.js";
293
338
  import "./chunk-BR4QREVK.js";
@@ -331,17 +376,50 @@ __name(pickAuthMethodsForAuthRequired, "pickAuthMethodsForAuthRequired");
331
376
 
332
377
  // packages/cli/src/acp-integration/service/filesystem.ts
333
378
  init_esbuild_shims();
379
+ import { realpath } from "node:fs/promises";
380
+ import path from "node:path";
334
381
  var RESOURCE_NOT_FOUND_CODE = -32002;
382
+ var PATH_OUTSIDE_WORKSPACE_KIND = "path_outside_workspace";
383
+ var SYMLINK_ESCAPE_KIND = "symlink_escape";
384
+ var LOCAL_READ_FALLBACK_ERROR_KINDS = /* @__PURE__ */ new Set([
385
+ PATH_OUTSIDE_WORKSPACE_KIND,
386
+ SYMLINK_ESCAPE_KIND
387
+ ]);
388
+ var debugLogger = createDebugLogger("ACP_FILE_SYSTEM");
389
+ function isRecord(value) {
390
+ return typeof value === "object" && value !== null && !Array.isArray(value);
391
+ }
392
+ __name(isRecord, "isRecord");
335
393
  function getErrorCode(error) {
336
394
  if (error instanceof RequestError) {
337
395
  return error.code;
338
396
  }
339
- if (typeof error === "object" && error !== null && "code" in error) {
340
- return error.code;
397
+ if (isRecord(error)) {
398
+ return error["code"];
341
399
  }
342
400
  return void 0;
343
401
  }
344
402
  __name(getErrorCode, "getErrorCode");
403
+ function getErrorData(error) {
404
+ const data = isRecord(error) ? error["data"] : void 0;
405
+ return isRecord(data) ? data : void 0;
406
+ }
407
+ __name(getErrorData, "getErrorData");
408
+ function getErrorKind(error) {
409
+ const data = getErrorData(error);
410
+ if (data && typeof data["errorKind"] === "string") {
411
+ return data["errorKind"];
412
+ }
413
+ return void 0;
414
+ }
415
+ __name(getErrorKind, "getErrorKind");
416
+ function normalizeError(error) {
417
+ if (error instanceof Error) return error;
418
+ return new Error(getErrorMessage(error), {
419
+ cause: error
420
+ });
421
+ }
422
+ __name(normalizeError, "normalizeError");
345
423
  function createEnoentError(filePath) {
346
424
  const err = new Error(`File not found: ${filePath}`);
347
425
  err.code = "ENOENT";
@@ -350,12 +428,32 @@ function createEnoentError(filePath) {
350
428
  return err;
351
429
  }
352
430
  __name(createEnoentError, "createEnoentError");
431
+ function isLocalReadFallbackErrorKind(errorKind) {
432
+ return typeof errorKind === "string" && LOCAL_READ_FALLBACK_ERROR_KINDS.has(errorKind);
433
+ }
434
+ __name(isLocalReadFallbackErrorKind, "isLocalReadFallbackErrorKind");
435
+ async function resolveRealPath(value) {
436
+ if (!value.trim()) return void 0;
437
+ try {
438
+ return await realpath(path.resolve(value));
439
+ } catch (error) {
440
+ if (getErrorCode(error) !== "ENOENT") {
441
+ debugLogger.warn("realpath failed during ACP local read fallback check", {
442
+ path: value,
443
+ error: getErrorMessage(error)
444
+ });
445
+ }
446
+ return void 0;
447
+ }
448
+ }
449
+ __name(resolveRealPath, "resolveRealPath");
353
450
  var AcpFileSystemService = class {
354
- constructor(connection, sessionId, capabilities, fallback) {
451
+ constructor(connection, sessionId, capabilities, fallback, options = {}) {
355
452
  this.connection = connection;
356
453
  this.sessionId = sessionId;
357
454
  this.capabilities = capabilities;
358
455
  this.fallback = fallback;
456
+ this.options = options;
359
457
  }
360
458
  static {
361
459
  __name(this, "AcpFileSystemService");
@@ -375,7 +473,45 @@ var AcpFileSystemService = class {
375
473
  if (errorCode === RESOURCE_NOT_FOUND_CODE) {
376
474
  throw createEnoentError(params.path);
377
475
  }
378
- throw error;
476
+ const errorKind = getErrorKind(error);
477
+ const shouldTryLocalReadFallback = isLocalReadFallbackErrorKind(errorKind);
478
+ const fallbackPath = shouldTryLocalReadFallback ? await this.getLocalReadFallbackPath(params.path) : void 0;
479
+ if (shouldTryLocalReadFallback && !fallbackPath) {
480
+ debugLogger.debug("Local read fallback skipped - no safe local path", {
481
+ path: params.path,
482
+ errorKind
483
+ });
484
+ }
485
+ if (shouldTryLocalReadFallback && fallbackPath) {
486
+ debugLogger.debug("Falling back to local read after ACP error", {
487
+ path: params.path,
488
+ resolvedPath: fallbackPath,
489
+ errorKind,
490
+ error: getErrorMessage(error)
491
+ });
492
+ try {
493
+ return await this.fallback.readTextFile({
494
+ ...params,
495
+ path: fallbackPath
496
+ });
497
+ } catch (fallbackError) {
498
+ if (getErrorCode(fallbackError) === "ENOENT") {
499
+ throw fallbackError;
500
+ }
501
+ debugLogger.warn("Local read fallback failed after ACP error", {
502
+ path: params.path,
503
+ resolvedPath: fallbackPath,
504
+ errorKind,
505
+ originalError: getErrorMessage(error),
506
+ fallbackError: getErrorMessage(fallbackError)
507
+ });
508
+ throw new Error(
509
+ `Local fallback read failed for ${params.path}: ${getErrorMessage(fallbackError)} (original ACP error: ${getErrorMessage(error)})`,
510
+ { cause: { fallbackError, acpError: error } }
511
+ );
512
+ }
513
+ }
514
+ throw normalizeError(error);
379
515
  }
380
516
  return response;
381
517
  }
@@ -384,27 +520,53 @@ var AcpFileSystemService = class {
384
520
  return this.fallback.writeTextFile(params);
385
521
  }
386
522
  const finalContent = params._meta?.["bom"] && params.content.charCodeAt(0) !== 65279 ? "\uFEFF" + params.content : params.content;
387
- await this.connection.writeTextFile({
388
- ...params,
389
- content: finalContent,
390
- sessionId: this.sessionId
391
- });
523
+ try {
524
+ await this.connection.writeTextFile({
525
+ ...params,
526
+ content: finalContent,
527
+ sessionId: this.sessionId
528
+ });
529
+ } catch (error) {
530
+ if (getErrorCode(error) === RESOURCE_NOT_FOUND_CODE) {
531
+ throw createEnoentError(params.path);
532
+ }
533
+ throw normalizeError(error);
534
+ }
392
535
  return { _meta: params._meta };
393
536
  }
394
537
  findFiles(fileName, searchPaths) {
395
538
  return this.fallback.findFiles(fileName, searchPaths);
396
539
  }
540
+ async getResolvedLocalReadRoots() {
541
+ const roots = await Promise.all(
542
+ (this.options.localReadRoots ?? []).map(resolveRealPath)
543
+ );
544
+ return roots.filter((root) => Boolean(root));
545
+ }
546
+ async getLocalReadFallbackPath(filePath) {
547
+ const normalizedFilePath = path.resolve(filePath);
548
+ const realFilePath = await resolveRealPath(normalizedFilePath);
549
+ if (!realFilePath) return void 0;
550
+ for (const realRoot of await this.getResolvedLocalReadRoots()) {
551
+ if (isSubpath(realRoot, realFilePath)) {
552
+ return realFilePath;
553
+ }
554
+ }
555
+ return void 0;
556
+ }
397
557
  };
398
558
 
399
559
  // packages/cli/src/acp-integration/acpAgent.ts
400
560
  import { Readable, Writable } from "node:stream";
401
561
  import { pipeline } from "node:stream/promises";
402
562
  import * as fs from "node:fs/promises";
403
- import * as path from "node:path";
563
+ import * as path3 from "node:path";
404
564
  import { createGunzip } from "node:zlib";
405
565
 
406
566
  // packages/cli/src/acp-integration/session/Session.ts
407
567
  init_esbuild_shims();
568
+ import * as os from "node:os";
569
+ import * as path2 from "node:path";
408
570
 
409
571
  // packages/cli/src/acp-integration/session/SubAgentTracker.ts
410
572
  init_esbuild_shims();
@@ -586,7 +748,7 @@ function toPermissionOptions(confirmation, forceHideAlwaysAllow = false) {
586
748
  __name(toPermissionOptions, "toPermissionOptions");
587
749
 
588
750
  // packages/cli/src/acp-integration/session/SubAgentTracker.ts
589
- var debugLogger = createDebugLogger("ACP_SUBAGENT_TRACKER");
751
+ var debugLogger2 = createDebugLogger("ACP_SUBAGENT_TRACKER");
590
752
  var SubAgentTracker = class {
591
753
  constructor(ctx, client, parentToolCallId, subagentType, onPermissionCancel) {
592
754
  this.ctx = ctx;
@@ -646,7 +808,7 @@ var SubAgentTracker = class {
646
808
  try {
647
809
  invocation = tool.build(event.args);
648
810
  } catch (e) {
649
- debugLogger.warn(`Failed to build subagent tool ${event.name}:`, e);
811
+ debugLogger2.warn(`Failed to build subagent tool ${event.name}:`, e);
650
812
  }
651
813
  }
652
814
  this.toolStates.set(event.callId, {
@@ -725,7 +887,7 @@ var SubAgentTracker = class {
725
887
  this.onPermissionCancel?.();
726
888
  }
727
889
  } catch (error) {
728
- debugLogger.error(
890
+ debugLogger2.error(
729
891
  `Permission request failed for subagent tool ${event.name}:`,
730
892
  error
731
893
  );
@@ -733,7 +895,7 @@ var SubAgentTracker = class {
733
895
  try {
734
896
  await event.respond("cancel" /* Cancel */);
735
897
  } catch (respondError) {
736
- debugLogger.error(
898
+ debugLogger2.error(
737
899
  `Failed to cancel subagent tool ${event.name} after permission request failure:`,
738
900
  respondError
739
901
  );
@@ -832,7 +994,7 @@ var TurnBuffer = class {
832
994
  init_esbuild_shims();
833
995
  import { readFileSync, existsSync } from "node:fs";
834
996
  import { resolve } from "node:path";
835
- var debugLogger2 = createDebugLogger("MESSAGE_REWRITER");
997
+ var debugLogger3 = createDebugLogger("MESSAGE_REWRITER");
836
998
  var DEFAULT_REWRITE_PROMPT = `You are an assistant that rewrites raw coding-agent output into concise, user-friendly progress updates.
837
999
 
838
1000
  The agent is a software engineering assistant that reads files, writes code, runs commands, and uses tools. Its raw output mixes internal reasoning with user-facing information. Your job: extract what the user cares about, drop what they don't.
@@ -861,11 +1023,11 @@ var LlmRewriter = class {
861
1023
  const filePath = resolve(rewriteConfig.promptFile);
862
1024
  if (existsSync(filePath)) {
863
1025
  this.prompt = readFileSync(filePath, "utf-8").trim();
864
- debugLogger2.info(
1026
+ debugLogger3.info(
865
1027
  `Loaded rewrite prompt from file: ${filePath} (${this.prompt.length} chars)`
866
1028
  );
867
1029
  } else {
868
- debugLogger2.warn(
1030
+ debugLogger3.warn(
869
1031
  `Rewrite prompt file not found: ${filePath}, using default`
870
1032
  );
871
1033
  this.prompt = DEFAULT_REWRITE_PROMPT;
@@ -902,7 +1064,7 @@ var LlmRewriter = class {
902
1064
  const inputText = inputParts.join("\n\n");
903
1065
  if (!inputText.trim()) return null;
904
1066
  if (inputText.length < 10) return null;
905
- debugLogger2.info(
1067
+ debugLogger3.info(
906
1068
  `[REWRITE INPUT] system_prompt_len=${this.prompt.length} input_len=${inputText.length} context_turns=${this.outputHistory.length}
907
1069
  --- INPUT TEXT ---
908
1070
  ${inputText}
@@ -931,10 +1093,10 @@ ${inputText}
931
1093
  });
932
1094
  const rewritten = result.text;
933
1095
  if (!rewritten || rewritten.length < 5) {
934
- debugLogger2.info(`[REWRITE OUTPUT] empty or too short, skipping`);
1096
+ debugLogger3.info(`[REWRITE OUTPUT] empty or too short, skipping`);
935
1097
  return null;
936
1098
  }
937
- debugLogger2.info(
1099
+ debugLogger3.info(
938
1100
  `[REWRITE OUTPUT] len=${rewritten.length}
939
1101
  --- OUTPUT ---
940
1102
  ${rewritten}
@@ -943,7 +1105,7 @@ ${rewritten}
943
1105
  this.outputHistory.push(rewritten);
944
1106
  return rewritten;
945
1107
  } catch (error) {
946
- debugLogger2.warn(
1108
+ debugLogger3.warn(
947
1109
  `LLM rewrite failed, skipping: ${error instanceof Error ? error.message : String(error)}`
948
1110
  );
949
1111
  return null;
@@ -952,7 +1114,7 @@ ${rewritten}
952
1114
  };
953
1115
 
954
1116
  // packages/cli/src/acp-integration/session/rewrite/MessageRewriteMiddleware.ts
955
- var debugLogger3 = createDebugLogger("MESSAGE_REWRITE");
1117
+ var debugLogger4 = createDebugLogger("MESSAGE_REWRITE");
956
1118
  var DEFAULT_REWRITE_TIMEOUT_MS = 3e4;
957
1119
  var REWRITE_META_EXCLUDED_KEYS = /* @__PURE__ */ new Set([]);
958
1120
  var MessageRewriteMiddleware = class {
@@ -1032,10 +1194,10 @@ var MessageRewriteMiddleware = class {
1032
1194
  try {
1033
1195
  const rewritten = await this.rewriter.rewrite(content, rewriteSignal);
1034
1196
  if (!rewritten) {
1035
- debugLogger3.info(`Turn ${turnIdx}: no rewrite output`);
1197
+ debugLogger4.info(`Turn ${turnIdx}: no rewrite output`);
1036
1198
  return;
1037
1199
  }
1038
- debugLogger3.info(
1200
+ debugLogger4.info(
1039
1201
  `Turn ${turnIdx}: rewritten ${rewritten.length} chars`
1040
1202
  );
1041
1203
  await this.sendUpdate({
@@ -1048,7 +1210,7 @@ var MessageRewriteMiddleware = class {
1048
1210
  }
1049
1211
  });
1050
1212
  } catch (error) {
1051
- debugLogger3.warn(
1213
+ debugLogger4.warn(
1052
1214
  `Turn ${turnIdx}: rewrite failed: ${error instanceof Error ? error.message : String(error)}`
1053
1215
  );
1054
1216
  }
@@ -1093,9 +1255,10 @@ function loadRewriteConfig(settings) {
1093
1255
  __name(loadRewriteConfig, "loadRewriteConfig");
1094
1256
 
1095
1257
  // packages/cli/src/acp-integration/session/Session.ts
1096
- var debugLogger4 = createDebugLogger("SESSION");
1258
+ var debugLogger5 = createDebugLogger("SESSION");
1097
1259
  var USER_CANCEL_ABORT_REASON = "qwen:user-cancel";
1098
1260
  var DAEMON_RETRY_META_KEY = "qwen.daemon.retry";
1261
+ var DAEMON_CONTINUE_META_KEY = "qwen.daemon.continueLastTurn";
1099
1262
  function maskApiKeyForDisplay(apiKey) {
1100
1263
  const trimmed = apiKey?.trim() ?? "";
1101
1264
  if (trimmed.length === 0) return "(not set)";
@@ -1103,7 +1266,58 @@ function maskApiKeyForDisplay(apiKey) {
1103
1266
  return `${trimmed.slice(0, 3)}...${trimmed.slice(-4)}`;
1104
1267
  }
1105
1268
  __name(maskApiKeyForDisplay, "maskApiKeyForDisplay");
1269
+ var DAEMON_TURN_TOOL_CALL_CAP = 100;
1270
+ var DAEMON_INVALID_TOOL_PARAMS_THRESHOLD = 3;
1106
1271
  var PERMISSION_CANCEL_SKIP_MESSAGE = "Skipped because a permission request was cancelled before the user answered; user input is required before continuing.";
1272
+ var LOOP_DETECTED_SKIP_MESSAGE = "Skipped because loop detection stopped the current turn before this tool call could run.";
1273
+ var LOOP_DETECTED_CONTEXT_MESSAGE = "System: this turn was terminated because the model exceeded tool-call safety limits. Try a different approach on the next turn.";
1274
+ function createDaemonToolLoopState() {
1275
+ return {
1276
+ totalToolCalls: 0,
1277
+ invalidToolParamErrors: /* @__PURE__ */ new Map(),
1278
+ loopDetected: false
1279
+ };
1280
+ }
1281
+ __name(createDaemonToolLoopState, "createDaemonToolLoopState");
1282
+ function recordDaemonLoopDetected(config, promptId, loopType, message, loopState) {
1283
+ if (!loopState.loopDetected) {
1284
+ loopState.loopDetected = true;
1285
+ debugLogger5.warn(message);
1286
+ logLoopDetected(config, new LoopDetectedEvent(loopType, promptId));
1287
+ }
1288
+ return true;
1289
+ }
1290
+ __name(recordDaemonLoopDetected, "recordDaemonLoopDetected");
1291
+ function recordDaemonToolCalls(config, promptId, loopState, count) {
1292
+ if (!loopState || loopState.loopDetected)
1293
+ return loopState?.loopDetected ?? false;
1294
+ loopState.totalToolCalls += count;
1295
+ if (loopState.totalToolCalls <= DAEMON_TURN_TOOL_CALL_CAP) return false;
1296
+ return recordDaemonLoopDetected(
1297
+ config,
1298
+ promptId,
1299
+ "turn_tool_call_cap" /* TURN_TOOL_CALL_CAP */,
1300
+ `Stopping ACP turn after ${loopState.totalToolCalls} tool calls in one turn.`,
1301
+ loopState
1302
+ );
1303
+ }
1304
+ __name(recordDaemonToolCalls, "recordDaemonToolCalls");
1305
+ function recordDaemonInvalidToolParams(config, promptId, loopState, toolName, error) {
1306
+ if (!loopState || loopState.loopDetected)
1307
+ return loopState?.loopDetected ?? false;
1308
+ const key = toolName;
1309
+ const count = (loopState.invalidToolParamErrors.get(key) ?? 0) + 1;
1310
+ loopState.invalidToolParamErrors.set(key, count);
1311
+ if (count < DAEMON_INVALID_TOOL_PARAMS_THRESHOLD) return false;
1312
+ return recordDaemonLoopDetected(
1313
+ config,
1314
+ promptId,
1315
+ "invalid_tool_params_stagnation" /* INVALID_TOOL_PARAMS_STAGNATION */,
1316
+ `Stopping ACP turn after repeated tool parameter errors from ${toolName}: ${error.message}`,
1317
+ loopState
1318
+ );
1319
+ }
1320
+ __name(recordDaemonInvalidToolParams, "recordDaemonInvalidToolParams");
1107
1321
  var MID_TURN_QUEUE_DRAIN_TIMEOUT_MS = 2e3;
1108
1322
  var MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS = 3e4;
1109
1323
  var MID_TURN_QUEUE_RESOLVE_TIMEOUT_MS = 1e4;
@@ -1111,12 +1325,20 @@ var MAX_MID_TURN_DRAIN_ITEMS = 10;
1111
1325
  var MID_TURN_ATTACHMENT_PROCESSING_FAILURE_TEXT = "[Attachment could not be processed]";
1112
1326
  var MAX_MID_TURN_RESOURCE_TEXT_LENGTH = 1e5;
1113
1327
  var MID_TURN_QUEUE_DRAIN_MAX_TIMEOUT_STRIKES = 3;
1114
- function isRecord(value) {
1328
+ var TRANSIENT_FS_CODES = [
1329
+ "EACCES",
1330
+ "EIO",
1331
+ "EBUSY",
1332
+ "EPERM",
1333
+ "EISDIR",
1334
+ "ENOTDIR"
1335
+ ];
1336
+ function isRecord2(value) {
1115
1337
  return value !== null && typeof value === "object";
1116
1338
  }
1117
- __name(isRecord, "isRecord");
1339
+ __name(isRecord2, "isRecord");
1118
1340
  function isContentBlock(value) {
1119
- if (!isRecord(value) || typeof value["type"] !== "string") return false;
1341
+ if (!isRecord2(value) || typeof value["type"] !== "string") return false;
1120
1342
  switch (value["type"]) {
1121
1343
  case "text":
1122
1344
  return typeof value["text"] === "string";
@@ -1129,7 +1351,7 @@ function isContentBlock(value) {
1129
1351
  case "resource":
1130
1352
  return isEmbeddedResourceResource(value["resource"]);
1131
1353
  default:
1132
- debugLogger4.warn(`Unknown ContentBlock type: ${value["type"]}`);
1354
+ debugLogger5.warn(`Unknown ContentBlock type: ${value["type"]}`);
1133
1355
  return false;
1134
1356
  }
1135
1357
  }
@@ -1155,7 +1377,7 @@ async function withTimeoutSignal(parentSignal, timeoutMs, fn) {
1155
1377
  }
1156
1378
  __name(withTimeoutSignal, "withTimeoutSignal");
1157
1379
  function isEmbeddedResourceResource(value) {
1158
- if (!isRecord(value) || typeof value["uri"] !== "string") return false;
1380
+ if (!isRecord2(value) || typeof value["uri"] !== "string") return false;
1159
1381
  if (typeof value["text"] === "string") {
1160
1382
  return value["text"].length <= MAX_MID_TURN_RESOURCE_TEXT_LENGTH;
1161
1383
  }
@@ -1168,7 +1390,7 @@ function hasInlineMediaContentBlock(content) {
1168
1390
  __name(hasInlineMediaContentBlock, "hasInlineMediaContentBlock");
1169
1391
  function capMidTurnDrainItems(items, fieldName) {
1170
1392
  if (items.length <= MAX_MID_TURN_DRAIN_ITEMS) return items;
1171
- debugLogger4.warn(
1393
+ debugLogger5.warn(
1172
1394
  `Mid-turn drain response had ${items.length} ${fieldName}; processing first ${MAX_MID_TURN_DRAIN_ITEMS}`
1173
1395
  );
1174
1396
  return items.slice(0, MAX_MID_TURN_DRAIN_ITEMS);
@@ -1183,7 +1405,7 @@ function getMidTurnItemDisplayTextForLog(displayText) {
1183
1405
  __name(getMidTurnItemDisplayTextForLog, "getMidTurnItemDisplayTextForLog");
1184
1406
  function getValidMidTurnContentBlocks(content, displayText) {
1185
1407
  if (!Array.isArray(content)) {
1186
- debugLogger4.warn(
1408
+ debugLogger5.warn(
1187
1409
  `Dropped invalid mid-turn item: ${getMidTurnItemDisplayTextForLog(
1188
1410
  displayText
1189
1411
  )}`
@@ -1193,7 +1415,7 @@ function getValidMidTurnContentBlocks(content, displayText) {
1193
1415
  const validBlocks = content.filter(isContentBlock);
1194
1416
  const invalidBlockCount = content.length - validBlocks.length;
1195
1417
  if (invalidBlockCount > 0) {
1196
- debugLogger4.warn(
1418
+ debugLogger5.warn(
1197
1419
  `Dropped ${invalidBlockCount} invalid mid-turn content block(s): ${getMidTurnItemDisplayTextForLog(
1198
1420
  displayText
1199
1421
  )}`
@@ -1213,11 +1435,11 @@ function getStructuredMidTurnDisplayText(content, displayText) {
1213
1435
  }
1214
1436
  __name(getStructuredMidTurnDisplayText, "getStructuredMidTurnDisplayText");
1215
1437
  function parseMidTurnDrainResponse(response) {
1216
- if (!isRecord(response)) return [];
1438
+ if (!isRecord2(response)) return [];
1217
1439
  if (Array.isArray(response["items"])) {
1218
1440
  return capMidTurnDrainItems(response["items"], "item(s)").flatMap(
1219
1441
  (item) => {
1220
- if (!isRecord(item)) {
1442
+ if (!isRecord2(item)) {
1221
1443
  return [];
1222
1444
  }
1223
1445
  const content = getValidMidTurnContentBlocks(
@@ -1239,7 +1461,7 @@ function parseMidTurnDrainResponse(response) {
1239
1461
  );
1240
1462
  }
1241
1463
  if (!Array.isArray(response["messages"])) {
1242
- debugLogger4.warn(
1464
+ debugLogger5.warn(
1243
1465
  `Mid-turn drain response had no recognized 'items' or 'messages' field; keys: ${Object.keys(
1244
1466
  response
1245
1467
  ).join(", ")}`
@@ -1262,6 +1484,17 @@ var MidTurnDrainTimeoutError = class extends Error {
1262
1484
  }
1263
1485
  };
1264
1486
  var MAX_NOTIFICATION_QUEUE = 20;
1487
+ function resolveHomeLoopResolverRoots({
1488
+ homeQwenDir = Storage.getGlobalQwenDir(),
1489
+ homeDir = os.homedir(),
1490
+ qwenHome = process.env["QWEN_HOME"]
1491
+ } = {}) {
1492
+ return {
1493
+ homeConfineRoot: (qwenHome ? homeQwenDir : homeDir) || path2.dirname(homeQwenDir),
1494
+ homeQwenDir
1495
+ };
1496
+ }
1497
+ __name(resolveHomeLoopResolverRoots, "resolveHomeLoopResolverRoots");
1265
1498
  function computeInitialTurnFromHistory(records, sessionId) {
1266
1499
  let maxPromptTurn = 0;
1267
1500
  let userMessageCount = 0;
@@ -1296,7 +1529,7 @@ async function fireSessionPermissionDeniedForAutoMode(config, decision, outcome,
1296
1529
  callId
1297
1530
  );
1298
1531
  } catch (hookError) {
1299
- debugLogger4.warn(
1532
+ debugLogger5.warn(
1300
1533
  `PermissionDenied hook failed for tool ${callId}: ${hookError instanceof Error ? hookError.message : String(hookError)}`
1301
1534
  );
1302
1535
  }
@@ -1337,6 +1570,18 @@ function isUserPromptRecord(record) {
1337
1570
  ) ?? false;
1338
1571
  }
1339
1572
  __name(isUserPromptRecord, "isUserPromptRecord");
1573
+ var AT_TOKEN_RE = /@([^\s,;!?()[\]{}]+)/g;
1574
+ function collectExtensionMentionRefs(text, mentions) {
1575
+ for (const match of text.matchAll(AT_TOKEN_RE)) {
1576
+ const pathName = match[1];
1577
+ if (!pathName) continue;
1578
+ const ref = parseExtensionRef(pathName);
1579
+ if (ref) {
1580
+ mentions.set(ref.name.toLowerCase(), ref.name);
1581
+ }
1582
+ }
1583
+ }
1584
+ __name(collectExtensionMentionRefs, "collectExtensionMentionRefs");
1340
1585
  async function buildAvailableCommandsSnapshot(config, abortSignal = AbortSignal.timeout(1e4), settings) {
1341
1586
  const slashCommands = await getAvailableCommands(
1342
1587
  config,
@@ -1356,7 +1601,12 @@ async function buildAvailableCommandsSnapshot(config, abortSignal = AbortSignal.
1356
1601
  sourceLabel: cmd.sourceLabel,
1357
1602
  supportedModes: getEffectiveSupportedModes(cmd),
1358
1603
  subcommands: getCommandSubcommandNames(cmd),
1359
- modelInvocable: cmd.modelInvocable === true
1604
+ modelInvocable: cmd.modelInvocable === true,
1605
+ // Carry aliases so a channel consumer (which only sees the wire snapshot,
1606
+ // not the command registry) can recognize an aliased command and avoid
1607
+ // tagging it. _meta is ACP's extension point; omitted when there are none
1608
+ // so command entries without aliases stay byte-identical on the wire.
1609
+ ...cmd.altNames && cmd.altNames.length > 0 ? { altNames: cmd.altNames } : {}
1360
1610
  }
1361
1611
  };
1362
1612
  });
@@ -1379,7 +1629,7 @@ async function buildAvailableCommandsSnapshot(config, abortSignal = AbortSignal.
1379
1629
  }
1380
1630
  }
1381
1631
  } catch (error) {
1382
- debugLogger4.error("Error loading available skills:", error);
1632
+ debugLogger5.error("Error loading available skills:", error);
1383
1633
  }
1384
1634
  for (const command of slashCommands) {
1385
1635
  if (command.kind !== "skill" /* SKILL */ || !command.skillDetail) {
@@ -1451,6 +1701,13 @@ var Session = class {
1451
1701
  cronQueue = [];
1452
1702
  cronProcessing = false;
1453
1703
  cronAbortController = null;
1704
+ // Resolves the `<<loop.md>>` / `<<loop.md-dynamic>>` sentinels at fire time.
1705
+ // Lazily created on the first loop tick; its content cache is reset on
1706
+ // compaction (see #sendMessageStreamWithAutoCompression) and it is rebuilt if
1707
+ // the working dir changes (e.g. /cd) so it always reads the current project's
1708
+ // loop.md.
1709
+ loopTickResolver = null;
1710
+ loopTickResolverRoot = null;
1454
1711
  cronCompletion = null;
1455
1712
  cronDisabledByTokenLimit = false;
1456
1713
  lastPromptTokenCount = 0;
@@ -1514,7 +1771,7 @@ var Session = class {
1514
1771
  */
1515
1772
  startCronScheduler() {
1516
1773
  this.#startCronSchedulerIfNeeded().catch((error) => {
1517
- debugLogger4.warn(
1774
+ debugLogger5.warn(
1518
1775
  `Cron scheduler startup failed [session ${this.sessionId}]: ${error}`
1519
1776
  );
1520
1777
  });
@@ -1561,7 +1818,7 @@ var Session = class {
1561
1818
  installRewriter() {
1562
1819
  const rewriteConfig = loadRewriteConfig(this.settings);
1563
1820
  if (rewriteConfig?.enabled) {
1564
- debugLogger4.info("Message rewrite middleware enabled");
1821
+ debugLogger5.info("Message rewrite middleware enabled");
1565
1822
  this.messageRewriter = new MessageRewriteMiddleware(
1566
1823
  this.config,
1567
1824
  rewriteConfig,
@@ -1572,7 +1829,7 @@ var Session = class {
1572
1829
  #installGoalTerminalObserver() {
1573
1830
  setGoalTerminalObserver(this.sessionId, (event) => {
1574
1831
  void this.messageEmitter.emitGoalTerminal(event).catch((error) => {
1575
- debugLogger4.warn(
1832
+ debugLogger5.warn(
1576
1833
  `Failed to emit goal terminal update: ${this.#formatError(error)}`
1577
1834
  );
1578
1835
  });
@@ -1580,7 +1837,7 @@ var Session = class {
1580
1837
  }
1581
1838
  emitGoalStatus(status) {
1582
1839
  void this.messageEmitter.emitGoalStatus(status).catch((error) => {
1583
- debugLogger4.warn(
1840
+ debugLogger5.warn(
1584
1841
  `Failed to emit goal status update: ${this.#formatError(error)}`
1585
1842
  );
1586
1843
  });
@@ -1783,6 +2040,36 @@ var Session = class {
1783
2040
  this.pendingPromptCompletion = null;
1784
2041
  }
1785
2042
  }
2043
+ /**
2044
+ * Classify whether an unfinished previous turn can be resumed — an
2045
+ * interrupted prompt (the model never answered) or a turn left with dangling
2046
+ * tool calls — without injecting a synthetic "continue" user message.
2047
+ * Classifies from persisted history. Idempotent no-op (accepted:false) when
2048
+ * the last turn ended cleanly or a prompt is already in flight.
2049
+ *
2050
+ * This is the accept/reject pre-check only — it does NOT fire the turn. When
2051
+ * accepted, the daemon bridge drives the continuation through the normal
2052
+ * prompt-admission path (`sendPrompt` with the trusted continue meta) so it is
2053
+ * tracked like any other prompt; `prompt()` then re-detects/strips
2054
+ * authoritatively. Powers `qwen/control/session/continue`.
2055
+ */
2056
+ async continueLastTurn() {
2057
+ const geminiClient = this.config.getGeminiClient();
2058
+ if (!geminiClient || !geminiClient.isInitialized()) {
2059
+ return { accepted: false, interruption: "none" };
2060
+ }
2061
+ const chat = this.#getCurrentChat();
2062
+ const detection = detectTurnInterruption(
2063
+ chat.getHistoryTailShallow?.(TURN_INTERRUPTION_HISTORY_TAIL_COUNT) ?? chat.getHistoryTail(TURN_INTERRUPTION_HISTORY_TAIL_COUNT)
2064
+ );
2065
+ if (detection.kind === "none") {
2066
+ return { accepted: false, interruption: "none" };
2067
+ }
2068
+ if (this.pendingPrompt && !this.pendingPrompt.signal.aborted) {
2069
+ return { accepted: false, interruption: detection.kind };
2070
+ }
2071
+ return { accepted: true, interruption: detection.kind };
2072
+ }
1786
2073
  /**
1787
2074
  * Generate a server-side follow-up suggestion for the just-completed
1788
2075
  * turn and push it to attached clients via the daemon's
@@ -1819,7 +2106,7 @@ var Session = class {
1819
2106
  const fullHistory = chat.getHistory(true);
1820
2107
  const lastEntry = fullHistory[fullHistory.length - 1];
1821
2108
  if (!lastEntry || lastEntry.role !== "model") {
1822
- debugLogger4.debug(
2109
+ debugLogger5.debug(
1823
2110
  "Skipping followup suggestion: last history entry is not model"
1824
2111
  );
1825
2112
  return;
@@ -1855,9 +2142,9 @@ var Session = class {
1855
2142
  }
1856
2143
  } catch (error) {
1857
2144
  if (ac.signal.aborted) {
1858
- debugLogger4.debug("Follow-up suggestion generation aborted");
2145
+ debugLogger5.debug("Follow-up suggestion generation aborted");
1859
2146
  } else {
1860
- debugLogger4.warn("Follow-up suggestion generation failed", error);
2147
+ debugLogger5.warn("Follow-up suggestion generation failed", error);
1861
2148
  }
1862
2149
  } finally {
1863
2150
  if (this.followupAbort === ac) {
@@ -1900,7 +2187,40 @@ var Session = class {
1900
2187
  )
1901
2188
  );
1902
2189
  const isRetry = params.retry === true || params._meta?.[DAEMON_RETRY_META_KEY] === true;
1903
- if (isRetry) {
2190
+ const isContinue = params._meta?.[DAEMON_CONTINUE_META_KEY] === true;
2191
+ let continuationParts = null;
2192
+ let strippedOrphanEntries = null;
2193
+ let orphanPushCountSnapshot = 0;
2194
+ if (isContinue) {
2195
+ const detection = detectTurnInterruption(
2196
+ this.#getCurrentChat().getHistory()
2197
+ );
2198
+ if (detection.kind === "none") {
2199
+ debugLogger5.warn(
2200
+ `[Session] continue ${promptId}: no interrupted turn on re-detection, nothing to continue`
2201
+ );
2202
+ logConversationFinishedEvent(
2203
+ this.config,
2204
+ new ConversationFinishedEvent(
2205
+ this.config.getApprovalMode(),
2206
+ 0
2207
+ )
2208
+ );
2209
+ return { stopReason: "end_turn" };
2210
+ }
2211
+ if (detection.kind === "interrupted_prompt") {
2212
+ strippedOrphanEntries = this.#getCurrentChat().stripOrphanedUserEntriesFromHistory() ?? null;
2213
+ orphanPushCountSnapshot = this.#getCurrentChat().getUserContentPushCount?.() ?? 0;
2214
+ continuationParts = detection.parts;
2215
+ } else {
2216
+ continuationParts = buildSyntheticToolResponseParts(
2217
+ detection.danglingCalls,
2218
+ ORPHAN_TOOL_USE_REPAIR_REASON
2219
+ );
2220
+ }
2221
+ }
2222
+ if (isContinue) {
2223
+ } else if (isRetry) {
1904
2224
  this.#getCurrentChat().stripOrphanedUserEntriesFromHistory();
1905
2225
  } else {
1906
2226
  this.config.getChatRecordingService()?.recordUserMessage(promptText);
@@ -1910,7 +2230,9 @@ var Session = class {
1910
2230
  );
1911
2231
  const inputText = firstTextBlock?.text || "";
1912
2232
  let parts;
1913
- if (isSlashCommand(inputText)) {
2233
+ if (isContinue) {
2234
+ parts = continuationParts;
2235
+ } else if (isSlashCommand(inputText)) {
1914
2236
  const slashCommandResult = await handleSlashCommand(
1915
2237
  inputText,
1916
2238
  pendingSend,
@@ -1932,7 +2254,7 @@ var Session = class {
1932
2254
  }
1933
2255
  const hooksEnabled = !this.config.getDisableAllHooks?.();
1934
2256
  const messageBus = this.config.getMessageBus?.();
1935
- if (hooksEnabled && messageBus && this.config.hasHooksForEvent?.("UserPromptSubmit")) {
2257
+ if (!isContinue && hooksEnabled && messageBus && this.config.hasHooksForEvent?.("UserPromptSubmit")) {
1936
2258
  const response = await messageBus.request(
1937
2259
  {
1938
2260
  type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
@@ -1948,7 +2270,7 @@ var Session = class {
1948
2270
  if (hookOutput?.isBlockingDecision() || hookOutput?.shouldStopExecution()) {
1949
2271
  const blockReason = hookOutput?.getEffectiveReason() || "No reason provided";
1950
2272
  await this.messageEmitter.emitAgentMessage(
1951
- `\u{1F6AB} **UserPromptSubmit blocked**: ${blockReason}`
2273
+ `\u2717 **UserPromptSubmit blocked**: ${blockReason}`
1952
2274
  );
1953
2275
  return { stopReason: "end_turn" };
1954
2276
  }
@@ -1966,30 +2288,34 @@ var Session = class {
1966
2288
  this.config.getChatRecordingService()?.recordFileHistorySnapshot(latestSnapshot);
1967
2289
  }
1968
2290
  } catch (e) {
1969
- debugLogger4.error(`FileHistory: recordSnapshot failed: ${e}`);
2291
+ debugLogger5.error(`FileHistory: recordSnapshot failed: ${e}`);
1970
2292
  }
1971
2293
  } catch (e) {
1972
- debugLogger4.error(`FileHistory: makeSnapshot failed: ${e}`);
2294
+ debugLogger5.error(`FileHistory: makeSnapshot failed: ${e}`);
1973
2295
  }
1974
2296
  const systemReminders = await this.#buildInitialSystemReminders();
1975
2297
  if (systemReminders.length > 0) {
1976
- parts = [...systemReminders, ...parts];
2298
+ const alreadyHasReminder = isContinue && parts.some(
2299
+ (part) => isSystemReminderContent({ role: "user", parts: [part] })
2300
+ );
2301
+ if (!alreadyHasReminder) {
2302
+ parts = insertAfterFunctionResponses(parts, systemReminders);
2303
+ }
1977
2304
  }
1978
2305
  if (this.pendingWorktreeNotice) {
1979
- parts = [
1980
- {
1981
- text: `<system-reminder>
2306
+ const noticePart = {
2307
+ text: `<system-reminder>
1982
2308
  ${this.pendingWorktreeNotice}
1983
2309
  </system-reminder>
1984
2310
 
1985
2311
  `
1986
- },
1987
- ...parts
1988
- ];
2312
+ };
2313
+ parts = insertAfterFunctionResponses(parts, [noticePart]);
1989
2314
  this.pendingWorktreeNotice = null;
1990
2315
  }
1991
2316
  let nextMessage = { role: "user", parts };
1992
2317
  let turnCount = 0;
2318
+ const toolLoopState = createDaemonToolLoopState();
1993
2319
  try {
1994
2320
  while (nextMessage !== null) {
1995
2321
  turnCount++;
@@ -2009,7 +2335,7 @@ ${this.pendingWorktreeNotice}
2009
2335
  if (!sendResult.responseStream) {
2010
2336
  this.#preserveUnsentMessageHistory(
2011
2337
  nextMessage,
2012
- sendResult.stopReason === "cancelled"
2338
+ isContinue || sendResult.stopReason === "cancelled"
2013
2339
  );
2014
2340
  return { stopReason: sendResult.stopReason };
2015
2341
  }
@@ -2040,6 +2366,12 @@ ${this.pendingWorktreeNotice}
2040
2366
  }
2041
2367
  }
2042
2368
  } catch (error) {
2369
+ if (strippedOrphanEntries && (this.#getCurrentChat().getUserContentPushCount?.() ?? 0) <= orphanPushCountSnapshot) {
2370
+ for (const entry of strippedOrphanEntries) {
2371
+ this.#getCurrentChat().addHistory(entry);
2372
+ }
2373
+ strippedOrphanEntries = null;
2374
+ }
2043
2375
  if (pendingSend.signal.aborted && pendingSend.signal.reason === USER_CANCEL_ABORT_REASON && this.#isAbortError(error)) {
2044
2376
  return { stopReason: "cancelled" };
2045
2377
  }
@@ -2053,7 +2385,7 @@ ${this.pendingWorktreeNotice}
2053
2385
  const hooksEnabledForStopFailure = !this.config.getDisableAllHooks?.();
2054
2386
  if (hooksEnabledForStopFailure && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
2055
2387
  hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
2056
- debugLogger4.warn(`StopFailure hook failed: ${err}`);
2388
+ debugLogger5.warn(`StopFailure hook failed: ${err}`);
2057
2389
  });
2058
2390
  }
2059
2391
  if (errorStatus === 429) {
@@ -2080,10 +2412,11 @@ ${this.pendingWorktreeNotice}
2080
2412
  const toolRun = await this.runToolCalls(
2081
2413
  pendingSend.signal,
2082
2414
  promptId,
2083
- functionCalls
2415
+ functionCalls,
2416
+ toolLoopState
2084
2417
  );
2085
2418
  if (toolRun.stopAfterPermissionCancel) {
2086
- await this.#preserveCancelledPermissionToolRun(
2419
+ await this.#preserveStoppedToolRun(
2087
2420
  toolRun,
2088
2421
  pendingSend.signal
2089
2422
  );
@@ -2093,6 +2426,13 @@ ${this.pendingWorktreeNotice}
2093
2426
  toolRun,
2094
2427
  pendingSend.signal
2095
2428
  );
2429
+ if (toolRun.loopDetected) {
2430
+ await this.#preserveStoppedToolRun(
2431
+ toolRun,
2432
+ pendingSend.signal
2433
+ );
2434
+ return { stopReason: "end_turn" };
2435
+ }
2096
2436
  }
2097
2437
  }
2098
2438
  if (this.messageRewriter) {
@@ -2140,13 +2480,18 @@ ${this.pendingWorktreeNotice}
2140
2480
  return { stopReason: "end_turn" };
2141
2481
  }
2142
2482
  const responseText = this.#getCurrentChat().getLastModelMessageText?.() || "[no response text]";
2483
+ const contextUsage = buildContextUsage(
2484
+ this.config.getContentGeneratorConfig()?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT,
2485
+ this.lastPromptTokenCount
2486
+ );
2143
2487
  const response = await messageBus.request(
2144
2488
  {
2145
2489
  type: "hook-execution-request" /* HOOK_EXECUTION_REQUEST */,
2146
2490
  eventName: "Stop",
2147
2491
  input: {
2148
2492
  stop_hook_active: true,
2149
- last_assistant_message: responseText
2493
+ last_assistant_message: responseText,
2494
+ ...contextUsage
2150
2495
  },
2151
2496
  signal: pendingSend.signal
2152
2497
  },
@@ -2175,7 +2520,7 @@ ${this.pendingWorktreeNotice}
2175
2520
  warning
2176
2521
  );
2177
2522
  await this.messageEmitter.emitAgentMessage(warning);
2178
- debugLogger4.warn(warning);
2523
+ debugLogger5.warn(warning);
2179
2524
  return { stopReason: "end_turn" };
2180
2525
  }
2181
2526
  if (stopHookIterationCount > 1) {
@@ -2190,6 +2535,7 @@ ${this.pendingWorktreeNotice}
2190
2535
  role: "user",
2191
2536
  parts: continueParts
2192
2537
  };
2538
+ const toolLoopState = createDaemonToolLoopState();
2193
2539
  while (nextMessage !== null) {
2194
2540
  if (pendingSend.signal.aborted) {
2195
2541
  return { stopReason: "cancelled" };
@@ -2246,7 +2592,7 @@ ${this.pendingWorktreeNotice}
2246
2592
  const hooksEnabledForStopFailure = !this.config.getDisableAllHooks?.();
2247
2593
  if (hooksEnabledForStopFailure && hookSystem && this.config.hasHooksForEvent?.("StopFailure")) {
2248
2594
  hookSystem.fireStopFailureEvent(errorType, errorMessage).catch((err) => {
2249
- debugLogger4.warn(`StopFailure hook failed: ${err}`);
2595
+ debugLogger5.warn(`StopFailure hook failed: ${err}`);
2250
2596
  });
2251
2597
  }
2252
2598
  if (errorStatus === 429) {
@@ -2270,19 +2616,21 @@ ${this.pendingWorktreeNotice}
2270
2616
  const toolRun = await this.runToolCalls(
2271
2617
  pendingSend.signal,
2272
2618
  promptId,
2273
- functionCalls
2619
+ functionCalls,
2620
+ toolLoopState
2274
2621
  );
2275
2622
  if (toolRun.stopAfterPermissionCancel) {
2276
- await this.#preserveCancelledPermissionToolRun(
2277
- toolRun,
2278
- pendingSend.signal
2279
- );
2623
+ await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
2280
2624
  return { stopReason: "end_turn" };
2281
2625
  }
2282
2626
  nextMessage = await this.#buildNextMessageAfterToolRun(
2283
2627
  toolRun,
2284
2628
  pendingSend.signal
2285
2629
  );
2630
+ if (toolRun.loopDetected) {
2631
+ await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
2632
+ return { stopReason: "end_turn" };
2633
+ }
2286
2634
  }
2287
2635
  }
2288
2636
  continue;
@@ -2323,21 +2671,22 @@ ${this.pendingWorktreeNotice}
2323
2671
  compressionInfo = compressed;
2324
2672
  this.#recordCompressionTokenCount(compressed);
2325
2673
  if (compressed.compressionStatus === 1 /* COMPRESSED */) {
2674
+ this.loopTickResolver?.resetCache();
2326
2675
  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()}`;
2327
2676
  compressionDiagnostic = `IMPORTANT: This conversation ${reasonClause}. A compressed context will be sent for future messages (compressed from: ${compressed.originalTokenCount ?? "unknown"} to ${compressed.newTokenCount ?? "unknown"} tokens).`;
2328
2677
  }
2329
2678
  } catch (compressionError) {
2330
2679
  if (abortSignal.aborted || this.#isAbortError(compressionError)) {
2331
- debugLogger4.debug(`Auto-compression aborted for prompt ${promptId}`);
2680
+ debugLogger5.debug(`Auto-compression aborted for prompt ${promptId}`);
2332
2681
  return { responseStream: null, stopReason: "cancelled" };
2333
2682
  }
2334
- debugLogger4.warn(
2683
+ debugLogger5.warn(
2335
2684
  `Auto-compression failed for prompt ${promptId}; proceeding without compression: ` + this.#formatError(compressionError)
2336
2685
  );
2337
2686
  }
2338
2687
  }
2339
2688
  if (abortSignal.aborted) {
2340
- debugLogger4.debug(`Auto-compression aborted for prompt ${promptId}`);
2689
+ debugLogger5.debug(`Auto-compression aborted for prompt ${promptId}`);
2341
2690
  return { responseStream: null, stopReason: "cancelled" };
2342
2691
  }
2343
2692
  if (!compressionInfo) {
@@ -2347,7 +2696,7 @@ ${this.pendingWorktreeNotice}
2347
2696
  if (sessionTokenLimit > 0) {
2348
2697
  const lastPromptTokenCount = this.#getPostCompressionTokenCount(compressionInfo);
2349
2698
  if (lastPromptTokenCount > sessionTokenLimit) {
2350
- debugLogger4.warn(
2699
+ debugLogger5.warn(
2351
2700
  `Session token limit exceeded for prompt ${promptId}: ${lastPromptTokenCount} > ${sessionTokenLimit}. Send dropped.`
2352
2701
  );
2353
2702
  await this.#emitAgentDiagnosticMessageSafely(
@@ -2364,7 +2713,7 @@ ${this.pendingWorktreeNotice}
2364
2713
  );
2365
2714
  }
2366
2715
  if (abortSignal.aborted) {
2367
- debugLogger4.debug(
2716
+ debugLogger5.debug(
2368
2717
  `Send aborted after compression diagnostic for prompt ${promptId}`
2369
2718
  );
2370
2719
  return { responseStream: null, stopReason: "cancelled" };
@@ -2392,7 +2741,7 @@ ${this.pendingWorktreeNotice}
2392
2741
  ) ?? [];
2393
2742
  const droppedParts = (message.parts?.length ?? 0) - functionResponseParts.length;
2394
2743
  if (droppedParts > 0) {
2395
- debugLogger4.debug(
2744
+ debugLogger5.debug(
2396
2745
  `Dropping ${droppedParts} non-functionResponse part(s) from unsent ACP message after send was skipped.`
2397
2746
  );
2398
2747
  }
@@ -2403,12 +2752,13 @@ ${this.pendingWorktreeNotice}
2403
2752
  });
2404
2753
  }
2405
2754
  }
2406
- async #preserveCancelledPermissionToolRun(toolRun, abortSignal) {
2755
+ async #preserveStoppedToolRun(toolRun, abortSignal) {
2407
2756
  this.#preserveUnsentMessageHistory(
2408
2757
  {
2409
2758
  role: "user",
2410
2759
  parts: [
2411
2760
  ...toolRun.parts,
2761
+ ...toolRun.loopDetected ? [{ text: LOOP_DETECTED_CONTEXT_MESSAGE }] : [],
2412
2762
  ...await this.#drainMidTurnUserMessages(abortSignal)
2413
2763
  ]
2414
2764
  },
@@ -2417,8 +2767,12 @@ ${this.pendingWorktreeNotice}
2417
2767
  await this.messageRewriter?.waitForPendingRewrites();
2418
2768
  }
2419
2769
  async #buildNextMessageAfterToolRun(toolRun, abortSignal) {
2770
+ if (toolRun.loopDetected) {
2771
+ debugLogger5.debug("Stopping ACP turn after daemon loop detection.");
2772
+ return null;
2773
+ }
2420
2774
  if (toolRun.repeatedDuplicateProviderToolCall) {
2421
- debugLogger4.debug(
2775
+ debugLogger5.debug(
2422
2776
  "Stopping ACP turn after dropping repeated duplicate provider tool-call response."
2423
2777
  );
2424
2778
  return null;
@@ -2496,7 +2850,7 @@ ${this.pendingWorktreeNotice}
2496
2850
  try {
2497
2851
  await this.#emitAgentDiagnosticMessage(text);
2498
2852
  } catch (notifyError) {
2499
- debugLogger4.warn(`${failureContext}: ${this.#formatError(notifyError)}`);
2853
+ debugLogger5.warn(`${failureContext}: ${this.#formatError(notifyError)}`);
2500
2854
  }
2501
2855
  }
2502
2856
  async #emitAgentDiagnosticMessage(text) {
@@ -2545,7 +2899,7 @@ ${this.pendingWorktreeNotice}
2545
2899
  if (isPermanentError) {
2546
2900
  this.midTurnDrainUnavailable = true;
2547
2901
  }
2548
- debugLogger4.warn(
2902
+ debugLogger5.warn(
2549
2903
  `Mid-turn queue drain ${isPermanentError ? "permanently " : ""}unavailable [session ${this.sessionId}]: ${errorMessage}`
2550
2904
  );
2551
2905
  return this.#buildMidTurnParts(recovered, abortSignal);
@@ -2587,14 +2941,14 @@ ${this.pendingWorktreeNotice}
2587
2941
  clearTimeout(timer);
2588
2942
  }
2589
2943
  if (late === expired) {
2590
- debugLogger4.warn(
2944
+ debugLogger5.warn(
2591
2945
  `[mid-turn] dropped a drain response that arrived after the ${MID_TURN_QUEUE_RECOVERY_TIMEOUT_MS}ms recovery deadline [session ${this.sessionId}]`
2592
2946
  );
2593
2947
  return;
2594
2948
  }
2595
2949
  const lateMessages = parseMidTurnDrainResponse(late);
2596
2950
  if (lateMessages.length > 0) {
2597
- debugLogger4.debug(
2951
+ debugLogger5.debug(
2598
2952
  `[mid-turn] recovered ${lateMessages.length} message(s) from a timed-out drain [session ${this.sessionId}]`
2599
2953
  );
2600
2954
  this.midTurnRecoveredMessages.push(...lateMessages);
@@ -2620,7 +2974,7 @@ ${this.pendingWorktreeNotice}
2620
2974
  } catch (messageError) {
2621
2975
  if (abortSignal.aborted) return parts;
2622
2976
  const errorMessage = this.#formatError(messageError);
2623
- debugLogger4.warn(`Failed to resolve mid-turn message: ${errorMessage}`);
2977
+ debugLogger5.warn(`Failed to resolve mid-turn message: ${errorMessage}`);
2624
2978
  rawParts = [{ text: displayText }];
2625
2979
  if (message.kind === "structured" && hasInlineMediaContentBlock(message.content)) {
2626
2980
  rawParts.push({ text: MID_TURN_ATTACHMENT_PROCESSING_FAILURE_TEXT });
@@ -2645,20 +2999,23 @@ ${this.pendingWorktreeNotice}
2645
2999
  try {
2646
3000
  await scheduler.enableDurable(this.sessionId);
2647
3001
  } catch (err) {
2648
- debugLogger4.warn(
3002
+ debugLogger5.warn(
2649
3003
  `Durable cron init failed \u2014 persistent tasks will not fire in this session: ${err}`
2650
3004
  );
2651
3005
  }
2652
3006
  if (this.disposed) return;
2653
3007
  if (!scheduler.hasPendingWork) return;
2654
- scheduler.start((job) => {
2655
- if (this.cronDisabledByTokenLimit) return;
2656
- this.cronQueue.push({
2657
- prompt: job.prompt,
2658
- source: job.cronExpr === "@wakeup" ? "loop" : "cron"
2659
- });
2660
- void this.#drainCronQueue();
2661
- });
3008
+ scheduler.start(
3009
+ (job) => {
3010
+ if (this.cronDisabledByTokenLimit) return;
3011
+ if (job.missed && detectAutonomousSentinel(job.prompt)) return;
3012
+ this.cronQueue.push({
3013
+ prompt: job.prompt,
3014
+ source: job.cronExpr === "@wakeup" ? "loop" : "cron"
3015
+ });
3016
+ void this.#drainCronQueue();
3017
+ }
3018
+ );
2662
3019
  }
2663
3020
  /**
2664
3021
  * Processes queued cron prompts one at a time. Uses `cronProcessing`
@@ -2692,6 +3049,27 @@ ${this.pendingWorktreeNotice}
2692
3049
  }
2693
3050
  }
2694
3051
  }
3052
+ #getLoopTickResolver() {
3053
+ const root = this.config.getWorkingDir();
3054
+ if (!this.loopTickResolver || this.loopTickResolverRoot !== root) {
3055
+ const { homeConfineRoot, homeQwenDir } = resolveHomeLoopResolverRoots();
3056
+ this.loopTickResolver = new LoopTickResolver({
3057
+ projectRoot: root,
3058
+ homeDir: homeConfineRoot,
3059
+ homeQwenDir,
3060
+ // The project `.qwen/loop.md` is repo-controlled, so an untrusted folder
3061
+ // must not read it and feed it to the model (mirrors getProjectHooks()'s
3062
+ // trust gate). The home/global `~/.qwen/loop.md` is user-owned and stays
3063
+ // allowed. Pass a getter, not a snapshot: isTrustedFolder() can flip
3064
+ // mid-session on an IDE workspace-trust update, and the resolver outlives
3065
+ // a single tick — re-read it on every resolve() so a trusted→untrusted
3066
+ // flip stops reading the project file immediately.
3067
+ allowProjectFile: /* @__PURE__ */ __name(() => this.config.isTrustedFolder(), "allowProjectFile")
3068
+ });
3069
+ this.loopTickResolverRoot = root;
3070
+ }
3071
+ return this.loopTickResolver;
3072
+ }
2695
3073
  /**
2696
3074
  * Executes a single cron-fired prompt: echoes it as a user message with
2697
3075
  * `_meta.source='cron'`, streams the model response, and handles tool calls.
@@ -2722,16 +3100,65 @@ ${this.pendingWorktreeNotice}
2722
3100
  async () => {
2723
3101
  let turnCount = 0;
2724
3102
  try {
3103
+ const loopMode = detectLoopSentinel(prompt);
3104
+ const autonomousMode = loopMode ? null : detectAutonomousSentinel(prompt);
3105
+ let loopTick = null;
3106
+ if (loopMode) {
3107
+ const resolver = this.#getLoopTickResolver();
3108
+ const trustedAtResolve = this.config.isTrustedFolder();
3109
+ try {
3110
+ loopTick = await resolver.resolve(loopMode, trustedAtResolve);
3111
+ } catch (resolveErr) {
3112
+ const code = resolveErr.code ?? "unknown";
3113
+ debugLogger5.warn(
3114
+ `loop.md sentinel resolution failed (mode=${loopMode}, code=${code}) \u2014 check .qwen/loop.md permissions/IO`,
3115
+ resolveErr
3116
+ );
3117
+ if (loopMode === "dynamic" && TRANSIENT_FS_CODES.includes(code)) {
3118
+ loopTick = resolver.buildTransientErrorTick(
3119
+ loopMode,
3120
+ trustedAtResolve,
3121
+ code
3122
+ );
3123
+ } else {
3124
+ throw new Error(
3125
+ `loop.md resolution failed (${code}) for ${resolver.absentLocations(
3126
+ trustedAtResolve
3127
+ )}`
3128
+ );
3129
+ }
3130
+ }
3131
+ } else if (autonomousMode) {
3132
+ loopTick = this.#getLoopTickResolver().resolveAutonomous(autonomousMode);
3133
+ }
3134
+ const modelText = loopTick ? loopTick.modelText : prompt;
3135
+ if (loopTick) {
3136
+ debugLogger5.debug(
3137
+ `loop tick: mode=${loopMode ?? autonomousMode} delivery=${loopTick.full ? "full" : loopTick.transientError ? "transient-error" : loopTick.autonomous ? "autonomous-tick" : loopTick.sourceLabel ? "reminder" : "absent"} source=${loopTick.sourceLabel ?? "none"} autonomous=${loopTick.autonomous ?? false} transient=${loopTick.transientError ?? false}`
3138
+ );
3139
+ }
3140
+ const echoText = !loopTick ? prompt : (
3141
+ // An autonomous tick (a bare-`/loop` sentinel, or a loop.md
3142
+ // sentinel whose file is gone and converged on the preamble).
3143
+ loopTick.autonomous ? "Autonomous loop tick" : loopTick.sourceLabel ? `Loop tick \u2014 tasks from ${loopTick.sourceLabel}` : (
3144
+ // The only remaining tick is a transient read failure
3145
+ // (buildTransientErrorTick): a loop.md exists but couldn't be
3146
+ // read this tick. A genuinely-absent loop.md converges on the
3147
+ // autonomous branch above, so there is no "not present" echo.
3148
+ "Loop tick \u2014 loop.md temporarily unavailable"
3149
+ )
3150
+ );
2725
3151
  await this.sendUpdate({
2726
3152
  sessionUpdate: "user_message_chunk",
2727
- content: { type: "text", text: prompt },
3153
+ content: { type: "text", text: echoText },
2728
3154
  _meta: { source: item.source }
2729
3155
  });
2730
3156
  const cronReminders = await this.#buildInitialSystemReminders();
2731
3157
  let nextMessage = {
2732
3158
  role: "user",
2733
- parts: [...cronReminders, { text: prompt }]
3159
+ parts: [...cronReminders, { text: modelText }]
2734
3160
  };
3161
+ const toolLoopState = createDaemonToolLoopState();
2735
3162
  while (nextMessage !== null) {
2736
3163
  turnCount++;
2737
3164
  if (ac.signal.aborted) return;
@@ -2754,6 +3181,9 @@ ${this.pendingWorktreeNotice}
2754
3181
  return;
2755
3182
  }
2756
3183
  const responseStream = sendResult.responseStream;
3184
+ if (loopTick && turnCount === 1) {
3185
+ this.loopTickResolver?.markDelivered();
3186
+ }
2757
3187
  nextMessage = null;
2758
3188
  for await (const resp of responseStream) {
2759
3189
  if (ac.signal.aborted) return;
@@ -2791,25 +3221,27 @@ ${this.pendingWorktreeNotice}
2791
3221
  const toolRun = await this.runToolCalls(
2792
3222
  ac.signal,
2793
3223
  promptId,
2794
- functionCalls
3224
+ functionCalls,
3225
+ toolLoopState
2795
3226
  );
2796
3227
  if (toolRun.stopAfterPermissionCancel) {
2797
- await this.#preserveCancelledPermissionToolRun(
2798
- toolRun,
2799
- ac.signal
2800
- );
3228
+ await this.#preserveStoppedToolRun(toolRun, ac.signal);
2801
3229
  return;
2802
3230
  }
2803
3231
  nextMessage = await this.#buildNextMessageAfterToolRun(
2804
3232
  toolRun,
2805
3233
  ac.signal
2806
3234
  );
3235
+ if (toolRun.loopDetected) {
3236
+ await this.#preserveStoppedToolRun(toolRun, ac.signal);
3237
+ return;
3238
+ }
2807
3239
  }
2808
3240
  }
2809
3241
  } catch (error) {
2810
3242
  if (ac.signal.aborted) return;
2811
3243
  cronHadError = true;
2812
- debugLogger4.error("Error processing cron prompt:", error);
3244
+ debugLogger5.error("Error processing cron prompt:", error);
2813
3245
  const msg = error instanceof Error ? error.message : String(error);
2814
3246
  await this.messageEmitter.emitAgentMessage(
2815
3247
  `[${item.source} error] ${msg}`
@@ -2893,7 +3325,7 @@ ${this.pendingWorktreeNotice}
2893
3325
  #enqueueBackgroundNotification(item) {
2894
3326
  while (this.notificationQueue.length >= MAX_NOTIFICATION_QUEUE) {
2895
3327
  const evicted = this.notificationQueue.shift();
2896
- debugLogger4.warn(
3328
+ debugLogger5.warn(
2897
3329
  `Notification queue overflow: evicting task=${evicted.taskId} kind=${evicted.kind}`
2898
3330
  );
2899
3331
  }
@@ -2950,6 +3382,7 @@ ${this.pendingWorktreeNotice}
2950
3382
  role: "user",
2951
3383
  parts: [...notificationReminders, ...notificationParts]
2952
3384
  };
3385
+ const toolLoopState = createDaemonToolLoopState();
2953
3386
  while (nextMessage !== null) {
2954
3387
  if (ac.signal.aborted) {
2955
3388
  await this.#emitBackgroundNotificationEndTurn("cancelled");
@@ -3026,13 +3459,11 @@ ${this.pendingWorktreeNotice}
3026
3459
  const toolRun = await this.runToolCalls(
3027
3460
  ac.signal,
3028
3461
  promptId,
3029
- functionCalls
3462
+ functionCalls,
3463
+ toolLoopState
3030
3464
  );
3031
3465
  if (toolRun.stopAfterPermissionCancel) {
3032
- await this.#preserveCancelledPermissionToolRun(
3033
- toolRun,
3034
- ac.signal
3035
- );
3466
+ await this.#preserveStoppedToolRun(toolRun, ac.signal);
3036
3467
  await this.#emitBackgroundNotificationEndTurn("end_turn");
3037
3468
  return;
3038
3469
  }
@@ -3040,6 +3471,11 @@ ${this.pendingWorktreeNotice}
3040
3471
  toolRun,
3041
3472
  ac.signal
3042
3473
  );
3474
+ if (toolRun.loopDetected) {
3475
+ await this.#preserveStoppedToolRun(toolRun, ac.signal);
3476
+ await this.#emitBackgroundNotificationEndTurn("end_turn");
3477
+ return;
3478
+ }
3043
3479
  }
3044
3480
  }
3045
3481
  if (this.messageRewriter) {
@@ -3051,14 +3487,14 @@ ${this.pendingWorktreeNotice}
3051
3487
  await this.#emitBackgroundNotificationEndTurn("cancelled");
3052
3488
  return;
3053
3489
  }
3054
- debugLogger4.error("Error processing background notification:", error);
3490
+ debugLogger5.error("Error processing background notification:", error);
3055
3491
  const msg = error instanceof Error ? error.message : String(error);
3056
3492
  try {
3057
3493
  await this.messageEmitter.emitAgentMessage(
3058
3494
  `[notification error] ${msg}`
3059
3495
  );
3060
3496
  } catch (emitError) {
3061
- debugLogger4.error(
3497
+ debugLogger5.error(
3062
3498
  "Failed to emit background notification error:",
3063
3499
  emitError
3064
3500
  );
@@ -3118,7 +3554,7 @@ ${this.pendingWorktreeNotice}
3118
3554
  source: "background_notification"
3119
3555
  });
3120
3556
  } catch (error) {
3121
- debugLogger4.debug(
3557
+ debugLogger5.debug(
3122
3558
  `Background notification end-turn extNotification dropped: ${this.#formatError(error)}`
3123
3559
  );
3124
3560
  }
@@ -3142,7 +3578,7 @@ ${this.pendingWorktreeNotice}
3142
3578
  };
3143
3579
  await this.sendUpdate(update);
3144
3580
  } catch (error) {
3145
- debugLogger4.error("Error sending available commands update:", error);
3581
+ debugLogger5.error("Error sending available commands update:", error);
3146
3582
  }
3147
3583
  }
3148
3584
  /**
@@ -3177,7 +3613,7 @@ ${this.pendingWorktreeNotice}
3177
3613
  sessionId: this.sessionId,
3178
3614
  currentModeId: params.modeId
3179
3615
  }).catch((error) => {
3180
- debugLogger4.debug("mode-update extNotification failed", error);
3616
+ debugLogger5.debug("mode-update extNotification failed", error);
3181
3617
  });
3182
3618
  }
3183
3619
  /**
@@ -3211,7 +3647,7 @@ ${this.pendingWorktreeNotice}
3211
3647
  sessionId: this.sessionId,
3212
3648
  currentModelId: effectiveModelId
3213
3649
  }).catch((error) => {
3214
- debugLogger4.debug("model-update extNotification failed", error);
3650
+ debugLogger5.debug("model-update extNotification failed", error);
3215
3651
  });
3216
3652
  if (options.persistDefault ?? true) {
3217
3653
  const persistScope = getPersistScopeForModelSelection(this.settings);
@@ -3264,7 +3700,7 @@ ${this.pendingWorktreeNotice}
3264
3700
  currentModeId: newModeId,
3265
3701
  legacyFrameSent: true
3266
3702
  }).catch((error) => {
3267
- debugLogger4.debug("mode-update extNotification failed", error);
3703
+ debugLogger5.debug("mode-update extNotification failed", error);
3268
3704
  });
3269
3705
  }
3270
3706
  /**
@@ -3277,8 +3713,58 @@ ${this.pendingWorktreeNotice}
3277
3713
  * sequential batch to preserve the implicit ordering the model may rely
3278
3714
  * on. Response-part ordering matches the original `functionCalls` order.
3279
3715
  */
3280
- async runToolCalls(abortSignal, promptId, functionCalls) {
3716
+ async runToolCalls(abortSignal, promptId, functionCalls, toolLoopState) {
3281
3717
  const dedupedFunctionCalls = dedupeToolCallsById(functionCalls);
3718
+ let skippedToolCallCounter = 0;
3719
+ const recordSkippedToolCall = /* @__PURE__ */ __name(async (fc, message = PERMISSION_CANCEL_SKIP_MESSAGE, emitStart = true) => {
3720
+ const toolName = fc.name ?? "unknown_tool";
3721
+ const callId = fc.id ?? `${toolName}-skip-${++skippedToolCallCounter}`;
3722
+ const part = {
3723
+ functionResponse: {
3724
+ id: callId,
3725
+ name: toolName,
3726
+ response: { error: message }
3727
+ }
3728
+ };
3729
+ const error = new Error(message);
3730
+ try {
3731
+ this.config.getChatRecordingService()?.recordToolResult([part], {
3732
+ callId,
3733
+ status: "error",
3734
+ resultDisplay: void 0,
3735
+ error,
3736
+ errorType: void 0
3737
+ });
3738
+ if (emitStart) {
3739
+ await this.toolCallEmitter.emitStart({
3740
+ callId,
3741
+ toolName,
3742
+ args: fc.args ?? {},
3743
+ status: "pending"
3744
+ });
3745
+ }
3746
+ await this.toolCallEmitter.emitError(callId, toolName, error);
3747
+ } catch (recordError) {
3748
+ debugLogger5.error("Failed to record skipped tool call:", recordError);
3749
+ }
3750
+ return part;
3751
+ }, "recordSkippedToolCall");
3752
+ if (recordDaemonToolCalls(
3753
+ this.config,
3754
+ promptId,
3755
+ toolLoopState,
3756
+ dedupedFunctionCalls.length
3757
+ )) {
3758
+ return {
3759
+ parts: await Promise.all(
3760
+ dedupedFunctionCalls.map(
3761
+ (fc) => recordSkippedToolCall(fc, LOOP_DETECTED_SKIP_MESSAGE, false)
3762
+ )
3763
+ ),
3764
+ stopAfterPermissionCancel: false,
3765
+ loopDetected: true
3766
+ };
3767
+ }
3282
3768
  const batches = [];
3283
3769
  const handledProviderToolCallIds = new Set(
3284
3770
  this.#getCurrentChat().getHistoryFunctionResponseIds()
@@ -3291,7 +3777,7 @@ ${this.pendingWorktreeNotice}
3291
3777
  );
3292
3778
  if (repeatedDuplicateCall) {
3293
3779
  const providerCallId = getProviderToolCallId(repeatedDuplicateCall) ?? repeatedDuplicateCall.id;
3294
- debugLogger4.debug(
3780
+ debugLogger5.debug(
3295
3781
  `[Session.runToolCalls] Dropping batch after repeated duplicate provider tool-call id: ${providerCallId} (tool: ${repeatedDuplicateCall.name ?? "unknown_tool"})`
3296
3782
  );
3297
3783
  return {
@@ -3307,7 +3793,7 @@ ${this.pendingWorktreeNotice}
3307
3793
  this.duplicateProviderToolCallResponseIds
3308
3794
  );
3309
3795
  const response = createDuplicateProviderToolCallResponse(request);
3310
- debugLogger4.debug(
3796
+ debugLogger5.debug(
3311
3797
  `[Session.runToolCalls] Suppressing duplicate provider tool-call id: ${providerCallId} (tool: ${request.name})`
3312
3798
  );
3313
3799
  batches.push({ kind: "duplicate", request, response });
@@ -3380,53 +3866,80 @@ ${this.pendingWorktreeNotice}
3380
3866
  batches.push({ kind: "execute", concurrent: isAgent, calls: [fc] });
3381
3867
  }
3382
3868
  }
3383
- let skippedToolCallCounter = 0;
3384
- const recordSkippedToolCall = /* @__PURE__ */ __name(async (fc) => {
3385
- const toolName = fc.name ?? "unknown_tool";
3386
- const callId = fc.id ?? `${toolName}-skip-${++skippedToolCallCounter}`;
3387
- const part = {
3388
- functionResponse: {
3389
- id: callId,
3390
- name: toolName,
3391
- response: { error: PERMISSION_CANCEL_SKIP_MESSAGE }
3392
- }
3393
- };
3394
- const error = new Error(PERMISSION_CANCEL_SKIP_MESSAGE);
3395
- try {
3396
- this.config.getChatRecordingService()?.recordToolResult([part], {
3397
- callId,
3398
- status: "error",
3399
- resultDisplay: void 0,
3400
- error,
3401
- errorType: void 0
3402
- });
3403
- await this.toolCallEmitter.emitStart({
3404
- callId,
3405
- toolName,
3406
- args: fc.args ?? {},
3407
- status: "pending"
3408
- });
3409
- await this.toolCallEmitter.emitError(callId, toolName, error);
3410
- } catch (recordError) {
3411
- debugLogger4.error("Failed to record skipped tool call:", recordError);
3412
- }
3413
- return part;
3414
- }, "recordSkippedToolCall");
3415
- const appendSkippedAfter = /* @__PURE__ */ __name(async (parts2, fc) => {
3869
+ const appendSkippedAfter = /* @__PURE__ */ __name(async (parts2, fc, message = PERMISSION_CANCEL_SKIP_MESSAGE) => {
3416
3870
  const startIndex = dedupedFunctionCalls.indexOf(fc) + 1;
3417
3871
  for (const remainingCall of dedupedFunctionCalls.slice(startIndex)) {
3418
- parts2.push(await recordSkippedToolCall(remainingCall));
3872
+ parts2.push(await recordSkippedToolCall(remainingCall, message));
3419
3873
  }
3420
3874
  }, "appendSkippedAfter");
3421
- const runBounded = /* @__PURE__ */ __name(async (calls, runAbortSignal, onStopAfterPermissionCancel, shouldSkipUnstarted) => {
3422
- const maxConcurrency = parsePositiveIntegerEnv(
3875
+ const runBounded = /* @__PURE__ */ __name(async (calls, runAbortSignal, onStopAfterPermissionCancel, onStopAfterLoopDetected, shouldSkipUnstarted) => {
3876
+ const configuredMaxConcurrency = parsePositiveIntegerEnv(
3423
3877
  process.env["QWEN_CODE_MAX_TOOL_CONCURRENCY"],
3424
3878
  10
3425
3879
  );
3880
+ const maxConcurrency = toolLoopState ? Math.min(
3881
+ configuredMaxConcurrency,
3882
+ DAEMON_INVALID_TOOL_PARAMS_THRESHOLD
3883
+ ) : configuredMaxConcurrency;
3426
3884
  const results = new Array(calls.length);
3427
3885
  const executing = /* @__PURE__ */ new Set();
3428
- for (let i = 0; i < calls.length; i++) {
3886
+ const fillLoopSkippedFrom = /* @__PURE__ */ __name(async (startIndex2) => {
3887
+ for (let i = startIndex2; i < calls.length; i++) {
3888
+ if (results[i]) continue;
3889
+ results[i] = {
3890
+ parts: [
3891
+ await recordSkippedToolCall(calls[i], LOOP_DETECTED_SKIP_MESSAGE)
3892
+ ],
3893
+ stopAfterPermissionCancel: false,
3894
+ loopDetected: true
3895
+ };
3896
+ }
3897
+ }, "fillLoopSkippedFrom");
3898
+ const fillPermissionSkippedFrom = /* @__PURE__ */ __name(async (startIndex2) => {
3899
+ for (let i = startIndex2; i < calls.length; i++) {
3900
+ if (results[i]) continue;
3901
+ results[i] = {
3902
+ parts: [await recordSkippedToolCall(calls[i])],
3903
+ stopAfterPermissionCancel: false
3904
+ };
3905
+ }
3906
+ }, "fillPermissionSkippedFrom");
3907
+ let startIndex = 0;
3908
+ if (toolLoopState && calls.length > DAEMON_INVALID_TOOL_PARAMS_THRESHOLD) {
3909
+ startIndex = DAEMON_INVALID_TOOL_PARAMS_THRESHOLD;
3910
+ for (let i = 0; i < startIndex; i++) {
3911
+ if (runAbortSignal.aborted && shouldSkipUnstarted?.()) {
3912
+ results[i] = {
3913
+ parts: [await recordSkippedToolCall(calls[i])],
3914
+ stopAfterPermissionCancel: false
3915
+ };
3916
+ continue;
3917
+ }
3918
+ const r = await this.runTool(
3919
+ runAbortSignal,
3920
+ promptId,
3921
+ calls[i],
3922
+ onStopAfterPermissionCancel,
3923
+ toolLoopState,
3924
+ recordSkippedToolCall
3925
+ );
3926
+ results[i] = r;
3927
+ if (r.loopDetected) {
3928
+ await fillLoopSkippedFrom(i + 1);
3929
+ return results;
3930
+ }
3931
+ if (r.stopAfterPermissionCancel) {
3932
+ await fillPermissionSkippedFrom(i + 1);
3933
+ return results;
3934
+ }
3935
+ }
3936
+ }
3937
+ for (let i = startIndex; i < calls.length; i++) {
3429
3938
  const idx = i;
3939
+ if (toolLoopState?.loopDetected) {
3940
+ await fillLoopSkippedFrom(idx);
3941
+ return results;
3942
+ }
3430
3943
  if (runAbortSignal.aborted && shouldSkipUnstarted?.()) {
3431
3944
  results[idx] = {
3432
3945
  parts: [await recordSkippedToolCall(calls[idx])],
@@ -3438,7 +3951,9 @@ ${this.pendingWorktreeNotice}
3438
3951
  runAbortSignal,
3439
3952
  promptId,
3440
3953
  calls[idx],
3441
- onStopAfterPermissionCancel
3954
+ onStopAfterPermissionCancel,
3955
+ toolLoopState,
3956
+ recordSkippedToolCall
3442
3957
  ).then((r) => {
3443
3958
  results[idx] = r;
3444
3959
  }).finally(() => {
@@ -3447,6 +3962,23 @@ ${this.pendingWorktreeNotice}
3447
3962
  executing.add(p);
3448
3963
  if (executing.size >= maxConcurrency) {
3449
3964
  await Promise.race(executing);
3965
+ if (results.some((result) => result?.loopDetected)) {
3966
+ onStopAfterLoopDetected?.();
3967
+ await Promise.all(executing);
3968
+ await fillLoopSkippedFrom(idx + 1);
3969
+ return results;
3970
+ }
3971
+ const invalidToolErrorNearThreshold = toolLoopState && [...toolLoopState.invalidToolParamErrors.values()].some(
3972
+ (count) => count >= DAEMON_INVALID_TOOL_PARAMS_THRESHOLD - 1
3973
+ );
3974
+ if (invalidToolErrorNearThreshold && executing.size > 0) {
3975
+ await Promise.all(executing);
3976
+ if (results.some((result) => result?.loopDetected)) {
3977
+ onStopAfterLoopDetected?.();
3978
+ await fillLoopSkippedFrom(idx + 1);
3979
+ return results;
3980
+ }
3981
+ }
3450
3982
  }
3451
3983
  }
3452
3984
  await Promise.all(executing);
@@ -3482,15 +4014,30 @@ ${this.pendingWorktreeNotice}
3482
4014
  batch.calls,
3483
4015
  batchAbortController.signal,
3484
4016
  stopBatchAfterPermissionCancel,
4017
+ () => batchAbortController.abort("loop_detected"),
3485
4018
  () => batchStopAfterPermissionCancel
3486
4019
  );
3487
4020
  } finally {
3488
4021
  abortSignal.removeEventListener("abort", propagateAbort);
3489
4022
  }
3490
4023
  let shouldStop = false;
4024
+ let shouldStopForLoop = false;
3491
4025
  for (const r of results) {
3492
4026
  parts.push(...r.parts);
3493
4027
  shouldStop ||= r.stopAfterPermissionCancel;
4028
+ shouldStopForLoop ||= r.loopDetected === true;
4029
+ }
4030
+ if (shouldStopForLoop) {
4031
+ await appendSkippedAfter(
4032
+ parts,
4033
+ batch.calls[batch.calls.length - 1],
4034
+ LOOP_DETECTED_SKIP_MESSAGE
4035
+ );
4036
+ return {
4037
+ parts,
4038
+ stopAfterPermissionCancel: false,
4039
+ loopDetected: true
4040
+ };
3494
4041
  }
3495
4042
  if (shouldStop) {
3496
4043
  await appendSkippedAfter(parts, batch.calls[batch.calls.length - 1]);
@@ -3502,8 +4049,23 @@ ${this.pendingWorktreeNotice}
3502
4049
  }
3503
4050
  } else {
3504
4051
  for (const fc of batch.calls) {
3505
- const r = await this.runTool(abortSignal, promptId, fc);
4052
+ const r = await this.runTool(
4053
+ abortSignal,
4054
+ promptId,
4055
+ fc,
4056
+ void 0,
4057
+ toolLoopState,
4058
+ recordSkippedToolCall
4059
+ );
3506
4060
  parts.push(...r.parts);
4061
+ if (r.loopDetected) {
4062
+ await appendSkippedAfter(parts, fc, LOOP_DETECTED_SKIP_MESSAGE);
4063
+ return {
4064
+ parts,
4065
+ stopAfterPermissionCancel: false,
4066
+ loopDetected: true
4067
+ };
4068
+ }
3507
4069
  if (r.stopAfterPermissionCancel) {
3508
4070
  await appendSkippedAfter(parts, fc);
3509
4071
  return {
@@ -3551,9 +4113,24 @@ ${this.pendingWorktreeNotice}
3551
4113
  }
3552
4114
  return reminders;
3553
4115
  }
3554
- async runTool(abortSignal, promptId, fc, onStopAfterPermissionCancel) {
4116
+ async runTool(abortSignal, promptId, fc, onStopAfterPermissionCancel, toolLoopState, recordSkippedToolCall) {
3555
4117
  const callId = fc.id ?? `${fc.name}-${Date.now()}`;
3556
4118
  let args = fc.args ?? {};
4119
+ if (toolLoopState?.loopDetected) {
4120
+ return {
4121
+ parts: [
4122
+ recordSkippedToolCall ? await recordSkippedToolCall(fc, LOOP_DETECTED_SKIP_MESSAGE, false) : {
4123
+ functionResponse: {
4124
+ id: callId,
4125
+ name: fc.name ?? "unknown_tool",
4126
+ response: { error: LOOP_DETECTED_SKIP_MESSAGE }
4127
+ }
4128
+ }
4129
+ ],
4130
+ stopAfterPermissionCancel: false,
4131
+ loopDetected: true
4132
+ };
4133
+ }
3557
4134
  const startTime = Date.now();
3558
4135
  let spanError;
3559
4136
  let activeToolAbortSignal = abortSignal;
@@ -3606,20 +4183,32 @@ ${this.pendingWorktreeNotice}
3606
4183
  error,
3607
4184
  errorType: void 0
3608
4185
  });
4186
+ const loopDetected = opts?.recordInvalidToolParams === true && !activeToolAbortSignal.aborted && !opts?.stopAfterPermissionCancel && recordDaemonInvalidToolParams(
4187
+ this.config,
4188
+ promptId,
4189
+ toolLoopState,
4190
+ toolName2,
4191
+ error
4192
+ );
3609
4193
  return {
3610
4194
  parts: errorParts,
3611
- stopAfterPermissionCancel: opts?.stopAfterPermissionCancel ?? false
4195
+ stopAfterPermissionCancel: opts?.stopAfterPermissionCancel ?? false,
4196
+ loopDetected
3612
4197
  };
3613
4198
  }, "earlyErrorResponse");
3614
4199
  if (!fc.name) {
3615
- return earlyErrorResponse(new Error("Missing function name"));
4200
+ return earlyErrorResponse(new Error("Missing function name"), void 0, {
4201
+ recordInvalidToolParams: true
4202
+ });
3616
4203
  }
3617
4204
  const toolName = fc.name;
3618
4205
  const toolRegistry = this.config.getToolRegistry();
3619
4206
  const tool = toolRegistry.getTool(toolName);
3620
4207
  if (!tool) {
3621
4208
  return earlyErrorResponse(
3622
- new Error(`Tool "${toolName}" not found in registry.`)
4209
+ new Error(`Tool "${toolName}" not found in registry.`),
4210
+ toolName,
4211
+ { recordInvalidToolParams: true }
3623
4212
  );
3624
4213
  }
3625
4214
  const toolSpan = startToolSpan(toolName, {
@@ -3663,8 +4252,10 @@ ${this.pendingWorktreeNotice}
3663
4252
  }
3664
4253
  const toolUseId = generateToolUseId();
3665
4254
  const approvalMode = this.config.getApprovalMode();
4255
+ let toolBuildSucceeded = false;
3666
4256
  try {
3667
4257
  const invocation = tool.build(args);
4258
+ toolBuildSucceeded = true;
3668
4259
  const taskEventEmitter = invocation.eventEmitter;
3669
4260
  if (isAgentTool && taskEventEmitter) {
3670
4261
  const parentToolCallId = callId;
@@ -3701,13 +4292,13 @@ ${this.pendingWorktreeNotice}
3701
4292
  toolName
3702
4293
  );
3703
4294
  }
3704
- const forceAutoReviewForAllow = approvalMode === "auto" /* AUTO */ && shouldForceAutoModeReviewForAllow(pmCtx, this.config.getCwd());
4295
+ const forceAutoReviewForAllow = approvalMode === "auto" /* AUTO */ && (shouldForceAutoModeReviewForAllow(pmCtx, this.config.getCwd()) || shouldClassifyAllShellForAutoMode(toolName, this.config));
3705
4296
  const confirmationPermission = getEffectivePermissionForConfirmation(
3706
4297
  finalPermission,
3707
4298
  forceAutoReviewForAllow
3708
4299
  );
3709
4300
  if (finalPermission === "allow" && forceAutoReviewForAllow) {
3710
- debugLogger4.info(
4301
+ debugLogger5.info(
3711
4302
  `Auto mode: L4 allow overridden by protected-write guard for ${toolName}`
3712
4303
  );
3713
4304
  }
@@ -3750,7 +4341,7 @@ ${this.pendingWorktreeNotice}
3750
4341
  autoModeAllowed = true;
3751
4342
  break;
3752
4343
  case "blocked":
3753
- debugLogger4.warn(
4344
+ debugLogger5.warn(
3754
4345
  `Auto mode blocked (${outcome.reason}): tool=${toolName}, ` + formatDenialStateLog(denialState)
3755
4346
  );
3756
4347
  return earlyErrorResponse(
@@ -3762,7 +4353,7 @@ ${this.pendingWorktreeNotice}
3762
4353
  outcome.reason
3763
4354
  );
3764
4355
  if (wasAutoModeDenialFallback) {
3765
- debugLogger4.warn(
4356
+ debugLogger5.warn(
3766
4357
  `Auto mode fallback to manual approval (${outcome.reason}): ` + formatDenialStateLog(denialState)
3767
4358
  );
3768
4359
  }
@@ -3780,12 +4371,12 @@ ${this.pendingWorktreeNotice}
3780
4371
  const before = this.config.getAutoModeDenialState();
3781
4372
  const after = recordFallbackApprove(before);
3782
4373
  if (after === before) {
3783
- debugLogger4.warn(
4374
+ debugLogger5.warn(
3784
4375
  `Auto mode denial counters already clear after fallback approval: ` + formatDenialStateLog(before)
3785
4376
  );
3786
4377
  return;
3787
4378
  }
3788
- debugLogger4.warn(
4379
+ debugLogger5.warn(
3789
4380
  `Auto mode denial counters reset after fallback approval: ${formatDenialStateLog(before)} -> ${formatDenialStateLog(after)}`
3790
4381
  );
3791
4382
  this.config.setAutoModeDenialState(after);
@@ -3891,7 +4482,7 @@ ${this.pendingWorktreeNotice}
3891
4482
  );
3892
4483
  outcome = output.outcome.outcome === "cancelled" ? "cancel" /* Cancel */ : external_exports.nativeEnum(ToolConfirmationOutcome).parse(output.outcome.optionId);
3893
4484
  } catch (error) {
3894
- debugLogger4.error(
4485
+ debugLogger5.error(
3895
4486
  `Permission request failed for tool ${toolName}:`,
3896
4487
  error
3897
4488
  );
@@ -3900,7 +4491,7 @@ ${this.pendingWorktreeNotice}
3900
4491
  "cancel" /* Cancel */
3901
4492
  );
3902
4493
  } catch (confirmError) {
3903
- debugLogger4.error(
4494
+ debugLogger5.error(
3904
4495
  `Failed to cancel tool ${toolName} after permission request failure:`,
3905
4496
  confirmError
3906
4497
  );
@@ -3925,7 +4516,7 @@ ${this.pendingWorktreeNotice}
3925
4516
  if (outcome !== "cancel" /* Cancel */) {
3926
4517
  throw error;
3927
4518
  }
3928
- debugLogger4.error(
4519
+ debugLogger5.error(
3929
4520
  `Failed to confirm cancellation for tool ${toolName}:`,
3930
4521
  error
3931
4522
  );
@@ -3990,12 +4581,12 @@ ${this.pendingWorktreeNotice}
3990
4581
  if (!preHookResult.shouldProceed) {
3991
4582
  const blockReason = preHookResult.blockReason || "Blocked by PreToolUse hook";
3992
4583
  await this.messageEmitter.emitAgentMessage(
3993
- `\u{1F6AB} **PreToolUse blocked**: ${toolName} - ${blockReason}`
4584
+ `\u2717 **PreToolUse blocked**: ${toolName} - ${blockReason}`
3994
4585
  );
3995
4586
  return earlyErrorResponse(new Error(blockReason), toolName);
3996
4587
  }
3997
4588
  if (preHookResult.additionalContext) {
3998
- debugLogger4.debug(
4589
+ debugLogger5.debug(
3999
4590
  `PreToolUse hook additional context for ${toolName}: ${preHookResult.additionalContext}`
4000
4591
  );
4001
4592
  }
@@ -4058,7 +4649,7 @@ ${this.pendingWorktreeNotice}
4058
4649
  );
4059
4650
  if (postHookResult.shouldStop) {
4060
4651
  const stopMessage = postHookResult.stopReason || "Execution stopped by PostToolUse hook";
4061
- debugLogger4.info(
4652
+ debugLogger5.info(
4062
4653
  `PostToolUse hook requested stop for ${toolName}: ${stopMessage}`
4063
4654
  );
4064
4655
  return earlyErrorResponse(new Error(stopMessage), toolName);
@@ -4081,7 +4672,7 @@ ${this.pendingWorktreeNotice}
4081
4672
  callId
4082
4673
  );
4083
4674
  if (failureHookResult.additionalContext) {
4084
- debugLogger4.debug(
4675
+ debugLogger5.debug(
4085
4676
  `PostToolUseFailure hook additional context for ${toolName}: ${failureHookResult.additionalContext}`
4086
4677
  );
4087
4678
  }
@@ -4157,7 +4748,7 @@ ${this.pendingWorktreeNotice}
4157
4748
  callId
4158
4749
  );
4159
4750
  if (failureHookResult.additionalContext) {
4160
- debugLogger4.debug(
4751
+ debugLogger5.debug(
4161
4752
  `PostToolUseFailure hook additional context for ${toolName}: ${failureHookResult.additionalContext}`
4162
4753
  );
4163
4754
  }
@@ -4181,9 +4772,17 @@ ${this.pendingWorktreeNotice}
4181
4772
  error,
4182
4773
  errorType: void 0
4183
4774
  });
4775
+ const loopDetected = !activeToolAbortSignal.aborted && !toolBuildSucceeded && recordDaemonInvalidToolParams(
4776
+ this.config,
4777
+ promptId,
4778
+ toolLoopState,
4779
+ toolName,
4780
+ error
4781
+ );
4184
4782
  return {
4185
4783
  parts: errorResponse(error),
4186
- stopAfterPermissionCancel: nestedPermissionCancelled
4784
+ stopAfterPermissionCancel: nestedPermissionCancelled,
4785
+ loopDetected
4187
4786
  };
4188
4787
  }
4189
4788
  });
@@ -4292,11 +4891,30 @@ ${this.pendingWorktreeNotice}
4292
4891
  async #resolvePrompt(message, abortSignal) {
4293
4892
  const FILE_URI_SCHEME = "file://";
4294
4893
  const embeddedContext = [];
4894
+ const extensionMentions = /* @__PURE__ */ new Map();
4895
+ const preserveUnsupportedImageForBridge = shouldRunVisionBridge(
4896
+ this.config
4897
+ );
4295
4898
  const parts = message.map((part) => {
4296
4899
  switch (part.type) {
4297
4900
  case "text":
4901
+ collectExtensionMentionRefs(part.text, extensionMentions);
4298
4902
  return { text: part.text };
4299
4903
  case "image":
4904
+ if (preserveUnsupportedImageForBridge) {
4905
+ return {
4906
+ inlineData: {
4907
+ mimeType: part.mimeType,
4908
+ data: part.data
4909
+ }
4910
+ };
4911
+ }
4912
+ return clampInlineMediaPart({
4913
+ inlineData: {
4914
+ mimeType: part.mimeType,
4915
+ data: part.data
4916
+ }
4917
+ });
4300
4918
  case "audio":
4301
4919
  return clampInlineMediaPart({
4302
4920
  inlineData: {
@@ -4328,8 +4946,18 @@ ${this.pendingWorktreeNotice}
4328
4946
  }
4329
4947
  });
4330
4948
  const atPathCommandParts = parts.filter((part) => "fileData" in part);
4949
+ const extensionParts = await this.#resolveExtensionMentionParts(
4950
+ extensionMentions,
4951
+ abortSignal
4952
+ );
4953
+ if (atPathCommandParts.length === 0 && embeddedContext.length === 0 && extensionParts.length === 0) {
4954
+ return this.#applyVisionBridgeIfNeeded(parts, abortSignal);
4955
+ }
4331
4956
  if (atPathCommandParts.length === 0 && embeddedContext.length === 0) {
4332
- return parts;
4957
+ return this.#applyVisionBridgeIfNeeded(
4958
+ [...parts, ...extensionParts],
4959
+ abortSignal
4960
+ );
4333
4961
  }
4334
4962
  const pathSpecsToRead = atPathCommandParts.map(
4335
4963
  (part) => part.fileData.fileUri
@@ -4351,19 +4979,24 @@ ${this.pendingWorktreeNotice}
4351
4979
  if (pathSpecsToRead.length > 0) {
4352
4980
  const readResult = await readManyFiles(this.config, {
4353
4981
  paths: pathSpecsToRead,
4354
- signal: abortSignal
4982
+ signal: abortSignal,
4983
+ ...preserveUnsupportedImageForBridge ? { preserveUnsupportedImageForBridge } : {}
4355
4984
  });
4356
4985
  const contentParts = Array.isArray(readResult.contentParts) ? readResult.contentParts : [readResult.contentParts];
4357
4986
  processedQueryParts.push({ text: initialQueryText });
4987
+ processedQueryParts.push(...extensionParts);
4358
4988
  for (const part of contentParts) {
4359
4989
  if (typeof part === "string") {
4360
4990
  processedQueryParts.push({ text: part });
4991
+ } else if (preserveUnsupportedImageForBridge && hasImageParts([part])) {
4992
+ processedQueryParts.push(part);
4361
4993
  } else {
4362
4994
  processedQueryParts.push(clampInlineMediaPart(part));
4363
4995
  }
4364
4996
  }
4365
4997
  } else {
4366
4998
  processedQueryParts.push({ text: initialQueryText.trim() });
4999
+ processedQueryParts.push(...extensionParts);
4367
5000
  }
4368
5001
  for (const contextPart of embeddedContext) {
4369
5002
  if ("text" in contextPart && contextPart.text) {
@@ -4373,21 +5006,104 @@ ${contextPart.text}`
4373
5006
  });
4374
5007
  }
4375
5008
  if ("blob" in contextPart && contextPart.blob) {
5009
+ const inlinePart = {
5010
+ inlineData: {
5011
+ mimeType: contextPart.mimeType ?? "application/octet-stream",
5012
+ data: contextPart.blob
5013
+ }
5014
+ };
4376
5015
  processedQueryParts.push(
4377
- clampInlineMediaPart({
4378
- inlineData: {
4379
- mimeType: contextPart.mimeType ?? "application/octet-stream",
4380
- data: contextPart.blob
4381
- }
4382
- })
5016
+ preserveUnsupportedImageForBridge && hasImageParts([inlinePart]) ? inlinePart : clampInlineMediaPart(inlinePart)
5017
+ );
5018
+ }
5019
+ }
5020
+ return this.#applyVisionBridgeIfNeeded(processedQueryParts, abortSignal);
5021
+ }
5022
+ async #applyVisionBridgeIfNeeded(parts, abortSignal) {
5023
+ if (!hasImageParts(parts) || !shouldRunVisionBridge(this.config)) {
5024
+ return parts;
5025
+ }
5026
+ let bridgeResult;
5027
+ try {
5028
+ debugLogger5.debug("vision bridge: gate matched, running conversion");
5029
+ bridgeResult = await runVisionBridge({
5030
+ config: this.config,
5031
+ parts,
5032
+ signal: abortSignal
5033
+ });
5034
+ } catch (error) {
5035
+ debugLogger5.debug(
5036
+ `vision bridge: failed before replacement; falling back to text-only parts error=${String(error instanceof Error ? error.message : error)}`
5037
+ );
5038
+ return splitImageParts(parts).nonImageParts;
5039
+ }
5040
+ debugLogger5.debug(
5041
+ `vision bridge: status=${bridgeResult.status} applied=${bridgeResult.applied} model=${bridgeResult.modelId ?? "(none)"}${bridgeResult.error ? ` error=${bridgeResult.error}` : ""}`
5042
+ );
5043
+ if (bridgeResult.status !== "skipped" || bridgeResult.egressOccurred) {
5044
+ try {
5045
+ await this.messageEmitter.emitAgentMessage(
5046
+ this.#formatVisionBridgeNotice(bridgeResult)
5047
+ );
5048
+ } catch (error) {
5049
+ debugLogger5.debug(
5050
+ `vision bridge: failed to emit notice; continuing with bridge result error=${String(error instanceof Error ? error.message : error)}`
5051
+ );
5052
+ }
5053
+ }
5054
+ if (abortSignal.aborted) {
5055
+ debugLogger5.debug("vision bridge: turn aborted after bridge returned");
5056
+ return splitImageParts(parts).nonImageParts;
5057
+ }
5058
+ if (bridgeResult.applied && bridgeResult.parts != null) {
5059
+ return normalizeParts(bridgeResult.parts);
5060
+ }
5061
+ return splitImageParts(parts).nonImageParts;
5062
+ }
5063
+ #formatVisionBridgeNotice(result) {
5064
+ const modelName = result.modelId ?? "vision model";
5065
+ const target = result.modelEndpoint ? `${modelName} (${result.modelEndpoint})` : modelName;
5066
+ const egressNote = result.egressOccurred ? ` Your image and prompt/context were sent to ${target}.` : "";
5067
+ if (result.status === "failed") {
5068
+ const reason = result.egressOccurred ? "the vision model request failed" : "the vision bridge could not run";
5069
+ return `Vision bridge (${modelName}) failed: ${reason}.${egressNote} The image was not interpreted.`;
5070
+ }
5071
+ if (result.status === "skipped") {
5072
+ return `Vision bridge cancelled.${egressNote}`;
5073
+ }
5074
+ const omitted = result.omittedCount > 0 ? ` (${result.omittedCount} image(s) omitted)` : "";
5075
+ return `Converted ${result.convertedCount} image(s)${omitted} to text via ${target}. Your image and prompt/context were sent to that model.`;
5076
+ }
5077
+ async #resolveExtensionMentionParts(extensionMentions, abortSignal) {
5078
+ if (extensionMentions.size === 0) return [];
5079
+ const activeExtensions = this.config.getActiveExtensions?.() ?? [];
5080
+ if (activeExtensions.length === 0) return [];
5081
+ const extensionParts = [];
5082
+ const resolvedExtensionNames = /* @__PURE__ */ new Set();
5083
+ let remainingBudget = EXTENSION_CONTEXT_BUDGET;
5084
+ for (const name of extensionMentions.values()) {
5085
+ const extension = matchExtensionByRef(name, activeExtensions);
5086
+ if (!extension) {
5087
+ this.debug(
5088
+ `Extension "${name}" not found among active extensions. Available: ${activeExtensions.map((e) => e.name).join(", ") || "(none)"}`
4383
5089
  );
5090
+ continue;
4384
5091
  }
5092
+ if (resolvedExtensionNames.has(extension.name)) continue;
5093
+ resolvedExtensionNames.add(extension.name);
5094
+ const context = await buildExtensionMentionContext(extension, {
5095
+ remainingBudget,
5096
+ signal: abortSignal,
5097
+ onDebugMessage: /* @__PURE__ */ __name((message) => this.debug(message), "onDebugMessage")
5098
+ });
5099
+ remainingBudget = context.remainingBudget;
5100
+ extensionParts.push({ text: context.text });
4385
5101
  }
4386
- return processedQueryParts;
5102
+ return extensionParts;
4387
5103
  }
4388
5104
  debug(msg) {
4389
5105
  if (this.config.getDebugMode()) {
4390
- debugLogger4.warn(msg);
5106
+ debugLogger5.warn(msg);
4391
5107
  }
4392
5108
  }
4393
5109
  /**
@@ -4407,7 +5123,7 @@ ${contextPart.text}`
4407
5123
  }
4408
5124
  );
4409
5125
  }).catch((err) => {
4410
- debugLogger4.debug(
5126
+ debugLogger5.debug(
4411
5127
  `ACP terminalSequence notification dropped (session=${this.sessionId}): ${err instanceof Error ? err.message : String(err)}`
4412
5128
  );
4413
5129
  });
@@ -4518,8 +5234,9 @@ function runWithAcpRuntimeOutputDir(settings, cwd, fn) {
4518
5234
  __name(runWithAcpRuntimeOutputDir, "runWithAcpRuntimeOutputDir");
4519
5235
 
4520
5236
  // packages/cli/src/acp-integration/acpAgent.ts
4521
- var debugLogger5 = createDebugLogger("ACP_AGENT");
5237
+ var debugLogger6 = createDebugLogger("ACP_AGENT");
4522
5238
  var BTW_CHILD_TIMEOUT_MS = 55e3;
5239
+ var WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS = 295e3;
4523
5240
  function collapseForkDirective(directive, maxLength) {
4524
5241
  const oneLine = directive.replace(/\s+/g, " ").trim();
4525
5242
  return oneLine.length > maxLength ? `${oneLine.slice(0, maxLength - 3)}\u2026` : oneLine;
@@ -4642,7 +5359,7 @@ function readRequiredString(value, fieldName) {
4642
5359
  }
4643
5360
  __name(readRequiredString, "readRequiredString");
4644
5361
  function validateSkillSlug(slug) {
4645
- if (!slug || slug === "." || slug === ".." || slug.includes("/") || slug.includes(path.sep) || !/^[a-zA-Z0-9._-]+$/.test(slug)) {
5362
+ if (!slug || slug === "." || slug === ".." || slug.includes("/") || slug.includes(path3.sep) || !/^[a-zA-Z0-9._-]+$/.test(slug)) {
4646
5363
  throw RequestError.invalidParams(void 0, "Invalid skill.slug");
4647
5364
  }
4648
5365
  }
@@ -5111,7 +5828,7 @@ async function downloadGitHubSkillDirectoryFromApi(githubUrl, directoryPath, rel
5111
5828
  const name = readRequiredString(record["name"], "github.name");
5112
5829
  const itemPath = readRequiredString(record["path"], "github.path");
5113
5830
  const type = readRequiredString(record["type"], "github.type");
5114
- const relativePath = relativeRoot ? path.posix.join(relativeRoot, name) : name;
5831
+ const relativePath = relativeRoot ? path3.posix.join(relativeRoot, name) : name;
5115
5832
  if (type === "dir") {
5116
5833
  files.push(
5117
5834
  ...await downloadGitHubSkillDirectoryFromApi(
@@ -5158,7 +5875,7 @@ async function downloadGitHubSkillDirectory(githubUrl, directoryPath) {
5158
5875
  githubUrl,
5159
5876
  directoryPath
5160
5877
  ).catch((error) => {
5161
- debugLogger5.warn(
5878
+ debugLogger6.warn(
5162
5879
  "GitHub API directory listing failed, falling back to archive download:",
5163
5880
  error
5164
5881
  );
@@ -5171,10 +5888,10 @@ __name(downloadGitHubSkillDirectory, "downloadGitHubSkillDirectory");
5171
5888
  async function downloadSkill(sourceUrl) {
5172
5889
  assertAllowedSkillSourceUrl(sourceUrl);
5173
5890
  const githubUrl = parseGitHubBlobSkillUrl(sourceUrl);
5174
- if (!githubUrl || path.posix.basename(githubUrl.filePath) !== "SKILL.md") {
5891
+ if (!githubUrl || path3.posix.basename(githubUrl.filePath) !== "SKILL.md") {
5175
5892
  return downloadSingleSkillFile(sourceUrl);
5176
5893
  }
5177
- const skillDirectory = path.posix.dirname(githubUrl.filePath);
5894
+ const skillDirectory = path3.posix.dirname(githubUrl.filePath);
5178
5895
  const files = await downloadGitHubSkillDirectory(githubUrl, skillDirectory);
5179
5896
  const skillFile = files.find((file) => file.relativePath === "SKILL.md");
5180
5897
  if (!skillFile) {
@@ -5190,9 +5907,9 @@ async function downloadSkill(sourceUrl) {
5190
5907
  }
5191
5908
  __name(downloadSkill, "downloadSkill");
5192
5909
  function resolveSkillInstallPath(skillDir, relativePath) {
5193
- const root = path.resolve(skillDir);
5194
- const target = path.resolve(skillDir, relativePath);
5195
- if (target !== root && !target.startsWith(root + path.sep)) {
5910
+ const root = path3.resolve(skillDir);
5911
+ const target = path3.resolve(skillDir, relativePath);
5912
+ if (target !== root && !target.startsWith(root + path3.sep)) {
5196
5913
  throw RequestError.invalidParams(
5197
5914
  void 0,
5198
5915
  `Invalid skill file path: ${relativePath}`
@@ -5202,9 +5919,9 @@ function resolveSkillInstallPath(skillDir, relativePath) {
5202
5919
  }
5203
5920
  __name(resolveSkillInstallPath, "resolveSkillInstallPath");
5204
5921
  function resolveManagedSkillDir(skillsBaseDir, slug) {
5205
- const root = path.resolve(skillsBaseDir);
5206
- const skillDir = path.resolve(skillsBaseDir, slug);
5207
- if (!skillDir.startsWith(root + path.sep)) {
5922
+ const root = path3.resolve(skillsBaseDir);
5923
+ const skillDir = path3.resolve(skillsBaseDir, slug);
5924
+ if (!skillDir.startsWith(root + path3.sep)) {
5208
5925
  throw RequestError.invalidParams(void 0, "Invalid skill.slug");
5209
5926
  }
5210
5927
  return skillDir;
@@ -5760,7 +6477,7 @@ function readMcpServers(source, scope) {
5760
6477
  const server = toMcpServerConfig(value);
5761
6478
  return server ? { name, scope, server: redactMcpServerSecrets(server) } : void 0;
5762
6479
  } catch (error) {
5763
- debugLogger5.warn(
6480
+ debugLogger6.warn(
5764
6481
  `Skipping malformed MCP server config [${scope}:${name}]:`,
5765
6482
  error
5766
6483
  );
@@ -5856,7 +6573,7 @@ function readHooks(source, scope, extensionName) {
5856
6573
  extensionName
5857
6574
  });
5858
6575
  } catch (error) {
5859
- debugLogger5.warn(
6576
+ debugLogger6.warn(
5860
6577
  `Skipping malformed hook entry [${scope}:${event}:${index}]:`,
5861
6578
  error
5862
6579
  );
@@ -5881,14 +6598,14 @@ function readScopeSettings(settings, scope) {
5881
6598
  __name(readScopeSettings, "readScopeSettings");
5882
6599
  async function resolvePreferredMemoryFile(dir, fallbackFilename) {
5883
6600
  for (const filename of getAllGeminiMdFilenames()) {
5884
- const filePath = path.join(dir, filename);
6601
+ const filePath = path3.join(dir, filename);
5885
6602
  try {
5886
6603
  await fs.access(filePath);
5887
6604
  return filePath;
5888
6605
  } catch {
5889
6606
  }
5890
6607
  }
5891
- return path.join(dir, fallbackFilename);
6608
+ return path3.join(dir, fallbackFilename);
5892
6609
  }
5893
6610
  __name(resolvePreferredMemoryFile, "resolvePreferredMemoryFile");
5894
6611
  async function resolveQwenMemoryPaths(params) {
@@ -5909,13 +6626,37 @@ async function resolveQwenMemoryPaths(params) {
5909
6626
  };
5910
6627
  }
5911
6628
  __name(resolveQwenMemoryPaths, "resolveQwenMemoryPaths");
6629
+ async function deliverClientMcpMessage(connection, serverName, message) {
6630
+ if (!connection) {
6631
+ throw new Error(
6632
+ `client MCP server '${serverName}' has no ACP connection yet`
6633
+ );
6634
+ }
6635
+ const response = await connection.extMethod(
6636
+ SERVE_CONTROL_EXT_METHODS.clientMcpMessage,
6637
+ { server: serverName, payload: message }
6638
+ );
6639
+ const payload = response["payload"];
6640
+ if (payload === void 0 || payload === null) {
6641
+ throw new Error(
6642
+ `client_mcp/message returned no payload for server '${serverName}'`
6643
+ );
6644
+ }
6645
+ return payload;
6646
+ }
6647
+ __name(deliverClientMcpMessage, "deliverClientMcpMessage");
5912
6648
  async function runAcpAgent(config, settings, argv) {
6649
+ let acpConnection;
6650
+ const bootstrapClientMcpSender = /* @__PURE__ */ __name((serverName, message) => deliverClientMcpMessage(acpConnection, serverName, message), "bootstrapClientMcpSender");
5913
6651
  await config.initialize({
5914
6652
  skipGeminiInitialization: true,
5915
6653
  // Bootstrap skips MCP discovery — each session runs its own
5916
6654
  // pool-routed discovery, so bootstrap-level spawns would be
5917
6655
  // redundant subprocess leaks (W119).
5918
- skipMcpDiscovery: true
6656
+ skipMcpDiscovery: true,
6657
+ // Bind the workspace-level manager's SDK callback so a runtime-added
6658
+ // client-hosted MCP server (#5626) round-trips over the parent WS.
6659
+ sendSdkMcpMessage: bootstrapClientMcpSender
5919
6660
  });
5920
6661
  const stdout = Writable.toWeb(process.stdout);
5921
6662
  const stdin = Readable.toWeb(process.stdin);
@@ -5925,6 +6666,7 @@ async function runAcpAgent(config, settings, argv) {
5925
6666
  const stream = ndJsonStream(stdout, stdin);
5926
6667
  let agentInstance;
5927
6668
  const connection = new AgentSideConnection((conn) => {
6669
+ acpConnection = conn;
5928
6670
  agentInstance = new QwenAgent(config, settings, argv, conn);
5929
6671
  return agentInstance;
5930
6672
  }, stream);
@@ -5933,7 +6675,7 @@ async function runAcpAgent(config, settings, argv) {
5933
6675
  try {
5934
6676
  await agentInstance.shutdownMcpPool(8e3);
5935
6677
  } catch (err) {
5936
- debugLogger5.error(`[ACP] MCP pool drain (${label}) error:`, err);
6678
+ debugLogger6.error(`[ACP] MCP pool drain (${label}) error:`, err);
5937
6679
  }
5938
6680
  }, "drainPoolBeforeExit");
5939
6681
  let shuttingDown = false;
@@ -5960,7 +6702,7 @@ async function runAcpAgent(config, settings, argv) {
5960
6702
  try {
5961
6703
  await hookSystem.fireSessionEndEvent(reason);
5962
6704
  } catch (err) {
5963
- debugLogger5.warn(
6705
+ debugLogger6.warn(
5964
6706
  `SessionEnd hook failed: ${err instanceof Error ? err.message : String(err)}`
5965
6707
  );
5966
6708
  }
@@ -5969,7 +6711,7 @@ async function runAcpAgent(config, settings, argv) {
5969
6711
  const shutdownHandler = /* @__PURE__ */ __name(async () => {
5970
6712
  if (shuttingDown) return;
5971
6713
  shuttingDown = true;
5972
- debugLogger5.debug("[ACP] Shutdown signal received, closing streams");
6714
+ debugLogger6.debug("[ACP] Shutdown signal received, closing streams");
5973
6715
  await fireSessionEndOnce("other" /* Other */);
5974
6716
  agentInstance?.disposeSessions();
5975
6717
  try {
@@ -5982,7 +6724,7 @@ async function runAcpAgent(config, settings, argv) {
5982
6724
  }
5983
6725
  await drainPoolBeforeExit("signal");
5984
6726
  runExitCleanup().catch((err) => {
5985
- debugLogger5.error("[ACP] Cleanup error:", err);
6727
+ debugLogger6.error("[ACP] Cleanup error:", err);
5986
6728
  }).finally(() => {
5987
6729
  process.exit(0);
5988
6730
  });
@@ -6169,33 +6911,55 @@ var QwenAgent = class {
6169
6911
  try {
6170
6912
  const result = await this.mcpPool.drainAll({ force: true, timeoutMs });
6171
6913
  if (result.forced > 0 || result.errors.length > 0) {
6172
- debugLogger5.warn(
6914
+ debugLogger6.warn(
6173
6915
  `MCP pool drain: ${result.drained} clean, ${result.forced} timed out, ${result.errors.length} errors`
6174
6916
  );
6175
6917
  }
6176
6918
  } catch (err) {
6177
- debugLogger5.error(
6919
+ debugLogger6.error(
6178
6920
  `MCP pool drainAll failed: ${err instanceof Error ? err.message : String(err)}`
6179
6921
  );
6180
6922
  }
6181
6923
  }
6182
- async closeStoredSession(sessionId) {
6924
+ async closeStoredSession(sessionId, opts) {
6183
6925
  const session = this.sessions.get(sessionId);
6184
6926
  if (!session) {
6185
6927
  this.mcpPool?.releaseSession(sessionId);
6186
6928
  return;
6187
6929
  }
6930
+ const requireFlush = opts?.requireFlush === true;
6931
+ const flushRecording = /* @__PURE__ */ __name(async () => {
6932
+ try {
6933
+ await session.getConfig().getChatRecordingService()?.flush();
6934
+ return void 0;
6935
+ } catch (err) {
6936
+ debugLogger6.debug(
6937
+ `Session ${sessionId} chat recording flush during close failed: ${err instanceof Error ? err.message : String(err)}`
6938
+ );
6939
+ return err;
6940
+ }
6941
+ }, "flushRecording");
6942
+ if (requireFlush) {
6943
+ const preCancelFlushError = await flushRecording();
6944
+ if (preCancelFlushError !== void 0) {
6945
+ throw preCancelFlushError;
6946
+ }
6947
+ }
6188
6948
  try {
6189
6949
  await session.cancelPendingPrompt();
6190
6950
  } catch (err) {
6191
- debugLogger5.debug(
6951
+ debugLogger6.debug(
6192
6952
  `Session ${sessionId} cancel during close failed: ${err instanceof Error ? err.message : String(err)}`
6193
6953
  );
6194
6954
  }
6955
+ const flushError = await flushRecording();
6956
+ if (flushError !== void 0 && requireFlush) {
6957
+ throw flushError;
6958
+ }
6195
6959
  try {
6196
6960
  await session.getConfig().getToolRegistry()?.stop();
6197
6961
  } catch (err) {
6198
- debugLogger5.debug(
6962
+ debugLogger6.debug(
6199
6963
  `Session ${sessionId} tool registry stop during close failed: ${err instanceof Error ? err.message : String(err)}`
6200
6964
  );
6201
6965
  }
@@ -6229,7 +6993,7 @@ var QwenAgent = class {
6229
6993
  scope: "workspace",
6230
6994
  ...event
6231
6995
  }).catch((err) => {
6232
- debugLogger5.debug(
6996
+ debugLogger6.debug(
6233
6997
  `MCP workspace budget event delivery to session ${sid} failed (kind=${event.kind}): ${err instanceof Error ? err.message : String(err)}`
6234
6998
  );
6235
6999
  });
@@ -6238,7 +7002,7 @@ var QwenAgent = class {
6238
7002
  async initialize(args) {
6239
7003
  this.clientCapabilities = args.clientCapabilities;
6240
7004
  const authMethods = buildAuthMethods();
6241
- const version = "0.19.3";
7005
+ const version = "0.19.4-nightly.20260702.46814e4f1";
6242
7006
  return {
6243
7007
  protocolVersion: PROTOCOL_VERSION,
6244
7008
  agentInfo: {
@@ -6394,7 +7158,7 @@ var QwenAgent = class {
6394
7158
  session.pendingWorktreeNotice = restored.contextMessage;
6395
7159
  }
6396
7160
  } catch (error) {
6397
- debugLogger5.warn(`ACP worktree restore failed: ${error}`);
7161
+ debugLogger6.warn(`ACP worktree restore failed: ${error}`);
6398
7162
  }
6399
7163
  }
6400
7164
  async unstable_listSessions(params) {
@@ -6515,7 +7279,7 @@ var QwenAgent = class {
6515
7279
  await extensionManager.refreshCache();
6516
7280
  extensions = extensionManager.getLoadedExtensions();
6517
7281
  } catch (error) {
6518
- debugLogger5.warn(
7282
+ debugLogger6.warn(
6519
7283
  "Extension loading failed, continuing without extensions:",
6520
7284
  error
6521
7285
  );
@@ -6641,7 +7405,7 @@ var QwenAgent = class {
6641
7405
  pm.addPersistentRule(rule, ruleType);
6642
7406
  }
6643
7407
  } catch (error) {
6644
- debugLogger5.warn(
7408
+ debugLogger6.warn(
6645
7409
  `Failed to sync permission rules to a live session: ${error instanceof Error ? error.message : String(error)}`
6646
7410
  );
6647
7411
  }
@@ -7151,7 +7915,7 @@ var QwenAgent = class {
7151
7915
  const currentAuth = activeRuntimeSnapshot?.authType ?? currentAuthType;
7152
7916
  const currentAcpModelId = hasCurrentModel && currentAuth ? formatAcpModelId(currentModelId, currentAuth) : currentModelId || void 0;
7153
7917
  const providers = /* @__PURE__ */ new Map();
7154
- for (const model of config.getAllConfiguredModels()) {
7918
+ for (const model of config.getAllConfiguredModels().filter(isMainSelectableModel)) {
7155
7919
  const authType = String(model.authType);
7156
7920
  let provider = providers.get(authType);
7157
7921
  if (!provider) {
@@ -7871,6 +8635,7 @@ var QwenAgent = class {
7871
8635
  id: ext.id,
7872
8636
  name: ext.name,
7873
8637
  displayName: ext.displayName,
8638
+ ...ext.config.description ? { description: ext.config.description } : {},
7874
8639
  version: ext.version,
7875
8640
  isActive: ext.isActive,
7876
8641
  path: ext.path,
@@ -7917,9 +8682,9 @@ var QwenAgent = class {
7917
8682
  );
7918
8683
  }
7919
8684
  const download = await downloadSkill(request.sourceUrl);
7920
- const skillsBaseDir = path.join(Storage.getGlobalQwenDir(), "skills");
8685
+ const skillsBaseDir = path3.join(Storage.getGlobalQwenDir(), "skills");
7921
8686
  const skillDir = resolveManagedSkillDir(skillsBaseDir, request.slug);
7922
- const skillFile = path.join(skillDir, "SKILL.md");
8687
+ const skillFile = path3.join(skillDir, "SKILL.md");
7923
8688
  const parsed = skillManager.parseSkillContent(
7924
8689
  download.skillContent,
7925
8690
  skillFile,
@@ -7939,7 +8704,7 @@ var QwenAgent = class {
7939
8704
  stagingDir,
7940
8705
  file.relativePath
7941
8706
  );
7942
- await fs.mkdir(path.dirname(targetPath), { recursive: true });
8707
+ await fs.mkdir(path3.dirname(targetPath), { recursive: true });
7943
8708
  await fs.writeFile(targetPath, file.content);
7944
8709
  }
7945
8710
  await fs.rm(skillDir, { recursive: true, force: true });
@@ -7978,11 +8743,11 @@ var QwenAgent = class {
7978
8743
  `Skill name "${parsed.name}" does not match requested slug "${request.slug}"`
7979
8744
  );
7980
8745
  }
7981
- const resolvedSkillDir = path.resolve(skillDir);
7982
- const resolvedSkillFile = path.resolve(skillFile);
7983
- const globalDir = path.resolve(Storage.getGlobalQwenDir());
7984
- const isDedicatedSkillDir = resolvedSkillFile === path.join(resolvedSkillDir, "SKILL.md");
7985
- if (!isDedicatedSkillDir || resolvedSkillDir === path.parse(resolvedSkillDir).root || resolvedSkillDir === globalDir) {
8746
+ const resolvedSkillDir = path3.resolve(skillDir);
8747
+ const resolvedSkillFile = path3.resolve(skillFile);
8748
+ const globalDir = path3.resolve(Storage.getGlobalQwenDir());
8749
+ const isDedicatedSkillDir = resolvedSkillFile === path3.join(resolvedSkillDir, "SKILL.md");
8750
+ if (!isDedicatedSkillDir || resolvedSkillDir === path3.parse(resolvedSkillDir).root || resolvedSkillDir === globalDir) {
7986
8751
  throw RequestError.invalidParams(
7987
8752
  void 0,
7988
8753
  `Refusing to delete unexpected skill directory: ${skillDir}`
@@ -7998,10 +8763,10 @@ var QwenAgent = class {
7998
8763
  async readManagedSkillFile(slug, scope, skillManager, cwd) {
7999
8764
  if (scope === "global") {
8000
8765
  const qwenSkillDir = resolveManagedSkillDir(
8001
- path.join(Storage.getGlobalQwenDir(), "skills"),
8766
+ path3.join(Storage.getGlobalQwenDir(), "skills"),
8002
8767
  slug
8003
8768
  );
8004
- const qwenSkillFile = path.join(qwenSkillDir, "SKILL.md");
8769
+ const qwenSkillFile = path3.join(qwenSkillDir, "SKILL.md");
8005
8770
  const qwenContent = await fs.readFile(qwenSkillFile, "utf8").catch(() => void 0);
8006
8771
  if (qwenContent !== void 0) {
8007
8772
  return {
@@ -8037,15 +8802,15 @@ var QwenAgent = class {
8037
8802
  );
8038
8803
  });
8039
8804
  return {
8040
- skillDir: path.dirname(skillFile),
8805
+ skillDir: path3.dirname(skillFile),
8041
8806
  skillFile,
8042
8807
  content
8043
8808
  };
8044
8809
  }
8045
8810
  async findProjectSkillFileFromCwd(slug, cwd, skillManager) {
8046
- const projectRoot = path.resolve(cwd);
8811
+ const projectRoot = path3.resolve(cwd);
8047
8812
  for (const configDir of PROJECT_SKILL_DIRS) {
8048
- const baseDir = path.join(projectRoot, configDir, SKILLS_DIR);
8813
+ const baseDir = path3.join(projectRoot, configDir, SKILLS_DIR);
8049
8814
  const skills = await skillManager.loadSkillsFromDir(baseDir, "project");
8050
8815
  const skill = skills.find((candidate) => candidate.name === slug);
8051
8816
  const skillFile = skill?.filePath;
@@ -8057,7 +8822,7 @@ var QwenAgent = class {
8057
8822
  );
8058
8823
  });
8059
8824
  return {
8060
- skillDir: path.dirname(skillFile),
8825
+ skillDir: path3.dirname(skillFile),
8061
8826
  skillFile,
8062
8827
  content
8063
8828
  };
@@ -8088,7 +8853,7 @@ var QwenAgent = class {
8088
8853
  }
8089
8854
  const nextContent = setSkillFrontmatterEnabled(content, request.enabled);
8090
8855
  skillManager.parseSkillContent(nextContent, skillFile, level);
8091
- if (path.basename(skillFile) !== "SKILL.md") {
8856
+ if (path3.basename(skillFile) !== "SKILL.md") {
8092
8857
  throw RequestError.invalidParams(
8093
8858
  void 0,
8094
8859
  `Refusing to write to unexpected skill file: ${skillFile}`
@@ -8368,6 +9133,76 @@ var QwenAgent = class {
8368
9133
  return this.buildWorkspaceExtensionsStatus(
8369
9134
  this.config
8370
9135
  );
9136
+ case SERVE_CONTROL_EXT_METHODS.workspaceMemoryRememberAvailability:
9137
+ return {
9138
+ available: this.config.isManagedMemoryAvailable()
9139
+ };
9140
+ case SERVE_CONTROL_EXT_METHODS.workspaceMemoryRemember: {
9141
+ const content = params["content"];
9142
+ if (typeof content !== "string" || !content.trim()) {
9143
+ throw RequestError.invalidParams(
9144
+ void 0,
9145
+ "Invalid or missing content"
9146
+ );
9147
+ }
9148
+ if (Buffer.byteLength(content, "utf8") > MAX_REMEMBER_CONTENT_BYTES) {
9149
+ throw RequestError.invalidParams(
9150
+ void 0,
9151
+ "Content exceeds maximum size"
9152
+ );
9153
+ }
9154
+ const rawContextMode = params["contextMode"] ?? "workspace";
9155
+ if (rawContextMode !== "workspace" && rawContextMode !== "clean") {
9156
+ throw RequestError.invalidParams(void 0, "Invalid contextMode");
9157
+ }
9158
+ const contextMode = rawContextMode;
9159
+ if (!this.config.isManagedMemoryAvailable()) {
9160
+ throw new RequestError(
9161
+ -32009,
9162
+ "Managed memory is unavailable for this daemon workspace",
9163
+ { errorKind: "managed_memory_unavailable" }
9164
+ );
9165
+ }
9166
+ const childSignal = AbortSignal.timeout(
9167
+ WORKSPACE_MEMORY_REMEMBER_CHILD_TIMEOUT_MS
9168
+ );
9169
+ try {
9170
+ const result = await runManagedRememberByAgent({
9171
+ config: this.config,
9172
+ projectRoot: this.config.getProjectRoot(),
9173
+ content: content.trim(),
9174
+ contextMode,
9175
+ abortSignal: childSignal
9176
+ });
9177
+ return result;
9178
+ } catch (err) {
9179
+ if (err instanceof RequestError) {
9180
+ throw err;
9181
+ }
9182
+ if (childSignal.aborted) {
9183
+ throw new RequestError(
9184
+ -32099,
9185
+ "Workspace memory remember timed out",
9186
+ { errorKind: "remember_timeout" }
9187
+ );
9188
+ }
9189
+ const code = extractRememberErrorCode(err);
9190
+ if (code === "managed_memory_unavailable") {
9191
+ throw new RequestError(
9192
+ -32009,
9193
+ "Managed memory is unavailable for this daemon workspace",
9194
+ { errorKind: "managed_memory_unavailable" }
9195
+ );
9196
+ }
9197
+ throw new RequestError(
9198
+ -32099,
9199
+ err instanceof Error && err.message ? err.message : "Workspace memory remember failed",
9200
+ {
9201
+ errorKind: code
9202
+ }
9203
+ );
9204
+ }
9205
+ }
8371
9206
  case SERVE_CONTROL_EXT_METHODS.workspaceMcpRestart: {
8372
9207
  const serverName = params["serverName"];
8373
9208
  if (typeof serverName !== "string" || serverName.length === 0) {
@@ -8523,23 +9358,34 @@ var QwenAgent = class {
8523
9358
  }
8524
9359
  if (action === "enable") {
8525
9360
  const settings = loadSettings(this.config.getTargetDir());
9361
+ let settingsChanged = false;
8526
9362
  for (const scope of ["User" /* User */, "Workspace" /* Workspace */]) {
8527
9363
  const scopeSettings = settings.forScope(scope).settings;
8528
9364
  const currentExcluded2 = scopeSettings.mcp?.excluded || [];
8529
- if (currentExcluded2.includes(serverName)) {
8530
- settings.setValue(
8531
- scope,
8532
- "mcp.excluded",
8533
- currentExcluded2.filter((name) => name !== serverName)
8534
- );
9365
+ const filtered = currentExcluded2.filter(
9366
+ (pattern) => pattern !== serverName
9367
+ );
9368
+ if (filtered.length !== currentExcluded2.length) {
9369
+ settings.setValue(scope, "mcp.excluded", filtered);
9370
+ settingsChanged = true;
8535
9371
  }
8536
9372
  }
8537
9373
  const currentExcluded = this.config.getExcludedMcpServers() || [];
8538
- this.config.setExcludedMcpServers(
8539
- currentExcluded.filter((name) => name !== serverName)
9374
+ const runtimeFiltered = currentExcluded.filter(
9375
+ (pattern) => pattern !== serverName
8540
9376
  );
9377
+ let runtimeChanged = false;
9378
+ if (runtimeFiltered.length !== currentExcluded.length) {
9379
+ this.config.setExcludedMcpServers(runtimeFiltered);
9380
+ runtimeChanged = true;
9381
+ }
8541
9382
  await toolRegistry.discoverToolsForServer(serverName);
8542
- return { serverName, action, ok: true, changed: true };
9383
+ return {
9384
+ serverName,
9385
+ action,
9386
+ ok: true,
9387
+ changed: settingsChanged || runtimeChanged
9388
+ };
8543
9389
  }
8544
9390
  if (action === "disable") {
8545
9391
  const settings = loadSettings(this.config.getTargetDir());
@@ -8561,18 +9407,27 @@ var QwenAgent = class {
8561
9407
  }
8562
9408
  const scopeSettings = settings.forScope(targetScope).settings;
8563
9409
  const currentExcluded = scopeSettings.mcp?.excluded || [];
8564
- if (!currentExcluded.includes(serverName)) {
9410
+ let settingsChanged = false;
9411
+ if (!matchesAnyServerPattern(serverName, currentExcluded)) {
8565
9412
  settings.setValue(targetScope, "mcp.excluded", [
8566
9413
  ...currentExcluded,
8567
9414
  serverName
8568
9415
  ]);
9416
+ settingsChanged = true;
8569
9417
  }
8570
9418
  const runtimeExcluded = this.config.getExcludedMcpServers() || [];
8571
- if (!runtimeExcluded.includes(serverName)) {
9419
+ let runtimeChanged = false;
9420
+ if (!matchesAnyServerPattern(serverName, runtimeExcluded)) {
8572
9421
  this.config.setExcludedMcpServers([...runtimeExcluded, serverName]);
9422
+ runtimeChanged = true;
8573
9423
  }
8574
9424
  await toolRegistry.disableMcpServer(serverName);
8575
- return { serverName, action, ok: true, changed: true };
9425
+ return {
9426
+ serverName,
9427
+ action,
9428
+ ok: true,
9429
+ changed: settingsChanged || runtimeChanged
9430
+ };
8576
9431
  }
8577
9432
  if (action === "clear-auth") {
8578
9433
  const tokenStorage = new MCPOAuthTokenStorage();
@@ -8687,7 +9542,9 @@ var QwenAgent = class {
8687
9542
  "Invalid or missing sessionId"
8688
9543
  );
8689
9544
  }
8690
- await this.closeStoredSession(sessionId);
9545
+ await this.closeStoredSession(sessionId, {
9546
+ requireFlush: params["requireFlush"] === true
9547
+ });
8691
9548
  return { sessionId, closed: true };
8692
9549
  }
8693
9550
  case SERVE_CONTROL_EXT_METHODS.sessionCd: {
@@ -8699,7 +9556,7 @@ var QwenAgent = class {
8699
9556
  "Invalid or missing sessionId"
8700
9557
  );
8701
9558
  }
8702
- if (typeof targetPath !== "string" || targetPath.length === 0 || !path.isAbsolute(targetPath) || targetPath.includes("\0")) {
9559
+ if (typeof targetPath !== "string" || targetPath.length === 0 || !path3.isAbsolute(targetPath) || targetPath.includes("\0")) {
8703
9560
  throw RequestError.invalidParams(
8704
9561
  void 0,
8705
9562
  "Invalid or missing path (must be an absolute path)"
@@ -8817,7 +9674,7 @@ var QwenAgent = class {
8817
9674
  try {
8818
9675
  await setLanguageAsync(language);
8819
9676
  } catch (err) {
8820
- debugLogger5.warn("setLanguageAsync failed:", err);
9677
+ debugLogger6.warn("setLanguageAsync failed:", err);
8821
9678
  throw new RequestError(
8822
9679
  -32603,
8823
9680
  `Failed to switch UI language: ${err instanceof Error ? err.message : String(err)}`
@@ -8831,7 +9688,7 @@ var QwenAgent = class {
8831
9688
  language
8832
9689
  );
8833
9690
  } catch (err) {
8834
- debugLogger5.warn("Failed to persist UI language setting:", err);
9691
+ debugLogger6.warn("Failed to persist UI language setting:", err);
8835
9692
  }
8836
9693
  let outputLanguage = null;
8837
9694
  let refreshed = false;
@@ -8846,7 +9703,7 @@ var QwenAgent = class {
8846
9703
  );
8847
9704
  fileWriteOk = true;
8848
9705
  } catch (err) {
8849
- debugLogger5.warn("Failed to write output-language.md:", err);
9706
+ debugLogger6.warn("Failed to write output-language.md:", err);
8850
9707
  }
8851
9708
  if (fileWriteOk) {
8852
9709
  try {
@@ -8856,7 +9713,7 @@ var QwenAgent = class {
8856
9713
  settingValue
8857
9714
  );
8858
9715
  } catch (err) {
8859
- debugLogger5.warn(
9716
+ debugLogger6.warn(
8860
9717
  "Failed to persist output language setting:",
8861
9718
  err
8862
9719
  );
@@ -8876,7 +9733,7 @@ var QwenAgent = class {
8876
9733
  writeOutputLanguageAndRegisterPath(settingValue, cfg);
8877
9734
  }
8878
9735
  } catch (err) {
8879
- debugLogger5.warn(
9736
+ debugLogger6.warn(
8880
9737
  `Failed to write output-language.md for session ${s.getId()} (path=${sessionPath ?? "global-default"}):`,
8881
9738
  err
8882
9739
  );
@@ -8889,7 +9746,7 @@ var QwenAgent = class {
8889
9746
  (r) => r.status === "rejected"
8890
9747
  ).length;
8891
9748
  if (failedCount > 0) {
8892
- debugLogger5.warn(
9749
+ debugLogger6.warn(
8893
9750
  `Language refresh failed for ${failedCount}/${results.length} session(s)`
8894
9751
  );
8895
9752
  }
@@ -8907,14 +9764,14 @@ var QwenAgent = class {
8907
9764
  "Invalid or missing sessionId"
8908
9765
  );
8909
9766
  }
8910
- debugLogger5.debug(`recap ext-method received for session=${sessionId}`);
9767
+ debugLogger6.debug(`recap ext-method received for session=${sessionId}`);
8911
9768
  const session = this.sessionOrThrow(sessionId);
8912
9769
  const config = session.getConfig();
8913
9770
  const recap = await generateSessionRecap(
8914
9771
  config,
8915
9772
  new AbortController().signal
8916
9773
  );
8917
- debugLogger5.debug(
9774
+ debugLogger6.debug(
8918
9775
  `recap ext-method completed for session=${sessionId} result=${recap ? `len=${recap.length}` : "null"}`
8919
9776
  );
8920
9777
  return { sessionId, recap };
@@ -8938,7 +9795,7 @@ var QwenAgent = class {
8938
9795
  const config = session.getConfig();
8939
9796
  const cacheSafeParams = buildBtwCacheSafeParams(config);
8940
9797
  if (!cacheSafeParams) {
8941
- debugLogger5.debug(`btw: no cacheSafeParams for session=${sessionId}`);
9798
+ debugLogger6.debug(`btw: no cacheSafeParams for session=${sessionId}`);
8942
9799
  return { sessionId, answer: null };
8943
9800
  }
8944
9801
  const childSignal = AbortSignal.timeout(BTW_CHILD_TIMEOUT_MS);
@@ -8986,7 +9843,7 @@ var QwenAgent = class {
8986
9843
  try {
8987
9844
  hasHistory = (config.getGeminiClient().getHistoryShallow() ?? []).length > 0;
8988
9845
  } catch (error) {
8989
- debugLogger5.debug("Failed to read history before /fork:", error);
9846
+ debugLogger6.debug("Failed to read history before /fork:", error);
8990
9847
  }
8991
9848
  if (!hasHistory) {
8992
9849
  throw RequestError.invalidParams(
@@ -9028,7 +9885,7 @@ var QwenAgent = class {
9028
9885
  ]
9029
9886
  });
9030
9887
  } catch (error) {
9031
- debugLogger5.debug("Failed to record fork event in history:", error);
9888
+ debugLogger6.debug("Failed to record fork event in history:", error);
9032
9889
  }
9033
9890
  return { sessionId, description, launched: true };
9034
9891
  }
@@ -9091,7 +9948,7 @@ ${outputText}
9091
9948
  'taskKind must be "agent", "shell", or "monitor"'
9092
9949
  );
9093
9950
  }
9094
- debugLogger5.info(
9951
+ debugLogger6.info(
9095
9952
  `sessionTaskCancel requested sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind}`
9096
9953
  );
9097
9954
  const session = this.sessionOrThrow(sessionId);
@@ -9101,7 +9958,7 @@ ${outputText}
9101
9958
  const task = config.getBackgroundTaskRegistry().get(taskId);
9102
9959
  if (!task || task.status !== "running" && task.status !== "paused") {
9103
9960
  const reason = task ? "not_running" : "not_found";
9104
- debugLogger5.info(
9961
+ debugLogger6.info(
9105
9962
  `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
9106
9963
  );
9107
9964
  return { cancelled: false, reason, status: task?.status };
@@ -9111,7 +9968,7 @@ ${outputText}
9111
9968
  } else {
9112
9969
  config.getBackgroundTaskRegistry().cancel(taskId);
9113
9970
  }
9114
- debugLogger5.info(
9971
+ debugLogger6.info(
9115
9972
  `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
9116
9973
  );
9117
9974
  return { cancelled: true, status: task.status };
@@ -9120,13 +9977,13 @@ ${outputText}
9120
9977
  const task = config.getBackgroundShellRegistry().get(taskId);
9121
9978
  if (!task || task.status !== "running") {
9122
9979
  const reason = task ? "not_running" : "not_found";
9123
- debugLogger5.info(
9980
+ debugLogger6.info(
9124
9981
  `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
9125
9982
  );
9126
9983
  return { cancelled: false, reason, status: task?.status };
9127
9984
  }
9128
9985
  config.getBackgroundShellRegistry().requestCancel(taskId);
9129
- debugLogger5.info(
9986
+ debugLogger6.info(
9130
9987
  `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
9131
9988
  );
9132
9989
  return { cancelled: true, status: task.status };
@@ -9135,13 +9992,13 @@ ${outputText}
9135
9992
  const task = config.getMonitorRegistry().get(taskId);
9136
9993
  if (!task || task.status !== "running") {
9137
9994
  const reason = task ? "not_running" : "not_found";
9138
- debugLogger5.info(
9995
+ debugLogger6.info(
9139
9996
  `sessionTaskCancel skipped sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} reason=${reason} status=${task?.status ?? "missing"}`
9140
9997
  );
9141
9998
  return { cancelled: false, reason, status: task?.status };
9142
9999
  }
9143
10000
  config.getMonitorRegistry().cancel(taskId);
9144
- debugLogger5.info(
10001
+ debugLogger6.info(
9145
10002
  `sessionTaskCancel completed sessionId=${sessionId} taskId=${taskId} taskKind=${taskKind} status=${task.status}`
9146
10003
  );
9147
10004
  return { cancelled: true, status: task.status };
@@ -9171,7 +10028,7 @@ ${outputText}
9171
10028
  durationMs: Date.now() - cleared.setAt
9172
10029
  });
9173
10030
  }
9174
- debugLogger5.info(
10031
+ debugLogger6.info(
9175
10032
  `sessionGoalClear sessionId=${sessionId} cleared=${!!cleared} condition=${cleared?.condition ?? "(none)"}`
9176
10033
  );
9177
10034
  return {
@@ -9179,6 +10036,21 @@ ${outputText}
9179
10036
  condition: cleared?.condition
9180
10037
  };
9181
10038
  }
10039
+ case SERVE_CONTROL_EXT_METHODS.sessionContinue: {
10040
+ const sessionId = params["sessionId"];
10041
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
10042
+ throw RequestError.invalidParams(
10043
+ void 0,
10044
+ "Invalid or missing sessionId"
10045
+ );
10046
+ }
10047
+ const session = this.sessionOrThrow(sessionId);
10048
+ const result = await session.continueLastTurn();
10049
+ debugLogger6.info(
10050
+ `sessionContinue sessionId=${sessionId} accepted=${result.accepted} interruption=${result.interruption}`
10051
+ );
10052
+ return result;
10053
+ }
9182
10054
  case SERVE_CONTROL_EXT_METHODS.workspaceMcpRuntimeAdd: {
9183
10055
  const name = params["name"];
9184
10056
  const config = params["config"];
@@ -9189,7 +10061,7 @@ ${outputText}
9189
10061
  "Invalid or missing name"
9190
10062
  );
9191
10063
  }
9192
- if (name.length > 256 || !/^[A-Za-z0-9_-]+$/.test(name) || name === "__proto__" || name === "constructor" || name === "prototype") {
10064
+ if (!isValidServerName(name)) {
9193
10065
  throw RequestError.invalidParams(
9194
10066
  void 0,
9195
10067
  "Server name must be \u2264256 chars, alphanumeric + underscore/hyphen, and not a reserved JS property name"
@@ -9225,13 +10097,37 @@ ${outputText}
9225
10097
  oauth: _oauth,
9226
10098
  headers: _headers,
9227
10099
  type: _type,
10100
+ // Reverse tool channel marker (issue #5626, Phase 2). The parent
10101
+ // serve process stamps this on a client-hosted (extension) MCP
10102
+ // server's runtime config; it never reaches the transport itself.
10103
+ [CLIENT_MCP_OVER_WS_CONFIG_FLAG]: clientMcpOverWs,
9228
10104
  ...safeConfig
9229
10105
  } = config;
10106
+ if (clientMcpOverWs === true) {
10107
+ safeConfig["type"] = "sdk";
10108
+ }
9230
10109
  const result = await manager.addRuntimeMcpServer(
9231
10110
  name,
9232
10111
  safeConfig,
9233
10112
  originatorClientId
9234
10113
  );
10114
+ await Promise.all(
10115
+ this.getActiveSessions().map(async (session) => {
10116
+ const sessionManager = session.getConfig().getToolRegistry()?.getMcpClientManager();
10117
+ if (!sessionManager) return;
10118
+ try {
10119
+ await sessionManager.addRuntimeMcpServer(
10120
+ name,
10121
+ safeConfig,
10122
+ originatorClientId
10123
+ );
10124
+ } catch (sessionErr) {
10125
+ debugLogger6.warn(
10126
+ `workspaceMcpRuntimeAdd: failed to add runtime MCP server '${name}' to active session ${session.getConfig().getSessionId()}: ${sessionErr instanceof Error ? sessionErr.message : String(sessionErr)}`
10127
+ );
10128
+ }
10129
+ })
10130
+ );
9235
10131
  return result;
9236
10132
  } catch (err) {
9237
10133
  if (err instanceof McpBudgetWouldExceedError) {
@@ -9266,7 +10162,7 @@ ${outputText}
9266
10162
  "Invalid or missing name"
9267
10163
  );
9268
10164
  }
9269
- if (name.length > 256 || !/^[A-Za-z0-9_-]+$/.test(name) || name === "__proto__" || name === "constructor" || name === "prototype") {
10165
+ if (!isValidServerName(name)) {
9270
10166
  throw RequestError.invalidParams(
9271
10167
  void 0,
9272
10168
  "Server name must be \u2264256 chars, alphanumeric + underscore/hyphen, and not a reserved JS property name"
@@ -9289,6 +10185,22 @@ ${outputText}
9289
10185
  name,
9290
10186
  originatorClientId
9291
10187
  );
10188
+ await Promise.all(
10189
+ this.getActiveSessions().map(async (session) => {
10190
+ const sessionManager = session.getConfig().getToolRegistry()?.getMcpClientManager();
10191
+ if (!sessionManager) return;
10192
+ try {
10193
+ await sessionManager.removeRuntimeMcpServer(
10194
+ name,
10195
+ originatorClientId
10196
+ );
10197
+ } catch (sessionErr) {
10198
+ debugLogger6.warn(
10199
+ `workspaceMcpRuntimeRemove: failed to remove runtime MCP server '${name}' from active session ${session.getConfig().getSessionId()}: ${sessionErr instanceof Error ? sessionErr.message : String(sessionErr)}`
10200
+ );
10201
+ }
10202
+ })
10203
+ );
9292
10204
  return result;
9293
10205
  }
9294
10206
  case SERVE_CONTROL_EXT_METHODS.workspaceExtensionsRefresh: {
@@ -9299,7 +10211,7 @@ ${outputText}
9299
10211
  try {
9300
10212
  await extensionManager.refreshTools();
9301
10213
  } catch (err) {
9302
- debugLogger5.warn(
10214
+ debugLogger6.warn(
9303
10215
  `Extension tool refresh failed for session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`
9304
10216
  );
9305
10217
  }
@@ -9447,7 +10359,7 @@ ${outputText}
9447
10359
  filesFailed = fileResult.filesFailed;
9448
10360
  } catch (err) {
9449
10361
  const reason = err instanceof Error ? err.message : String(err);
9450
- debugLogger5.error(
10362
+ debugLogger6.error(
9451
10363
  `[ACP] File-history rewind failed for session=${sessionId} promptId=${promptId}: ${reason}`
9452
10364
  );
9453
10365
  filesFailed = [`file-history-rewind: ${reason}`];
@@ -9505,7 +10417,7 @@ ${outputText}
9505
10417
  );
9506
10418
  } catch (error) {
9507
10419
  replayError = error instanceof Error ? error.message : String(error);
9508
- debugLogger5.warn(
10420
+ debugLogger6.warn(
9509
10421
  "[loadUpdates] History replay failed for session %s (partial updates: %d):",
9510
10422
  sessionId,
9511
10423
  updates.length,
@@ -9865,7 +10777,7 @@ ${outputText}
9865
10777
  newMerged.providerProtocol ?? {}
9866
10778
  );
9867
10779
  } catch (err) {
9868
- debugLogger5.warn(
10780
+ debugLogger6.warn(
9869
10781
  `reload: reloadModelProvidersConfig failed for session ${id}: ${err}`
9870
10782
  );
9871
10783
  }
@@ -9875,7 +10787,7 @@ ${outputText}
9875
10787
  try {
9876
10788
  await config.switchModel(authType, newModelName);
9877
10789
  } catch (err) {
9878
- debugLogger5.warn(
10790
+ debugLogger6.warn(
9879
10791
  `reload: switchModel failed for session ${id}: ${err}`
9880
10792
  );
9881
10793
  }
@@ -9883,7 +10795,7 @@ ${outputText}
9883
10795
  try {
9884
10796
  await config.refreshAuth(authType);
9885
10797
  } catch (err) {
9886
- debugLogger5.warn(
10798
+ debugLogger6.warn(
9887
10799
  `reload: refreshAuth failed for session ${id}: ${err}`
9888
10800
  );
9889
10801
  }
@@ -9898,7 +10810,7 @@ ${outputText}
9898
10810
  try {
9899
10811
  config.setApprovalMode(newMode);
9900
10812
  } catch (err) {
9901
- debugLogger5.warn(
10813
+ debugLogger6.warn(
9902
10814
  `reload: setApprovalMode failed for session ${id}: ${err}`
9903
10815
  );
9904
10816
  }
@@ -9907,14 +10819,14 @@ ${outputText}
9907
10819
  try {
9908
10820
  await config.refreshHierarchicalMemory();
9909
10821
  } catch (err) {
9910
- debugLogger5.warn(
10822
+ debugLogger6.warn(
9911
10823
  `reload: refreshHierarchicalMemory failed for session ${id}: ${err}`
9912
10824
  );
9913
10825
  }
9914
10826
  try {
9915
10827
  await config.getGeminiClient()?.refreshSystemInstruction();
9916
10828
  } catch (err) {
9917
- debugLogger5.warn(
10829
+ debugLogger6.warn(
9918
10830
  `reload: refreshSystemInstruction failed for session ${id}: ${err}`
9919
10831
  );
9920
10832
  }
@@ -9924,7 +10836,7 @@ ${outputText}
9924
10836
  for (let i = 0; i < results.length; i++) {
9925
10837
  if (results[i].status === "rejected") {
9926
10838
  const reason = results[i].reason;
9927
- debugLogger5.warn(
10839
+ debugLogger6.warn(
9928
10840
  `Session ${sessions[i][0]} reload failed: ${reason}`
9929
10841
  );
9930
10842
  skipped.push(sessions[i][0]);
@@ -9942,6 +10854,21 @@ ${outputText}
9942
10854
  }
9943
10855
  }
9944
10856
  // --- private helpers ---
10857
+ /**
10858
+ * Reverse tool channel (issue #5626, Phase 2). Build the session
10859
+ * `McpClientManager`'s `sendSdkMcpMessage` callback. Client-hosted
10860
+ * (extension) MCP servers are registered SDK-type, so the manager routes
10861
+ * their JSON-RPC through this callback. We forward each frame UP to the
10862
+ * parent serve process via the `qwen/control/client_mcp/message` ext-method;
10863
+ * the parent's `BridgeClient.extMethod` hands it to the per-WS-connection
10864
+ * `ClientMcpRegistrar`, which carries it down the daemon WS to the extension
10865
+ * and returns the correlated response (the `payload` field). All SDK-type
10866
+ * servers in this session share one callback — the `serverName` argument
10867
+ * routes to the right client-hosted server in the parent.
10868
+ */
10869
+ buildClientMcpSender() {
10870
+ return (serverName, message) => deliverClientMcpMessage(this.connection, serverName, message);
10871
+ }
9945
10872
  async newSessionConfig(cwd, mcpServers, sessionId, resume) {
9946
10873
  this.settings = loadSettings(cwd);
9947
10874
  const sessionMcpServers = {};
@@ -10026,6 +10953,14 @@ ${outputText}
10026
10953
  if (typeof config.enableFileCheckpointing === "function") {
10027
10954
  config.enableFileCheckpointing();
10028
10955
  }
10956
+ if (typeof this.config.getRuntimeMcpServers === "function" && typeof config.addRuntimeMcpServer === "function") {
10957
+ const bootstrapRuntimeMcpServers = this.config.getRuntimeMcpServers();
10958
+ for (const [runtimeServerName, runtimeServerConfig] of Object.entries(
10959
+ bootstrapRuntimeMcpServers
10960
+ )) {
10961
+ config.addRuntimeMcpServer(runtimeServerName, runtimeServerConfig);
10962
+ }
10963
+ }
10029
10964
  if (this.mcpPool !== void 0 && typeof config.setMcpTransportPool === "function") {
10030
10965
  config.setMcpTransportPool(this.mcpPool);
10031
10966
  }
@@ -10039,13 +10974,20 @@ ${outputText}
10039
10974
  sessionId: sid,
10040
10975
  ...event
10041
10976
  }).catch((err) => {
10042
- debugLogger5.debug(
10977
+ debugLogger6.debug(
10043
10978
  `MCP budget extNotification dropped (session=${sid}, kind=${event.kind}): ${err instanceof Error ? err.message : String(err)}`
10044
10979
  );
10045
10980
  });
10046
10981
  });
10047
10982
  }
10048
- await config.initialize();
10983
+ await config.initialize({
10984
+ // Reverse tool channel (issue #5626, Phase 2): bind the session
10985
+ // manager's SDK MCP callback to the `client_mcp/message` ext-method so a
10986
+ // client-hosted (extension) MCP server added at runtime reaches the
10987
+ // daemon WS. Servers that aren't client-hosted never use this callback
10988
+ // (the daemon only adds SDK-type runtime servers for client MCP).
10989
+ sendSdkMcpMessage: this.buildClientMcpSender()
10990
+ });
10049
10991
  await config.waitForMcpReady();
10050
10992
  const failedMcpServers = typeof config.getFailedMcpServerNames === "function" ? config.getFailedMcpServerNames() : [];
10051
10993
  if (failedMcpServers.length > 0) {
@@ -10067,7 +11009,7 @@ ${outputText}
10067
11009
  try {
10068
11010
  await config.refreshAuth(selectedType, true);
10069
11011
  } catch (e) {
10070
- debugLogger5.error(`Authentication failed: ${e}`);
11012
+ debugLogger6.error(`Authentication failed: ${e}`);
10071
11013
  throw RequestError.authRequired(
10072
11014
  {
10073
11015
  authMethods: pickAuthMethodsForAuthRequired(selectedType)
@@ -10082,7 +11024,20 @@ ${outputText}
10082
11024
  this.connection,
10083
11025
  config.getSessionId(),
10084
11026
  this.clientCapabilities.fs,
10085
- config.getFileSystemService()
11027
+ config.getFileSystemService(),
11028
+ {
11029
+ // SYNC: Mirrors ReadFileTool's default allowed local roots, including
11030
+ // auto-memory roots, so ACP-local read fallback follows the same policy.
11031
+ localReadRoots: [
11032
+ config.storage.getProjectTempDir(),
11033
+ path3.join(config.storage.getProjectDir(), "subagents"),
11034
+ Storage.getGlobalTempDir(),
11035
+ getAutoMemoryRoot(config.getTargetDir()),
11036
+ getUserAutoMemoryRoot(),
11037
+ ...config.storage.getUserSkillsDirs(),
11038
+ Storage.getUserExtensionsDir()
11039
+ ]
11040
+ }
10086
11041
  );
10087
11042
  config.setFileSystemService(acpFileSystemService);
10088
11043
  }
@@ -10120,7 +11075,7 @@ ${outputText}
10120
11075
  buildAvailableModels(config) {
10121
11076
  const rawCurrentModelId = (config.getModel() || this.config.getModel() || "").trim();
10122
11077
  const currentAuthType = config.getAuthType();
10123
- const allConfiguredModels = config.getAllConfiguredModels();
11078
+ const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
10124
11079
  const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
10125
11080
  const currentModelId = activeRuntimeSnapshot ? formatAcpModelId(
10126
11081
  activeRuntimeSnapshot.id,
@@ -10156,7 +11111,7 @@ ${outputText}
10156
11111
  }
10157
11112
  buildConfigOptions(config) {
10158
11113
  const currentApprovalMode = config.getApprovalMode();
10159
- const allConfiguredModels = config.getAllConfiguredModels();
11114
+ const allConfiguredModels = config.getAllConfiguredModels().filter(isMainSelectableModel);
10160
11115
  const rawCurrentModelId = (config.getModel() || "").trim();
10161
11116
  const currentAuthType = config.getAuthType?.();
10162
11117
  const activeRuntimeSnapshot = config.getActiveRuntimeModelSnapshot?.();
@@ -10202,6 +11157,10 @@ ${outputText}
10202
11157
  return authType ? formatAcpModelId(baseModelId, authType) : baseModelId;
10203
11158
  }
10204
11159
  };
11160
+ function isMainSelectableModel(model) {
11161
+ return model.fastOnly !== true && model.voiceOnly !== true;
11162
+ }
11163
+ __name(isMainSelectableModel, "isMainSelectableModel");
10205
11164
  function diffSettingsKeys(oldMerged, newMerged) {
10206
11165
  const changed = /* @__PURE__ */ new Set();
10207
11166
  const allKeys = /* @__PURE__ */ new Set([
@@ -10220,6 +11179,7 @@ export {
10220
11179
  AUTH_PREFLIGHT_ENV_KEYS,
10221
11180
  AUTH_PREFLIGHT_WAIVED_AUTH_TYPES,
10222
11181
  createWorkspaceMcpBudget,
11182
+ deliverClientMcpMessage,
10223
11183
  extractFilesFromTarGz,
10224
11184
  fetchAllowedGitHub,
10225
11185
  normalizeCoreSettingValue,