@qwen-code/qwen-code 0.19.2 → 0.19.3-nightly.20260630.e00fe6a27

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 (286) hide show
  1. package/bundled/extension-creator/SKILL.md +357 -0
  2. package/bundled/loop/SKILL.md +21 -7
  3. package/bundled/loop/SKILL.test.ts +41 -0
  4. package/bundled/loop/loop-task-file.test.ts +1064 -0
  5. package/bundled/loop/loop-task-file.ts +404 -0
  6. package/bundled/loop/loop-tick-resolver.test.ts +665 -0
  7. package/bundled/loop/loop-tick-resolver.ts +347 -0
  8. package/bundled/qc-helper/SKILL.md +26 -19
  9. package/bundled/qc-helper/docs/configuration/auth.md +11 -11
  10. package/bundled/qc-helper/docs/configuration/model-providers.md +13 -13
  11. package/bundled/qc-helper/docs/configuration/settings.md +76 -75
  12. package/bundled/qc-helper/docs/features/channels/plugins.md +1 -1
  13. package/bundled/qc-helper/docs/features/commands.md +108 -77
  14. package/bundled/qc-helper/docs/features/hooks.md +6 -1
  15. package/bundled/qc-helper/docs/features/memory.md +52 -0
  16. package/bundled/qc-helper/docs/qwen-serve.md +24 -2
  17. package/bundled/qc-helper/docs/support/tos-privacy.md +16 -3
  18. package/bundled/qc-helper/docs/support/troubleshooting.md +6 -0
  19. package/chunks/{MaxSizedBox-7RERNLFK.js → MaxSizedBox-T5X5MN5Q.js} +29 -27
  20. package/chunks/{StandaloneSessionPicker-S22XBSN6.js → StandaloneSessionPicker-P3E3WTOQ.js} +42 -37
  21. package/chunks/{acpAgent-4MCETHF2.js → acpAgent-QEP6DXFW.js} +864 -175
  22. package/chunks/{agent-K3ENVYSB.js → agent-UDANDUF7.js} +24 -23
  23. package/chunks/{agent-headless-HO3V3A3A.js → agent-headless-KYVSZHSO.js} +24 -23
  24. package/chunks/{anthropicContentGenerator-E3TQ5XJJ.js → anthropicContentGenerator-KWSJAQ7B.js} +33 -12
  25. package/chunks/{artifact-tool-QA4KRO3B.js → artifact-tool-D2X7G3C3.js} +2 -2
  26. package/chunks/{askUserQuestion-HMWXW6VN.js → askUserQuestion-UGETICJQ.js} +2 -2
  27. package/chunks/{bridge-4LV2HBH2.js → bridge-X42LXPTA.js} +31 -30
  28. package/chunks/{ca-7WC7ELX6.js → ca-IUFWESZL.js} +1 -0
  29. package/chunks/{chunk-JXQ23SNT.js → chunk-2JKEQWDZ.js} +623 -78
  30. package/chunks/{chunk-2TJ3YN6Q.js → chunk-2RJA6J57.js} +416 -401
  31. package/chunks/{chunk-3UALQ56H.js → chunk-3227DZO4.js} +1 -1
  32. package/chunks/{chunk-BGUDAULQ.js → chunk-3CPCDXFR.js} +5 -5
  33. package/chunks/{chunk-5RVSM6NW.js → chunk-3HUHAQF3.js} +1 -1
  34. package/chunks/{chunk-D5E5TMYH.js → chunk-3M4W4U5G.js} +16 -2
  35. package/chunks/{chunk-DBZUXSSV.js → chunk-4X7NIEQJ.js} +3 -3
  36. package/chunks/chunk-5GOVVXEV.js +73 -0
  37. package/chunks/{chunk-2BAQ3ACS.js → chunk-5SM4VT6V.js} +405 -5
  38. package/chunks/{chunk-NYOHZYHM.js → chunk-5XQ4A26L.js} +45 -3
  39. package/chunks/{chunk-CI5O6SQL.js → chunk-6FKEVYCD.js} +4 -4
  40. package/chunks/{chunk-ZD3NW5KL.js → chunk-7LM4XHPE.js} +63 -9
  41. package/chunks/chunk-7UNP2XM7.js +840 -0
  42. package/chunks/{chunk-OQHQHJRK.js → chunk-ARYCHZ4B.js} +151 -4775
  43. package/chunks/chunk-BC3C4F3Q.js +40 -0
  44. package/chunks/{chunk-CGEGHF2C.js → chunk-BKXBARJZ.js} +1 -1
  45. package/chunks/chunk-BMFMJINR.js +45 -0
  46. package/chunks/chunk-BOJWHRMX.js +11273 -0
  47. package/chunks/{chunk-CGOMICBE.js → chunk-BSZEQNUA.js} +34 -1
  48. package/chunks/{chunk-OIC6S73E.js → chunk-BUUQ5HPX.js} +1 -1
  49. package/chunks/{chunk-2BIF2NVV.js → chunk-BVSVIN45.js} +2 -2
  50. package/chunks/{chunk-TOEGRAVX.js → chunk-BX5YO7UW.js} +17 -5
  51. package/chunks/{chunk-4W4T77MO.js → chunk-BXVWV4GI.js} +109 -6
  52. package/chunks/{chunk-VQRYPFCV.js → chunk-CYHVPQYT.js} +10 -460
  53. package/chunks/{chunk-PRYMDRBV.js → chunk-DDMOU62Q.js} +1 -1
  54. package/chunks/{chunk-S566HKUS.js → chunk-DHXWNSJA.js} +15 -1
  55. package/chunks/{chunk-F6XNTYNE.js → chunk-E6XQPQXW.js} +30 -307
  56. package/chunks/{chunk-QBZ22PQX.js → chunk-EWALXUAD.js} +3 -1
  57. package/chunks/{chunk-EL5IHQON.js → chunk-EWYQFWYF.js} +6 -27
  58. package/chunks/{chunk-HG6YKEBI.js → chunk-F2DOT6UZ.js} +161 -30
  59. package/chunks/{chunk-JTYRZODB.js → chunk-FOOIZCWL.js} +16 -7
  60. package/chunks/{chunk-EX4W6ZYG.js → chunk-G5G4TFZ7.js} +1 -1
  61. package/chunks/{chunk-EW6C27XX.js → chunk-GEHVIWAT.js} +9 -3
  62. package/chunks/chunk-HEVBZSXG.js +4923 -0
  63. package/chunks/chunk-HFQ2XSE7.js +690 -0
  64. package/chunks/{chunk-PF523XJX.js → chunk-HPGTGMP3.js} +1 -1
  65. package/chunks/{chunk-OTBK43JR.js → chunk-HYCC3XM3.js} +1 -1
  66. package/chunks/{chunk-TMBPDHXD.js → chunk-I4H7PRWQ.js} +1 -1
  67. package/chunks/{chunk-EEEZIVFJ.js → chunk-JOLIREYR.js} +1 -1
  68. package/chunks/{chunk-TZM5JIAX.js → chunk-JYPQTRA3.js} +10 -7
  69. package/chunks/chunk-K5XU6E4T.js +167 -0
  70. package/chunks/{chunk-BS64PXY3.js → chunk-KFVU7ODN.js} +1 -1
  71. package/chunks/{chunk-3RJ3ZPPR.js → chunk-KKQWC6WS.js} +7 -7
  72. package/chunks/{chunk-3QFE4OTE.js → chunk-LBW7NW7X.js} +4 -4
  73. package/chunks/{chunk-Z7XXFPGP.js → chunk-LDWTYE33.js} +1 -1
  74. package/chunks/{chunk-EXMX5YHG.js → chunk-LP5E4YIH.js} +1 -1
  75. package/chunks/{chunk-ZUAXI2OA.js → chunk-M2U5NM24.js} +188 -11366
  76. package/chunks/{chunk-I5T6PIBQ.js → chunk-MBV2CTRL.js} +1010 -925
  77. package/chunks/{chunk-5JZLXFPD.js → chunk-MTHMXSNT.js} +11 -11
  78. package/chunks/{chunk-FPACXT7T.js → chunk-MTJVHQDP.js} +534 -164
  79. package/chunks/chunk-NDOC6M5S.js +481 -0
  80. package/chunks/{chunk-HGNSRCRB.js → chunk-NL4C3H6C.js} +1 -1
  81. package/chunks/{chunk-GZCJS5WH.js → chunk-O2VBQ3Y2.js} +2 -2
  82. package/chunks/{chunk-UN3C6GOR.js → chunk-PPFWEV7G.js} +10 -108
  83. package/chunks/{chunk-RS4HRC75.js → chunk-QI2CFVRF.js} +85 -8
  84. package/chunks/{chunk-XU5P23GN.js → chunk-QLDPRILK.js} +3 -3
  85. package/chunks/chunk-QYUE6W3T.js +123 -0
  86. package/chunks/{chunk-USGMPVOY.js → chunk-R3LHDWF2.js} +21 -12
  87. package/chunks/{chunk-BNXANYLH.js → chunk-RD6YJJJ6.js} +81 -1
  88. package/chunks/{chunk-PXFFKNCO.js → chunk-RIP623NO.js} +4877 -2469
  89. package/chunks/{chunk-XKBP7M7A.js → chunk-RYBSUGOR.js} +142 -30
  90. package/chunks/{chunk-BG4BIP7F.js → chunk-SBBN4BWR.js} +4 -2
  91. package/chunks/{chunk-T2VDEFUU.js → chunk-SKQZZVAV.js} +2 -2
  92. package/chunks/{chunk-5R4SBCX6.js → chunk-SL5MOGQH.js} +3 -3
  93. package/chunks/chunk-SMXULRAN.js +37 -0
  94. package/chunks/{chunk-WA5OJGFL.js → chunk-SS6WJMQK.js} +363 -76
  95. package/chunks/{chunk-UAARVVBI.js → chunk-TNWFHGN6.js} +1 -1
  96. package/chunks/{chunk-I2RFAW6Q.js → chunk-TT6Q3EN4.js} +7 -5
  97. package/chunks/chunk-UAKXW3DN.js +30 -0
  98. package/chunks/{chunk-C4K3FEQ2.js → chunk-UX4SGCGN.js} +32 -3
  99. package/chunks/{chunk-J6AW7O5D.js → chunk-WHDQZHRU.js} +2 -2
  100. package/chunks/{chunk-WGAEWQTF.js → chunk-WN6K47NR.js} +1 -2
  101. package/chunks/{chunk-UAARJR2G.js → chunk-WQT5SOI6.js} +2 -2
  102. package/chunks/{chunk-VYFMN7C6.js → chunk-WUMVIRTN.js} +225 -37
  103. package/chunks/{chunk-R36VMVEP.js → chunk-XJMKOTVV.js} +5 -5
  104. package/chunks/{chunk-TXVGVZYR.js → chunk-XJYUPHRC.js} +3 -3
  105. package/chunks/{chunk-2JD55C6G.js → chunk-Y6YMRS3U.js} +2 -2
  106. package/chunks/{chunk-LLFFKXZZ.js → chunk-YALKC62D.js} +56 -29
  107. package/chunks/{chunk-GYZQA2FO.js → chunk-YLZJCUH6.js} +1 -1
  108. package/chunks/chunk-YTFBURQD.js +313 -0
  109. package/chunks/{chunk-KBXLIVWQ.js → chunk-ZFPRYHWR.js} +2 -2
  110. package/chunks/{chunk-X6EDWN44.js → chunk-ZP6UKOPB.js} +1 -1
  111. package/chunks/{computer-use-6BBRNT6S.js → computer-use-LF2S6LUZ.js} +103 -49
  112. package/chunks/{contextCommand-UJAJ6N57.js → contextCommand-VPX5UG55.js} +26 -25
  113. package/chunks/{cron-create-4KQE6YLH.js → cron-create-SKEY55RG.js} +4 -4
  114. package/chunks/{cron-delete-THME66UP.js → cron-delete-I3NAT32Z.js} +6 -6
  115. package/chunks/{cron-list-7A54SJ5N.js → cron-list-6XRRL6TH.js} +5 -5
  116. package/chunks/{daemon-status-provider-CSL3BUOY.js → daemon-status-provider-5JAWZS4V.js} +34 -34
  117. package/chunks/{de-ACHALDED.js → de-EOW5PVHF.js} +1 -0
  118. package/chunks/{dist-BD27IA3L.js → dist-4FCNM6KJ.js} +2 -2
  119. package/chunks/{dist-OD2FY543.js → dist-O3H4PABL.js} +2 -2
  120. package/chunks/{dist-BOOH5JWN.js → dist-SCAAUWPS.js} +18 -4
  121. package/chunks/{dist-QHJRBCMP.js → dist-UKPWQJCL.js} +44 -2
  122. package/chunks/{dist-QFFZWACU.js → dist-VD7HXTML.js} +20 -9
  123. package/chunks/{earlyInputCapture-X6KMCSHD.js → earlyInputCapture-7FRQMU4P.js} +25 -24
  124. package/chunks/{edit-7J6WXARF.js → edit-REKJDIRL.js} +68 -27
  125. package/chunks/{en-NCUBOJOY.js → en-HPWEJBTR.js} +8 -0
  126. package/chunks/{enter-worktree-KULCKJPJ.js → enter-worktree-GBKUDCD3.js} +24 -23
  127. package/chunks/{enterPlanMode-H2CIOLMH.js → enterPlanMode-WB3Y4FGT.js} +24 -23
  128. package/chunks/{errors-6G2JYR54.js → errors-2XJDYO3M.js} +26 -25
  129. package/chunks/{exit-worktree-ECBWBVC5.js → exit-worktree-KKC7O2ZV.js} +24 -23
  130. package/chunks/{exitPlanMode-DQV56GTW.js → exitPlanMode-7HMS3TF7.js} +24 -23
  131. package/chunks/{fast-path-35JJFS3C.js → fast-path-XVNIYWGG.js} +4 -4
  132. package/chunks/{fast-path-settings-TSVI53EB.js → fast-path-settings-UQLRFDQP.js} +3 -2
  133. package/chunks/{fr-GVKZ27MF.js → fr-BN5NKSAC.js} +1 -0
  134. package/chunks/{gemini-NDTG7WAX.js → gemini-U5TPU3IQ.js} +210 -104
  135. package/chunks/{geminiContentGenerator-XTG6SFLA.js → geminiContentGenerator-ZVWRHXOO.js} +4 -4
  136. package/chunks/{glob-SJ2JBZK4.js → glob-FEJWIH2W.js} +24 -23
  137. package/chunks/{grep-BRMWFEWR.js → grep-KMP25IIA.js} +24 -23
  138. package/chunks/{headlessSafetyWarnings-LJ6FJX77.js → headlessSafetyWarnings-5DEDET4M.js} +24 -23
  139. package/chunks/{initializer-P5AXF7PV.js → initializer-FJTACLOH.js} +31 -28
  140. package/chunks/{ja-FKZ2WZ4D.js → ja-7DEUB2TS.js} +1 -0
  141. package/chunks/{keychain-token-storage-CSYMNRPP.js → keychain-token-storage-YLMDLKXT.js} +2 -2
  142. package/chunks/{list-6COK34BP.js → list-SCQXYJUW.js} +32 -29
  143. package/chunks/{loadedSettingsAdapter-XIUTN245.js → loadedSettingsAdapter-P6KPNAG7.js} +30 -27
  144. package/chunks/{loop-wakeup-33GPXJLA.js → loop-wakeup-FYEQEJUA.js} +7 -7
  145. package/chunks/{ls-BYQY7RVV.js → ls-R5WP4BGZ.js} +4 -4
  146. package/chunks/{lsp-X54RO4IV.js → lsp-GSICJMMW.js} +2 -2
  147. package/chunks/{monitor-DH7KFSJV.js → monitor-JU3HHHVY.js} +24 -23
  148. package/chunks/nonInteractiveCli-IZEKUHU3.js +100 -0
  149. package/chunks/{notebook-edit-D4R4ZLMA.js → notebook-edit-YHY3Z2QQ.js} +52 -23
  150. package/chunks/{openaiContentGenerator-F74IPQ6E.js → openaiContentGenerator-4WUVA3Q2.js} +13 -12
  151. package/chunks/{pt-SY5NCFGR.js → pt-TNZRNLQB.js} +1 -0
  152. package/chunks/{qwenContentGenerator-ATTK5P4D.js → qwenContentGenerator-MVYYR44B.js} +26 -25
  153. package/chunks/{qwenOAuth2-K4AASPCV.js → qwenOAuth2-6HNGHALT.js} +5 -4
  154. package/chunks/{read-file-767G23OY.js → read-file-NABEO2X4.js} +8 -8
  155. package/chunks/{read-mcp-resource-I5HCZJ6X.js → read-mcp-resource-DQQW7H7T.js} +2 -2
  156. package/chunks/{ripGrep-4OMMGSWX.js → ripGrep-YXLK6UTX.js} +24 -23
  157. package/chunks/{ru-FWNXJG4F.js → ru-NIAUBKCN.js} +1 -0
  158. package/chunks/{run-qwen-serve-REKSTYRA.js → run-qwen-serve-4B5QBMLQ.js} +144 -41
  159. package/chunks/{scheduler-UNTFDQLG.js → scheduler-XAKIYBDL.js} +24 -23
  160. package/chunks/{send-message-2DAFXEYP.js → send-message-EE4TWKVU.js} +2 -2
  161. package/chunks/{serve-KZ56QB5S.js → serve-4RXVLUKD.js} +30 -28
  162. package/chunks/{server-BV5TZUMU.js → server-7PA5QGIA.js} +11037 -8747
  163. package/chunks/{session-OIBXFSGD.js → session-WNVWRYAH.js} +194 -57
  164. package/chunks/{settings-WJTCBIRA.js → settings-YJH32TFF.js} +30 -27
  165. package/chunks/{shell-WC52IBHW.js → shell-YFLABOS5.js} +24 -23
  166. package/chunks/{skill-U7F7JR4M.js → skill-N3J7FUAY.js} +38 -11
  167. package/chunks/{spawnChannel-EI2E5BCW.js → spawnChannel-D6WVMFZJ.js} +26 -25
  168. package/chunks/{src-4F6FBZFI.js → src-IU5GDBEH.js} +88 -28
  169. package/chunks/{startInteractiveUI-SFRVB3CE.js → startInteractiveUI-QIPQTRT4.js} +2784 -2474
  170. package/chunks/{syntheticOutput-PPS3HDVA.js → syntheticOutput-T5YHQ6S2.js} +3 -3
  171. package/chunks/{task-create-OUYCA553.js → task-create-BLY7RHXX.js} +6 -6
  172. package/chunks/{task-list-WAX7I2IA.js → task-list-3QUYNVYR.js} +5 -5
  173. package/chunks/{task-stop-CAIL6FNH.js → task-stop-U4RATK2V.js} +2 -2
  174. package/chunks/{task-update-AHEO5OUT.js → task-update-U54O7KJX.js} +6 -6
  175. package/chunks/{team-create-3WC7CIIW.js → team-create-TP4CLLM3.js} +24 -23
  176. package/chunks/{team-delete-HGHFG23B.js → team-delete-LKBYPASD.js} +5 -5
  177. package/chunks/{theme-manager-DICFCT3P.js → theme-manager-N2TBIBZT.js} +25 -24
  178. package/chunks/{todoWrite-IFBMN3FH.js → todoWrite-OYXKTVYN.js} +4 -4
  179. package/chunks/{tool-search-L5K7HLER.js → tool-search-GBBSICYB.js} +101 -16
  180. package/chunks/trustedFolders-F7TBU5CD.js +76 -0
  181. package/chunks/types-QX5C3CHJ.js +12 -0
  182. package/chunks/{validateNonInterActiveAuth-GCH4Z6KI.js → validateNonInterActiveAuth-EQDQPSMI.js} +55 -50
  183. package/chunks/{web-fetch-B5IIQ5TT.js → web-fetch-PCVCML2X.js} +29 -15
  184. package/chunks/{workflow-OB3AND7I.js → workflow-ANJ4FYBK.js} +32 -30
  185. package/chunks/{workspace-providers-status-ZIR5EPOO.js → workspace-providers-status-462QQKQM.js} +32 -29
  186. package/chunks/workspace-service-VTG4G6CW.js +75 -0
  187. package/chunks/{write-file-SEXBNDZM.js → write-file-3P3WMQQW.js} +63 -27
  188. package/chunks/{zh-RBEGQVQT.js → zh-5EUYXASN.js} +8 -0
  189. package/chunks/{zh-TW-QGAVRXT3.js → zh-TW-TR3XYGE5.js} +8 -0
  190. package/cli-entry.js +20 -9
  191. package/cli.js +6 -6
  192. package/locales/ca.js +2 -0
  193. package/locales/de.js +2 -0
  194. package/locales/en.js +14 -0
  195. package/locales/fr.js +2 -0
  196. package/locales/ja.js +1 -0
  197. package/locales/pt.js +2 -0
  198. package/locales/ru.js +2 -0
  199. package/locales/zh-TW.js +12 -0
  200. package/locales/zh.js +12 -0
  201. package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +35 -0
  202. package/node_modules/@qwen-code/audio-capture/dist/index.js +48 -0
  203. package/node_modules/@qwen-code/audio-capture/dist/index.js.map +1 -0
  204. package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +7 -0
  205. package/node_modules/@qwen-code/audio-capture/dist/platform.js +18 -0
  206. package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +1 -0
  207. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +21 -0
  208. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +58 -0
  209. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +5 -0
  210. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +84 -0
  211. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +19 -0
  212. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +6 -0
  213. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +207 -0
  214. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +7 -0
  215. package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +43 -0
  216. package/node_modules/@qwen-code/audio-capture/package.json +30 -0
  217. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  218. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  219. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
  220. package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
  221. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  222. package/package.json +18 -6
  223. package/patches/chrome-devtools-mcp+1.4.0.patch +31 -0
  224. package/postinstall.js +50 -0
  225. package/web-shell/assets/{arc-D_ae_Y9k.js → arc-DIemsO_F.js} +1 -1
  226. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BMLRjppa.js → architectureDiagram-3BPJPVTR-CTlUvIQv.js} +1 -1
  227. package/web-shell/assets/{blockDiagram-GPEHLZMM-qVDQC8KL.js → blockDiagram-GPEHLZMM-DG0jmIZK.js} +1 -1
  228. package/web-shell/assets/{c4Diagram-AAUBKEIU-CxWS6MBd.js → c4Diagram-AAUBKEIU-WljDBi55.js} +1 -1
  229. package/web-shell/assets/channel-CbzRbdpA.js +1 -0
  230. package/web-shell/assets/{chunk-2J33WTMH-BtNuY5Yp.js → chunk-2J33WTMH-8nc6onr2.js} +1 -1
  231. package/web-shell/assets/{chunk-4BX2VUAB-BU-x368d.js → chunk-4BX2VUAB-EopXix8Z.js} +1 -1
  232. package/web-shell/assets/{chunk-55IACEB6-DRgTLvBu.js → chunk-55IACEB6-BE4Jz3N3.js} +1 -1
  233. package/web-shell/assets/{chunk-727SXJPM-D2aElv7q.js → chunk-727SXJPM-C_eErgDN.js} +1 -1
  234. package/web-shell/assets/{chunk-AQP2D5EJ-BwHCMRQ0.js → chunk-AQP2D5EJ-DFtVW-Is.js} +1 -1
  235. package/web-shell/assets/{chunk-FMBD7UC4-BMyPH-ku.js → chunk-FMBD7UC4-CJPyxIg9.js} +1 -1
  236. package/web-shell/assets/{chunk-ND2GUHAM-BNKTstUf.js → chunk-ND2GUHAM-Bk-xgj6M.js} +1 -1
  237. package/web-shell/assets/{chunk-QZHKN3VN-gD3vsFId.js → chunk-QZHKN3VN-p3Y4yaFB.js} +1 -1
  238. package/web-shell/assets/classDiagram-4FO5ZUOK-BFdyQ52h.js +1 -0
  239. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BFdyQ52h.js +1 -0
  240. package/web-shell/assets/{cose-bilkent-S5V4N54A-BG9FAsXE.js → cose-bilkent-S5V4N54A-BCMO6Js3.js} +1 -1
  241. package/web-shell/assets/{dagre-BM42HDAG-Cy1sR7om.js → dagre-BM42HDAG-DUMrwASf.js} +1 -1
  242. package/web-shell/assets/default-aEtf0G7-.svg +1 -0
  243. package/web-shell/assets/{diagram-2AECGRRQ-Bxi1I_Vu.js → diagram-2AECGRRQ-C5d7qJbL.js} +1 -1
  244. package/web-shell/assets/{diagram-5GNKFQAL-BGCJjO6y.js → diagram-5GNKFQAL-CjLzMhbT.js} +1 -1
  245. package/web-shell/assets/{diagram-KO2AKTUF-CMTeLXp7.js → diagram-KO2AKTUF-RCoNWeAm.js} +1 -1
  246. package/web-shell/assets/{diagram-LMA3HP47-BN1wyAGh.js → diagram-LMA3HP47-KThXjaNV.js} +1 -1
  247. package/web-shell/assets/{diagram-OG6HWLK6-UYSG_zcp.js → diagram-OG6HWLK6-DKsC2BWz.js} +1 -1
  248. package/web-shell/assets/{erDiagram-TEJ5UH35-DqXL08lw.js → erDiagram-TEJ5UH35-DJ2ukmq0.js} +1 -1
  249. package/web-shell/assets/{flowDiagram-I6XJVG4X-BPJ9SFji.js → flowDiagram-I6XJVG4X-zzqaARRX.js} +1 -1
  250. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BEd9Pnms.js → ganttDiagram-6RSMTGT7-kFUg-gqT.js} +1 -1
  251. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DrNRH3oi.js → gitGraphDiagram-PVQCEYII-C6JnMVzD.js} +1 -1
  252. package/web-shell/assets/index-B3XUm7dw.js +713 -0
  253. package/web-shell/assets/index-CHmzAWys.css +5 -0
  254. package/web-shell/assets/{infoDiagram-5YYISTIA-CFYBrkVI.js → infoDiagram-5YYISTIA-XqPVhp89.js} +1 -1
  255. package/web-shell/assets/insert-DmRbh9Rv.svg +1 -0
  256. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-Dxsddth7.js → ishikawaDiagram-YF4QCWOH-D2k8sGpf.js} +1 -1
  257. package/web-shell/assets/{journeyDiagram-JHISSGLW-DxaDK80t.js → journeyDiagram-JHISSGLW-CQEST1GE.js} +1 -1
  258. package/web-shell/assets/{kanban-definition-UN3LZRKU-Q0jQCOct.js → kanban-definition-UN3LZRKU-DZ7kqVBC.js} +1 -1
  259. package/web-shell/assets/{linear-DGGM2kcP.js → linear-B6C_6q_E.js} +1 -1
  260. package/web-shell/assets/{mermaid.core-ByoxnWut.js → mermaid.core-CsyNP1qt.js} +5 -5
  261. package/web-shell/assets/{mindmap-definition-RKZ34NQL-C0ZseHsi.js → mindmap-definition-RKZ34NQL-B1VDIzJq.js} +1 -1
  262. package/web-shell/assets/{pieDiagram-4H26LBE5-MQXNhl5A.js → pieDiagram-4H26LBE5-DN0E7Jwu.js} +1 -1
  263. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DIR4ee2A.js → quadrantDiagram-W4KKPZXB-UKTQdHKm.js} +1 -1
  264. package/web-shell/assets/queue-DWpnpFgW.svg +1 -0
  265. package/web-shell/assets/{requirementDiagram-4Y6WPE33-BTR0UdYQ.js → requirementDiagram-4Y6WPE33-BRGceQvN.js} +1 -1
  266. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-sfnhMELN.js → sankeyDiagram-5OEKKPKP-2DDyu41v.js} +1 -1
  267. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DXczHOHH.js → sequenceDiagram-3UESZ5HK-CT5iE8ZU.js} +1 -1
  268. package/web-shell/assets/{stateDiagram-AJRCARHV-DdE_KQFH.js → stateDiagram-AJRCARHV-CqMQ_s4G.js} +1 -1
  269. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CamhV5pM.js +1 -0
  270. package/web-shell/assets/{timeline-definition-PNZ67QCA-DIc61uE8.js → timeline-definition-PNZ67QCA-Di37QSZU.js} +1 -1
  271. package/web-shell/assets/{vennDiagram-CIIHVFJN-BF7ODmxB.js → vennDiagram-CIIHVFJN-DCXvpz5S.js} +1 -1
  272. package/web-shell/assets/{wardley-L42UT6IY-3pc1MEaw.js → wardley-L42UT6IY-CpUCk6OX.js} +1 -1
  273. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-9jnPcZuP.js → wardleyDiagram-YWT4CUSO-D01pVEBg.js} +1 -1
  274. package/web-shell/assets/{xychartDiagram-2RQKCTM6-DcthaZK6.js → xychartDiagram-2RQKCTM6-DOu5JDoz.js} +1 -1
  275. package/web-shell/index.html +2 -2
  276. package/chunks/chunk-B6HQGTYR.js +0 -410
  277. package/chunks/chunk-R2BXK7SX.js +0 -13
  278. package/chunks/chunk-RKIZXJMB.js +0 -13
  279. package/chunks/nonInteractiveCli-7575LBXP.js +0 -91
  280. package/chunks/workspace-service-OM5D4OI2.js +0 -60
  281. package/web-shell/assets/channel-DYPNiTww.js +0 -1
  282. package/web-shell/assets/classDiagram-4FO5ZUOK-p_g25Tni.js +0 -1
  283. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-p_g25Tni.js +0 -1
  284. package/web-shell/assets/index-CpOXWDPh.js +0 -714
  285. package/web-shell/assets/index-oVK7qhre.css +0 -5
  286. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BHUSEndU.js +0 -1
