@qwen-code/qwen-code 0.19.9 → 0.19.10-nightly.20260715.c538bd70d

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 (310) hide show
  1. package/bundled/qc-helper/docs/configuration/settings.md +8 -0
  2. package/bundled/qc-helper/docs/features/channels/dingtalk.md +3 -0
  3. package/bundled/qc-helper/docs/features/channels/overview.md +11 -3
  4. package/bundled/qc-helper/docs/features/code-review.md +88 -44
  5. package/bundled/qc-helper/docs/features/commands.md +6 -6
  6. package/bundled/qc-helper/docs/features/hooks.md +29 -0
  7. package/bundled/qc-helper/docs/features/sub-agents.md +21 -0
  8. package/bundled/qc-helper/docs/qwen-serve.md +46 -20
  9. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +1 -1
  10. package/bundled/review/DESIGN.md +219 -33
  11. package/bundled/review/SKILL.md +449 -153
  12. package/chunks/{MaxSizedBox-6HYJ3ZPJ.js → MaxSizedBox-LOOBDOTZ.js} +31 -31
  13. package/chunks/{StandaloneSessionPicker-WS4GXYOG.js → StandaloneSessionPicker-76CLZSH2.js} +51 -49
  14. package/chunks/{acpAgent-5FWSOZL5.js → acpAgent-DFJOK7EB.js} +567 -482
  15. package/chunks/{agent-HOLHB3RT.js → agent-OJQWXNHP.js} +26 -26
  16. package/chunks/{agent-headless-SXL6H56A.js → agent-headless-ND7GOORY.js} +26 -26
  17. package/chunks/{anthropicContentGenerator-L73TO3DB.js → anthropicContentGenerator-56U3YCP2.js} +5 -5
  18. package/chunks/{artifact-tool-U7IRTLIV.js → artifact-tool-MIZLXBLF.js} +2 -2
  19. package/chunks/{askUserQuestion-2DLUUZZG.js → askUserQuestion-XMJVPNWM.js} +2 -2
  20. package/chunks/{bridge-BCJQ7BBB.js → bridge-SNJK2JLK.js} +32 -32
  21. package/chunks/{ca-UJ6HC4H2.js → ca-C2JTT2N5.js} +4 -1
  22. package/chunks/channel-worker-group-K7QKH6IG.js +13 -0
  23. package/chunks/channel-worker-manager-YSFB4HUU.js +418 -0
  24. package/chunks/channel-worker-supervisor-QCKIOYZD.js +20 -0
  25. package/chunks/{chunk-CCDMCZPP.js → chunk-26UABK2C.js} +1 -1
  26. package/chunks/{chunk-LXNEJD34.js → chunk-2FO5GFKZ.js} +1 -1
  27. package/chunks/{chunk-L36P3NBD.js → chunk-2W3OOD4W.js} +2 -0
  28. package/chunks/{chunk-7R3CZA3Q.js → chunk-2Z5UX4KI.js} +1 -1
  29. package/chunks/chunk-3MX7D6QN.js +516 -0
  30. package/chunks/{chunk-CLFKOZEK.js → chunk-3XULHBC7.js} +1 -1
  31. package/chunks/chunk-4QZADKJS.js +34 -0
  32. package/chunks/{chunk-E2IY465S.js → chunk-4UDHAJFV.js} +10 -10
  33. package/chunks/{chunk-5U27BZ72.js → chunk-55SAYASQ.js} +190 -77
  34. package/chunks/chunk-5HBA2Z7V.js +28 -0
  35. package/chunks/{chunk-ZJE7NSL3.js → chunk-5M6IDOMF.js} +34 -97
  36. package/chunks/{chunk-HVY356M2.js → chunk-5SQ55XJZ.js} +3 -3
  37. package/chunks/{chunk-EAXLGSFP.js → chunk-6GRAQNTY.js} +8 -8
  38. package/chunks/{chunk-K434OPES.js → chunk-6WPJRYLZ.js} +3 -3
  39. package/chunks/{chunk-VWPVHAMO.js → chunk-7DXHIMXL.js} +5 -1
  40. package/chunks/{chunk-J4TSQ5EQ.js → chunk-7NFB7LBQ.js} +8 -1
  41. package/chunks/{chunk-5SFOMFG4.js → chunk-7R4DPSRA.js} +36 -2
  42. package/chunks/{chunk-W2UIXQDL.js → chunk-7STBROEV.js} +1 -1
  43. package/chunks/{chunk-BMGVRZN4.js → chunk-AETEZU4U.js} +1 -1
  44. package/chunks/{chunk-HPDUL5MF.js → chunk-AGZWN4WV.js} +1 -1
  45. package/chunks/{chunk-RDJSGHZE.js → chunk-ALGGS7UH.js} +1 -38
  46. package/chunks/{chunk-GWHYEOV2.js → chunk-ARKANCNX.js} +2 -1
  47. package/chunks/{chunk-34Z3U5RQ.js → chunk-AU4R7ACM.js} +518 -114
  48. package/chunks/{channel-worker-supervisor-WSOAAYUO.js → chunk-B7MFJBKE.js} +67 -35
  49. package/chunks/{chunk-524RQP4Q.js → chunk-B7NUUEXC.js} +10 -4
  50. package/chunks/{chunk-MC23XKFE.js → chunk-BLPMRN4B.js} +3 -3
  51. package/chunks/{chunk-A3XKDTSH.js → chunk-BRRKAUGR.js} +4 -4
  52. package/chunks/{chunk-W3JHSWRP.js → chunk-BUPLOPUA.js} +4 -4
  53. package/chunks/{chunk-3VAGG5T5.js → chunk-BYOOK22F.js} +121 -16
  54. package/chunks/{chunk-EMDMPBJ4.js → chunk-C47ENMZJ.js} +3 -3
  55. package/chunks/{chunk-3AHXV67U.js → chunk-C5TVXLJZ.js} +1 -1
  56. package/chunks/{chunk-X6TJ3YZV.js → chunk-CGT5TQIZ.js} +159 -57
  57. package/chunks/{chunk-4C5BQ2D2.js → chunk-CQQD7MDG.js} +82 -12
  58. package/chunks/{chunk-KKNMOEZH.js → chunk-E2UVIFI2.js} +185 -398
  59. package/chunks/{chunk-P67PFTUT.js → chunk-EPY4ZEU6.js} +4 -4
  60. package/chunks/{chunk-6PESIEKJ.js → chunk-EZSRHWGS.js} +68 -6
  61. package/chunks/{chunk-4GORAUCN.js → chunk-F333XEAU.js} +1 -1
  62. package/chunks/{chunk-YLV3XXFB.js → chunk-FBU7WRZI.js} +17 -1
  63. package/chunks/{chunk-Q6I5QHMH.js → chunk-FGTCZJOD.js} +1 -1
  64. package/chunks/{chunk-LK7KUE55.js → chunk-FSA7ERJ2.js} +1 -1
  65. package/chunks/{chunk-B4G2WOFA.js → chunk-FTU55ZZ7.js} +3 -3
  66. package/chunks/{chunk-EGYZ275M.js → chunk-GCD7PEDZ.js} +4 -4
  67. package/chunks/{chunk-SJHDYNHF.js → chunk-GDQQRV43.js} +3 -3
  68. package/chunks/{chunk-3BTRNWWD.js → chunk-GOXKNSDZ.js} +1 -64
  69. package/chunks/{chunk-GZRTYZ2R.js → chunk-GQKWT45X.js} +154 -1
  70. package/chunks/chunk-GYTKQYDB.js +23 -0
  71. package/chunks/{chunk-YVEDCAUE.js → chunk-HGT6JR3U.js} +19 -0
  72. package/chunks/{chunk-N3EMS75V.js → chunk-HKM2TEF7.js} +1268 -1233
  73. package/chunks/chunk-IDS7MSUP.js +74 -0
  74. package/chunks/{chunk-PVZM22CK.js → chunk-INKAZNYC.js} +5 -5
  75. package/chunks/{chunk-3G7CIHVV.js → chunk-IOQ75R4R.js} +4 -4
  76. package/chunks/{chunk-AURZZYMD.js → chunk-IQOOJSPR.js} +4 -4
  77. package/chunks/{chunk-AKIVHSJR.js → chunk-J4UQXGVL.js} +111 -93
  78. package/chunks/{chunk-VTU57BWZ.js → chunk-JEKOCLRN.js} +1 -1
  79. package/chunks/{chunk-B5ORX3FG.js → chunk-JGUT3LWZ.js} +6 -1
  80. package/chunks/{chunk-PDZ7MBDG.js → chunk-JIQHT2O6.js} +27 -6
  81. package/chunks/{chunk-XUOOWUZS.js → chunk-JPAH76WA.js} +1 -1
  82. package/chunks/{chunk-BAPGSC7U.js → chunk-K4LG6TVJ.js} +23 -9
  83. package/chunks/chunk-KET3M4K4.js +493 -0
  84. package/chunks/{chunk-ORBGV7NJ.js → chunk-KKRTYONM.js} +6 -4
  85. package/chunks/{chunk-SCGXUJBT.js → chunk-KPA5QGN2.js} +3 -3
  86. package/chunks/{chunk-IAVB3HM3.js → chunk-KSO42X3Z.js} +13 -0
  87. package/chunks/{chunk-4QLQE5WJ.js → chunk-L5U3ELZX.js} +1 -1
  88. package/chunks/{chunk-77XJ3SWN.js → chunk-LIUEXYPL.js} +5 -5
  89. package/chunks/{chunk-4ZVWQHEQ.js → chunk-LQIDP6K5.js} +1 -1
  90. package/chunks/{chunk-34MW4KTI.js → chunk-LT5CRUA5.js} +2 -2
  91. package/chunks/{chunk-DC5Z3DU6.js → chunk-LXLTBIDP.js} +43 -6
  92. package/chunks/{chunk-VA4KQY6V.js → chunk-MEK7CQJR.js} +3 -3
  93. package/chunks/{chunk-KHUQZZJ6.js → chunk-MO7O5722.js} +370 -36
  94. package/chunks/chunk-MR3PXB6E.js +48 -0
  95. package/chunks/{chunk-PODM4SZ4.js → chunk-MV52C5MA.js} +5 -5
  96. package/chunks/{chunk-WL4L7EZH.js → chunk-OA4JVLSA.js} +27 -1
  97. package/chunks/{chunk-X4NKFGFJ.js → chunk-OJAMDVF5.js} +0 -15
  98. package/chunks/{chunk-BZIFY5TF.js → chunk-OKAIGAYW.js} +3 -3
  99. package/chunks/{chunk-ECNYL4AA.js → chunk-OPNPGO6D.js} +1 -1
  100. package/chunks/{chunk-3SJSQXNS.js → chunk-PEJWA3QQ.js} +12 -8
  101. package/chunks/{chunk-MXL5UP4X.js → chunk-Q6TUALBE.js} +1 -1
  102. package/chunks/{chunk-QYMQSECS.js → chunk-QAW7PIHT.js} +2 -2
  103. package/chunks/{chunk-5HOMWMCL.js → chunk-QEDYXCLG.js} +4167 -1338
  104. package/chunks/chunk-QNNTA7IX.js +91 -0
  105. package/chunks/{chunk-D2GFWEXB.js → chunk-QNT5T4Q2.js} +6 -6
  106. package/chunks/{chunk-CUMMSHH6.js → chunk-R5XLNDE2.js} +1 -1
  107. package/chunks/{chunk-LUODI7WD.js → chunk-RCEEXIOO.js} +6 -4
  108. package/chunks/{chunk-WQUQVGTF.js → chunk-RQWDLG6Z.js} +183 -96
  109. package/chunks/{chunk-XYQSMABS.js → chunk-SLCVZX6F.js} +4 -4
  110. package/chunks/{chunk-CEA3E3JB.js → chunk-SPJQO2CE.js} +2 -2
  111. package/chunks/{chunk-JSG7ZSBC.js → chunk-TQBODCFD.js} +1 -1
  112. package/chunks/chunk-TV6K2FB4.js +224 -0
  113. package/chunks/{chunk-5XUCZNSY.js → chunk-TYLOFD2U.js} +1 -1
  114. package/chunks/{chunk-CNEIYWSA.js → chunk-UKWAQ7AV.js} +5 -5
  115. package/chunks/{chunk-7PU4FYLG.js → chunk-US4YQT62.js} +4 -4
  116. package/chunks/{chunk-Q4YQW46J.js → chunk-UYPPCIJQ.js} +36 -18
  117. package/chunks/{chunk-YAUFFXDS.js → chunk-UZKG7HFG.js} +12 -11
  118. package/chunks/{chunk-GWLSNBG4.js → chunk-VQLRHJKL.js} +30 -26
  119. package/chunks/{chunk-L6P4KPHR.js → chunk-VRUHAVZS.js} +59 -39
  120. package/chunks/{chunk-HKMAENAO.js → chunk-W2U4RJJU.js} +8 -6
  121. package/chunks/{chunk-ATAKVRIF.js → chunk-WDSCTLAB.js} +7 -7
  122. package/chunks/{chunk-ZLXQN2QS.js → chunk-WIEO4CWB.js} +1 -1
  123. package/chunks/{chunk-OQ4DRIOA.js → chunk-WJOC24IW.js} +2 -2
  124. package/chunks/{chunk-J3JA76CF.js → chunk-WTNZ6DXB.js} +1 -1
  125. package/chunks/{chunk-NVYTCB5Z.js → chunk-XCR44EEA.js} +14589 -13980
  126. package/chunks/{chunk-TDEXZKMT.js → chunk-Y66VOBRP.js} +2 -2
  127. package/chunks/{chunk-CC2ITGCF.js → chunk-YHN5SUIJ.js} +49 -6
  128. package/chunks/{chunk-D5SKC4YG.js → chunk-ZBAROUR6.js} +41 -13
  129. package/chunks/{chunk-U2NNEY4R.js → chunk-ZEEVKDO7.js} +1 -1
  130. package/chunks/{chunk-5VXNZWGF.js → chunk-ZLOHB76C.js} +1 -1
  131. package/chunks/{chunk-JZETEN3Z.js → chunk-ZNCLTRNU.js} +3 -3
  132. package/chunks/{chunk-TO6QE22P.js → chunk-ZP4ZZJJG.js} +26 -22
  133. package/chunks/{chunk-UOZWKTQG.js → chunk-ZPAHLFZU.js} +2 -2
  134. package/chunks/{chunk-XLA62MYB.js → chunk-ZTXZ7FM4.js} +2 -2
  135. package/chunks/{computer-use-HM37RX6A.js → computer-use-DRETHEQF.js} +26 -26
  136. package/chunks/{config-utils-NV2SVGPB.js → config-utils-XSHOMMK4.js} +3 -2
  137. package/chunks/{contextCommand-OF5SFGH2.js → contextCommand-7YJUW23J.js} +28 -28
  138. package/chunks/{create-sub-session-INX7TXJI.js → create-sub-session-EAB2U5XW.js} +2 -2
  139. package/chunks/{create-sub-session-KPRT7ECJ.js → create-sub-session-L5HOZAEL.js} +26 -26
  140. package/chunks/{cron-create-AMBKGZX6.js → cron-create-GMVKSXZT.js} +4 -4
  141. package/chunks/{cron-delete-LRU4OJYG.js → cron-delete-VDZKUAVK.js} +4 -4
  142. package/chunks/{cron-list-WVP2JXER.js → cron-list-UG7C7RAR.js} +4 -4
  143. package/chunks/{daemon-ARHTSTXP.js → daemon-MI5HF26N.js} +603 -62
  144. package/chunks/{daemon-status-provider-B2UTZUJ2.js → daemon-status-provider-2D47GOJT.js} +36 -36
  145. package/chunks/{de-EBJN3OVN.js → de-2VCDRDGU.js} +4 -1
  146. package/chunks/{dist-WKPOYU7O.js → dist-IEXZC5ZR.js} +1 -1
  147. package/chunks/{dist-XKU3ABM5.js → dist-PQK4GCKA.js} +1 -1
  148. package/chunks/{dist-VHV4EVHG.js → dist-U75JK3PF.js} +1 -1
  149. package/chunks/{dist-R7LN5AZE.js → dist-VXO7QBON.js} +2 -2
  150. package/chunks/{dist-PNVLTKTL.js → dist-WH4TZSH3.js} +158 -4
  151. package/chunks/{dist-QYCAEZIT.js → dist-X2ABIW5R.js} +5 -1
  152. package/chunks/{earlyInputCapture-3VTHRC4C.js → earlyInputCapture-OJQPGCSX.js} +27 -27
  153. package/chunks/{edit-VNI4NJ73.js → edit-LZOAFLT2.js} +27 -27
  154. package/chunks/{en-5A6LA7RK.js → en-NJCA3TAA.js} +5 -1
  155. package/chunks/{enter-worktree-IYYBKPZT.js → enter-worktree-VL2TCGI5.js} +26 -26
  156. package/chunks/{enterPlanMode-GMC5FD5A.js → enterPlanMode-QGHBHZWI.js} +42 -27
  157. package/chunks/{environment-5HSSRMJ7.js → environment-2G5IC2EU.js} +30 -29
  158. package/chunks/{errors-HMFCJJ7P.js → errors-7QNQDNI2.js} +28 -28
  159. package/chunks/{exit-worktree-6BHCMP5G.js → exit-worktree-5V3X2MRH.js} +26 -26
  160. package/chunks/{exitPlanMode-CJW7G74W.js → exitPlanMode-XDPNQB4H.js} +26 -26
  161. package/chunks/{fast-path-FRDMDVHG.js → fast-path-NNSD4BVZ.js} +3 -3
  162. package/chunks/{fast-path-settings-5QZE64HR.js → fast-path-settings-DAOVFADC.js} +6 -4
  163. package/chunks/{fr-YXRABLYZ.js → fr-5F3E7WKD.js} +4 -1
  164. package/chunks/{gemini-QYSEGHAX.js → gemini-RKDUWGXJ.js} +139 -76
  165. package/chunks/{geminiContentGenerator-J7YNZDYP.js → geminiContentGenerator-V7QGHXVF.js} +4 -4
  166. package/chunks/{glob-BRCSGFOT.js → glob-ZNQUUOTS.js} +28 -27
  167. package/chunks/{grep-EE2DUOSY.js → grep-NYIGLM7R.js} +26 -26
  168. package/chunks/{handleAutoUpdate-5NWTLR67.js → handleAutoUpdate-UBQ4IQXY.js} +30 -30
  169. package/chunks/{i18n-BASPENNQ.js → i18n-5T3S6SGP.js} +27 -27
  170. package/chunks/initializer-TJED246Q.js +73 -0
  171. package/chunks/{installationInfo-W6V4DT7W.js → installationInfo-OTEU7FKR.js} +27 -27
  172. package/chunks/{ja-7VQOANVG.js → ja-66J6B53L.js} +5 -2
  173. package/chunks/{keychain-token-storage-VKUBNCP4.js → keychain-token-storage-AU22CQI6.js} +2 -2
  174. package/chunks/list-6OHS7MFJ.js +76 -0
  175. package/chunks/loadedSettingsAdapter-5456XIYG.js +70 -0
  176. package/chunks/{loop-wakeup-WX7BLWWP.js → loop-wakeup-C4EXLYVA.js} +5 -5
  177. package/chunks/{ls-ZB2BDP6T.js → ls-2OUTG3IZ.js} +4 -4
  178. package/chunks/{lsp-V5DN4YUJ.js → lsp-4VMNXZW6.js} +2 -2
  179. package/chunks/mcp-NOWRPFKY.js +70 -0
  180. package/chunks/{monitor-X5Z3H4FY.js → monitor-CVDZO3IZ.js} +26 -26
  181. package/chunks/nonInteractiveCli-UGI7Y43E.js +129 -0
  182. package/chunks/{notebook-edit-TRM2QIJK.js → notebook-edit-NWIA5TEJ.js} +27 -27
  183. package/chunks/{openaiContentGenerator-AAYONPMF.js → openaiContentGenerator-KJONBBJO.js} +12 -12
  184. package/chunks/{pidfile-D4I4FTVA.js → pidfile-L3TGXMTF.js} +27 -27
  185. package/chunks/{pt-2C6YCSHL.js → pt-TFZO5Y6T.js} +4 -1
  186. package/chunks/{qwenContentGenerator-K3HZ2H76.js → qwenContentGenerator-GXUZB5SJ.js} +28 -28
  187. package/chunks/{qwenOAuth2-PJQ27GSO.js → qwenOAuth2-LEYOU7RU.js} +5 -5
  188. package/chunks/{read-file-7SZSIZOL.js → read-file-I4OUZXTA.js} +8 -8
  189. package/chunks/{read-mcp-resource-SM5DA4RG.js → read-mcp-resource-JUFHCKSC.js} +2 -2
  190. package/chunks/{record-artifact-4XKZX3UW.js → record-artifact-DWSMBXYY.js} +2 -2
  191. package/chunks/{ripGrep-YNPFVCZP.js → ripGrep-XLCXANIO.js} +26 -26
  192. package/chunks/{ru-6BBHVZZV.js → ru-4L4LFHIT.js} +4 -1
  193. package/chunks/{run-qwen-serve-DMWV5N6L.js → run-qwen-serve-R6SBP6YS.js} +1232 -371
  194. package/chunks/{runtime-57WWRVHN.js → runtime-U3S33YUQ.js} +42 -37
  195. package/chunks/{scheduler-YBLGWSIL.js → scheduler-65QCIELP.js} +26 -26
  196. package/chunks/{send-message-F36YD6KQ.js → send-message-JZ752NWD.js} +3 -3
  197. package/chunks/{serve-R63NK3CV.js → serve-PSPOFCOD.js} +36 -34
  198. package/chunks/{server-Z22GCAXP.js → server-6P5ZB56O.js} +5561 -1377
  199. package/chunks/{session-HKAAI7BN.js → session-ILLOYW75.js} +298 -98
  200. package/chunks/{settings-3EYN66GK.js → settings-RMUJGYIK.js} +34 -32
  201. package/chunks/{shell-6YXINTWJ.js → shell-65GTAN5Q.js} +28 -26
  202. package/chunks/{skill-OO57P4X6.js → skill-L76U3YCX.js} +10 -10
  203. package/chunks/{spawnChannel-JD7G4VU6.js → spawnChannel-LXIGZ4EM.js} +28 -28
  204. package/chunks/{src-G5UPLZSZ.js → src-DWZT2X3Z.js} +87 -27
  205. package/chunks/{standalone-update-OB7F4JPZ.js → standalone-update-F6SRKPSD.js} +28 -28
  206. package/chunks/{startInteractiveUI-73EGLIIH.js → startInteractiveUI-UEJQP7S3.js} +730 -397
  207. package/chunks/{syntheticOutput-34H7LGA7.js → syntheticOutput-LELY6HAI.js} +3 -3
  208. package/chunks/{task-create-FRGOEIKY.js → task-create-QY3BIMNM.js} +8 -7
  209. package/chunks/{task-list-OW766BXZ.js → task-list-D2U47WBJ.js} +6 -5
  210. package/chunks/{task-stop-SIR5SNBN.js → task-stop-N6E5IYLH.js} +2 -2
  211. package/chunks/{task-update-YRA532MT.js → task-update-HJCQILLB.js} +8 -7
  212. package/chunks/{team-create-PECAAPXK.js → team-create-4QBZVOGN.js} +26 -26
  213. package/chunks/{team-delete-6OSECUZC.js → team-delete-RCWYDRF4.js} +6 -5
  214. package/chunks/{team-plan-approval-L6F434RY.js → team-plan-approval-BK4CIOFV.js} +26 -26
  215. package/chunks/{theme-manager-ZG4RY6GU.js → theme-manager-5F2IGW6E.js} +27 -27
  216. package/chunks/{todoWrite-YDMFEV4G.js → todoWrite-FKCGATPJ.js} +4 -4
  217. package/chunks/{tool-search-THVIWAPB.js → tool-search-J2ESO33H.js} +9 -9
  218. package/chunks/{total-session-admission-JOKNRIIA.js → total-session-admission-U4LZ5QTQ.js} +33 -33
  219. package/chunks/tree-sitter-YJVE2ZUK.js +2980 -0
  220. package/chunks/{trustedFolders-EOGT3UVS.js → trustedFolders-TIDOXCJA.js} +29 -28
  221. package/chunks/types-ML3TRJQ5.js +16 -0
  222. package/chunks/{updateCheck-2KYEYVSB.js → updateCheck-VRVR26FI.js} +29 -29
  223. package/chunks/{validateNonInterActiveAuth-EN6ZQSR2.js → validateNonInterActiveAuth-UGKYAVLG.js} +75 -71
  224. package/chunks/{version-3UEA6ZIR.js → version-7HR6VZAT.js} +1 -1
  225. package/chunks/{web-fetch-PAAEQKBW.js → web-fetch-QGODBB6S.js} +5 -5
  226. package/chunks/{workflow-QCCCMYP3.js → workflow-EDQAITEW.js} +27 -27
  227. package/chunks/workspace-providers-status-27UGAK77.js +73 -0
  228. package/chunks/workspace-registration-store-5H7577YQ.js +27 -0
  229. package/chunks/{workspace-registry-24QFLQYH.js → workspace-registry-F3THGAXC.js} +33 -33
  230. package/chunks/workspace-service-4JSAQAHP.js +86 -0
  231. package/chunks/workspace-skills-status-5JTWZHZU.js +72 -0
  232. package/chunks/{write-file-I3MUDRIE.js → write-file-UY6XCZZM.js} +27 -27
  233. package/chunks/{zh-RQE7I22T.js → zh-FSAH32JB.js} +6 -2
  234. package/chunks/{zh-TW-TBPQGHKH.js → zh-TW-D2YIE2LO.js} +6 -2
  235. package/cli.js +11 -11
  236. package/locales/ca.js +6 -0
  237. package/locales/de.js +6 -0
  238. package/locales/en.js +8 -0
  239. package/locales/fr.js +6 -0
  240. package/locales/ja.js +7 -1
  241. package/locales/pt.js +6 -0
  242. package/locales/ru.js +6 -0
  243. package/locales/zh-TW.js +9 -1
  244. package/locales/zh.js +9 -1
  245. package/package.json +3 -3
  246. package/web-shell/assets/{arc-C15hqnJe.js → arc-CObt_TK9.js} +1 -1
  247. package/web-shell/assets/{architectureDiagram-3BPJPVTR-B-At37J_.js → architectureDiagram-3BPJPVTR-B7aiJ6CK.js} +1 -1
  248. package/web-shell/assets/{blockDiagram-GPEHLZMM-B51wjcU7.js → blockDiagram-GPEHLZMM-BSK2CrID.js} +1 -1
  249. package/web-shell/assets/{c4Diagram-AAUBKEIU-xBK7BAwN.js → c4Diagram-AAUBKEIU-Bhx2p7T_.js} +1 -1
  250. package/web-shell/assets/channel-BBaLq8MV.js +1 -0
  251. package/web-shell/assets/{chunk-2J33WTMH-xKmLg7Yr.js → chunk-2J33WTMH-c3Hzv0Ni.js} +1 -1
  252. package/web-shell/assets/{chunk-4BX2VUAB-BfkdKZCM.js → chunk-4BX2VUAB-DNXaK7ZF.js} +1 -1
  253. package/web-shell/assets/{chunk-55IACEB6-DT713sZf.js → chunk-55IACEB6-BXjvDj_i.js} +1 -1
  254. package/web-shell/assets/{chunk-727SXJPM-CYEeAamv.js → chunk-727SXJPM-B6CaKFp-.js} +1 -1
  255. package/web-shell/assets/{chunk-AQP2D5EJ-DBN-5CFd.js → chunk-AQP2D5EJ-tij6Mz9B.js} +1 -1
  256. package/web-shell/assets/{chunk-FMBD7UC4-CGZKk4PH.js → chunk-FMBD7UC4-DdlsGk2h.js} +1 -1
  257. package/web-shell/assets/{chunk-ND2GUHAM-qQbC2O-y.js → chunk-ND2GUHAM-CdgGZSHE.js} +1 -1
  258. package/web-shell/assets/{chunk-QZHKN3VN-B3zeXwsX.js → chunk-QZHKN3VN-D8oJn1C0.js} +1 -1
  259. package/web-shell/assets/classDiagram-4FO5ZUOK-B2ZK6sWE.js +1 -0
  260. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B2ZK6sWE.js +1 -0
  261. package/web-shell/assets/{cose-bilkent-S5V4N54A-BKsl1Wvy.js → cose-bilkent-S5V4N54A-BUVmNp1Y.js} +1 -1
  262. package/web-shell/assets/{dagre-BM42HDAG-CuuuLiBK.js → dagre-BM42HDAG-CO4q310O.js} +1 -1
  263. package/web-shell/assets/{diagram-2AECGRRQ-CG4_eKiV.js → diagram-2AECGRRQ-C6gFvWkg.js} +1 -1
  264. package/web-shell/assets/{diagram-5GNKFQAL-DSFTmZB1.js → diagram-5GNKFQAL-NG2I_c0h.js} +1 -1
  265. package/web-shell/assets/{diagram-KO2AKTUF-BxwlEHn8.js → diagram-KO2AKTUF-DFKMS6I9.js} +1 -1
  266. package/web-shell/assets/{diagram-LMA3HP47-BUAg-jMG.js → diagram-LMA3HP47-BiElfsfP.js} +1 -1
  267. package/web-shell/assets/{diagram-OG6HWLK6-BFM2jlO5.js → diagram-OG6HWLK6-zLWDYUqu.js} +1 -1
  268. package/web-shell/assets/{erDiagram-TEJ5UH35-CerbFPa2.js → erDiagram-TEJ5UH35-DoVaazhK.js} +1 -1
  269. package/web-shell/assets/{flowDiagram-I6XJVG4X-BNeZz5Zz.js → flowDiagram-I6XJVG4X-0RLz-mXg.js} +1 -1
  270. package/web-shell/assets/{ganttDiagram-6RSMTGT7-CTOogLaM.js → ganttDiagram-6RSMTGT7-C-eX1A39.js} +3 -3
  271. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-yxSFkXPj.js → gitGraphDiagram-PVQCEYII-BauQu1Bu.js} +1 -1
  272. package/web-shell/assets/index-3uAT3ehO.css +5 -0
  273. package/web-shell/assets/index-CVHa2QH7.js +3 -0
  274. package/web-shell/assets/index-D1KigWat.js +1128 -0
  275. package/web-shell/assets/{infoDiagram-5YYISTIA-DYdAUevm.js → infoDiagram-5YYISTIA-C800cFvZ.js} +1 -1
  276. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-CNuijZ90.js → ishikawaDiagram-YF4QCWOH-CFK1Jk6e.js} +1 -1
  277. package/web-shell/assets/{journeyDiagram-JHISSGLW-BTyiuonf.js → journeyDiagram-JHISSGLW-Cjx0WAC4.js} +1 -1
  278. package/web-shell/assets/{kanban-definition-UN3LZRKU-DPJo0Uow.js → kanban-definition-UN3LZRKU-DWMDHbiv.js} +1 -1
  279. package/web-shell/assets/{linear-C8wKCbIC.js → linear-vpW4FrZs.js} +1 -1
  280. package/web-shell/assets/{mermaid.core-BV5o7nW5.js → mermaid.core-DsHRUnDd.js} +5 -5
  281. package/web-shell/assets/{mindmap-definition-RKZ34NQL-BtLcBmSt.js → mindmap-definition-RKZ34NQL-D3Eb1cAK.js} +1 -1
  282. package/web-shell/assets/{pieDiagram-4H26LBE5-C22J7A9Q.js → pieDiagram-4H26LBE5-Cr2-6sWt.js} +1 -1
  283. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DSL10wiL.js → quadrantDiagram-W4KKPZXB-Yd7yUgVp.js} +1 -1
  284. package/web-shell/assets/{requirementDiagram-4Y6WPE33-feqh0UpX.js → requirementDiagram-4Y6WPE33-BE6SprL8.js} +1 -1
  285. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-Z4OVaWaZ.js → sankeyDiagram-5OEKKPKP-DzkuMK8d.js} +1 -1
  286. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DIluIcb1.js → sequenceDiagram-3UESZ5HK-wD9A3Dhi.js} +1 -1
  287. package/web-shell/assets/{stateDiagram-AJRCARHV-hlhnuoYH.js → stateDiagram-AJRCARHV-BOp5q1Nm.js} +1 -1
  288. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDsWUmPG.js +1 -0
  289. package/web-shell/assets/{timeline-definition-PNZ67QCA-Dqf_CqgL.js → timeline-definition-PNZ67QCA-Btc08sUA.js} +1 -1
  290. package/web-shell/assets/{vennDiagram-CIIHVFJN-DD2QhaTX.js → vennDiagram-CIIHVFJN-Bv6upsOA.js} +1 -1
  291. package/web-shell/assets/{wardley-L42UT6IY-B2uI85U_.js → wardley-L42UT6IY-D-XamnCK.js} +1 -1
  292. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-U1aJX71g.js → wardleyDiagram-YWT4CUSO-BbP_5Wfp.js} +1 -1
  293. package/web-shell/assets/{xychartDiagram-2RQKCTM6-C0ODHUee.js → xychartDiagram-2RQKCTM6-CO5Hh28b.js} +1 -1
  294. package/web-shell/index.html +2 -2
  295. package/chunks/chunk-CU3L64TP.js +0 -93
  296. package/chunks/initializer-UTQ7FB7G.js +0 -71
  297. package/chunks/list-DEICDL65.js +0 -74
  298. package/chunks/loadedSettingsAdapter-NOWEGDYJ.js +0 -68
  299. package/chunks/mcp-L5JXEEIA.js +0 -68
  300. package/chunks/nonInteractiveCli-LS2K4X6A.js +0 -124
  301. package/chunks/types-JNKGKUJT.js +0 -12
  302. package/chunks/workspace-providers-status-4KMGETOJ.js +0 -71
  303. package/chunks/workspace-service-RVLBMSMZ.js +0 -80
  304. package/chunks/workspace-skills-status-DS4ZVFTO.js +0 -70
  305. package/web-shell/assets/channel-CLiQD50f.js +0 -1
  306. package/web-shell/assets/classDiagram-4FO5ZUOK-D3sYqow5.js +0 -1
  307. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-D3sYqow5.js +0 -1
  308. package/web-shell/assets/index-D5IWFVk1.css +0 -5
  309. package/web-shell/assets/index-KFJRDhX7.js +0 -783
  310. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BhQMwgpl.js +0 -1
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  formatHistoryGapNotice,
5
5
  indexGapsByChild
