@qwen-code/qwen-code 0.21.8-nightly.20260810.55e20db328 → 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 (211) hide show
  1. package/README.md +2 -1
  2. package/bundled/qc-helper/docs/configuration/settings.md +2 -1
  3. package/bundled/qc-helper/docs/features/code-review.md +2 -0
  4. package/bundled/qc-helper/docs/qwen-serve.md +38 -37
  5. package/bundled/review/SKILL.md +7 -7
  6. package/chunks/{MaxSizedBox-A52T4GEU.js → MaxSizedBox-6BZANW7X.js} +3 -3
  7. package/chunks/{StandaloneSessionPicker-4J654SOB.js → StandaloneSessionPicker-VDC5KE7T.js} +19 -16
  8. package/chunks/{acpAgent-65AM4AOR.js → acpAgent-F2NSBCUF.js} +49 -40
  9. package/chunks/{agent-SVQ7U7NJ.js → agent-VOHJOEBB.js} +3 -3
  10. package/chunks/{agent-headless-XXGY2SQO.js → agent-headless-J5NFOPHP.js} +3 -3
  11. package/chunks/{bridge-Y32B5S3G.js → bridge-NQIZZLAS.js} +4 -4
  12. package/chunks/{channel-settings-store-OW43U6VB.js → channel-settings-store-VIY2OENO.js} +11 -9
  13. package/chunks/{chunk-OR6R3IUW.js → chunk-23VQTELM.js} +1 -1
  14. package/chunks/{chunk-VHXPSL57.js → chunk-3WDYBTTI.js} +4 -4
  15. package/chunks/{chunk-FAD7OC7V.js → chunk-3XVDTHBJ.js} +2 -2
  16. package/chunks/{chunk-7XX5IBXL.js → chunk-46YJDYCU.js} +6 -6
  17. package/chunks/{chunk-FYB4GIDW.js → chunk-4XQNB3KN.js} +2 -2
  18. package/chunks/chunk-5EJZE3FP.js +140 -0
  19. package/chunks/{chunk-DPMJLKUP.js → chunk-5RBDAGC6.js} +2 -2
  20. package/chunks/{chunk-D2D7J3EW.js → chunk-7AEPJGL4.js} +1 -1
  21. package/chunks/{chunk-HKI3KHAO.js → chunk-7GOYZQPR.js} +19 -1976
  22. package/chunks/{chunk-2ASQDZSJ.js → chunk-7S4JBJLU.js} +3 -3
  23. package/chunks/{chunk-EISUQQPS.js → chunk-ATMO54WE.js} +2 -2
  24. package/chunks/{chunk-NZMXUSKS.js → chunk-BAMN6XSD.js} +1 -1
  25. package/chunks/{chunk-2545EHZR.js → chunk-BIRWP6LU.js} +1 -1
  26. package/chunks/{chunk-UGUKH3SU.js → chunk-BZP4QPNK.js} +9 -9
  27. package/chunks/{chunk-AUWN7BAG.js → chunk-D7BXVKSF.js} +1 -1
  28. package/chunks/{chunk-TXO5BMR2.js → chunk-DH5YXSIF.js} +2 -2
  29. package/chunks/{chunk-OSVH4SMN.js → chunk-FBMCDU63.js} +3 -3
  30. package/chunks/{chunk-GY24H6IA.js → chunk-FCLHHEUK.js} +10 -10
  31. package/chunks/{chunk-FWWQIFDT.js → chunk-G2TX5ZXC.js} +3 -3
  32. package/chunks/{chunk-HDYQYYWB.js → chunk-GAKGOXTC.js} +1 -1
  33. package/chunks/{chunk-TWFFY5BP.js → chunk-HADWBW5R.js} +4 -4
  34. package/chunks/{chunk-E2N554HK.js → chunk-HZGBKOX3.js} +2 -2
  35. package/chunks/{chunk-V47QNK5W.js → chunk-II6G3EY6.js} +288 -72
  36. package/chunks/{chunk-ZV7LTLFE.js → chunk-IOEZE2YT.js} +1 -1
  37. package/chunks/chunk-JUWS2Z5K.js +1995 -0
  38. package/chunks/{chunk-F3II4BTZ.js → chunk-K4ADGWHU.js} +1 -1
  39. package/chunks/{chunk-5ZTS3OQ3.js → chunk-KSAFZ5TA.js} +1 -1
  40. package/chunks/{chunk-KMS57JDD.js → chunk-KTKYGS2P.js} +29 -6
  41. package/chunks/{chunk-HWXGX534.js → chunk-KTQGGPGV.js} +3 -3
  42. package/chunks/{chunk-ZIKTCMQF.js → chunk-LZZLUCRS.js} +5 -5
  43. package/chunks/{chunk-S5CULOHB.js → chunk-MHP476D3.js} +64 -21
  44. package/chunks/{chunk-PNVPFARW.js → chunk-MTCYGTWH.js} +4 -4
  45. package/chunks/{chunk-WQYWK4DG.js → chunk-OGDIJKHK.js} +1 -1
  46. package/chunks/{chunk-WH6QMLUW.js → chunk-OGNFHGIG.js} +3 -3
  47. package/chunks/{chunk-P5ULXPK5.js → chunk-P2FK2HBV.js} +2 -2
  48. package/chunks/{chunk-5WZL724W.js → chunk-P3RBMQ4P.js} +3 -3
  49. package/chunks/{chunk-4EOURKA4.js → chunk-P4ZEZSJZ.js} +177 -17
  50. package/chunks/{chunk-RQ6U2JJQ.js → chunk-PNBBJOCP.js} +1 -1
  51. package/chunks/{chunk-E2T7FLDU.js → chunk-PVOGHGSF.js} +5 -5
  52. package/chunks/{chunk-EUVOKP6Y.js → chunk-QSNSL4R4.js} +1 -1
  53. package/chunks/{chunk-RYQMJCUO.js → chunk-RBHQXGDK.js} +1 -1
  54. package/chunks/{chunk-ZI5IH2HP.js → chunk-RVSTTOJ4.js} +2 -2
  55. package/chunks/{chunk-OW2BWT2P.js → chunk-SCAZZ3Q4.js} +4 -4
  56. package/chunks/{chunk-OBP6QWD5.js → chunk-T5RLUXWJ.js} +2 -2
  57. package/chunks/{chunk-KP25NJNM.js → chunk-T6HEQR2Q.js} +16 -5
  58. package/chunks/{chunk-RMPLEWF3.js → chunk-THIKWITR.js} +2 -2
  59. package/chunks/{chunk-V2226RFW.js → chunk-TQCYNLNJ.js} +1 -1
  60. package/chunks/{chunk-O277O7U6.js → chunk-TZ6KJ44L.js} +1 -1
  61. package/chunks/{chunk-ZCJBCKL5.js → chunk-UJUM2SRN.js} +115 -29
  62. package/chunks/{chunk-3KJQZEN3.js → chunk-UYAUA63Q.js} +1 -1
  63. package/chunks/chunk-VFMASYQA.js +389 -0
  64. package/chunks/chunk-VGHZT6JR.js +51 -0
  65. package/chunks/{chunk-H4JC7LZX.js → chunk-W3FSZSF2.js} +1 -1
  66. package/chunks/{chunk-YFRFZCIP.js → chunk-WGK4TSB7.js} +3 -3
  67. package/chunks/{chunk-BL65MO72.js → chunk-WP5VOXF7.js} +6 -127
  68. package/chunks/{chunk-T2O6NIOY.js → chunk-XTY5DN4Y.js} +1 -1
  69. package/chunks/{chunk-47ZUMGGY.js → chunk-XX7QFP34.js} +56 -55
  70. package/chunks/{chunk-ZSNBQXMZ.js → chunk-YTFDX4PR.js} +8 -3
  71. package/chunks/{chunk-F6TUUA2U.js → chunk-ZBXGWQ4S.js} +2 -2
  72. package/chunks/{chunk-YR7ZQ4RD.js → chunk-ZOSTTOUJ.js} +1 -1
  73. package/chunks/{chunk-UAB6J2IE.js → chunk-ZTQSOV56.js} +3 -3
  74. package/chunks/{computer-use-SLN7DV2E.js → computer-use-PMZ7ICGY.js} +3 -3
  75. package/chunks/{contextCommand-EHFYX6FW.js → contextCommand-GEZU7YHE.js} +6 -5
  76. package/chunks/{core-runtime-FBSP5IIG.js → core-runtime-HRJI2MN6.js} +3 -3
  77. package/chunks/{create-sub-session-WC7YGVSL.js → create-sub-session-VBNVI4YU.js} +3 -3
  78. package/chunks/{daemon-VSX3FHTY.js → daemon-POSQPD3G.js} +26 -3
  79. package/chunks/{daemon-status-provider-WW2AD2T7.js → daemon-status-provider-N4CRQKFI.js} +8 -8
  80. package/chunks/{daemon-trust-policy-E4MAYWPI.js → daemon-trust-policy-BHO3JUHM.js} +11 -9
  81. package/chunks/{daemon-trust-policy-monitor-XVIDV6T4.js → daemon-trust-policy-monitor-YEKJ2726.js} +11 -9
  82. package/chunks/{deferred-core-runtime-5SVUC6UC.js → deferred-core-runtime-SH57E3J3.js} +3 -3
  83. package/chunks/{earlyInputCapture-72FYHYRD.js → earlyInputCapture-B2JJ2HME.js} +3 -3
  84. package/chunks/{edit-SWVVWHXW.js → edit-7FTXENAN.js} +5 -3
  85. package/chunks/{enter-worktree-2HF6WQEO.js → enter-worktree-JPHV2PBG.js} +3 -3
  86. package/chunks/{enterPlanMode-OW6S7L4P.js → enterPlanMode-CGBJ3AHJ.js} +3 -3
  87. package/chunks/{environment-LRTBTDXH.js → environment-LXASZAHD.js} +6 -6
  88. package/chunks/{errors-4CO2DME6.js → errors-XIGVT2AD.js} +5 -5
  89. package/chunks/{exit-worktree-GHJEIRGL.js → exit-worktree-DKSGXW65.js} +3 -3
  90. package/chunks/{exitPlanMode-JVP4FNNK.js → exitPlanMode-THFL2HYN.js} +3 -3
  91. package/chunks/{fast-path-HCCY7GR2.js → fast-path-4NXAT5UL.js} +3 -3
  92. package/chunks/{fast-path-settings-MN5QQQET.js → fast-path-settings-RPAVBD7P.js} +2 -2
  93. package/chunks/{gemini-V3YVRKXU.js → gemini-B3N6FACU.js} +39 -44
  94. package/chunks/{geminiContentGenerator-LVPMQUWX.js → geminiContentGenerator-YYFE2HIE.js} +1 -1
  95. package/chunks/{glob-7HRXHYDR.js → glob-BPHSCFJP.js} +3 -3
  96. package/chunks/{grep-BITQUON5.js → grep-ROZIRFLU.js} +3 -3
  97. package/chunks/{handleAutoUpdate-DKFY454S.js → handleAutoUpdate-WV6SPZQB.js} +8 -7
  98. package/chunks/{i18n-53BQIQSH.js → i18n-YWJWPQKD.js} +8 -6
  99. package/chunks/{image-gen-H62FLDFB.js → image-gen-SISRZ6FS.js} +1 -1
  100. package/chunks/{initializer-Q7NUQRSU.js → initializer-66OAD6GO.js} +11 -9
  101. package/chunks/{installationInfo-BWFHT7WU.js → installationInfo-3YUGIP36.js} +4 -4
  102. package/chunks/{list-OWQKR6JK.js → list-VEYR66XB.js} +14 -12
  103. package/chunks/{loadedSettingsAdapter-HNPKQMFA.js → loadedSettingsAdapter-V2JCPCYD.js} +11 -9
  104. package/chunks/{loggingContentGenerator-A5I3VQUC.js → loggingContentGenerator-RBLNDREC.js} +1 -1
  105. package/chunks/{managed-npm-update-CUUJGUTV.js → managed-npm-update-C5ID5R4R.js} +4 -4
  106. package/chunks/{mcp-GJSC7ZPO.js → mcp-OZ54NZJQ.js} +11 -9
  107. package/chunks/{monitor-TZ2II7JM.js → monitor-7Y4TVFTK.js} +3 -3
  108. package/chunks/{nonInteractiveCli-22RS4EVH.js → nonInteractiveCli-QEK4LHAC.js} +35 -33
  109. package/chunks/{notebook-edit-YZC3YPY2.js → notebook-edit-OG35EBGY.js} +4 -3
  110. package/chunks/{openaiContentGenerator-OIGO5XNF.js → openaiContentGenerator-443BNRPJ.js} +1 -1
  111. package/chunks/{pidfile-QJQ65RJ2.js → pidfile-ZK4OLRZA.js} +3 -3
  112. package/chunks/{processUtils-V5VSDIKS.js → processUtils-4YGB4RUZ.js} +2 -2
  113. package/chunks/{qwenContentGenerator-HXM25BGB.js → qwenContentGenerator-D5C2LCSZ.js} +3 -3
  114. package/chunks/{qwenOAuth2-7MTMS2Z5.js → qwenOAuth2-Z4YQBMYN.js} +1 -1
  115. package/chunks/{read-file-PMGXB3ZB.js → read-file-EXTJLWMU.js} +1 -1
  116. package/chunks/{resumeHistoryUtils-YUTPLHUH.js → resumeHistoryUtils-UPCGRJUL.js} +7 -6
  117. package/chunks/{ripGrep-XUS66YIV.js → ripGrep-QBVIRCU4.js} +3 -3
  118. package/chunks/{run-qwen-serve-4NVSU75L.js → run-qwen-serve-ZYGJNE3E.js} +67 -51
  119. package/chunks/{runtime-QLRVP6HM.js → runtime-BZZ4LWCP.js} +12 -10
  120. package/chunks/{scheduler-PBB2NSOP.js → scheduler-YNQNOU2E.js} +131 -11
  121. package/chunks/{serve-BWKTRX72.js → serve-LJHAXDCH.js} +11 -9
  122. package/chunks/{server-E7CH7WPK.js → server-ADMLELR6.js} +256 -750
  123. package/chunks/{session-QEKSJ6YU.js → session-YKZXFFTM.js} +36 -34
  124. package/chunks/{settings-BRAOSJIM.js → settings-EQBUAQ7Y.js} +10 -8
  125. package/chunks/{shell-OF3CK4AT.js → shell-3QZOJGTT.js} +3 -3
  126. package/chunks/{skill-HLH2NO2H.js → skill-UWSSMXE7.js} +1 -1
  127. package/chunks/{skill-settings-DRPU7YI5.js → skill-settings-T6MLB7NU.js} +10 -8
  128. package/chunks/{spawnChannel-4KO7YTFM.js → spawnChannel-VS7ZFUFP.js} +3 -3
  129. package/chunks/{standalone-update-DJN2R3YW.js → standalone-update-2W26SRRO.js} +6 -5
  130. package/chunks/{startInteractiveUI-O3WQQ2WC.js → startInteractiveUI-CVS7K3D6.js} +55 -45
  131. package/chunks/{team-create-2E4ACBJD.js → team-create-4WFTNNUL.js} +3 -3
  132. package/chunks/{team-plan-approval-LWIDXYOA.js → team-plan-approval-DBYI33S3.js} +3 -3
  133. package/chunks/{terminal-image-renderer-PJM7663W.js → terminal-image-renderer-S5WPPQKH.js} +3 -3
  134. package/chunks/{theme-manager-FDQTM3RP.js → theme-manager-LI2RIOVR.js} +3 -3
  135. package/chunks/{tool-search-TL5ZXBF4.js → tool-search-4773EUQ4.js} +1 -1
  136. package/chunks/{total-session-admission-LS6EY5KT.js → total-session-admission-PAY5CH2Z.js} +4 -4
  137. package/chunks/{trustedFolders-5RWP5OB7.js → trustedFolders-FRSLE674.js} +4 -4
  138. package/chunks/{update-relaunch-HWQ4USKY.js → update-relaunch-ZSFQTU6M.js} +5 -5
  139. package/chunks/{updateCheck-EZMBTEIR.js → updateCheck-AXWSO7KA.js} +7 -6
  140. package/chunks/{useAutoAcceptIndicator-JDMI627A.js → useAutoAcceptIndicator-VHLXIA7U.js} +11 -9
  141. package/chunks/{validateNonInterActiveAuth-RRUHC5RO.js → validateNonInterActiveAuth-PDJECMFU.js} +32 -30
  142. package/chunks/{version-XS4UTKUX.js → version-RBN764QS.js} +1 -1
  143. package/chunks/{web-fetch-2JV7H6OM.js → web-fetch-7BJK42AF.js} +1 -1
  144. package/chunks/{web-search-RCTN2GAM.js → web-search-L2CILQXV.js} +2 -2
  145. package/chunks/{workflow-6MKLKQ6R.js → workflow-5VH2R74I.js} +4 -4
  146. package/chunks/{workspace-providers-status-YZ2IJ4DK.js → workspace-providers-status-OZTQ6JFH.js} +13 -11
  147. package/chunks/{workspace-registration-store-IYN72LBS.js → workspace-registration-store-IX5RJQS5.js} +1 -1
  148. package/chunks/{workspace-registry-7LFBZQLT.js → workspace-registry-73N4BGG4.js} +4 -4
  149. package/chunks/{workspace-service-2EHSPRJB.js → workspace-service-I5RUPSNV.js} +14 -12
  150. package/chunks/{workspace-skills-status-5VJ2D5HN.js → workspace-skills-status-DMK6VYDO.js} +11 -9
  151. package/chunks/{workspace-trust-reconciler-X2LZ6XCI.js → workspace-trust-reconciler-QX4RL3TC.js} +12 -10
  152. package/chunks/{write-file-CJK6SCCF.js → write-file-UGDD5UTS.js} +3 -3
  153. package/chunks/{zoom-image-BVW4RFYW.js → zoom-image-ZAHEO3YP.js} +1 -1
  154. package/cli.js +12 -12
  155. package/package.json +3 -3
  156. package/web-shell/assets/{arc-DDtoJrAW.js → arc-CHY4saMJ.js} +1 -1
  157. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BAInIW4O.js → architectureDiagram-3BPJPVTR-Bu6CwvHj.js} +1 -1
  158. package/web-shell/assets/{blockDiagram-GPEHLZMM-ChPDk7fP.js → blockDiagram-GPEHLZMM-XGt6iGf_.js} +1 -1
  159. package/web-shell/assets/{c4Diagram-AAUBKEIU-Cd9BJuUy.js → c4Diagram-AAUBKEIU-CBbYl19W.js} +1 -1
  160. package/web-shell/assets/channel-B40srHOa.js +1 -0
  161. package/web-shell/assets/{chunk-2J33WTMH-COQkLp56.js → chunk-2J33WTMH-BEhmVrdx.js} +1 -1
  162. package/web-shell/assets/{chunk-4BX2VUAB-GUJshQ_l.js → chunk-4BX2VUAB-fQLyE0Bg.js} +1 -1
  163. package/web-shell/assets/{chunk-55IACEB6-Boaa7c10.js → chunk-55IACEB6-yGdtyx4a.js} +1 -1
  164. package/web-shell/assets/{chunk-727SXJPM-CcRXaly2.js → chunk-727SXJPM-DYbX2GLo.js} +1 -1
  165. package/web-shell/assets/{chunk-AQP2D5EJ-BY0OARHh.js → chunk-AQP2D5EJ-DyKTEsYn.js} +1 -1
  166. package/web-shell/assets/{chunk-FMBD7UC4-DLFQfv0S.js → chunk-FMBD7UC4-D8Uy_GzX.js} +1 -1
  167. package/web-shell/assets/{chunk-ND2GUHAM-C_S0u-D2.js → chunk-ND2GUHAM-C8wNR2K7.js} +1 -1
  168. package/web-shell/assets/{chunk-QZHKN3VN-DP9m64hw.js → chunk-QZHKN3VN-BnybNxnt.js} +1 -1
  169. package/web-shell/assets/classDiagram-4FO5ZUOK-qumcXQW_.js +1 -0
  170. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-qumcXQW_.js +1 -0
  171. package/web-shell/assets/{cose-bilkent-S5V4N54A-CGY6zGE1.js → cose-bilkent-S5V4N54A-jq4uiFRp.js} +1 -1
  172. package/web-shell/assets/{dagre-BM42HDAG-yB7whKNn.js → dagre-BM42HDAG-Bs7oye1U.js} +1 -1
  173. package/web-shell/assets/{diagram-2AECGRRQ-Bdlc59La.js → diagram-2AECGRRQ-BHGGJNyM.js} +1 -1
  174. package/web-shell/assets/{diagram-5GNKFQAL-BUClP18L.js → diagram-5GNKFQAL-BJezVLGT.js} +1 -1
  175. package/web-shell/assets/{diagram-KO2AKTUF-aZm08kjn.js → diagram-KO2AKTUF-5JzQN_rL.js} +1 -1
  176. package/web-shell/assets/{diagram-LMA3HP47-7DGwEkxC.js → diagram-LMA3HP47-BlPYOgY9.js} +1 -1
  177. package/web-shell/assets/{diagram-OG6HWLK6-CAGKayxx.js → diagram-OG6HWLK6-CtMHEaFK.js} +1 -1
  178. package/web-shell/assets/{erDiagram-TEJ5UH35-CptoHHQi.js → erDiagram-TEJ5UH35-CRNdq2AB.js} +1 -1
  179. package/web-shell/assets/{flowDiagram-I6XJVG4X-CNOH0s7Q.js → flowDiagram-I6XJVG4X-De1WwLzu.js} +1 -1
  180. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CCQuCU3y.js → ganttDiagram-6RSMTGT7-YbWfL5Zn.js} +1 -1
  181. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-B7gv9f4E.js → gitGraphDiagram-PVQCEYII-DzCN8bmd.js} +1 -1
  182. package/web-shell/assets/index-BDOGGGaN.css +5 -0
  183. package/web-shell/assets/{index-BoUPVvpf.js → index-CocPvF9Q.js} +1 -1
  184. package/web-shell/assets/index-DSH2KrMc.js +1771 -0
  185. package/web-shell/assets/{infoDiagram-5YYISTIA-Bb3KYEbR.js → infoDiagram-5YYISTIA-DLHoopnE.js} +1 -1
  186. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DWEzbAGi.js → ishikawaDiagram-YF4QCWOH-XnbU04SS.js} +1 -1
  187. package/web-shell/assets/{journeyDiagram-JHISSGLW-CmxwEqeb.js → journeyDiagram-JHISSGLW-CipPyEeJ.js} +1 -1
  188. package/web-shell/assets/{kanban-definition-UN3LZRKU-Ci0gDD8O.js → kanban-definition-UN3LZRKU-DrSeyE5d.js} +1 -1
  189. package/web-shell/assets/{linear-BqxZqGhS.js → linear-LrBKnAo_.js} +1 -1
  190. package/web-shell/assets/{mermaid.core-BDAFffHv.js → mermaid.core-C3U9UVmf.js} +5 -5
  191. package/web-shell/assets/{mindmap-definition-RKZ34NQL-Btvx-xvS.js → mindmap-definition-RKZ34NQL-DqRWgC9H.js} +1 -1
  192. package/web-shell/assets/{pieDiagram-4H26LBE5-CKAg7yG7.js → pieDiagram-4H26LBE5-BNXY5p6H.js} +1 -1
  193. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DIt8E1mC.js → quadrantDiagram-W4KKPZXB-Ccj9QzE4.js} +1 -1
  194. package/web-shell/assets/{requirementDiagram-4Y6WPE33-BA7mQWQJ.js → requirementDiagram-4Y6WPE33-CjPM2_gx.js} +1 -1
  195. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-D0JppnVU.js → sankeyDiagram-5OEKKPKP-C0RsvKbj.js} +1 -1
  196. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-29DIQFjB.js → sequenceDiagram-3UESZ5HK-ClN1P5z7.js} +1 -1
  197. package/web-shell/assets/{stateDiagram-AJRCARHV-iiXMdK--.js → stateDiagram-AJRCARHV-By0eiB2Y.js} +1 -1
  198. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Ds0WlK3F.js +1 -0
  199. package/web-shell/assets/{timeline-definition-PNZ67QCA-CeF7ngSW.js → timeline-definition-PNZ67QCA-g_iaSQgv.js} +1 -1
  200. package/web-shell/assets/{vennDiagram-CIIHVFJN-Beilq8D6.js → vennDiagram-CIIHVFJN-y9ZqJ-TD.js} +1 -1
  201. package/web-shell/assets/{wardley-L42UT6IY-DOYjY4zO.js → wardley-L42UT6IY-BVVfaxpF.js} +1 -1
  202. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-Dtqp8o18.js → wardleyDiagram-YWT4CUSO-D3ooZKZT.js} +1 -1
  203. package/web-shell/assets/{xychartDiagram-2RQKCTM6-cdAc0Dfv.js → xychartDiagram-2RQKCTM6-6GXrY6M1.js} +1 -1
  204. package/web-shell/index.html +2 -2
  205. package/chunks/chunk-R4ZA6GBK.js +0 -208
  206. package/web-shell/assets/channel-0OQklTfP.js +0 -1
  207. package/web-shell/assets/classDiagram-4FO5ZUOK-CZ4-apsu.js +0 -1
  208. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CZ4-apsu.js +0 -1
  209. package/web-shell/assets/index-D1YQXYsB.css +0 -5
  210. package/web-shell/assets/index-J4N7zCmU.js +0 -1768
  211. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CEGK3l80.js +0 -1
