@qwen-code/qwen-code 0.21.8 → 0.21.9

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 (258) hide show
  1. package/README.md +2 -1
  2. package/bundled/qc-helper/docs/configuration/settings.md +32 -30
  3. package/bundled/qc-helper/docs/extension/introduction.md +15 -1
  4. package/bundled/qc-helper/docs/features/code-review.md +2 -0
  5. package/bundled/qc-helper/docs/qwen-serve-deploy-local.md +1 -1
  6. package/bundled/qc-helper/docs/qwen-serve.md +42 -39
  7. package/bundled/review/SKILL.md +11 -7
  8. package/chunks/{MaxSizedBox-7PM6BGJ5.js → MaxSizedBox-6BZANW7X.js} +9 -9
  9. package/chunks/{StandaloneSessionPicker-3UQHM7GV.js → StandaloneSessionPicker-VDC5KE7T.js} +28 -25
  10. package/chunks/{acpAgent-5UWLFV6V.js → acpAgent-F2NSBCUF.js} +1433 -706
  11. package/chunks/{agent-624T2BLO.js → agent-VOHJOEBB.js} +8 -8
  12. package/chunks/{agent-headless-2EE5J2KX.js → agent-headless-J5NFOPHP.js} +8 -8
  13. package/chunks/{bridge-JENKGFFP.js → bridge-NQIZZLAS.js} +13 -13
  14. package/chunks/{channel-management-service-DDWUPHMU.js → channel-management-service-PHELKOW2.js} +4 -4
  15. package/chunks/{channel-settings-store-SA6JZSPH.js → channel-settings-store-VIY2OENO.js} +17 -15
  16. package/chunks/{channel-worker-group-E67EN3RV.js → channel-worker-group-MSHT6CWR.js} +6 -6
  17. package/chunks/{channel-worker-manager-SW4C45CF.js → channel-worker-manager-XXD3GDSK.js} +6 -6
  18. package/chunks/{channel-worker-supervisor-5SRD24FG.js → channel-worker-supervisor-IVSDIMI4.js} +4 -4
  19. package/chunks/{chunk-S3Y3GBFD.js → chunk-23VQTELM.js} +2 -2
  20. package/chunks/{chunk-XZKQT2B2.js → chunk-3WDYBTTI.js} +5 -5
  21. package/chunks/{chunk-YEF7XEO2.js → chunk-3XVDTHBJ.js} +3 -3
  22. package/chunks/{chunk-IBYULW65.js → chunk-46YJDYCU.js} +145 -10
  23. package/chunks/chunk-4JRM2BSM.js +48 -0
  24. package/chunks/{chunk-L4N6TIP7.js → chunk-4TRQPFDP.js} +4 -2
  25. package/chunks/{chunk-5ZGR6RKJ.js → chunk-4XQNB3KN.js} +3 -3
  26. package/chunks/chunk-5EJZE3FP.js +140 -0
  27. package/chunks/{chunk-XHD7CRP2.js → chunk-5RBDAGC6.js} +2 -2
  28. package/chunks/{chunk-KJZ26VBX.js → chunk-67EWJE77.js} +1 -1
  29. package/chunks/{chunk-TE3ZAJE7.js → chunk-6Q5ABMTU.js} +1 -1
  30. package/chunks/{chunk-U5SLIG7E.js → chunk-7AEPJGL4.js} +1 -1
  31. package/chunks/{chunk-OHKJVAKG.js → chunk-7GOYZQPR.js} +20 -1977
  32. package/chunks/{chunk-3SE7J4DR.js → chunk-7S4JBJLU.js} +3 -3
  33. package/chunks/{chunk-TKSZJ5BV.js → chunk-AKJQSWV4.js} +3 -3
  34. package/chunks/{chunk-E4ZBKMSF.js → chunk-ATMO54WE.js} +6 -6
  35. package/chunks/{chunk-KKDVZLGL.js → chunk-BAMN6XSD.js} +1 -1
  36. package/chunks/{chunk-IUQ52YJK.js → chunk-BIRWP6LU.js} +1 -1
  37. package/chunks/{chunk-AKA24BGO.js → chunk-BT6GQIEJ.js} +1 -1
  38. package/chunks/{chunk-ZCLLJARV.js → chunk-BZP4QPNK.js} +10 -10
  39. package/chunks/{chunk-2M5N3DHY.js → chunk-CMOBWHWW.js} +1 -1
  40. package/chunks/{chunk-WV5J6Q6N.js → chunk-D7BXVKSF.js} +1 -1
  41. package/chunks/{chunk-NLREQJFK.js → chunk-DA4HRSBW.js} +15 -0
  42. package/chunks/{chunk-GOEVOFQI.js → chunk-DH5YXSIF.js} +3 -3
  43. package/chunks/{chunk-DIWZNWZT.js → chunk-DX3RGJMS.js} +13 -2
  44. package/chunks/{chunk-PLFATELK.js → chunk-DZUO626D.js} +1 -1
  45. package/chunks/{chunk-NGYI5EF5.js → chunk-E2FPSSKP.js} +8 -8
  46. package/chunks/{chunk-ZPLALRXZ.js → chunk-FBMCDU63.js} +4 -137
  47. package/chunks/{chunk-2ILAOM2I.js → chunk-FCLHHEUK.js} +17 -16
  48. package/chunks/{chunk-SNRT2TJJ.js → chunk-G2TX5ZXC.js} +3 -3
  49. package/chunks/{chunk-C5VWPTGT.js → chunk-GAKGOXTC.js} +1 -1
  50. package/chunks/{chunk-QQWD7LZV.js → chunk-HADWBW5R.js} +4 -4
  51. package/chunks/{chunk-77QVBFCU.js → chunk-HZGBKOX3.js} +3 -3
  52. package/chunks/{chunk-RPWVIXDB.js → chunk-I5VM3SKY.js} +4 -4
  53. package/chunks/{chunk-7VUERJHS.js → chunk-II6G3EY6.js} +325 -104
  54. package/chunks/{chunk-FN6YXJOP.js → chunk-IOEZE2YT.js} +1 -1
  55. package/chunks/chunk-JUWS2Z5K.js +1995 -0
  56. package/chunks/{chunk-VXZ5HAOV.js → chunk-K4ADGWHU.js} +1 -1
  57. package/chunks/{chunk-YOM7DEGB.js → chunk-KSAFZ5TA.js} +2 -2
  58. package/chunks/{chunk-KAQCJO6Y.js → chunk-KTKYGS2P.js} +35 -9
  59. package/chunks/{chunk-GBDINRIL.js → chunk-KTQGGPGV.js} +3 -3
  60. package/chunks/{chunk-6M26GBKX.js → chunk-L2AFWWJE.js} +42 -3
  61. package/chunks/{chunk-QXVSWG66.js → chunk-LHEC5V6G.js} +2 -2
  62. package/chunks/{chunk-J26HHFI5.js → chunk-LP2QGNSP.js} +32 -2
  63. package/chunks/{chunk-HWX5HPUG.js → chunk-LZZLUCRS.js} +108 -11
  64. package/chunks/{chunk-KTJFU7RT.js → chunk-MFDYOYZZ.js} +2 -2
  65. package/chunks/{chunk-3WNQOYHQ.js → chunk-MHP476D3.js} +74 -31
  66. package/chunks/{chunk-Y4TETC74.js → chunk-MTCYGTWH.js} +4 -4
  67. package/chunks/{chunk-EN2TKTFH.js → chunk-NV3F44FL.js} +6 -2
  68. package/chunks/{chunk-BF52U4XJ.js → chunk-OGDIJKHK.js} +1 -1
  69. package/chunks/{chunk-7DVT4LVV.js → chunk-OGNFHGIG.js} +3 -3
  70. package/chunks/{chunk-IYES5WOT.js → chunk-P2FK2HBV.js} +2 -2
  71. package/chunks/{chunk-2M4K4KYS.js → chunk-P3RBMQ4P.js} +4 -4
  72. package/chunks/{chunk-FYAXKJK7.js → chunk-P4ZEZSJZ.js} +692 -106
  73. package/chunks/{chunk-UFALT4LK.js → chunk-PBRVUF5B.js} +1 -1
  74. package/chunks/{chunk-LKRMNRSN.js → chunk-PDPFQ2FO.js} +2 -0
  75. package/chunks/{chunk-6TR7CFXH.js → chunk-PNBBJOCP.js} +1 -1
  76. package/chunks/{chunk-M3STHEMM.js → chunk-PVOGHGSF.js} +5 -5
  77. package/chunks/{chunk-4WU6NY7F.js → chunk-QSNSL4R4.js} +2 -2
  78. package/chunks/{chunk-DQSDACD2.js → chunk-RBHQXGDK.js} +1 -1
  79. package/chunks/{chunk-ZHA242R4.js → chunk-RVSTTOJ4.js} +2 -2
  80. package/chunks/{chunk-EIVICVHM.js → chunk-SCAZZ3Q4.js} +4 -4
  81. package/chunks/{chunk-BF224XHL.js → chunk-T5RLUXWJ.js} +2 -2
  82. package/chunks/{chunk-B52AWKBB.js → chunk-T6HEQR2Q.js} +16 -5
  83. package/chunks/{chunk-FWBXMANW.js → chunk-THIKWITR.js} +3 -3
  84. package/chunks/{chunk-WG6542MA.js → chunk-TQCYNLNJ.js} +1 -1
  85. package/chunks/chunk-TQOF5KBL.js +71 -0
  86. package/chunks/{chunk-NPUXXRJI.js → chunk-TZ6KJ44L.js} +1 -1
  87. package/chunks/{chunk-YHJFF7KH.js → chunk-UJUM2SRN.js} +147 -47
  88. package/chunks/{chunk-4ALIARFY.js → chunk-UYAUA63Q.js} +1 -1
  89. package/chunks/chunk-VFMASYQA.js +389 -0
  90. package/chunks/chunk-VGHZT6JR.js +51 -0
  91. package/chunks/{chunk-ZWBUH5UP.js → chunk-VKJ4KTPJ.js} +1 -1
  92. package/chunks/{chunk-QGHSP65Z.js → chunk-VNNWAZSI.js} +2 -2
  93. package/chunks/{chunk-757GB46F.js → chunk-W3FSZSF2.js} +1 -1
  94. package/chunks/{chunk-GQCEQPYQ.js → chunk-WGK4TSB7.js} +3 -3
  95. package/chunks/{chunk-PG62DFWD.js → chunk-WP5VOXF7.js} +6 -127
  96. package/chunks/{chunk-NVRDQFCX.js → chunk-XTY5DN4Y.js} +1 -1
  97. package/chunks/{chunk-GXSNGY5M.js → chunk-XX7QFP34.js} +1677 -1307
  98. package/chunks/{chunk-3PY4LPJF.js → chunk-YGT2AUJ7.js} +84 -4
  99. package/chunks/{chunk-4DU3NCHG.js → chunk-YTFDX4PR.js} +8 -3
  100. package/chunks/{chunk-OA4JVLSA.js → chunk-Z7VOMGDC.js} +22 -1
  101. package/chunks/{chunk-DX5W5AON.js → chunk-ZBXGWQ4S.js} +3 -3
  102. package/chunks/chunk-ZLBIFIMV.js +1099 -0
  103. package/chunks/{chunk-HA3SAB64.js → chunk-ZOSTTOUJ.js} +1 -1
  104. package/chunks/{chunk-VWMUDKLC.js → chunk-ZTQSOV56.js} +4 -4
  105. package/chunks/{computer-use-ESQYJ3JY.js → computer-use-PMZ7ICGY.js} +8 -8
  106. package/chunks/{config-utils-XQHBJP7G.js → config-utils-ULCSRMWD.js} +4 -4
  107. package/chunks/{contextCommand-CX3YF4ZE.js → contextCommand-GEZU7YHE.js} +11 -10
  108. package/chunks/{core-runtime-DIAU7JMG.js → core-runtime-HRJI2MN6.js} +8 -8
  109. package/chunks/{create-sub-session-HDPD6HOA.js → create-sub-session-VBNVI4YU.js} +9 -9
  110. package/chunks/{daemon-VZBGJUNL.js → daemon-POSQPD3G.js} +115 -7
  111. package/chunks/{daemon-status-provider-H6O4UQ4F.js → daemon-status-provider-N4CRQKFI.js} +18 -18
  112. package/chunks/{daemon-trust-policy-BNUXG6MT.js → daemon-trust-policy-BHO3JUHM.js} +16 -14
  113. package/chunks/{daemon-trust-policy-monitor-E4EQ7GF4.js → daemon-trust-policy-monitor-YEKJ2726.js} +16 -14
  114. package/chunks/{deferred-core-runtime-CBMSHPAU.js → deferred-core-runtime-SH57E3J3.js} +8 -8
  115. package/chunks/{dist-KVISB2O2.js → dist-3NTTUAHT.js} +1 -1
  116. package/chunks/{dist-RCH3I7SS.js → dist-6BMOAE3Z.js} +2 -2
  117. package/chunks/{dist-IRL7WA5Z.js → dist-B2CGTW6T.js} +1 -1
  118. package/chunks/{dist-TUSWNE2I.js → dist-EWW3J4HQ.js} +1 -1
  119. package/chunks/{dist-HIACTZEK.js → dist-EZE3V3Q7.js} +1 -1
  120. package/chunks/{dist-EEEQGCZA.js → dist-J47AXBZC.js} +1 -1
  121. package/chunks/{dist-MKX5OSFF.js → dist-OTUO3AS2.js} +4 -1088
  122. package/chunks/{dist-MBF3I5MX.js → dist-Z3CS4OVP.js} +1 -1
  123. package/chunks/{earlyInputCapture-UIOP7OIN.js → earlyInputCapture-B2JJ2HME.js} +8 -8
  124. package/chunks/{edit-ITMZYFKA.js → edit-7FTXENAN.js} +10 -8
  125. package/chunks/{enter-worktree-S4PL5UV6.js → enter-worktree-JPHV2PBG.js} +8 -8
  126. package/chunks/{enterPlanMode-YV52NNJT.js → enterPlanMode-CGBJ3AHJ.js} +8 -8
  127. package/chunks/{environment-NGOWPRZV.js → environment-LXASZAHD.js} +11 -11
  128. package/chunks/{errors-BU26DTDX.js → errors-XIGVT2AD.js} +10 -10
  129. package/chunks/{exit-worktree-XRM6LH5F.js → exit-worktree-DKSGXW65.js} +8 -8
  130. package/chunks/{exitPlanMode-ZLI5F7KR.js → exitPlanMode-THFL2HYN.js} +8 -8
  131. package/chunks/{fast-path-UJWORIED.js → fast-path-4NXAT5UL.js} +4 -3
  132. package/chunks/{fast-path-settings-37WQD5B2.js → fast-path-settings-RPAVBD7P.js} +2 -2
  133. package/chunks/{gemini-ZSKNDUMU.js → gemini-B3N6FACU.js} +56 -60
  134. package/chunks/{geminiContentGenerator-YGEXOEKD.js → geminiContentGenerator-YYFE2HIE.js} +1 -1
  135. package/chunks/{glob-7KZG44BR.js → glob-BPHSCFJP.js} +8 -8
  136. package/chunks/{grep-6ZFC7RJ5.js → grep-ROZIRFLU.js} +8 -8
  137. package/chunks/{handleAutoUpdate-KSEL3TQS.js → handleAutoUpdate-WV6SPZQB.js} +13 -12
  138. package/chunks/{i18n-ZIUXBW5V.js → i18n-YWJWPQKD.js} +13 -11
  139. package/chunks/{image-gen-AUIZJJNK.js → image-gen-SISRZ6FS.js} +1 -1
  140. package/chunks/{initializer-7AHY3N6Q.js → initializer-66OAD6GO.js} +16 -14
  141. package/chunks/{installationInfo-VGBXMYLY.js → installationInfo-3YUGIP36.js} +9 -9
  142. package/chunks/{list-PSFKQHLB.js → list-VEYR66XB.js} +19 -17
  143. package/chunks/{loadedSettingsAdapter-FTMRCTOF.js → loadedSettingsAdapter-V2JCPCYD.js} +16 -14
  144. package/chunks/{loggingContentGenerator-MZAN52UV.js → loggingContentGenerator-RBLNDREC.js} +6 -6
  145. package/chunks/main-PXAUXNF2.js +8 -0
  146. package/chunks/{managed-npm-update-O4NHK45X.js → managed-npm-update-C5ID5R4R.js} +9 -9
  147. package/chunks/{mcp-6CFP2AYQ.js → mcp-OZ54NZJQ.js} +16 -14
  148. package/chunks/{monitor-DAKD6OU4.js → monitor-7Y4TVFTK.js} +18 -14
  149. package/chunks/{nonInteractiveCli-RC7VIUZL.js → nonInteractiveCli-QEK4LHAC.js} +51 -49
  150. package/chunks/{notebook-edit-474DPMRL.js → notebook-edit-OG35EBGY.js} +9 -8
  151. package/chunks/{openaiContentGenerator-2NFY2MWO.js → openaiContentGenerator-443BNRPJ.js} +5 -5
  152. package/chunks/{pidfile-KBFVDNQP.js → pidfile-ZK4OLRZA.js} +8 -8
  153. package/chunks/{processUtils-6GRYH4Y3.js → processUtils-4YGB4RUZ.js} +2 -2
  154. package/chunks/{qwenContentGenerator-KAHU6IZY.js → qwenContentGenerator-D5C2LCSZ.js} +9 -9
  155. package/chunks/{qwenOAuth2-RRQTCP4W.js → qwenOAuth2-Z4YQBMYN.js} +1 -1
  156. package/chunks/{read-file-3M6WXDXP.js → read-file-EXTJLWMU.js} +4 -4
  157. package/chunks/{resumeHistoryUtils-D6TAY3HX.js → resumeHistoryUtils-UPCGRJUL.js} +13 -12
  158. package/chunks/{ripGrep-PLPENT3E.js → ripGrep-QBVIRCU4.js} +8 -8
  159. package/chunks/{run-qwen-serve-ZAFIJKJC.js → run-qwen-serve-ZYGJNE3E.js} +169 -68
  160. package/chunks/{runtime-ZED7CYOZ.js → runtime-BZZ4LWCP.js} +21 -19
  161. package/chunks/{scheduler-MD3AL2W5.js → scheduler-YNQNOU2E.js} +135 -15
  162. package/chunks/{sdk-exporters-http-KHAB5JSD.js → sdk-exporters-http-XVTZQRCE.js} +2 -2
  163. package/chunks/{sdk-impl-KD7FSTC3.js → sdk-impl-7W7AKCS4.js} +7 -3
  164. package/chunks/{serve-E3VUDRZN.js → serve-LJHAXDCH.js} +18 -14
  165. package/chunks/{server-OWMEMAKR.js → server-ADMLELR6.js} +1229 -1098
  166. package/chunks/{session-HZ57QK7K.js → session-YKZXFFTM.js} +55 -53
  167. package/chunks/{settings-V7BR3RKP.js → settings-EQBUAQ7Y.js} +15 -13
  168. package/chunks/{shell-23OMNMW6.js → shell-3QZOJGTT.js} +8 -8
  169. package/chunks/{skill-6HBDYZRS.js → skill-UWSSMXE7.js} +5 -5
  170. package/chunks/{skill-settings-HWJLTU7M.js → skill-settings-T6MLB7NU.js} +15 -13
  171. package/chunks/{spawnChannel-N6KKUW5B.js → spawnChannel-VS7ZFUFP.js} +11 -11
  172. package/chunks/{standalone-update-QGIKHATD.js → standalone-update-2W26SRRO.js} +11 -10
  173. package/chunks/{startInteractiveUI-GX2CRL42.js → startInteractiveUI-CVS7K3D6.js} +86 -75
  174. package/chunks/{team-create-CFFKU7KL.js → team-create-4WFTNNUL.js} +8 -8
  175. package/chunks/{team-plan-approval-6EFOKR6A.js → team-plan-approval-DBYI33S3.js} +8 -8
  176. package/chunks/{terminal-image-renderer-TLDNACQY.js → terminal-image-renderer-S5WPPQKH.js} +8 -8
  177. package/chunks/{theme-manager-5LJ3H7VO.js → theme-manager-LI2RIOVR.js} +8 -8
  178. package/chunks/{tool-search-UJV3A24H.js → tool-search-4773EUQ4.js} +4 -4
  179. package/chunks/{total-session-admission-D63QYXGO.js → total-session-admission-PAY5CH2Z.js} +16 -15
  180. package/chunks/{trustedFolders-KZCOB53W.js → trustedFolders-FRSLE674.js} +9 -9
  181. package/chunks/{types-ML3TRJQ5.js → types-WYCFCEHR.js} +5 -3
  182. package/chunks/{update-relaunch-IFILM3SA.js → update-relaunch-ZSFQTU6M.js} +5 -5
  183. package/chunks/{updateCheck-YZ2CRCFC.js → updateCheck-AXWSO7KA.js} +12 -11
  184. package/chunks/{useAutoAcceptIndicator-AXEVNA3Q.js → useAutoAcceptIndicator-VHLXIA7U.js} +18 -16
  185. package/chunks/{validateNonInterActiveAuth-3WTOJFWA.js → validateNonInterActiveAuth-PDJECMFU.js} +48 -46
  186. package/chunks/{version-GXK7OHZY.js → version-RBN764QS.js} +1 -1
  187. package/chunks/{web-fetch-ILB4GAVR.js → web-fetch-7BJK42AF.js} +4 -4
  188. package/chunks/{web-search-HKRPGPZZ.js → web-search-L2CILQXV.js} +4 -3
  189. package/chunks/{workflow-FXIPRQQA.js → workflow-5VH2R74I.js} +170 -137
  190. package/chunks/{workspace-providers-status-NYHC7HSR.js → workspace-providers-status-OZTQ6JFH.js} +19 -17
  191. package/chunks/{workspace-registration-store-AKEB7OMW.js → workspace-registration-store-IX5RJQS5.js} +1 -1
  192. package/chunks/{workspace-registry-CDAS5TVZ.js → workspace-registry-73N4BGG4.js} +16 -15
  193. package/chunks/{workspace-service-PAMUKMAV.js → workspace-service-I5RUPSNV.js} +26 -22
  194. package/chunks/{workspace-skills-status-YQSHCCMS.js → workspace-skills-status-DMK6VYDO.js} +17 -15
  195. package/chunks/{workspace-trust-reconciler-RPTXOIY2.js → workspace-trust-reconciler-QX4RL3TC.js} +24 -21
  196. package/chunks/{write-file-CGS57TS4.js → write-file-UGDD5UTS.js} +8 -8
  197. package/chunks/{zoom-image-E7D72GYV.js → zoom-image-ZAHEO3YP.js} +4 -4
  198. package/cli.js +12 -12
  199. package/package.json +3 -3
  200. package/web-shell/assets/{arc-DybZaz32.js → arc-CHY4saMJ.js} +1 -1
  201. package/web-shell/assets/{architectureDiagram-3BPJPVTR-jN3qBd19.js → architectureDiagram-3BPJPVTR-Bu6CwvHj.js} +1 -1
  202. package/web-shell/assets/{blockDiagram-GPEHLZMM-Ct5DZPrj.js → blockDiagram-GPEHLZMM-XGt6iGf_.js} +1 -1
  203. package/web-shell/assets/{c4Diagram-AAUBKEIU-CUAFMlUG.js → c4Diagram-AAUBKEIU-CBbYl19W.js} +1 -1
  204. package/web-shell/assets/channel-B40srHOa.js +1 -0
  205. package/web-shell/assets/{chunk-2J33WTMH-CuV8n90B.js → chunk-2J33WTMH-BEhmVrdx.js} +1 -1
  206. package/web-shell/assets/{chunk-4BX2VUAB-BUzl_kls.js → chunk-4BX2VUAB-fQLyE0Bg.js} +1 -1
  207. package/web-shell/assets/{chunk-55IACEB6-BnDBLX6A.js → chunk-55IACEB6-yGdtyx4a.js} +1 -1
  208. package/web-shell/assets/{chunk-727SXJPM-B83uTOjm.js → chunk-727SXJPM-DYbX2GLo.js} +1 -1
  209. package/web-shell/assets/{chunk-AQP2D5EJ-DM3HLgpT.js → chunk-AQP2D5EJ-DyKTEsYn.js} +1 -1
  210. package/web-shell/assets/{chunk-FMBD7UC4-CSQ5hgyb.js → chunk-FMBD7UC4-D8Uy_GzX.js} +1 -1
  211. package/web-shell/assets/{chunk-ND2GUHAM-BWOF4SxX.js → chunk-ND2GUHAM-C8wNR2K7.js} +1 -1
  212. package/web-shell/assets/{chunk-QZHKN3VN-BsOH9LMi.js → chunk-QZHKN3VN-BnybNxnt.js} +1 -1
  213. package/web-shell/assets/classDiagram-4FO5ZUOK-qumcXQW_.js +1 -0
  214. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-qumcXQW_.js +1 -0
  215. package/web-shell/assets/{cose-bilkent-S5V4N54A-IENqH2iJ.js → cose-bilkent-S5V4N54A-jq4uiFRp.js} +1 -1
  216. package/web-shell/assets/{dagre-BM42HDAG--dyzDTGo.js → dagre-BM42HDAG-Bs7oye1U.js} +1 -1
  217. package/web-shell/assets/{diagram-2AECGRRQ-BE7Dcqof.js → diagram-2AECGRRQ-BHGGJNyM.js} +1 -1
  218. package/web-shell/assets/{diagram-5GNKFQAL-Cr8-5OTj.js → diagram-5GNKFQAL-BJezVLGT.js} +1 -1
  219. package/web-shell/assets/{diagram-KO2AKTUF-Bzey3uAY.js → diagram-KO2AKTUF-5JzQN_rL.js} +1 -1
  220. package/web-shell/assets/{diagram-LMA3HP47-sErl28Xb.js → diagram-LMA3HP47-BlPYOgY9.js} +1 -1
  221. package/web-shell/assets/{diagram-OG6HWLK6-Dq30ATIo.js → diagram-OG6HWLK6-CtMHEaFK.js} +1 -1
  222. package/web-shell/assets/{erDiagram-TEJ5UH35-BkCoEfVW.js → erDiagram-TEJ5UH35-CRNdq2AB.js} +1 -1
  223. package/web-shell/assets/{flowDiagram-I6XJVG4X-DHwO0rrI.js → flowDiagram-I6XJVG4X-De1WwLzu.js} +1 -1
  224. package/web-shell/assets/{ganttDiagram-6RSMTGT7-s7vAPxWq.js → ganttDiagram-6RSMTGT7-YbWfL5Zn.js} +1 -1
  225. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CQxhwPOU.js → gitGraphDiagram-PVQCEYII-DzCN8bmd.js} +1 -1
  226. package/web-shell/assets/index-BDOGGGaN.css +5 -0
  227. package/web-shell/assets/{index-CF4wApWp.js → index-CocPvF9Q.js} +1 -1
  228. package/web-shell/assets/index-DSH2KrMc.js +1771 -0
  229. package/web-shell/assets/{infoDiagram-5YYISTIA-BnFrD0bq.js → infoDiagram-5YYISTIA-DLHoopnE.js} +1 -1
  230. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-Bf9zK5NK.js → ishikawaDiagram-YF4QCWOH-XnbU04SS.js} +1 -1
  231. package/web-shell/assets/{journeyDiagram-JHISSGLW-CyGAhsxi.js → journeyDiagram-JHISSGLW-CipPyEeJ.js} +1 -1
  232. package/web-shell/assets/{kanban-definition-UN3LZRKU-CbsK5akB.js → kanban-definition-UN3LZRKU-DrSeyE5d.js} +1 -1
  233. package/web-shell/assets/{linear-cQsF4P8D.js → linear-LrBKnAo_.js} +1 -1
  234. package/web-shell/assets/{mermaid.core-Z4HVqrKY.js → mermaid.core-C3U9UVmf.js} +5 -5
  235. package/web-shell/assets/{mindmap-definition-RKZ34NQL-CYnrBo_f.js → mindmap-definition-RKZ34NQL-DqRWgC9H.js} +1 -1
  236. package/web-shell/assets/{pieDiagram-4H26LBE5-CBzOfhrs.js → pieDiagram-4H26LBE5-BNXY5p6H.js} +1 -1
  237. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-D4LK1Ebn.js → quadrantDiagram-W4KKPZXB-Ccj9QzE4.js} +1 -1
  238. package/web-shell/assets/{requirementDiagram-4Y6WPE33-Bzti2Lcj.js → requirementDiagram-4Y6WPE33-CjPM2_gx.js} +1 -1
  239. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-yTBw5Z2s.js → sankeyDiagram-5OEKKPKP-C0RsvKbj.js} +1 -1
  240. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-D07UVRiZ.js → sequenceDiagram-3UESZ5HK-ClN1P5z7.js} +1 -1
  241. package/web-shell/assets/{stateDiagram-AJRCARHV-UE_lHsTn.js → stateDiagram-AJRCARHV-By0eiB2Y.js} +1 -1
  242. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Ds0WlK3F.js +1 -0
  243. package/web-shell/assets/{timeline-definition-PNZ67QCA-DRvYdiTs.js → timeline-definition-PNZ67QCA-g_iaSQgv.js} +1 -1
  244. package/web-shell/assets/{vennDiagram-CIIHVFJN-CQLIXs0u.js → vennDiagram-CIIHVFJN-y9ZqJ-TD.js} +1 -1
  245. package/web-shell/assets/{wardley-L42UT6IY-JZz_Xqse.js → wardley-L42UT6IY-BVVfaxpF.js} +1 -1
  246. package/web-shell/assets/{wardleyDiagram-YWT4CUSO--i7BiJwB.js → wardleyDiagram-YWT4CUSO-D3ooZKZT.js} +1 -1
  247. package/web-shell/assets/{xychartDiagram-2RQKCTM6-Bkc-bW0S.js → xychartDiagram-2RQKCTM6-6GXrY6M1.js} +1 -1
  248. package/web-shell/index.html +2 -2
  249. package/chunks/chunk-RXFQM6FQ.js +0 -42
  250. package/chunks/chunk-UA5H3DS2.js +0 -203
  251. package/web-shell/assets/channel-BMwLrwLW.js +0 -1
  252. package/web-shell/assets/classDiagram-4FO5ZUOK-BGDv98jY.js +0 -1
  253. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BGDv98jY.js +0 -1
  254. package/web-shell/assets/index-Bckpjr-I.css +0 -5
  255. package/web-shell/assets/index-DwEr9HEW.js +0 -1768
  256. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DC6xgXjX.js +0 -1
  257. /package/chunks/{chunk-VXZBKKQR.js → chunk-NTTBXH7U.js} +0 -0
  258. /package/chunks/{chunk-GRC5HGPI.js → chunk-OIBUG24X.js} +0 -0
