@qwen-code/qwen-code 0.19.3 → 0.19.4

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