package/README.md CHANGED
@@ -14,7 +14,8 @@
14
14
  <a href="https://qwenlm.github.io/qwen-code-docs/fr/users/overview">français</a> |
15
15
  <a href="https://qwenlm.github.io/qwen-code-docs/ja/users/overview">日本語</a> |
16
16
  <a href="https://qwenlm.github.io/qwen-code-docs/ru/users/overview">Русский</a> |
17
- <a href="https://qwenlm.github.io/qwen-code-docs/pt-BR/users/overview">Português (Brasil)</a>
17
+ <a href="https://qwenlm.github.io/qwen-code-docs/pt-BR/users/overview">Português (Brasil)</a> |
18
+ <a href="https://qwenlm.github.io/qwen-code-docs/ko/users/overview">한국어</a>
18
19
 
19
20
  </div>
20
21
 
@@ -181,6 +181,7 @@ Settings are organized into categories. Most settings should be placed within th
181
181
  | `model.skipStartupContext` | boolean | Skips sending the startup workspace context (environment summary and acknowledgement) at the beginning of each session. Enable this if you prefer to provide context manually or want to save tokens on startup. | `false` |
182
182
  | `model.enableOpenAILogging` | boolean | Enables logging of OpenAI API calls for debugging and analysis. When enabled, API requests and responses are logged to JSON files. | `false` |