@@ -39,7 +39,7 @@ The first npm release of `qwen serve` (v0.16-alpha) is intentionally narrow —
39
39
  - ✅ Bring-your-own bearer token via `QWEN_SERVER_TOKEN` env var ([Authentication](#authentication) for setup)
40
40
  - ❌ **Containerized deployment** — Docker / Compose / Kubernetes / nginx reverse-proxy with TLS termination NOT in v0.16-alpha. Defers to v0.16.x once an enterprise pilot is committed (would otherwise rot from no-one-validating).
41
41
  - ❌ **Multi-daemon coordination on one host** — one daemon can host several explicitly registered workspaces, but daemons do not coordinate with each other. Cross-host federation, instance-path token keying, and stale-token cleanup defer to v0.16.x.
42
- - **Auto-generated daemon tokens** — alpha is BYO-token (one `openssl rand -hex 32` away). Auto-gen + token-store infrastructure defers to v0.16.x.
42
+ - **Fresh Local Control tokens** — `--local-control` generates a token for that process. General daemon token storage remains BYO-token.
43
43
 
44
44
  **Hardening — minimum viable for local single-user:**
45
45
 
@@ -192,7 +192,7 @@ idle daemon returns `initialized: false` with an empty snapshot. Once a
192
192
  session is alive they switch to `initialized: true` and surface the real
193
193
  state.
194
194
 
195
- To mirror the CLI `/skills` panel remotely, call `POST /workspace/skills/:name/enable` with `{ "enabled": true | false }` after checking the `workspace_skill_toggle` capability. The route updates workspace `skills.disabled` and `skills.enabled` as needed, rejects unknown, hidden, inactive-extension, higher-scope-locked, and untrusted targets, and immediately refreshes active ACP sessions. Enabling a `skills.defaultDisabled` skill writes a canonical opt-in to `skills.enabled`; a hard `skills.disabled` entry inherited from a higher scope still cannot be overridden. Skill status cells expose `disabledReason` (`hard`, `default`, or `inactive_extension`) and an optional `lockedScope`. A `deferred` response means the setting was saved while no ACP child was running; it will apply when the child starts. `skills.disabled` disables both manual and model use, unlike `disable-model-invocation: true`, which keeps direct `/skill-name` invocation available.
195
+ To mirror the CLI `/skills` panel remotely, call `POST /workspace/skills/:name/enable` with `{ "enabled": true | false }` after checking the `workspace_skill_toggle` capability. To change several Skills, check `workspace_skill_batch_toggle` and call `POST /workspace/skills/enable` with `{ "skillNames": ["review", "deploy"], "enabled": false }`; its response separates successful `results` from per-target `errors`, persists valid targets together, and refreshes active ACP sessions once. The routes update workspace `skills.disabled` and `skills.enabled` as needed and reject unknown, hidden, inactive-extension, higher-scope-locked, and untrusted targets. Enabling a `skills.defaultDisabled` skill writes a canonical opt-in to `skills.enabled`; a hard `skills.disabled` entry inherited from a higher scope still cannot be overridden. Skill status cells expose `disabledReason` (`hard`, `default`, or `inactive_extension`) and an optional `lockedScope`. A `deferred` response means the setting was saved while no ACP child was running; it will apply when the child starts. `skills.disabled` disables both manual and model use, unlike `disable-model-invocation: true`, which keeps direct `/skill-name` invocation available.
196
196
 
197
197
  `GET /workspace/env` and `GET /workspace/preflight` always answer with
198
198
  `initialized: true` regardless of ACP state. `env` never consults ACP
@@ -212,7 +212,7 @@ always render. **ACP-level cells** (auth, MCP discovery, skills, providers,
212
212
  tool registry, egress) require a live ACP child — when the daemon is idle
213
213
  they emit `status: 'not_started'` placeholders rather than spawning ACP just
214
214
  to populate them. Failures map to a closed `errorKind` enum (`missing_binary`,
215
- `auth_env_error`, `init_timeout`, `protocol_error`, `missing_file`,
215
+ `auth_env_error`, `init_timeout`, `restore_timeout`, `protocol_error`, `missing_file`,
216
216
  `parse_error`, `blocked_egress`) so client UIs can render structured
217
217
  remediation.
218
218
 
@@ -380,38 +380,40 @@ Notes:
380
380
 
381
381
  ## CLI flags
382
382
 
383
- | Flag | Default | Purpose |
384
- | --------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
385
- | `--port <n>` | `4170` | TCP port. `0` = OS-assigned ephemeral port. |
386
- | `--hostname <addr>` | `127.0.0.1` | Bind interface. Anything beyond loopback requires a token. |
387
- | `--token <str>` | | Bearer token. Falls back to `QWEN_SERVER_TOKEN` env var (with leading/trailing whitespace stripped handy for `$(cat token.txt)`). |
388
- | `--require-auth` | `false` | Refuse to start without a bearer token, even on loopback. Hardens the `127.0.0.1` developer default for shared dev hosts / CI runners / multi-tenant workstations where any local user can hit the listener. Boots only with `--token` or `QWEN_SERVER_TOKEN` set; gates `/health` behind the bearer too. |
389
- | `--tls-cert <path>` | | Path to a PEM certificate file. Serve over **HTTPS** instead of HTTP. Must be paired with `--tls-key` (boot fails if only one is given). Unlocks secure-context browser APIs voice input (`getUserMedia`), WebRTC over a LAN IP, which browsers otherwise block on plain `http://`. TLS termination only; no auto-generation / ACME. See [HTTPS / TLS](#https--tls-for-mobile--cross-device-access) below. |
390
- | `--tls-key <path>` | — | Path to a PEM private key file. Must be paired with `--tls-cert`. |
391
- | `--max-sessions <n>` | `32` | Cap on concurrent live sessions. New `POST /session` requests that would spawn a fresh child return `503` (with `Retry-After: 5`) when the cap is hit; attaches to existing sessions are NOT counted. Set to `0` to disable. Sized for single-user / small-team usage; raise it if your deployment has the RAM/FD headroom (~30–50 MB per session). |
392
- | `--max-total-sessions <n>` | derived | Optional non-negative integer daemon-wide cap on fresh session creation across all registered workspace runtimes. It applies to new child sessions, session restore, and branch/fork-created sessions; attaching to an existing live session does not consume a slot. Set to `0` for unlimited. When omitted with several startup/restored workspaces, the daemon derives a fixed cap from the per-workspace limit and the startup workspace count; later dynamic registration does not recompute it. |
393
- | `--max-pending-prompts-per-session <n>` | `5` | Per-session cap on prompts accepted by `POST /session/:id/prompt` but not yet settled, including queued prompts and the active prompt. The bridge rejects overflow synchronously with `503`, `Retry-After: 5`, and `code: "prompt_queue_full"` before returning a `promptId`. Set to `0` to disable. `branchSession` serializes on the same FIFO but does not count against this prompt cap. |
394
- | `--workspace <path>` | `process.cwd()` | Absolute workspace directory registered by this daemon. Repeat the flag to host multiple workspaces in one process; the first is primary and remains the default when a request omits `cwd`. Relative values are rejected. Session requests whose canonical `cwd` is not registered return `400 workspace_mismatch`. |
395
- | `--memory-project-scope <mode>` | `git-root` | Project-memory partitioning mode. `git-root` (default) shares memory among workspaces resolved to the same Git root; `workspace` keys memory by the exact registered workspace directory so each daemon workspace gets its own isolated memory. Overrides `QWEN_CODE_MEMORY_PROJECT_SCOPE` when provided; an unrecognized env value is ignored with a one-time warning and falls back to `git-root`. Switching to `workspace` does not migrate existing git-root project memory those entries stop being visible until you switch back. |
396
- | `--channel <name\|all>` | | Experimental daemon-managed channel worker. Repeat the flag to select multiple configured channels, or pass `all` to start every configured channel. `all` cannot be combined with named channels. Selected channel `cwd` values must resolve to a registered workspace; a multi-workspace daemon runs one worker per owning workspace. The worker is owned by `qwen serve`; stop the daemon to stop serve-managed channels. |
397
- | `--max-connections <n>` | `256` | Listener-level TCP connection cap (`server.maxConnections`). Bounds raw socket count irrespective of session count slow / phantom SSE clients get rejected at accept time once full. Raise alongside `--max-sessions` if your deployment expects many SSE subscribers per session. |
398
- | `--memory-budget-mb <n>` | 50% of cgroup/host | Total memory budget in MB for the whole daemon process tree. When unset, derived as 50% of the cgroup limit or host memory; either way the effective value is capped at resolved available memory, and both the configured and effective figures are reported. Currently observation only — it does not change how any `qwen --acp` child is sized. Resolved figures appear under `limits.memory` in `GET /daemon/status`, alongside registered and live child counts and advisory per-child shares under `runtime.memory`. A host too small for the minimum reports `insufficientMemory` rather than being clamped upward; because the derived fraction is 50%, any host under ~2 GB trips this. Pass an explicit `--memory-budget-mb 1024` on such a host to override the derived figure (the flag still requires at least 1024 MB of available memory to clear the warning). Must be an integer in `[1024, 1048576]`. |
399
- | `--memory-pressure-mode <mode>` | `observe` | Whether the daemon turns its own memory reading into a verdict. `observe` (default) reports the pressure level under `runtime.memory.pressure` in `GET /daemon/status` and raises a `daemon_memory_pressure` issue a `warning`, so the overall `status` leaves `ok` whenever the level leaves `normal`. `off` still reports every figure, including the level, but raises no issue, so the overall `status` is unchanged; use it while calibrating, or if you alert on the top-level status. The level is the worse of two ratios: RSS against available memory (what the cgroup OOM killer watches) and V8 heap used against this process's heap ceiling. It covers the daemon root process only; compare it against `runtime.memory.children.rssBytes` for the children. Nothing remediates in either mode. One of `off`, `observe`. |
400
- | `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing, and says so on the wire: `maxConcurrentChildren` and `perChildCeilingMb` are both `null` rather than carrying a partition you switched off. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |
401
- | `--event-ring-size <n>` | `8000` | Per-session SSE replay ring depth (#3803 §02 target). Sets the backlog available to `GET /session/:id/events` with `Last-Event-ID: N`. Larger = more reconnect headroom at the cost of a few hundred KB extra RAM per session. SDK clients can additionally request a larger per-subscriber backlog cap on a specific subscription via `?maxQueued=N` (range `[16, 2048]`, default 256). Daemons also emit a non-terminal `slow_client_warning` SSE frame at 75% queue fill so clients can drain / reconnect before getting evicted. Pre-flight `caps.features.slow_client_warning`. |
402
- | `--compacted-replay-max-bytes <n>` | `4194304` | Per-live-session byte cap for the retained replay events in the bounded snapshot returned by `POST /session/:id/load`. The cap applies to `compactedReplay`; the current in-flight `liveJournal` is separately capped by `--max-journal-events` and `--max-journal-bytes`. Values must be positive safe integers; invalid values fail at boot, and the hard ceiling is 256 MiB. When older retained replay is dropped, the snapshot begins with `history_truncated`. This does not limit the on-disk transcript. |
403
- | `--max-journal-events <n>` | `10000` | Per-session cap on the number of raw events retained in the in-flight live journal (the current unfinished turn). When exceeded, the oldest journal entries are dropped and a `history_truncated` marker is prepended. Must be a positive safe integer. |
404
- | `--max-journal-bytes <n>` | `8388608` | Per-session byte cap on the in-flight live journal. When exceeded, the oldest journal entries are dropped (at least one entry is always kept). Must be a positive safe integer. Defaults to 8 MiB. |
405
- | `--mcp-client-budget <n>` | | Positive integer cap on live MCP clients. When `mcp_workspace_pool` is advertised, the cap and transports are shared per workspace runtime; when the tag is absent, the legacy per-session manager enforces it. Combine with `--mcp-budget-mode`. When unset, no accounting-driven enforcement (but `GET /workspace/mcp` still reports `clientCount`). Distinct from claude-code's `MCP_SERVER_CONNECTION_BATCH_SIZE`, which gates startup concurrency rather than total live clients. Pre-flight `caps.features.mcp_guardrails` and `caps.features.mcp_workspace_pool`. |
406
- | `--mcp-budget-mode <m>` | `warn` / `off` | How `--mcp-client-budget` is enforced. `warn` (default when budget set): no refusal, snapshot's `budgets[0].status` flips to `warning` at ≥75% of budget. `enforce`: connects past the cap are refused, per-server cell shows `disabledReason: 'budget'`, deterministic by `mcpServers` declaration order. `off` (default when budget unset): pure observability. Boot rejects `enforce` without a budget. |
407
- | `--external-tool-guard-mode <m>` | `off` | Managed ACP external pre-execution policy. `off` makes no provider calls and advertises no capability. `required` fails startup unless a compatible provider completes the v1 handshake, then fails every supported top-level tool invocation closed unless its single prepare request is allowed. |
408
- | `--external-tool-guard-endpoint <url>` | | Origin-only loopback HTTP(S) provider URL used in `required` mode, for example `http://127.0.0.1:8787`. Paths, URL credentials, redirects, non-loopback hosts, and proxy routing are not accepted. |
409
- | `--external-tool-guard-timeout-ms <n>` | `3000` | Integer `100..30000`; applies independently to the startup handshake and each prepare request. |
410
- | `--http-bridge` | `true` | Stage 1 mode: production attempts to preheat one primary `qwen --acp` child for compatibility and retries on first use after failure, while each trusted secondary can start one child on demand. Sessions targeting a runtime multiplex onto its child via ACP `newSession()`; untrusted secondaries cannot start ACP. Stage 2 native in-process becomes available later. |
411
- | `--initialize-timeout-ms <n>` | `10000` | ACP child request timeout, including the `initialize` handshake (ms). Must be a positive integer up to `2147483647`. Values above the JS timer ceiling (`2^31-1`) are rejected at boot because Node silently compresses them to 1 ms. Cold-container deployments that need extra headroom for child startup can raise this; the same value governs `newSession`, workspace-status polls, and other ACP ext-method deadlines. |
412
- | `--allow-origin <pat>` | | T2.4 ([#4514](https://github.com/QwenLM/qwen-code/issues/4514)). Cross-origin allowlist for browser webui clients. Repeatable. Each value is `*` (any origin — boot refuses if no bearer token is configured; `--require-auth` on loopback is recommended so `/health` and `/demo` are also bearer-gated, since both are pre-auth on loopback by default) or a canonical URL origin (`<scheme>://<host>[:<port>]`, no trailing slash / path / userinfo / query). **Subdomain wildcards (`https://*.example.com`) are intentionally unsupported** list each subdomain explicitly, or use `*` with a configured token (and `--require-auth` for full hardening). Matched origins receive CORS response headers (`Access-Control-Allow-Origin`, `Vary: Origin`, methods, headers, max-age, and exposed `Retry-After`); unmatched origins still get a 403 with the same envelope as today's wall. `Origin: null` (sandboxed iframes, file:// docs) is always rejected, even under `*`. Pre-flight via `caps.features.allow_origin`. Loopback self-origin hits are unaffected. |
413
- | `--web` / `--no-web` | `true` | Serve the built Web Shell SPA at the daemon root (`GET /`, `/assets/*`, and `GET /session/<id>` document navigations). These entry points are registered **before** the bearer-auth gate a browser can't attach a token to a `<script>` subresource or an address-bar navigation, and the shell carries no secrets. Every API route stays token-gated regardless, and the SPA deep-link fallback for all other paths sits behind the bearer gate too. On non-loopback binds a one-line stderr warning notes the UI is reachable without auth. Use `--no-web` for an API-only daemon. No effect when the build omits the Web Shell assets (the daemon logs a breadcrumb and runs API-only). |
414
- | `--open` | `false` | After the listener is up, open the Web Shell in your default browser at the daemon URL (with `#token=` appended as a URL fragment when a token is configured a fragment is never sent to the server, keeping the token out of access logs and Referer headers). No-op with `--no-web`, or in headless / CI / SSH environments where no browser is available. |
383
+ | Flag | Default | Purpose |
384
+ | --------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
385
+ | `--port <n>` | `4170` | TCP port. `0` = OS-assigned ephemeral port. |
386
+ | `--hostname <addr>` | `127.0.0.1` | Bind interface. Anything beyond loopback requires a token. |
387
+ | `--local-control` | `false` | Share the authenticated Web Shell on every non-loopback IPv4 interface with a fresh per-process token, labelled terminal QR codes, exact browser origins, a fixed port, and best-effort sleep inhibition. Conflicts with `--token`, `--allow-origin`, `--no-web`, `--port 0`, and non-default `--hostname`; add `--tls-cert` + `--tls-key` for secure-context browser APIs such as voice input. |
388
+ | `--token <str>` | | Bearer token. Falls back to `QWEN_SERVER_TOKEN` env var (with leading/trailing whitespace stripped handy for `$(cat token.txt)`). |
389
+ | `--require-auth` | `false` | Refuse to start without a bearer token, even on loopback. Hardens the `127.0.0.1` developer default for shared dev hosts / CI runners / multi-tenant workstations where any local user can hit the listener. Boots only with `--token` or `QWEN_SERVER_TOKEN` set; gates `/health` behind the bearer too. |
390
+ | `--tls-cert <path>` | — | Path to a PEM certificate file. Serve over **HTTPS** instead of HTTP. Must be paired with `--tls-key` (boot fails if only one is given). Unlocks secure-context browser APIs — voice input (`getUserMedia`), WebRTC — over a LAN IP, which browsers otherwise block on plain `http://`. TLS termination only; no auto-generation / ACME. See [HTTPS / TLS](#https--tls-for-mobile--cross-device-access) below. |
391
+ | `--tls-key <path>` | | Path to a PEM private key file. Must be paired with `--tls-cert`. |
392
+ | `--max-sessions <n>` | `32` | Cap on concurrent live sessions. New `POST /session` requests that would spawn a fresh child return `503` (with `Retry-After: 5`) when the cap is hit; attaches to existing sessions are NOT counted. Set to `0` to disable. Sized for single-user / small-team usage; raise it if your deployment has the RAM/FD headroom (~30–50 MB per session). |
393
+ | `--max-total-sessions <n>` | derived | Optional non-negative integer daemon-wide cap on fresh session creation across all registered workspace runtimes. It applies to new child sessions, session restore, and branch/fork-created sessions; attaching to an existing live session does not consume a slot. Set to `0` for unlimited. When omitted with several startup/restored workspaces, the daemon derives a fixed cap from the per-workspace limit and the startup workspace count; later dynamic registration does not recompute it. |
394
+ | `--max-pending-prompts-per-session <n>` | `5` | Per-session cap on prompts accepted by `POST /session/:id/prompt` but not yet settled, including queued prompts and the active prompt. The bridge rejects overflow synchronously with `503`, `Retry-After: 5`, and `code: "prompt_queue_full"` before returning a `promptId`. Set to `0` to disable. `branchSession` serializes on the same FIFO but does not count against this prompt cap. |
395
+ | `--workspace <path>` | `process.cwd()` | Absolute workspace directory registered by this daemon. Repeat the flag to host multiple workspaces in one process; the first is primary and remains the default when a request omits `cwd`. Relative values are rejected. Session requests whose canonical `cwd` is not registered return `400 workspace_mismatch`. |
396
+ | `--memory-project-scope <mode>` | `git-root` | Project-memory partitioning mode. `git-root` (default) shares memory among workspaces resolved to the same Git root; `workspace` keys memory by the exact registered workspace directory so each daemon workspace gets its own isolated memory. Overrides `QWEN_CODE_MEMORY_PROJECT_SCOPE` when provided; an unrecognized env value is ignored with a one-time warning and falls back to `git-root`. Switching to `workspace` does not migrate existing git-root project memory — those entries stop being visible until you switch back. |
397
+ | `--channel <name\|all>` | | Experimental daemon-managed channel worker. Repeat the flag to select multiple configured channels, or pass `all` to start every configured channel. `all` cannot be combined with named channels. Selected channel `cwd` values must resolve to a registered workspace; a multi-workspace daemon runs one worker per owning workspace. The worker is owned by `qwen serve`; stop the daemon to stop serve-managed channels. |
398
+ | `--max-connections <n>` | `256` | Listener-level TCP connection cap (`server.maxConnections`). Bounds raw socket count irrespective of session count slow / phantom SSE clients get rejected at accept time once full. Raise alongside `--max-sessions` if your deployment expects many SSE subscribers per session. |
399
+ | `--memory-budget-mb <n>` | 50% of cgroup/host | Total memory budget in MB for the whole daemon process tree. When unset, derived as 50% of the cgroup limit or host memory; either way the effective value is capped at resolved available memory, and both the configured and effective figures are reported. Currently observation only it does not change how any `qwen --acp` child is sized. Resolved figures appear under `limits.memory` in `GET /daemon/status`, alongside registered and live child counts and advisory per-child shares under `runtime.memory`. A host too small for the minimum reports `insufficientMemory` rather than being clamped upward; because the derived fraction is 50%, any host under ~2 GB trips this. Pass an explicit `--memory-budget-mb 1024` on such a host to override the derived figure (the flag still requires at least 1024 MB of available memory to clear the warning). Must be an integer in `[1024, 1048576]`. |
400
+ | `--memory-pressure-mode <mode>` | `observe` | Whether the daemon turns its own memory reading into a verdict. `observe` (default) reports the pressure level under `runtime.memory.pressure` in `GET /daemon/status` and raises a `daemon_memory_pressure` issue a `warning`, so the overall `status` leaves `ok` whenever the level leaves `normal`. `off` still reports every figure, including the level, but raises no issue, so the overall `status` is unchanged; use it while calibrating, or if you alert on the top-level status. The level is the worse of two ratios: RSS against available memory (what the cgroup OOM killer watches) and V8 heap used against this process's heap ceiling. It covers the daemon root process only; compare it against `runtime.memory.children.rssBytes` for the children. Nothing remediates in either mode. One of `off`, `observe`. |
401
+ | `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing, and says so on the wire: `maxConcurrentChildren` and `perChildCeilingMb` are both `null` rather than carrying a partition you switched off. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |
402
+ | `--event-ring-size <n>` | `8000` | Per-session SSE replay ring depth (#3803 §02 target). Sets the backlog available to `GET /session/:id/events` with `Last-Event-ID: N`. Larger = more reconnect headroom at the cost of a few hundred KB extra RAM per session. SDK clients can additionally request a larger per-subscriber backlog cap on a specific subscription via `?maxQueued=N` (range `[16, 2048]`, default 256). Daemons also emit a non-terminal `slow_client_warning` SSE frame at 75% queue fill so clients can drain / reconnect before getting evicted. Pre-flight `caps.features.slow_client_warning`. |
403
+ | `--compacted-replay-max-bytes <n>` | `4194304` | Per-live-session byte cap for the retained replay events in the bounded snapshot returned by `POST /session/:id/load`. The cap applies to `compactedReplay`; the current in-flight `liveJournal` is separately capped by `--max-journal-events` and `--max-journal-bytes`. Values must be positive safe integers; invalid values fail at boot, and the hard ceiling is 256 MiB. When older retained replay is dropped, the snapshot begins with `history_truncated`. This does not limit the on-disk transcript. |
404
+ | `--max-journal-events <n>` | `10000` | Per-session cap on replay entries retained in the in-flight `liveJournal` for the current unfinished turn. Consecutive compatible text or thought chunks share an entry, with at most 256 source events per entry; other event boundaries are preserved. When exceeded, the oldest entries are dropped and a `history_truncated` marker is prepended. The marker's `truncatedEvents` and `retainedEvents` counts describe source events. Must be a positive safe integer. |
405
+ | `--max-journal-bytes <n>` | `8388608` | Per-session byte cap on the in-flight `liveJournal`, accounted from the serialized source events even when compatible chunks share a replay entry. When exceeded, the oldest entries are dropped whole (at least one entry is always kept), so the retained tail can be much smaller than the cap. Must be a positive safe integer. Defaults to 8 MiB. |
406
+ | `--mcp-client-budget <n>` | — | Positive integer cap on live MCP clients. When `mcp_workspace_pool` is advertised, the cap and transports are shared per workspace runtime; when the tag is absent, the legacy per-session manager enforces it. Combine with `--mcp-budget-mode`. When unset, no accounting-driven enforcement (but `GET /workspace/mcp` still reports `clientCount`). Distinct from claude-code's `MCP_SERVER_CONNECTION_BATCH_SIZE`, which gates startup concurrency rather than total live clients. Pre-flight `caps.features.mcp_guardrails` and `caps.features.mcp_workspace_pool`. |
407
+ | `--mcp-budget-mode <m>` | `warn` / `off` | How `--mcp-client-budget` is enforced. `warn` (default when budget set): no refusal, snapshot's `budgets[0].status` flips to `warning` at ≥75% of budget. `enforce`: connects past the cap are refused, per-server cell shows `disabledReason: 'budget'`, deterministic by `mcpServers` declaration order. `off` (default when budget unset): pure observability. Boot rejects `enforce` without a budget. |
408
+ | `--external-tool-guard-mode <m>` | `off` | Managed ACP external pre-execution policy. `off` makes no provider calls and advertises no capability. `required` fails startup unless a compatible provider completes the v1 handshake, then fails every supported top-level tool invocation closed unless its single prepare request is allowed. |
409
+ | `--external-tool-guard-endpoint <url>` | — | Origin-only loopback HTTP(S) provider URL used in `required` mode, for example `http://127.0.0.1:8787`. Paths, URL credentials, redirects, non-loopback hosts, and proxy routing are not accepted. |
410
+ | `--external-tool-guard-timeout-ms <n>` | `3000` | Integer `100..30000`; applies independently to the startup handshake and each prepare request. |
411
+ | `--http-bridge` | `true` | Stage 1 mode: production attempts to preheat one primary `qwen --acp` child for compatibility and retries on first use after failure, while each trusted secondary can start one child on demand. Sessions targeting a runtime multiplex onto its child via ACP `newSession()`; untrusted secondaries cannot start ACP. Stage 2 native in-process becomes available later. |
412
+ | `--initialize-timeout-ms <n>` | `10000` | ACP child request timeout, including the `initialize` handshake (ms). Must be a positive integer up to `2147483647`. Values above the JS timer ceiling (`2^31-1`) are rejected at boot because Node silently compresses them to 1 ms. Cold-container deployments that need extra headroom for child startup can raise this; the same value governs `newSession`, workspace-status polls, and other ACP ext-method deadlines. |
413
+ | `--session-restore-timeout-ms <n>` | `60000` | ACP session load/resume deadline in milliseconds. Must be a positive integer up to `2147483647`; `0` is invalid. If omitted, the default is 60 seconds, raised to an explicitly supplied `--initialize-timeout-ms` when that value is larger; a shorter initialize timeout never lowers the restore budget. The SDK and WebUI add 10 and 15 seconds of client headroom. A timeout returns retryable `504 session_restore_timeout`; it does not imply that the daemon itself exited. |
414
+ | `--allow-origin <pat>` | | T2.4 ([#4514](https://github.com/QwenLM/qwen-code/issues/4514)). Cross-origin allowlist for browser webui clients. Repeatable. Each value is `*` (any origin — boot refuses if no bearer token is configured; `--require-auth` on loopback is recommended so `/health` is also bearer-gated, since it is pre-auth on loopback by default; the Web Shell static assets stay pre-auth in every mode, so pass `--no-web` to remove them) or a canonical URL origin (`<scheme>://<host>[:<port>]`, no trailing slash / path / userinfo / query). **Subdomain wildcards (`https://*.example.com`) are intentionally unsupported** list each subdomain explicitly, or use `*` with a configured token (and `--require-auth` for full hardening). Matched origins receive CORS response headers (`Access-Control-Allow-Origin`, `Vary: Origin`, methods, headers, max-age, and exposed `Retry-After`); unmatched origins still get a 403 with the same envelope as today's wall. `Origin: null` (sandboxed iframes, file:// docs) is always rejected, even under `*`. Pre-flight via `caps.features.allow_origin`. Loopback self-origin hits are unaffected. |
415
+ | `--web` / `--no-web` | `true` | Serve the built Web Shell SPA at the daemon root (`GET /`, `/assets/*`, and `GET /session/<id>` document navigations). These entry points are registered **before** the bearer-auth gate — a browser can't attach a token to a `<script>` subresource or an address-bar navigation, and the shell carries no secrets. Every API route stays token-gated regardless, and the SPA deep-link fallback for all other paths sits behind the bearer gate too. On non-loopback binds a one-line stderr warning notes the UI is reachable without auth. Use `--no-web` for an API-only daemon. No effect when the build omits the Web Shell assets (the daemon logs a breadcrumb and runs API-only). |
416
+ | `--open` | `false` | After the listener is up, open the Web Shell in your default browser at the daemon URL (with `#token=` appended as a URL fragment when a token is configured — a fragment is never sent to the server, keeping the token out of access logs and Referer headers). No-op with `--no-web`, or in headless / CI / SSH environments where no browser is available. |
415
417
 
416
418
  > **Memory project scope caveats.**
417
419
  >
@@ -554,10 +556,11 @@ provider decision with their normal tool policy and isolation boundary.
554
556
  - **`--hostname 0.0.0.0` requires a token** — boot refuses without one.
555
557
  - **`LOOPBACK_BINDS` includes IPv6** — `::1` and `[::1]` count as loopback for the no-token rule.
556
558
  - **Host header allowlist** — on **loopback** binds the daemon checks `Host:` matches `localhost:port` / `127.0.0.1:port` / `[::1]:port` / `host.docker.internal:port` (case-insensitive per RFC 7230 §5.4) to defend against DNS rebinding. **Non-loopback binds (`--hostname 0.0.0.0`) intentionally bypass the Host allowlist** — the operator has chosen the surface area, so the bearer-token gate is the sole authentication layer; reverse proxies / SNI / client cert pinning are the operator's responsibility, not the daemon's. If you need Host-based isolation on a non-loopback bind, terminate TLS + check Host at a front proxy.
557
- - **CORS denies any browser Origin by default** — returns `403` JSON. Pass **`--allow-origin <pattern>`** (repeatable, T2.4 #4514) to opt specific browser origins through. Each value is either the literal `*` (any origin — boot refuses if no bearer token is configured; `--require-auth` on loopback is recommended for full hardening since `/health` and `/demo` remain pre-auth on loopback by default) or a canonical URL origin (`<scheme>://<host>[:<port>]`, no trailing slash / path / userinfo). Matched origins receive proper CORS response headers (`Access-Control-Allow-Origin: <echoed>`, `Vary: Origin`, plus standard methods / headers / max-age and exposed `Retry-After`); unmatched origins still get a 403 with the same envelope as the default wall. `caps.features.allow_origin` is advertised conditionally so SDK / webui clients can pre-flight whether the daemon honors cross-origin hits before issuing them. Example: `qwen serve --allow-origin http://localhost:3000 --allow-origin http://localhost:5173`. Loopback self-origin hits (e.g. the `/demo` page) are unaffected — a separate Origin-strip shim handles them regardless of `--allow-origin`. **Browser webuis without `--allow-origin` configured** still fall back to the same Stage 1 options as before: package as a native shell (Electron/Tauri) so no `Origin` header is sent, or front the daemon with a same-origin reverse proxy.
559
+ - **CORS denies any browser Origin by default** — returns `403` JSON. Pass **`--allow-origin <pattern>`** (repeatable, T2.4 #4514) to opt specific browser origins through. Each value is either the literal `*` (any origin — boot refuses if no bearer token is configured; `--require-auth` on loopback is recommended for full hardening since `/health` remains pre-auth on loopback by default — note that the Web Shell static assets (`/`, `/assets/*`, `/session/:id` document navigations) are mounted before the bearer in every mode and stay pre-auth even under `--require-auth`, so use `--no-web` when the residual browser surface matters) or a canonical URL origin (`<scheme>://<host>[:<port>]`, no trailing slash / path / userinfo). Matched origins receive proper CORS response headers (`Access-Control-Allow-Origin: <echoed>`, `Vary: Origin`, plus standard methods / headers / max-age and exposed `Retry-After`); unmatched origins still get a 403 with the same envelope as the default wall. `caps.features.allow_origin` is advertised conditionally so SDK / webui clients can pre-flight whether the daemon honors cross-origin hits before issuing them. Example: `qwen serve --allow-origin http://localhost:3000 --allow-origin http://localhost:5173`. Loopback self-origin hits (e.g. the Web Shell UI) are unaffected — a separate Origin-strip shim handles them regardless of `--allow-origin`. **Browser webuis without `--allow-origin` configured** still fall back to the same Stage 1 options as before: package as a native shell (Electron/Tauri) so no `Origin` header is sent, or front the daemon with a same-origin reverse proxy.
558
560
  - **Chrome extension browser automation is separate from framing.** `qwen serve --allow-origin chrome-extension://<id>` lets the extension frame the Web Shell and connect to the daemon. Console/network/screenshot/click tools require an external CDP MCP adapter command: `QWEN_CDP_MCP_COMMAND=/path/to/cdp-mcp-adapter qwen serve --allow-origin chrome-extension://<id>`. The main CLI package does not bundle a browser automation adapter; clients can check `caps.features.includes('browser_automation_mcp')` before presenting those tools as available.
559
- - **A spawned `qwen --acp` child receives its owning runtime's effective environment.** The daemon freezes a process-env base, applies that workspace's settings/env-file overlay to a runtime-local snapshot, and never writes the overlay back to `process.env`; same-named keys in another runtime do not cross over. `QWEN_SERVER_TOKEN` is scrubbed before spawn because the agent does not need the daemon bearer. Loader-affecting variables (`NODE_OPTIONS`, `npm_config_node_options` and npm's config-file redirects, `NODE_PATH`, `LD_PRELOAD`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`, `BASH_ENV`, `ZDOTDIR`, exported bash function definitions `BASH_FUNC_*`) are likewise never passed to session subprocesses — the daemon scrubs them from its own `process.env` and from the frozen base env that session-hosting children spawn with (the base env keeps them only under the `DEV=true` harness, whose `.ts` entries still need the tsx loader), and `.env` / `settings.json` `env` sources reject them (see [settings](./configuration/settings.md)); this applies to every session the daemon hosts. Base credentials such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `QWEN_*`, and `DASHSCOPE_API_KEY` otherwise pass through unless the runtime overlay changes them. **This is intentional, not a sandbox.** The agent runs as the same UID with shell-tool access, so anything in `~/.bashrc`, `~/.aws/credentials`, or `~/.npmrc` is reachable by prompt injection regardless. Environment isolation between runtimes is not an operating-system security boundary; do not run `qwen serve` under an identity that has credentials you would not trust the agent with.
560
- - **Agent text reads are child-local and follow the regular CLI permission rules, not the workspace filesystem boundary.** Direct `read_file` can reach host text paths outside every registered workspace: external paths default to confirmation, and allow rules or approval modes may approve them automatically. Approved reads use the configurable CLI output limits rather than the workspace filesystem's returned-output, full-snapshot, and large-text scan caps. This applies to every shared text-read consumer, so the pre-reads performed by write, edit, notebook, sed, and artifact operations lose those caps together with the workspace filesystem's read audit, symlink rejection, and read-side TOCTOU protections — see [the design doc](../design/daemon-local-text-reads.md) for the exact list. Because a confirmation payload is built by reading the file, an out-of-workspace diff is fanned out to **every** attached SSE subscriber before anyone approves it — in the interactive CLI that content is seen only by the person at the terminal. Treat authenticated daemon clients as the same security principal. HTTP filesystem routes remain workspace-scoped and still refuse these paths, agent discovery-tool behavior is unchanged, and final ACP `writeTextFile` content writes continue through the workspace filesystem.
561
+ - **A spawned `qwen --acp` child receives its owning runtime's effective environment.** The daemon freezes a process-env base, applies that workspace's settings/env-file overlay to a runtime-local snapshot, and never writes the overlay back to `process.env`; same-named keys in another runtime do not cross over. `QWEN_SERVER_TOKEN` is scrubbed before spawn because the agent does not need the daemon bearer. Loader-affecting variables (`NODE_OPTIONS`, `npm_config_node_options` and npm's config-file redirects, `NODE_PATH`, `OPENSSL_CONF`, `NODE_REPL_EXTERNAL_MODULE`, `npm_config_node_gyp`, `npm_config_init_module`, `LD_PRELOAD`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`, `BASH_ENV`, `ZDOTDIR`, exported bash function definitions `BASH_FUNC_*`) are likewise never passed to session subprocesses — the daemon scrubs them from its own `process.env` and from the frozen base env that session-hosting children spawn with (the base env keeps them only under the `DEV=true` harness, whose `.ts` entries still need the tsx loader), and `.env` / `settings.json` `env` sources reject them (see [settings](./configuration/settings.md)); this applies to every session the daemon hosts. Base credentials such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `QWEN_*`, and `DASHSCOPE_API_KEY` otherwise pass through unless the runtime overlay changes them. **This is intentional, not a sandbox.** The agent runs as the same UID with shell-tool access, so anything in `~/.bashrc`, `~/.aws/credentials`, or `~/.npmrc` is reachable by prompt injection regardless. Environment isolation between runtimes is not an operating-system security boundary; do not run `qwen serve` under an identity that has credentials you would not trust the agent with.
562
+ - **Agent text reads are child-local and follow the regular CLI permission rules, not the workspace filesystem boundary.** Direct `read_file` can reach host text paths outside every registered workspace: external paths default to confirmation, and allow rules or approval modes may approve them automatically. Approved reads use the configurable CLI output limits rather than the workspace filesystem's returned-output, full-snapshot, and large-text scan caps. This applies to every shared text-read consumer, so the pre-reads performed by write, edit, notebook, sed, and artifact operations lose those caps together with the workspace filesystem's read audit, symlink rejection, and read-side TOCTOU protections — see [the read design](../design/daemon-local-text-reads.md) for the exact list. Because a confirmation payload is built by reading the file, an out-of-workspace diff is fanned out to **every** attached SSE subscriber before anyone approves it — in the interactive CLI that content is seen only by the person at the terminal. Treat authenticated daemon clients as the same security principal. HTTP filesystem routes remain workspace-scoped and agent discovery-tool behavior is unchanged.
563
+ - **Approved final writes from built-in text tools have a narrow same-host route.** `write_file`, `edit`, `notebook_edit`, and the shell tool's simulated sed editor attach internal provenance only after the existing permission policy allows execution. Their final ACP text write can therefore target an absolute path outside the owning workspace without a second confirmation; allow rules, AUTO/AUTO_EDIT and YOLO behave like the CLI, while rejection, Plan, Hook/Guard refusal and pre-execution cancellation do not send the final write. Cancellation after a tool has already entered a non-cancellable filesystem operation keeps that tool's existing behavior. Workspace targets still use WFS. External targets use a daemon host writer with the same trust snapshot, 5 MiB encoded limit, leaf-symlink rejection, canonical path lock, atomic rename, mode preservation, `0600` new-file mode, generation guard and filesystem audit. HTTP writes, generic or unmarked ACP writes, injected bridge/workspace-registry/factory integrations and arbitrary shell redirection do not receive this exception. See [the external-write design](../design/daemon-external-tool-text-writes.md).
561
564
  - **Per-subscriber bounded SSE queues** — a slow client that overflows its queue gets a `client_evicted` terminal frame and is closed; one stuck consumer can't pin the daemon.
562
565
  - **Per-session prompt admission cap** — defaults to 5 accepted-but-unsettled prompts per session. A buggy client cannot enqueue unbounded prompt promises or temporary SSE waits for one session.
563
566
  - **Graceful shutdown** — SIGINT/SIGTERM drain the agent children before closing the listener (10s deadline per child).
@@ -644,7 +647,7 @@ for await (const event of session.events()) {
644
647
  }
645
648
  ```
646
649
 
647
- Pre-flight `caps.features.session_load`, `caps.features.session_resume`, or `caps.features.session_transcript` before calling the matching route — older daemons return `404`. `unstable_session_resume` is still advertised as a deprecated compatibility alias. Concurrent same-action requests for the same id coalesce; cross-action races (a `load` racing a `resume`) get `409 restore_in_progress` with `Retry-After: 5`. See the [protocol reference](../developers/qwen-serve-protocol.md) for the full error envelope.
650
+ Pre-flight `caps.features.session_load`, `caps.features.session_resume`, or `caps.features.session_transcript` before calling the matching route — older daemons return `404`. `unstable_session_resume` is still advertised as a deprecated compatibility alias. Concurrent same-action requests for the same id coalesce; cross-action races (a `load` racing a `resume`) and caller-supplied-id spawns racing a restore get `409 restore_in_progress` with `Retry-After: 5`. A restore that exceeds `limits.sessionRestoreTimeoutMs` gets retryable `504 session_restore_timeout` with a budget-derived `Retry-After` (clamped to 5-120s); the still-running child request remains fenced until cleanup settles, and same-id retries during that window get `409 restore_in_progress` with `reason: awaiting_abandoned_cleanup` and a budget-derived `Retry-After` clamped to 5-120 seconds instead of a fixed 5-second delay. If cleanup is uncertain, or the abandoned restore has still not settled a full restore budget after its deadline, fresh session work temporarily gets `503 acp_channel_unavailable` with `reason: restore_cleanup_failed` or `restore_settlement_overdue`, while already-live sessions remain usable. See the [protocol reference](../developers/qwen-serve-protocol.md) for the full error envelope.
648
651
 
649
652
  For full persisted replay, page with `DaemonClient.getSessionTranscriptPage(sessionId, { cursor, limit })` or the raw REST route:
650
653
 
@@ -211,7 +211,7 @@ Read from it:
211
211
  - `diffLines`, `diffChars`, and `srcDiffLines` / `testDiffLines` / `docsDiffLines` / `generatedDiffLines`
212
212
  - `chunks[]` — contiguous, non-overlapping line ranges tiling the whole diff. Each entry has `id`, `startLine`, `endLine` (1-based, inclusive), `lines`, `chars`, an `oversized` flag, and `files[]` naming the source files and new-side line ranges it covers. A chunk with `oversized: true` may exceed what one `read_file` call returns.
213
213
  - `files[]` — per-file `kind` (`source` / `test` / `generated`), `hunks[]` new-side ranges (Step 7 validates comment anchors against these), `addedRanges[]` and `diffRange` (present only on `heavy` files — the exact lines the PR wrote, and where that file's own diff lives, so an invariant agent can see what was deleted), change counts, and the `heavy` flag
214
- - `budget` — how much walking the **size-elastic** parts of this run owe, derived from `srcDiffLines` the same way the topology gate is, and recorded here rather than passed as a flag so every reader sees one number. `inlineAngles` and `sweep` scope Step 3C's low pass; `specialistCap` is the Agent 8 ceiling (**0** below 80 source lines — "one domain dominates the diff" is a judgement, and a judgement made about forty lines finds a dominant domain every time, because forty lines are usually all one thing); `verifyShard` is Step 4's findings-per-verifier; `agentToolBudget` is the base rate of the soft tool-call ceiling `agent-prompt` bakes into every finder and auditor brief — not the verifier's, not Agent 7's, and not Agent 0's, whose mandatory work scales with the linked issues rather than the diff. The ceiling is per **launch**: a scoped agent (a chunk, a heavy file) gets an allowance derived from its own territory — never above the plan's recorded allowance, which is clamped into the budget's own band in both directions, so the plan stays the one number every launch answers to — and every launch's assigned reads ride on top of the allowance rather than inside it, so a huge diff's mandatory chunk reads can never exhaust the exploration a whole-diff role owes — because a wave's wall clock is its slowest agent and the slowest agent is reliably one that kept exploring past any recall gain: the same 14-agent fan-out has measured 11.7 and 41 minutes on comparable diffs, the difference being individual agents spending 40-100 calls walking the tree (measured; DESIGN.md — The forty-one minute wave). The ceiling is soft and the briefs restate the recall rule beside it: at the budget an agent stops **exploring**, never reporting — findings in hand are filed, and each stopped check is disclosed on its own line in the fixed form `Budget gap: <the check>`, which `check-coverage` parses out of the transcripts (its report's `budgetGaps`) — see Step 3D for the ruling each gap is owed. **It never scales a dimension away** — which agents a review owes is the roster's answer and the roster reads `effort`, so a size input cannot become a back door into shrinking coverage. Nothing here is yours to override: a budget the caller can inflate is a budget that gets inflated. **A plan with no `budget` field** (written by an older CLI — the version-skew this skill has already measured once) falls back to the pre-budget flat behaviour, which errs toward more coverage, never less: walk all six angles, run the sweep, cap Agent 8 at 2, shard verification at 8.
214
+ - `budget` — how much walking the **size-elastic** parts of this run owe, sized from `srcDiffLines` except that an all-non-source diff (docs, lockfiles) counts its total lines at an eighth rate, so the size these tiers read is `effective = max(srcDiffLines, floor(diffLines / 8))`; recorded here rather than passed as a flag so every reader sees one number. `inlineAngles` and `sweep` scope Step 3C's low pass; `specialistCap` is the Agent 8 ceiling (**0** below 80 source lines — "one domain dominates the diff" is a judgement, and a judgement made about forty lines finds a dominant domain every time, because forty lines are usually all one thing — **and 0 again for a huge diff (effective ≥ 3000)**, where an Agent 8 whole-diff pass on top of the base fan-out is the marginal cost that tips a review too big to finish into posting nothing); `verifyShard` is Step 4's findings-per-verifier; `reverseAuditRounds` is the reverse-audit loop's round cap — **5** normally, **3 for a huge diff** (effective ≥ 3000 lines). A reverse-audit round re-reads the whole diff against a growing findings list, so it costs ~90 minutes on a 4,000-line PR, where five rounds (450 min) alone exceed the six-hour ceiling before the fan-out and tail are counted — the 6-hour timeouts that posted nothing were 4,000-5,300-line PRs (measured; DESIGN.md — The six-hour timeouts). Three is one audit round above the convergence floor of two — the all-dry rounds-1-and-2 shape converges under any cap of two or more, since the convergence check runs before the cap gate; the extra round buys hot chunks one more pass. The `agent-prompt` builder enforces the cap itself (a `ROUND CAP:` refusal, exit 4, that writes a marker `compose-review` caps on — same contract as the deadline gate below), so you never count rounds yourself. `agentToolBudget` is the base rate of the soft tool-call ceiling `agent-prompt` bakes into every finder and auditor brief — not the verifier's, not Agent 7's, and not Agent 0's, whose mandatory work scales with the linked issues rather than the diff. The ceiling is per **launch**: a scoped agent (a chunk, a heavy file) gets an allowance derived from its own territory — never above the plan's recorded allowance, which is clamped into the budget's own band in both directions, so the plan stays the one number every launch answers to — and every launch's assigned reads ride on top of the allowance rather than inside it, so a huge diff's mandatory chunk reads can never exhaust the exploration a whole-diff role owes — because a wave's wall clock is its slowest agent and the slowest agent is reliably one that kept exploring past any recall gain: the same 14-agent fan-out has measured 11.7 and 41 minutes on comparable diffs, the difference being individual agents spending 40-100 calls walking the tree (measured; DESIGN.md — The forty-one minute wave). The ceiling is soft and the briefs restate the recall rule beside it: at the budget an agent stops **exploring**, never reporting — findings in hand are filed, and each stopped check is disclosed on its own line in the fixed form `Budget gap: <the check>`, which `check-coverage` parses out of the transcripts (its report's `budgetGaps`) — see Step 3D for the ruling each gap is owed. **It never scales a dimension away** — which agents a review owes is the roster's answer and the roster reads `effort`, so a size input cannot become a back door into shrinking coverage. Nothing here is yours to override: a budget the caller can inflate is a budget that gets inflated. **A plan with no `budget` field** (written by an older CLI — the version-skew this skill has already measured once) falls back to the pre-budget flat behaviour, which errs toward more coverage, never less: walk all six angles, run the sweep, cap Agent 8 at 2, shard verification at 8.
215
215
 
216
216
  A chunk is read with `read_file(file_path=diffPathAbsolute, offset=startLine - 1, limit=endLine - startLine + 1)` — `offset` is 0-based.
217
217
 
@@ -623,7 +623,7 @@ After deduplication, run reverse audit **iteratively** — the first launch ride
623
623
 
624
624
  - **Small diffs (Step 3A path):** one reverse audit agent per round, reading the whole diff — except rounds 1 and 2, which are **the convergence pair** and launch together (below).
625
625
  - **Large diffs (Step 3B path):** one reverse audit agent **per chunk** per round, launched together in a single response. A single agent asked to re-read a 5 800-line diff with a growing finding list appended is the most context-starved agent in the pipeline — precisely on the PRs where the reverse audit matters most. Each per-chunk auditor gets the same territory as its Step 3B counterpart, plus the cumulative finding list for the **whole** diff (so it knows what is already covered elsewhere).
626
- - **The builder schedules the 3B fan-out; you do not.** Rounds 1 and 2 audit every chunk — they are what establishes each territory's record. From round 3 on, `--all-chunks` reads the harness transcripts and **retires** any chunk whose own last two audits were substantively dry (the receipt named what it examined AND the transcript shows the diff was opened): a retired chunk is cold-checked on alternating rounds instead of every round, and a cold check that yields anything returns it to every-round auditing. The savings land on the odd rounds — every retired chunk cold-checks together on the even ones, so an even round's fan-out is unchanged; expect rounds 3 and 5 to shrink, not round 4. The blocks it prints are the round; the `retirement:` note after the `end of round` line names each skipped chunk and its certificate — relay that note in your narration, and do not hand-build an auditor for a chunk the builder skipped. Why, measured: on a real 6-chunk run, two chunks were dry in **all five rounds** — a third of the loop's auditors re-certifying territories that had already converged, while the three hot chunks were where every finding came from. Attention follows evidence; the certificate a retired chunk holds (two consecutive substantive dry audits) is exactly the one the whole loop used to end on.
626
+ - **The builder schedules the 3B fan-out; you do not.** Rounds 1 and 2 audit every chunk — they are what establishes each territory's record. From round 3 on, `--all-chunks` reads the harness transcripts and **retires** any chunk whose own last two audits were substantively dry (the receipt named what it examined AND the transcript shows the diff was opened): a retired chunk is cold-checked on alternating rounds instead of every round, and a cold check that yields anything returns it to every-round auditing. The savings land on the odd rounds — every retired chunk cold-checks together on the even ones, so an even round's fan-out is unchanged; expect the odd rounds to shrink, not the even ones (under the 3-round huge-diff cap only round 3 can shrink — the cap ends the loop before round 5). The blocks it prints are the round; the `retirement:` note after the `end of round` line names each skipped chunk and its certificate — relay that note in your narration, and do not hand-build an auditor for a chunk the builder skipped. Why, measured: on a real 6-chunk run, two chunks were dry in **all five rounds** — a third of the loop's auditors re-certifying territories that had already converged, while the three hot chunks were where every finding came from. Attention follows evidence; the certificate a retired chunk holds (two consecutive substantive dry audits) is exactly the one the whole loop used to end on.
627
627
 
628
628
  **The convergence pair (3A only).** Rounds 1 and 2 launch **in one response** — together with Step 4's verifier shards (Step 4 names this) — each built by its own `agent-prompt` call: `--round 1` and `--round 2`, the **same** `--findings` file. This is not a loosened criterion; it is the serial shape's own arithmetic made concurrent: a dry round leaves the cumulative list unchanged, so round 2's launch input was already substantively identical to round 1's — the same entries, at most with verification tags the merge had cleared in between — an independent rerun that the serial shape bought with a full round of wall clock, and that one budget-gated run could no longer afford at all, shipping a capped verdict for want of a second dry audit it had time to run in parallel but not in series (measured; DESIGN.md — The serial convergence pair). What the two-consecutive-dry criterion demands is unchanged: two independent, substantively-dry audits of the whole diff. The one delta the pair does introduce is the same one-round suppression window the pipelined loop already accepts (the merge bullet in the termination rules): the round-2 member audits with entries a verifier may be rejecting mid-flight still on its do-not-re-report list.
629
629
 
@@ -667,12 +667,12 @@ The brief holds what the auditor is for: hunt only the **gaps** no prior agent c
667
667
  - A round is **dry** only when _every_ agent in it returned zero new findings **with** the evidence-bearing receipt (`No issues found — <what it re-examined>`). A round containing a twice-whiffed agent is **not dry** — silence is not convergence evidence — so the loop continues (the hard cap below still bounds it).
668
668
  - **When the loop ends with any scope still outstanding** (by cap, or by dry rounds elsewhere), terminal prose is not enough: add one self-explained entry per scope to `unreviewedDimensions` — e.g. `reverse audit of chunk 3 — the auditor returned nothing substantive twice` — so compose-review serializes it and caps a would-be Approve at `COMMENT`. The primary Step 3 pass did read that scope (its receipt stands), but this run's contract includes the reverse audit, and a verdict must not silently claim an audit that never ran.
669
669
  - Stop after **two consecutive dry rounds** (the 3A criterion — one auditor, so round-dry and territory-dry are the same thing). One dry round is not evidence of convergence: on PR #6457 the review returned "no blockers" twice and the very next round surfaced five Criticals, three of them in code that had been in the diff since the first commit. A single lazy agent must not be able to end the loop. A dry convergence pair satisfies this rule in one launch — its two members are exactly the two independent audits the rule demands; what the pair removes is the wall clock between them, not either audit. When the loop ends on this rule, the last reporting round's verifiers are already in flight (they launched with the next round's auditors) — wait for their verdicts and apply them in the final merge before Step 6.
670
- - **On the 3B path the builder is also the convergence ledger**: when every chunk holds two consecutive substantive dry audits and none is due a cold check, `--all-chunks` builds nothing, prints a `CONVERGED` explanation to stderr and exits **5**. Stop the loop and proceed to Step 6 — this is a **clean** convergence, not a gap: no `unreviewedDimensions` entry is owed, because each chunk holds the two-dry rule's evidence chunk by chunk — two consecutive dry **audits**, though not necessarily in consecutive rounds (a chunk dry in rounds 1 and 2 skips round 3 and cold-checks dry in round 4, holding rounds 2 and 4). Exit 5 is mainly the CLI enforcing the stop the two-dry-rounds rule above used to leave to orchestrator discretion; the new savings are the odd-round skips and a round-5 convergence. (It cannot owe a verification launch: a reporting round makes its chunk hot, so every verifier launched with a later round that did run.)
671
- - Stop after **5 rounds** regardless (hard cap), and say so in the output rather than implying convergence. If round 5 reported findings, its verifiers have NOT launched — that launch rides the next round's build, which the cap forbids — so launch them alone before Step 6 and wait for their verdicts; the tag backstop below (and `compose-review`'s machine-read of it) is what catches a miss.
672
- - Findings **reported** by each round are merged into the cumulative list **before** the next round begins, so each round sees an updated baseline. **The merge runs unconditionally — before every round build and before Step 6, whether or not the previous round reported findings**: under the pipelined loop below, round _k_'s verdicts land during round _k+1_, and every termination mode (two dry rounds, CONVERGED, budget stop, the 5-round cap) can arrive with the final rounds dry — a merge keyed to "some round reported something" would never apply the last verdicts that landed. Each merge applies every Step 4 verdict that has landed: confirmed removes the tag, rejected removes the entry. Verification status does not gate the merge — the list exists so auditors do not re-report what is already filed, and an unverified entry serves that purpose exactly as well as a confirmed one. The trade, named: an entry a verifier later rejects will have suppressed one round of rediscovery in its neighbourhood — the window is one round in one location, and the 5-round cap still bounds the loop. The tag is what keeps this mechanical rather than remembered: an entry enters the list tagged `— [unverified]`; the merge after its Step 4 verdict removes the tag (confirmed) or the entry (rejected). Step 6's confirmed-only read then has something to key on — anything still tagged is left out of the confirmed set — instead of a memory of which round each entry arrived in. The tag rides inside the findings file, which is hashed into the record key and copied to the digest-named list file each block points at — so a launch that drops the pointer matches no record, and the delivery floor counts the agent's read of that file exactly as it counts the brief's.
673
- - **A reporting round whose every finding the verifier rejected is retroactively dry.** The merge already removes a rejected entry from the cumulative list; from the merge that applies the last of a round's rejections, the round also stops counting as a reporting round, and the two-consecutive-dry rule reads rounds' **effective** status. Rejected means rejected — an entry confirmed at low confidence keeps its round a reporting round. Under the pipelined loop a round's verdicts land while the next round runs, so the upgrade usually arrives one round late, and that is still one round saved: a measured run held round 2 dry, watched round 3's sole finding be rejected, and then ran rounds 4 **and 5** — round 4's dry return plus the rejection already in hand was the two-dry evidence, and the fifth round audited nothing the loop had not already answered (measured; DESIGN.md — The rounds a rejected finding bought (PR #8353)). The rule leans on the rejection bar the verifier's brief already enforces — a rejection claims direct counter-evidence, never mere unverifiability — so a round retired by rejections is retired on evidence, not on doubt. **It pairs forward only, and is consulted when a round returns**: on round _k_'s dry return, first apply every verdict that has landed (the unconditional merge — the retirement takes effect at this application, not at some earlier moment), then end the loop if round _k−1_ was dry or is now retired. Round _k−1_ counts **launches, not labels**: the convergence pair is one round here — a pair member is never round _k−1_ on its own (the pair bullet's not-carried-forward rule stands), and a reporting pair retires only when every finding from **both** members is rejected. The upgrade never ends the loop by itself — a preceding dry round plus a freshly-retired round stops nothing while the next round is already in flight: that round was launched, and its return is taken whatever it says, because a launched auditor can be carrying a real Critical. This is the measured shape (round 4's return is where the loop closes under this rule — the measured run, which predates it, ran a fifth round) and the only pairing licensed here. It softens nothing else: a whiffed scope stays not-audited whatever the verdicts say, and on 3B the retirement ledger's per-chunk certificates are untouched — this rule reads at the level the round counter reads.
670
+ - **On the 3B path the builder is also the convergence ledger**: when every chunk holds two consecutive substantive dry audits and none is due a cold check, `--all-chunks` builds nothing, prints a `CONVERGED` explanation to stderr and exits **5**. Stop the loop and proceed to Step 6 — this is a **clean** convergence, not a gap: no `unreviewedDimensions` entry is owed, because each chunk holds the two-dry rule's evidence chunk by chunk — two consecutive dry **audits**, though not necessarily in consecutive rounds (a chunk dry in rounds 1 and 2 skips round 3 and cold-checks dry in round 4, holding rounds 2 and 4). Exit 5 is mainly the CLI enforcing the stop the two-dry-rounds rule above used to leave to orchestrator discretion; the new savings are the odd-round skips and a convergence at the cap round (round 5 normally, round 3 under the huge-diff cap). (It cannot owe a verification launch: a reporting round makes its chunk hot, so every verifier launched with a later round that did run.)
671
+ - Stop at the plan's **`reverseAuditRounds` cap**5, or 3 for a huge diff (effective ≥ 3000 lines) and say so in the output rather than implying convergence. The builder enforces this itself: a round past the cap gets a `ROUND CAP:` refusal on stderr and exit **4**, and — like the time-budget gate — writes a marker `compose-review` caps the verdict on whether or not you relay anything; still add the entry the message names to `unreviewedDimensions` so the terminal report agrees. If the cap round reported findings, its verifiers have NOT launched — that launch rides the next round's build, which the cap forbids — so launch them alone before Step 6 and wait for their verdicts; the tag backstop below (and `compose-review`'s machine-read of it) is what catches a miss.
672
+ - Findings **reported** by each round are merged into the cumulative list **before** the next round begins, so each round sees an updated baseline. **The merge runs unconditionally — before every round build and before Step 6, whether or not the previous round reported findings**: under the pipelined loop below, round _k_'s verdicts land during round _k+1_, and every termination mode (two dry rounds, CONVERGED, budget stop, the round cap) can arrive with the final rounds dry — a merge keyed to "some round reported something" would never apply the last verdicts that landed. Each merge applies every Step 4 verdict that has landed: confirmed removes the tag, rejected removes the entry. Verification status does not gate the merge — the list exists so auditors do not re-report what is already filed, and an unverified entry serves that purpose exactly as well as a confirmed one. The trade, named: an entry a verifier later rejects will have suppressed one round of rediscovery in its neighbourhood — the window is one round in one location, and the plan's round cap still bounds the loop. The tag is what keeps this mechanical rather than remembered: an entry enters the list tagged `— [unverified]`; the merge after its Step 4 verdict removes the tag (confirmed) or the entry (rejected). Step 6's confirmed-only read then has something to key on — anything still tagged is left out of the confirmed set — instead of a memory of which round each entry arrived in. The tag rides inside the findings file, which is hashed into the record key and copied to the digest-named list file each block points at — so a launch that drops the pointer matches no record, and the delivery floor counts the agent's read of that file exactly as it counts the brief's.
673
+ - **A reporting round whose every finding the verifier rejected is retroactively dry.** The merge already removes a rejected entry from the cumulative list; from the merge that applies the last of a round's rejections, the round also stops counting as a reporting round, and the two-consecutive-dry rule reads rounds' **effective** status. Rejected means rejected — an entry confirmed at low confidence keeps its round a reporting round. Under the pipelined loop a round's verdicts land while the next round runs, so the upgrade usually arrives one round late, and that is still one round saved: a measured run held round 2 dry, watched round 3's sole finding be rejected, and then ran rounds 4 **and 5** — round 4's dry return plus the rejection already in hand was the two-dry evidence, and the fifth round audited nothing the loop had not already answered (measured; DESIGN.md — The rounds a rejected finding bought (PR #8353)). The rule leans on the rejection bar the verifier's brief already enforces — a rejection claims direct counter-evidence, never mere unverifiability — so a round retired by rejections is retired on evidence, not on doubt. **It pairs forward only, and is consulted when a round returns**: on round _k_'s dry return, first apply every verdict that has landed (the unconditional merge — the retirement takes effect at this application, not at some earlier moment), then end the loop if round _k−1_ was dry or is now retired. Round _k−1_ counts **launches, not labels**: the convergence pair is one round here — a pair member is never round _k−1_ on its own (the pair bullet's not-carried-forward rule stands), and a reporting pair retires only when every finding from **both** members is rejected. The upgrade never ends the loop by itself — a preceding dry round plus a freshly-retired round stops nothing while the next round is already in flight: that round was launched, and its return is taken whatever it says, because a launched auditor can be carrying a real Critical. This is the measured shape (round 4's return is where the loop closes under this rule — the measured run, which predates it, ran a fifth round; a cap-5 shape — under the 3-round huge-diff tier the upgrade can only ever retire rounds 1–2, since the cap round's verdicts land during its solo verification, after the loop has already ended) and the only pairing licensed here. It softens nothing else: a whiffed scope stays not-audited whatever the verdicts say, and on 3B the retirement ledger's per-chunk certificates are untouched — this rule reads at the level the round counter reads.
674
674
  - **Verification rides alongside the next round, not ahead of it.** When round _k_ returns with new findings, one response launches BOTH round _k_'s verifiers (Step 4, `--role verify --round k` with that round's new findings) AND round _k+1_'s auditors — build the two prompt sets first, then fire every agent together, exactly as Step 3 fans out. (Step 4's initial verification is the k=0 case of the same rule: its shards ride with the first reverse-audit launch — the convergence pair on 3A, round 1's fan-out on 3B.) The serial shape (audit → wait for verification → next round) spent 5-8 minutes per round waiting for verifiers whose results the next round's auditors never needed. Two orderings still hold: the **last** round's verification must complete before Step 6 (that ordering is what keeps unverified entries out of the report and the PR, backed by the tag backstop at the end of this step — which `compose-review` machine-checks from `findingsPath`, Step 6), and a rejected finding leaves the cumulative list at the next merge.
675
- - **The round builder is also the loop's clock.** In a time-budgeted run (CI exports `QWEN_REVIEW_DEADLINE_EPOCH`; a local run normally has no deadline and is untouched), `agent-prompt --role reverse-audit` refuses to build a round that no longer fits: the remaining time must cover **the round itself** (estimated from the costliest round's measured cost so far — a repair relaunch can make one round the expensive one, and the gate prices the worst case the run has proved, not the newest dip — or a conservative constant for round 1) **plus** the reserve kept for its verification, compose-review and submission. On refusal it prints a `BUDGET:` line to stderr and exits **4**. That refusal is a termination rule, not an error — do not rebuild the round, do not relaunch auditors, and do not retry the command. The builder also records a budget-stop marker that `compose-review` reads directly, so the verdict is capped whether or not you relay anything; still add the exact entry the message names (`reverse audit — stopped before round <k> by the review time budget`) to `unreviewedDimensions` so the terminal report and the body agree, and proceed to Step 6 with the findings already confirmedspending what remains only on verifying findings already in hand, composing, and submitting. Why this exists, measured: a +1699-line PR's CI review ran the audit loop to the 5-round cap, spent 3.5 of its 4 budgeted hours there, and was killed by the outer CI timeout while round 5's findings were still being verifiedevery confirmed finding died with it. A review that stops on the budget still reports everything it proved; one that runs past it reports nothing.
675
+ - **The round builder is also the loop's clock.** In a time-budgeted run (CI exports `QWEN_REVIEW_DEADLINE_EPOCH`; a local run normally has no deadline and is untouched), `agent-prompt --role reverse-audit` refuses to build a round that no longer fits: the remaining time must cover **the round itself** (estimated from the costliest round's measured cost so far — a repair relaunch can make one round the expensive one, and the gate prices the worst case the run has proved, not the newest dip — or a conservative constant for round 1) **plus** the reserve kept for its verification, compose-review and submission. On refusal it prints a `BUDGET:` line to stderr and exits **4**. That refusal is a termination rule, not an error — do not rebuild the round, do not relaunch auditors, and do not retry the command. The builder also records a budget-stop marker that `compose-review` reads directly, so the verdict is capped whether or not you relay anything; still add the exact entry the message names (`reverse audit — stopped before round <k> by the review time budget`) to `unreviewedDimensions` so the terminal report and the body agree, and proceed to Step 6. **The tail after a budget stop is bounded, and its order is load-bearing.** Verify the last round's findingsthe ones whose verifiers would have ridden the round the gate just refused — **only through `agent-prompt --role verify`, never a hand-rolled `agent`**: that builder is gated on a **compose floor** and prints a `VERIFY BUDGET:` refusal (exit 4) once too little time remains, at which point you stop verifying and compose **immediately** — findings still carrying `— [unverified]` keep the tag, and `compose-review` caps the verdict on it and never treats an unverified finding as a confirmed blocker; everything earlier rounds confirmed still posts. **Bound the wait, not just the launch:** the builder gate stops a verifier from being _built_ below the floor, but a verifier admitted _above_ it can still run a real filesystem/git E2E workload past the floor while you wait on its batch — and `agent-prompt` builds prompts, it cannot cancel a running agent. So when the deadline is within the compose floor and a verifier batch has not returned, **stop waiting on it yourself**: take the findings in hand at their current tag and compose. A verifier you stopped waiting on leaves its findings `— [unverified]`, which caps the verdict exactly as a refused build would. Do **not** re-verify findings already confirmed in earlier rounds, and do **not** invent a fresh re-verification pass — that is the unbounded work a wall runs into. Compose and submit are non-negotiable; they always run. Why this exists, measured twice: a +1699-line PR's CI review ran the audit loop to the 5-round cap and was killed while round 5's findings were still being verified (#8368); and a 4,269-line cross-worktree git guard stopped the audit correctly with ~110 minutes left, then a single hand-rolled agent re-running a 15-family shell/git bypass battery with real filesystem E2E consumed all of it the wall hit mid-verification, compose never ran, and ~20 E2E-confirmed Critical bypasses were never posted (measured; DESIGN.md — The killed-before-compose tail (PR #8687)). A review that stops on the budget still reports everything it proved; one that runs past it reports nothing.
676
676
 
677
677
  **Reverse audit findings go through Step 4 verification like any other finding.** They used to skip it on the theory that the auditor "already has full context." That premise fails exactly when the diff is large — the auditor with the least room to think was the one whose output nobody checked.
678
678
 
@@ -900,6 +900,8 @@ If the user responds with "post comments" (or similar intent like "yes post them
900
900
 
901
901
  It also refuses a payload that contradicts itself — a body promising inline comments next to an empty `comments` array, a literal `\n` from building the JSON with `-f body=`, a `start_line` without its `side` fields — because GitHub accepts every one of those and the author is the one who finds out.
902
902
 
903
+ **On success, relay the link.** `submit`'s stdout JSON carries `url` — the `html_url` deep link GitHub returned for the review just created. Put it in your final summary on its own line, `Posted: <url>`, immediately **before** the machine-readable `Review complete:` line (which never carries it — Step 9 forbids putting anything on or after that line). This is the only way the user reaches what was just posted in one click: in the Web Shell there is no terminal scrollback to fish the stderr line out of, and a summary without the link reports a public write while hiding where it landed. If the stdout JSON has no `url` (GitHub answered without one), fall back to the PR page the run already knows — `https://<host>/<owner>/<repo>/pull/<n>` — rather than omitting the line; a resubmission after the 422 recovery relays the `url` of the review that actually posted, the last one.
904
+
903
905
  **Why this is code and not a rule you remember.** The gate below is what this step used to be: a paragraph asking you to check, first, before anything else. It has now failed twice under dogfooding. Both runs reasoned their way to a verdict they wanted to file — one a public COMMENT on this skill's own PR, with no authorisation at all (measured; DESIGN.md — The self-filed COMMENT review (PR #6771)). That is the same failure the event and body had, for the same reason, and it has the same fix: the decision is a computed fact, so a subcommand computes it. Read the gate below to understand _what_ authorises a post; do not treat it as the thing that enforces one.
904
906
 
905
907
  **The gate, for your understanding — `submit` is what enforces it.** Posting is a public, irreversible write to someone else's PR, so it happens ONLY on an explicit instruction, never as a courtesy or because a verdict "wants" to be filed. A run is authorised **only if** one of these is true:
@@ -1265,6 +1267,8 @@ where `<target>` is the same suffix as above (`pr-6740`, `local`, a filename) an
1265
1267
  - `<verdict>, not posted (<C> Critical, <S> Suggestion)` — **high or medium** effort without `--comment`/publish authorization (medium never posts — `--comment` forces high); `<verdict>` is Approve / Request changes / Comment (a medium verdict never exceeds Comment — see Step 5).
1266
1268
  - `quick pass, not posted (<N> unverified findings)` — **low** effort only.
1267
1269
 
1270
+ For any `posted` disposition, the line immediately **above** this one is `Posted: <url>` — the review link `submit` returned (Step 7). The link rides its own line because the completion line's shape is fixed and scrapers must not have to strip a URL out of it.
1271
+
1268
1272
  **The word `posted` is a fact about this run, not a description of the verdict, and it is not yours to reason about.** Write it **only** if `qwen review submit` returned `{"posted": true}` in this run. That command is the one thing here that writes to the pull request, so its answer _is_ the fact — not the `gh api` call you did not make (Step 7 forbids it, and keying the contract on a call that can no longer happen would report every successful submission as `not posted`), and not the verdict you would have liked to file. If `submit` never ran, or refused (exit 3, `{"posted": false}`), or Step 7 was skipped entirely — the target is not a PR, the effort was low or medium — the disposition takes the `not posted` form, carrying the verdict you computed. **The posting gate and this line are the same fact stated twice; they cannot disagree.** A run has emitted `APPROVE posted` where nothing whatsoever was sent to GitHub (measured; DESIGN.md — The phantom APPROVE posted line). Nothing downstream can detect that: this line _is_ the completion contract that batch drivers and log scrapers read, so a review that files no approval and announces one has handed its wrapper a public approval that does not exist.
1269
1273
 
1270
1274
  Everything before this line is for the human; this line is for machines — batch drivers, CI wrappers, and log scrapers detect run completion by `^Review complete: `, and dogfooding measured three different ad-hoc completion phrasings across one batch, each needing its own regex. Do not reword it, translate it, wrap it in markdown emphasis, or put text after it.
@@ -7,14 +7,14 @@ import {
7
7
  } from "./chunk-ONSZH6VM.js";
8
8
  import "./chunk-EKPKKXY2.js";
9
9
  import "./chunk-SV5PQVQE.js";
10
+ import "./chunk-CTJLZLB5.js";
10
11
  import "./chunk-2IIJTXYF.js";
11
12
  import "./chunk-5QWWOFGG.js";
12
- import "./chunk-CTJLZLB5.js";
13
13
  import "./chunk-4NFY2S7N.js";
14
14
  import "./chunk-2MIN6GRR.js";
15
15
  import "./chunk-QHTIBUWB.js";
16
16
  import "./chunk-RKUWKYED.js";
17
- import "./chunk-GXSNGY5M.js";
17
+ import "./chunk-XX7QFP34.js";
18
18
  import "./chunk-GOFAQQZA.js";
19
19
  import "./chunk-5M6IDOMF.js";
20
20
  import "./chunk-TWPJO254.js";
@@ -24,7 +24,7 @@ import "./chunk-MVUQRLOH.js";
24
24
  import "./chunk-XCWO3SMQ.js";
25
25
  import "./chunk-7MH4J33A.js";
26
26
  import "./chunk-6PVPNMXU.js";
27
- import "./chunk-VXZ5HAOV.js";
27
+ import "./chunk-K4ADGWHU.js";
28
28
  import "./chunk-IRH27ZC2.js";
29
29
  import "./chunk-QHWCP53L.js";
30
30
  import "./chunk-GNPNYXJB.js";
@@ -40,8 +40,8 @@ import "./chunk-SEZ556DZ.js";
40
40
  import "./chunk-2FRY5CRX.js";
41
41
  import "./chunk-67N6HB5H.js";
42
42
  import "./chunk-WAUGQKUU.js";
43
- import "./chunk-PLFATELK.js";
44
- import "./chunk-L4N6TIP7.js";
43
+ import "./chunk-DZUO626D.js";
44
+ import "./chunk-4TRQPFDP.js";
45
45
  import "./chunk-SAH4BD2J.js";
46
46
  import "./chunk-CPG6P4GG.js";
47
47
  import "./chunk-S6LOFUVP.js";
@@ -63,23 +63,23 @@ import "./chunk-AXMWHKXA.js";
63
63
  import "./chunk-GLCZKT5V.js";
64
64
  import "./chunk-DJ2GSRLV.js";
65
65
  import "./chunk-EI3HZX6C.js";
66
- import "./chunk-3PY4LPJF.js";
66
+ import "./chunk-YGT2AUJ7.js";
67
67
  import "./chunk-WOJZWRAZ.js";
68
68
  import "./chunk-VGC4I5JJ.js";
69
69
  import "./chunk-FVKHVJZQ.js";
70
70
  import "./chunk-COBO5EBO.js";
71
71
  import "./chunk-6ZRFAKXK.js";
72
72
  import "./chunk-PYQADEFD.js";
73
- import "./chunk-M3STHEMM.js";
73
+ import "./chunk-PVOGHGSF.js";
74
74
  import "./chunk-F6WFNA7U.js";
75
75
  import "./chunk-46UV252V.js";
76
76
  import "./chunk-ZBVS26BY.js";
77
77
  import "./chunk-KKBPOU75.js";
78
78
  import "./chunk-EUNWC2VS.js";
79
79
  import "./chunk-FOGU3LJX.js";
80
- import "./chunk-6M26GBKX.js";
80
+ import "./chunk-L2AFWWJE.js";
81
81
  import "./chunk-W4RQUHY3.js";
82
- import "./chunk-LKRMNRSN.js";
82
+ import "./chunk-PDPFQ2FO.js";
83
83
  import "./chunk-OIVKILPI.js";
84
84
  import "./chunk-VOQXFAY5.js";
85
85
  import "./chunk-75DOP5OR.js";