@qwen-code/qwen-code 0.19.3 → 0.19.4-nightly.20260702.46814e4f1

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 (308) hide show
  1. package/bundled/loop/SKILL.md +22 -3
  2. package/bundled/loop/SKILL.test.ts +20 -0
  3. package/bundled/loop/autonomous-loop.test.ts +59 -0
  4. package/bundled/loop/autonomous-loop.ts +102 -0
  5. package/bundled/loop/loop-task-file.test.ts +1064 -0
  6. package/bundled/loop/loop-task-file.ts +404 -0
  7. package/bundled/loop/loop-tick-resolver.test.ts +906 -0
  8. package/bundled/loop/loop-tick-resolver.ts +416 -0
  9. package/bundled/qc-helper/SKILL.md +12 -5
  10. package/bundled/qc-helper/docs/configuration/auth.md +1 -1
  11. package/bundled/qc-helper/docs/configuration/settings.md +62 -32
  12. package/bundled/qc-helper/docs/features/approval-mode.md +3 -0
  13. package/bundled/qc-helper/docs/features/auto-mode.md +40 -0
  14. package/bundled/qc-helper/docs/features/channels/overview.md +81 -18
  15. package/bundled/qc-helper/docs/features/channels/plugins.md +1 -1
  16. package/bundled/qc-helper/docs/features/code-review.md +17 -61
  17. package/bundled/qc-helper/docs/features/commands.md +42 -40
  18. package/bundled/qc-helper/docs/features/headless.md +19 -18
  19. package/bundled/qc-helper/docs/features/hooks.md +6 -1
  20. package/bundled/qc-helper/docs/features/mcp.md +3 -1
  21. package/bundled/qc-helper/docs/features/memory.md +1 -1
  22. package/bundled/qc-helper/docs/features/scheduled-tasks.md +10 -0
  23. package/bundled/qc-helper/docs/features/status-line.md +3 -3
  24. package/bundled/qc-helper/docs/ide-integration/ide-integration.md +4 -4
  25. package/bundled/qc-helper/docs/qwen-serve.md +53 -0
  26. package/bundled/qc-helper/docs/support/troubleshooting.md +11 -0
  27. package/bundled/review/DESIGN.md +21 -26
  28. package/bundled/review/SKILL.md +45 -129
  29. package/chunks/MaxSizedBox-GHGRQ2OS.js +69 -0
  30. package/chunks/{StandaloneSessionPicker-Z52JTYST.js → StandaloneSessionPicker-G7YTTM27.js} +49 -42
  31. package/chunks/{acpAgent-7RZUOWOM.js → acpAgent-SDOILBSU.js} +1325 -365
  32. package/chunks/{agent-4FG7WTHJ.js → agent-NTSXGV4L.js} +31 -25
  33. package/chunks/agent-headless-WYURMSAB.js +62 -0
  34. package/chunks/{anthropicContentGenerator-PFITVL7Y.js → anthropicContentGenerator-WL3LJOUB.js} +110 -31
  35. package/chunks/{artifact-tool-D2X7G3C3.js → artifact-tool-W76WC2JX.js} +3 -3
  36. package/chunks/{askUserQuestion-UGETICJQ.js → askUserQuestion-J3P6TH3V.js} +2 -2
  37. package/chunks/bridge-AMOL4W4P.js +73 -0
  38. package/chunks/{ca-IUFWESZL.js → ca-HCMB2I54.js} +10 -9
  39. package/chunks/channel-worker-supervisor-C3IB63V7.js +659 -0
  40. package/chunks/{chunk-STUWFKGD.js → chunk-26JIREA5.js} +466 -543
  41. package/chunks/{chunk-SLNKLLEO.js → chunk-2MRV62AU.js} +37 -8
  42. package/chunks/{chunk-ODLIHNOY.js → chunk-2U4HUMOL.js} +1 -1
  43. package/chunks/{chunk-CKQA6AWF.js → chunk-4D73HUOP.js} +1 -1
  44. package/chunks/{chunk-M2U5NM24.js → chunk-54YZOP7P.js} +309 -72
  45. package/chunks/{chunk-J7FA27RO.js → chunk-5LKL3PAU.js} +3 -3
  46. package/chunks/chunk-5P5XGNYH.js +93 -0
  47. package/chunks/{chunk-UFLGERMB.js → chunk-6JE32BRB.js} +178 -124
  48. package/chunks/{chunk-FKAVULLM.js → chunk-75AVTQJ2.js} +74 -10
  49. package/chunks/{chunk-I2LYBGNI.js → chunk-7IOMIYOE.js} +1 -1
  50. package/chunks/{chunk-4LUZAKMK.js → chunk-7KXAGQJ2.js} +1 -1
  51. package/chunks/{chunk-BXSHL2SF.js → chunk-7LUOQJ3D.js} +1 -1
  52. package/chunks/{chunk-YAINVQ3Q.js → chunk-A5OPLHFM.js} +18693 -17055
  53. package/chunks/chunk-AHNXYU4O.js +24 -0
  54. package/chunks/{chunk-UX4SGCGN.js → chunk-AVQDCKNF.js} +96 -22
  55. package/chunks/{chunk-HYCC3XM3.js → chunk-B236XPGF.js} +1 -1
  56. package/chunks/chunk-B6SZLQAZ.js +3523 -0
  57. package/chunks/{chunk-5DWXLGOM.js → chunk-BJBWRCSK.js} +1 -1
  58. package/chunks/chunk-BPFJQGY7.js +193 -0
  59. package/chunks/{chunk-SKQZZVAV.js → chunk-BZHLWC6T.js} +2 -2
  60. package/chunks/{chunk-U3KSMM7G.js → chunk-D5KISTLW.js} +339 -383
  61. package/chunks/{chunk-GT2FGICD.js → chunk-DE7YXUR7.js} +1 -1
  62. package/chunks/{chunk-BUUQ5HPX.js → chunk-DG573EOO.js} +1 -1
  63. package/chunks/{chunk-VVLOGDB6.js → chunk-DX3TKXNT.js} +2 -2
  64. package/chunks/chunk-E5A7LHNN.js +51 -0
  65. package/chunks/{chunk-3PJXIDKI.js → chunk-E6US47KI.js} +3 -27
  66. package/chunks/{chunk-QBZ22PQX.js → chunk-EWALXUAD.js} +3 -1
  67. package/chunks/{chunk-PDE7CSDS.js → chunk-FXKI7OW6.js} +2 -2
  68. package/chunks/{chunk-F3ZYSEY6.js → chunk-FY4TT4L4.js} +3 -3
  69. package/chunks/{chunk-QLDPRILK.js → chunk-G7OQ3RFJ.js} +3 -3
  70. package/chunks/{chunk-R2BXK7SX.js → chunk-GHOTR7HL.js} +6 -1
  71. package/chunks/{chunk-KMGMKW5Q.js → chunk-GLPV75ES.js} +9 -3
  72. package/chunks/{chunk-LD2XBG6Z.js → chunk-GO6LNQXT.js} +1 -39
  73. package/chunks/{chunk-CNLEDYE3.js → chunk-H6TNLOLM.js} +5 -5
  74. package/chunks/{chunk-JVHEVCAU.js → chunk-HLW4EGKR.js} +31 -6
  75. package/chunks/{chunk-I4H7PRWQ.js → chunk-HTWHAN44.js} +1 -1
  76. package/chunks/{chunk-QE6KJYLT.js → chunk-IPYE5BIS.js} +123 -25
  77. package/chunks/{chunk-EJYJYJZ2.js → chunk-IRJWHZWU.js} +3 -70
  78. package/chunks/{chunk-EDMZZFDU.js → chunk-J3YPTTNL.js} +930 -798
  79. package/chunks/{chunk-GGBIN5K7.js → chunk-JGAJMIJZ.js} +1 -1
  80. package/chunks/{chunk-N4EJFBN5.js → chunk-JVLP5US5.js} +427 -263
  81. package/chunks/{chunk-R5OP2ATH.js → chunk-JZG7VB6C.js} +11 -11
  82. package/chunks/{chunk-QI7GOGJR.js → chunk-KKQWC6WS.js} +1 -1
  83. package/chunks/{chunk-ADCIHOQZ.js → chunk-KX5QZRW2.js} +142 -30
  84. package/chunks/{chunk-SBBN4BWR.js → chunk-L7OJ3ZO3.js} +1 -1
  85. package/chunks/{chunk-NDOC6M5S.js → chunk-L7QPM2YD.js} +2 -2
  86. package/chunks/{chunk-BVSVIN45.js → chunk-LKWAVAB5.js} +3 -3
  87. package/chunks/{chunk-T4T2FNV4.js → chunk-LOW52WYZ.js} +2 -2
  88. package/chunks/{chunk-4QK3O43S.js → chunk-LU7BIVWC.js} +4 -4
  89. package/chunks/{chunk-ZP6UKOPB.js → chunk-LWGLBVWA.js} +1 -1
  90. package/chunks/{chunk-L7OKCI7T.js → chunk-M7BLWURA.js} +15 -1
  91. package/chunks/{chunk-PR32FRT6.js → chunk-MIM62IXS.js} +3 -3
  92. package/chunks/{chunk-RD6YJJJ6.js → chunk-MOUXUCXQ.js} +1 -1
  93. package/chunks/{chunk-VBZ452JT.js → chunk-MSNGFH6L.js} +494 -114
  94. package/chunks/{chunk-BX5YO7UW.js → chunk-MTNTVOJR.js} +542 -370
  95. package/chunks/chunk-MX2YXRER.js +44 -0
  96. package/chunks/chunk-NIOPRZNN.js +27 -0
  97. package/chunks/{chunk-SMYQZVZ3.js → chunk-NOGSPTLJ.js} +1 -1
  98. package/chunks/{chunk-DDMOU62Q.js → chunk-NPQWQYU6.js} +1 -1
  99. package/chunks/{chunk-MNF6S5PR.js → chunk-P5THQV6A.js} +4 -4
  100. package/chunks/{chunk-6V4NZFUS.js → chunk-PCYOT4PK.js} +5 -5
  101. package/chunks/{chunk-CF6BB3LT.js → chunk-PPFWEV7G.js} +10 -1
  102. package/chunks/{chunk-FXKVLFFY.js → chunk-PSFDB5FZ.js} +3 -3
  103. package/chunks/{chunk-XJYUPHRC.js → chunk-QOMAHFYI.js} +3 -3
  104. package/chunks/{chunk-J37FGIOA.js → chunk-QXPI7FEC.js} +38 -4
  105. package/chunks/{chunk-RRFZXMYP.js → chunk-QZDBHNMV.js} +691 -164
  106. package/chunks/{chunk-S7AKE7HL.js → chunk-R6RI2HIC.js} +63 -9
  107. package/chunks/{chunk-HZT4UCFB.js → chunk-RB7S52NJ.js} +4 -4
  108. package/chunks/{chunk-J33ZR6OJ.js → chunk-RK22ACPP.js} +16 -16
  109. package/chunks/chunk-RM3PGSGL.js +66 -0
  110. package/chunks/chunk-RNBYOUGV.js +42 -0
  111. package/chunks/chunk-S6RLAIUR.js +23 -0
  112. package/chunks/{chunk-3TV3HUWZ.js → chunk-SCYK2YLI.js} +81 -30
  113. package/chunks/{chunk-HPZTOP4I.js → chunk-SEKRT4SD.js} +133 -23
  114. package/chunks/chunk-SEZC2725.js +36 -0
  115. package/chunks/chunk-SIUQ3YYX.js +78 -0
  116. package/chunks/{chunk-O2VBQ3Y2.js → chunk-SJBIFVD2.js} +2 -2
  117. package/chunks/chunk-SYCJMSIJ.js +82 -0
  118. package/chunks/{chunk-JE5FVRPC.js → chunk-T22H4V4R.js} +48 -23
  119. package/chunks/{chunk-3227DZO4.js → chunk-T42ACHFI.js} +1 -1
  120. package/chunks/{chunk-UO5GVXQE.js → chunk-TENOUWY2.js} +3 -3
  121. package/chunks/{chunk-JOLIREYR.js → chunk-TOAUDIS7.js} +1 -1
  122. package/chunks/{chunk-QNKBEJO2.js → chunk-UA6XBZT7.js} +2776 -1289
  123. package/chunks/{chunk-O75BHFQW.js → chunk-UQTYK5SS.js} +2 -2
  124. package/chunks/{chunk-Y6YMRS3U.js → chunk-UY2TCIZS.js} +17 -3
  125. package/chunks/{chunk-FNJ7BDPH.js → chunk-UY7ZAYTY.js} +10 -7
  126. package/chunks/{chunk-UYT3MWEB.js → chunk-UYHWM24C.js} +30 -2
  127. package/chunks/{chunk-SMXULRAN.js → chunk-VXGPYF7U.js} +1 -1
  128. package/chunks/{chunk-BTLJ2MF5.js → chunk-W7CG5HPC.js} +3 -3
  129. package/chunks/{chunk-JJXWTVSK.js → chunk-XHZKDN7M.js} +34 -1
  130. package/chunks/{chunk-K5XU6E4T.js → chunk-XKSCTXPK.js} +2 -2
  131. package/chunks/chunk-Y4LITCRB.js +367 -0
  132. package/chunks/chunk-YHTNWGRR.js +239 -0
  133. package/chunks/{chunk-BKXBARJZ.js → chunk-YPCPZSZB.js} +1 -1
  134. package/chunks/{chunk-QWFNTRH4.js → chunk-YSM24SCE.js} +1 -1
  135. package/chunks/chunk-YTFBURQD.js +313 -0
  136. package/chunks/{chunk-NL4C3H6C.js → chunk-YXK4GPYO.js} +1 -1
  137. package/chunks/{chunk-RYLJU2DY.js → chunk-ZN5T4BHI.js} +1 -1
  138. package/chunks/{computer-use-JGB36FDW.js → computer-use-MTSVPLPY.js} +34 -28
  139. package/chunks/contextCommand-VGJ3BPTK.js +67 -0
  140. package/chunks/{cron-create-SKEY55RG.js → cron-create-6YTCOX6P.js} +4 -4
  141. package/chunks/{cron-delete-I3NAT32Z.js → cron-delete-E3NIFSUK.js} +4 -4
  142. package/chunks/{cron-list-6XRRL6TH.js → cron-list-Q64MNQNE.js} +4 -4
  143. package/chunks/daemon-EEB2RYOL.js +8453 -0
  144. package/chunks/daemon-status-provider-H5SNMK42.js +71 -0
  145. package/chunks/{de-EOW5PVHF.js → de-PLLFDERI.js} +10 -9
  146. package/chunks/{dist-NHEYYB2B.js → dist-ADMIQGHI.js} +2 -2
  147. package/chunks/{dist-J37VXPLD.js → dist-BCQ7INM7.js} +40 -15
  148. package/chunks/{dist-663NZ6MI.js → dist-KNZFCY5F.js} +116 -11
  149. package/chunks/{dist-LB6OGPVM.js → dist-LHUWC3K6.js} +26 -5
  150. package/chunks/{dist-PQPZ5Q4N.js → dist-WBBZ76JZ.js} +20 -9
  151. package/chunks/{earlyInputCapture-44RISVVJ.js → earlyInputCapture-U4ZIQSQU.js} +32 -26
  152. package/chunks/{edit-NN6GM5DT.js → edit-SYIQ2KBI.js} +41 -38
  153. package/chunks/{en-HPWEJBTR.js → en-M4EBUBNU.js} +32 -10
  154. package/chunks/{enter-worktree-PB3O2C7D.js → enter-worktree-HPPOO244.js} +31 -25
  155. package/chunks/{enterPlanMode-YQFNNSUA.js → enterPlanMode-56YYO52U.js} +41 -25
  156. package/chunks/{errors-LS7XU5DZ.js → errors-EO4SXCFP.js} +33 -27
  157. package/chunks/{exit-worktree-E2FTNSBK.js → exit-worktree-UDM7E6XH.js} +31 -25
  158. package/chunks/{exitPlanMode-GLYK2BUB.js → exitPlanMode-M6P3AWLJ.js} +149 -33
  159. package/chunks/{fast-path-WQ6M7LI7.js → fast-path-42P4QYWI.js} +33 -14
  160. package/chunks/{fast-path-settings-OAGU3X5H.js → fast-path-settings-UQLRFDQP.js} +2 -2
  161. package/chunks/{fr-BN5NKSAC.js → fr-MBMNQSQ6.js} +10 -9
  162. package/chunks/{gemini-2YFLVA2M.js → gemini-NRLXS5J7.js} +84 -70
  163. package/chunks/{geminiContentGenerator-XV6SV6CD.js → geminiContentGenerator-DLJPYYPA.js} +41 -5
  164. package/chunks/{glob-2H6CDDMK.js → glob-JPY3F74Y.js} +31 -25
  165. package/chunks/{grep-VXIUXM3C.js → grep-HW23YES5.js} +31 -25
  166. package/chunks/initializer-HWOZTC4A.js +67 -0
  167. package/chunks/{ja-7DEUB2TS.js → ja-HFV5QCHR.js} +10 -9
  168. package/chunks/{keychain-token-storage-YLMDLKXT.js → keychain-token-storage-GPAKRDKQ.js} +2 -2
  169. package/chunks/list-OZGAGRDG.js +71 -0
  170. package/chunks/loadedSettingsAdapter-RUTZVVIA.js +66 -0
  171. package/chunks/{loop-wakeup-CITGGQHA.js → loop-wakeup-G2EHBRGR.js} +5 -5
  172. package/chunks/{ls-R5WP4BGZ.js → ls-IDHUVSAG.js} +4 -4
  173. package/chunks/{lsp-GSICJMMW.js → lsp-FSRYXGFT.js} +2 -2
  174. package/chunks/{monitor-HKLRS2HQ.js → monitor-QVWEIXU3.js} +33 -26
  175. package/chunks/nonInteractiveCli-RV2WYPML.js +109 -0
  176. package/chunks/{notebook-edit-Y6SKSGTT.js → notebook-edit-B26W3FFH.js} +37 -34
  177. package/chunks/{openaiContentGenerator-J7G2FYJT.js → openaiContentGenerator-6TH7AZGQ.js} +15 -13
  178. package/chunks/pidfile-KW2QQJZI.js +73 -0
  179. package/chunks/{pt-TNZRNLQB.js → pt-2THT3YHA.js} +10 -9
  180. package/chunks/{qwenContentGenerator-5XIEM5GV.js → qwenContentGenerator-5IQAC7RN.js} +33 -27
  181. package/chunks/{qwenOAuth2-AG3PFFLZ.js → qwenOAuth2-LWLA6MQG.js} +5 -4
  182. package/chunks/{read-file-J7D552ZJ.js → read-file-CWFAHOI2.js} +11 -10
  183. package/chunks/{read-mcp-resource-DQQW7H7T.js → read-mcp-resource-PIJF24RO.js} +2 -2
  184. package/chunks/ripGrep-FTZGDKY4.js +60 -0
  185. package/chunks/{ru-NIAUBKCN.js → ru-Z67YJHBG.js} +10 -9
  186. package/chunks/{run-qwen-serve-SGEEVMXD.js → run-qwen-serve-FN2SW5ZI.js} +545 -97
  187. package/chunks/{scheduler-7CQI3S4Q.js → scheduler-BMS23O3I.js} +31 -25
  188. package/chunks/{send-message-EE4TWKVU.js → send-message-33PYFMCT.js} +20 -4
  189. package/chunks/serve-PXFYKL65.js +71 -0
  190. package/chunks/{server-4QMUWXPW.js → server-BRKALJ4F.js} +4132 -1175
  191. package/chunks/{session-NRO7UFOY.js → session-AQMISBP4.js} +205 -64
  192. package/chunks/{settings-SGWRDQSC.js → settings-JCPVUZMB.js} +35 -29
  193. package/chunks/{shell-62ANGIHL.js → shell-POHCRVN2.js} +31 -25
  194. package/chunks/{skill-VWAQAWW6.js → skill-QPEVHS6H.js} +13 -12
  195. package/chunks/spawnChannel-V3QT62TE.js +69 -0
  196. package/chunks/{src-KUV5J5Q2.js → src-IZSP7L6Y.js} +127 -32
  197. package/chunks/{startInteractiveUI-6EYJN3JV.js → startInteractiveUI-63KATISO.js} +16762 -16075
  198. package/chunks/{syntheticOutput-T5YHQ6S2.js → syntheticOutput-YOSWIHDJ.js} +3 -3
  199. package/chunks/{task-create-BLY7RHXX.js → task-create-NHS2NGB3.js} +9 -7
  200. package/chunks/{task-list-3QUYNVYR.js → task-list-5UIB564R.js} +7 -6
  201. package/chunks/{task-stop-U4RATK2V.js → task-stop-HGXZ2VZ3.js} +2 -2
  202. package/chunks/{task-update-U54O7KJX.js → task-update-IYYWUILG.js} +40 -7
  203. package/chunks/{team-create-LPOGCPM7.js → team-create-E5O2F3CA.js} +31 -25
  204. package/chunks/{team-delete-LKBYPASD.js → team-delete-FEKJCU4P.js} +7 -6
  205. package/chunks/team-plan-approval-2TU4YROB.js +202 -0
  206. package/chunks/theme-manager-37KOGPJL.js +64 -0
  207. package/chunks/{todoWrite-OYXKTVYN.js → todoWrite-TKMVZ2HT.js} +4 -4
  208. package/chunks/{tool-search-YPUFJ3ZQ.js → tool-search-4ZTGNSFI.js} +38 -11
  209. package/chunks/{trustedFolders-5B24TMEA.js → trustedFolders-RJ5WXJ6L.js} +32 -26
  210. package/chunks/{validateNonInterActiveAuth-BTDTYOVT.js → validateNonInterActiveAuth-ZWMHZAOK.js} +66 -55
  211. package/chunks/{web-fetch-EEB5LZYU.js → web-fetch-J4PIW36Q.js} +5 -4
  212. package/chunks/{workflow-LAWIW5LI.js → workflow-6PG2PC5M.js} +68 -39
  213. package/chunks/workspace-providers-status-4QIZN2W4.js +68 -0
  214. package/chunks/workspace-service-CYMCOEIO.js +78 -0
  215. package/chunks/{write-file-PCV226DJ.js → write-file-R354LGQF.js} +41 -40
  216. package/chunks/{zh-5EUYXASN.js → zh-SLZNSSZB.js} +32 -10
  217. package/chunks/{zh-TW-TR3XYGE5.js → zh-TW-P53C2SL5.js} +32 -10
  218. package/cli.js +6 -6
  219. package/locales/ca.js +15 -13
  220. package/locales/de.js +15 -13
  221. package/locales/en.js +52 -15
  222. package/locales/fr.js +15 -14
  223. package/locales/ja.js +15 -14
  224. package/locales/pt.js +15 -13
  225. package/locales/ru.js +15 -13
  226. package/locales/zh-TW.js +50 -14
  227. package/locales/zh.js +50 -14
  228. package/node_modules/@qwen-code/audio-capture/package.json +1 -1
  229. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
  230. package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
  231. package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
  232. package/package.json +15 -6
  233. package/patches/chrome-devtools-mcp+1.4.0.patch +31 -0
  234. package/postinstall.js +50 -0
  235. package/web-shell/assets/{arc-DA7r8Ueo.js → arc-Bd3048nX.js} +1 -1
  236. package/web-shell/assets/{architectureDiagram-3BPJPVTR-ZDUxPtZ2.js → architectureDiagram-3BPJPVTR-DyzbzABx.js} +1 -1
  237. package/web-shell/assets/{blockDiagram-GPEHLZMM-vrtOf1kJ.js → blockDiagram-GPEHLZMM-CTjxm9Sy.js} +1 -1
  238. package/web-shell/assets/{c4Diagram-AAUBKEIU-BHFjzIi4.js → c4Diagram-AAUBKEIU-DSOFWNU6.js} +1 -1
  239. package/web-shell/assets/channel-oVoup_Ci.js +1 -0
  240. package/web-shell/assets/{chunk-2J33WTMH-mdeQVz59.js → chunk-2J33WTMH-D_PFdjo2.js} +1 -1
  241. package/web-shell/assets/{chunk-4BX2VUAB-BkKMlwBX.js → chunk-4BX2VUAB-52eG6wOT.js} +1 -1
  242. package/web-shell/assets/{chunk-55IACEB6-B7bnXNfu.js → chunk-55IACEB6-CGTZfKkC.js} +1 -1
  243. package/web-shell/assets/{chunk-727SXJPM-DGz8Mm6i.js → chunk-727SXJPM-CZ-qfRUm.js} +1 -1
  244. package/web-shell/assets/{chunk-AQP2D5EJ-BQAydUNk.js → chunk-AQP2D5EJ-B2dSDcqM.js} +1 -1
  245. package/web-shell/assets/{chunk-FMBD7UC4-GHr2LN_p.js → chunk-FMBD7UC4-Cfy3tyA0.js} +1 -1
  246. package/web-shell/assets/{chunk-ND2GUHAM-7R91lnUx.js → chunk-ND2GUHAM-CApNUpUq.js} +1 -1
  247. package/web-shell/assets/{chunk-QZHKN3VN-aTJD3lSQ.js → chunk-QZHKN3VN-CVBG8kgg.js} +1 -1
  248. package/web-shell/assets/classDiagram-4FO5ZUOK-B8WsaT2G.js +1 -0
  249. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-B8WsaT2G.js +1 -0
  250. package/web-shell/assets/{cose-bilkent-S5V4N54A-BTf8t1RL.js → cose-bilkent-S5V4N54A-URd77-_-.js} +1 -1
  251. package/web-shell/assets/{dagre-BM42HDAG-_sgp3WuA.js → dagre-BM42HDAG-C07H4G9-.js} +1 -1
  252. package/web-shell/assets/{diagram-2AECGRRQ-CROtRy1w.js → diagram-2AECGRRQ-CYbpXrSn.js} +1 -1
  253. package/web-shell/assets/{diagram-5GNKFQAL-BqGRzwSc.js → diagram-5GNKFQAL-SI8s6fjX.js} +1 -1
  254. package/web-shell/assets/{diagram-KO2AKTUF-CMrcEtA7.js → diagram-KO2AKTUF-mBkC4RTW.js} +1 -1
  255. package/web-shell/assets/{diagram-LMA3HP47-BRydGNW5.js → diagram-LMA3HP47-CyBPDzWJ.js} +1 -1
  256. package/web-shell/assets/{diagram-OG6HWLK6-D0ilgNjV.js → diagram-OG6HWLK6-DwK7kBCu.js} +1 -1
  257. package/web-shell/assets/{erDiagram-TEJ5UH35-2lnQeJUM.js → erDiagram-TEJ5UH35-Ds1StEUd.js} +1 -1
  258. package/web-shell/assets/{flowDiagram-I6XJVG4X-C_ZMdQiC.js → flowDiagram-I6XJVG4X-BDC4-jHO.js} +1 -1
  259. package/web-shell/assets/{ganttDiagram-6RSMTGT7-Dw9V_Ny0.js → ganttDiagram-6RSMTGT7-CV2aHQqf.js} +1 -1
  260. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-HDXI-F1B.js → gitGraphDiagram-PVQCEYII-B8RcuV_L.js} +1 -1
  261. package/web-shell/assets/index-B5WZ0qP1.js +717 -0
  262. package/web-shell/assets/index-CYE1IMd1.css +5 -0
  263. package/web-shell/assets/{infoDiagram-5YYISTIA-CuB8Ebua.js → infoDiagram-5YYISTIA-DUPoTf50.js} +1 -1
  264. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-D_tyhVwh.js → ishikawaDiagram-YF4QCWOH-FDYOx9rf.js} +1 -1
  265. package/web-shell/assets/{journeyDiagram-JHISSGLW-CgCwJQNU.js → journeyDiagram-JHISSGLW-C5_ohMRp.js} +1 -1
  266. package/web-shell/assets/{kanban-definition-UN3LZRKU-PZUtcbPx.js → kanban-definition-UN3LZRKU-Bn4UnOMG.js} +1 -1
  267. package/web-shell/assets/{linear-CZqRdJIU.js → linear-Bl0pKQtY.js} +1 -1
  268. package/web-shell/assets/{mermaid.core-CPW_4UEF.js → mermaid.core-sevQFoji.js} +72 -72
  269. package/web-shell/assets/{mindmap-definition-RKZ34NQL-D_Y75ID7.js → mindmap-definition-RKZ34NQL-DSH4JZF9.js} +1 -1
  270. package/web-shell/assets/{pieDiagram-4H26LBE5-Q4dTyLeF.js → pieDiagram-4H26LBE5-acWvdQTD.js} +1 -1
  271. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-ckw329Ft.js → quadrantDiagram-W4KKPZXB-DlggX4Sm.js} +1 -1
  272. package/web-shell/assets/{requirementDiagram-4Y6WPE33-DbVDm4It.js → requirementDiagram-4Y6WPE33--mMqwmV8.js} +1 -1
  273. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-CHuZ_ecH.js → sankeyDiagram-5OEKKPKP-BtV9tZ77.js} +1 -1
  274. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DU2YSpq-.js → sequenceDiagram-3UESZ5HK-iDQzPFlj.js} +1 -1
  275. package/web-shell/assets/{stateDiagram-AJRCARHV-CA9PEBUo.js → stateDiagram-AJRCARHV-CsCyQLZ3.js} +1 -1
  276. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CPapECG9.js +1 -0
  277. package/web-shell/assets/{timeline-definition-PNZ67QCA-Bhlo4WVc.js → timeline-definition-PNZ67QCA-Dl0rnKvr.js} +1 -1
  278. package/web-shell/assets/{vennDiagram-CIIHVFJN-6xtPLsyl.js → vennDiagram-CIIHVFJN-qF7nTZzA.js} +1 -1
  279. package/web-shell/assets/{wardley-L42UT6IY-3eozmXPf.js → wardley-L42UT6IY-fWoK9i7F.js} +1 -1
  280. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BOn6hKss.js → wardleyDiagram-YWT4CUSO-pHGfkMnc.js} +1 -1
  281. package/web-shell/assets/{xychartDiagram-2RQKCTM6-BsP4-I_y.js → xychartDiagram-2RQKCTM6-3Doc4Trz.js} +1 -1
  282. package/web-shell/index.html +18 -2
  283. package/chunks/MaxSizedBox-QNF3EIGU.js +0 -63
  284. package/chunks/agent-headless-RQ5BTX3G.js +0 -56
  285. package/chunks/bridge-JL2PMCIW.js +0 -66
  286. package/chunks/chunk-5GOVVXEV.js +0 -73
  287. package/chunks/chunk-BC3C4F3Q.js +0 -40
  288. package/chunks/chunk-BW4KCQFD.js +0 -1103
  289. package/chunks/chunk-RKIZXJMB.js +0 -13
  290. package/chunks/contextCommand-WYU6OQ2F.js +0 -61
  291. package/chunks/daemon-status-provider-HH2EC5FI.js +0 -65
  292. package/chunks/headlessSafetyWarnings-TBE35GJ4.js +0 -55
  293. package/chunks/initializer-2Y7SLOKV.js +0 -61
  294. package/chunks/list-ZAFHZ7OS.js +0 -65
  295. package/chunks/loadedSettingsAdapter-6KJXD6M3.js +0 -60
  296. package/chunks/nonInteractiveCli-5W4GIZQM.js +0 -96
  297. package/chunks/ripGrep-IEIFIWP4.js +0 -54
  298. package/chunks/serve-V6GVZR6U.js +0 -65
  299. package/chunks/spawnChannel-3VHW5EPY.js +0 -63
  300. package/chunks/theme-manager-LYLGAERF.js +0 -58
  301. package/chunks/workspace-providers-status-PIAFVYHA.js +0 -62
  302. package/chunks/workspace-service-YAIOD4HG.js +0 -71
  303. package/web-shell/assets/channel-iRo68q4Q.js +0 -1
  304. package/web-shell/assets/classDiagram-4FO5ZUOK-iHF24a8X.js +0 -1
  305. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-iHF24a8X.js +0 -1
  306. package/web-shell/assets/index-CHmzAWys.css +0 -5
  307. package/web-shell/assets/index-w6GTOHh1.js +0 -713
  308. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-t1LRI0oY.js +0 -1