183
183
  | `model.openAILoggingDir` | string | Custom directory path for OpenAI API logs. If not specified, defaults to `logs/openai` in the current working directory. Supports absolute paths, relative paths (resolved from current working directory), and `~` expansion (home directory). | `undefined` |
184
+ | `model.openAILogRetentionDays` | number | Days to retain OpenAI API log files written when `model.enableOpenAILogging` is on. Log files older than this are removed by an interactive-session background housekeeping pass that runs at most once per day; headless and SDK-only use does not start the pass. `0` = minimum retention (~1 hour). For a custom `model.openAILoggingDir`, configure retention at user or system scope; workspace-scoped retention is skipped because one custom directory can be shared by multiple workspaces. Changes take effect after restart. | `7` |
184
185
 
185
186
  **Example model.generationConfig:**
186
187
 
@@ -724,7 +725,7 @@ For authentication-related variables (like `OPENAI_*`) and the recommended `.qwe
724
725
 
725
726
  > [!warning]
726
727
  >
727
- > **Loader-affecting variables are always rejected:** Variables that make a spawned Node.js process or OS loader execute an attacker-chosen file — `NODE_OPTIONS`, `npm_config_node_options` (and npm's config-file redirects `npm_config_userconfig`, `npm_config_globalconfig`, `npm_config_script_shell`, `npm_config_prefix`), `NODE_PATH`, `LD_PRELOAD`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`, `BASH_ENV`, `ZDOTDIR`, and exported bash function definitions (`BASH_FUNC_*`) — are never loaded from `.env` files (any scope, including `.qwen/.env` and user-level files) or from the top-level `settings.json` `env` section. A workspace-controlled value there could hijack module resolution for every subprocess Qwen Code spawns, so Qwen Code prints a warning when it rejects such a key (once per process, per key and source — in a multi-workspace daemon each workspace's rejection is reported separately). To use one of these variables, export it in the environment you launch Qwen Code from; sessions hosted by a `qwen serve` daemon deliberately do not inherit them, while direct editor (ACP) sessions and the plain CLI keep the exported value. Library _search_ paths (`LD_LIBRARY_PATH`, `DYLD_LIBRARY_PATH`) and the interactive-shell-only `ENV` are intentionally not on this list — rejecting them breaks mainstream toolchains (`ENV=production`, conda/CUDA library dirs) — but a project `.env` still cannot apply them on reload. This rejection applies to the top-level `env` section only: per-server `mcpServers[].env` and per-hook `hooks[].env` are intentionally scoped to that server or hook and still apply (both surfaces are gated by folder trust for workspace-provided configs). Separately, a project `.env` can never set `QWEN_CLI_ENTRY` (the daemon's session-process entrypoint), `NODE_EXTRA_CA_CERTS`, or `DEV` (the dev-harness launch marker); those stay settable from the shell environment or a user-level `.env`. Upgrade note: before this denylist existed, some of these keys could load from `.env` files or `settings.json` `env` on some paths; they are now rejected everywhere with a warning, and a `qwen serve` daemon no longer passes inherited values of them to session subprocesses.
728
+ > **Loader-affecting variables are always rejected:** Variables that make a spawned Node.js process or OS loader execute an attacker-chosen file — `NODE_OPTIONS`, `npm_config_node_options` (and npm's config-file redirects `npm_config_userconfig`, `npm_config_globalconfig`, `npm_config_script_shell`, `npm_config_prefix`), `NODE_PATH`, `OPENSSL_CONF` (dlopens an attacker OpenSSL engine at startup), `NODE_REPL_EXTERNAL_MODULE`, `npm_config_node_gyp`, `npm_config_init_module`, `LD_PRELOAD`, `LD_AUDIT`, `DYLD_INSERT_LIBRARIES`, `BASH_ENV`, `ZDOTDIR`, and exported bash function definitions (`BASH_FUNC_*`) — are never loaded from `.env` files (any scope, including `.qwen/.env` and user-level files) or from the top-level `settings.json` `env` section. A workspace-controlled value there could hijack module resolution for every subprocess Qwen Code spawns, so Qwen Code prints a warning when it rejects such a key (once per process, per key and source — in a multi-workspace daemon each workspace's rejection is reported separately). To use one of these variables, export it in the environment you launch Qwen Code from; sessions hosted by a `qwen serve` daemon deliberately do not inherit them, while direct editor (ACP) sessions and the plain CLI keep the exported value. Library _search_ paths (`LD_LIBRARY_PATH`, `DYLD_LIBRARY_PATH`) and the interactive-shell-only `ENV` are intentionally not on this list — rejecting them breaks mainstream toolchains (`ENV=production`, conda/CUDA library dirs) — but a project `.env` still cannot apply them on reload. This rejection applies to the top-level `env` section only: per-server `mcpServers[].env` and per-hook `hooks[].env` are intentionally scoped to that server or hook and still apply (both surfaces are gated by folder trust for workspace-provided configs). Separately, a project `.env` can never set `QWEN_CLI_ENTRY` (the daemon's session-process entrypoint), `QWEN_CDP_MCP_COMMAND` (the command the daemon spawns as the browser-automation MCP adapter), `QWEN_SERVE_CDP_TUNNEL_OVER_WS` (switches that tunnel surface on), `DEV` (the dev-harness launch marker), the TLS trust-anchor variables (`NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `SSL_CERT_DIR`, `CURL_CA_BUNDLE`, `REQUESTS_CA_BUNDLE`, `GIT_SSL_CAINFO`, `GIT_SSL_CAPATH`, `npm_config_cafile`, `npm_config_ca`, `npm_config_strict_ssl`, `PIP_CERT` — an attacker CA there, or `npm_config_strict_ssl=false`, would enable MITM of the token-bearing traffic a session's `git`/`npm`/`pip`/`curl` calls make), the git command-execution variables (`GIT_SSH_COMMAND`, `GIT_SSH`, `GIT_EXEC_PATH`, `GIT_TEMPLATE_DIR`, `GIT_ASKPASS`, `GIT_PROXY_COMMAND`, `GIT_EDITOR`, `GIT_SEQUENCE_EDITOR`, `GIT_EXTERNAL_DIFF`, `GIT_CONFIG_GLOBAL`, `GIT_CONFIG_SYSTEM`, `GIT_CONFIG_COUNT`, `GIT_CONFIG_PARAMETERS` and the numbered `GIT_CONFIG_KEY_<n>`/`GIT_CONFIG_VALUE_<n>` pairs — git runs these on any session `git` invocation — and `XDG_CONFIG_HOME`, which redirects the `$XDG_CONFIG_HOME/git/config` git merges with `~/.gitconfig`), the curl/wget rc-file redirects (`CURL_HOME`, `WGETRC` — their rc files can install an attacker proxy or CA), `PIP_CONFIG_FILE` (redirects all of pip's configuration — `index-url`, `trusted-host`, proxy, or cert settings in an attacker file send session pip traffic or credentials to attacker infrastructure), `SSH_ASKPASS` (git/ssh execute it as the fallback passphrase-prompt program on an auth challenge), `LESSOPEN` and `LESSCLOSE` (`less` executes them as input preprocessors on every file a session views), the node-gyp interpreter-selection variables (`NODE_GYP_FORCE_PYTHON`, `npm_config_python`, `PYTHON` — run as the build Python during native-addon installs — and `npm_config_git`, run as npm's git binary), the editor and startup hooks (`VISUAL`, `EDITOR` — git's editor fallback chain, also spawned by the CLI's own external-editor flows — and `PYTHONSTARTUP`, which CPython executes at interactive startup), or `BROWSER` (the CLI execs it via the secure browser launcher). Those stay settable from the shell environment or a user-level `.env`; unlike the loader list above they are rejected from project files only, so a value you export yourself is preserved. They are also frozen at boot from a user-level `.env`: a settings reload does not apply edits to them there — or their removal — until the process restarts. Upgrade note: before this denylist existed, some of these keys could load from `.env` files or `settings.json` `env` on some paths; they are now rejected everywhere with a warning, and a `qwen serve` daemon no longer passes inherited values of them to session subprocesses.
728
729
 