6
- } from "./chunk-U2NNEY4R.js";
6
+ } from "./chunk-ZEEVKDO7.js";
7
7
  import {
8
8
  require_undici
9
9
  } from "./chunk-L573Z2QZ.js";
@@ -12,13 +12,16 @@ import {
12
12
  } from "./chunk-YUZI3WAC.js";
13
13
  import {
14
14
  getActiveGoal
15
- } from "./chunk-NVYTCB5Z.js";
15
+ } from "./chunk-XCR44EEA.js";
16
+ import {
17
+ apiActivityTracker
18
+ } from "./chunk-BYOOK22F.js";
16
19
  import {
17
20
  ToolNames
18
- } from "./chunk-B5ORX3FG.js";
21
+ } from "./chunk-JGUT3LWZ.js";
19
22
  import {
20
23
  createDebugLogger
21
- } from "./chunk-J4TSQ5EQ.js";
24
+ } from "./chunk-7NFB7LBQ.js";
22
25
  import {
23
26
  init_esbuild_shims
24
27
  } from "./chunk-5O2XNYP6.js";
@@ -27,1435 +30,1467 @@ import {
27
30
  __toESM
28
31
  } from "./chunk-J2S4EL5Y.js";
29
32
 
30
- // packages/cli/src/acp-integration/session/HistoryReplayer.ts
31
- init_esbuild_shims();
32
-
33
- // packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts
33
+ // packages/cli/src/services/setup-github.ts
34
34
  init_esbuild_shims();
35
+ var import_undici2 = __toESM(require_undici(), 1);
36
+ import { promises as fsp } from "node:fs";
37
+ import * as path from "node:path";
35
38
 
36
- // packages/cli/src/acp-integration/session/emitters/BaseEmitter.ts
39
+ // packages/cli/src/utils/gitUtils.ts
37
40
  init_esbuild_shims();