@@ -41,6 +41,7 @@ __name(getGlobalQwenDir, "getGlobalQwenDir");
41
41
  // packages/channels/base/dist/SessionRouter.js
42
42
  init_esbuild_shims();
43
43
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
44
+ import process2 from "node:process";
44
45
  var SessionRouter = class {
45
46
  static {
46
47
  __name(this, "SessionRouter");
@@ -51,6 +52,8 @@ var SessionRouter = class {
51
52
  // session ID → target
52
53
  toCwd = /* @__PURE__ */ new Map();
53
54
  // session ID → cwd
55
+ creatingSessions = /* @__PURE__ */ new Map();
56
+ sessionLoadWindows = /* @__PURE__ */ new Set();
54
57
  bridge;
55
58
  defaultCwd;
56
59
  defaultScope;
@@ -82,57 +85,89 @@ var SessionRouter = class {
82
85
  return `${channelName}:${senderId}:${chatId}`;
83
86
  }
84
87
  }
85
- /**
86
- * Sender-scoped key prefix for by-sender (no chatId) scans. The trailing
87
- * ':' delimiter ensures a sender id matches as a whole segment — without it
88
- * sender "bob" would also match another sender "bobby"
89
- * (key `${channelName}:bobby:${chatId}`).
90
- */
91
- senderPrefix(channelName, senderId) {
92
- return `${channelName}:${senderId}:`;
93
- }
94
88
  async resolve(channelName, senderId, chatId, threadId, cwd) {
95
89
  const key = this.routingKey(channelName, senderId, chatId, threadId);
96
- const existing = this.toSession.get(key);
97
- if (existing) {
98
- return existing;
90
+ let failedCreateWaits = 0;
91
+ for (; ; ) {
92
+ const existing = this.toSession.get(key);
93
+ if (existing) {
94
+ return existing;
95
+ }
96
+ const creating = this.creatingSessions.get(key);
97
+ if (creating) {
98
+ try {
99
+ return await creating;
100
+ } catch (err) {
101
+ if (this.creatingSessions.get(key) === creating) {
102
+ this.creatingSessions.delete(key);
103
+ }
104
+ failedCreateWaits++;
105
+ if (failedCreateWaits > 3) {
106
+ throw err;
107
+ }
108
+ continue;
109
+ }
110
+ }
111
+ const created = Promise.resolve().then(async () => {
112
+ const sessionCwd = cwd || this.defaultCwd;
113
+ const loadWindow = this.beginSessionLoad();
114
+ try {
115
+ const sessionId = await this.createLiveSession(sessionCwd, loadWindow, key);
116
+ this.toSession.set(key, sessionId);
117
+ this.toTarget.set(sessionId, {
118
+ channelName,
119
+ senderId,
120
+ chatId,
121
+ threadId
122
+ });
123
+ this.toCwd.set(sessionId, sessionCwd);
124
+ this.persist();
125
+ return sessionId;
126
+ } finally {
127
+ this.endSessionLoad(loadWindow);
128
+ }
129
+ });
130
+ this.creatingSessions.set(key, created);
131
+ try {
132
+ return await created;
133
+ } finally {
134
+ if (this.creatingSessions.get(key) === created) {
135
+ this.creatingSessions.delete(key);
136
+ }
137
+ }
99
138
  }
100
- const sessionCwd = cwd || this.defaultCwd;
101
- const sessionId = await this.bridge.newSession(sessionCwd);
102
- this.toSession.set(key, sessionId);
103
- this.toTarget.set(sessionId, { channelName, senderId, chatId, threadId });
104
- this.toCwd.set(sessionId, sessionCwd);
105
- this.persist();
106
- return sessionId;
107
139
  }
108
140
  getTarget(sessionId) {
109
141
  return this.toTarget.get(sessionId);
110
142
  }
111
- hasSession(channelName, senderId, chatId) {
143
+ getSession(channelName, senderId, chatId, threadId) {
144
+ return this.toSession.get(this.routingKey(channelName, senderId, chatId, threadId));
145
+ }
146
+ hasSession(channelName, senderId, chatId, threadId) {
112
147
  if (chatId) {
113
- return this.toSession.has(this.routingKey(channelName, senderId, chatId));
148
+ return this.toSession.has(this.routingKey(channelName, senderId, chatId, threadId));
114
149
  }
115
- const prefix = this.senderPrefix(channelName, senderId);
116
- for (const k of this.toSession.keys()) {
117
- if (k.startsWith(prefix))
150
+ for (const target of this.toTarget.values()) {
151
+ if (target.channelName === channelName && target.senderId === senderId) {
118
152
  return true;
153
+ }
119
154
  }
120
155
  return false;
121
156
  }
122
157
  /**
123
158
  * Remove session(s) for the given sender. Returns the removed session IDs.
124
159
  */
125
- removeSession(channelName, senderId, chatId) {
160
+ removeSession(channelName, senderId, chatId, threadId) {
126
161
  const removedIds = [];
127
162
  if (chatId) {
128
- const key = this.routingKey(channelName, senderId, chatId);
163
+ const key = this.routingKey(channelName, senderId, chatId, threadId);
129
164
  const sessionId = this.deleteByKey(key);
130
165
  if (sessionId)
131
166
  removedIds.push(sessionId);
132
167
  } else {
133
- const prefix = this.senderPrefix(channelName, senderId);
134
- for (const k of [...this.toSession.keys()]) {
135
- if (k.startsWith(prefix)) {
168
+ for (const [k, mappedSessionId] of [...this.toSession.entries()]) {
169
+ const target = this.toTarget.get(mappedSessionId);
170
+ if (target?.channelName === channelName && target.senderId === senderId) {
136
171
  const sessionId = this.deleteByKey(k);
137
172
  if (sessionId)
138
173
  removedIds.push(sessionId);
@@ -143,6 +178,31 @@ var SessionRouter = class {
143
178
  this.persist();
144
179
  return removedIds;
145
180
  }
181
+ /** Remove a session mapping by daemon/ACP session ID. */
182
+ removeSessionId(sessionId) {
183
+ let removed = false;
184
+ for (const [key, mappedSessionId] of [...this.toSession.entries()]) {
185
+ if (mappedSessionId === sessionId) {
186
+ this.toSession.delete(key);
187
+ removed = true;
188
+ }
189
+ }
190
+ if (this.toTarget.delete(sessionId)) {
191
+ removed = true;
192
+ }
193
+ if (this.toCwd.delete(sessionId)) {
194
+ removed = true;
195
+ }
196
+ if (!removed && this.sessionLoadWindows.size > 0) {
197
+ for (const loadWindow of this.sessionLoadWindows) {
198
+ loadWindow.add(sessionId);
199
+ }
200
+ }
201
+ if (removed) {
202
+ this.persist();
203
+ }
204
+ return removed;
205
+ }
146
206
  deleteByKey(key) {
147
207
  const sessionId = this.toSession.get(key);
148
208
  if (!sessionId)
@@ -166,7 +226,7 @@ var SessionRouter = class {
166
226
  /**
167
227
  * Restore session mappings from a previous bridge.
168
228
  * Called after bridge restart — attempts loadSession for each saved mapping.
169
- * Failed loads are silently dropped (new session on next message).
229
+ * Failed loads are dropped (new session on next message).
170
230
  */
171
231
  async restoreSessions() {
172
232
  if (!this.persistPath || !existsSync(this.persistPath)) {
@@ -180,18 +240,54 @@ var SessionRouter = class {
180
240
  }
181
241
  let restored = 0;
182
242
  let failed = 0;
183
- for (const [key, entry] of Object.entries(entries)) {
184
- try {
185
- const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd);
186
- this.toSession.set(key, sessionId);
187
- this.toTarget.set(sessionId, entry.target);
188
- this.toCwd.set(sessionId, entry.cwd);
189
- restored++;
190
- } catch {
191
- failed++;
243
+ let changed = false;
244
+ const reservations = /* @__PURE__ */ new Map();
245
+ for (const key of Object.keys(entries)) {
246
+ this.deleteByKey(key);
247
+ const reservation = this.createSessionReservation();
248
+ reservation.promise.catch(() => void 0);
249
+ this.creatingSessions.set(key, reservation.promise);
250
+ reservations.set(key, reservation);
251
+ }
252
+ const loadWindow = this.beginSessionLoad();
253
+ try {
254
+ for (const [key, entry] of Object.entries(entries)) {
255
+ const reservation = reservations.get(key);
256
+ if (!reservation)
257
+ continue;
258
+ try {
259
+ const sessionId = await this.bridge.loadSession(entry.sessionId, entry.cwd);
260
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
261
+ throw new Error("Invalid restored session ID");
262
+ }
263
+ if (loadWindow.delete(sessionId)) {
264
+ throw new Error("Restored session died before routing completed");
265
+ }
266
+ this.toSession.set(key, sessionId);
267
+ this.toTarget.set(sessionId, entry.target);
268
+ this.toCwd.set(sessionId, entry.cwd);
269
+ reservation.resolve(sessionId);
270
+ if (sessionId !== entry.sessionId) {
271
+ changed = true;
272
+ }
273
+ restored++;
274
+ } catch (err) {
275
+ const reason = err instanceof Error ? err.message : String(err);
276
+ process2.stderr.write(`[SessionRouter] Failed to restore session ${entry.sessionId} for key ${key}: ${reason}
277
+ `);
278
+ reservation.reject(new Error("Session restore failed", { cause: err }));
279
+ failed++;
280
+ changed = true;
281
+ } finally {
282
+ if (this.creatingSessions.get(key) === reservation.promise) {
283
+ this.creatingSessions.delete(key);
284
+ }
285
+ }
192
286
  }
287
+ } finally {
288
+ this.endSessionLoad(loadWindow);
193
289
  }
194
- if (failed > 0) {
290
+ if (changed) {
195
291
  this.persist();
196
292
  }
197
293
  return { restored, failed };
@@ -201,6 +297,8 @@ var SessionRouter = class {
201
297
  this.toSession.clear();
202
298
  this.toTarget.clear();
203
299
  this.toCwd.clear();
300
+ this.creatingSessions.clear();
301
+ this.sessionLoadWindows.clear();
204
302
  if (this.persistPath && existsSync(this.persistPath)) {
205
303
  try {
206
304
  unlinkSync(this.persistPath);
@@ -227,10 +325,80 @@ var SessionRouter = class {
227
325
  } catch {
228
326
  }
229
327
  }
328
+ async createLiveSession(cwd, loadWindow, routingKey) {
329
+ const maxAttempts = 2;
330
+ let lastDeadSessionId;
331
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
332
+ const sessionId = await this.bridge.newSession(cwd);
333
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
334
+ throw new Error("Invalid session ID from bridge");
335
+ }
336
+ if (!loadWindow.delete(sessionId)) {
337
+ return sessionId;
338
+ }
339
+ lastDeadSessionId = sessionId;
340
+ }
341
+ throw new Error(`Session ${lastDeadSessionId ?? "unknown"} died before routing completed (${maxAttempts}/${maxAttempts} attempts, key ${routingKey})`);
342
+ }
343
+ beginSessionLoad() {
344
+ const loadWindow = /* @__PURE__ */ new Set();
345
+ this.sessionLoadWindows.add(loadWindow);
346
+ return loadWindow;
347
+ }
348
+ createSessionReservation() {
349
+ let resolveReservation;
350
+ let rejectReservation;
351
+ const promise = new Promise((resolve2, reject) => {
352
+ resolveReservation = resolve2;
353
+ rejectReservation = reject;
354
+ });
355
+ return {
356
+ promise,
357
+ resolve: resolveReservation,
358
+ reject: rejectReservation
359
+ };
360
+ }
361
+ endSessionLoad(loadWindow) {
362
+ this.sessionLoadWindows.delete(loadWindow);
363
+ }
230
364
  };
231
365
 
366
+ // packages/channels/base/dist/sanitize.js
367
+ init_esbuild_shims();
368
+ var PROMPT_UNSAFE_INVISIBLES = /[\u0080-\u009f\u200b-\u200d\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff]/g;
369
+ function truncateCodePoints(str, max) {
370
+ const cp = Array.from(str);
371
+ return cp.length > max ? cp.slice(0, max).join("") : str;
372
+ }
373
+ __name(truncateCodePoints, "truncateCodePoints");
374
+ function sanitizeSenderName(name) {
375
+ const cleaned = name.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/[[\]\r\n]/g, " ");
376
+ return truncateCodePoints(cleaned, 64).trim() || "unknown";
377
+ }
378
+ __name(sanitizeSenderName, "sanitizeSenderName");
379
+ function sanitizeQuotedText(text, maxLen) {
380
+ const cleaned = text.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/["[\]]/g, " ");
381
+ const cp = Array.from(cleaned);
382
+ return cp.length > maxLen ? cp.slice(0, maxLen - 1).join("") + "\u2026" : cleaned;
383
+ }
384
+ __name(sanitizeQuotedText, "sanitizeQuotedText");
385
+ function sanitizePromptText(text) {
386
+ return text.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/^([ \t]*)\[([^\]\r\n]{1,64})\](:?)/gm, "$1$2$3").replace(/[\u0000-\u001f\u007f]/g, " ");
387
+ }
388
+ __name(sanitizePromptText, "sanitizePromptText");
389
+ function sanitizePromptPath(path3) {
390
+ const cleaned = path3.replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ");
391
+ return truncateCodePoints(cleaned, 1024);
392
+ }
393
+ __name(sanitizePromptPath, "sanitizePromptPath");
394
+ function sanitizeLogText(text, maxLen) {
395
+ return truncateCodePoints(text, maxLen).replace(/\n/g, "\\n").replace(PROMPT_UNSAFE_INVISIBLES, " ").replace(/[\u0000-\u001f\u007f]/g, " ");
396
+ }
397
+ __name(sanitizeLogText, "sanitizeLogText");
398
+
232
399
  // packages/channels/base/dist/ChannelBase.js
233
400
  init_esbuild_shims();
401
+ import { basename } from "node:path";
234
402
 
235
403
  // packages/channels/base/dist/BlockStreamer.js
236
404
  init_esbuild_shims();
@@ -265,6 +433,11 @@ var BlockStreamer = class {
265
433
  }
266
434
  await this.sending;
267
435
  }
436
+ /** Drop buffered text and cancel future idle emission. */
437
+ stop() {
438
+ this.clearIdleTimer();
439
+ this.buffer = "";
440
+ }
268
441
  // ---------------------------------------------------------------------------
269
442
  // Internal
270
443
  // ---------------------------------------------------------------------------
@@ -526,6 +699,10 @@ function generateCode() {
526
699
  __name(generateCode, "generateCode");
527
700
 
528
701
  // packages/channels/base/dist/ChannelBase.js
702
+ var CLEAR_CANCEL_TIMEOUT_MS = 3e3;
703
+ var COMMAND_TOKEN_CHARS = "a-zA-Z0-9_:-";
704
+ var PARSE_COMMAND_RE = new RegExp(`^\\/([${COMMAND_TOKEN_CHARS}]+)(?:@\\S+)?\\s*(.*)`, "s");
705
+ var COMMAND_TOKEN_RE = new RegExp(`^[${COMMAND_TOKEN_CHARS}]+(?:@\\S+)?$`);
529
706
  var ChannelBase = class {
530
707
  static {
531
708
  __name(this, "ChannelBase");
@@ -542,10 +719,26 @@ var ChannelBase = class {
542
719
  commands = /* @__PURE__ */ new Map();
543
720
  /** Per-session promise chain to serialize prompt + send (followup mode). */
544
721
  sessionQueues = /* @__PURE__ */ new Map();
722
+ registerBridgeEvents;
723
+ /**
724
+ * Per-session generation, bumped by /clear. A queued followup turn captures the
725
+ * generation when it enqueues and bails if /clear bumped it before the turn ran,
726
+ * so a cleared session can't be resurrected by an already-queued prompt.
727
+ */
728
+ sessionGenerations = /* @__PURE__ */ new Map();
545
729
  /** Per-session active prompt tracking for dispatch modes. */
546
730
  activePrompts = /* @__PURE__ */ new Map();
547
731
  /** Per-session message buffer for collect mode. */
548
732
  collectBuffers = /* @__PURE__ */ new Map();
733
+ bridgeToolCallListener = /* @__PURE__ */ __name((event) => {
734
+ const target = this.router.getTarget(event.sessionId);
735
+ if (target) {
736
+ this.onToolCall(target.chatId, event);
737
+ }
738
+ }, "bridgeToolCallListener");
739
+ bridgeSessionDiedListener = /* @__PURE__ */ __name((event) => {
740
+ this.onSessionDied(event.sessionId);
741
+ }, "bridgeSessionDiedListener");
549
742
  constructor(name, config, bridge, options) {
550
743
  this.name = name;
551
744
  this.config = config;
@@ -556,21 +749,36 @@ var ChannelBase = class {
556
749
  this.gate = new SenderGate(config.senderPolicy, config.allowedUsers, pairingStore);
557
750
  this.router = options?.router || new SessionRouter(bridge, config.cwd, config.sessionScope);
558
751
  this.registerSharedCommands();
559
- if (!options?.router) {
560
- bridge.on("toolCall", (event) => {
561
- const target = this.router.getTarget(event.sessionId);
562
- if (target) {
563
- this.onToolCall(target.chatId, event);
564
- }
565
- });
752
+ this.registerBridgeEvents = options?.registerBridgeEvents ?? !options?.router;
753
+ if (this.registerBridgeEvents) {
754
+ this.attachBridgeEvents(bridge);
566
755
  }
567
756
  }
568
757
  /** Replace the bridge instance (used after crash recovery restart). */
569
758
  setBridge(bridge) {
759
+ if (this.registerBridgeEvents) {
760
+ this.detachBridgeEvents(this.bridge);
761
+ }
762
+ this.router.setBridge(bridge);
570
763
  this.bridge = bridge;
764
+ if (this.registerBridgeEvents) {
765
+ this.attachBridgeEvents(bridge);
766
+ }
571
767
  }
572
768
  onToolCall(_chatId, _event) {
573
769
  }
770
+ onSessionDied(sessionId) {
771
+ this.router.removeSessionId(sessionId);
772
+ this.instructedSessions.delete(sessionId);
773
+ }
774
+ attachBridgeEvents(bridge) {
775
+ bridge.on("toolCall", this.bridgeToolCallListener);
776
+ bridge.on("sessionDied", this.bridgeSessionDiedListener);
777
+ }
778
+ detachBridgeEvents(bridge) {
779
+ bridge.off("toolCall", this.bridgeToolCallListener);
780
+ bridge.off("sessionDied", this.bridgeSessionDiedListener);
781
+ }
574
782
  /**
575
783
  * Called when a prompt actually begins processing (inside the session queue).
576
784
  * Override to show a platform-specific working indicator (e.g., typing, reaction).
@@ -607,38 +815,145 @@ var ChannelBase = class {
607
815
  registerCommand(name, handler) {
608
816
  this.commands.set(name.toLowerCase(), handler);
609
817
  }
818
+ registerCancelCommand(name = "cancel") {
819
+ this.registerCommand(name, async (envelope) => {
820
+ if (!this.isAuthorizedForSharedSession(envelope)) {
821
+ await this.sendMessage(envelope.chatId, "Only authorized members can cancel requests in this shared session.");
822
+ return true;
823
+ }
824
+ const activeSessionId = this.findActiveSessionId(envelope);
825
+ if (!activeSessionId) {
826
+ await this.sendMessage(envelope.chatId, "No request is currently running.");
827
+ return true;
828
+ }
829
+ const active = this.activePrompts.get(activeSessionId);
830
+ if (!active) {
831
+ await this.sendMessage(envelope.chatId, "No request is currently running.");
832
+ return true;
833
+ }
834
+ const cancelRequested = active.cancelRequested ?? this.bridge.cancelSession(activeSessionId).then(() => true, (err) => {
835
+ process.stderr.write(`[${this.name}] cancelSession failed for session=${activeSessionId}: ${err instanceof Error ? err.message : err}
836
+ `);
837
+ active.cancelRequested = void 0;
838
+ return false;
839
+ });
840
+ active.cancelRequested = cancelRequested;
841
+ const cancelSucceeded = await cancelRequested;
842
+ if (!cancelSucceeded) {
843
+ await this.sendMessage(envelope.chatId, "Failed to cancel current request.");
844
+ return true;
845
+ }
846
+ active.cancelled = true;
847
+ active.stopStreaming?.();
848
+ this.collectBuffers.delete(activeSessionId);
849
+ await this.sendMessage(envelope.chatId, "Cancelled current request.");
850
+ return true;
851
+ });
852
+ }
610
853
  /** Register shared slash commands. Called from constructor. */
611
854
  registerSharedCommands() {
612
- const clearHandler = /* @__PURE__ */ __name(async (envelope) => {
613
- const removedIds = this.router.removeSession(this.name, envelope.senderId, envelope.chatId);
855
+ const doClear = /* @__PURE__ */ __name(async (envelope) => {
856
+ const removedIds = this.router.removeSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
614
857
  if (removedIds.length > 0) {
615
858
  for (const id of removedIds) {
859
+ if (this.isSharedSession(envelope)) {
860
+ const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
861
+ process.stderr.write(`[${this.name}] shared session ${id} cleared by ${who} (sender ${envelope.senderId})
862
+ `);
863
+ }
864
+ this.sessionGenerations.set(id, (this.sessionGenerations.get(id) ?? 0) + 1);
865
+ const active = this.activePrompts.get(id);
866
+ this.collectBuffers.delete(id);
867
+ if (active) {
868
+ const settled = await this.cancelAndAwaitActive(active, id);
869
+ if (!settled) {
870
+ const wedgedChat = active.chatId ? sanitizeLogText(active.chatId, 64) : "unknown";
871
+ const wedgedMessage = active.messageId ? `, message ${sanitizeLogText(active.messageId, 64)}` : "";
872
+ process.stderr.write(`[${this.name}] /clear abandoned a wedged turn for session ${id} (chat ${wedgedChat}${wedgedMessage}): it did not wind down within ${CLEAR_CANCEL_TIMEOUT_MS}ms
873
+ `);
874
+ active.clearEvicted = true;
875
+ try {
876
+ this.onPromptEnd(active.chatId, id, active.messageId);
877
+ } catch (err) {
878
+ process.stderr.write(`[${this.name}] onPromptEnd threw during /clear eviction for session ${id}: ${err instanceof Error ? err.message : err}
879
+ `);
880
+ }
881
+ }
882
+ }
616
883
  this.instructedSessions.delete(id);
884
+ const drained = this.sessionQueues.get(id);
885
+ const bumpedGeneration = this.sessionGenerations.get(id);
886
+ this.sessionQueues.delete(id);
887
+ this.activePrompts.delete(id);
888
+ if (drained) {
889
+ void drained.then(() => {
890
+ if (!this.sessionQueues.has(id) && this.sessionGenerations.get(id) === bumpedGeneration) {
891
+ this.sessionGenerations.delete(id);
892
+ }
893
+ });
894
+ } else {
895
+ this.sessionGenerations.delete(id);
896
+ }
617
897
  }
618
- await this.sendMessage(envelope.chatId, "Session cleared. Your next message will start a fresh conversation.");
898
+ await this.sendMessage(envelope.chatId, "Session cleared. The next message starts a fresh conversation.");
619
899
  } else {
620
900
  await this.sendMessage(envelope.chatId, "No active session to clear.");
621
901
  }
902
+ }, "doClear");
903
+ const clearHandler = /* @__PURE__ */ __name(async (envelope, args) => {
904
+ if (!this.isAuthorizedForSharedSession(envelope)) {
905
+ await this.sendMessage(envelope.chatId, "Only authorized members can clear this shared session.");
906
+ return true;
907
+ }
908
+ if (this.isSharedSession(envelope) && args.toLowerCase() !== "confirm") {
909
+ await this.sendMessage(envelope.chatId, 'This clears the shared session for everyone who shares it. Re-send with "confirm" (e.g. /clear confirm) to proceed.');
910
+ return true;
911
+ }
912
+ await doClear(envelope);
622
913
  return true;
623
914
  }, "clearHandler");
624
915
  this.registerCommand("clear", clearHandler);
625
916
  this.registerCommand("reset", clearHandler);
626
917
  this.registerCommand("new", clearHandler);
918
+ this.registerCommand("who", async (envelope) => {
919
+ if (!this.isAuthorizedForSharedSession(envelope)) {
920
+ await this.sendMessage(envelope.chatId, "Only authorized members can view this shared session.");
921
+ return true;
922
+ }
923
+ const active = this.router.hasSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
924
+ const scopeNote = this.config.sessionScope === "single" ? " (shared channel-wide)" : this.isSharedSession(envelope) ? envelope.isGroup ? " (shared by this group)" : "" : envelope.isGroup ? " (private to you)" : "";
925
+ await this.sendMessage(envelope.chatId, [
926
+ `Channel: ${this.name}`,
927
+ // Only the basename — don't leak the absolute cwd to group members.
928
+ `Workspace: ${basename(this.config.cwd)}`,
929
+ `Session: ${active ? "active" : "none"}${scopeNote}`
930
+ ].join("\n"));
931
+ return true;
932
+ });
627
933
  this.registerCommand("help", async (envelope) => {
628
934
  const lines = [
629
935
  "Commands:",
630
936
  "/help \u2014 Show this help",
631
- "/clear \u2014 Clear your session (aliases: /reset, /new)",
937
+ this.isSharedSession(envelope) ? "/clear confirm \u2014 Clear the shared session (aliases: /reset, /new)" : "/clear \u2014 Clear your session (aliases: /reset, /new)",
938
+ "/who \u2014 Show current session & workspace",
632
939
  "/status \u2014 Show session info"
633
940
  ];
634
- const sharedCmds = /* @__PURE__ */ new Set(["help", "clear", "reset", "new", "status"]);
941
+ const sharedCmds = /* @__PURE__ */ new Set([
942
+ "help",
943
+ "clear",
944
+ "reset",
945
+ "new",
946
+ "who",
947
+ "status"
948
+ ]);
635
949
  const platformCmds = [...this.commands.keys()].filter((c) => !sharedCmds.has(c));
636
950
  if (platformCmds.length > 0) {
637
951
  for (const cmd of platformCmds) {
638
952
  lines.push(`/${cmd}`);
639
953
  }
640
954
  }
641
- const agentCommands = this.bridge.availableCommands;
955
+ const sessionId = this.router.getSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
956
+ const agentCommands = sessionId ? this.getAgentCommandsForSession(sessionId) : this.bridge.availableCommands;
642
957
  if (agentCommands.length > 0) {
643
958
  lines.push("", "Agent commands (forwarded to Qwen Code):");
644
959
  for (const cmd of agentCommands) {
@@ -650,7 +965,11 @@ var ChannelBase = class {
650
965
  return true;
651
966
  });
652
967
  this.registerCommand("status", async (envelope) => {
653
- const hasSession = this.router.hasSession(this.name, envelope.senderId, envelope.chatId);
968
+ if (!this.isAuthorizedForSharedSession(envelope)) {
969
+ await this.sendMessage(envelope.chatId, "Only authorized members can view this shared session.");
970
+ return true;
971
+ }
972
+ const hasSession = this.router.hasSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
654
973
  const policy = this.config.senderPolicy;
655
974
  const lines = [
656
975
  `Session: ${hasSession ? "active" : "none"}`,
@@ -666,17 +985,150 @@ var ChannelBase = class {
666
985
  const parsed = this.parseCommand(text);
667
986
  return parsed !== null && this.commands.has(parsed.command);
668
987
  }
988
+ findActiveSessionId(envelope) {
989
+ const sessionId = this.router.getSession(this.name, envelope.senderId, envelope.chatId, envelope.threadId);
990
+ return sessionId && this.activePrompts.has(sessionId) ? sessionId : void 0;
991
+ }
992
+ /**
993
+ * Whether the resolved session is SHARED across senders. `single` collapses
994
+ * the whole channel to one `__single__` session for EVERY sender — group OR
995
+ * DM — so it is ALWAYS shared (even a DM maps to `__single__`). `thread` is
996
+ * shared only in a group (a DM maps to the lone caller's own chat). `user` is
997
+ * per-sender, never shared. Drives both the destructive-/clear confirm gate
998
+ * and the host-shell (`!`) gate.
999
+ */
1000
+ isSharedSession(envelope) {
1001
+ return this.config.sessionScope === "single" || envelope.isGroup && this.config.sessionScope === "thread";
1002
+ }
1003
+ /**
1004
+ * Whether `envelope.senderId` may act on the resolved session's destructive or
1005
+ * workspace-leaking commands (/clear, /who). A SHARED session with a non-empty
1006
+ * allowedUsers list is restricted to those members; a per-user session, or one
1007
+ * with no allowlist, is unrestricted. Shared verbatim by /clear and /who so the
1008
+ * gate can't drift; each caller sends its own rejection wording.
1009
+ */
1010
+ isAuthorizedForSharedSession(envelope) {
1011
+ if (!this.isSharedSession(envelope))
1012
+ return true;
1013
+ const authorized = this.config.allowedUsers;
1014
+ return authorized.length === 0 || authorized.includes(envelope.senderId);
1015
+ }
1016
+ stopActiveStreaming(active, sessionId, reason) {
1017
+ try {
1018
+ active.stopStreaming?.();
1019
+ } catch (err) {
1020
+ process.stderr.write(`[${this.name}] stopStreaming threw during ${reason} for session ${sessionId}: ${err instanceof Error ? err.message : err}
1021
+ `);
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Cancel the active turn and wait (bounded) for it to wind down. Stops the
1026
+ * BlockStreamer so buffered text can't leak via the idle timer, then fires a
1027
+ * best-effort cancelSession (NOT awaited — a wedged child/daemon can leave the
1028
+ * request pending forever). Returns true if active.done settled first, false
1029
+ * if the CLEAR_CANCEL_TIMEOUT_MS bound won (the turn never wound down). Used by
1030
+ * /clear, which genuinely EVICTS the session and so must proceed even when the
1031
+ * turn is wedged. Steer no longer uses this: it best-effort cancels then chains
1032
+ * the new turn behind the old one (see handleInbound), so it never needs to
1033
+ * proceed past a still-active turn.
1034
+ */
1035
+ async cancelAndAwaitActive(active, sessionId) {
1036
+ active.cancelled = true;
1037
+ this.stopActiveStreaming(active, sessionId, "cancel");
1038
+ void this.bridge.cancelSession(sessionId).catch((err) => {
1039
+ process.stderr.write(`[${this.name}] cancelSession failed for session=${sessionId} (clear/await): ${err instanceof Error ? err.message : err}
1040
+ `);
1041
+ });
1042
+ let timer;
1043
+ const settled = await Promise.race([
1044
+ active.done.then(() => true),
1045
+ new Promise((resolve2) => {
1046
+ timer = setTimeout(() => resolve2(false), CLEAR_CANCEL_TIMEOUT_MS);
1047
+ })
1048
+ ]);
1049
+ clearTimeout(timer);
1050
+ return settled;
1051
+ }
669
1052
  /**
670
1053
  * Parse a slash command from message text.
671
- * Returns { command, args } or null if not a slash command.
1054
+ * Returns { command, raw, args } or null if not a slash command. `command` is
1055
+ * lowercased for case-insensitive LOCAL dispatch (registerCommand lowercases the
1056
+ * names it stores); `raw` keeps the typed case so agent-command matching can be
1057
+ * CASE-SENSITIVE, mirroring the CLI's parseSlashCommand (`cmd.name === part`).
672
1058
  */
673
1059
  parseCommand(text) {
674
- if (!text.startsWith("/"))
1060
+ const trimmed = text.trim();
1061
+ if (!trimmed.startsWith("/"))
675
1062
  return null;
676
- const match = text.match(/^\/([a-zA-Z0-9_]+)(?:@\S+)?\s*(.*)/s);
1063
+ const match = trimmed.match(PARSE_COMMAND_RE);
677
1064
  if (!match)
678
1065
  return null;
679
- return { command: match[1].toLowerCase(), args: match[2].trim() };
1066
+ return {
1067
+ command: match[1].toLowerCase(),
1068
+ raw: match[1],
1069
+ args: match[2].trim()
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Whether `text` is a real slash command rather than prose that merely starts
1074
+ * with `/`. A command's first whitespace-delimited token must match
1075
+ * parseCommand()'s charset — `[a-zA-Z0-9_:-]+`, plus an optional `@botname`
1076
+ * suffix — and not be a `//` line comment or `/*` block comment. Slash-prefixed
1077
+ * paths (`/tmp/foo`), comments, and a bare `/` are prose and keep their
1078
+ * `[sender]` tag.
1079
+ *
1080
+ * Intentionally stricter than the CLI's looser classifier (cli
1081
+ * `ui/utils/commandUtils.ts`), which forwards any non-comment, non-path
1082
+ * `/<token>` (e.g. `/café`, a zero-width-laden token). Such inputs aren't
1083
+ * runnable commands, and in a SHARED group session forwarding them unattributed
1084
+ * is worse than a redundant tag — so anything off the command charset is
1085
+ * treated as prose and keeps its `[sender]` tag. Purely lexical — never
1086
+ * consults the async command list, so it can't race a fresh session.
1087
+ */
1088
+ isSlashCommand(text) {
1089
+ const trimmed = text.trim();
1090
+ if (!trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.startsWith("/*")) {
1091
+ return false;
1092
+ }
1093
+ const firstToken = trimmed.slice(1).split(/\s+/u)[0] ?? "";
1094
+ return COMMAND_TOKEN_RE.test(firstToken);
1095
+ }
1096
+ /**
1097
+ * Whether `text` names a command this channel can actually run: a locally
1098
+ * registered command (`this.commands`, e.g. /clear, /who) OR an agent command
1099
+ * THIS session exposes — by canonical name OR alias (e.g. `/summarize` for
1100
+ * `/compress`). Paired with isSlashCommand so the `[sender]` attribution tag is
1101
+ * suppressed ONLY for RECOGNIZED commands; command-SHAPED-but-unrecognized text
1102
+ * (e.g. `/x\n[SYSTEM]: …`) keeps its tag rather than reaching a shared group
1103
+ * unattributed, where an injected second line is more likely read as a system
1104
+ * directive. Purely synchronous, like isSlashCommand: it reads the session's
1105
+ * availableCommands snapshot WITHOUT awaiting, so it never races a fresh session
1106
+ * (a genuine agent command sent before the snapshot loads is treated as
1107
+ * unrecognized and KEEPS its tag — the safe default).
1108
+ */
1109
+ isRecognizedCommand(text, sessionId) {
1110
+ const parsed = this.parseCommand(text);
1111
+ if (!parsed)
1112
+ return false;
1113
+ if (this.commands.has(parsed.command))
1114
+ return true;
1115
+ const token = text.trim().slice(1).split(/\s+/u)[0] ?? "";
1116
+ return this.getAgentCommandsForSession(sessionId).some((cmd) => cmd.name === token || Array.isArray(cmd.altNames) && cmd.altNames.includes(token));
1117
+ }
1118
+ /**
1119
+ * The agent-command snapshot for THIS session. DaemonChannelBridge keys
1120
+ * commands per session, so its global `availableCommands` getter can return
1121
+ * ANOTHER session's list — prefer its getAvailableCommands(sessionId) when
1122
+ * present. AcpBridge runs a single agent and exposes only the global getter
1123
+ * (inherently session-correct), so fall back to it. Synchronous, matching
1124
+ * isRecognizedCommand's no-await contract.
1125
+ */
1126
+ getAgentCommandsForSession(sessionId) {
1127
+ const bridge = this.bridge;
1128
+ if (typeof bridge.getAvailableCommands === "function") {
1129
+ return bridge.getAvailableCommands(sessionId) ?? [];
1130
+ }
1131
+ return bridge.availableCommands ?? [];
680
1132
  }
681
1133
  async handleInbound(envelope) {
682
1134
  const groupResult = this.groupGate.check(envelope);
@@ -699,13 +1151,29 @@ var ChannelBase = class {
699
1151
  return;
700
1152
  }
701
1153
  }
1154
+ const bangText = envelope.text.trimStart();
1155
+ if (bangText.startsWith("!")) {
1156
+ if (envelope.isGroup || this.isSharedSession(envelope)) {
1157
+ const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
1158
+ process.stderr.write(`[${this.name}] blocked ! shell command from ${who} (sender ${envelope.senderId}) in chat ${sanitizeLogText(envelope.chatId, 64)}
1159
+ `);
1160
+ }
1161
+ if (envelope.isGroup) {
1162
+ await this.sendMessage(envelope.chatId, "Shell commands (`!`) are disabled in group chats.");
1163
+ return;
1164
+ }
1165
+ if (this.isSharedSession(envelope)) {
1166
+ await this.sendMessage(envelope.chatId, "Shell commands (`!`) are disabled in shared sessions.");
1167
+ return;
1168
+ }
1169
+ }
702
1170
  const sessionId = await this.router.resolve(this.name, envelope.senderId, envelope.chatId, envelope.threadId, this.config.cwd);
703
- if (envelope.text.startsWith("!")) {
704
- const cmd = envelope.text.slice(1).trim();
705
- const bridgeShellCommand = this.bridge["shellCommand"];
706
- if (cmd && typeof bridgeShellCommand === "function") {
1171
+ if (bangText.startsWith("!")) {
1172
+ const cmd = bangText.slice(1).trim();
1173
+ const bridgeShellCommand = this.bridge.shellCommand;
1174
+ if (cmd && bridgeShellCommand) {
707
1175
  try {
708
- const result2 = await bridgeShellCommand(sessionId, cmd);
1176
+ const result2 = await this.bridge.shellCommand(sessionId, cmd);
709
1177
  const longestRun = Math.max(0, ...Array.from((result2.output || "").matchAll(/`+/g), (m) => m[0].length));
710
1178
  const fence = "`".repeat(Math.max(3, longestRun + 1));
711
1179
  const output = result2.output ? `${fence}
@@ -722,8 +1190,13 @@ ${output}${exitLine}`);
722
1190
  }
723
1191
  }
724
1192
  let promptText = envelope.text;
1193
+ if ((envelope.isGroup || this.config.sessionScope === "single") && !envelope.alreadyPrefixed && !(this.isSlashCommand(envelope.text) && this.isRecognizedCommand(envelope.text, sessionId))) {
1194
+ const who = sanitizeSenderName(envelope.senderName || envelope.senderId || "unknown");
1195
+ promptText = `[${who}] ${sanitizePromptText(promptText)}`;
1196
+ }
725
1197
  if (envelope.referencedText) {
726
- promptText = `[Replying to: "${envelope.referencedText}"]
1198
+ const quoted = sanitizeQuotedText(envelope.referencedText, 500);
1199
+ promptText = `[Replying to: "${quoted}"]
727
1200
 
728
1201
  ${promptText}`;
729
1202
  }
@@ -737,8 +1210,9 @@ ${promptText}`;
737
1210
  imageMimeType = att.mimeType;
738
1211
  } else if (att.filePath) {
739
1212
  const label = att.type === "file" ? "file" : att.type;
740
- const name = att.fileName ? ` "${att.fileName}"` : "";
741
- filePaths.push(`User sent a ${label}${name}. It has been saved to: ${att.filePath}`);
1213
+ const name = att.fileName ? ` "${sanitizeQuotedText(att.fileName, 128)}"` : "";
1214
+ const renderedPath = sanitizePromptPath(att.filePath);
1215
+ filePaths.push(`User sent a ${label}${name}. It has been saved to: ${renderedPath}`);
742
1216
  }
743
1217
  }
744
1218
  if (filePaths.length > 0) {
@@ -754,6 +1228,7 @@ ${promptText}`;
754
1228
  const groupCfg = envelope.isGroup ? this.config.groups[envelope.chatId] || this.config.groups["*"] : void 0;
755
1229
  const mode = groupCfg?.dispatchMode || this.config.dispatchMode || "steer";
756
1230
  const active = this.activePrompts.get(sessionId);
1231
+ let steerWatchdog;
757
1232
  if (active) {
758
1233
  switch (mode) {
759
1234
  case "collect": {
@@ -766,10 +1241,26 @@ ${promptText}`;
766
1241
  return;
767
1242
  }
768
1243
  case "steer": {
1244
+ if (!this.isAuthorizedForSharedSession(envelope)) {
1245
+ process.stderr.write(`[${this.name}] steer denied for ${envelope.senderId} in shared session (chat=${sanitizeLogText(envelope.chatId, 64)}); queuing instead
1246
+ `);
1247
+ break;
1248
+ }
769
1249
  active.cancelled = true;
770
- await this.bridge.cancelSession(sessionId).catch(() => {
1250
+ process.stderr.write(`[${this.name}] steer: cancelled active turn for ${envelope.senderId} in session ${sessionId}
1251
+ `);
1252
+ this.stopActiveStreaming(active, sessionId, "steer");
1253
+ void this.bridge.cancelSession(sessionId).catch((err) => {
1254
+ process.stderr.write(`[${this.name}] cancelSession failed for session=${sessionId} (steer): ${err instanceof Error ? err.message : err}
1255
+ `);
771
1256
  });
772
- await active.done;
1257
+ steerWatchdog = setTimeout(() => {
1258
+ if (this.activePrompts.get(sessionId) === active) {
1259
+ process.stderr.write(`[${this.name}] steer queued behind active turn for session ${sessionId}: still waiting after ${CLEAR_CANCEL_TIMEOUT_MS}ms (use /clear to recover)
1260
+ `);
1261
+ }
1262
+ }, CLEAR_CANCEL_TIMEOUT_MS);
1263
+ steerWatchdog.unref?.();
773
1264
  promptText = `[The user sent a new message while you were working. Their previous request has been cancelled.]
774
1265
 
775
1266
  ${promptText}`;
@@ -785,14 +1276,28 @@ ${promptText}`;
785
1276
  }
786
1277
  }
787
1278
  const prev = this.sessionQueues.get(sessionId) ?? Promise.resolve();
1279
+ const generation = this.sessionGenerations.get(sessionId) ?? 0;
788
1280
  const useBlockStreaming = this.config.blockStreaming === "on";
789
1281
  const current = prev.then(async () => {
1282
+ clearTimeout(steerWatchdog);
1283
+ if ((this.sessionGenerations.get(sessionId) ?? 0) !== generation) {
1284
+ const loggedText = sanitizeLogText(envelope.text, 80);
1285
+ process.stderr.write(`[${this.name}] dropped queued turn from ${envelope.senderId} for session ${sessionId}: session was cleared before it ran (text: ${loggedText})
1286
+ `);
1287
+ return;
1288
+ }
790
1289
  let doneResolve = /* @__PURE__ */ __name(() => {
791
1290
  }, "doneResolve");
792
1291
  const done = new Promise((r) => {
793
1292
  doneResolve = r;
794
1293
  });
795
- const promptState = { cancelled: false, done, resolve: doneResolve };
1294
+ const promptState = {
1295
+ cancelled: false,
1296
+ done,
1297
+ resolve: doneResolve,
1298
+ chatId: envelope.chatId,
1299
+ messageId: envelope.messageId
1300
+ };
796
1301
  this.activePrompts.set(sessionId, promptState);
797
1302
  this.onPromptStart(envelope.chatId, sessionId, envelope.messageId);
798
1303
  const streamer = useBlockStreaming ? new BlockStreamer({
@@ -801,18 +1306,23 @@ ${promptText}`;
801
1306
  idleMs: this.config.blockStreamingCoalesce?.idleMs ?? 1500,
802
1307
  send: /* @__PURE__ */ __name((text) => this.sendMessage(envelope.chatId, text), "send")
803
1308
  }) : null;
1309
+ promptState.stopStreaming = () => streamer?.stop();
804
1310
  const onChunk = /* @__PURE__ */ __name((sid, chunk) => {
805
- if (sid === sessionId) {
1311
+ if (sid === sessionId && !promptState.cancelled) {
806
1312
  this.onResponseChunk(envelope.chatId, chunk, sessionId);
807
1313
  streamer?.push(chunk);
808
1314
  }
809
1315
  }, "onChunk");
810
- this.bridge.on("textChunk", onChunk);
1316
+ const promptBridge = this.bridge;
1317
+ promptBridge.on("textChunk", onChunk);
811
1318
  try {
812
- const response = await this.bridge.prompt(sessionId, promptText, {
1319
+ const response = await promptBridge.prompt(sessionId, promptText, {
813
1320
  imageBase64,
814
1321
  imageMimeType
815
1322
  });
1323
+ if (promptState.cancelRequested && !promptState.cancelled) {
1324
+ promptState.cancelled = await promptState.cancelRequested;
1325
+ }
816
1326
  if (!promptState.cancelled && response) {
817
1327
  if (streamer) {
818
1328
  await streamer.flush();
@@ -821,25 +1331,41 @@ ${promptText}`;
821
1331
  }
822
1332
  }
823
1333
  } finally {
824
- this.bridge.off("textChunk", onChunk);
825
- this.onPromptEnd(envelope.chatId, sessionId, envelope.messageId);
826
- this.activePrompts.delete(sessionId);
1334
+ promptBridge.off("textChunk", onChunk);
1335
+ streamer?.stop();
1336
+ const stillCurrent = this.activePrompts.get(sessionId) === promptState;
1337
+ if (!promptState.clearEvicted) {
1338
+ try {
1339
+ this.onPromptEnd(envelope.chatId, sessionId, envelope.messageId);
1340
+ } catch (err) {
1341
+ process.stderr.write(`[${this.name}] onPromptEnd threw in finally for session ${sessionId}: ${err instanceof Error ? err.message : err}
1342
+ `);
1343
+ }
1344
+ }
1345
+ if (stillCurrent) {
1346
+ this.activePrompts.delete(sessionId);
1347
+ }
827
1348
  promptState.resolve();
828
1349
  const buffer = this.collectBuffers.get(sessionId);
829
- if (buffer && buffer.length > 0) {
1350
+ if (stillCurrent && buffer && buffer.length > 0) {
830
1351
  this.collectBuffers.delete(sessionId);
1352
+ const lost = buffer.length;
831
1353
  const coalesced = buffer.map((b) => b.text).join("\n\n");
832
1354
  const lastEnvelope = buffer[buffer.length - 1].envelope;
833
1355
  const syntheticEnvelope = {
834
1356
  ...lastEnvelope,
835
1357
  text: coalesced,
1358
+ // Coalesced text already carries each message's [sender] prefix.
1359
+ alreadyPrefixed: true,
836
1360
  // Clear attachments/references — already resolved in original text
837
1361
  referencedText: void 0,
838
1362
  attachments: void 0,
839
1363
  imageBase64: void 0,
840
1364
  imageMimeType: void 0
841
1365
  };
842
- this.handleInbound(syntheticEnvelope).catch(() => {
1366
+ this.handleInbound(syntheticEnvelope).catch((err) => {
1367
+ process.stderr.write(`[${this.name}] dropped ${lost} buffered message(s) on collect re-entry for session ${sessionId} (last sender ${lastEnvelope.senderId}): ${err instanceof Error ? err.message : String(err)}
1368
+ `);
843
1369
  });
844
1370
  }
845
1371
  }
@@ -868,6 +1394,19 @@ init_esbuild_shims();
868
1394
  import { spawn } from "node:child_process";
869
1395
  import { Readable, Writable } from "node:stream";
870
1396
  import { EventEmitter } from "node:events";
1397
+ function readAvailableCommandAltNames(raw) {
1398
+ if (typeof raw !== "object" || raw === null)
1399
+ return void 0;
1400
+ const record = raw;
1401
+ const meta = record["_meta"];
1402
+ const fromMeta = typeof meta === "object" && meta !== null ? meta["altNames"] : void 0;
1403
+ const source = Array.isArray(record["altNames"]) ? record["altNames"] : Array.isArray(fromMeta) ? fromMeta : void 0;
1404
+ if (!source)
1405
+ return void 0;
1406
+ const names = source.filter((n) => typeof n === "string");
1407
+ return names.length > 0 ? names : void 0;
1408
+ }
1409
+ __name(readAvailableCommandAltNames, "readAvailableCommandAltNames");
871
1410
  var AcpBridge = class extends EventEmitter {
872
1411
  static {
873
1412
  __name(this, "AcpBridge");
@@ -1021,7 +1560,10 @@ var AcpBridge = class extends EventEmitter {
1021
1560
  }
1022
1561
  case "available_commands_update": {
1023
1562
  if (Array.isArray(update["availableCommands"])) {
1024
- this._availableCommands = update["availableCommands"];
1563
+ this._availableCommands = update["availableCommands"].map((cmd) => {
1564
+ const altNames = readAvailableCommandAltNames(cmd);
1565
+ return altNames ? { ...cmd, altNames } : cmd;
1566
+ });
1025
1567
  }
1026
1568
  break;
1027
1569
  }
@@ -1047,5 +1589,8 @@ export {
1047
1589
  AcpBridge,
1048
1590
  PairingStore,
1049
1591
  SessionRouter,
1592
+ sanitizeSenderName,
1593
+ sanitizePromptText,
1594
+ sanitizeLogText,
1050
1595
  ChannelBase
1051
1596
  };