729
730
  ### Environment Variables Table
730
731
 
@@ -387,6 +387,8 @@ The exit code is the contract a gate should read:
387
387
 
388
388
  A time-budgeted run can also export a **soft** deadline so the review stops its open-ended reverse-audit loop while there is still time to verify, compose and post: `QWEN_REVIEW_DEADLINE_EPOCH` is the Unix-seconds moment the run will be killed, and `QWEN_REVIEW_DEADLINE_RESERVE_SECONDS` (default 3600; `0` keeps only the round estimate) is the tail that must remain for the last round's verification, `compose-review` and submission. When the remaining budget no longer fits another round plus that tail, the round builder refuses to build it, and the composed verdict discloses the truncated audit (an otherwise-Approve verdict is capped at Comment). A missing or malformed deadline leaves the review ungated — the outer timeout still bounds the run.
389
389
 
390
+ Nested inside that reserve is a smaller **compose floor**, `QWEN_REVIEW_DEADLINE_COMPOSE_FLOOR_SECONDS` (default 1200; `0` disables this gate entirely, at every point including past the deadline). The reserve is one number covering "verify the last round **plus** compose **plus** submit", which fits a normal per-finding re-trace but not a security review whose verification re-runs real filesystem/git workloads without bound. So the verifier — not the round builder — is gated on this floor: once the floor or less remains, `agent-prompt --role verify` refuses to build (a `VERIFY BUDGET:` line, exit **4**), the findings in hand keep their unverified tag (which caps the verdict), and `compose-review` and submission run. The floor is strictly below the reserve, so a healthy run hits the reverse-audit gate first and never reaches it; it is the cover for the one span the reserve cannot bound.
391
+
390
392
  ## Cross-file Impact Analysis
