chainlesschain 0.162.95 → 0.162.97
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/README.md +1 -1
- package/bin/chainlesschain.js +8 -18
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DLvvkb9x.js → AIOps-C1YvIYh5.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxhQTwqd.js → ActionButton-jqcXEakj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C4oUXzhG.js → Analytics-C8Xwqd3_.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CjzKJdv3.js → AppLayout-Dx5ypTfA.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-B-MrugUs.js → Audit-1GZ4Tv2f.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ecIfEpIZ.js → Backup-CexR2hyk.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUcSfWnx.js → BaseInput-DwAruKdy.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DIc1n_Lk.js → Chat-W2FkU5BY.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BGzg7oVA.js → ChatBubbleRenderer-BCLnhLfu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQ3Ueff-.js → Checkbox-Dkh5JY5B.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvSGLnqE.js → Codegen-LfYOgsdA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Br9IXZcE.js → Col-DEgketAC.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tPuLOf0r.js → Community-LeFQMaZm.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-vMWH73BE.js → Compact-CrlKYvBc.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ct9z699x.js → Compliance-ca_4PGYF.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bxs1kL1y.js → Cowork-BcMWqh2a.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-QEpr2k4U.js → Cron-pVVEA3dw.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-u4xbUw6O.js → Crosschain-CiKSGTqp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DXTEvyrW.js → DID-B2bGzdOj.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C4pZgzkg.js → Dashboard-sQ6jcgCn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-LRuqREGe.js → Dropdown-rFx_8P04.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D7XI1yBM.js → EmailListRenderer-smw2hh6f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-3y_SuAMQ.js → FamilyGuardDashboard-CZhg2_S2.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DbjbJJCW.js → Federation-DQE-368G.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-WPpMZcUw.js → FormItemContext-2XPb9izr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-nNT9Tn4E.js → GenericCardRenderer-DCGMz5E6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DqDaVzwV.js → Git-BFwrxK4Q.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BquBZpcg.js → Governance-ChxBRwG1.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RcPPYijs.js → Inference-C7kyknu_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ByXfZV13.js → KnowledgeGraph-BsMd4XAt.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CHT3DUcq.js → Logs-Bs3yrpow.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Bi0HJ9pN.js → Marketplace-Cc29jdBv.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-QCBxPRRc.js → McpTools-zYpYsUsl.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D4RlxGXn.js → Memory-x9r4yKtS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CU_PV7e-.js → MobileBridge-CjUo8prx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-c1CMFD8z.js → MobileProjects-Cmz4wVrF.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BezDB1-s.js → Mtc-Y-SBnNxm.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CgLpiHpE.js → MtcAudit-ogiKnrxj.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-vx6bz-bA.js → Multisig-C1hygkFR.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CGOagg3-.js → NLProgramming-fWWYiTsY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BcuDGYRS.js → Notes-CkCNF4gC.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ZAGCDUpc.js → NotificationSettings-DThB7WQG.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-B5onE0eX.js → OrderTableRenderer-Dh64aMq6.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-y_y20-5Y.js → Organization-BSY16zvP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BEICS31D.js → Overflow-C_TDh18K.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D_WRMzh4.js → P2P-CvxZk6og.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-0JZ5JF4M.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-3aqCc_4f.js → Permissions-DV8ubpjM.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DT3R9g9Y.js → PersonalDataHub-g2Dgls8e.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-MiMWIhw0.js → Pipeline-DGp8zQ0C.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C3JsLuDa.js → Privacy-C0uHrjWR.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-EhHVi1ff.js → ProjectInit-2XGICaFt.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D_dNEA37.js → ProjectSettings-Cfy72e0Q.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CXydUXp8.js → Projects-Xsvk_kKT.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CPEhi2iZ.js → Providers-vuvDVg7U.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BFQpMleX.js → QuickAsk-BUy7kPyQ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DzbovRC8.js → Recommend-DlzpC_JE.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoBeV3TD.js → Reputation-BiWFwPMh.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DthhwX_B.js → Row-dmfZBK-k.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dsf5U0lP.js → RssFeed-Cf6tlRKq.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CJPB8kKl.js → Search-DhzXDRAJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DI9MfpdD.js → Security-C-bTfgeJ.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BdG0TFGf.js → Services-D3KNwBqu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DSYazY_B.js → Skeleton-CHH8i1xY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BD309VTT.js → Skills-DHiONX0z.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CaUlw4_6.js → Sla-DwWMb-Ui.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZAPkR7H.js → SpeechSettings-DAq64iWB.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFRSowN3.js → SyncSettings-DJU-kHko.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Di8EOluc.js → Tasks-RAIzA3u-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B5FTziwi.js → Templates-By4cbadT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Dj3XYfBg.js → Tenant-kCAXsxWu.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CznH_rJT.js → Terminal-D8pup_CO.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DTHzaXPt.js → TimelineRenderer-Bk2c9TUI.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bo56nuML.js → Tokens-B0wTiZRa.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-pyVUVBwb.js → Trigger-Ca_9t0gk.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cp7jMkKN.js → Trust-BnfNDDDm.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aPcZCdpz.js → UkeySign-fnlF2-Yr.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DAv5j--w.js → VideoEditing-Cfshhzyw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DRhK7IDR.js → Wallet-BWMpDOLG.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2yQJuq8.js → WebAuthn-B6xeXZvw.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-D98DhR54.js → WorkflowEditor-Cb3-FPCE.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BXrRNi8C.js → chat-CP_opuMK.js} +1 -1
- package/src/assets/web-panel/assets/{colors-jQ94T_qn.js → colors-RLzfjxRQ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-KqUJLlF2.js → compact-item-BTRloNKV.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DxM3zuQW.js → createContext-gXgXTLlU.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CYlmApo_.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BGm946KV.js → hasIn-CrIpxkRC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQvgx2e.js → index-AsI1SqnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5NQai7O.js → index-B3L5vTSs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crw76vIF.js → index-B5sN_pwd.js} +1 -1
- package/src/assets/web-panel/assets/{index-1P0sXNhG.js → index-BEcRPNHv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce-YoL4x.js → index-BVrcoJe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaU_fSK7.js → index-BewxG36A.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGjCjsf1.js → index-Bi87H9yk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpVYD4j.js → index-Bl4OQ--4.js} +1 -1
- package/src/assets/web-panel/assets/{index-yavsyHif.js → index-BlE2lCS-.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4b7KjRq.js → index-BlS91Ac0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbS0cBU9.js → index-Bm0cNeeQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-LWTZAud0.js → index-Bo2Fl2j7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZMzj4ay.js → index-Bsz3X-aU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOYcemym.js → index-BurvDWMS.js} +1 -1
- package/src/assets/web-panel/assets/{index-T4UJiTi3.js → index-BzBLtthb.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Z2m28C.js → index-C0wmEN2j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSzb9VOG.js → index-C4bBR-Vd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfE3jxrT.js → index-COhACL0I.js} +1 -1
- package/src/assets/web-panel/assets/{index-FSYz5aww.js → index-CQ-cnhZM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXMZLfd8.js → index-CRr99L7q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWcd5zDG.js → index-CaTkUkJp.js} +3 -3
- package/src/assets/web-panel/assets/{index-D1VWL6Lc.js → index-CfOjPxso.js} +1 -1
- package/src/assets/web-panel/assets/index-Cf_SLgtj.js +1 -0
- package/src/assets/web-panel/assets/{index-mZNLT2SP.js → index-Ch_TYQNW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-2HpfWo.js → index-CisGGaF3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CghRL1dh.js → index-DA-_2qTs.js} +1 -1
- package/src/assets/web-panel/assets/index-DYBIDVGn.js +1 -0
- package/src/assets/web-panel/assets/{index-Dgzw6eKr.js → index-DZxK7MW9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CreXAr5Y.js → index-DiXnGU0A.js} +1 -1
- package/src/assets/web-panel/assets/{index-aOLXuHpG.js → index-DsiyfyBE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboUBf6R.js → index-G_OgtHiC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEZfi5D.js → index-IjV-dnE8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAPk6ntc.js → index-Mltfj22U.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTUs9t5F.js → index-U-kmEJdu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMp41Q1Y.js → index-cgf-OVry.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_SFSix.js → index-ch8zcf-h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzsaWQy.js → index-cqOT5RiJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sBLech.js → index-cywkZJ-I.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dib0FsTT.js → index-sE_I7gVL.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDoZJI7g.js → initDefaultProps-DMwnVZzH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-sceLjgp-.js → motion-Dnpjpvo7.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTWQpFa7.js → move-iuRh5U9Y.js} +1 -1
- package/src/assets/web-panel/assets/{omit-dr-NCrHZ.js → omit-Bl4WRV59.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DK-GX_Z2.js → pickAttrs-B9KSXaep.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIPf--18.js → placementArrow-TEGIow4r.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BzrWT5tV.js → responsiveObserve--BECM92v.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D5XhxlET.js → slide-Ca8G7XbL.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-6DI-k-9E.js → statusUtils-8QPG_T3D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DMw-eyn9.js → styleChecker-p3FsA6mg.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D4oVdliM.js → useFlexGapSupport-Ct0yIzrp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3PPZnvy.js → useFs-3nlByl_3.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Gh5ba4KP.js → usePersonalDataHub-N3OsoSRM.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEM1u4x.js → vnode-Cfe3ywq3.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClT_-9RZ.js → zoom-DVBuDS_3.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/activitypub.js +4 -3
- package/src/commands/agent.js +25 -0
- package/src/commands/agents.js +18 -4
- package/src/commands/ask.js +13 -0
- package/src/commands/command.js +12 -2
- package/src/commands/config.js +17 -1
- package/src/commands/cost.js +6 -1
- package/src/commands/dao.js +4 -3
- package/src/commands/dlp.js +2 -1
- package/src/commands/hub.js +138 -0
- package/src/commands/mtc.js +37 -1
- package/src/gateways/discord/discord-formatter.js +18 -1
- package/src/gateways/telegram/telegram-formatter.js +6 -3
- package/src/gateways/ws/ws-server.js +19 -1
- package/src/harness/plugin-manager.js +20 -0
- package/src/harness/worktree-isolator.js +17 -1
- package/src/index.js +2 -0
- package/src/lib/__tests__/model-deprecation.test.js +194 -0
- package/src/lib/a2a-protocol.js +24 -9
- package/src/lib/agent-core.js +4 -5
- package/src/lib/audit-mtc.js +40 -2
- package/src/lib/chat-core.js +146 -18
- package/src/lib/cli-anything-bridge.js +5 -0
- package/src/lib/cli-numeric.js +38 -0
- package/src/lib/config-manager.js +37 -6
- package/src/lib/cowork/ab-comparator-cli.js +6 -2
- package/src/lib/cowork-cron.js +45 -34
- package/src/lib/cowork-learning.js +31 -1
- package/src/lib/cowork-share.js +39 -3
- package/src/lib/cowork-template-marketplace.js +54 -2
- package/src/lib/cowork-workflow.js +33 -6
- package/src/lib/cross-chain-mtc.js +41 -12
- package/src/lib/crypto-manager.js +12 -2
- package/src/lib/dbevo.js +19 -8
- package/src/lib/downloader.js +24 -9
- package/src/lib/evomap-governance.js +4 -4
- package/src/lib/evomap-manager.js +54 -7
- package/src/lib/fatal-handler.js +48 -0
- package/src/lib/file-checkpoint.js +26 -0
- package/src/lib/git-integration.js +28 -0
- package/src/lib/goal-store.js +36 -10
- package/src/lib/hierarchical-memory.js +6 -0
- package/src/lib/llm-pricing.js +52 -2
- package/src/lib/mcp-oauth.js +37 -7
- package/src/lib/memory-manager.js +23 -2
- package/src/lib/model-deprecation.js +249 -0
- package/src/lib/pdh-feedback-ledger.js +131 -0
- package/src/lib/permanent-memory.js +30 -2
- package/src/lib/permission-rules.cjs +11 -3
- package/src/lib/personal-data-hub-aichat-wizard.js +33 -3
- package/src/lib/personal-data-hub-wiring.js +39 -16
- package/src/lib/project-instructions.js +10 -4
- package/src/lib/runnable-provider.js +62 -31
- package/src/lib/session-usage.js +34 -1
- package/src/lib/skill-loader.js +4 -1
- package/src/lib/skill-packs/generator.js +25 -2
- package/src/lib/stream-retry.js +56 -0
- package/src/lib/sync-credentials.js +53 -17
- package/src/lib/token-tracker.js +12 -2
- package/src/lib/with-file-lock.js +87 -0
- package/src/repl/agent-repl.js +47 -7
- package/src/repl/chat-repl.js +19 -0
- package/src/repl/session-cost.js +31 -3
- package/src/runtime/agent-core.js +478 -53
- package/src/runtime/coding-agent-contract-shared.cjs +58 -1
- package/src/runtime/coding-agent-policy.cjs +10 -0
- package/src/runtime/file-ref-expander.js +29 -1
- package/src/runtime/headless-runner.js +12 -1
- package/src/runtime/headless-stream.js +200 -9
- package/src/skills/video-editing/tools/commit.js +15 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C8f5NbbI.js +0 -7
- package/src/assets/web-panel/assets/devWarning-Bo1r5_zX.js +0 -1
- package/src/assets/web-panel/assets/index-BCU6dVQ-.js +0 -1
- package/src/assets/web-panel/assets/index-N_oOFphx.js +0 -1
package/src/commands/hub.js
CHANGED
|
@@ -356,6 +356,15 @@ async function cmdSyncAdapter(name, options) {
|
|
|
356
356
|
if (options.passphrase) opts.passphrase = String(options.passphrase);
|
|
357
357
|
// Cookie-mode sources (weread): pass the login cookie through to the adapter.
|
|
358
358
|
if (options.cookie) opts.cookie = String(options.cookie);
|
|
359
|
+
// L1 local files (module 101): the local-files adapter walks directories
|
|
360
|
+
// given via opts.roots (comma-separated dirs → array). Without this the
|
|
361
|
+
// adapter falls back to its default user-data dirs.
|
|
362
|
+
if (options.roots) {
|
|
363
|
+
opts.roots = String(options.roots)
|
|
364
|
+
.split(",")
|
|
365
|
+
.map((s) => s.trim())
|
|
366
|
+
.filter(Boolean);
|
|
367
|
+
}
|
|
359
368
|
const report = await hub.registry.syncAdapter(name, opts);
|
|
360
369
|
if (spinner) spinner.succeed(`synced ${name}`);
|
|
361
370
|
// 2026-05-24 in-APK Android exit + flush-race fix. Real-device repro on
|
|
@@ -843,6 +852,116 @@ async function cmdSalvage(dumpfile, options) {
|
|
|
843
852
|
}
|
|
844
853
|
}
|
|
845
854
|
|
|
855
|
+
// ─── collect-qq (QQNT frida-free decrypt → vault) ────────────────────────
|
|
856
|
+
//
|
|
857
|
+
// module 101 QQNT 采集方案 — on-device path. Decrypts a (root-staged) encrypted
|
|
858
|
+
// nt_msg.db via the DERIVED key (MD5(MD5(uid)+rand), no frida), protobuf-parses
|
|
859
|
+
// the messages, and ingests them. The Android CollectQqNativeTool su-reads the
|
|
860
|
+
// DB + uid candidates, then invokes this. Same logic also runs on PC (USB).
|
|
861
|
+
// Plaintext is written to a temp file only for parsing, then deleted.
|
|
862
|
+
async function cmdCollectQq(options) {
|
|
863
|
+
const spinner = options.json
|
|
864
|
+
? null
|
|
865
|
+
: ora("collect-qq (decrypt + ingest)...").start();
|
|
866
|
+
try {
|
|
867
|
+
const { createRequire } = await import("module");
|
|
868
|
+
const require = createRequire(import.meta.url);
|
|
869
|
+
const fs = require("fs");
|
|
870
|
+
const os = require("os");
|
|
871
|
+
const path = require("path");
|
|
872
|
+
const qq = options._qqCore
|
|
873
|
+
? options._qqCore
|
|
874
|
+
: await import("@chainlesschain/personal-data-hub/forensics/qq-nt-collect");
|
|
875
|
+
|
|
876
|
+
if (!options.db) throw new Error("need --db <encrypted nt_msg.db>");
|
|
877
|
+
if (!options.uids) throw new Error("need --uids <file with u_ candidates>");
|
|
878
|
+
const raw = fs.readFileSync(options.db);
|
|
879
|
+
const rand = options.rand || qq.extractRand(raw);
|
|
880
|
+
if (!rand)
|
|
881
|
+
throw new Error(
|
|
882
|
+
"could not read `rand` from nt_msg.db header (pass --rand)",
|
|
883
|
+
);
|
|
884
|
+
const uids = fs
|
|
885
|
+
.readFileSync(options.uids, "utf8")
|
|
886
|
+
.split(/\s+/)
|
|
887
|
+
.filter((u) => /^u_[A-Za-z0-9_-]{10,}$/.test(u));
|
|
888
|
+
if (!uids.length) throw new Error("no u_ uid candidates in --uids file");
|
|
889
|
+
|
|
890
|
+
const result = qq.deriveAndDecrypt(raw, uids, rand);
|
|
891
|
+
if (!result) {
|
|
892
|
+
const report = {
|
|
893
|
+
ok: false,
|
|
894
|
+
reason: "no-uid-match",
|
|
895
|
+
uidCandidates: uids.length,
|
|
896
|
+
};
|
|
897
|
+
if (options.json) {
|
|
898
|
+
jsonAndExit(report);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (spinner)
|
|
902
|
+
spinner.fail(
|
|
903
|
+
"no uid matched (account uid not in candidates, or formula changed)",
|
|
904
|
+
);
|
|
905
|
+
process.exit(2);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// better-sqlite3's native binding isn't at the standard `bindings` path on
|
|
909
|
+
// Android (the bundle loads it specially) — reuse the vault's already-loaded
|
|
910
|
+
// Database constructor instead of require("better-sqlite3").
|
|
911
|
+
const hub = await (options._getHub || getHub)();
|
|
912
|
+
const vault = hub.vault;
|
|
913
|
+
const Database = (vault.db || vault._db || vault).constructor;
|
|
914
|
+
|
|
915
|
+
// Write the plaintext next to the (app-writable) input DB — os.tmpdir() is
|
|
916
|
+
// /data/local/tmp on Android, which the app uid can't write (EACCES).
|
|
917
|
+
void os;
|
|
918
|
+
const tmp = path.join(
|
|
919
|
+
path.dirname(path.resolve(options.db)),
|
|
920
|
+
`qqnt-dec-${process.pid}.db`,
|
|
921
|
+
);
|
|
922
|
+
fs.writeFileSync(tmp, result.decrypted);
|
|
923
|
+
let events;
|
|
924
|
+
try {
|
|
925
|
+
events = qq.parseEvents(Database, tmp, options.self || "self");
|
|
926
|
+
} finally {
|
|
927
|
+
try {
|
|
928
|
+
fs.unlinkSync(tmp);
|
|
929
|
+
} catch {
|
|
930
|
+
/* best-effort wipe */
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
let ingested = 0;
|
|
935
|
+
for (const ev of events) {
|
|
936
|
+
try {
|
|
937
|
+
vault.putEvent(ev);
|
|
938
|
+
ingested++;
|
|
939
|
+
} catch {
|
|
940
|
+
/* skip dup/invalid */
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const report = {
|
|
944
|
+
ok: true,
|
|
945
|
+
matchedUid: result.uid,
|
|
946
|
+
kdf: result.kdf,
|
|
947
|
+
messages: events.length,
|
|
948
|
+
ingested,
|
|
949
|
+
};
|
|
950
|
+
if (spinner) spinner.succeed(`collect-qq: ${ingested} QQ messages → vault`);
|
|
951
|
+
if (options.json) {
|
|
952
|
+
jsonAndExit(report);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
logger.log(chalk.green("✓ QQNT collect succeeded"));
|
|
956
|
+
logger.log(` matched uid: ${result.uid} (kdf ${result.kdf})`);
|
|
957
|
+
logger.log(` messages: ${events.length}`);
|
|
958
|
+
logger.log(` ingested: ${ingested}`);
|
|
959
|
+
process.exit(0);
|
|
960
|
+
} catch (err) {
|
|
961
|
+
fail(spinner, err, options.json);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
846
965
|
// ─── Phase 10.3 — cc hub aichat <verb> wizard subcommand surface ─────
|
|
847
966
|
//
|
|
848
967
|
// Mirrors the WS topics (personal-data-hub.aichat-*) so scripts and the
|
|
@@ -2239,6 +2358,10 @@ export function registerHubCommand(program) {
|
|
|
2239
2358
|
"--input <path>",
|
|
2240
2359
|
"Path to a snapshot file or local DB (system-data-android snapshot, douyin <uid>_im.db, wechat-pc MSG*.db, etc.)",
|
|
2241
2360
|
)
|
|
2361
|
+
.option(
|
|
2362
|
+
"--roots <dirs>",
|
|
2363
|
+
"Comma-separated directories to scan (local-files adapter, e.g. /sdcard/Documents,/sdcard/Download)",
|
|
2364
|
+
)
|
|
2242
2365
|
.option(
|
|
2243
2366
|
"--db-path <path>",
|
|
2244
2367
|
"Explicit local DB path (alias of --input for device-pull adapters)",
|
|
@@ -2548,6 +2671,21 @@ export function registerHubCommand(program) {
|
|
|
2548
2671
|
.option("--json", "Output JSON")
|
|
2549
2672
|
.action(cmdSalvage);
|
|
2550
2673
|
|
|
2674
|
+
hub
|
|
2675
|
+
.command("collect-qq")
|
|
2676
|
+
.description(
|
|
2677
|
+
"QQNT (modern QQ): decrypt a root-staged encrypted nt_msg.db via the DERIVED key (no frida), protobuf-parse messages, and ingest into the vault. Used by the Android CollectQqNativeTool (su-reads DB + uids); also runnable on PC.",
|
|
2678
|
+
)
|
|
2679
|
+
.requiredOption("--db <path>", "Encrypted nt_msg.db (root-staged / pulled)")
|
|
2680
|
+
.requiredOption(
|
|
2681
|
+
"--uids <path>",
|
|
2682
|
+
"File with u_ uid candidates (one per line)",
|
|
2683
|
+
)
|
|
2684
|
+
.option("--rand <str>", "Override the header-derived rand")
|
|
2685
|
+
.option("--self <qq>", "Your own QQ number (attribution fallback)")
|
|
2686
|
+
.option("--json", "Output JSON")
|
|
2687
|
+
.action(cmdCollectQq);
|
|
2688
|
+
|
|
2551
2689
|
hub
|
|
2552
2690
|
.command("event-detail <eventId>")
|
|
2553
2691
|
.description(
|
package/src/commands/mtc.js
CHANGED
|
@@ -1374,6 +1374,14 @@ function registerFederationCommands(mtc) {
|
|
|
1374
1374
|
const issuer =
|
|
1375
1375
|
options.issuer || `mtca:cc:${federationId}:${options.memberId}`;
|
|
1376
1376
|
const registry = loadFederationRegistry();
|
|
1377
|
+
// Founding the federation = first time this node has seen it. The
|
|
1378
|
+
// founder writes a genesis `create` event into governance.log so the
|
|
1379
|
+
// founding member is part of the replayed roster everywhere the log is
|
|
1380
|
+
// synced (a peer that pulls the log must know who the founder is to
|
|
1381
|
+
// recognize their votes — the membership-gated vote replay would
|
|
1382
|
+
// otherwise silently drop the founder's own approve). See the create
|
|
1383
|
+
// handler in core-mtc replayGovernanceLog.
|
|
1384
|
+
const isFounding = !registry.federations[federationId];
|
|
1377
1385
|
const fedEntry = registry.federations[federationId] || {
|
|
1378
1386
|
federation_id: federationId,
|
|
1379
1387
|
members: {},
|
|
@@ -1429,6 +1437,25 @@ function registerFederationCommands(mtc) {
|
|
|
1429
1437
|
registry.federations[federationId] = fedEntry;
|
|
1430
1438
|
saveFederationRegistry(registry);
|
|
1431
1439
|
|
|
1440
|
+
// Genesis event: anchor the founder into the governance.log so the
|
|
1441
|
+
// M-of-N vote replay (which only counts votes from current members)
|
|
1442
|
+
// recognizes the founder — locally and on any peer that syncs the log.
|
|
1443
|
+
if (isFounding) {
|
|
1444
|
+
emitAndPersist(federationId, {
|
|
1445
|
+
eventType: "create",
|
|
1446
|
+
actorMemberId: options.memberId,
|
|
1447
|
+
secretKey: keys.secretKey,
|
|
1448
|
+
publicKey: keys.publicKey,
|
|
1449
|
+
alg: sig.signer.ALG,
|
|
1450
|
+
payload: {
|
|
1451
|
+
bootstrap_member_id: options.memberId,
|
|
1452
|
+
bootstrap_pubkey_id: trustAnchor.pubkey_id,
|
|
1453
|
+
bootstrap_alg: sig.signer.ALG,
|
|
1454
|
+
initial_threshold: 1,
|
|
1455
|
+
},
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1432
1459
|
if (options.json) {
|
|
1433
1460
|
console.log(
|
|
1434
1461
|
JSON.stringify(
|
|
@@ -2770,7 +2797,16 @@ function registerFederationGovernanceCommands(fed) {
|
|
|
2770
2797
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2771
2798
|
|
|
2772
2799
|
function getDiscoverAnnouncesDir(dropZone, federationId) {
|
|
2773
|
-
|
|
2800
|
+
// Sanitize federation_id (same charset as getDiscoverFilename's pubkey_id):
|
|
2801
|
+
// a value from a (remote) announce must not escape the drop zone via "../",
|
|
2802
|
+
// and ":" in a did-style id is an invalid path char on Windows. Mirrors
|
|
2803
|
+
// core-mtc's _persistAnnounce. Publish + scan both go through here, so the
|
|
2804
|
+
// sanitized dir name stays consistent between write and read.
|
|
2805
|
+
const safe = String(federationId == null ? "" : federationId).replace(
|
|
2806
|
+
/[^a-zA-Z0-9_-]/g,
|
|
2807
|
+
"_",
|
|
2808
|
+
);
|
|
2809
|
+
return path.join(dropZone, "federation-announces", safe);
|
|
2774
2810
|
}
|
|
2775
2811
|
|
|
2776
2812
|
function getDiscoverFilename(announce) {
|
|
@@ -20,7 +20,11 @@ export function formatForDiscord(response, options = {}) {
|
|
|
20
20
|
if (!response) return "";
|
|
21
21
|
const maxLength = options.maxLength || DISCORD_MAX_LENGTH;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Defang mass-mention pings (@everyone / @here) that can appear in agent
|
|
24
|
+
// output — a zero-width space after the `@` renders identically but stops
|
|
25
|
+
// Discord parsing it as a mention. There is no allowed_mentions safety net at
|
|
26
|
+
// the send layer, so the formatter must be safe by default.
|
|
27
|
+
let text = neutralizeMassMentions(response);
|
|
24
28
|
|
|
25
29
|
if (text.length > maxLength) {
|
|
26
30
|
text = text.substring(0, maxLength - 3) + "...";
|
|
@@ -29,6 +33,19 @@ export function formatForDiscord(response, options = {}) {
|
|
|
29
33
|
return text;
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Insert a zero-width space into @everyone / @here so they don't ping. Exported
|
|
38
|
+
* so callers composing messages by other means can reuse it.
|
|
39
|
+
* @param {string} text
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function neutralizeMassMentions(text) {
|
|
43
|
+
return String(text == null ? "" : text).replace(
|
|
44
|
+
/@(everyone|here)/g,
|
|
45
|
+
"@$1",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
/**
|
|
33
50
|
* Split a response into Discord-sized chunks.
|
|
34
51
|
* Tries to split at code block or newline boundaries.
|
|
@@ -47,17 +47,20 @@ export function toTelegramMarkdown(markdown) {
|
|
|
47
47
|
// Escape special characters in text
|
|
48
48
|
result = escapeMarkdownV2(result);
|
|
49
49
|
|
|
50
|
-
// Restore code blocks and inline code (unescaped)
|
|
50
|
+
// Restore code blocks and inline code (unescaped). Use a replacement FUNCTION
|
|
51
|
+
// so `$`-sequences inside the code (e.g. `$&`, `$1`, `$$` in sed/regex/shell)
|
|
52
|
+
// are inserted literally — a string replacement would interpret them as
|
|
53
|
+
// String.replace patterns and corrupt the code.
|
|
51
54
|
for (let i = codeBlocks.length - 1; i >= 0; i--) {
|
|
52
55
|
result = result.replace(
|
|
53
56
|
`__CODE_BLOCK_${i}__`.replace(ESCAPE_CHARS, "\\$1"),
|
|
54
|
-
codeBlocks[i],
|
|
57
|
+
() => codeBlocks[i],
|
|
55
58
|
);
|
|
56
59
|
}
|
|
57
60
|
for (let i = inlineCode.length - 1; i >= 0; i--) {
|
|
58
61
|
result = result.replace(
|
|
59
62
|
`__INLINE_CODE_${i}__`.replace(ESCAPE_CHARS, "\\$1"),
|
|
60
|
-
inlineCode[i],
|
|
63
|
+
() => inlineCode[i],
|
|
61
64
|
);
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { EventEmitter } from "node:events";
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { dirname, join } from "node:path";
|
|
17
18
|
import { WebSocketServer } from "ws";
|
|
@@ -525,10 +526,27 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
525
526
|
return handleLlmChat(this, id, ws, message);
|
|
526
527
|
}
|
|
527
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Constant-time check of a client-supplied token against the configured one.
|
|
531
|
+
* A plain `===` leaks timing that a network client could use to recover the
|
|
532
|
+
* token byte-by-byte; hash both to a fixed length and compare in constant
|
|
533
|
+
* time. Preserves the original semantics: no configured token matches only an
|
|
534
|
+
* absent token (the auto-authenticated path).
|
|
535
|
+
* @private
|
|
536
|
+
*/
|
|
537
|
+
_tokenMatches(provided) {
|
|
538
|
+
const expected = this.token;
|
|
539
|
+
if (expected == null) return provided == null;
|
|
540
|
+
if (typeof provided !== "string") return false;
|
|
541
|
+
const a = createHash("sha256").update(provided).digest();
|
|
542
|
+
const b = createHash("sha256").update(String(expected)).digest();
|
|
543
|
+
return timingSafeEqual(a, b);
|
|
544
|
+
}
|
|
545
|
+
|
|
528
546
|
/** @private */
|
|
529
547
|
_handleAuth(clientId, ws, message) {
|
|
530
548
|
const { id, token } = message;
|
|
531
|
-
const success =
|
|
549
|
+
const success = this._tokenMatches(token);
|
|
532
550
|
const client = this.clients.get(clientId);
|
|
533
551
|
|
|
534
552
|
if (success && client) {
|
|
@@ -352,10 +352,20 @@ export function installPluginSkills(db, pluginName, pluginPath, skills) {
|
|
|
352
352
|
const installed = [];
|
|
353
353
|
|
|
354
354
|
for (const skill of skills) {
|
|
355
|
+
if (!skill || typeof skill.name !== "string" || typeof skill.path !== "string") {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
355
358
|
const srcDir = path.resolve(pluginPath, skill.path);
|
|
359
|
+
// Reject manifest entries that escape the plugin (read) or the marketplace
|
|
360
|
+
// (arbitrary file WRITE) via `../` — a malicious plugin could otherwise drop
|
|
361
|
+
// files anywhere (e.g. overwrite ~/.bashrc) during install.
|
|
362
|
+
if (!_isWithin(pluginPath, srcDir)) continue;
|
|
356
363
|
if (!fs.existsSync(srcDir)) continue;
|
|
357
364
|
|
|
358
365
|
const destDir = path.join(marketplaceDir, skill.name);
|
|
366
|
+
if (!_isWithin(marketplaceDir, destDir) || path.resolve(destDir) === path.resolve(marketplaceDir)) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
359
369
|
fs.mkdirSync(destDir, { recursive: true });
|
|
360
370
|
|
|
361
371
|
// Copy skill files
|
|
@@ -419,6 +429,16 @@ export function getPluginSkills(db, pluginName) {
|
|
|
419
429
|
/**
|
|
420
430
|
* Recursively copy a directory
|
|
421
431
|
*/
|
|
432
|
+
/**
|
|
433
|
+
* True when `child` resolves to `parent` or a path inside it. Used to keep a
|
|
434
|
+
* (semi-trusted) plugin manifest from escaping its sandbox via `../` in
|
|
435
|
+
* skill.path / skill.name.
|
|
436
|
+
*/
|
|
437
|
+
function _isWithin(parent, child) {
|
|
438
|
+
const rel = path.relative(path.resolve(parent), path.resolve(child));
|
|
439
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
440
|
+
}
|
|
441
|
+
|
|
422
442
|
function _copyDirSync(src, dest) {
|
|
423
443
|
fs.mkdirSync(dest, { recursive: true });
|
|
424
444
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
import { execSync } from "node:child_process";
|
|
11
11
|
import { existsSync, rmSync } from "node:fs";
|
|
12
12
|
import { resolve } from "node:path";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
isGitRepo,
|
|
15
|
+
gitExec,
|
|
16
|
+
assertSafeGitRef,
|
|
17
|
+
} from "../lib/git-integration.js";
|
|
14
18
|
|
|
15
19
|
const WORKTREE_DIR = ".worktrees";
|
|
16
20
|
|
|
17
21
|
export function createWorktree(repoDir, branchName, baseBranch) {
|
|
22
|
+
assertSafeGitRef(branchName, "branch name");
|
|
23
|
+
if (baseBranch) assertSafeGitRef(baseBranch, "base branch");
|
|
18
24
|
if (!isGitRepo(repoDir)) {
|
|
19
25
|
throw new Error("Not a git repository");
|
|
20
26
|
}
|
|
@@ -157,6 +163,8 @@ export function cleanupAgentWorktrees(repoDir) {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
export function diffWorktree(repoDir, branchName, options = {}) {
|
|
166
|
+
assertSafeGitRef(branchName, "branch name");
|
|
167
|
+
if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
|
|
160
168
|
const base = options.baseBranch || "HEAD";
|
|
161
169
|
const filePath = options.filePath || null;
|
|
162
170
|
const fileArg = filePath ? ` -- "${filePath}"` : "";
|
|
@@ -204,6 +212,8 @@ export function diffWorktree(repoDir, branchName, options = {}) {
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
export function mergeWorktree(repoDir, branchName, options = {}) {
|
|
215
|
+
assertSafeGitRef(branchName, "branch name");
|
|
216
|
+
if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
|
|
207
217
|
const strategy = options.strategy || "merge";
|
|
208
218
|
const deleteBranch = options.deleteBranch !== false;
|
|
209
219
|
|
|
@@ -285,6 +295,8 @@ export function mergeWorktree(repoDir, branchName, options = {}) {
|
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
export function previewWorktreeMerge(repoDir, branchName, options = {}) {
|
|
298
|
+
assertSafeGitRef(branchName, "branch name");
|
|
299
|
+
if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
|
|
288
300
|
const strategy = options.strategy || "merge";
|
|
289
301
|
const baseBranch = _resolveBaseBranchForMerge(repoDir, options.baseBranch);
|
|
290
302
|
const worktree = _findWorktreeByBranch(repoDir, branchName);
|
|
@@ -374,6 +386,8 @@ export function applyWorktreeAutomationCandidate(
|
|
|
374
386
|
branchName,
|
|
375
387
|
options = {},
|
|
376
388
|
) {
|
|
389
|
+
assertSafeGitRef(branchName, "branch name");
|
|
390
|
+
if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
|
|
377
391
|
const filePath = options.filePath || null;
|
|
378
392
|
const candidateId = options.candidateId || null;
|
|
379
393
|
const conflictType = options.conflictType || null;
|
|
@@ -473,6 +487,8 @@ export function applyWorktreeAutomationCandidate(
|
|
|
473
487
|
}
|
|
474
488
|
|
|
475
489
|
export function worktreeLog(repoDir, branchName, baseBranch = "HEAD") {
|
|
490
|
+
assertSafeGitRef(branchName, "branch name");
|
|
491
|
+
assertSafeGitRef(baseBranch, "base branch");
|
|
476
492
|
try {
|
|
477
493
|
const output = gitExec(
|
|
478
494
|
`log ${baseBranch}..${branchName} --pretty=format:"%h|%s|%ci"`,
|
package/src/index.js
CHANGED
|
@@ -62,6 +62,7 @@ import { registerContextCommand } from "./commands/context.js";
|
|
|
62
62
|
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
63
63
|
import { registerGoalCommand } from "./commands/goal.js";
|
|
64
64
|
import { registerCommandCommand } from "./commands/command.js";
|
|
65
|
+
import { registerAgentsCommand } from "./commands/agents.js";
|
|
65
66
|
import { registerCompactCommand } from "./commands/compact.js";
|
|
66
67
|
import { registerLoopCommand } from "./commands/loop.js";
|
|
67
68
|
import { registerReviewCommand } from "./commands/review.js";
|
|
@@ -478,6 +479,7 @@ export function createProgram(opts = {}) {
|
|
|
478
479
|
registerCheckpointCommand(program);
|
|
479
480
|
registerGoalCommand(program);
|
|
480
481
|
registerCommandCommand(program);
|
|
482
|
+
registerAgentsCommand(program);
|
|
481
483
|
registerCompactCommand(program);
|
|
482
484
|
registerLoopCommand(program);
|
|
483
485
|
registerReviewCommand(program);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DEPRECATED_MODELS,
|
|
4
|
+
checkModelDeprecation,
|
|
5
|
+
formatModelDeprecationWarning,
|
|
6
|
+
maybeWarnDeprecatedModel,
|
|
7
|
+
_warned,
|
|
8
|
+
} from "../model-deprecation.js";
|
|
9
|
+
|
|
10
|
+
describe("checkModelDeprecation", () => {
|
|
11
|
+
it("flags an exact retired snapshot", () => {
|
|
12
|
+
const info = checkModelDeprecation("claude-3-opus-20240229");
|
|
13
|
+
expect(info).toBeTruthy();
|
|
14
|
+
expect(info.id).toBe("claude-3-opus-20240229");
|
|
15
|
+
expect(info.state).toBe("retired");
|
|
16
|
+
expect(info.replacement).toMatch(/claude-opus-4-8/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("flags retired Claude 3.5 / 3.7 families by prefix (the common ones)", () => {
|
|
20
|
+
expect(checkModelDeprecation("claude-3-5-sonnet-20240620")).toBeTruthy();
|
|
21
|
+
expect(checkModelDeprecation("claude-3-5-sonnet-20241022")).toBeTruthy();
|
|
22
|
+
expect(checkModelDeprecation("claude-3-5-haiku-20241022")).toBeTruthy();
|
|
23
|
+
expect(checkModelDeprecation("claude-3-7-sonnet-20250219")).toBeTruthy();
|
|
24
|
+
expect(checkModelDeprecation("claude-3-haiku-20240307")).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("flags the retired ORIGINAL Claude 4 snapshots (exact dated id)", () => {
|
|
28
|
+
expect(checkModelDeprecation("claude-opus-4-20250514").state).toBe(
|
|
29
|
+
"retired",
|
|
30
|
+
);
|
|
31
|
+
expect(checkModelDeprecation("claude-sonnet-4-20250514").state).toBe(
|
|
32
|
+
"retired",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("flags deprecated-but-live Claude Opus 4.1 with state=deprecated", () => {
|
|
37
|
+
const info = checkModelDeprecation("claude-opus-4-1-20250805");
|
|
38
|
+
expect(info).toBeTruthy();
|
|
39
|
+
expect(info.state).toBe("deprecated");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("prefix-matches a retired family (claude-2 → claude-2.1)", () => {
|
|
43
|
+
expect(checkModelDeprecation("claude-2.1")).toBeTruthy();
|
|
44
|
+
expect(checkModelDeprecation("claude-2.0")).toBeTruthy();
|
|
45
|
+
expect(checkModelDeprecation("claude-instant-1.2")).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("flags retired OpenAI snapshots", () => {
|
|
49
|
+
expect(checkModelDeprecation("gpt-4-0314")).toBeTruthy();
|
|
50
|
+
expect(checkModelDeprecation("gpt-4-32k-0613")).toBeTruthy();
|
|
51
|
+
expect(checkModelDeprecation("gpt-3.5-turbo-0301")).toBeTruthy();
|
|
52
|
+
expect(checkModelDeprecation("text-davinci-003")).toBeTruthy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("is case-insensitive and trims", () => {
|
|
56
|
+
expect(checkModelDeprecation(" GPT-4-0314 ")).toBeTruthy();
|
|
57
|
+
expect(checkModelDeprecation("Claude-2.1")).toBeTruthy();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("CRITICAL: does NOT flag ACTIVE Claude 4 models (prefix-collision guard)", () => {
|
|
61
|
+
// These share a prefix with the retired 4.x snapshots — the entries use
|
|
62
|
+
// exact dated ids precisely so these stay silent.
|
|
63
|
+
expect(checkModelDeprecation("claude-opus-4-8")).toBeNull();
|
|
64
|
+
expect(checkModelDeprecation("claude-opus-4-7")).toBeNull();
|
|
65
|
+
expect(checkModelDeprecation("claude-opus-4-6")).toBeNull();
|
|
66
|
+
expect(checkModelDeprecation("claude-opus-4-5-20251101")).toBeNull();
|
|
67
|
+
expect(checkModelDeprecation("claude-sonnet-4-6")).toBeNull();
|
|
68
|
+
expect(checkModelDeprecation("claude-sonnet-4-5-20250929")).toBeNull();
|
|
69
|
+
expect(checkModelDeprecation("claude-haiku-4-5-20251001")).toBeNull();
|
|
70
|
+
expect(checkModelDeprecation("claude-haiku-4-5")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("does NOT flag other current GA / provider models", () => {
|
|
74
|
+
expect(checkModelDeprecation("gpt-4o")).toBeNull();
|
|
75
|
+
expect(checkModelDeprecation("gpt-4o-mini")).toBeNull();
|
|
76
|
+
expect(checkModelDeprecation("gpt-5")).toBeNull();
|
|
77
|
+
expect(checkModelDeprecation("gpt-4-turbo")).toBeNull();
|
|
78
|
+
// provider-specific ids (volcengine/doubao/ollama) never match
|
|
79
|
+
expect(checkModelDeprecation("doubao-seed-2-0-lite-260215")).toBeNull();
|
|
80
|
+
expect(checkModelDeprecation("qwen2.5-coder:7b")).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("handles empty / non-string input", () => {
|
|
84
|
+
expect(checkModelDeprecation("")).toBeNull();
|
|
85
|
+
expect(checkModelDeprecation(null)).toBeNull();
|
|
86
|
+
expect(checkModelDeprecation(undefined)).toBeNull();
|
|
87
|
+
expect(checkModelDeprecation(42)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("every list entry is internally consistent and self-matches", () => {
|
|
91
|
+
for (const e of DEPRECATED_MODELS) {
|
|
92
|
+
expect(typeof e.id).toBe("string");
|
|
93
|
+
expect(e.id.length).toBeGreaterThan(0);
|
|
94
|
+
expect(["retired", "deprecated"]).toContain(e.state);
|
|
95
|
+
expect(typeof e.reason).toBe("string");
|
|
96
|
+
expect(typeof e.replacement).toBe("string");
|
|
97
|
+
expect(checkModelDeprecation(e.id)).toBeTruthy();
|
|
98
|
+
// the recommended replacement must itself never be flagged as retired
|
|
99
|
+
const firstReplacement = e.replacement.split(/\s+or\s+|\s+/)[0];
|
|
100
|
+
expect(checkModelDeprecation(firstReplacement)).toBeNull();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("formatModelDeprecationWarning", () => {
|
|
106
|
+
it("uses 'has been retired' for retired models", () => {
|
|
107
|
+
const info = checkModelDeprecation("claude-3-5-sonnet-20241022");
|
|
108
|
+
const line = formatModelDeprecationWarning(info);
|
|
109
|
+
expect(line).toMatch(/claude-3-5-sonnet-20241022/);
|
|
110
|
+
expect(line).toMatch(/has been retired/);
|
|
111
|
+
expect(line).toMatch(/claude-sonnet-4-6/);
|
|
112
|
+
expect(line).toMatch(/CC_MODEL_NOTICE=0/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("uses 'is deprecated' for deprecated-but-live models", () => {
|
|
116
|
+
const info = checkModelDeprecation("claude-opus-4-1-20250805");
|
|
117
|
+
const line = formatModelDeprecationWarning(info);
|
|
118
|
+
expect(line).toMatch(/is deprecated/);
|
|
119
|
+
expect(line).not.toMatch(/has been retired/);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("maybeWarnDeprecatedModel", () => {
|
|
124
|
+
let lines;
|
|
125
|
+
let print;
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
_warned.clear();
|
|
128
|
+
lines = [];
|
|
129
|
+
print = (l) => lines.push(l);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("warns once for a deprecated model", () => {
|
|
133
|
+
const r = maybeWarnDeprecatedModel({ model: "claude-2.1", env: {}, print });
|
|
134
|
+
expect(r.warned).toBe(true);
|
|
135
|
+
expect(r.info.id).toBe("claude-2.1");
|
|
136
|
+
expect(lines).toHaveLength(1);
|
|
137
|
+
expect(lines[0]).toMatch(/claude-2\.1/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("warns for a retired Claude 3.5 Sonnet (the common stale id)", () => {
|
|
141
|
+
const r = maybeWarnDeprecatedModel({
|
|
142
|
+
model: "claude-3-5-sonnet-20241022",
|
|
143
|
+
env: {},
|
|
144
|
+
print,
|
|
145
|
+
});
|
|
146
|
+
expect(r.warned).toBe(true);
|
|
147
|
+
expect(lines[0]).toMatch(/has been retired/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("does not repeat the same model within a process", () => {
|
|
151
|
+
maybeWarnDeprecatedModel({ model: "gpt-4-0314", env: {}, print });
|
|
152
|
+
const r = maybeWarnDeprecatedModel({ model: "gpt-4-0314", env: {}, print });
|
|
153
|
+
expect(r.warned).toBe(false);
|
|
154
|
+
expect(r.info).toBeTruthy(); // still reports the match, just doesn't re-print
|
|
155
|
+
expect(lines).toHaveLength(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("stays silent for a current model", () => {
|
|
159
|
+
const r = maybeWarnDeprecatedModel({
|
|
160
|
+
model: "claude-opus-4-8",
|
|
161
|
+
env: {},
|
|
162
|
+
print,
|
|
163
|
+
});
|
|
164
|
+
expect(r.warned).toBe(false);
|
|
165
|
+
expect(r.info).toBeNull();
|
|
166
|
+
expect(lines).toHaveLength(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("honors CC_MODEL_NOTICE=0", () => {
|
|
170
|
+
const r = maybeWarnDeprecatedModel({
|
|
171
|
+
model: "claude-2.1",
|
|
172
|
+
env: { CC_MODEL_NOTICE: "0" },
|
|
173
|
+
print,
|
|
174
|
+
});
|
|
175
|
+
expect(r.warned).toBe(false);
|
|
176
|
+
expect(lines).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("fail-open on a throwing print", () => {
|
|
180
|
+
const r = maybeWarnDeprecatedModel({
|
|
181
|
+
model: "claude-2.1",
|
|
182
|
+
env: {},
|
|
183
|
+
print: () => {
|
|
184
|
+
throw new Error("boom");
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
expect(r.warned).toBe(false); // swallowed, no throw
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("no-ops on missing model", () => {
|
|
191
|
+
expect(maybeWarnDeprecatedModel({ env: {}, print }).warned).toBe(false);
|
|
192
|
+
expect(lines).toHaveLength(0);
|
|
193
|
+
});
|
|
194
|
+
});
|