@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.
- package/bundled/extension-creator/SKILL.md +357 -0
- package/bundled/loop/SKILL.md +21 -7
- package/bundled/loop/SKILL.test.ts +41 -0
- package/bundled/loop/loop-task-file.test.ts +1064 -0
- package/bundled/loop/loop-task-file.ts +404 -0
- package/bundled/loop/loop-tick-resolver.test.ts +665 -0
- package/bundled/loop/loop-tick-resolver.ts +347 -0
- package/bundled/qc-helper/SKILL.md +26 -19
- package/bundled/qc-helper/docs/configuration/auth.md +11 -11
- package/bundled/qc-helper/docs/configuration/model-providers.md +13 -13
- package/bundled/qc-helper/docs/configuration/settings.md +76 -75
- package/bundled/qc-helper/docs/features/channels/plugins.md +1 -1
- package/bundled/qc-helper/docs/features/commands.md +108 -77
- package/bundled/qc-helper/docs/features/hooks.md +6 -1
- package/bundled/qc-helper/docs/features/memory.md +52 -0
- package/bundled/qc-helper/docs/qwen-serve.md +24 -2
- package/bundled/qc-helper/docs/support/tos-privacy.md +16 -3
- package/bundled/qc-helper/docs/support/troubleshooting.md +6 -0
- package/chunks/{MaxSizedBox-7RERNLFK.js → MaxSizedBox-T5X5MN5Q.js} +29 -27
- package/chunks/{StandaloneSessionPicker-S22XBSN6.js → StandaloneSessionPicker-P3E3WTOQ.js} +42 -37
- package/chunks/{acpAgent-4MCETHF2.js → acpAgent-QEP6DXFW.js} +864 -175
- package/chunks/{agent-K3ENVYSB.js → agent-UDANDUF7.js} +24 -23
- package/chunks/{agent-headless-HO3V3A3A.js → agent-headless-KYVSZHSO.js} +24 -23
- package/chunks/{anthropicContentGenerator-E3TQ5XJJ.js → anthropicContentGenerator-KWSJAQ7B.js} +33 -12
- package/chunks/{artifact-tool-QA4KRO3B.js → artifact-tool-D2X7G3C3.js} +2 -2
- package/chunks/{askUserQuestion-HMWXW6VN.js → askUserQuestion-UGETICJQ.js} +2 -2
- package/chunks/{bridge-4LV2HBH2.js → bridge-X42LXPTA.js} +31 -30
- package/chunks/{ca-7WC7ELX6.js → ca-IUFWESZL.js} +1 -0
- package/chunks/{chunk-JXQ23SNT.js → chunk-2JKEQWDZ.js} +623 -78
- package/chunks/{chunk-2TJ3YN6Q.js → chunk-2RJA6J57.js} +416 -401
- package/chunks/{chunk-3UALQ56H.js → chunk-3227DZO4.js} +1 -1
- package/chunks/{chunk-BGUDAULQ.js → chunk-3CPCDXFR.js} +5 -5
- package/chunks/{chunk-5RVSM6NW.js → chunk-3HUHAQF3.js} +1 -1
- package/chunks/{chunk-D5E5TMYH.js → chunk-3M4W4U5G.js} +16 -2
- package/chunks/{chunk-DBZUXSSV.js → chunk-4X7NIEQJ.js} +3 -3
- package/chunks/chunk-5GOVVXEV.js +73 -0
- package/chunks/{chunk-2BAQ3ACS.js → chunk-5SM4VT6V.js} +405 -5
- package/chunks/{chunk-NYOHZYHM.js → chunk-5XQ4A26L.js} +45 -3
- package/chunks/{chunk-CI5O6SQL.js → chunk-6FKEVYCD.js} +4 -4
- package/chunks/{chunk-ZD3NW5KL.js → chunk-7LM4XHPE.js} +63 -9
- package/chunks/chunk-7UNP2XM7.js +840 -0
- package/chunks/{chunk-OQHQHJRK.js → chunk-ARYCHZ4B.js} +151 -4775
- package/chunks/chunk-BC3C4F3Q.js +40 -0
- package/chunks/{chunk-CGEGHF2C.js → chunk-BKXBARJZ.js} +1 -1
- package/chunks/chunk-BMFMJINR.js +45 -0
- package/chunks/chunk-BOJWHRMX.js +11273 -0
- package/chunks/{chunk-CGOMICBE.js → chunk-BSZEQNUA.js} +34 -1
- package/chunks/{chunk-OIC6S73E.js → chunk-BUUQ5HPX.js} +1 -1
- package/chunks/{chunk-2BIF2NVV.js → chunk-BVSVIN45.js} +2 -2
- package/chunks/{chunk-TOEGRAVX.js → chunk-BX5YO7UW.js} +17 -5
- package/chunks/{chunk-4W4T77MO.js → chunk-BXVWV4GI.js} +109 -6
- package/chunks/{chunk-VQRYPFCV.js → chunk-CYHVPQYT.js} +10 -460
- package/chunks/{chunk-PRYMDRBV.js → chunk-DDMOU62Q.js} +1 -1
- package/chunks/{chunk-S566HKUS.js → chunk-DHXWNSJA.js} +15 -1
- package/chunks/{chunk-F6XNTYNE.js → chunk-E6XQPQXW.js} +30 -307
- package/chunks/{chunk-QBZ22PQX.js → chunk-EWALXUAD.js} +3 -1
- package/chunks/{chunk-EL5IHQON.js → chunk-EWYQFWYF.js} +6 -27
- package/chunks/{chunk-HG6YKEBI.js → chunk-F2DOT6UZ.js} +161 -30
- package/chunks/{chunk-JTYRZODB.js → chunk-FOOIZCWL.js} +16 -7
- package/chunks/{chunk-EX4W6ZYG.js → chunk-G5G4TFZ7.js} +1 -1
- package/chunks/{chunk-EW6C27XX.js → chunk-GEHVIWAT.js} +9 -3
- package/chunks/chunk-HEVBZSXG.js +4923 -0
- package/chunks/chunk-HFQ2XSE7.js +690 -0
- package/chunks/{chunk-PF523XJX.js → chunk-HPGTGMP3.js} +1 -1
- package/chunks/{chunk-OTBK43JR.js → chunk-HYCC3XM3.js} +1 -1
- package/chunks/{chunk-TMBPDHXD.js → chunk-I4H7PRWQ.js} +1 -1
- package/chunks/{chunk-EEEZIVFJ.js → chunk-JOLIREYR.js} +1 -1
- package/chunks/{chunk-TZM5JIAX.js → chunk-JYPQTRA3.js} +10 -7
- package/chunks/chunk-K5XU6E4T.js +167 -0
- package/chunks/{chunk-BS64PXY3.js → chunk-KFVU7ODN.js} +1 -1
- package/chunks/{chunk-3RJ3ZPPR.js → chunk-KKQWC6WS.js} +7 -7
- package/chunks/{chunk-3QFE4OTE.js → chunk-LBW7NW7X.js} +4 -4
- package/chunks/{chunk-Z7XXFPGP.js → chunk-LDWTYE33.js} +1 -1
- package/chunks/{chunk-EXMX5YHG.js → chunk-LP5E4YIH.js} +1 -1
- package/chunks/{chunk-ZUAXI2OA.js → chunk-M2U5NM24.js} +188 -11366
- package/chunks/{chunk-I5T6PIBQ.js → chunk-MBV2CTRL.js} +1010 -925
- package/chunks/{chunk-5JZLXFPD.js → chunk-MTHMXSNT.js} +11 -11
- package/chunks/{chunk-FPACXT7T.js → chunk-MTJVHQDP.js} +534 -164
- package/chunks/chunk-NDOC6M5S.js +481 -0
- package/chunks/{chunk-HGNSRCRB.js → chunk-NL4C3H6C.js} +1 -1
- package/chunks/{chunk-GZCJS5WH.js → chunk-O2VBQ3Y2.js} +2 -2
- package/chunks/{chunk-UN3C6GOR.js → chunk-PPFWEV7G.js} +10 -108
- package/chunks/{chunk-RS4HRC75.js → chunk-QI2CFVRF.js} +85 -8
- package/chunks/{chunk-XU5P23GN.js → chunk-QLDPRILK.js} +3 -3
- package/chunks/chunk-QYUE6W3T.js +123 -0
- package/chunks/{chunk-USGMPVOY.js → chunk-R3LHDWF2.js} +21 -12
- package/chunks/{chunk-BNXANYLH.js → chunk-RD6YJJJ6.js} +81 -1
- package/chunks/{chunk-PXFFKNCO.js → chunk-RIP623NO.js} +4877 -2469
- package/chunks/{chunk-XKBP7M7A.js → chunk-RYBSUGOR.js} +142 -30
- package/chunks/{chunk-BG4BIP7F.js → chunk-SBBN4BWR.js} +4 -2
- package/chunks/{chunk-T2VDEFUU.js → chunk-SKQZZVAV.js} +2 -2
- package/chunks/{chunk-5R4SBCX6.js → chunk-SL5MOGQH.js} +3 -3
- package/chunks/chunk-SMXULRAN.js +37 -0
- package/chunks/{chunk-WA5OJGFL.js → chunk-SS6WJMQK.js} +363 -76
- package/chunks/{chunk-UAARVVBI.js → chunk-TNWFHGN6.js} +1 -1
- package/chunks/{chunk-I2RFAW6Q.js → chunk-TT6Q3EN4.js} +7 -5
- package/chunks/chunk-UAKXW3DN.js +30 -0
- package/chunks/{chunk-C4K3FEQ2.js → chunk-UX4SGCGN.js} +32 -3
- package/chunks/{chunk-J6AW7O5D.js → chunk-WHDQZHRU.js} +2 -2
- package/chunks/{chunk-WGAEWQTF.js → chunk-WN6K47NR.js} +1 -2
- package/chunks/{chunk-UAARJR2G.js → chunk-WQT5SOI6.js} +2 -2
- package/chunks/{chunk-VYFMN7C6.js → chunk-WUMVIRTN.js} +225 -37
- package/chunks/{chunk-R36VMVEP.js → chunk-XJMKOTVV.js} +5 -5
- package/chunks/{chunk-TXVGVZYR.js → chunk-XJYUPHRC.js} +3 -3
- package/chunks/{chunk-2JD55C6G.js → chunk-Y6YMRS3U.js} +2 -2
- package/chunks/{chunk-LLFFKXZZ.js → chunk-YALKC62D.js} +56 -29
- package/chunks/{chunk-GYZQA2FO.js → chunk-YLZJCUH6.js} +1 -1
- package/chunks/chunk-YTFBURQD.js +313 -0
- package/chunks/{chunk-KBXLIVWQ.js → chunk-ZFPRYHWR.js} +2 -2
- package/chunks/{chunk-X6EDWN44.js → chunk-ZP6UKOPB.js} +1 -1
- package/chunks/{computer-use-6BBRNT6S.js → computer-use-LF2S6LUZ.js} +103 -49
- package/chunks/{contextCommand-UJAJ6N57.js → contextCommand-VPX5UG55.js} +26 -25
- package/chunks/{cron-create-4KQE6YLH.js → cron-create-SKEY55RG.js} +4 -4
- package/chunks/{cron-delete-THME66UP.js → cron-delete-I3NAT32Z.js} +6 -6
- package/chunks/{cron-list-7A54SJ5N.js → cron-list-6XRRL6TH.js} +5 -5
- package/chunks/{daemon-status-provider-CSL3BUOY.js → daemon-status-provider-5JAWZS4V.js} +34 -34
- package/chunks/{de-ACHALDED.js → de-EOW5PVHF.js} +1 -0
- package/chunks/{dist-BD27IA3L.js → dist-4FCNM6KJ.js} +2 -2
- package/chunks/{dist-OD2FY543.js → dist-O3H4PABL.js} +2 -2
- package/chunks/{dist-BOOH5JWN.js → dist-SCAAUWPS.js} +18 -4
- package/chunks/{dist-QHJRBCMP.js → dist-UKPWQJCL.js} +44 -2
- package/chunks/{dist-QFFZWACU.js → dist-VD7HXTML.js} +20 -9
- package/chunks/{earlyInputCapture-X6KMCSHD.js → earlyInputCapture-7FRQMU4P.js} +25 -24
- package/chunks/{edit-7J6WXARF.js → edit-REKJDIRL.js} +68 -27
- package/chunks/{en-NCUBOJOY.js → en-HPWEJBTR.js} +8 -0
- package/chunks/{enter-worktree-KULCKJPJ.js → enter-worktree-GBKUDCD3.js} +24 -23
- package/chunks/{enterPlanMode-H2CIOLMH.js → enterPlanMode-WB3Y4FGT.js} +24 -23
- package/chunks/{errors-6G2JYR54.js → errors-2XJDYO3M.js} +26 -25
- package/chunks/{exit-worktree-ECBWBVC5.js → exit-worktree-KKC7O2ZV.js} +24 -23
- package/chunks/{exitPlanMode-DQV56GTW.js → exitPlanMode-7HMS3TF7.js} +24 -23
- package/chunks/{fast-path-35JJFS3C.js → fast-path-XVNIYWGG.js} +4 -4
- package/chunks/{fast-path-settings-TSVI53EB.js → fast-path-settings-UQLRFDQP.js} +3 -2
- package/chunks/{fr-GVKZ27MF.js → fr-BN5NKSAC.js} +1 -0
- package/chunks/{gemini-NDTG7WAX.js → gemini-U5TPU3IQ.js} +210 -104
- package/chunks/{geminiContentGenerator-XTG6SFLA.js → geminiContentGenerator-ZVWRHXOO.js} +4 -4
- package/chunks/{glob-SJ2JBZK4.js → glob-FEJWIH2W.js} +24 -23
- package/chunks/{grep-BRMWFEWR.js → grep-KMP25IIA.js} +24 -23
- package/chunks/{headlessSafetyWarnings-LJ6FJX77.js → headlessSafetyWarnings-5DEDET4M.js} +24 -23
- package/chunks/{initializer-P5AXF7PV.js → initializer-FJTACLOH.js} +31 -28
- package/chunks/{ja-FKZ2WZ4D.js → ja-7DEUB2TS.js} +1 -0
- package/chunks/{keychain-token-storage-CSYMNRPP.js → keychain-token-storage-YLMDLKXT.js} +2 -2
- package/chunks/{list-6COK34BP.js → list-SCQXYJUW.js} +32 -29
- package/chunks/{loadedSettingsAdapter-XIUTN245.js → loadedSettingsAdapter-P6KPNAG7.js} +30 -27
- package/chunks/{loop-wakeup-33GPXJLA.js → loop-wakeup-FYEQEJUA.js} +7 -7
- package/chunks/{ls-BYQY7RVV.js → ls-R5WP4BGZ.js} +4 -4
- package/chunks/{lsp-X54RO4IV.js → lsp-GSICJMMW.js} +2 -2
- package/chunks/{monitor-DH7KFSJV.js → monitor-JU3HHHVY.js} +24 -23
- package/chunks/nonInteractiveCli-IZEKUHU3.js +100 -0
- package/chunks/{notebook-edit-D4R4ZLMA.js → notebook-edit-YHY3Z2QQ.js} +52 -23
- package/chunks/{openaiContentGenerator-F74IPQ6E.js → openaiContentGenerator-4WUVA3Q2.js} +13 -12
- package/chunks/{pt-SY5NCFGR.js → pt-TNZRNLQB.js} +1 -0
- package/chunks/{qwenContentGenerator-ATTK5P4D.js → qwenContentGenerator-MVYYR44B.js} +26 -25
- package/chunks/{qwenOAuth2-K4AASPCV.js → qwenOAuth2-6HNGHALT.js} +5 -4
- package/chunks/{read-file-767G23OY.js → read-file-NABEO2X4.js} +8 -8
- package/chunks/{read-mcp-resource-I5HCZJ6X.js → read-mcp-resource-DQQW7H7T.js} +2 -2
- package/chunks/{ripGrep-4OMMGSWX.js → ripGrep-YXLK6UTX.js} +24 -23
- package/chunks/{ru-FWNXJG4F.js → ru-NIAUBKCN.js} +1 -0
- package/chunks/{run-qwen-serve-REKSTYRA.js → run-qwen-serve-4B5QBMLQ.js} +144 -41
- package/chunks/{scheduler-UNTFDQLG.js → scheduler-XAKIYBDL.js} +24 -23
- package/chunks/{send-message-2DAFXEYP.js → send-message-EE4TWKVU.js} +2 -2
- package/chunks/{serve-KZ56QB5S.js → serve-4RXVLUKD.js} +30 -28
- package/chunks/{server-BV5TZUMU.js → server-7PA5QGIA.js} +11037 -8747
- package/chunks/{session-OIBXFSGD.js → session-WNVWRYAH.js} +194 -57
- package/chunks/{settings-WJTCBIRA.js → settings-YJH32TFF.js} +30 -27
- package/chunks/{shell-WC52IBHW.js → shell-YFLABOS5.js} +24 -23
- package/chunks/{skill-U7F7JR4M.js → skill-N3J7FUAY.js} +38 -11
- package/chunks/{spawnChannel-EI2E5BCW.js → spawnChannel-D6WVMFZJ.js} +26 -25
- package/chunks/{src-4F6FBZFI.js → src-IU5GDBEH.js} +88 -28
- package/chunks/{startInteractiveUI-SFRVB3CE.js → startInteractiveUI-QIPQTRT4.js} +2784 -2474
- package/chunks/{syntheticOutput-PPS3HDVA.js → syntheticOutput-T5YHQ6S2.js} +3 -3
- package/chunks/{task-create-OUYCA553.js → task-create-BLY7RHXX.js} +6 -6
- package/chunks/{task-list-WAX7I2IA.js → task-list-3QUYNVYR.js} +5 -5
- package/chunks/{task-stop-CAIL6FNH.js → task-stop-U4RATK2V.js} +2 -2
- package/chunks/{task-update-AHEO5OUT.js → task-update-U54O7KJX.js} +6 -6
- package/chunks/{team-create-3WC7CIIW.js → team-create-TP4CLLM3.js} +24 -23
- package/chunks/{team-delete-HGHFG23B.js → team-delete-LKBYPASD.js} +5 -5
- package/chunks/{theme-manager-DICFCT3P.js → theme-manager-N2TBIBZT.js} +25 -24
- package/chunks/{todoWrite-IFBMN3FH.js → todoWrite-OYXKTVYN.js} +4 -4
- package/chunks/{tool-search-L5K7HLER.js → tool-search-GBBSICYB.js} +101 -16
- package/chunks/trustedFolders-F7TBU5CD.js +76 -0
- package/chunks/types-QX5C3CHJ.js +12 -0
- package/chunks/{validateNonInterActiveAuth-GCH4Z6KI.js → validateNonInterActiveAuth-EQDQPSMI.js} +55 -50
- package/chunks/{web-fetch-B5IIQ5TT.js → web-fetch-PCVCML2X.js} +29 -15
- package/chunks/{workflow-OB3AND7I.js → workflow-ANJ4FYBK.js} +32 -30
- package/chunks/{workspace-providers-status-ZIR5EPOO.js → workspace-providers-status-462QQKQM.js} +32 -29
- package/chunks/workspace-service-VTG4G6CW.js +75 -0
- package/chunks/{write-file-SEXBNDZM.js → write-file-3P3WMQQW.js} +63 -27
- package/chunks/{zh-RBEGQVQT.js → zh-5EUYXASN.js} +8 -0
- package/chunks/{zh-TW-QGAVRXT3.js → zh-TW-TR3XYGE5.js} +8 -0
- package/cli-entry.js +20 -9
- package/cli.js +6 -6
- package/locales/ca.js +2 -0
- package/locales/de.js +2 -0
- package/locales/en.js +14 -0
- package/locales/fr.js +2 -0
- package/locales/ja.js +1 -0
- package/locales/pt.js +2 -0
- package/locales/ru.js +2 -0
- package/locales/zh-TW.js +12 -0
- package/locales/zh.js +12 -0
- package/node_modules/@qwen-code/audio-capture/dist/index.d.ts +35 -0
- package/node_modules/@qwen-code/audio-capture/dist/index.js +48 -0
- package/node_modules/@qwen-code/audio-capture/dist/index.js.map +1 -0
- package/node_modules/@qwen-code/audio-capture/dist/platform.d.ts +7 -0
- package/node_modules/@qwen-code/audio-capture/dist/platform.js +18 -0
- package/node_modules/@qwen-code/audio-capture/dist/platform.js.map +1 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/LICENSE +21 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/README.md +58 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/SECURITY.md +5 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/bin.js +84 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/build-test.js +19 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/index.js +6 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/node-gyp-build.js +207 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/optional.js +7 -0
- package/node_modules/@qwen-code/audio-capture/node_modules/node-gyp-build/package.json +43 -0
- package/node_modules/@qwen-code/audio-capture/package.json +30 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/linux-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/linux-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
- package/package.json +18 -6
- package/patches/chrome-devtools-mcp+1.4.0.patch +31 -0
- package/postinstall.js +50 -0
- package/web-shell/assets/{arc-D_ae_Y9k.js → arc-DIemsO_F.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-BMLRjppa.js → architectureDiagram-3BPJPVTR-CTlUvIQv.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-qVDQC8KL.js → blockDiagram-GPEHLZMM-DG0jmIZK.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-CxWS6MBd.js → c4Diagram-AAUBKEIU-WljDBi55.js} +1 -1
- package/web-shell/assets/channel-CbzRbdpA.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-BtNuY5Yp.js → chunk-2J33WTMH-8nc6onr2.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-BU-x368d.js → chunk-4BX2VUAB-EopXix8Z.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-DRgTLvBu.js → chunk-55IACEB6-BE4Jz3N3.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-D2aElv7q.js → chunk-727SXJPM-C_eErgDN.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-BwHCMRQ0.js → chunk-AQP2D5EJ-DFtVW-Is.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-BMyPH-ku.js → chunk-FMBD7UC4-CJPyxIg9.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-BNKTstUf.js → chunk-ND2GUHAM-Bk-xgj6M.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-gD3vsFId.js → chunk-QZHKN3VN-p3Y4yaFB.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-BFdyQ52h.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BFdyQ52h.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-BG9FAsXE.js → cose-bilkent-S5V4N54A-BCMO6Js3.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-Cy1sR7om.js → dagre-BM42HDAG-DUMrwASf.js} +1 -1
- package/web-shell/assets/default-aEtf0G7-.svg +1 -0
- package/web-shell/assets/{diagram-2AECGRRQ-Bxi1I_Vu.js → diagram-2AECGRRQ-C5d7qJbL.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-BGCJjO6y.js → diagram-5GNKFQAL-CjLzMhbT.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-CMTeLXp7.js → diagram-KO2AKTUF-RCoNWeAm.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-BN1wyAGh.js → diagram-LMA3HP47-KThXjaNV.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-UYSG_zcp.js → diagram-OG6HWLK6-DKsC2BWz.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-DqXL08lw.js → erDiagram-TEJ5UH35-DJ2ukmq0.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-BPJ9SFji.js → flowDiagram-I6XJVG4X-zzqaARRX.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BEd9Pnms.js → ganttDiagram-6RSMTGT7-kFUg-gqT.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DrNRH3oi.js → gitGraphDiagram-PVQCEYII-C6JnMVzD.js} +1 -1
- package/web-shell/assets/index-B3XUm7dw.js +713 -0
- package/web-shell/assets/index-CHmzAWys.css +5 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-CFYBrkVI.js → infoDiagram-5YYISTIA-XqPVhp89.js} +1 -1
- package/web-shell/assets/insert-DmRbh9Rv.svg +1 -0
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-Dxsddth7.js → ishikawaDiagram-YF4QCWOH-D2k8sGpf.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-DxaDK80t.js → journeyDiagram-JHISSGLW-CQEST1GE.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-Q0jQCOct.js → kanban-definition-UN3LZRKU-DZ7kqVBC.js} +1 -1
- package/web-shell/assets/{linear-DGGM2kcP.js → linear-B6C_6q_E.js} +1 -1
- package/web-shell/assets/{mermaid.core-ByoxnWut.js → mermaid.core-CsyNP1qt.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-C0ZseHsi.js → mindmap-definition-RKZ34NQL-B1VDIzJq.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-MQXNhl5A.js → pieDiagram-4H26LBE5-DN0E7Jwu.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-DIR4ee2A.js → quadrantDiagram-W4KKPZXB-UKTQdHKm.js} +1 -1
- package/web-shell/assets/queue-DWpnpFgW.svg +1 -0
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-BTR0UdYQ.js → requirementDiagram-4Y6WPE33-BRGceQvN.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-sfnhMELN.js → sankeyDiagram-5OEKKPKP-2DDyu41v.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-DXczHOHH.js → sequenceDiagram-3UESZ5HK-CT5iE8ZU.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-DdE_KQFH.js → stateDiagram-AJRCARHV-CqMQ_s4G.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CamhV5pM.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-DIc61uE8.js → timeline-definition-PNZ67QCA-Di37QSZU.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-BF7ODmxB.js → vennDiagram-CIIHVFJN-DCXvpz5S.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-3pc1MEaw.js → wardley-L42UT6IY-CpUCk6OX.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-9jnPcZuP.js → wardleyDiagram-YWT4CUSO-D01pVEBg.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-DcthaZK6.js → xychartDiagram-2RQKCTM6-DOu5JDoz.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-B6HQGTYR.js +0 -410
- package/chunks/chunk-R2BXK7SX.js +0 -13
- package/chunks/chunk-RKIZXJMB.js +0 -13
- package/chunks/nonInteractiveCli-7575LBXP.js +0 -91
- package/chunks/workspace-service-OM5D4OI2.js +0 -60
- package/web-shell/assets/channel-DYPNiTww.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-p_g25Tni.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-p_g25Tni.js +0 -1
- package/web-shell/assets/index-CpOXWDPh.js +0 -714
- package/web-shell/assets/index-oVK7qhre.css +0 -5
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BHUSEndU.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
|
+
}
|