391
393
 
392
394
  A dedicated cross-file tracer (Agent 1c) owns this walk end-to-end. When code changes modify exported functions, classes, or interfaces, it searches for all callers and checks compatibility:
@@ -380,40 +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
- | `--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 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. |
405
- | `--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. |
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` 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. |
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. |
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. |
417
417
 
418
418
  > **Memory project scope caveats.**
419
419
  >
@@ -556,10 +556,11 @@ provider decision with their normal tool policy and isolation boundary.
556
556
  - **`--hostname 0.0.0.0` requires a token** — boot refuses without one.
557
557
  - **`LOOPBACK_BINDS` includes IPv6** — `::1` and `[::1]` count as loopback for the no-token rule.
558
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.
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` 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.
560
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.
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`, `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 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).
563
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.
564
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.
565
566
  - **Graceful shutdown** — SIGINT/SIGTERM drain the agent children before closing the listener (10s deadline per child).
@@ -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
 
@@ -14,7 +14,7 @@ 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-47ZUMGGY.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-F3II4BTZ.js";
27
+ import "./chunk-K4ADGWHU.js";
28
28
  import "./chunk-IRH27ZC2.js";
29
29
  import "./chunk-QHWCP53L.js";
30
30
  import "./chunk-GNPNYXJB.js";
