chainlesschain 0.162.94 → 0.162.96
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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CiEoV_Hg.js → AIOps-Bo9Ux24z.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DrrjQMuK.js → ActionButton-CExgXSQT.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-ChlhPe9a.js → Analytics-BNnZhRTZ.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-CIkI7d5o.js → AppLayout-D9xfqFCT.js} +4 -4
- package/src/assets/web-panel/assets/{Audit-_sY9bNSN.js → Audit-Cpgu8uCw.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DvzaThN8.js → Backup-CHdEJnNE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DDZW_DU8.js → BaseInput-Cbw-PxBq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DqErkog2.js → Chat-ChHTZK_6.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BcnNHS-r.js → ChatBubbleRenderer-Dl6z6ZH7.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-NWDuyRK6.js → Checkbox-Cj8d9bru.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B_jnSC8a.js → Codegen-C2YobW4j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-uyUy0z.js → Col-CxfV5FGT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Dmf1MpG7.js → Community-CiwOASMj.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-fGI68M5y.js → Compact-CTg_2iKW.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-PV9qT-iV.js → Compliance-HMbsTmtw.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BM6BvCgv.js → Cowork-DyNDqGR2.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BsSWwFFo.js → Cron-9-x5M7qy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DUdPVYvS.js → Crosschain-4msgDaYJ.js} +1 -1
- package/src/assets/web-panel/assets/{DID-PPLLhrJs.js → DID--2Beb5Po.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cn_Xjj80.js → Dashboard-T6nq0Lzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DkTXjy5D.js → Dropdown-D_Q6T10v.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BnA27cjB.js → EmailListRenderer-TnN21bJx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-jqrv5VCp.js → FamilyGuardDashboard-kky_uIij.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DESwXwtR.js → Federation-C1GkNY4B.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C9gbyY1r.js → FormItemContext-5-_Fc5YV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CkZB9crH.js → GenericCardRenderer-CmHiB7sr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B6niu0W9.js → Git-Cuci_BPI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BrF28dEi.js → Governance-BU0cO2LD.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CZDnV8SN.js → Inference-BDFNgEcx.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-eqZ0hP0l.js → KnowledgeGraph-DITXUzHz.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BlHS7DoS.js → Logs-Ea-07CRd.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-NOp9G83e.js → Marketplace-D6OBbRVG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B6wMx173.js → McpTools-eiKpIJMi.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BKVeTAeT.js → Memory-M-X-T8UF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Da2y6iYW.js → MobileBridge-xcZb9Ekv.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-DCfoTPRC.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-B7Hy5rAT.js → Mtc-BWfGDzD8.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BgBoX0XI.js → MtcAudit-imBuld-U.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Db7bwi5J.js → Multisig-C2kcsq7I.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-f9ca2vst.js → NLProgramming-DObvXSXP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-meVqoOMz.js → Notes-C-6OGJMe.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BfEhBXNo.js → NotificationSettings-ub7Ozxci.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-e25Q6ALJ.js → OrderTableRenderer-CR2W89Vk.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BpgOG9lj.js → Organization-DNA_Wty7.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-fI3L05Rq.js → Overflow-rT4Wb4rQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-wD0wpJJY.js → P2P-CK-hARKv.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BWV9J4VZ.js → PdhVaultBrowser-C2pbtNbT.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BJH7W2iv.js → Permissions-B8EIEqGJ.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CqQ3XpMz.js → PersonalDataHub-BxNVNU3s.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CHyXCU1i.js → Pipeline-CT9pbkyY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CJtfNlc7.js → Privacy-DhCNd2CS.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CKOaHFGR.js → ProjectInit-Qngnx-vQ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-QD8wp_EM.js → ProjectSettings-Dfn-nQc1.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BKdiSaPR.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BPESqCEg.js → Providers-4o9B_n-X.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C5AzvvRo.js → QuickAsk-bs1qvF30.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-cGpF3tsA.js → Recommend-CuEnKA2K.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-Duaxvbt9.js → Reputation-DbfvmRb2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-xsUwB5Ay.js → Row-DvMJGAI3.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DmUUsqaF.js → RssFeed-oJ0BZvmA.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Do9YuDbX.js → Search-DnIVrY9O.js} +1 -1
- package/src/assets/web-panel/assets/{Security-06BgEn-d.js → Security-CIWhI_Tc.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DyaCes7r.js → Services-DLrnpjwm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cj_VbkFG.js → Skeleton-CYmqgtVb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DCOWMJOR.js → Skills-BUzCkoeu.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C44_86bu.js → Sla-DYRE4bnS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BXhBJSMB.js → SpeechSettings-_6MurdOg.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BpHsaNWg.js → SyncSettings-UIdnF5Li.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-COx2CjT8.js +1 -0
- package/src/assets/web-panel/assets/{Templates-aEWbCOdo.js → Templates-BcHAmkZo.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DkOLw1Yz.js → Tenant-CjYQS8yZ.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-5Z4paIoh.js → Terminal-BmfOPcWt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CYhublOm.js → TimelineRenderer-D5HuarNQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-h57dBT4T.js → Tokens-C_fbProA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-abE21FpD.js → Trigger-JPulpRx8.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-urB3VZip.js → Trust-CnO-062O.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D6uXV9pp.js → UkeySign-HpNovvau.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C9MxXAzw.js → VideoEditing-yMncxH7y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DTn5rW4u.js → Wallet-Cug-t7Np.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-BNYEqeqr.js → WebAuthn--U1-NR-3.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CsRoOJPT.js → WorkflowEditor-R-XvSJ3S.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B5GoUHDS.js → chat-CCI1i6Rp.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BF_Rqaab.js → colors-C-mqoaLt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BNPfqT4P.js → compact-item-De_N2iRR.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C3qLKXX-.js → createContext-DbTM6sjZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-OcLPHSl5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BLpAeFXB.js → hasIn-D0Adylyc.js} +1 -1
- package/src/assets/web-panel/assets/index-7E_mHkDU.js +1 -0
- package/src/assets/web-panel/assets/{index-BOoTjHGU.js → index-AY937BxS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cv1WRH-j.js → index-B6hgy5VC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2wJowV1.js → index-B7XnpLHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dj6rUk3f.js → index-B7gw-tDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dz7o1Zt6.js → index-BBgDgkwW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMRdeB2r.js → index-BMmeiw88.js} +1 -1
- package/src/assets/web-panel/assets/{index-2-7AD4Fq.js → index-BU6XtnSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-7Tt41tHa.js → index-Bi7VUG0R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaROOJ5Z.js → index-BtE3cw9m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmrPbYXh.js → index-C9ej-MF0.js} +3 -3
- package/src/assets/web-panel/assets/{index-CgIrRLBP.js → index-CIEiQl1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DudglWTC.js → index-CKgaW9E5.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8B3rHnr.js → index-CY0KKR5f.js} +1 -1
- package/src/assets/web-panel/assets/index-CbV3-WlB.js +1 -0
- package/src/assets/web-panel/assets/{index-ixN0R0We.js → index-Co1UkcFX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cqb8TXEp.js → index-CzP5d5LW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DH95hKUN.js → index-D0NJ5lRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6yRwT6e.js → index-D0gBsqTX.js} +1 -1
- package/src/assets/web-panel/assets/{index-o1doEeT3.js → index-D5F6shtn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxWEK9FS.js → index-DVtamtlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cxd2RsZR.js → index-DWT18hDG.js} +1 -1
- package/src/assets/web-panel/assets/{index-vXHPZOmO.js → index-DaXb4B9P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJsVUj80.js → index-DjQA0Vez.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIjPwa7h.js → index-Ds8NJVEL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMKiwMJa.js → index-DuP-8HWu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIpGZeh2.js → index-DwLTiJ31.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn95Blhx.js → index-T3VhH8cO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqjU6Jp9.js → index-TDY1o0w3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBFekqcY.js → index-TRy2Gc3V.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-PNuV4r.js → index-W1OMxCmI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlEDjdTc.js → index-ftFRZbuc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMKbLaTg.js → index-iGPGaORA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYWExa2Z.js → index-jhy5KQvg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BryugSCS.js → index-qUSZJcgw.js} +1 -1
- package/src/assets/web-panel/assets/{index-81hgV6U6.js → index-w3f6lqkD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmBMP52w.js → index-xdMc71Kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-KUdyAwqr.js → index-xhX5eg2Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGVf-eOG.js → index-zkTiW6d6.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Dhfx-jOJ.js → initDefaultProps-CIHqouWk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DzZPc4Mq.js → motion-D1iUKftk.js} +1 -1
- package/src/assets/web-panel/assets/{move-D6R8EIjz.js → move-YOpWjmjL.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CPniUUR1.js → omit-C_1357UY.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DbzxHNNa.js → pickAttrs-CDb4dq6o.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B_MxmwXz.js → placementArrow-D-OGYyhd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Em-jKJ7U.js → responsiveObserve-hS-j69A0.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B2RV1mQl.js → slide-tnjuvphU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C9Ps6HQe.js → statusUtils-BERfMMcU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCoehh1S.js → styleChecker-DKpl2K9E.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DZOaLVto.js → useFlexGapSupport-C_sX4D3N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Dk_JA91R.js → useFs-DBcxC1vK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CJR6Dqtz.js → usePersonalDataHub-CeoDf2XI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BaFNaDQL.js → vnode-DC9Pmj_u.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CxA9IYpM.js → zoom-CZA58J7M.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/activitypub.js +19 -3
- package/src/commands/agent.js +25 -0
- package/src/commands/ask.js +13 -0
- package/src/commands/config.js +17 -1
- package/src/commands/cost.js +6 -1
- package/src/commands/dao.js +16 -3
- package/src/commands/dlp.js +7 -1
- package/src/commands/hub.js +13 -0
- package/src/commands/mtc.js +37 -1
- package/src/commands/pdh.js +162 -0
- 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/mcp-client.js +19 -2
- 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 +23 -9
- package/src/lib/agent-core.js +2 -5
- package/src/lib/audit-mtc.js +40 -2
- package/src/lib/chat-core.js +141 -18
- package/src/lib/claude-code-bridge.js +14 -2
- 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 +56 -4
- 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 +36 -13
- package/src/lib/project-instructions.js +10 -4
- package/src/lib/runnable-provider.js +62 -31
- package/src/lib/session-tail.js +6 -1
- 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 +52 -13
- package/src/repl/chat-repl.js +10 -0
- package/src/repl/session-cost.js +31 -3
- package/src/runtime/agent-core.js +474 -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 +51 -6
- package/src/runtime/headless-stream.js +219 -2
- package/src/skills/video-editing/tools/commit.js +15 -1
- package/src/assets/web-panel/assets/MobileProjects-CTFPhn4A.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dj33CO-H.js +0 -1
- package/src/assets/web-panel/assets/Tasks-C83eyBn8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D5Rwbl_Y.js +0 -1
- package/src/assets/web-panel/assets/index-7ls1Z3dH.js +0 -1
- package/src/assets/web-panel/assets/index-DoldiBPq.js +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc pdh` — inspect PDH bridge discovery (Phase 0).
|
|
3
|
+
*
|
|
4
|
+
* The agent auto-connects the Android app's "device capability" MCP server
|
|
5
|
+
* (collect / query / file / task tools) when the app spawns cc with
|
|
6
|
+
* `CHAINLESSCHAIN_PDH_PORT` set, or via a lockfile scan for external agents.
|
|
7
|
+
* These subcommands make that discovery visible and debuggable WITHOUT running
|
|
8
|
+
* an agent:
|
|
9
|
+
*
|
|
10
|
+
* cc pdh list list live PDH lockfiles found
|
|
11
|
+
* cc pdh status which server `cc agent` would connect to (+ its MCP config)
|
|
12
|
+
* cc pdh doctor explain WHY discovery did / didn't pick a server
|
|
13
|
+
*
|
|
14
|
+
* Pure read-only over ~/.chainlesschain/pdh-bridge/*.json — mirrors `cc ide`
|
|
15
|
+
* (module 98). See docs/design/modules/101_个人数据IDE桥接方案.md.
|
|
16
|
+
*/
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import {
|
|
19
|
+
readPdhLocks,
|
|
20
|
+
discoverPdhServer,
|
|
21
|
+
pdhServerToMcpConfig,
|
|
22
|
+
isInPdhTerminal,
|
|
23
|
+
pdhLockDir,
|
|
24
|
+
diagnosePdh,
|
|
25
|
+
} from "../lib/pdh-bridge.js";
|
|
26
|
+
|
|
27
|
+
function emit(obj) {
|
|
28
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Strip the raw bearer token + internal _file path from a lock for output. */
|
|
32
|
+
function redactLock(lock) {
|
|
33
|
+
if (!lock) return null;
|
|
34
|
+
const { token, _file, ...rest } = lock;
|
|
35
|
+
return { ...rest, hasToken: !!token };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function registerPdhCommand(program) {
|
|
39
|
+
const pdh = program
|
|
40
|
+
.command("pdh")
|
|
41
|
+
.description(
|
|
42
|
+
"Inspect PDH bridge discovery (Android personal-data MCP server auto-connect)",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
pdh
|
|
46
|
+
.command("list")
|
|
47
|
+
.description(
|
|
48
|
+
"List live PDH lockfiles (device capability MCP servers advertised)",
|
|
49
|
+
)
|
|
50
|
+
.option("--json", "Machine-readable output")
|
|
51
|
+
.action((options) => {
|
|
52
|
+
const locks = readPdhLocks();
|
|
53
|
+
if (options.json) {
|
|
54
|
+
// Never surface the raw bearer token or internal _file path.
|
|
55
|
+
const safe = locks.map(redactLock);
|
|
56
|
+
emit({ lockDir: pdhLockDir(), count: safe.length, locks: safe });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!locks.length) {
|
|
60
|
+
console.log(chalk.gray(`No live PDH lockfiles in ${pdhLockDir()}`));
|
|
61
|
+
console.log(
|
|
62
|
+
chalk.gray(
|
|
63
|
+
"Start the app's PDH bridge server, or run `cc pdh doctor`.",
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log(
|
|
69
|
+
chalk.bold(`PDH servers (${locks.length}) in ${pdhLockDir()}:`),
|
|
70
|
+
);
|
|
71
|
+
for (const l of locks) {
|
|
72
|
+
console.log(
|
|
73
|
+
` ${chalk.cyan(l.device)} port ${l.port} ${l.transport} ` +
|
|
74
|
+
`${l.token ? chalk.green("token") : chalk.yellow("no-token")}`,
|
|
75
|
+
);
|
|
76
|
+
console.log(chalk.gray(` url ${l.url}`));
|
|
77
|
+
if (l.appUid != null) {
|
|
78
|
+
console.log(chalk.gray(` uid ${l.appUid}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
pdh
|
|
84
|
+
.command("status")
|
|
85
|
+
.description("Show the PDH server `cc agent` would connect to right now")
|
|
86
|
+
.option("--json", "Machine-readable output")
|
|
87
|
+
.action((options) => {
|
|
88
|
+
const env = process.env;
|
|
89
|
+
const inPdh = isInPdhTerminal(env);
|
|
90
|
+
const lock = discoverPdhServer({ env });
|
|
91
|
+
const cfg = lock ? pdhServerToMcpConfig(lock) : null;
|
|
92
|
+
// Redact the bearer token in the previewed config.
|
|
93
|
+
const safeCfg = cfg
|
|
94
|
+
? {
|
|
95
|
+
...cfg,
|
|
96
|
+
headers: cfg.headers?.Authorization
|
|
97
|
+
? { ...cfg.headers, Authorization: "Bearer ***" }
|
|
98
|
+
: cfg.headers,
|
|
99
|
+
}
|
|
100
|
+
: null;
|
|
101
|
+
if (options.json) {
|
|
102
|
+
emit({
|
|
103
|
+
inPdhTerminal: inPdh,
|
|
104
|
+
chosen: redactLock(lock),
|
|
105
|
+
mcpConfig: safeCfg,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
console.log(
|
|
110
|
+
`In PDH terminal: ${inPdh ? chalk.green("yes") : chalk.gray("no")}`,
|
|
111
|
+
);
|
|
112
|
+
if (!lock) {
|
|
113
|
+
console.log(
|
|
114
|
+
chalk.gray(
|
|
115
|
+
"No PDH server would be connected. Try `cc pdh doctor` for why.",
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(
|
|
121
|
+
`Would connect: ${chalk.cyan(lock.device)} on port ${lock.port} ` +
|
|
122
|
+
`(${lock.transport}) as MCP server ${chalk.bold("pdh")}`,
|
|
123
|
+
);
|
|
124
|
+
console.log(chalk.gray("MCP config:"));
|
|
125
|
+
console.log(chalk.gray(" " + JSON.stringify(safeCfg)));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
pdh
|
|
129
|
+
.command("doctor")
|
|
130
|
+
.description("Explain why PDH discovery did / didn't pick a server")
|
|
131
|
+
.option("--json", "Machine-readable output")
|
|
132
|
+
.action((options) => {
|
|
133
|
+
const diag = diagnosePdh({ env: process.env });
|
|
134
|
+
if (options.json) {
|
|
135
|
+
emit(diag);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
console.log(chalk.bold("PDH bridge discovery"));
|
|
139
|
+
console.log(` lock dir : ${diag.lockDir}`);
|
|
140
|
+
console.log(
|
|
141
|
+
` in PDH terminal: ${diag.inPdhTerminal ? chalk.green("yes") : chalk.gray("no")}`,
|
|
142
|
+
);
|
|
143
|
+
console.log(` live locks : ${diag.locks.length}`);
|
|
144
|
+
for (const l of diag.locks) {
|
|
145
|
+
console.log(
|
|
146
|
+
` - ${chalk.cyan(l.device)} port ${l.port} ${l.transport} ` +
|
|
147
|
+
`${l.hasToken ? "token" : chalk.yellow("no-token")}` +
|
|
148
|
+
`${l.appUid != null ? ` uid ${l.appUid}` : ""}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
console.log(
|
|
152
|
+
` result : ${
|
|
153
|
+
diag.chosen
|
|
154
|
+
? chalk.green(`connect ${diag.chosen.device}:${diag.chosen.port}`)
|
|
155
|
+
: chalk.yellow("nothing to connect")
|
|
156
|
+
}`,
|
|
157
|
+
);
|
|
158
|
+
console.log(` reason : ${diag.reason}`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return pdh;
|
|
162
|
+
}
|
|
@@ -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) {
|
|
@@ -188,6 +188,12 @@ export class MCPClient extends EventEmitter {
|
|
|
188
188
|
prompts: [],
|
|
189
189
|
_pending: new Map(),
|
|
190
190
|
_buffer: "",
|
|
191
|
+
// Per-connection streaming decoders so a multi-byte UTF-8 character (e.g.
|
|
192
|
+
// a 3-byte Chinese char) split across two stdout/stderr chunks is
|
|
193
|
+
// reassembled instead of corrupted into U+FFFD. Decoding each chunk
|
|
194
|
+
// independently (data.toString("utf8")) would mangle a split character.
|
|
195
|
+
_decoder: new TextDecoder("utf-8"),
|
|
196
|
+
_stderrDecoder: new TextDecoder("utf-8"),
|
|
191
197
|
};
|
|
192
198
|
|
|
193
199
|
this.servers.set(name, entry);
|
|
@@ -219,11 +225,22 @@ export class MCPClient extends EventEmitter {
|
|
|
219
225
|
entry.process = proc;
|
|
220
226
|
|
|
221
227
|
proc.stdout.on("data", (data) => {
|
|
222
|
-
this._handleData(
|
|
228
|
+
this._handleData(
|
|
229
|
+
name,
|
|
230
|
+
typeof data === "string"
|
|
231
|
+
? data
|
|
232
|
+
: entry._decoder.decode(data, { stream: true }),
|
|
233
|
+
);
|
|
223
234
|
});
|
|
224
235
|
|
|
225
236
|
proc.stderr.on("data", (data) => {
|
|
226
|
-
this.emit("server-error", {
|
|
237
|
+
this.emit("server-error", {
|
|
238
|
+
name,
|
|
239
|
+
error:
|
|
240
|
+
typeof data === "string"
|
|
241
|
+
? data
|
|
242
|
+
: entry._stderrDecoder.decode(data, { stream: true }),
|
|
243
|
+
});
|
|
227
244
|
});
|
|
228
245
|
|
|
229
246
|
// If the server process dies with requests in flight, reject them
|
|
@@ -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
|
@@ -71,6 +71,7 @@ import { registerOutputStyleCommand } from "./commands/output-style.js";
|
|
|
71
71
|
import { registerStatuslineCommand } from "./commands/statusline.js";
|
|
72
72
|
import { registerTerminalSetupCommand } from "./commands/terminal-setup.js";
|
|
73
73
|
import { registerIdeCommand } from "./commands/ide.js";
|
|
74
|
+
import { registerPdhCommand } from "./commands/pdh.js";
|
|
74
75
|
import { registerConsolCommand } from "./commands/consol.js";
|
|
75
76
|
import { registerImportCommand } from "./commands/import.js";
|
|
76
77
|
import { registerExportCommand } from "./commands/export.js";
|
|
@@ -486,6 +487,7 @@ export function createProgram(opts = {}) {
|
|
|
486
487
|
registerStatuslineCommand(program);
|
|
487
488
|
registerTerminalSetupCommand(program);
|
|
488
489
|
registerIdeCommand(program);
|
|
490
|
+
registerPdhCommand(program);
|
|
489
491
|
registerConsolCommand(program);
|
|
490
492
|
|
|
491
493
|
// Phase 2: Knowledge & content management
|
|
@@ -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
|
+
});
|
package/src/lib/a2a-protocol.js
CHANGED
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
* capability negotiation, and peer discovery.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// Safe JSON-array parse for values read back from the DB (capabilities / skills /
|
|
9
|
+
// history / artifacts are stored as JSON strings). Written via JSON.stringify so
|
|
10
|
+
// normally valid, but a corrupted or externally-edited DB must not crash agent
|
|
11
|
+
// discovery / task lifecycle with an uncaught SyntaxError — fall back to [].
|
|
12
|
+
function parseJsonArray(value) {
|
|
13
|
+
if (value == null || value === "") return [];
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(value);
|
|
16
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
// ─── Task statuses ───────────────────────────────────────────────
|
|
9
23
|
export const TASK_STATUS = {
|
|
10
24
|
SUBMITTED: "submitted",
|
|
@@ -165,8 +179,8 @@ export function discoverAgents(db, filter = {}) {
|
|
|
165
179
|
// Parse JSON fields
|
|
166
180
|
rows = rows.map((r) => ({
|
|
167
181
|
...r,
|
|
168
|
-
capabilities:
|
|
169
|
-
skills:
|
|
182
|
+
capabilities: parseJsonArray(r.capabilities),
|
|
183
|
+
skills: parseJsonArray(r.skills),
|
|
170
184
|
}));
|
|
171
185
|
|
|
172
186
|
if (filter.capability) {
|
|
@@ -220,7 +234,7 @@ export function completeTask(db, taskId, output, artifacts = []) {
|
|
|
220
234
|
|
|
221
235
|
const now = nowISO();
|
|
222
236
|
const task = _getTask(db, taskId);
|
|
223
|
-
const history =
|
|
237
|
+
const history = parseJsonArray(task.history);
|
|
224
238
|
history.push({ status: TASK_STATUS.COMPLETED, timestamp: now });
|
|
225
239
|
|
|
226
240
|
db.prepare(
|
|
@@ -249,7 +263,7 @@ export function failTask(db, taskId, error) {
|
|
|
249
263
|
|
|
250
264
|
const now = nowISO();
|
|
251
265
|
const task = _getTask(db, taskId);
|
|
252
|
-
const history =
|
|
266
|
+
const history = parseJsonArray(task.history);
|
|
253
267
|
history.push({ status: TASK_STATUS.FAILED, timestamp: now });
|
|
254
268
|
|
|
255
269
|
db.prepare(
|
|
@@ -276,8 +290,8 @@ export function getTaskStatus(db, taskId) {
|
|
|
276
290
|
const task = _getTask(db, taskId);
|
|
277
291
|
return {
|
|
278
292
|
...task,
|
|
279
|
-
history:
|
|
280
|
-
artifacts:
|
|
293
|
+
history: parseJsonArray(task.history),
|
|
294
|
+
artifacts: parseJsonArray(task.artifacts),
|
|
281
295
|
};
|
|
282
296
|
}
|
|
283
297
|
|
|
@@ -305,7 +319,7 @@ export function negotiateCapability(db, agentId, requiredCapabilities) {
|
|
|
305
319
|
.get(agentId);
|
|
306
320
|
if (!card) throw new Error(`Agent not found: ${agentId}`);
|
|
307
321
|
|
|
308
|
-
const agentCaps =
|
|
322
|
+
const agentCaps = parseJsonArray(card.capabilities);
|
|
309
323
|
const supported = requiredCapabilities.filter((c) => agentCaps.includes(c));
|
|
310
324
|
const missing = requiredCapabilities.filter((c) => !agentCaps.includes(c));
|
|
311
325
|
|
|
@@ -329,8 +343,8 @@ export function listPeers(db) {
|
|
|
329
343
|
.all();
|
|
330
344
|
return rows.map((r) => ({
|
|
331
345
|
...r,
|
|
332
|
-
capabilities:
|
|
333
|
-
skills:
|
|
346
|
+
capabilities: parseJsonArray(r.capabilities),
|
|
347
|
+
skills: parseJsonArray(r.skills),
|
|
334
348
|
}));
|
|
335
349
|
}
|
|
336
350
|
|