38
- var BaseEmitter = class {
39
- constructor(ctx) {
40
- this.ctx = ctx;
41
+ var import_undici = __toESM(require_undici(), 1);
42
+ import * as childProcess from "node:child_process";
43
+ var debugLogger = createDebugLogger("GIT");
44
+ async function runGit(args, opts = {}) {
45
+ return await new Promise((resolve2, reject) => {
46
+ childProcess.execFile(
47
+ "git",
48
+ args,
49
+ {
50
+ encoding: "utf-8",
51
+ ...opts.cwd ? { cwd: opts.cwd } : {}
52
+ },
53
+ (err, stdout) => {
54
+ if (err) {
55
+ reject(err);
56
+ return;
57
+ }
58
+ resolve2(String(stdout ?? "").trim());
59
+ }
60
+ );
61
+ });
62
+ }
63
+ __name(runGit, "runGit");
64
+ var isGitHubRepositoryAsync = /* @__PURE__ */ __name(async (opts = {}) => {
65
+ try {
66
+ const remotes = await runGit(["remote", "-v"], opts);
67
+ return remotes.split("\n").some((line) => {
68
+ const remoteUrl = line.trim().split(/\s+/)[1];
69
+ return remoteUrl ? isGitHubRemoteUrl(remoteUrl) : false;
70
+ });
71
+ } catch (_error) {
72
+ debugLogger.debug(`Failed to get git remote:`, _error);
73
+ return false;
41
74
  }
42
- static {
43
- __name(this, "BaseEmitter");
75
+ }, "isGitHubRepositoryAsync");
76
+ function isGitHubRemoteUrl(remoteUrl) {
77
+ if (remoteUrl.startsWith("git@github.com:")) {
78
+ return true;
44
79
  }
45
- /**
46
- * Converts an ISO timestamp string or epoch ms to epoch ms number.
47
- * Returns undefined if the input is not a valid timestamp.
48
- */
49
- static toEpochMs(ts) {
50
- if (typeof ts === "number") {
51
- return Number.isFinite(ts) ? ts : void 0;
80
+ if (remoteUrl.startsWith("git@")) {
81
+ return false;
82
+ }
83
+ try {
84
+ return new URL(remoteUrl).hostname === "github.com";
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ __name(isGitHubRemoteUrl, "isGitHubRemoteUrl");
90
+ var getGitRepoRootAsync = /* @__PURE__ */ __name(async (opts = {}) => {
91
+ const gitRepoRoot = await runGit(["rev-parse", "--show-toplevel"], opts);
92
+ if (!gitRepoRoot) {
93
+ throw new Error(`Git repo returned empty value`);
94
+ }
95
+ return gitRepoRoot;
96
+ }, "getGitRepoRootAsync");
97
+ var getLatestGitHubRelease = /* @__PURE__ */ __name(async (proxy) => {
98
+ try {
99
+ const controller = new AbortController();
100
+ const endpoint = `https://api.github.com/repos/QwenLM/qwen-code-action/releases/latest`;
101
+ const response = await fetch(endpoint, {
102
+ method: "GET",
103
+ headers: {
104
+ Accept: "application/vnd.github+json",
105
+ "Content-Type": "application/json",
106
+ "X-GitHub-Api-Version": "2022-11-28"
107
+ },
108
+ dispatcher: proxy ? new import_undici.ProxyAgent(proxy) : void 0,
109
+ signal: AbortSignal.any([AbortSignal.timeout(3e4), controller.signal])
110
+ });
111
+ if (!response.ok) {
112
+ throw new Error(
113
+ `Invalid response code: ${response.status} - ${response.statusText}`
114
+ );
52
115
  }
53
- if (typeof ts === "string") {
54
- const ms = new Date(ts).getTime();
55
- return Number.isFinite(ms) ? ms : void 0;
116
+ const releaseTag = (await response.json()).tag_name;
117
+ if (!releaseTag) {
118
+ throw new Error(`Response did not include tag_name field`);
56
119
  }
57
- return void 0;
120
+ return releaseTag;
121
+ } catch (_error) {
122
+ debugLogger.debug(
123
+ `Failed to determine latest qwen-code-action release:`,
124
+ _error
125
+ );
126
+ throw new Error(
127
+ `Unable to determine the latest qwen-code-action release on GitHub.`
128
+ );
58
129
  }
59
- /**
60
- * Sends a session update to the ACP client.
61
- * If a message rewriter is configured, updates pass through it first
62
- * (original messages are sent as-is, rewritten versions are appended).
63
- */
64
- async sendUpdate(update) {
65
- if (this.ctx.messageRewriter) {
66
- return this.ctx.messageRewriter.interceptUpdate(update);
67
- }
68
- return this.ctx.sendUpdate(update);
130
+ }, "getLatestGitHubRelease");
131
+ async function getGitHubRepoInfoAsync(opts = {}) {
132
+ return parseGitHubRepoInfo(
133
+ await runGit(["remote", "get-url", "origin"], opts)
134
+ );
135
+ }
136
+ __name(getGitHubRepoInfoAsync, "getGitHubRepoInfoAsync");
137
+ function parseGitHubRepoInfo(remoteUrl) {
138
+ let urlToParse = remoteUrl;
139
+ if (remoteUrl.startsWith("git@github.com:")) {
140
+ urlToParse = remoteUrl.replace("git@github.com:", "");
141
+ } else if (remoteUrl.startsWith("git@")) {
142
+ throw new Error(
143
+ `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
144
+ );
69
145
  }
70
- /**
71
- * Gets the session configuration.
72
- */
73
- get config() {
74
- return this.ctx.config;
146
+ let parsedUrl;
147
+ try {
148
+ parsedUrl = new URL(urlToParse, "https://github.com");
149
+ } catch {
150
+ throw new Error(
151
+ `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
152
+ );
75
153
  }
76
- /**
77
- * Gets the session ID.
78
- */
79
- get sessionId() {
80
- return this.ctx.sessionId;
154
+ if (parsedUrl.hostname !== "github.com") {
155
+ throw new Error(
156
+ `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
157
+ );
81
158
  }
82
- };
159
+ const parts = parsedUrl.pathname.split("/").filter((part) => part !== "");
160
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
161
+ throw new Error(
162
+ `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
163
+ );
164
+ }
165
+ return { owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
166
+ }
167
+ __name(parseGitHubRepoInfo, "parseGitHubRepoInfo");
83
168
 
84
- // packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts
85
- var MessageEmitter = class extends BaseEmitter {
169
+ // packages/cli/src/services/setup-github.ts
170
+ var debugLogger2 = createDebugLogger("SETUP_GITHUB");
171
+ var GITHUB_WORKFLOW_PATHS = [
172
+ "qwen-dispatch/qwen-dispatch.yml",
173
+ "qwen-assistant/qwen-invoke.yml",
174
+ "issue-triage/qwen-triage.yml",
175
+ "issue-triage/qwen-scheduled-triage.yml",
176
+ "pr-review/qwen-review.yml"
177
+ ];
178
+ var GITIGNORE_ENTRIES = [".qwen/", "gha-creds-*.json"];
179
+ var MAX_WORKFLOW_DOWNLOAD_BYTES = 5 * 1024 * 1024;
180
+ var SetupGithubError = class extends Error {
86
181
  static {
87
- __name(this, "MessageEmitter");
182
+ __name(this, "SetupGithubError");
88
183
  }
89
- /**
90
- * Emits a StopHookLoop event when Stop hooks create a loop.
91
- * This informs the client that Stop hooks have been executed multiple times.
92
- *
93
- * @param iterationCount - The current iteration count
94
- * @param reasons - Array of reasons from each Stop hook execution
95
- * @param stopHookCount - Number of Stop hooks that were executed
96
- */
97
- async emitStopHookLoop(iterationCount, reasons, stopHookCount) {
98
- const activeGoal = getActiveGoal(this.sessionId);
99
- await this.sendUpdate({
100
- sessionUpdate: "agent_message_chunk",
101
- content: { type: "text", text: "" },
102
- _meta: {
103
- stopHookLoop: {
104
- iterationCount,
105
- reasons,
106
- stopHookCount,
107
- ...activeGoal ? {
108
- goal: {
109
- condition: activeGoal.condition,
110
- iterations: activeGoal.iterations,
111
- setAt: activeGoal.setAt,
112
- lastReason: activeGoal.lastReason
113
- }
114
- } : {}
115
- }
116
- }
117
- });
184
+ code;
185
+ status;
186
+ partial;
187
+ partialResult;
188
+ constructor(code, message, status, partialResult) {
189
+ super(message);
190
+ this.name = "SetupGithubError";
191
+ this.code = code;
192
+ this.status = status;
193
+ this.partial = partialResult !== void 0;
194
+ this.partialResult = partialResult;
118
195
  }
119
- async emitGoalTerminal(event) {
120
- await this.sendUpdate({
121
- sessionUpdate: "agent_message_chunk",
122
- content: { type: "text", text: "" },
123
- _meta: {
124
- goalTerminal: event
125
- }
196
+ };
197
+ var nodeFileOps = {
198
+ assertCanWrite() {
199
+ },
200
+ async ensureWorkflowDirectory(gitRepoRoot) {
201
+ await fsp.mkdir(path.join(gitRepoRoot, ".github", "workflows"), {
202
+ recursive: true
126
203
  });
127
- }
128
- async emitGoalStatus(status) {
129
- await this.sendUpdate({
130
- sessionUpdate: "agent_message_chunk",
131
- content: { type: "text", text: "" },
132
- _meta: {
133
- goalStatus: status
204
+ },
205
+ async writeTextFile(gitRepoRoot, relativePath, content) {
206
+ const target = path.join(gitRepoRoot, relativePath);
207
+ await fsp.writeFile(target, content, { mode: 420 });
208
+ return { sizeBytes: Buffer.byteLength(content, "utf8") };
209
+ },
210
+ async readTextFile(gitRepoRoot, relativePath) {
211
+ try {
212
+ return await fsp.readFile(path.join(gitRepoRoot, relativePath), "utf8");
213
+ } catch (error) {
214
+ if (error.code === "ENOENT") {
215
+ return void 0;
134
216
  }
135
- });
217
+ throw error;
218
+ }
136
219
  }
137
- /**
138
- * Emits a user message chunk.
139
- *
140
- * @param text - The user message text content
141
- * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
142
- */
143
- async emitUserMessage(text, timestamp, options = {}) {
144
- const epochMs = BaseEmitter.toEpochMs(timestamp);
145
- const _meta = {
146
- ...epochMs != null ? { timestamp: epochMs } : {},
147
- ...options.source ? { source: options.source } : {}
148
- };
149
- await this.sendUpdate({
150
- sessionUpdate: "user_message_chunk",
151
- content: { type: "text", text },
152
- ...Object.keys(_meta).length > 0 ? { _meta } : {}
153
- });
154
- }
155
- /**
156
- * Emits an agent thought chunk.
157
- *
158
- * @param text - The thought text content
159
- * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
160
- */
161
- async emitAgentThought(text, timestamp, subagentMeta) {
162
- const _meta = this.buildChunkMeta(
163
- BaseEmitter.toEpochMs(timestamp),
164
- subagentMeta
220
+ };
221
+ async function setupGithub(options = {}) {
222
+ const cwd = options.cwd ?? process.cwd();
223
+ const fileOps = options.fileOps ?? nodeFileOps;
224
+ if (!await isGitHubRepositoryAsync({ cwd })) {
225
+ throw new SetupGithubError(
226
+ "github_repository_not_found",
227
+ "Unable to determine the GitHub repository. /setup-github must be run from a git repository.",
228
+ 400
165
229
  );
166
- await this.sendUpdate({
167
- sessionUpdate: "agent_thought_chunk",
168
- content: { type: "text", text },
169
- ..._meta ? { _meta } : {}
170
- });
171
230
  }
172
- /**
173
- * Emits an agent message chunk.
174
- *
175
- * @param text - The agent message text content
176
- * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
177
- */
178
- async emitAgentMessage(text, timestamp, subagentMeta) {
179
- const _meta = this.buildChunkMeta(
180
- BaseEmitter.toEpochMs(timestamp),
181
- subagentMeta
231
+ let gitRepoRoot;
232
+ try {
233
+ gitRepoRoot = await getGitRepoRootAsync({ cwd });
234
+ } catch (error) {
235
+ debugLogger2.debug("Failed to get git repo root:", error);
236
+ throw new SetupGithubError(
237
+ "github_repository_not_found",
238
+ "Unable to determine the GitHub repository. /setup-github must be run from a git repository.",
239
+ 400
182
240
  );
183
- await this.sendUpdate({
184
- sessionUpdate: "agent_message_chunk",
185
- content: { type: "text", text },
186
- ..._meta ? { _meta } : {}
187
- });
188
241
  }
189
- /**
190
- * Emits usage metadata.
191
- */
192
- async emitUsageMetadata(usageMetadata, text = "", durationMs, subagentMeta) {
193
- const usage = {
194
- inputTokens: usageMetadata.promptTokenCount ?? 0,
195
- outputTokens: usageMetadata.candidatesTokenCount ?? 0,
196
- totalTokens: usageMetadata.totalTokenCount ?? 0,
197
- thoughtTokens: usageMetadata.thoughtsTokenCount,
198
- cachedReadTokens: usageMetadata.cachedContentTokenCount
199
- };
200
- const cumulative = this.ctx.cumulativeUsage;
201
- if (cumulative) {
202
- const addFinite = /* @__PURE__ */ __name((total, value) => typeof value === "number" && Number.isFinite(value) ? total + value : total, "addFinite");
203
- cumulative.promptTokens = addFinite(
204
- cumulative.promptTokens,
205
- usage.inputTokens
242
+ if (options.workspaceRoot) {
243
+ const [gitRootReal, workspaceReal] = await Promise.all([
244
+ realpathOrResolve(gitRepoRoot),
245
+ realpathOrResolve(options.workspaceRoot)
246
+ ]);
247
+ if (gitRootReal !== workspaceReal) {
248
+ throw new SetupGithubError(
249
+ "github_git_root_mismatch",
250
+ "The Git repository root must match the daemon workspace root.",
251
+ 400
206
252
  );
207
- cumulative.candidateTokens = addFinite(
208
- cumulative.candidateTokens,
209
- usage.outputTokens
253
+ }
254
+ }
255
+ fileOps.assertCanWrite?.();
256
+ let releaseTag;
257
+ try {
258
+ releaseTag = await getLatestGitHubRelease(options.proxy);
259
+ } catch (error) {
260
+ writeStderrLine(
261
+ `qwen setup-github: failed to determine latest qwen-code-action release: ${error instanceof Error ? error.message : String(error)}`
262
+ );
263
+ debugLogger2.debug(
264
+ "Failed to determine latest qwen-code-action release:",
265
+ error
266
+ );
267
+ throw new SetupGithubError(
268
+ "github_release_lookup_failed",
269
+ "Unable to determine the latest qwen-code-action release on GitHub.",
270
+ 502
271
+ );
272
+ }
273
+ const readmeUrl = `https://github.com/QwenLM/qwen-code-action/blob/${releaseTag}/README.md#quick-start`;
274
+ const secretsUrl = await resolveSecretsUrl(cwd);
275
+ const downloads = await downloadWorkflows({
276
+ releaseTag,
277
+ proxy: options.proxy,
278
+ abortSignal: options.abortSignal,
279
+ fetchImpl: options.fetchImpl ?? fetch
280
+ });
281
+ const result = {
282
+ kind: "github_setup",
283
+ workspaceCwd: options.workspaceRoot ?? gitRepoRoot,
284
+ gitRepoRoot,
285
+ releaseTag,
286
+ readmeUrl,
287
+ ...secretsUrl ? { secretsUrl } : {},
288
+ workflows: [],
289
+ gitignore: { path: ".gitignore", status: "skipped" },
290
+ warnings: []
291
+ };
292
+ result.gitignore = await updateGitignore(gitRepoRoot, fileOps);
293
+ if (result.gitignore.status === "failed") {
294
+ result.warnings.push("Failed to update .gitignore.");
295
+ }
296
+ try {
297
+ await fileOps.ensureWorkflowDirectory(gitRepoRoot);
298
+ for (const workflow of downloads) {
299
+ const relativePath = path.posix.join(
300
+ ".github",
301
+ "workflows",
302
+ path.posix.basename(workflow.sourcePath)
210
303
  );
211
- cumulative.cachedTokens = addFinite(
212
- cumulative.cachedTokens,
213
- usage.cachedReadTokens
304
+ try {
305
+ const write = await fileOps.writeTextFile(
306
+ gitRepoRoot,
307
+ relativePath,
308
+ workflow.content
309
+ );
310
+ result.workflows.push({
311
+ sourcePath: workflow.sourcePath,
312
+ path: relativePath,
313
+ status: "written",
314
+ sizeBytes: write.sizeBytes
315
+ });
316
+ } catch (error) {
317
+ result.partial = true;
318
+ result.workflows.push({
319
+ sourcePath: workflow.sourcePath,
320
+ path: relativePath,
321
+ status: "failed",
322
+ error: error instanceof Error ? error.message : String(error)
323
+ });
324
+ throw new SetupGithubError(
325
+ "github_workflow_write_failed",
326
+ `Unable to write ${relativePath}.`,
327
+ 500,
328
+ result
329
+ );
330
+ }
331
+ }
332
+ } catch (error) {
333
+ if (error instanceof SetupGithubError) throw error;
334
+ throw new SetupGithubError(
335
+ "github_workflow_write_failed",
336
+ "Unable to create .github/workflows.",
337
+ 500,
338
+ result
339
+ );
340
+ }
341
+ return result;
342
+ }
343
+ __name(setupGithub, "setupGithub");
344
+ async function updateGitignore(gitRepoRoot, fileOps = nodeFileOps) {
345
+ try {
346
+ const existingContent = await fileOps.readTextFile(
347
+ gitRepoRoot,
348
+ ".gitignore"
349
+ );
350
+ if (existingContent === void 0) {
351
+ const content = GITIGNORE_ENTRIES.join("\n") + "\n";
352
+ await fileOps.writeTextFile(gitRepoRoot, ".gitignore", content);
353
+ return {
354
+ path: ".gitignore",
355
+ status: "created",
356
+ added: [...GITIGNORE_ENTRIES]
357
+ };
358
+ }
359
+ const missingEntries = GITIGNORE_ENTRIES.filter(
360
+ (entry) => !existingContent.split(/\r?\n/).some((line) => line.split("#")[0].trim() === entry)
361
+ );
362
+ if (missingEntries.length === 0) {
363
+ return { path: ".gitignore", status: "unchanged" };
364
+ }
365
+ const nextContent = existingContent + "\n" + missingEntries.join("\n") + "\n";
366
+ await fileOps.writeTextFile(gitRepoRoot, ".gitignore", nextContent);
367
+ return {
368
+ path: ".gitignore",
369
+ status: "updated",
370
+ added: missingEntries
371
+ };
372
+ } catch (error) {
373
+ debugLogger2.debug("Failed to update .gitignore:", error);
374
+ return {
375
+ path: ".gitignore",
376
+ status: "failed",
377
+ error: error instanceof Error ? error.message : String(error)
378
+ };
379
+ }
380
+ }
381
+ __name(updateGitignore, "updateGitignore");
382
+ async function downloadWorkflows(options) {
383
+ const internalAbort = new AbortController();
384
+ try {
385
+ const dispatcher = options.proxy ? new import_undici2.ProxyAgent(options.proxy) : void 0;
386
+ return await Promise.all(
387
+ GITHUB_WORKFLOW_PATHS.map(async (workflow) => {
388
+ const endpoint = `https://raw.githubusercontent.com/QwenLM/qwen-code-action/refs/tags/${options.releaseTag}/examples/workflows/${workflow}`;
389
+ const response = await options.fetchImpl(endpoint, {
390
+ method: "GET",
391
+ dispatcher,
392
+ signal: AbortSignal.any([
393
+ AbortSignal.timeout(3e4),
394
+ internalAbort.signal,
395
+ ...options.abortSignal ? [options.abortSignal] : []
396
+ ])
397
+ });
398
+ if (!response.ok) {
399
+ throw new Error(
400
+ `Invalid response code downloading ${endpoint}: ${response.status} - ${response.statusText}`
401
+ );
402
+ }
403
+ return {
404
+ sourcePath: workflow,
405
+ content: await readResponseTextWithLimit(response, workflow)
406
+ };
407
+ })
408
+ );
409
+ } catch (error) {
410
+ internalAbort.abort();
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ debugLogger2.debug("Failed to download qwen-code-action workflows:", error);
413
+ throw new SetupGithubError(
414
+ "github_workflow_download_failed",
415
+ `Unable to download qwen-code-action workflows from GitHub. ${message}`,
416
+ 502
417
+ );
418
+ }
419
+ }
420
+ __name(downloadWorkflows, "downloadWorkflows");
421
+ async function readResponseTextWithLimit(response, sourcePath) {
422
+ const contentLength = response.headers.get("content-length");
423
+ if (contentLength !== null) {
424
+ const parsedLength = Number(contentLength);
425
+ if (Number.isFinite(parsedLength) && parsedLength > MAX_WORKFLOW_DOWNLOAD_BYTES) {
426
+ throw new Error(
427
+ `${sourcePath} exceeds download limit of ${MAX_WORKFLOW_DOWNLOAD_BYTES} bytes`
214
428
  );
215
- cumulative.apiTimeMs = addFinite(cumulative.apiTimeMs, durationMs);
216
429
  }
217
- const meta = typeof durationMs === "number" ? { usage, durationMs, ...subagentMeta } : { usage, ...subagentMeta };
218
- await this.sendUpdate({
219
- sessionUpdate: "agent_message_chunk",
220
- content: { type: "text", text },
221
- _meta: meta
222
- });
223
430
  }
224
- /**
225
- * Emits a message chunk based on role and thought flag.
226
- * This is the unified method that handles all message types.
227
- *
228
- * @param text - The message text content
229
- * @param role - Whether this is a user or assistant message
230
- * @param isThought - Whether this is an assistant thought (only applies to assistant role)
231
- * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
232
- */
233
- async emitMessage(text, role, isThought = false, timestamp, subagentMeta) {
234
- if (role === "user") {
235
- return this.emitUserMessage(text, timestamp);
431
+ if (!response.body) return "";
432
+ const reader = response.body.getReader();
433
+ const chunks = [];
434
+ let totalBytes = 0;
435
+ try {
436
+ while (true) {
437
+ const { done, value } = await reader.read();
438
+ if (done) break;
439
+ if (!value) continue;
440
+ totalBytes += value.byteLength;
441
+ if (totalBytes > MAX_WORKFLOW_DOWNLOAD_BYTES) {
442
+ await reader.cancel().catch(() => {
443
+ });
444
+ throw new Error(
445
+ `${sourcePath} exceeds download limit of ${MAX_WORKFLOW_DOWNLOAD_BYTES} bytes`
446
+ );
447
+ }
448
+ chunks.push(value);
236
449
  }
237
- return isThought ? this.emitAgentThought(text, timestamp, subagentMeta) : this.emitAgentMessage(text, timestamp, subagentMeta);
450
+ } finally {
451
+ reader.releaseLock();
238
452
  }
239
- buildChunkMeta(epochMs, subagentMeta) {
240
- const meta = {
241
- ...subagentMeta?.parentToolCallId ? { parentToolCallId: subagentMeta.parentToolCallId } : {},
242
- ...subagentMeta?.subagentType ? { subagentType: subagentMeta.subagentType } : {},
243
- ...epochMs != null ? { timestamp: epochMs } : {}
244
- };
245
- return Object.keys(meta).length > 0 ? meta : void 0;
453
+ const body = new Uint8Array(totalBytes);
454
+ let offset = 0;
455
+ for (const chunk of chunks) {
456
+ body.set(chunk, offset);
457
+ offset += chunk.byteLength;
246
458
  }
247
- };
459
+ return new TextDecoder().decode(body);
460
+ }
461
+ __name(readResponseTextWithLimit, "readResponseTextWithLimit");
462
+ async function resolveSecretsUrl(cwd) {
463
+ try {
464
+ const repoInfo = await getGitHubRepoInfoAsync({ cwd });
465
+ return `https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`;
466
+ } catch {
467
+ return void 0;
468
+ }
469
+ }
470
+ __name(resolveSecretsUrl, "resolveSecretsUrl");
471
+ async function realpathOrResolve(input) {
472
+ try {
473
+ return await fsp.realpath(input);
474
+ } catch {
475
+ return path.resolve(input);
476
+ }
477
+ }
478
+ __name(realpathOrResolve, "realpathOrResolve");
248
479
 
249
- // packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts
480
+ // packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts
250
481
  init_esbuild_shims();
251
482
 
252
- // packages/cli/src/acp-integration/session/emitters/PlanEmitter.ts
483
+ // packages/cli/src/acp-integration/session/emitters/base-emitter.ts
253
484
  init_esbuild_shims();
254
- var PlanEmitter = class extends BaseEmitter {
485
+ var BaseEmitter = class {
486
+ constructor(ctx) {
487
+ this.ctx = ctx;
488
+ }
255
489
  static {
256
- __name(this, "PlanEmitter");
490
+ __name(this, "BaseEmitter");
257
491
  }
258
492
  /**
259
- * Emits a plan update with the given todo items.
260
- *
261
- * @param todos - Array of todo items to send as plan entries
493
+ * Converts an ISO timestamp string or epoch ms to epoch ms number.
494
+ * Returns undefined if the input is not a valid timestamp.
262
495
  */
263
- async emitPlan(todos) {
264
- const entries = todos.map((todo) => ({
265
- content: todo.content,
266
- priority: "medium",
267
- // Default priority since todos don't have priority
268
- status: todo.status
269
- }));
270
- const cumulative = this.ctx.cumulativeUsage;
271
- await this.sendUpdate({
272
- sessionUpdate: "plan",
273
- entries,
274
- ...cumulative ? { _meta: { stats: { ...cumulative } } } : {}
275
- });
496
+ static toEpochMs(ts) {
497
+ if (typeof ts === "number") {
498
+ return Number.isFinite(ts) ? ts : void 0;
499
+ }
500
+ if (typeof ts === "string") {
501
+ const ms = new Date(ts).getTime();
502
+ return Number.isFinite(ms) ? ms : void 0;
503
+ }
504
+ return void 0;
276
505
  }
277
506
  /**
278
- * Extracts todos from tool result display or args.
279
- * Tries multiple sources in priority order:
280
- * 1. Result display object with type 'todo_list'
281
- * 2. Result display as JSON string
282
- * 3. Args with 'todos' array
283
- *
284
- * @param resultDisplay - The tool result display (object, string, or undefined)
285
- * @param args - The tool call arguments (fallback source)
286
- * @returns Array of todos if found, null otherwise
507
+ * Sends a session update to the ACP client.
508
+ * If a message rewriter is configured, updates pass through it first
509
+ * (original messages are sent as-is, rewritten versions are appended).
287
510
  */
288
- extractTodos(resultDisplay, args) {
289
- const fromDisplay = this.extractFromResultDisplay(resultDisplay);
290
- if (fromDisplay) return fromDisplay;
291
- if (args && Array.isArray(args["todos"])) {
292
- return args["todos"];
511
+ async sendUpdate(update) {
512
+ if (this.ctx.messageRewriter) {
513
+ return this.ctx.messageRewriter.interceptUpdate(update);
293
514
  }
294
- return null;
515
+ return this.ctx.sendUpdate(update);
295
516
  }
296
517
  /**
297
- * Extracts todos from a result display value.
298
- * Handles both object and JSON string formats.
518
+ * Gets the session ID.
299
519
  */
300
- extractFromResultDisplay(resultDisplay) {
301
- if (!resultDisplay) return null;
302
- if (typeof resultDisplay === "object") {
303
- const obj = resultDisplay;
304
- if (obj["type"] === "todo_list" && Array.isArray(obj["todos"])) {
305
- return obj["todos"];
306
- }
307
- }
308
- if (typeof resultDisplay === "string") {
309
- try {
310
- const parsed = JSON.parse(resultDisplay);
311
- if (parsed?.["type"] === "todo_list" && Array.isArray(parsed["todos"])) {
312
- return parsed["todos"];
313
- }
314
- } catch {
315
- }
316
- }
317
- return null;
520
+ get sessionId() {
521
+ return this.ctx.sessionId;
318
522
  }
319
523
  };
320
524
 
321
- // packages/cli/src/utils/truncatedDiffPreview.ts
322
- init_esbuild_shims();
323
- function buildTruncatedDiffPreviewText(display) {
324
- const fileName = typeof display["fileName"] === "string" ? display["fileName"] : "the edited file";
325
- const fileDiffLength = typeof display["fileDiffLength"] === "number" ? ` Original fileDiff length: ${display["fileDiffLength"]} chars.` : "";
326
- if (display["fileDiffTruncated"] === true) {
327
- return `Full diff omitted from saved session history for ${fileName}.${fileDiffLength}`;
328
- }
329
- return `Saved session preview only for ${fileName}; full original and new file contents are unavailable.`;
330
- }
331
- __name(buildTruncatedDiffPreviewText, "buildTruncatedDiffPreviewText");
332
-
333
- // packages/cli/src/acp-integration/session/emitters/ToolCallEmitter.ts
334
- var KIND_MAP = {
335
- ["read" /* Read */]: "read",
336
- ["edit" /* Edit */]: "edit",
337
- ["delete" /* Delete */]: "delete",
338
- ["move" /* Move */]: "move",
339
- ["search" /* Search */]: "search",
340
- ["execute" /* Execute */]: "execute",
341
- ["think" /* Think */]: "think",
342
- ["fetch" /* Fetch */]: "fetch",
343
- // ACP defines no 'agent' ToolKind (verified through @agentclientprotocol/sdk
344
- // 0.25.1). The daemon's ClientSideConnection Zod-validates every session/update
345
- // and session/request_permission from the `qwen --acp` child before fanning out
346
- // to SSE clients, so emitting 'agent' is rejected at that hop and the frame is
347
- // dropped. Map the internal Kind.Agent to 'other' on the wire to stay
348
- // protocol-valid; dedicated agent UI is delivered out-of-band (via _meta.toolName)
349
- // in a follow-up rather than via a kind the protocol can't carry.
350
- ["agent" /* Agent */]: "other",
351
- ["other" /* Other */]: "other"
352
- };
353
- var ToolCallEmitter = class _ToolCallEmitter extends BaseEmitter {
525
+ // packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts
526
+ var MessageEmitter = class extends BaseEmitter {
354
527
  static {
355
- __name(this, "ToolCallEmitter");
356
- }
357
- planEmitter;
358
- constructor(ctx) {
359
- super(ctx);
360
- this.planEmitter = new PlanEmitter(ctx);
528
+ __name(this, "MessageEmitter");
361
529
  }
362
530
  /**
363
- * Emits a tool call start event.
531
+ * Emits a StopHookLoop event when Stop hooks create a loop.
532
+ * This informs the client that Stop hooks have been executed multiple times.
364
533
  *
365
- * @param params - Tool call start parameters
366
- * @returns true if event was emitted, false if skipped (e.g., TodoWriteTool)
534
+ * @param iterationCount - The current iteration count
535
+ * @param reasons - Array of reasons from each Stop hook execution
536
+ * @param stopHookCount - Number of Stop hooks that were executed
367
537
  */
368
- async emitStart(params) {
369
- if (this.isTodoWriteTool(params.toolName)) {
370
- return false;
371
- }
372
- const { title, locations, kind } = this.resolveToolMetadata(
373
- params.toolName,
374
- params.args
375
- );
376
- const provenance = _ToolCallEmitter.resolveToolProvenance(
377
- params.toolName,
378
- params.subagentMeta
379
- );
538
+ async emitStopHookLoop(iterationCount, reasons, stopHookCount) {
539
+ const activeGoal = getActiveGoal(this.sessionId);
380
540
  await this.sendUpdate({
381
- sessionUpdate: "tool_call",
382
- toolCallId: params.callId,
383
- status: params.status || "pending",
384
- title,
385
- content: [],
386
- locations,
387
- kind,
388
- rawInput: params.args ?? {},
541
+ sessionUpdate: "agent_message_chunk",
542
+ content: { type: "text", text: "" },
389
543
  _meta: {
390
- toolName: params.toolName,
391
- ...params.subagentMeta,
392
- provenance: provenance.provenance,
393
- ...provenance.serverId ? { serverId: provenance.serverId } : {},
394
- ...BaseEmitter.toEpochMs(params.timestamp) != null && {
395
- timestamp: BaseEmitter.toEpochMs(params.timestamp)
544
+ stopHookLoop: {
545
+ iterationCount,
546
+ reasons,
547
+ stopHookCount,
548
+ ...activeGoal ? {
549
+ goal: {
550
+ condition: activeGoal.condition,
551
+ iterations: activeGoal.iterations,
552
+ setAt: activeGoal.setAt,
553
+ lastReason: activeGoal.lastReason
554
+ }
555
+ } : {}
396
556
  }
397
557
  }
398
558
  });
399
- return true;
400
559
  }
401
- /**
402
- * Emits a tool call result event.
403
- * Handles TodoWriteTool specially by routing to plan updates.
404
- *
405
- * @param params - Tool call result parameters
406
- */
407
- async emitResult(params) {
408
- if (this.isTodoWriteTool(params.toolName)) {
409
- const todos = this.planEmitter.extractTodos(
410
- params.resultDisplay,
411
- params.args
412
- );
413
- if (todos && todos.length > 0) {
414
- await this.planEmitter.emitPlan(todos);
415
- } else if (params.args && Array.isArray(params.args["todos"])) {
416
- await this.planEmitter.emitPlan([]);
560
+ async emitGoalTerminal(event) {
561
+ await this.sendUpdate({
562
+ sessionUpdate: "agent_message_chunk",
563
+ content: { type: "text", text: "" },
564
+ _meta: {
565
+ goalTerminal: event
417
566
  }
418
- return;
419
- }
420
- let contentArray = [];
421
- const diffContent = this.extractDiffContent(params.resultDisplay);
422
- if (diffContent) {
423
- contentArray = [diffContent];
424
- } else if (params.error) {
425
- contentArray = [
426
- {
427
- type: "content",
428
- content: { type: "text", text: params.error.message }
429
- }
430
- ];
431
- } else {
432
- contentArray = this.transformPartsToToolCallContent(params.message);
433
- }
434
- const provenance = _ToolCallEmitter.resolveToolProvenance(
435
- params.toolName,
436
- params.subagentMeta
437
- );
438
- const update = {
439
- sessionUpdate: "tool_call_update",
440
- toolCallId: params.callId,
441
- status: params.success ? "completed" : "failed",
442
- content: contentArray,
567
+ });
568
+ }
569
+ async emitGoalStatus(status) {
570
+ await this.sendUpdate({
571
+ sessionUpdate: "agent_message_chunk",
572
+ content: { type: "text", text: "" },
443
573
  _meta: {
444
- toolName: params.toolName,
445
- ...params.subagentMeta,
446
- provenance: provenance.provenance,
447
- ...provenance.serverId ? { serverId: provenance.serverId } : {},
448
- ...BaseEmitter.toEpochMs(params.timestamp) != null && {
449
- timestamp: BaseEmitter.toEpochMs(params.timestamp)
450
- },
451
- ...params.artifacts && params.artifacts.length > 0 ? { artifacts: params.artifacts } : {}
574
+ goalStatus: status
452
575
  }
453
- };
454
- if (params.resultDisplay !== void 0 && !this.isTruncatedSessionDiffDisplay(params.resultDisplay)) {
455
- update["rawOutput"] = params.resultDisplay;
456
- }
457
- await this.sendUpdate(update);
576
+ });
458
577
  }
459
578
  /**
460
- * Emits a tool call error event.
461
- * Use this for explicit error handling when not using emitResult.
579
+ * Emits a user message chunk.
462
580
  *
463
- * @param callId - The tool call ID
464
- * @param toolName - The tool name
465
- * @param error - The error that occurred
466
- * @param subagentMeta - Optional subagent metadata
581
+ * @param text - The user message text content
582
+ * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
467
583
  */
468
- async emitError(callId, toolName, error, subagentMeta) {
469
- const provenance = _ToolCallEmitter.resolveToolProvenance(
470
- toolName,
471
- subagentMeta
472
- );
584
+ async emitUserMessage(text, timestamp, options = {}) {
585
+ const epochMs = BaseEmitter.toEpochMs(timestamp);
586
+ const _meta = {
587
+ ...epochMs != null ? { timestamp: epochMs } : {},
588
+ ...options.source ? { source: options.source } : {}
589
+ };
473
590
  await this.sendUpdate({
474
- sessionUpdate: "tool_call_update",
475
- toolCallId: callId,
476
- status: "failed",
477
- content: [
478
- { type: "content", content: { type: "text", text: error.message } }
479
- ],
480
- _meta: {
481
- toolName,
482
- ...subagentMeta,
483
- provenance: provenance.provenance,
484
- ...provenance.serverId ? { serverId: provenance.serverId } : {}
485
- }
591
+ sessionUpdate: "user_message_chunk",
592
+ content: { type: "text", text },
593
+ ...Object.keys(_meta).length > 0 ? { _meta } : {}
486
594
  });
487
595
  }
488
596
  /**
489
- * Resolve a tool's provenance for UI dispatch on tool_call events.
490
- * The SDK reads `_meta.
491
- * provenance` + `_meta.serverId` to render builtin / MCP-server-badge /
492
- * subagent-block differently. Without this stamping, the SDK falls
493
- * back to string-matching the toolName which can't reliably
494
- * distinguish builtin from subagent.
495
- *
496
- * Resolution rules:
497
- * - `subagentMeta` present → `'subagent'` (a Task tool / Codex
498
- * subagent / etc. wrapping its own tool calls)
499
- * - toolName matches `mcp__<server>__<tool>` → `'mcp'` with
500
- * `serverId: <server>`. Naming convention from
501
- * `packages/core/src/tools/mcp-tool.ts` in the
502
- * `@qwen-code/qwen-code-core` package — mirrors the SDK's same
503
- * heuristic fallback so SDK consumers stay consistent with
504
- * daemon classification.
505
- * - everything else → `'builtin'`
597
+ * Emits an agent thought chunk.
506
598
  *
507
- * Static + pure so it can be unit-tested without an emitter
508
- * instance. Exported via `ToolCallEmitter.resolveToolProvenance`.
599
+ * @param text - The thought text content
600
+ * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
509
601
  */
510
- static resolveToolProvenance(toolName, subagentMeta) {
511
- if (subagentMeta !== void 0) {
512
- return { provenance: "subagent" };
513
- }
514
- if (toolName.startsWith("mcp__")) {
515
- const parts = toolName.split("__");
516
- if (parts.length >= 3 && parts[1] && parts[1].length > 0) {
517
- return { provenance: "mcp", serverId: parts[1] };
518
- }
519
- }
520
- return { provenance: "builtin" };
602
+ async emitAgentThought(text, timestamp, subagentMeta) {
603
+ const _meta = this.buildChunkMeta(
604
+ BaseEmitter.toEpochMs(timestamp),
605
+ subagentMeta
606
+ );
607
+ await this.sendUpdate({
608
+ sessionUpdate: "agent_thought_chunk",
609
+ content: { type: "text", text },
610
+ ..._meta ? { _meta } : {}
611
+ });
521
612
  }
522
- // ==================== Public Utilities ====================
523
613
  /**
524
- * Checks if a tool name is the TodoWriteTool.
525
- * Exposed for external use in components that need to check this.
614
+ * Emits an agent message chunk.
615
+ *
616
+ * @param text - The agent message text content
617
+ * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
526
618
  */
527
- isTodoWriteTool(toolName) {
528
- return toolName === ToolNames.TODO_WRITE;
619
+ async emitAgentMessage(text, timestamp, subagentMeta) {
620
+ const _meta = this.buildChunkMeta(
621
+ BaseEmitter.toEpochMs(timestamp),
622
+ subagentMeta
623
+ );
624
+ await this.sendUpdate({
625
+ sessionUpdate: "agent_message_chunk",
626
+ content: { type: "text", text },
627
+ ..._meta ? { _meta } : {}
628
+ });
529
629
  }
530
- /**
531
- * Checks if a tool name is the ExitPlanModeTool.
532
- */
533
- isExitPlanModeTool(toolName) {
534
- return toolName === ToolNames.EXIT_PLAN_MODE;
630
+ async emitSlashCommandOutput(text, timestamp) {
631
+ const epochMs = BaseEmitter.toEpochMs(timestamp);
632
+ await this.sendUpdate({
633
+ sessionUpdate: "agent_message_chunk",
634
+ content: { type: "text", text },
635
+ _meta: {
636
+ source: "slash_command",
637
+ ...epochMs != null ? { timestamp: epochMs } : {}
638
+ }
639
+ });
535
640
  }
536
641
  /**
537
- * Checks if a tool name is the EnterPlanModeTool.
642
+ * Emits usage metadata.
538
643
  */
539
- isEnterPlanModeTool(toolName) {
540
- return toolName === ToolNames.ENTER_PLAN_MODE;
644
+ async emitUsageMetadata(usageMetadata, text = "", durationMs, subagentMeta) {
645
+ const usage = {
646
+ inputTokens: usageMetadata.promptTokenCount ?? 0,
647
+ outputTokens: usageMetadata.candidatesTokenCount ?? 0,
648
+ totalTokens: usageMetadata.totalTokenCount ?? 0,
649
+ thoughtTokens: usageMetadata.thoughtsTokenCount,
650
+ cachedReadTokens: usageMetadata.cachedContentTokenCount
651
+ };
652
+ const cumulative = this.ctx.cumulativeUsage;
653
+ if (cumulative) {
654
+ const addFinite = /* @__PURE__ */ __name((total, value) => typeof value === "number" && Number.isFinite(value) ? total + value : total, "addFinite");
655
+ cumulative.promptTokens = addFinite(
656
+ cumulative.promptTokens,
657
+ usage.inputTokens
658
+ );
659
+ cumulative.candidateTokens = addFinite(
660
+ cumulative.candidateTokens,
661
+ usage.outputTokens
662
+ );
663
+ cumulative.cachedTokens = addFinite(
664
+ cumulative.cachedTokens,
665
+ usage.cachedReadTokens
666
+ );
667
+ cumulative.apiTimeMs = addFinite(cumulative.apiTimeMs, durationMs);
668
+ }
669
+ let meta;
670
+ if (typeof durationMs === "number") {
671
+ const activity = apiActivityTracker.drain();
672
+ meta = {
673
+ usage,
674
+ durationMs,
675
+ ...activity.errors > 0 ? { apiErrors: activity.errors } : {},
676
+ ...activity.retries > 0 ? { apiRetries: activity.retries } : {},
677
+ ...subagentMeta
678
+ };
679
+ } else {
680
+ meta = { usage, ...subagentMeta };
681
+ }
682
+ await this.sendUpdate({
683
+ sessionUpdate: "agent_message_chunk",
684
+ content: { type: "text", text },
685
+ _meta: meta
686
+ });
541
687
  }
542
688
  /**
543
- * Resolves tool metadata from the registry.
544
- * Falls back to defaults if tool not found or build fails.
689
+ * Emits a message chunk based on role and thought flag.
690
+ * This is the unified method that handles all message types.
545
691
  *
546
- * @param toolName - Name of the tool
547
- * @param args - Tool call arguments (used to build invocation)
692
+ * @param text - The message text content
693
+ * @param role - Whether this is a user or assistant message
694
+ * @param isThought - Whether this is an assistant thought (only applies to assistant role)
695
+ * @param timestamp - Optional server-side timestamp (ISO string or ms) for message ordering
548
696
  */
549
- resolveToolMetadata(toolName, args) {
550
- const toolRegistry = this.config.getToolRegistry();
551
- const tool = toolRegistry.getTool(toolName);
552
- let title = tool?.displayName ?? toolName;
553
- let locations = [];
554
- let kind = "other";
555
- if (tool && args) {
556
- try {
557
- const invocation = tool.build(args);
558
- title = `${title}: ${invocation.getDescription()}`;
559
- locations = invocation.toolLocations().map((loc) => ({
560
- path: loc.path,
561
- line: loc.line ?? null
562
- }));
563
- kind = this.mapToolKind(tool.kind, toolName);
564
- } catch {
565
- if (typeof args["description"] === "string") {
566
- title = `${title}: ${args["description"]}`;
567
- }
568
- if (tool.kind) {
569
- kind = this.mapToolKind(tool.kind, toolName);
570
- }
571
- }
697
+ async emitMessage(text, role, isThought = false, timestamp, subagentMeta) {
698
+ if (role === "user") {
699
+ return this.emitUserMessage(text, timestamp);
572
700
  }
573
- return { title, locations, kind };
701
+ return isThought ? this.emitAgentThought(text, timestamp, subagentMeta) : this.emitAgentMessage(text, timestamp, subagentMeta);
702
+ }
703
+ buildChunkMeta(epochMs, subagentMeta) {
704
+ const meta = {
705
+ ...subagentMeta?.parentToolCallId ? { parentToolCallId: subagentMeta.parentToolCallId } : {},
706
+ ...subagentMeta?.subagentType ? { subagentType: subagentMeta.subagentType } : {},
707
+ ...epochMs != null ? { timestamp: epochMs } : {}
708
+ };
709
+ return Object.keys(meta).length > 0 ? meta : void 0;
710
+ }
711
+ };
712
+
713
+ // packages/cli/src/acp-integration/session/emitters/PlanEmitter.ts
714
+ init_esbuild_shims();
715
+ var PlanEmitter = class extends BaseEmitter {
716
+ static {
717
+ __name(this, "PlanEmitter");
574
718
  }
575
719
  /**
576
- * Maps core Tool Kind enum to ACP ToolKind string literals.
720
+ * Emits a plan update with the given todo items.
577
721
  *
578
- * @param kind - The core Kind enum value
579
- * @param toolName - Optional tool name to handle special cases like exit_plan_mode
722
+ * @param todos - Array of todo items to send as plan entries
580
723
  */
581
- mapToolKind(kind, toolName) {
582
- if (toolName && (this.isExitPlanModeTool(toolName) || this.isEnterPlanModeTool(toolName))) {
583
- return "switch_mode";
584
- }
585
- return KIND_MAP[kind] ?? "other";
724
+ async emitPlan(todos) {
725
+ const entries = todos.map((todo) => ({
726
+ content: todo.content,
727
+ priority: "medium",
728
+ // Default priority since todos don't have priority
729
+ status: todo.status
730
+ }));
731
+ const cumulative = this.ctx.cumulativeUsage;
732
+ await this.sendUpdate({
733
+ sessionUpdate: "plan",
734
+ entries,
735
+ ...cumulative ? { _meta: { stats: { ...cumulative } } } : {}
736
+ });
586
737
  }
587
- // ==================== Private Helpers ====================
588
738
  /**
589
- * Extracts diff content from resultDisplay if it's a diff type (edit tool result).
590
- * Returns null if not a diff.
739
+ * Extracts todos from tool result display or args.
740
+ * Tries multiple sources in priority order:
741
+ * 1. Result display object with type 'todo_list'
742
+ * 2. Result display as JSON string
743
+ * 3. Args with 'todos' array
744
+ *
745
+ * @param resultDisplay - The tool result display (object, string, or undefined)
746
+ * @param args - The tool call arguments (fallback source)
747
+ * @returns Array of todos if found, null otherwise
591
748
  */
592
- extractDiffContent(resultDisplay) {
593
- if (!resultDisplay || typeof resultDisplay !== "object") return null;
594
- const obj = resultDisplay;
595
- if ("fileName" in obj && "newContent" in obj) {
596
- if (this.isTruncatedSessionDiffDisplay(resultDisplay)) {
597
- return {
598
- type: "content",
599
- content: {
600
- type: "text",
601
- text: buildTruncatedDiffPreviewText(obj)
602
- }
603
- };
604
- }
605
- return {
606
- type: "diff",
607
- path: obj["fileName"],
608
- oldText: obj["originalContent"] ?? "",
609
- newText: obj["newContent"]
610
- };
749
+ extractTodos(resultDisplay, args) {
750
+ const fromDisplay = this.extractFromResultDisplay(resultDisplay);
751
+ if (fromDisplay) return fromDisplay;
752
+ if (args && Array.isArray(args["todos"])) {
753
+ return args["todos"];
611
754
  }
612
755
  return null;
613
756
  }
614
- isTruncatedSessionDiffDisplay(resultDisplay) {
615
- if (!resultDisplay || typeof resultDisplay !== "object") return false;
616
- const obj = resultDisplay;
617
- return obj["truncatedForSession"] === true && "fileName" in obj && "newContent" in obj;
618
- }
619
757
  /**
620
- * Transforms Part[] to ToolCallContent[].
621
- * Extracts text from functionResponse parts and text parts.
758
+ * Extracts todos from a result display value.
759
+ * Handles both object and JSON string formats.
622
760
  */
623
- transformPartsToToolCallContent(parts) {
624
- const result = [];
625
- for (const part of parts) {
626
- if ("text" in part && part.text) {
627
- result.push({
628
- type: "content",
629
- content: { type: "text", text: part.text }
630
- });
761
+ extractFromResultDisplay(resultDisplay) {
762
+ if (!resultDisplay) return null;
763
+ if (typeof resultDisplay === "object") {
764
+ const obj = resultDisplay;
765
+ if (obj["type"] === "todo_list" && Array.isArray(obj["todos"])) {
766
+ return obj["todos"];
631
767
  }
632
- if ("functionResponse" in part && part.functionResponse) {
633
- try {
634
- const resp = part.functionResponse.response;
635
- const outputField = resp["output"];
636
- const errorField = resp["error"];
637
- const responseText = typeof outputField === "string" ? outputField : typeof errorField === "string" ? errorField : JSON.stringify(resp);
638
- result.push({
639
- type: "content",
640
- content: { type: "text", text: responseText }
641
- });
642
- } catch {
768
+ }
769
+ if (typeof resultDisplay === "string") {
770
+ try {
771
+ const parsed = JSON.parse(resultDisplay);
772
+ if (parsed?.["type"] === "todo_list" && Array.isArray(parsed["todos"])) {
773
+ return parsed["todos"];
643
774
  }
775
+ } catch {
644
776
  }
645
777
  }
646
- return result;
778
+ return null;
647
779
  }
648
780
  };
649
781
 
650
- // packages/cli/src/utils/chat-record-tool-call-id.ts
782
+ // packages/cli/src/acp-integration/session/emitters/tool-call-emitter.ts
651
783
  init_esbuild_shims();
652
- function getToolResultCallId(record) {
653
- return getExplicitToolResultCallId(record) ?? record.uuid;
654
- }
655
- __name(getToolResultCallId, "getToolResultCallId");
656
- function getExplicitToolResultCallId(record) {
657
- const resultCallId = record.toolCallResult?.callId;
658
- if (typeof resultCallId === "string" && resultCallId.length > 0) {
659
- return resultCallId;
660
- }
661
- return extractFunctionResponseId(record);
784
+
785
+ // packages/cli/src/acp-integration/session/types.ts
786
+ init_esbuild_shims();
787
+ function hasFullSessionContext(context) {
788
+ return "config" in context;
662
789
  }
663
- __name(getExplicitToolResultCallId, "getExplicitToolResultCallId");
664
- function extractFunctionResponseId(record) {
665
- if (!record.message?.parts) {
666
- return void 0;
667
- }
668
- for (const part of record.message.parts) {
669
- const id = "functionResponse" in part ? part.functionResponse?.id : void 0;
670
- if (typeof id === "string" && id.length > 0) {
671
- return id;
672
- }
790
+ __name(hasFullSessionContext, "hasFullSessionContext");
791
+
792
+ // packages/cli/src/utils/truncatedDiffPreview.ts
793
+ init_esbuild_shims();
794
+ function buildTruncatedDiffPreviewText(display) {
795
+ const fileName = typeof display["fileName"] === "string" ? display["fileName"] : "the edited file";
796
+ const fileDiffLength = typeof display["fileDiffLength"] === "number" ? ` Original fileDiff length: ${display["fileDiffLength"]} chars.` : "";
797
+ if (display["fileDiffTruncated"] === true) {
798
+ return `Full diff omitted from saved session history for ${fileName}.${fileDiffLength}`;
673
799
  }
674
- return void 0;
800
+ return `Saved session preview only for ${fileName}; full original and new file contents are unavailable.`;
675
801
  }
676
- __name(extractFunctionResponseId, "extractFunctionResponseId");
802
+ __name(buildTruncatedDiffPreviewText, "buildTruncatedDiffPreviewText");
677
803
 
678
- // packages/cli/src/acp-integration/session/HistoryReplayer.ts
679
- var MISSING_TOOL_RESULT_MESSAGE = "Tool result missing from saved history; the previous run likely ended before this tool completed.";
680
- var HistoryReplayer = class {
804
+ // packages/cli/src/acp-integration/session/emitters/tool-call-emitter.ts
805
+ var KIND_MAP = {
806
+ ["read" /* Read */]: "read",
807
+ ["edit" /* Edit */]: "edit",
808
+ ["delete" /* Delete */]: "delete",
809
+ ["move" /* Move */]: "move",
810
+ ["search" /* Search */]: "search",
811
+ ["execute" /* Execute */]: "execute",
812
+ ["think" /* Think */]: "think",
813
+ ["fetch" /* Fetch */]: "fetch",
814
+ // ACP defines no 'agent' ToolKind (verified through @agentclientprotocol/sdk
815
+ // 0.25.1). The daemon's ClientSideConnection Zod-validates every session/update
816
+ // and session/request_permission from the `qwen --acp` child before fanning out
817
+ // to SSE clients, so emitting 'agent' is rejected at that hop and the frame is
818
+ // dropped. Map the internal Kind.Agent to 'other' on the wire to stay
819
+ // protocol-valid; dedicated agent UI is delivered out-of-band (via _meta.toolName)
820
+ // in a follow-up rather than via a kind the protocol can't carry.
821
+ ["agent" /* Agent */]: "other",
822
+ ["other" /* Other */]: "other"
823
+ };
824
+ var ToolCallEmitter = class _ToolCallEmitter extends BaseEmitter {
681
825
  static {
682
- __name(this, "HistoryReplayer");
826
+ __name(this, "ToolCallEmitter");
683
827
  }
684
- ctx;
685
- messageEmitter;
686
- toolCallEmitter;
687
- pendingReplayToolCalls = /* @__PURE__ */ new Map();
828
+ planEmitter;
688
829
  constructor(ctx) {
689
- this.ctx = ctx;
690
- this.messageEmitter = new MessageEmitter(ctx);
691
- this.toolCallEmitter = new ToolCallEmitter(ctx);
830
+ super(ctx);
831
+ this.planEmitter = new PlanEmitter(ctx);
692
832
  }
693
833
  /**
694
- * Replays all chat records from a loaded session.
834
+ * Emits a tool call start event.
695
835
  *
696
- * @param records - Array of chat records to replay
697
- * @param gaps - Optional detected history gaps; a visible notice is emitted
698
- * immediately before each gap's child record so the user sees that an
699
- * earlier segment was lost rather than assuming the halves are contiguous.
836
+ * @param params - Tool call start parameters
837
+ * @returns true if event was emitted, false if skipped (e.g., TodoWriteTool)
700
838
  */
701
- async replay(records, gaps) {
702
- try {
703
- await this.replayPage(records, { finalizeDangling: true, gaps });
704
- } finally {
705
- this.pendingReplayToolCalls.clear();
706
- this.setActiveRecordId(null);
707
- }
708
- }
709
- async replayPage(records, options = {}) {
710
- this.pendingReplayToolCalls.clear();
711
- for (const pending of options.pendingToolCalls ?? []) {
712
- this.pendingReplayToolCalls.set(pending.callId, pending);
839
+ async emitStart(params) {
840
+ if (this.isTodoWriteTool(params.toolName)) {
841
+ return false;
713
842
  }
714
- const gapByChildUuid = indexGapsByChild(options.gaps);
715
- let replayError;
716
- try {
717
- for (const record of records) {
718
- const gap = gapByChildUuid.get(record.uuid);
719
- if (gap) {
720
- await this.emitHistoryGapNotice(gap, record.timestamp);
843
+ const { title, locations, kind } = this.resolveToolMetadata(
844
+ params.toolName,
845
+ params.args
846
+ );
847
+ const provenance = _ToolCallEmitter.resolveToolProvenance(
848
+ params.toolName,
849
+ params.subagentMeta
850
+ );
851
+ await this.sendUpdate({
852
+ sessionUpdate: "tool_call",
853
+ toolCallId: params.callId,
854
+ status: params.status || "pending",
855
+ title,
856
+ content: [],
857
+ locations,
858
+ kind,
859
+ rawInput: params.args ?? {},
860
+ _meta: {
861
+ toolName: params.toolName,
862
+ ...params.subagentMeta,
863
+ provenance: provenance.provenance,
864
+ ...provenance.serverId ? { serverId: provenance.serverId } : {},
865
+ ...BaseEmitter.toEpochMs(params.timestamp) != null && {
866
+ timestamp: BaseEmitter.toEpochMs(params.timestamp)
721
867
  }
722
- await this.replayRecord(record);
723
868
  }
724
- } catch (error) {
725
- replayError = error;
726
- }
727
- let danglingError;
728
- if (options.finalizeDangling === true) {
729
- try {
730
- await this.failDanglingToolCalls();
731
- } catch (error) {
732
- danglingError = error;
733
- }
734
- }
735
- const state = {
736
- pendingToolCalls: options.finalizeDangling === true ? [] : Array.from(this.pendingReplayToolCalls.values())
737
- };
738
- this.setActiveRecordId(null);
739
- if (replayError && danglingError) {
740
- throw new AggregateError(
741
- [replayError, danglingError],
742
- "Replay and dangling-cleanup both failed"
743
- );
744
- }
745
- if (replayError) {
746
- throw replayError;
747
- }
748
- if (danglingError) {
749
- throw danglingError;
750
- }
751
- return state;
752
- }
753
- getPendingToolCalls() {
754
- return Array.from(this.pendingReplayToolCalls.values());
869
+ });
870
+ return true;
755
871
  }
756
872
  /**
757
- * Replays a single chat record.
873
+ * Emits a tool call result event.
874
+ * Handles TodoWriteTool specially by routing to plan updates.
875
+ *
876
+ * @param params - Tool call result parameters
758
877
  */
759
- async replayRecord(record) {
760
- this.setActiveRecordId(record.uuid, record.timestamp);
761
- try {
762
- switch (record.type) {
763
- case "user":
764
- if (record.subtype === "notification" || record.subtype === "cron") {
765
- const displayText = record.systemPayload?.displayText;
766
- if (displayText) {
767
- await this.messageEmitter.emitUserMessage(
768
- displayText,
769
- record.timestamp,
770
- record.subtype === "cron" ? { source: "cron" } : void 0
771
- );
772
- }
773
- break;
774
- }
775
- if (record.subtype === "mid_turn_user_message") {
776
- const displayText = record.systemPayload?.displayText;
777
- if (displayText) {
778
- await this.messageEmitter.emitUserMessage(
779
- displayText,
780
- record.timestamp
781
- );
782
- } else if (record.message) {
783
- await this.replayContent(
784
- record.message,
785
- "user",
786
- record.timestamp,
787
- record.uuid
788
- );
789
- }
790
- break;
791
- }
792
- if (record.message) {
793
- await this.replayContent(
794
- record.message,
795
- "user",
796
- record.timestamp,
797
- record.uuid
798
- );
799
- }
800
- break;
801
- case "assistant":
802
- if (record.message) {
803
- await this.replayContent(
804
- record.message,
805
- "assistant",
806
- record.timestamp,
807
- record.uuid
808
- );
809
- }
810
- if (record.usageMetadata) {
811
- await this.replayUsageMetadata(record.usageMetadata);
812
- }
813
- break;
814
- case "tool_result":
815
- await this.replayToolResult(record);
816
- break;
817
- case "system":
818
- if (record.subtype === "slash_command") {
819
- await this.replaySlashCommandResult(record);
820
- }
821
- break;
822
- default:
823
- break;
878
+ async emitResult(params) {
879
+ if (this.isTodoWriteTool(params.toolName)) {
880
+ const todos = this.planEmitter.extractTodos(
881
+ params.resultDisplay,
882
+ params.args
883
+ );
884
+ if (todos && todos.length > 0) {
885
+ await this.planEmitter.emitPlan(todos);
886
+ } else if (params.args && Array.isArray(params.args["todos"])) {
887
+ await this.planEmitter.emitPlan([]);
824
888
  }
825
- } finally {
826
- this.setActiveRecordId(null);
889
+ return;
890
+ }
891
+ let contentArray = [];
892
+ const diffContent = this.extractDiffContent(params.resultDisplay);
893
+ if (diffContent) {
894
+ contentArray = [diffContent];
895
+ } else if (params.error) {
896
+ contentArray = [
897
+ {
898
+ type: "content",
899
+ content: { type: "text", text: params.error.message }
900
+ }
901
+ ];
902
+ } else {
903
+ contentArray = this.transformPartsToToolCallContent(params.message);
904
+ }
905
+ const provenance = _ToolCallEmitter.resolveToolProvenance(
906
+ params.toolName,
907
+ params.subagentMeta
908
+ );
909
+ const update = {
910
+ sessionUpdate: "tool_call_update",
911
+ toolCallId: params.callId,
912
+ status: params.success ? "completed" : "failed",
913
+ content: contentArray,
914
+ _meta: {
915
+ toolName: params.toolName,
916
+ ...params.subagentMeta,
917
+ provenance: provenance.provenance,
918
+ ...provenance.serverId ? { serverId: provenance.serverId } : {},
919
+ ...BaseEmitter.toEpochMs(params.timestamp) != null && {
920
+ timestamp: BaseEmitter.toEpochMs(params.timestamp)
921
+ },
922
+ ...params.artifacts && params.artifacts.length > 0 ? { artifacts: params.artifacts } : {}
923
+ }
924
+ };
925
+ if (params.resultDisplay !== void 0 && !this.isTruncatedSessionDiffDisplay(params.resultDisplay)) {
926
+ update["rawOutput"] = params.resultDisplay;
827
927
  }
928
+ await this.sendUpdate(update);
828
929
  }
829
930
  /**
830
- * Emits a visible notice marking a break in the persisted history chain: an
831
- * earlier segment was physically lost (storage interruption) and could not be
832
- * recovered, so the surviving turns below must not be read as contiguous with
833
- * whatever came before the gap. Uses the agent message channel — the same one
834
- * used for other system notices (see MessageEmitter.emitStopHookLoop) — so no
835
- * new session-update kind is needed.
931
+ * Emits a tool call error event.
932
+ * Use this for explicit error handling when not using emitResult.
933
+ *
934
+ * @param callId - The tool call ID
935
+ * @param toolName - The tool name
936
+ * @param error - The error that occurred
937
+ * @param subagentMeta - Optional subagent metadata
836
938
  */
837
- async emitHistoryGapNotice(gap, timestamp) {
838
- await this.messageEmitter.emitAgentMessage(
839
- formatHistoryGapNotice(gap),
840
- timestamp
939
+ async emitError(callId, toolName, error, subagentMeta) {
940
+ const provenance = _ToolCallEmitter.resolveToolProvenance(
941
+ toolName,
942
+ subagentMeta
841
943
  );
944
+ await this.sendUpdate({
945
+ sessionUpdate: "tool_call_update",
946
+ toolCallId: callId,
947
+ status: "failed",
948
+ content: [
949
+ { type: "content", content: { type: "text", text: error.message } }
950
+ ],
951
+ _meta: {
952
+ toolName,
953
+ ...subagentMeta,
954
+ provenance: provenance.provenance,
955
+ ...provenance.serverId ? { serverId: provenance.serverId } : {}
956
+ }
957
+ });
842
958
  }
843
959
  /**
844
- * Replays content from a message (user or assistant).
845
- * Handles text parts, thought parts, and function calls.
960
+ * Resolve a tool's provenance for UI dispatch on tool_call events.
961
+ * The SDK reads `_meta.
962
+ * provenance` + `_meta.serverId` to render builtin / MCP-server-badge /
963
+ * subagent-block differently. Without this stamping, the SDK falls
964
+ * back to string-matching the toolName which can't reliably
965
+ * distinguish builtin from subagent.
846
966
  *
847
- * @param content - The content to replay
848
- * @param role - The role (user or assistant)
849
- * @param timestamp - Optional server-side timestamp from the JSONL record
967
+ * Resolution rules:
968
+ * - `subagentMeta` present `'subagent'` (a Task tool / Codex
969
+ * subagent / etc. wrapping its own tool calls)
970
+ * - toolName matches `mcp__<server>__<tool>` → `'mcp'` with
971
+ * `serverId: <server>`. Naming convention from
972
+ * `packages/core/src/tools/mcp-tool.ts` in the
973
+ * `@qwen-code/qwen-code-core` package — mirrors the SDK's same
974
+ * heuristic fallback so SDK consumers stay consistent with
975
+ * daemon classification.
976
+ * - everything else → `'builtin'`
977
+ *
978
+ * Static + pure so it can be unit-tested without an emitter
979
+ * instance. Exported via `ToolCallEmitter.resolveToolProvenance`.
850
980
  */
851
- async replayContent(content, role, timestamp, recordId) {
852
- for (const part of content.parts ?? []) {
853
- if ("text" in part && part.text) {
854
- const isThought = part.thought ?? false;
855
- await this.messageEmitter.emitMessage(
856
- part.text,
857
- role,
858
- isThought,
859
- timestamp
860
- );
861
- }
862
- if ("functionCall" in part && part.functionCall) {
863
- const functionName = part.functionCall.name ?? "";
864
- const sourceCallId = part.functionCall.id;
865
- const callId = sourceCallId ?? `${functionName}-${Date.now()}`;
866
- const emitted = await this.toolCallEmitter.emitStart({
867
- toolName: functionName,
868
- callId,
869
- args: part.functionCall.args,
870
- status: "in_progress",
871
- timestamp
872
- });
873
- if (emitted && role === "assistant" && recordId && sourceCallId) {
874
- this.pendingReplayToolCalls.set(callId, {
875
- callId,
876
- toolName: functionName,
877
- timestamp,
878
- recordId
879
- });
880
- }
981
+ static resolveToolProvenance(toolName, subagentMeta) {
982
+ if (subagentMeta !== void 0) {
983
+ return { provenance: "subagent" };
984
+ }
985
+ if (toolName.startsWith("mcp__")) {
986
+ const parts = toolName.split("__");
987
+ if (parts.length >= 3 && parts[1] && parts[1].length > 0) {
988
+ return { provenance: "mcp", serverId: parts[1] };
881
989
  }
882
990
  }
991
+ return { provenance: "builtin" };
883
992
  }
993
+ // ==================== Public Utilities ====================
884
994
  /**
885
- * Replays usage metadata.
886
- * @param usageMetadata - The usage metadata to replay
995
+ * Checks if a tool name is the TodoWriteTool.
996
+ * Exposed for external use in components that need to check this.
887
997
  */
888
- async replayUsageMetadata(usageMetadata) {
889
- await this.messageEmitter.emitUsageMetadata(usageMetadata);
998
+ isTodoWriteTool(toolName) {
999
+ return toolName === ToolNames.TODO_WRITE;
890
1000
  }
891
1001
  /**
892
- * Replays a tool result record.
1002
+ * Checks if a tool name is the ExitPlanModeTool.
893
1003
  */
894
- async replayToolResult(record) {
895
- if (!record.message?.parts) {
896
- return;
897
- }
898
- const result = record.toolCallResult;
899
- const callId = getToolResultCallId(record);
900
- this.pendingReplayToolCalls.delete(callId);
901
- const toolName = this.extractToolNameFromRecord(record);
902
- await this.toolCallEmitter.emitResult({
903
- toolName,
904
- callId,
905
- success: !result?.error,
906
- message: record.message.parts,
907
- resultDisplay: result?.resultDisplay,
908
- artifacts: result?.artifacts,
909
- // For TodoWriteTool fallback, try to extract args from the record
910
- // Note: args aren't stored in tool_result records by default
911
- args: void 0,
912
- timestamp: record.timestamp
913
- });
914
- const { resultDisplay } = result ?? {};
915
- if (!!resultDisplay && typeof resultDisplay === "object" && "type" in resultDisplay && resultDisplay.type === "task_execution") {
916
- await this.emitTaskUsageFromResultDisplay(
917
- resultDisplay
918
- );
919
- }
1004
+ isExitPlanModeTool(toolName) {
1005
+ return toolName === ToolNames.EXIT_PLAN_MODE;
920
1006
  }
921
- async failDanglingToolCalls() {
922
- let firstError;
923
- for (const pending of this.pendingReplayToolCalls.values()) {
924
- this.setActiveRecordId(pending.recordId, pending.timestamp);
1007
+ /**
1008
+ * Checks if a tool name is the EnterPlanModeTool.
1009
+ */
1010
+ isEnterPlanModeTool(toolName) {
1011
+ return toolName === ToolNames.ENTER_PLAN_MODE;
1012
+ }
1013
+ /**
1014
+ * Resolves tool metadata from the registry.
1015
+ * Falls back to defaults if tool not found or build fails.
1016
+ *
1017
+ * @param toolName - Name of the tool
1018
+ * @param args - Tool call arguments (used to build invocation)
1019
+ */
1020
+ resolveToolMetadata(toolName, args) {
1021
+ if (!hasFullSessionContext(this.ctx)) {
1022
+ const description = typeof args?.["description"] === "string" ? args["description"].trim() : "";
1023
+ return {
1024
+ title: description ? `${toolName}: ${description}` : toolName,
1025
+ locations: [],
1026
+ kind: "other"
1027
+ };
1028
+ }
1029
+ const toolRegistry = this.ctx.config.getToolRegistry();
1030
+ const tool = toolRegistry.getTool(toolName);
1031
+ let title = tool?.displayName ?? toolName;
1032
+ let locations = [];
1033
+ let kind = "other";
1034
+ if (tool && args) {
925
1035
  try {
926
- await this.toolCallEmitter.emitResult({
927
- toolName: pending.toolName,
928
- callId: pending.callId,
929
- success: false,
930
- message: [],
931
- error: new Error(MISSING_TOOL_RESULT_MESSAGE),
932
- timestamp: pending.timestamp
933
- });
934
- } catch (error) {
935
- firstError ??= error;
936
- } finally {
937
- this.setActiveRecordId(null);
1036
+ const invocation = tool.build(args);
1037
+ title = `${title}: ${invocation.getDescription()}`;
1038
+ locations = invocation.toolLocations().map((loc) => ({
1039
+ path: loc.path,
1040
+ line: loc.line ?? null
1041
+ }));
1042
+ kind = this.mapToolKind(tool.kind, toolName);
1043
+ } catch {
1044
+ if (typeof args["description"] === "string") {
1045
+ title = `${title}: ${args["description"]}`;
1046
+ }
1047
+ if (tool.kind) {
1048
+ kind = this.mapToolKind(tool.kind, toolName);
1049
+ }
938
1050
  }
939
1051
  }
940
- if (firstError) {
941
- throw firstError;
942
- }
1052
+ return { title, locations, kind };
943
1053
  }
944
1054
  /**
945
- * Emits token usage from a AgentResultDisplay execution summary, if present.
1055
+ * Maps core Tool Kind enum to ACP ToolKind string literals.
1056
+ *
1057
+ * @param kind - The core Kind enum value
1058
+ * @param toolName - Optional tool name to handle special cases like exit_plan_mode
946
1059
  */
947
- async emitTaskUsageFromResultDisplay(resultDisplay) {
948
- const summary = resultDisplay.executionSummary;
949
- if (!summary) {
950
- return;
951
- }
952
- const usageMetadata = {};
953
- if (Number.isFinite(summary.inputTokens)) {
954
- usageMetadata.promptTokenCount = summary.inputTokens;
955
- }
956
- if (Number.isFinite(summary.outputTokens)) {
957
- usageMetadata.candidatesTokenCount = summary.outputTokens;
958
- }
959
- if (Number.isFinite(summary.thoughtTokens)) {
960
- usageMetadata.thoughtsTokenCount = summary.thoughtTokens;
961
- }
962
- if (Number.isFinite(summary.cachedTokens)) {
963
- usageMetadata.cachedContentTokenCount = summary.cachedTokens;
964
- }
965
- if (Number.isFinite(summary.totalTokens)) {
966
- usageMetadata.totalTokenCount = summary.totalTokens;
967
- }
968
- if (Object.keys(usageMetadata).length > 0) {
969
- await this.messageEmitter.emitUsageMetadata(usageMetadata);
1060
+ mapToolKind(kind, toolName) {
1061
+ if (toolName && (this.isExitPlanModeTool(toolName) || this.isEnterPlanModeTool(toolName))) {
1062
+ return "switch_mode";
970
1063
  }
1064
+ return KIND_MAP[kind] ?? "other";
971
1065
  }
1066
+ // ==================== Private Helpers ====================
972
1067
  /**
973
- * Replays a slash_command system record by re-emitting its output as an
974
- * agent message chunk. This allows Zed to reconstruct the correct turn
975
- * structure (user → agent) on session resume without polluting model context.
1068
+ * Extracts diff content from resultDisplay if it's a diff type (edit tool result).
1069
+ * Returns null if not a diff.
976
1070
  */
977
- async replaySlashCommandResult(record) {
978
- const payload = record.systemPayload;
979
- if (payload?.phase !== "result" || !payload.outputHistoryItems?.length) {
980
- return;
981
- }
982
- for (const item of payload.outputHistoryItems) {
983
- const text = typeof item["text"] === "string" ? item["text"] : "";
984
- if (text) {
985
- await this.messageEmitter.emitAgentMessage(
986
- text.replace(/\n/g, " \n"),
987
- record.timestamp
988
- );
1071
+ extractDiffContent(resultDisplay) {
1072
+ if (!resultDisplay || typeof resultDisplay !== "object") return null;
1073
+ const obj = resultDisplay;
1074
+ if ("fileName" in obj && "newContent" in obj) {
1075
+ if (this.isTruncatedSessionDiffDisplay(resultDisplay)) {
1076
+ return {
1077
+ type: "content",
1078
+ content: {
1079
+ type: "text",
1080
+ text: buildTruncatedDiffPreviewText(obj)
1081
+ }
1082
+ };
989
1083
  }
1084
+ return {
1085
+ type: "diff",
1086
+ path: obj["fileName"],
1087
+ oldText: obj["originalContent"] ?? "",
1088
+ newText: obj["newContent"]
1089
+ };
990
1090
  }
1091
+ return null;
1092
+ }
1093
+ isTruncatedSessionDiffDisplay(resultDisplay) {
1094
+ if (!resultDisplay || typeof resultDisplay !== "object") return false;
1095
+ const obj = resultDisplay;
1096
+ return obj["truncatedForSession"] === true && "fileName" in obj && "newContent" in obj;
991
1097
  }
992
1098
  /**
993
- * Extracts tool name from a chat record's function response.
1099
+ * Transforms Part[] to ToolCallContent[].
1100
+ * Extracts text from functionResponse parts and text parts.
994
1101
  */
995
- extractToolNameFromRecord(record) {
996
- if (record.message?.parts) {
997
- for (const part of record.message.parts) {
998
- if ("functionResponse" in part && part.functionResponse?.name) {
999
- return part.functionResponse.name;
1102
+ transformPartsToToolCallContent(parts) {
1103
+ const result = [];
1104
+ for (const part of parts) {
1105
+ if ("text" in part && part.text) {
1106
+ result.push({
1107
+ type: "content",
1108
+ content: { type: "text", text: part.text }
1109
+ });
1110
+ }
1111
+ if ("functionResponse" in part && part.functionResponse) {
1112
+ try {
1113
+ const resp = part.functionResponse.response;
1114
+ const outputField = resp["output"];
1115
+ const errorField = resp["error"];
1116
+ const responseText = typeof outputField === "string" ? outputField : typeof errorField === "string" ? errorField : JSON.stringify(resp);
1117
+ result.push({
1118
+ type: "content",
1119
+ content: { type: "text", text: responseText }
1120
+ });
1121
+ } catch {
1000
1122
  }
1001
1123
  }
1002
1124
  }
1003
- return "";
1004
- }
1005
- setActiveRecordId(recordId, timestamp) {
1006
- const context = this.ctx;
1007
- if (typeof context.setActiveRecordId === "function") {
1008
- context.setActiveRecordId(recordId, timestamp);
1009
- }
1125
+ return result;
1010
1126
  }
1011
1127
  };
1012
1128
 
1013
- // packages/cli/src/services/setup-github.ts
1129
+ // packages/cli/src/acp-integration/session/history-replayer.ts
1014
1130
  init_esbuild_shims();
1015
- var import_undici2 = __toESM(require_undici(), 1);
1016
- import { promises as fsp } from "node:fs";
1017
- import * as path from "node:path";
1018
1131
 
1019
- // packages/cli/src/utils/gitUtils.ts
1132
+ // packages/cli/src/utils/chat-record-tool-call-id.ts
1020
1133
  init_esbuild_shims();
1021
- var import_undici = __toESM(require_undici(), 1);
1022
- import * as childProcess from "node:child_process";
1023
- var debugLogger = createDebugLogger("GIT");
1024
- async function runGit(args, opts = {}) {
1025
- return await new Promise((resolve2, reject) => {
1026
- childProcess.execFile(
1027
- "git",
1028
- args,
1029
- {
1030
- encoding: "utf-8",
1031
- ...opts.cwd ? { cwd: opts.cwd } : {}
1032
- },
1033
- (err, stdout) => {
1034
- if (err) {
1035
- reject(err);
1036
- return;
1037
- }
1038
- resolve2(String(stdout ?? "").trim());
1039
- }
1040
- );
1041
- });
1134
+ function getToolResultCallId(record) {
1135
+ return getExplicitToolResultCallId(record) ?? record.uuid;
1042
1136
  }
1043
- __name(runGit, "runGit");
1044
- var isGitHubRepositoryAsync = /* @__PURE__ */ __name(async (opts = {}) => {
1045
- try {
1046
- const remotes = await runGit(["remote", "-v"], opts);
1047
- return remotes.split("\n").some((line) => {
1048
- const remoteUrl = line.trim().split(/\s+/)[1];
1049
- return remoteUrl ? isGitHubRemoteUrl(remoteUrl) : false;
1050
- });
1051
- } catch (_error) {
1052
- debugLogger.debug(`Failed to get git remote:`, _error);
1053
- return false;
1054
- }
1055
- }, "isGitHubRepositoryAsync");
1056
- function isGitHubRemoteUrl(remoteUrl) {
1057
- if (remoteUrl.startsWith("git@github.com:")) {
1058
- return true;
1059
- }
1060
- if (remoteUrl.startsWith("git@")) {
1061
- return false;
1062
- }
1063
- try {
1064
- return new URL(remoteUrl).hostname === "github.com";
1065
- } catch {
1066
- return false;
1137
+ __name(getToolResultCallId, "getToolResultCallId");
1138
+ function getExplicitToolResultCallId(record) {
1139
+ const resultCallId = record.toolCallResult?.callId;
1140
+ if (typeof resultCallId === "string" && resultCallId.length > 0) {
1141
+ return resultCallId;
1067
1142
  }
1143
+ return extractFunctionResponseId(record);
1068
1144
  }
1069
- __name(isGitHubRemoteUrl, "isGitHubRemoteUrl");
1070
- var getGitRepoRootAsync = /* @__PURE__ */ __name(async (opts = {}) => {
1071
- const gitRepoRoot = await runGit(["rev-parse", "--show-toplevel"], opts);
1072
- if (!gitRepoRoot) {
1073
- throw new Error(`Git repo returned empty value`);
1145
+ __name(getExplicitToolResultCallId, "getExplicitToolResultCallId");
1146
+ function extractFunctionResponseId(record) {
1147
+ if (!record.message?.parts) {
1148
+ return void 0;
1074
1149
  }
1075
- return gitRepoRoot;
1076
- }, "getGitRepoRootAsync");
1077
- var getLatestGitHubRelease = /* @__PURE__ */ __name(async (proxy) => {
1078
- try {
1079
- const controller = new AbortController();
1080
- const endpoint = `https://api.github.com/repos/QwenLM/qwen-code-action/releases/latest`;
1081
- const response = await fetch(endpoint, {
1082
- method: "GET",
1083
- headers: {
1084
- Accept: "application/vnd.github+json",
1085
- "Content-Type": "application/json",
1086
- "X-GitHub-Api-Version": "2022-11-28"
1087
- },
1088
- dispatcher: proxy ? new import_undici.ProxyAgent(proxy) : void 0,
1089
- signal: AbortSignal.any([AbortSignal.timeout(3e4), controller.signal])
1090
- });
1091
- if (!response.ok) {
1092
- throw new Error(
1093
- `Invalid response code: ${response.status} - ${response.statusText}`
1094
- );
1095
- }
1096
- const releaseTag = (await response.json()).tag_name;
1097
- if (!releaseTag) {
1098
- throw new Error(`Response did not include tag_name field`);
1150
+ for (const part of record.message.parts) {
1151
+ const id = "functionResponse" in part ? part.functionResponse?.id : void 0;
1152
+ if (typeof id === "string" && id.length > 0) {
1153
+ return id;
1099
1154
  }
1100
- return releaseTag;
1101
- } catch (_error) {
1102
- debugLogger.debug(
1103
- `Failed to determine latest qwen-code-action release:`,
1104
- _error
1105
- );
1106
- throw new Error(
1107
- `Unable to determine the latest qwen-code-action release on GitHub.`
1108
- );
1109
- }
1110
- }, "getLatestGitHubRelease");
1111
- async function getGitHubRepoInfoAsync(opts = {}) {
1112
- return parseGitHubRepoInfo(
1113
- await runGit(["remote", "get-url", "origin"], opts)
1114
- );
1115
- }
1116
- __name(getGitHubRepoInfoAsync, "getGitHubRepoInfoAsync");
1117
- function parseGitHubRepoInfo(remoteUrl) {
1118
- let urlToParse = remoteUrl;
1119
- if (remoteUrl.startsWith("git@github.com:")) {
1120
- urlToParse = remoteUrl.replace("git@github.com:", "");
1121
- } else if (remoteUrl.startsWith("git@")) {
1122
- throw new Error(
1123
- `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
1124
- );
1125
- }
1126
- let parsedUrl;
1127
- try {
1128
- parsedUrl = new URL(urlToParse, "https://github.com");
1129
- } catch {
1130
- throw new Error(
1131
- `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
1132
- );
1133
- }
1134
- if (parsedUrl.hostname !== "github.com") {
1135
- throw new Error(
1136
- `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
1137
- );
1138
- }
1139
- const parts = parsedUrl.pathname.split("/").filter((part) => part !== "");
1140
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
1141
- throw new Error(
1142
- `Owner & repo could not be extracted from remote URL: ${remoteUrl}`
1143
- );
1144
1155
  }
1145
- return { owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
1156
+ return void 0;
1146
1157
  }
1147
- __name(parseGitHubRepoInfo, "parseGitHubRepoInfo");
1158
+ __name(extractFunctionResponseId, "extractFunctionResponseId");
1148
1159
 
1149
- // packages/cli/src/services/setup-github.ts
1150
- var debugLogger2 = createDebugLogger("SETUP_GITHUB");
1151
- var GITHUB_WORKFLOW_PATHS = [
1152
- "qwen-dispatch/qwen-dispatch.yml",
1153
- "qwen-assistant/qwen-invoke.yml",
1154
- "issue-triage/qwen-triage.yml",
1155
- "issue-triage/qwen-scheduled-triage.yml",
1156
- "pr-review/qwen-review.yml"
1157
- ];
1158
- var GITIGNORE_ENTRIES = [".qwen/", "gha-creds-*.json"];
1159
- var MAX_WORKFLOW_DOWNLOAD_BYTES = 5 * 1024 * 1024;
1160
- var SetupGithubError = class extends Error {
1160
+ // packages/cli/src/acp-integration/session/history-replayer.ts
1161
+ var MISSING_TOOL_RESULT_MESSAGE = "Tool result missing from saved history; the previous run likely ended before this tool completed.";
1162
+ var HistoryReplayer = class {
1161
1163
  static {
1162
- __name(this, "SetupGithubError");
1164
+ __name(this, "HistoryReplayer");
1163
1165
  }
1164
- code;
1165
- status;
1166
- partial;
1167
- partialResult;
1168
- constructor(code, message, status, partialResult) {
1169
- super(message);
1170
- this.name = "SetupGithubError";
1171
- this.code = code;
1172
- this.status = status;
1173
- this.partial = partialResult !== void 0;
1174
- this.partialResult = partialResult;
1166
+ ctx;
1167
+ messageEmitter;
1168
+ toolCallEmitter;
1169
+ pendingReplayToolCalls = /* @__PURE__ */ new Map();
1170
+ constructor(ctx) {
1171
+ this.ctx = ctx;
1172
+ this.messageEmitter = new MessageEmitter(ctx);
1173
+ this.toolCallEmitter = new ToolCallEmitter(ctx);
1175
1174
  }
1176
- };
1177
- var nodeFileOps = {
1178
- assertCanWrite() {
1179
- },
1180
- async ensureWorkflowDirectory(gitRepoRoot) {
1181
- await fsp.mkdir(path.join(gitRepoRoot, ".github", "workflows"), {
1182
- recursive: true
1183
- });
1184
- },
1185
- async writeTextFile(gitRepoRoot, relativePath, content) {
1186
- const target = path.join(gitRepoRoot, relativePath);
1187
- await fsp.writeFile(target, content, { mode: 420 });
1188
- return { sizeBytes: Buffer.byteLength(content, "utf8") };
1189
- },
1190
- async readTextFile(gitRepoRoot, relativePath) {
1175
+ /**
1176
+ * Replays all chat records from a loaded session.
1177
+ *
1178
+ * @param records - Array of chat records to replay
1179
+ * @param gaps - Optional detected history gaps; a visible notice is emitted
1180
+ * immediately before each gap's child record so the user sees that an
1181
+ * earlier segment was lost rather than assuming the halves are contiguous.
1182
+ */
1183
+ async replay(records, gaps) {
1184
+ try {
1185
+ await this.replayPage(records, { finalizeDangling: true, gaps });
1186
+ } finally {
1187
+ this.pendingReplayToolCalls.clear();
1188
+ this.setActiveRecordId(null);
1189
+ }
1190
+ }
1191
+ async replayPage(records, options = {}) {
1192
+ this.pendingReplayToolCalls.clear();
1193
+ for (const pending of options.pendingToolCalls ?? []) {
1194
+ this.pendingReplayToolCalls.set(pending.callId, pending);
1195
+ }
1196
+ const gapByChildUuid = indexGapsByChild(options.gaps);
1197
+ let replayError;
1191
1198
  try {
1192
- return await fsp.readFile(path.join(gitRepoRoot, relativePath), "utf8");
1199
+ for (const record of records) {
1200
+ const gap = gapByChildUuid.get(record.uuid);
1201
+ if (gap) {
1202
+ await this.emitHistoryGapNotice(gap, record.timestamp);
1203
+ }
1204
+ await this.replayRecord(record);
1205
+ }
1193
1206
  } catch (error) {
1194
- if (error.code === "ENOENT") {
1195
- return void 0;
1207
+ replayError = error;
1208
+ }
1209
+ let danglingError;
1210
+ if (options.finalizeDangling === true) {
1211
+ try {
1212
+ await this.failDanglingToolCalls();
1213
+ } catch (error) {
1214
+ danglingError = error;
1196
1215
  }
1197
- throw error;
1198
1216
  }
1217
+ const state = {
1218
+ pendingToolCalls: options.finalizeDangling === true ? [] : Array.from(this.pendingReplayToolCalls.values())
1219
+ };
1220
+ this.setActiveRecordId(null);
1221
+ if (replayError && danglingError) {
1222
+ throw new AggregateError(
1223
+ [replayError, danglingError],
1224
+ "Replay and dangling-cleanup both failed"
1225
+ );
1226
+ }
1227
+ if (replayError) {
1228
+ throw replayError;
1229
+ }
1230
+ if (danglingError) {
1231
+ throw danglingError;
1232
+ }
1233
+ return state;
1199
1234
  }
1200
- };
1201
- async function setupGithub(options = {}) {
1202
- const cwd = options.cwd ?? process.cwd();
1203
- const fileOps = options.fileOps ?? nodeFileOps;
1204
- if (!await isGitHubRepositoryAsync({ cwd })) {
1205
- throw new SetupGithubError(
1206
- "github_repository_not_found",
1207
- "Unable to determine the GitHub repository. /setup-github must be run from a git repository.",
1208
- 400
1209
- );
1210
- }
1211
- let gitRepoRoot;
1212
- try {
1213
- gitRepoRoot = await getGitRepoRootAsync({ cwd });
1214
- } catch (error) {
1215
- debugLogger2.debug("Failed to get git repo root:", error);
1216
- throw new SetupGithubError(
1217
- "github_repository_not_found",
1218
- "Unable to determine the GitHub repository. /setup-github must be run from a git repository.",
1219
- 400
1220
- );
1235
+ getPendingToolCalls() {
1236
+ return Array.from(this.pendingReplayToolCalls.values());
1221
1237
  }
1222
- if (options.workspaceRoot) {
1223
- const [gitRootReal, workspaceReal] = await Promise.all([
1224
- realpathOrResolve(gitRepoRoot),
1225
- realpathOrResolve(options.workspaceRoot)
1226
- ]);
1227
- if (gitRootReal !== workspaceReal) {
1228
- throw new SetupGithubError(
1229
- "github_git_root_mismatch",
1230
- "The Git repository root must match the daemon workspace root.",
1231
- 400
1232
- );
1238
+ /**
1239
+ * Replays a single chat record.
1240
+ */
1241
+ async replayRecord(record) {
1242
+ this.setActiveRecordId(record.uuid, record.timestamp);
1243
+ try {
1244
+ switch (record.type) {
1245
+ case "user":
1246
+ if (record.subtype === "notification" || record.subtype === "cron") {
1247
+ const displayText = record.systemPayload?.displayText;
1248
+ if (displayText) {
1249
+ await this.messageEmitter.emitUserMessage(
1250
+ displayText,
1251
+ record.timestamp,
1252
+ record.subtype === "cron" ? { source: "cron" } : void 0
1253
+ );
1254
+ }
1255
+ break;
1256
+ }
1257
+ if (record.subtype === "mid_turn_user_message") {
1258
+ const displayText = record.systemPayload?.displayText;
1259
+ if (displayText) {
1260
+ await this.messageEmitter.emitUserMessage(
1261
+ displayText,
1262
+ record.timestamp
1263
+ );
1264
+ } else if (record.message) {
1265
+ await this.replayContent(
1266
+ record.message,
1267
+ "user",
1268
+ record.timestamp,
1269
+ record.uuid
1270
+ );
1271
+ }
1272
+ break;
1273
+ }
1274
+ if (record.message) {
1275
+ await this.replayContent(
1276
+ record.message,
1277
+ "user",
1278
+ record.timestamp,
1279
+ record.uuid
1280
+ );
1281
+ }
1282
+ break;
1283
+ case "assistant":
1284
+ if (record.message) {
1285
+ await this.replayContent(
1286
+ record.message,
1287
+ "assistant",
1288
+ record.timestamp,
1289
+ record.uuid
1290
+ );
1291
+ }
1292
+ if (record.usageMetadata) {
1293
+ await this.replayUsageMetadata(record.usageMetadata);
1294
+ }
1295
+ break;
1296
+ case "tool_result":
1297
+ await this.replayToolResult(record);
1298
+ break;
1299
+ case "system":
1300
+ if (record.subtype === "slash_command") {
1301
+ await this.replaySlashCommandResult(record);
1302
+ }
1303
+ break;
1304
+ default:
1305
+ break;
1306
+ }
1307
+ } finally {
1308
+ this.setActiveRecordId(null);
1233
1309
  }
1234
1310
  }
1235
- fileOps.assertCanWrite?.();
1236
- let releaseTag;
1237
- try {
1238
- releaseTag = await getLatestGitHubRelease(options.proxy);
1239
- } catch (error) {
1240
- writeStderrLine(
1241
- `qwen setup-github: failed to determine latest qwen-code-action release: ${error instanceof Error ? error.message : String(error)}`
1242
- );
1243
- debugLogger2.debug(
1244
- "Failed to determine latest qwen-code-action release:",
1245
- error
1246
- );
1247
- throw new SetupGithubError(
1248
- "github_release_lookup_failed",
1249
- "Unable to determine the latest qwen-code-action release on GitHub.",
1250
- 502
1311
+ /**
1312
+ * Emits a visible notice marking a break in the persisted history chain: an
1313
+ * earlier segment was physically lost (storage interruption) and could not be
1314
+ * recovered, so the surviving turns below must not be read as contiguous with
1315
+ * whatever came before the gap. Uses the agent message channel — the same one
1316
+ * used for other system notices (see MessageEmitter.emitStopHookLoop) — so no
1317
+ * new session-update kind is needed.
1318
+ */
1319
+ async emitHistoryGapNotice(gap, timestamp) {
1320
+ await this.messageEmitter.emitAgentMessage(
1321
+ formatHistoryGapNotice(gap),
1322
+ timestamp
1251
1323
  );
1252
1324
  }
1253
- const readmeUrl = `https://github.com/QwenLM/qwen-code-action/blob/${releaseTag}/README.md#quick-start`;
1254
- const secretsUrl = await resolveSecretsUrl(cwd);
1255
- const downloads = await downloadWorkflows({
1256
- releaseTag,
1257
- proxy: options.proxy,
1258
- abortSignal: options.abortSignal,
1259
- fetchImpl: options.fetchImpl ?? fetch
1260
- });
1261
- const result = {
1262
- kind: "github_setup",
1263
- workspaceCwd: options.workspaceRoot ?? gitRepoRoot,
1264
- gitRepoRoot,
1265
- releaseTag,
1266
- readmeUrl,
1267
- ...secretsUrl ? { secretsUrl } : {},
1268
- workflows: [],
1269
- gitignore: { path: ".gitignore", status: "skipped" },
1270
- warnings: []
1271
- };
1272
- result.gitignore = await updateGitignore(gitRepoRoot, fileOps);
1273
- if (result.gitignore.status === "failed") {
1274
- result.warnings.push("Failed to update .gitignore.");
1325
+ /**
1326
+ * Replays content from a message (user or assistant).
1327
+ * Handles text parts, thought parts, and function calls.
1328
+ *
1329
+ * @param content - The content to replay
1330
+ * @param role - The role (user or assistant)
1331
+ * @param timestamp - Optional server-side timestamp from the JSONL record
1332
+ */
1333
+ async replayContent(content, role, timestamp, recordId) {
1334
+ for (const part of content.parts ?? []) {
1335
+ if ("text" in part && part.text) {
1336
+ const isThought = part.thought ?? false;
1337
+ await this.messageEmitter.emitMessage(
1338
+ part.text,
1339
+ role,
1340
+ isThought,
1341
+ timestamp
1342
+ );
1343
+ }
1344
+ if ("functionCall" in part && part.functionCall) {
1345
+ const functionName = part.functionCall.name ?? "";
1346
+ const sourceCallId = part.functionCall.id;
1347
+ const callId = sourceCallId ?? `${functionName}-${Date.now()}`;
1348
+ const emitted = await this.toolCallEmitter.emitStart({
1349
+ toolName: functionName,
1350
+ callId,
1351
+ args: part.functionCall.args,
1352
+ status: "in_progress",
1353
+ timestamp
1354
+ });
1355
+ if (emitted && role === "assistant" && recordId && sourceCallId) {
1356
+ this.pendingReplayToolCalls.set(callId, {
1357
+ callId,
1358
+ toolName: functionName,
1359
+ timestamp,
1360
+ recordId
1361
+ });
1362
+ }
1363
+ }
1364
+ }
1275
1365
  }
1276
- try {
1277
- await fileOps.ensureWorkflowDirectory(gitRepoRoot);
1278
- for (const workflow of downloads) {
1279
- const relativePath = path.posix.join(
1280
- ".github",
1281
- "workflows",
1282
- path.posix.basename(workflow.sourcePath)
1366
+ /**
1367
+ * Replays usage metadata.
1368
+ * @param usageMetadata - The usage metadata to replay
1369
+ */
1370
+ async replayUsageMetadata(usageMetadata) {
1371
+ await this.messageEmitter.emitUsageMetadata(usageMetadata);
1372
+ }
1373
+ /**
1374
+ * Replays a tool result record.
1375
+ */
1376
+ async replayToolResult(record) {
1377
+ if (!record.message?.parts) {
1378
+ return;
1379
+ }
1380
+ const result = record.toolCallResult;
1381
+ const callId = getToolResultCallId(record);
1382
+ this.pendingReplayToolCalls.delete(callId);
1383
+ const toolName = this.extractToolNameFromRecord(record);
1384
+ await this.toolCallEmitter.emitResult({
1385
+ toolName,
1386
+ callId,
1387
+ success: !result?.error,
1388
+ message: record.message.parts,
1389
+ resultDisplay: result?.resultDisplay,
1390
+ artifacts: result?.artifacts,
1391
+ // For TodoWriteTool fallback, try to extract args from the record
1392
+ // Note: args aren't stored in tool_result records by default
1393
+ args: void 0,
1394
+ timestamp: record.timestamp
1395
+ });
1396
+ const { resultDisplay } = result ?? {};
1397
+ if (!!resultDisplay && typeof resultDisplay === "object" && "type" in resultDisplay && resultDisplay.type === "task_execution") {
1398
+ await this.emitTaskUsageFromResultDisplay(
1399
+ resultDisplay
1283
1400
  );
1401
+ }
1402
+ }
1403
+ async failDanglingToolCalls() {
1404
+ let firstError;
1405
+ for (const pending of this.pendingReplayToolCalls.values()) {
1406
+ this.setActiveRecordId(pending.recordId, pending.timestamp);
1284
1407
  try {
1285
- const write = await fileOps.writeTextFile(
1286
- gitRepoRoot,
1287
- relativePath,
1288
- workflow.content
1289
- );
1290
- result.workflows.push({
1291
- sourcePath: workflow.sourcePath,
1292
- path: relativePath,
1293
- status: "written",
1294
- sizeBytes: write.sizeBytes
1408
+ await this.toolCallEmitter.emitResult({
1409
+ toolName: pending.toolName,
1410
+ callId: pending.callId,
1411
+ success: false,
1412
+ message: [],
1413
+ error: new Error(MISSING_TOOL_RESULT_MESSAGE),
1414
+ timestamp: pending.timestamp
1295
1415
  });
1296
1416
  } catch (error) {
1297
- result.partial = true;
1298
- result.workflows.push({
1299
- sourcePath: workflow.sourcePath,
1300
- path: relativePath,
1301
- status: "failed",
1302
- error: error instanceof Error ? error.message : String(error)
1303
- });
1304
- throw new SetupGithubError(
1305
- "github_workflow_write_failed",
1306
- `Unable to write ${relativePath}.`,
1307
- 500,
1308
- result
1309
- );
1417
+ firstError ??= error;
1418
+ } finally {
1419
+ this.setActiveRecordId(null);
1310
1420
  }
1311
1421
  }
1312
- } catch (error) {
1313
- if (error instanceof SetupGithubError) throw error;
1314
- throw new SetupGithubError(
1315
- "github_workflow_write_failed",
1316
- "Unable to create .github/workflows.",
1317
- 500,
1318
- result
1319
- );
1422
+ if (firstError) {
1423
+ throw firstError;
1424
+ }
1320
1425
  }
1321
- return result;
1322
- }
1323
- __name(setupGithub, "setupGithub");
1324
- async function updateGitignore(gitRepoRoot, fileOps = nodeFileOps) {
1325
- try {
1326
- const existingContent = await fileOps.readTextFile(
1327
- gitRepoRoot,
1328
- ".gitignore"
1329
- );
1330
- if (existingContent === void 0) {
1331
- const content = GITIGNORE_ENTRIES.join("\n") + "\n";
1332
- await fileOps.writeTextFile(gitRepoRoot, ".gitignore", content);
1333
- return {
1334
- path: ".gitignore",
1335
- status: "created",
1336
- added: [...GITIGNORE_ENTRIES]
1337
- };
1426
+ /**
1427
+ * Emits token usage from a AgentResultDisplay execution summary, if present.
1428
+ */
1429
+ async emitTaskUsageFromResultDisplay(resultDisplay) {
1430
+ const summary = resultDisplay.executionSummary;
1431
+ if (!summary) {
1432
+ return;
1338
1433
  }
1339
- const missingEntries = GITIGNORE_ENTRIES.filter(
1340
- (entry) => !existingContent.split(/\r?\n/).some((line) => line.split("#")[0].trim() === entry)
1341
- );
1342
- if (missingEntries.length === 0) {
1343
- return { path: ".gitignore", status: "unchanged" };
1434
+ const usageMetadata = {};
1435
+ if (Number.isFinite(summary.inputTokens)) {
1436
+ usageMetadata.promptTokenCount = summary.inputTokens;
1344
1437
  }
1345
- const nextContent = existingContent + "\n" + missingEntries.join("\n") + "\n";
1346
- await fileOps.writeTextFile(gitRepoRoot, ".gitignore", nextContent);
1347
- return {
1348
- path: ".gitignore",
1349
- status: "updated",
1350
- added: missingEntries
1351
- };
1352
- } catch (error) {
1353
- debugLogger2.debug("Failed to update .gitignore:", error);
1354
- return {
1355
- path: ".gitignore",
1356
- status: "failed",
1357
- error: error instanceof Error ? error.message : String(error)
1358
- };
1359
- }
1360
- }
1361
- __name(updateGitignore, "updateGitignore");
1362
- async function downloadWorkflows(options) {
1363
- const internalAbort = new AbortController();
1364
- try {
1365
- const dispatcher = options.proxy ? new import_undici2.ProxyAgent(options.proxy) : void 0;
1366
- return await Promise.all(
1367
- GITHUB_WORKFLOW_PATHS.map(async (workflow) => {
1368
- const endpoint = `https://raw.githubusercontent.com/QwenLM/qwen-code-action/refs/tags/${options.releaseTag}/examples/workflows/${workflow}`;
1369
- const response = await options.fetchImpl(endpoint, {
1370
- method: "GET",
1371
- dispatcher,
1372
- signal: AbortSignal.any([
1373
- AbortSignal.timeout(3e4),
1374
- internalAbort.signal,
1375
- ...options.abortSignal ? [options.abortSignal] : []
1376
- ])
1377
- });
1378
- if (!response.ok) {
1379
- throw new Error(
1380
- `Invalid response code downloading ${endpoint}: ${response.status} - ${response.statusText}`
1381
- );
1382
- }
1383
- return {
1384
- sourcePath: workflow,
1385
- content: await readResponseTextWithLimit(response, workflow)
1386
- };
1387
- })
1388
- );
1389
- } catch (error) {
1390
- internalAbort.abort();
1391
- const message = error instanceof Error ? error.message : String(error);
1392
- debugLogger2.debug("Failed to download qwen-code-action workflows:", error);
1393
- throw new SetupGithubError(
1394
- "github_workflow_download_failed",
1395
- `Unable to download qwen-code-action workflows from GitHub. ${message}`,
1396
- 502
1397
- );
1398
- }
1399
- }
1400
- __name(downloadWorkflows, "downloadWorkflows");
1401
- async function readResponseTextWithLimit(response, sourcePath) {
1402
- const contentLength = response.headers.get("content-length");
1403
- if (contentLength !== null) {
1404
- const parsedLength = Number(contentLength);
1405
- if (Number.isFinite(parsedLength) && parsedLength > MAX_WORKFLOW_DOWNLOAD_BYTES) {
1406
- throw new Error(
1407
- `${sourcePath} exceeds download limit of ${MAX_WORKFLOW_DOWNLOAD_BYTES} bytes`
1408
- );
1438
+ if (Number.isFinite(summary.outputTokens)) {
1439
+ usageMetadata.candidatesTokenCount = summary.outputTokens;
1440
+ }
1441
+ if (Number.isFinite(summary.thoughtTokens)) {
1442
+ usageMetadata.thoughtsTokenCount = summary.thoughtTokens;
1443
+ }
1444
+ if (Number.isFinite(summary.cachedTokens)) {
1445
+ usageMetadata.cachedContentTokenCount = summary.cachedTokens;
1446
+ }
1447
+ if (Number.isFinite(summary.totalTokens)) {
1448
+ usageMetadata.totalTokenCount = summary.totalTokens;
1449
+ }
1450
+ if (Object.keys(usageMetadata).length > 0) {
1451
+ await this.messageEmitter.emitUsageMetadata(usageMetadata);
1409
1452
  }
1410
1453
  }
1411
- if (!response.body) return "";
1412
- const reader = response.body.getReader();
1413
- const chunks = [];
1414
- let totalBytes = 0;
1415
- try {
1416
- while (true) {
1417
- const { done, value } = await reader.read();
1418
- if (done) break;
1419
- if (!value) continue;
1420
- totalBytes += value.byteLength;
1421
- if (totalBytes > MAX_WORKFLOW_DOWNLOAD_BYTES) {
1422
- await reader.cancel().catch(() => {
1423
- });
1424
- throw new Error(
1425
- `${sourcePath} exceeds download limit of ${MAX_WORKFLOW_DOWNLOAD_BYTES} bytes`
1454
+ /**
1455
+ * Replays a slash_command system record by re-emitting its output as an
1456
+ * agent message chunk. This allows Zed to reconstruct the correct turn
1457
+ * structure (user → agent) on session resume without polluting model context.
1458
+ */
1459
+ async replaySlashCommandResult(record) {
1460
+ const payload = record.systemPayload;
1461
+ if (payload?.phase !== "result" || !payload.outputHistoryItems?.length) {
1462
+ return;
1463
+ }
1464
+ for (const item of payload.outputHistoryItems) {
1465
+ const text = typeof item["text"] === "string" ? item["text"] : "";
1466
+ if (text) {
1467
+ await this.messageEmitter.emitSlashCommandOutput(
1468
+ text.replace(/\n/g, " \n"),
1469
+ record.timestamp
1426
1470
  );
1427
1471
  }
1428
- chunks.push(value);
1429
1472
  }
1430
- } finally {
1431
- reader.releaseLock();
1432
- }
1433
- const body = new Uint8Array(totalBytes);
1434
- let offset = 0;
1435
- for (const chunk of chunks) {
1436
- body.set(chunk, offset);
1437
- offset += chunk.byteLength;
1438
1473
  }
1439
- return new TextDecoder().decode(body);
1440
- }
1441
- __name(readResponseTextWithLimit, "readResponseTextWithLimit");
1442
- async function resolveSecretsUrl(cwd) {
1443
- try {
1444
- const repoInfo = await getGitHubRepoInfoAsync({ cwd });
1445
- return `https://github.com/${repoInfo.owner}/${repoInfo.repo}/settings/secrets/actions`;
1446
- } catch {
1447
- return void 0;
1474
+ /**
1475
+ * Extracts tool name from a chat record's function response.
1476
+ */
1477
+ extractToolNameFromRecord(record) {
1478
+ if (record.message?.parts) {
1479
+ for (const part of record.message.parts) {
1480
+ if ("functionResponse" in part && part.functionResponse?.name) {
1481
+ return part.functionResponse.name;
1482
+ }
1483
+ }
1484
+ }
1485
+ return "";
1448
1486
  }
1449
- }
1450
- __name(resolveSecretsUrl, "resolveSecretsUrl");
1451
- async function realpathOrResolve(input) {
1452
- try {
1453
- return await fsp.realpath(input);
1454
- } catch {
1455
- return path.resolve(input);
1487
+ setActiveRecordId(recordId, timestamp) {
1488
+ const context = this.ctx;
1489
+ if (typeof context.setActiveRecordId === "function") {
1490
+ context.setActiveRecordId(recordId, timestamp);
1491
+ }
1456
1492
  }
1457
- }
1458
- __name(realpathOrResolve, "realpathOrResolve");
1493
+ };
1459
1494
 
1460
1495
  // packages/cli/src/ui/utils/export/collect.ts
1461
1496
  init_esbuild_shims();
@@ -1646,7 +1681,7 @@ async function extractMetadata(conversation, config) {
1646
1681
  const gitBranch = firstRecord?.gitBranch;
1647
1682
  let gitRepo;
1648
1683
  if (cwd) {
1649
- const { getGitRepoName } = await import("./src-G5UPLZSZ.js");
1684
+ const { getGitRepoName } = await import("./src-DWZT2X3Z.js");
1650
1685
  gitRepo = getGitRepoName(cwd);
1651
1686
  }
1652
1687
  let model;
@@ -2491,7 +2526,7 @@ export {
2491
2526
  };
2492
2527
  /**
2493
2528
  * @license
2494
- * Copyright 2025 Qwen
2529
+ * Copyright 2025 Google LLC
2495
2530
  * SPDX-License-Identifier: Apache-2.0
2496
2531
  */
2497
2532
  /**
@@ -2501,7 +2536,7 @@ export {
2501
2536
  */
2502
2537
  /**
2503
2538
  * @license
2504
- * Copyright 2025 Google LLC
2539
+ * Copyright 2025 Qwen
2505
2540
  * SPDX-License-Identifier: Apache-2.0
2506
2541
  */
2507
2542
  /**