@@ -70,7 +70,7 @@ 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-E2T7FLDU.js";
73
+ import "./chunk-PVOGHGSF.js";
74
74
  import "./chunk-F6WFNA7U.js";
75
75
  import "./chunk-46UV252V.js";
76
76
  import "./chunk-ZBVS26BY.js";
@@ -4,13 +4,13 @@ import {
4
4
  ConfigContext,
5
5
  SessionPicker,
6
6
  SettingsContext
7
- } from "./chunk-S5CULOHB.js";
8
- import "./chunk-WH6QMLUW.js";
7
+ } from "./chunk-MHP476D3.js";
8
+ import "./chunk-OGNFHGIG.js";
9
9
  import {
10
10
  KeypressProvider
11
11
  } from "./chunk-VNNWAZSI.js";
12
- import "./chunk-T2O6NIOY.js";
13
- import "./chunk-DPMJLKUP.js";
12
+ import "./chunk-XTY5DN4Y.js";
13
+ import "./chunk-5RBDAGC6.js";
14
14
  import "./chunk-NV3F44FL.js";
15
15
  import "./chunk-4WGODMVR.js";
16
16
  import "./chunk-KW7NOTN6.js";
@@ -30,29 +30,32 @@ import {
30
30
  } from "./chunk-5QWWOFGG.js";