@@ -0,0 +1,404 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Qwen
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as fs from 'node:fs/promises';
8
+ import * as path from 'node:path';
9
+ import { createDebugLogger } from '../../../utils/debugLogger.js';
10
+
11
+ const debugLogger = createDebugLogger('LOOP_TASK_FILE');
12
+
13
+ export const LOOP_TASK_FILE_MAX_BYTES = 25_000;
14
+
15
+ /** Which candidate a found loop.md came from. The caller maps this to a label
16
+ * (an exhaustive map fails closed if a new candidate is added). */
17
+ export type LoopTaskFileSource = 'project' | 'home';
18
+
19
+ export type LoopTaskFileResult =
20
+ | {
21
+ status: 'found';
22
+ path: string;
23
+ source: LoopTaskFileSource;
24
+ content: string;
25
+ truncated: boolean;
26
+ }
27
+ | {
28
+ status: 'missing';
29
+ checkedPaths: string[];
30
+ };
31
+
32
+ export interface ReadLoopTaskFileOptions {
33
+ projectRoot: string;
34
+ /**
35
+ * Confinement root for the home candidate's resolved (symlink-followed)
36
+ * target — a target escaping this dir (e.g. `-> /etc/passwd`) is refused while
37
+ * an in-root dotfile symlink is followed. Pass `$QWEN_HOME` when set, else
38
+ * `$HOME` (see `homeQwenDir`).
39
+ */
40
+ homeDir: string;
41
+ /**
42
+ * Directory holding the home/global `loop.md` candidate (`<homeQwenDir>/loop.md`).
43
+ * Pass the QWEN_HOME-aware global dir (`Storage.getGlobalQwenDir()`) so a
44
+ * relocated config home is honored instead of always reading the real OS home.
45
+ * Defaults to `<homeDir>/.qwen` so a direct barrel caller keeps the `~/.qwen`
46
+ * layout.
47
+ */
48
+ homeQwenDir?: string;
49
+ /**
50
+ * When false, the project `.qwen/loop.md` candidate is skipped entirely — it
51
+ * is repo-controlled, so an untrusted workspace must not read it and feed it
52
+ * to the model (mirrors the folder-trust gate on project hooks). The
53
+ * home/global `~/.qwen/loop.md` is user-owned and always allowed.
54
+ *
55
+ * Defaults to false (fail-secure): this function is re-exported from the core
56
+ * barrel, so a caller that omits the option must NOT silently read an
57
+ * untrusted workspace's repo-controlled file — callers opt IN by passing the
58
+ * trust-derived value explicitly.
59
+ */
60
+ allowProjectFile?: boolean;
61
+ /**
62
+ * Per-resolver cache for the boundary `fs.realpath()` results. LoopTickResolver
63
+ * passes its own instance-scoped Map so the cache lifetime is tied to the
64
+ * resolver (rebuilt on `/cd`, cleared by `resetCache()`) instead of living
65
+ * forever at module scope. Omitted by direct barrel callers, who fall back to a
66
+ * process-lifetime cache. Eviction-on-failure is preserved either way.
67
+ */
68
+ realDirCache?: Map<string, Promise<string>>;
69
+ }
70
+
71
+ /**
72
+ * Process-lifetime fallback `fs.realpath(dir)` cache for the two confinement
73
+ * boundaries — the workspace root and the home dir. Used only by direct callers
74
+ * of this re-exported function that don't supply their own cache; resolver-driven
75
+ * ticks pass an instance-scoped cache (see `ReadLoopTaskFileOptions.realDirCache`)
76
+ * so the boundary realpath stays invalidatable and a long-lived process can't pin
77
+ * a stale boundary after a `/cd` or symlink re-point. Keyed by the TRUSTED dir the
78
+ * caller passes (never a path derived from file contents), so a caller can't widen
79
+ * a boundary with a stale/broader path.
80
+ */
81
+ const moduleRealDirCache = new Map<string, Promise<string>>();
82
+
83
+ function resolveRealDir(
84
+ dir: string,
85
+ cache: Map<string, Promise<string>>,
86
+ ): Promise<string> {
87
+ let real = cache.get(dir);
88
+ if (real === undefined) {
89
+ real = fs.realpath(dir);
90
+ // Don't pin a rejection: a transient failure (EACCES, ENOENT) must be
91
+ // retried next tick rather than cached, preserving per-tick error semantics.
92
+ real.catch(() => cache.delete(dir));
93
+ cache.set(dir, real);
94
+ }
95
+ return real;
96
+ }
97
+
98
+ /**
99
+ * True when `real` is `root` itself or a descendant of it — the prefix
100
+ * confinement shared by the project and home candidates. The separator isn't
101
+ * double-appended: at a filesystem root `root` is already `/` (or `C:\`), so
102
+ * `root + path.sep` would be `//` / `C:\\`, which no descendant startsWith,
103
+ * wrongly refusing everything — so `real === root` is allowed too.
104
+ */
105
+ function isWithin(root: string, real: string): boolean {
106
+ if (real === root) {
107
+ return true;
108
+ }
109
+ const prefix = root.endsWith(path.sep) ? root : root + path.sep;
110
+ return real.startsWith(prefix);
111
+ }
112
+
113
+ /**
114
+ * Read at most `LOOP_TASK_FILE_MAX_BYTES + 1` bytes — the one extra byte is the
115
+ * truncation signal and the only thing we need past the cap, so a huge/malicious
116
+ * loop.md is never fully read or decoded. Returns `null` for a non-regular node
117
+ * (e.g. a directory at the loop.md path) so the caller skips to the next
118
+ * candidate. Symlink/escape filtering is the caller's job and already done.
119
+ */
120
+ async function readBoundedTaskFile(filePath: string): Promise<Buffer | null> {
121
+ const handle = await fs.open(filePath, 'r');
122
+ try {
123
+ const stat = await handle.stat();
124
+ if (!stat.isFile()) {
125
+ return null;
126
+ }
127
+ const cap = LOOP_TASK_FILE_MAX_BYTES + 1;
128
+ // Size the buffer to the file (+1 to still detect a file that exceeds the
129
+ // cap), never above cap — so a small loop.md doesn't zero-fill 25 KB every
130
+ // tick. `read` below is bounded by this length too, so a file that grows
131
+ // past `stat.size` between stat and read is still read safely (its tail just
132
+ // isn't seen this tick).
133
+ const allocSize = Math.min(cap, stat.size + 1);
134
+ const buffer = Buffer.alloc(allocSize);
135
+ let total = 0;
136
+ // A single read() may return short even before EOF; loop until full or EOF.
137
+ while (total < allocSize) {
138
+ const { bytesRead } = await handle.read(
139
+ buffer,
140
+ total,
141
+ allocSize - total,
142
+ total,
143
+ );
144
+ if (bytesRead === 0) {
145
+ break;
146
+ }
147
+ total += bytesRead;
148
+ }
149
+ return buffer.subarray(0, total);
150
+ } finally {
151
+ // Guard the close so a close failure (e.g. EBADF) can't replace an in-flight
152
+ // read/stat error (e.g. EIO) — JS would otherwise surface the close error and
153
+ // mask the original. Swallow it (debug-log only) and let the original throw.
154
+ try {
155
+ await handle.close();
156
+ } catch (closeErr) {
157
+ debugLogger.debug('failed to close loop.md handle', { closeErr });
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Reads `.qwen/loop.md`, project before home, byte-capped at 25 KB. A missing,
164
+ * directory, non-regular, or empty (whitespace-only) path is skipped to the next
165
+ * candidate rather than treated as present; all candidates exhausted → missing.
166
+ * Only the byte cap lives here — the fire-time resolver owns the user-facing
167
+ * truncation notice so the byte-vs-line nuance stays in one place.
168
+ *
169
+ * Project candidate: must be a real regular file at the literal path, and is
170
+ * stat'd BEFORE the blocking open. A symlinked `.qwen/loop.md` is refused
171
+ * outright — a repo-controlled symlink such as `-> ../.env` resolves *inside*
172
+ * the workspace, so confinement alone would pass and exfiltrate that file to the
173
+ * model. A FIFO/socket/device/dir is refused too, so a named pipe can never
174
+ * wedge the tick (a blocking `open` on a FIFO waits for a writer) or be read as
175
+ * a task list. The canonical path is still confined to the workspace root to
176
+ * catch an *ancestor* symlink like a checked-in `.qwen -> /outside` that a
177
+ * final-component `lstat` cannot see. When `allowProjectFile` is false (untrusted
178
+ * folder) the candidate is dropped entirely.
179
+ *
180
+ * Home candidate: `<homeQwenDir>/loop.md` (the QWEN_HOME-aware global dir, not
181
+ * always the real `~/.qwen`). It is the user's own dotfile, so a symlink IS
182
+ * followed (a common, legitimate setup — e.g. into a synced dotfiles repo), but
183
+ * the resolved target must be a regular file AND stay within the home
184
+ * confinement root (`homeDir`: `$QWEN_HOME` or `$HOME`) so a FIFO/device/dir
185
+ * can't hang the tick and an escaping symlink (e.g. `-> /etc/passwd`) can't be
186
+ * exfiltrated.
187
+ */
188
+ export async function readLoopTaskFile({
189
+ projectRoot,
190
+ homeDir,
191
+ homeQwenDir = path.join(homeDir, '.qwen'),
192
+ allowProjectFile = false,
193
+ realDirCache = moduleRealDirCache,
194
+ }: ReadLoopTaskFileOptions): Promise<LoopTaskFileResult> {
195
+ if (!allowProjectFile) {
196
+ // Repo-controlled file in an untrusted folder — never read it (the
197
+ // candidate is dropped below; this is the trace for why).
198
+ debugLogger.debug('skipping project loop.md: folder is untrusted');
199
+ }
200
+ const candidates: ReadonlyArray<{
201
+ source: LoopTaskFileSource;
202
+ path: string;
203
+ }> = [
204
+ ...(allowProjectFile
205
+ ? [
206
+ {
207
+ source: 'project' as const,
208
+ path: path.join(projectRoot, '.qwen', 'loop.md'),
209
+ },
210
+ ]
211
+ : []),
212
+ { source: 'home', path: path.join(homeQwenDir, 'loop.md') },
213
+ ];
214
+
215
+ for (const { source, path: filePath } of candidates) {
216
+ let buffer: Buffer | null;
217
+ try {
218
+ if (source === 'project') {
219
+ // lstat WITHOUT following the final component, BEFORE the blocking open.
220
+ // A symlinked loop.md is the exfiltration vector (it may point at an
221
+ // in-workspace `.env`, which confinement would wave through), so refuse
222
+ // it; a FIFO/socket/device/dir is refused too so open can never block.
223
+ const projectStat = await fs.lstat(filePath);
224
+ if (projectStat.isSymbolicLink()) {
225
+ debugLogger.debug('skipping symlinked project loop.md', { filePath });
226
+ continue;
227
+ }
228
+ if (!projectStat.isFile()) {
229
+ debugLogger.debug('skipping non-regular project loop.md', {
230
+ filePath,
231
+ });
232
+ continue;
233
+ }
234
+ // A hard-linked loop.md is an ordinary regular file (lstat sees no
235
+ // symlink) but shares a sensitive target's inode (e.g. `ln .env
236
+ // .qwen/loop.md`), so confinement passes on the same fs and the secret
237
+ // would be read every tick. `nlink > 1` is the only tell — refuse it,
238
+ // mirroring canonicalizeKeytermsFile.
239
+ if (projectStat.nlink > 1) {
240
+ debugLogger.debug('skipping hard-linked project loop.md', {
241
+ filePath,
242
+ });
243
+ continue;
244
+ }
245
+ // A final-component lstat can't see an ANCESTOR symlink (e.g. a
246
+ // checked-in `.qwen -> /outside`); realpath resolves it, so confine the
247
+ // canonical path to the workspace root before reading.
248
+ const realRoot = await resolveRealDir(projectRoot, realDirCache);
249
+ const real = await fs.realpath(filePath);
250
+ if (!isWithin(realRoot, real)) {
251
+ debugLogger.debug(
252
+ 'skipping project loop.md that escapes the workspace',
253
+ {
254
+ filePath,
255
+ resolved: real,
256
+ },
257
+ );
258
+ continue;
259
+ }
260
+ buffer = await readBoundedTaskFile(real);
261
+ } else {
262
+ // Home loop.md is the user's own dotfile: a symlink is a legitimate,
263
+ // common setup, so follow it (stat, not lstat). But require the resolved
264
+ // target to be a regular file so a FIFO/device/dir can neither hang the
265
+ // tick on a blocking open nor be decoded as a task list.
266
+ const homeStat = await fs.stat(filePath);
267
+ if (!homeStat.isFile()) {
268
+ debugLogger.debug('skipping non-regular home loop.md', { filePath });
269
+ continue;
270
+ }
271
+ // Same hard-link guard as the project candidate: a `nlink > 1` regular
272
+ // file shares another inode's content (e.g. `ln ~/.ssh/id_ed25519
273
+ // ~/.qwen/loop.md`) and would otherwise be read and fed to the model.
274
+ if (homeStat.nlink > 1) {
275
+ debugLogger.debug('skipping hard-linked home loop.md', { filePath });
276
+ continue;
277
+ }
278
+ // A home symlink IS followed, but its target must stay WITHIN $HOME:
279
+ // otherwise `~/.qwen/loop.md -> /etc/passwd` (or `-> /dev/...`) would be
280
+ // read and fed to the model every tick. In-home dotfile symlinks (e.g.
281
+ // `-> ~/dotfiles/loop.md`) still resolve inside $HOME and are allowed.
282
+ const realHome = await resolveRealDir(homeDir, realDirCache);
283
+ const real = await fs.realpath(filePath);
284
+ if (!isWithin(realHome, real)) {
285
+ debugLogger.debug(
286
+ 'skipping home loop.md that escapes the home directory',
287
+ { filePath, resolved: real },
288
+ );
289
+ continue;
290
+ }
291
+ buffer = await readBoundedTaskFile(real);
292
+ }
293
+ } catch (error) {
294
+ const code = (error as NodeJS.ErrnoException).code;
295
+ // None of these name a readable loop.md, so try the next candidate:
296
+ // absent (ENOENT), a directory (EISDIR), a non-directory path component
297
+ // (ENOTDIR, e.g. a stray file where `.qwen` should be), a symlink loop
298
+ // (ELOOP, e.g. a self-referential `~/.qwen/loop.md`), or an over-long path
299
+ // (ENAMETOOLONG). Anything else (EACCES permissions, real I/O) surfaces
300
+ // rather than being silently swallowed.
301
+ if (
302
+ code === 'ENOENT' ||
303
+ code === 'EISDIR' ||
304
+ code === 'ENOTDIR' ||
305
+ code === 'ELOOP' ||
306
+ code === 'ENAMETOOLONG'
307
+ ) {
308
+ continue;
309
+ }
310
+ throw error;
311
+ }
312
+
313
+ // A non-regular node (e.g. a directory where loop.md was expected) → skip.
314
+ if (buffer === null) {
315
+ continue;
316
+ }
317
+
318
+ // A whitespace-only file is not a task list; fall through to the next path.
319
+ // Log it (like every other skip branch) so a present-but-empty loop.md is
320
+ // distinguishable from an absent one in debug logs.
321
+ if (buffer.toString('utf8').trim().length === 0) {
322
+ debugLogger.debug('skipping whitespace-only loop.md', {
323
+ source,
324
+ filePath,
325
+ });
326
+ continue;
327
+ }
328
+
329
+ const truncated = buffer.byteLength > LOOP_TASK_FILE_MAX_BYTES;
330
+ let content: string;
331
+ if (truncated) {
332
+ // Cap by bytes on a UTF-8 char boundary. First back off any trailing
333
+ // continuation bytes (10xxxxxx) left by a mid-character cut at the cap...
334
+ let end = LOOP_TASK_FILE_MAX_BYTES;
335
+ while (end > 0 && (buffer[end] & 0xc0) === 0x80) {
336
+ end--;
337
+ }
338
+ // ...then drop any malformed trailing unit. `lead` is the last
339
+ // non-continuation byte, and the back-off skipped exactly the continuation
340
+ // bytes after it, so the trailing character is well-formed iff its declared
341
+ // width reaches `end` exactly. A mismatch is either an INCOMPLETE lead (too
342
+ // few continuations, `lead + width > end`) or an ORPHAN lead whose stray
343
+ // continuations belong to nothing (`lead + width < end`) — e.g. a width
344
+ // check that only tests `> end` keeps `c3 41 80 80 80` (orphan `c3` plus
345
+ // stray continuations after the `41`). Drop the unit and re-check, since
346
+ // several malformed units can stack. Each surviving orphan decodes to a
347
+ // trailing U+FFFD the byte-length re-clamp below cannot remove, so this loop
348
+ // is load-bearing and the re-clamp is a pure safety net.
349
+ while (end > 0) {
350
+ let lead = end - 1;
351
+ while (lead >= 0 && (buffer[lead] & 0xc0) === 0x80) {
352
+ lead--;
353
+ }
354
+ if (lead < 0) {
355
+ break;
356
+ }
357
+ const b = buffer[lead];
358
+ const width =
359
+ (b & 0x80) === 0x00
360
+ ? 1
361
+ : (b & 0xe0) === 0xc0
362
+ ? 2
363
+ : (b & 0xf0) === 0xe0
364
+ ? 3
365
+ : (b & 0xf8) === 0xf0
366
+ ? 4
367
+ : 1; // invalid lead (0xC0/0xC1/0xF8–0xFF): treat as a 1-byte unit
368
+ if (lead + width === end) {
369
+ break; // a complete, well-formed trailing character
370
+ }
371
+ end = lead;
372
+ }
373
+ content = buffer.subarray(0, end).toString('utf8');
374
+ while (Buffer.byteLength(content, 'utf8') > LOOP_TASK_FILE_MAX_BYTES) {
375
+ content = content.slice(0, -1);
376
+ }
377
+ } else {
378
+ content = buffer.toString('utf8');
379
+ }
380
+
381
+ // The one happy-path trace (all other logs here are skip/failure) so oncall
382
+ // can confirm a tick actually picked up a file. Logs the relative source
383
+ // label and byte count, never the absolute path (which would leak the OS
384
+ // username / dir layout into debug logs).
385
+ debugLogger.debug('read loop.md', {
386
+ source,
387
+ bytes: buffer.byteLength,
388
+ truncated,
389
+ });
390
+
391
+ return {
392
+ status: 'found',
393
+ path: filePath,
394
+ source,
395
+ content,
396
+ truncated,
397
+ };
398
+ }
399
+
400
+ return {
401
+ status: 'missing',
402
+ checkedPaths: candidates.map((c) => c.path),
403
+ };
404
+ }