31
31
  import "./chunk-S6HYI3MF.js";
32
32
  import "./chunk-PJRW65JO.js";
33
- import "./chunk-O277O7U6.js";
34
- import "./chunk-3KJQZEN3.js";
33
+ import "./chunk-TZ6KJ44L.js";
34
+ import "./chunk-P2FK2HBV.js";
35
+ import "./chunk-UYAUA63Q.js";
35
36
  import "./chunk-4NFY2S7N.js";
36
37
  import "./chunk-2MIN6GRR.js";
37
38
  import "./chunk-QHTIBUWB.js";
38
39
  import "./chunk-EKTCKZV5.js";
39
40
  import "./chunk-3SH53ANB.js";
40
41
  import "./chunk-57OAHC2Y.js";
41
- import "./chunk-AUWN7BAG.js";
42
- import "./chunk-HKI3KHAO.js";
42
+ import "./chunk-D7BXVKSF.js";
43
+ import "./chunk-JUWS2Z5K.js";
44
+ import "./chunk-7GOYZQPR.js";
43
45
  import "./chunk-RKUWKYED.js";
44
46
  import "./chunk-GOXKNSDZ.js";
45
47
  import "./chunk-IDS7MSUP.js";
46
- import "./chunk-BL65MO72.js";
47
- import "./chunk-KP25NJNM.js";
48
- import "./chunk-R4ZA6GBK.js";
49
- import "./chunk-YR7ZQ4RD.js";
48
+ import "./chunk-WP5VOXF7.js";
49
+ import "./chunk-5EJZE3FP.js";
50
+ import "./chunk-T6HEQR2Q.js";
51
+ import "./chunk-VFMASYQA.js";
52
+ import "./chunk-ZOSTTOUJ.js";
50
53
  import "./chunk-NPQ5ZKRP.js";
51
54
  import "./chunk-MR3PXB6E.js";
52
55
  import {
53
56
  SessionService,
54
57
  getGitBranch
55
- } from "./chunk-47ZUMGGY.js";
58
+ } from "./chunk-XX7QFP34.js";
56
59
  import "./chunk-GOFAQQZA.js";
57
60
  import "./chunk-5M6IDOMF.js";
58
61
  import "./chunk-TWPJO254.js";
@@ -62,7 +65,7 @@ import "./chunk-MVUQRLOH.js";
62
65
  import "./chunk-XCWO3SMQ.js";
63
66
  import "./chunk-7MH4J33A.js";
64
67
  import "./chunk-6PVPNMXU.js";
65
- import "./chunk-F3II4BTZ.js";
68
+ import "./chunk-K4ADGWHU.js";
66
69
  import "./chunk-IRH27ZC2.js";
67
70
  import "./chunk-QHWCP53L.js";
68
71
  import "./chunk-GNPNYXJB.js";
@@ -108,7 +111,7 @@ import "./chunk-FVKHVJZQ.js";
108
111
  import "./chunk-COBO5EBO.js";
109
112
  import "./chunk-6ZRFAKXK.js";
110
113
  import "./chunk-PYQADEFD.js";
111
- import "./chunk-E2T7FLDU.js";
114
+ import "./chunk-PVOGHGSF.js";
112
115
  import "./chunk-F6WFNA7U.js";
113
116
  import "./chunk-46UV252V.js";
114
117
  import "./chunk-ZBVS26BY.js";
@@ -120,7 +123,7 @@ import "./chunk-W4RQUHY3.js";
120
123
  import "./chunk-PDPFQ2FO.js";
121
124
  import "./chunk-OIVKILPI.js";
122
125
  import "./chunk-VOQXFAY5.js";
123
- import "./chunk-RQ6U2JJQ.js";
126
+ import "./chunk-PNBBJOCP.js";
124
127
  import {
125
128
  writeStdoutLine
126
129
  } from "./chunk-KGJGEEVR.js";