chainlesschain 0.162.95 → 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/assets/{AIOps-DLvvkb9x.js → AIOps-Bo9Ux24z.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxhQTwqd.js → ActionButton-CExgXSQT.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C4oUXzhG.js → Analytics-BNnZhRTZ.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CjzKJdv3.js → AppLayout-D9xfqFCT.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-B-MrugUs.js → Audit-Cpgu8uCw.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ecIfEpIZ.js → Backup-CHdEJnNE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUcSfWnx.js → BaseInput-Cbw-PxBq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DIc1n_Lk.js → Chat-ChHTZK_6.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BGzg7oVA.js → ChatBubbleRenderer-Dl6z6ZH7.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQ3Ueff-.js → Checkbox-Cj8d9bru.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvSGLnqE.js → Codegen-C2YobW4j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Br9IXZcE.js → Col-CxfV5FGT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tPuLOf0r.js → Community-CiwOASMj.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-vMWH73BE.js → Compact-CTg_2iKW.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ct9z699x.js → Compliance-HMbsTmtw.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bxs1kL1y.js → Cowork-DyNDqGR2.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-QEpr2k4U.js → Cron-9-x5M7qy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-u4xbUw6O.js → Crosschain-4msgDaYJ.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DXTEvyrW.js → DID--2Beb5Po.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C4pZgzkg.js → Dashboard-T6nq0Lzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-LRuqREGe.js → Dropdown-D_Q6T10v.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D7XI1yBM.js → EmailListRenderer-TnN21bJx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-3y_SuAMQ.js → FamilyGuardDashboard-kky_uIij.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DbjbJJCW.js → Federation-C1GkNY4B.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-WPpMZcUw.js → FormItemContext-5-_Fc5YV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-nNT9Tn4E.js → GenericCardRenderer-CmHiB7sr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DqDaVzwV.js → Git-Cuci_BPI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BquBZpcg.js → Governance-BU0cO2LD.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RcPPYijs.js → Inference-BDFNgEcx.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ByXfZV13.js → KnowledgeGraph-DITXUzHz.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CHT3DUcq.js → Logs-Ea-07CRd.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Bi0HJ9pN.js → Marketplace-D6OBbRVG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-QCBxPRRc.js → McpTools-eiKpIJMi.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D4RlxGXn.js → Memory-M-X-T8UF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CU_PV7e-.js → MobileBridge-xcZb9Ekv.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-c1CMFD8z.js → MobileProjects-DCfoTPRC.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BezDB1-s.js → Mtc-BWfGDzD8.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CgLpiHpE.js → MtcAudit-imBuld-U.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-vx6bz-bA.js → Multisig-C2kcsq7I.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CGOagg3-.js → NLProgramming-DObvXSXP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BcuDGYRS.js → Notes-C-6OGJMe.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-ZAGCDUpc.js → NotificationSettings-ub7Ozxci.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-B5onE0eX.js → OrderTableRenderer-CR2W89Vk.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-y_y20-5Y.js → Organization-DNA_Wty7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BEICS31D.js → Overflow-rT4Wb4rQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D_WRMzh4.js → P2P-CK-hARKv.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-C2pbtNbT.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-3aqCc_4f.js → Permissions-B8EIEqGJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DT3R9g9Y.js → PersonalDataHub-BxNVNU3s.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-MiMWIhw0.js → Pipeline-CT9pbkyY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C3JsLuDa.js → Privacy-DhCNd2CS.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-EhHVi1ff.js → ProjectInit-Qngnx-vQ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D_dNEA37.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-CPEhi2iZ.js → Providers-4o9B_n-X.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BFQpMleX.js → QuickAsk-bs1qvF30.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DzbovRC8.js → Recommend-CuEnKA2K.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoBeV3TD.js → Reputation-DbfvmRb2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DthhwX_B.js → Row-DvMJGAI3.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dsf5U0lP.js → RssFeed-oJ0BZvmA.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CJPB8kKl.js → Search-DnIVrY9O.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DI9MfpdD.js → Security-CIWhI_Tc.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BdG0TFGf.js → Services-DLrnpjwm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DSYazY_B.js → Skeleton-CYmqgtVb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BD309VTT.js → Skills-BUzCkoeu.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CaUlw4_6.js → Sla-DYRE4bnS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZAPkR7H.js → SpeechSettings-_6MurdOg.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFRSowN3.js → SyncSettings-UIdnF5Li.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Di8EOluc.js → Tasks-COx2CjT8.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B5FTziwi.js → Templates-BcHAmkZo.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Dj3XYfBg.js → Tenant-CjYQS8yZ.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CznH_rJT.js → Terminal-BmfOPcWt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DTHzaXPt.js → TimelineRenderer-D5HuarNQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bo56nuML.js → Tokens-C_fbProA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-pyVUVBwb.js → Trigger-JPulpRx8.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cp7jMkKN.js → Trust-CnO-062O.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aPcZCdpz.js → UkeySign-HpNovvau.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DAv5j--w.js → VideoEditing-yMncxH7y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DRhK7IDR.js → Wallet-Cug-t7Np.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2yQJuq8.js → WebAuthn--U1-NR-3.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D98DhR54.js → WorkflowEditor-R-XvSJ3S.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BXrRNi8C.js → chat-CCI1i6Rp.js} +1 -1
- package/src/assets/web-panel/assets/{colors-jQ94T_qn.js → colors-C-mqoaLt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-KqUJLlF2.js → compact-item-De_N2iRR.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DxM3zuQW.js → createContext-DbTM6sjZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-OcLPHSl5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BGm946KV.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-Dib0FsTT.js → index-AY937BxS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5NQai7O.js → index-B6hgy5VC.js} +1 -1
- package/src/assets/web-panel/assets/{index-LWTZAud0.js → index-B7XnpLHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXMZLfd8.js → index-B7gw-tDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTUs9t5F.js → index-BBgDgkwW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce-YoL4x.js → index-BMmeiw88.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaU_fSK7.js → index-BU6XtnSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMp41Q1Y.js → index-Bi7VUG0R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQvgx2e.js → index-BtE3cw9m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWcd5zDG.js → index-C9ej-MF0.js} +3 -3
- package/src/assets/web-panel/assets/{index-BbS0cBU9.js → index-CIEiQl1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VWL6Lc.js → index-CKgaW9E5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzsaWQy.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-DSzb9VOG.js → index-Co1UkcFX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-2HpfWo.js → index-CzP5d5LW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboUBf6R.js → index-D0NJ5lRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZMzj4ay.js → index-D0gBsqTX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CghRL1dh.js → index-D5F6shtn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_SFSix.js → index-DVtamtlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-T4UJiTi3.js → index-DWT18hDG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CreXAr5Y.js → index-DaXb4B9P.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Z2m28C.js → index-DjQA0Vez.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crw76vIF.js → index-Ds8NJVEL.js} +1 -1
- package/src/assets/web-panel/assets/{index-mZNLT2SP.js → index-DuP-8HWu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEZfi5D.js → index-DwLTiJ31.js} +1 -1
- package/src/assets/web-panel/assets/{index-1P0sXNhG.js → index-T3VhH8cO.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4b7KjRq.js → index-TDY1o0w3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOYcemym.js → index-TRy2Gc3V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAPk6ntc.js → index-W1OMxCmI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dgzw6eKr.js → index-ftFRZbuc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sBLech.js → index-iGPGaORA.js} +1 -1
- package/src/assets/web-panel/assets/{index-FSYz5aww.js → index-jhy5KQvg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpVYD4j.js → index-qUSZJcgw.js} +1 -1
- package/src/assets/web-panel/assets/{index-yavsyHif.js → index-w3f6lqkD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfE3jxrT.js → index-xdMc71Kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-aOLXuHpG.js → index-xhX5eg2Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGjCjsf1.js → index-zkTiW6d6.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDoZJI7g.js → initDefaultProps-CIHqouWk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-sceLjgp-.js → motion-D1iUKftk.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTWQpFa7.js → move-YOpWjmjL.js} +1 -1
- package/src/assets/web-panel/assets/{omit-dr-NCrHZ.js → omit-C_1357UY.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DK-GX_Z2.js → pickAttrs-CDb4dq6o.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIPf--18.js → placementArrow-D-OGYyhd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BzrWT5tV.js → responsiveObserve-hS-j69A0.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D5XhxlET.js → slide-tnjuvphU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-6DI-k-9E.js → statusUtils-BERfMMcU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DMw-eyn9.js → styleChecker-DKpl2K9E.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D4oVdliM.js → useFlexGapSupport-C_sX4D3N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3PPZnvy.js → useFs-DBcxC1vK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Gh5ba4KP.js → usePersonalDataHub-CeoDf2XI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEM1u4x.js → vnode-DC9Pmj_u.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClT_-9RZ.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/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/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/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 +36 -13
- 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 +39 -7
- 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 +12 -1
- package/src/runtime/headless-stream.js +131 -5
- 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/Projects-CXydUXp8.js +0 -1
- 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
|
@@ -13,6 +13,34 @@ export function isGitRepo(dir) {
|
|
|
13
13
|
return existsSync(join(dir, ".git"));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Reject a git ref (branch / base / tag) that could break out of a
|
|
18
|
+
* shell-interpolated `git` command. gitExec interpolates its args into a shell
|
|
19
|
+
* string, so a ref like `x$(rm -rf ~)`, a backtick, or `--upload-pack=evil`
|
|
20
|
+
* would otherwise execute or inject a flag (double-quoting does NOT stop `$()`
|
|
21
|
+
* / backticks). Legit refs are branch/tag names — alphanumerics plus `. _ / -`;
|
|
22
|
+
* `HEAD` and `origin/main` pass. Throws on anything else. Exported for reuse +
|
|
23
|
+
* tests; callers validate caller/agent-supplied refs before interpolation.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} ref
|
|
26
|
+
* @param {string} [label="git ref"]
|
|
27
|
+
*/
|
|
28
|
+
export function assertSafeGitRef(ref, label = "git ref") {
|
|
29
|
+
if (typeof ref !== "string" || ref.length === 0) {
|
|
30
|
+
throw new Error(`Invalid ${label}: expected a non-empty string`);
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
!/^[A-Za-z0-9._/-]+$/.test(ref) || // only ref-safe chars (no space/shell metachars)
|
|
34
|
+
ref.startsWith("-") || // block flag injection (e.g. --upload-pack=…)
|
|
35
|
+
ref.includes("..") || // forbidden in refnames; also blocks range tricks
|
|
36
|
+
ref.includes("//") ||
|
|
37
|
+
ref.endsWith("/") ||
|
|
38
|
+
ref.endsWith(".lock")
|
|
39
|
+
) {
|
|
40
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(ref)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
/**
|
|
17
45
|
* Run a git command and return stdout.
|
|
18
46
|
*/
|
package/src/lib/goal-store.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { getHomeDir } from "./paths.js";
|
|
22
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
22
23
|
|
|
23
24
|
/** Valid goal lifecycle states. `active` goals are the ones injected. */
|
|
24
25
|
export const GOAL_STATUS = Object.freeze({
|
|
@@ -38,6 +39,28 @@ function ensureDir(dir) {
|
|
|
38
39
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Write a goal file atomically: a crash mid-write must not truncate/corrupt an
|
|
44
|
+
* existing goal (an unparseable goal file is silently dropped by getGoal's catch
|
|
45
|
+
* → the user loses that goal). Temp sibling + rename (atomic within a
|
|
46
|
+
* filesystem). Temp names end in `.tmp` (never `.json`), so a hard-crash
|
|
47
|
+
* leftover is ignored by listGoals.
|
|
48
|
+
*/
|
|
49
|
+
function atomicWriteFileSync(filePath, data) {
|
|
50
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
51
|
+
try {
|
|
52
|
+
fs.writeFileSync(tmp, data, "utf-8");
|
|
53
|
+
fs.renameSync(tmp, filePath);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
57
|
+
} catch {
|
|
58
|
+
/* best-effort temp cleanup */
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
function newId(prefix) {
|
|
42
65
|
// Date.now/random are fine here (plain CLI lib, not a resumable workflow).
|
|
43
66
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
@@ -98,7 +121,7 @@ export function createGoal(input = {}, opts = {}) {
|
|
|
98
121
|
updatedAt: nowIso(),
|
|
99
122
|
};
|
|
100
123
|
ensureDir(root);
|
|
101
|
-
|
|
124
|
+
atomicWriteFileSync(goalFile(root, id), JSON.stringify(goal, null, 2));
|
|
102
125
|
return goal;
|
|
103
126
|
}
|
|
104
127
|
|
|
@@ -137,11 +160,7 @@ function saveGoal(goal, opts = {}) {
|
|
|
137
160
|
const root = opts.root || defaultRoot();
|
|
138
161
|
ensureDir(root);
|
|
139
162
|
goal.updatedAt = nowIso();
|
|
140
|
-
|
|
141
|
-
goalFile(root, goal.id),
|
|
142
|
-
JSON.stringify(goal, null, 2),
|
|
143
|
-
"utf-8",
|
|
144
|
-
);
|
|
163
|
+
atomicWriteFileSync(goalFile(root, goal.id), JSON.stringify(goal, null, 2));
|
|
145
164
|
return goal;
|
|
146
165
|
}
|
|
147
166
|
|
|
@@ -162,10 +181,17 @@ export function listGoals(opts = {}) {
|
|
|
162
181
|
|
|
163
182
|
/** Mutate a goal via `fn(goal)`; throws if not found. */
|
|
164
183
|
function mutate(id, fn, opts = {}) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
// Serialize the read-modify-write across processes (cc+cc, or cc+desktop
|
|
185
|
+
// sharing the goals dir) so concurrent goal edits don't lose an update.
|
|
186
|
+
// Best-effort lock (with-file-lock): on contention timeout it proceeds anyway,
|
|
187
|
+
// so the CLI never hangs.
|
|
188
|
+
const root = opts.root || defaultRoot();
|
|
189
|
+
return withFileLock(goalFile(root, id), () => {
|
|
190
|
+
const goal = getGoal(id, opts);
|
|
191
|
+
if (!goal) throw new Error(`no such goal: ${id}`);
|
|
192
|
+
fn(goal);
|
|
193
|
+
return saveGoal(goal, opts);
|
|
194
|
+
});
|
|
169
195
|
}
|
|
170
196
|
|
|
171
197
|
/** Add a key result to a goal. */
|
|
@@ -1006,6 +1006,12 @@ export function startConsolidationTimer({
|
|
|
1006
1006
|
}
|
|
1007
1007
|
}
|
|
1008
1008
|
}, intervalMs);
|
|
1009
|
+
// Background maintenance must not by itself keep the CLI process alive (the
|
|
1010
|
+
// process should exit once foreground work is done); matches the unref
|
|
1011
|
+
// discipline used by downloader / PtyManager / background-task-manager.
|
|
1012
|
+
if (_consolidationTimer && typeof _consolidationTimer.unref === "function") {
|
|
1013
|
+
_consolidationTimer.unref();
|
|
1014
|
+
}
|
|
1009
1015
|
return _consolidationTimer;
|
|
1010
1016
|
}
|
|
1011
1017
|
|
package/src/lib/llm-pricing.js
CHANGED
|
@@ -30,7 +30,19 @@ export const FREE_PROVIDERS = Object.freeze([
|
|
|
30
30
|
*/
|
|
31
31
|
export const PRICE_TABLE = Object.freeze({
|
|
32
32
|
anthropic: [
|
|
33
|
-
|
|
33
|
+
// Current Anthropic list prices (USD per 1M tokens), verified 2026-06-21
|
|
34
|
+
// against the claude-api reference. The Opus tier dropped to $5/$25 with
|
|
35
|
+
// Opus 4.5 (Nov 2025) — the old $15/$75 applies only to the retired
|
|
36
|
+
// Opus 4 / 4.1. Longest-pattern-first matching (see matchRate) resolves
|
|
37
|
+
// dated ids to the specific rate; bare "opus" defaults to the current price.
|
|
38
|
+
{ match: "fable-5", in: 10, out: 50 },
|
|
39
|
+
{ match: "mythos-5", in: 10, out: 50 },
|
|
40
|
+
{ match: "opus-4-8", in: 5, out: 25 },
|
|
41
|
+
{ match: "opus-4-7", in: 5, out: 25 },
|
|
42
|
+
{ match: "opus-4-6", in: 5, out: 25 },
|
|
43
|
+
{ match: "opus-4-5", in: 5, out: 25 },
|
|
44
|
+
{ match: "opus-4-1", in: 15, out: 75 },
|
|
45
|
+
{ match: "opus", in: 5, out: 25 },
|
|
34
46
|
{ match: "sonnet", in: 3, out: 15 },
|
|
35
47
|
{ match: "haiku", in: 1, out: 5 },
|
|
36
48
|
],
|
|
@@ -155,11 +167,33 @@ export function lookupRate(provider, model, table = PRICE_TABLE) {
|
|
|
155
167
|
* @returns {{ inputCost:number, outputCost:number, totalCost:number,
|
|
156
168
|
* currency:string, matched:boolean, free:boolean, rate:object|null }}
|
|
157
169
|
*/
|
|
170
|
+
// Prompt-cache price multipliers relative to the input rate. A cache WRITE
|
|
171
|
+
// (Anthropic 5-minute ephemeral breakpoint) bills at ~125%; only Anthropic
|
|
172
|
+
// reports cache creation. Cache READ pricing differs per provider — Anthropic
|
|
173
|
+
// ~10%, OpenAI ~50%, DeepSeek ~25% of the input rate — so the default below is
|
|
174
|
+
// overridden per provider. Applied only when cache token counts are present, so
|
|
175
|
+
// non-caching usage is priced exactly as before. Public list-price estimates
|
|
176
|
+
// that drift; override the table via config.llm.pricing for the base rates.
|
|
177
|
+
export const CACHE_READ_MULTIPLIER = 0.1;
|
|
178
|
+
export const CACHE_WRITE_MULTIPLIER = 1.25;
|
|
179
|
+
export const CACHE_READ_MULTIPLIER_BY_PROVIDER = Object.freeze({
|
|
180
|
+
anthropic: 0.1,
|
|
181
|
+
openai: 0.5,
|
|
182
|
+
deepseek: 0.25,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
function cacheReadMultiplier(provider) {
|
|
186
|
+
const p = String(provider || "").toLowerCase();
|
|
187
|
+
return CACHE_READ_MULTIPLIER_BY_PROVIDER[p] ?? CACHE_READ_MULTIPLIER;
|
|
188
|
+
}
|
|
189
|
+
|
|
158
190
|
export function estimateCost({
|
|
159
191
|
provider,
|
|
160
192
|
model,
|
|
161
193
|
inputTokens = 0,
|
|
162
194
|
outputTokens = 0,
|
|
195
|
+
cacheReadTokens = 0,
|
|
196
|
+
cacheCreationTokens = 0,
|
|
163
197
|
table,
|
|
164
198
|
} = {}) {
|
|
165
199
|
const rate = lookupRate(provider, model, table);
|
|
@@ -167,6 +201,8 @@ export function estimateCost({
|
|
|
167
201
|
return {
|
|
168
202
|
inputCost: 0,
|
|
169
203
|
outputCost: 0,
|
|
204
|
+
cacheReadCost: 0,
|
|
205
|
+
cacheCreationCost: 0,
|
|
170
206
|
totalCost: 0,
|
|
171
207
|
currency: "USD",
|
|
172
208
|
matched: false,
|
|
@@ -176,10 +212,20 @@ export function estimateCost({
|
|
|
176
212
|
}
|
|
177
213
|
const inputCost = round((Number(inputTokens) / 1e6) * rate.in);
|
|
178
214
|
const outputCost = round((Number(outputTokens) / 1e6) * rate.out);
|
|
215
|
+
const cacheReadCost = round(
|
|
216
|
+
(Number(cacheReadTokens) / 1e6) * rate.in * cacheReadMultiplier(provider),
|
|
217
|
+
);
|
|
218
|
+
const cacheCreationCost = round(
|
|
219
|
+
(Number(cacheCreationTokens) / 1e6) * rate.in * CACHE_WRITE_MULTIPLIER,
|
|
220
|
+
);
|
|
179
221
|
return {
|
|
180
222
|
inputCost,
|
|
181
223
|
outputCost,
|
|
182
|
-
|
|
224
|
+
cacheReadCost,
|
|
225
|
+
cacheCreationCost,
|
|
226
|
+
totalCost: round(
|
|
227
|
+
inputCost + outputCost + cacheReadCost + cacheCreationCost,
|
|
228
|
+
),
|
|
183
229
|
currency: "USD",
|
|
184
230
|
matched: true,
|
|
185
231
|
free: rate.pattern === "free",
|
|
@@ -202,6 +248,8 @@ export function priceRollup(aggregate, { table } = {}) {
|
|
|
202
248
|
model: row.model,
|
|
203
249
|
inputTokens: row.inputTokens,
|
|
204
250
|
outputTokens: row.outputTokens,
|
|
251
|
+
cacheReadTokens: row.cacheReadTokens,
|
|
252
|
+
cacheCreationTokens: row.cacheCreationTokens,
|
|
205
253
|
table,
|
|
206
254
|
});
|
|
207
255
|
return {
|
|
@@ -209,6 +257,8 @@ export function priceRollup(aggregate, { table } = {}) {
|
|
|
209
257
|
cost: est.totalCost,
|
|
210
258
|
inputCost: est.inputCost,
|
|
211
259
|
outputCost: est.outputCost,
|
|
260
|
+
cacheReadCost: est.cacheReadCost,
|
|
261
|
+
cacheCreationCost: est.cacheCreationCost,
|
|
212
262
|
currency: est.currency,
|
|
213
263
|
matched: est.matched,
|
|
214
264
|
free: est.free,
|
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -306,12 +306,46 @@ export function getStoredToken(serverUrl) {
|
|
|
306
306
|
return loadTokenStore()[serverKey(serverUrl)] || null;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// The store holds OAuth access + refresh tokens, so it must not be world-
|
|
310
|
+
// readable. writeFileSync's `mode` only applies when the file is created, so
|
|
311
|
+
// also chmod (best-effort) to tighten a file an older version left at 0644.
|
|
312
|
+
function _writeStoreSecure(file, store) {
|
|
313
|
+
_deps.fs.mkdirSync(pathDefault.dirname(file), { recursive: true, mode: 0o700 });
|
|
314
|
+
const data = JSON.stringify(store, null, 2) + "\n";
|
|
315
|
+
// Atomic temp+rename: the store holds live OAuth tokens, so a crash (or a
|
|
316
|
+
// concurrent `cc mcp login`) mid-write must not truncate it and lose auth for
|
|
317
|
+
// every server. The temp inherits the 0600 mode. Falls back to a direct write
|
|
318
|
+
// when the injected fs lacks renameSync (test mocks); the real fs always has
|
|
319
|
+
// it, so production is always atomic.
|
|
320
|
+
if (typeof _deps.fs.renameSync !== "function") {
|
|
321
|
+
_deps.fs.writeFileSync(file, data, { encoding: "utf-8", mode: 0o600 });
|
|
322
|
+
} else {
|
|
323
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
324
|
+
try {
|
|
325
|
+
_deps.fs.writeFileSync(tmp, data, { encoding: "utf-8", mode: 0o600 });
|
|
326
|
+
_deps.fs.renameSync(tmp, file);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
try {
|
|
329
|
+
if (_deps.fs.existsSync && _deps.fs.existsSync(tmp) && _deps.fs.unlinkSync)
|
|
330
|
+
_deps.fs.unlinkSync(tmp);
|
|
331
|
+
} catch {
|
|
332
|
+
/* best-effort temp cleanup */
|
|
333
|
+
}
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
_deps.fs.chmodSync(file, 0o600);
|
|
339
|
+
} catch {
|
|
340
|
+
/* chmod unsupported on this platform/fs (e.g. Windows) — mode is advisory */
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
309
344
|
export function saveStoredToken(serverUrl, record) {
|
|
310
345
|
const file = tokenStorePath();
|
|
311
346
|
const store = loadTokenStore();
|
|
312
347
|
store[serverKey(serverUrl)] = { server: serverKey(serverUrl), ...record };
|
|
313
|
-
|
|
314
|
-
_deps.fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", "utf-8");
|
|
348
|
+
_writeStoreSecure(file, store);
|
|
315
349
|
return store[serverKey(serverUrl)];
|
|
316
350
|
}
|
|
317
351
|
|
|
@@ -322,11 +356,7 @@ export function deleteStoredToken(serverUrl) {
|
|
|
322
356
|
if (!(key in store)) return false;
|
|
323
357
|
delete store[key];
|
|
324
358
|
try {
|
|
325
|
-
|
|
326
|
-
file,
|
|
327
|
-
JSON.stringify(store, null, 2) + "\n",
|
|
328
|
-
"utf-8",
|
|
329
|
-
);
|
|
359
|
+
_writeStoreSecure(file, store);
|
|
330
360
|
} catch {
|
|
331
361
|
/* best-effort */
|
|
332
362
|
}
|
|
@@ -8,6 +8,27 @@
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Atomically write a UTF-8 file: a crash mid-write must not truncate MEMORY.md
|
|
13
|
+
* (the long-term knowledge file, re-read each session). Temp sibling + rename,
|
|
14
|
+
* which is atomic within a filesystem, so a reader/crash sees either the old
|
|
15
|
+
* file or the complete new one.
|
|
16
|
+
*/
|
|
17
|
+
function atomicWriteFileSync(filePath, data) {
|
|
18
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
19
|
+
try {
|
|
20
|
+
fs.writeFileSync(tmp, data, "utf8");
|
|
21
|
+
fs.renameSync(tmp, filePath);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
try {
|
|
24
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
25
|
+
} catch {
|
|
26
|
+
/* best-effort temp cleanup */
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
/**
|
|
12
33
|
* Ensure the memory directory structure exists
|
|
13
34
|
*/
|
|
@@ -146,7 +167,7 @@ export function appendDailyNote(memoryDir, content) {
|
|
|
146
167
|
fs.appendFileSync(filePath, entry, "utf8");
|
|
147
168
|
} else {
|
|
148
169
|
const header = `# Daily Note: ${today}\n${entry}`;
|
|
149
|
-
|
|
170
|
+
atomicWriteFileSync(filePath, header);
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
return { date: today, path: filePath };
|
|
@@ -206,7 +227,7 @@ export function getMemoryFile(memoryDir) {
|
|
|
206
227
|
export function updateMemoryFile(memoryDir, content) {
|
|
207
228
|
ensureMemoryDirs(memoryDir);
|
|
208
229
|
const filePath = path.join(memoryDir, "MEMORY.md");
|
|
209
|
-
|
|
230
|
+
atomicWriteFileSync(filePath, content);
|
|
210
231
|
return { path: filePath };
|
|
211
232
|
}
|
|
212
233
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated-model warning — Claude-Code 2.1.183 parity:
|
|
3
|
+
* "a warning when the requested model is deprecated or automatically updated
|
|
4
|
+
* to a newer model".
|
|
5
|
+
*
|
|
6
|
+
* The "automatically updated to a newer model" half is already covered by the
|
|
7
|
+
* fallback-model machinery (a model-not-found error switches to the next model
|
|
8
|
+
* in the chain and surfaces an onFallback notice — see fallback-model.js +
|
|
9
|
+
* agent.js / agent-repl.js). This module covers the PROACTIVE half: warn once,
|
|
10
|
+
* up front, when the user's *configured / requested* model id is on a small,
|
|
11
|
+
* high-confidence list of provider-retired model snapshots — before the run
|
|
12
|
+
* fails with an opaque "model not found".
|
|
13
|
+
*
|
|
14
|
+
* The Anthropic entries below are kept in sync with the authoritative model
|
|
15
|
+
* deprecation table (platform.claude.com/docs/en/about-claude/model-deprecations,
|
|
16
|
+
* verified 2026-06-21). `state` distinguishes "retired" (requests now fail) from
|
|
17
|
+
* "deprecated" (still works, not recommended, has a retirement date).
|
|
18
|
+
*
|
|
19
|
+
* Design constraints (this project hates model-id churn noise):
|
|
20
|
+
* - Only ids the provider has officially retired or deprecated. A false warning
|
|
21
|
+
* on a live model is worse than a missing one.
|
|
22
|
+
* - CRITICAL: the Claude 4 family has BOTH active and retired members that
|
|
23
|
+
* differ only by date suffix (claude-opus-4-8 active vs claude-opus-4-20250514
|
|
24
|
+
* retired), so those entries use EXACT dated ids and must never be turned into
|
|
25
|
+
* bare prefixes. Older families (1 / 2 / 3 / 3.5 / 3.7) are fully retired, so a
|
|
26
|
+
* family prefix is safe there.
|
|
27
|
+
* - Pure + fail-open: a lookup never throws and is trivially unit-testable.
|
|
28
|
+
* - The notice goes to stderr (keeps stdout clean for JSON/headless output),
|
|
29
|
+
* fires at most once per model per process, and is silenced with
|
|
30
|
+
* CC_MODEL_NOTICE=0.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import chalk from "chalk";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Retired / deprecated model snapshots, most-specific first. `id` is matched
|
|
37
|
+
* case-insensitively as an exact id OR a prefix (so `claude-2` covers
|
|
38
|
+
* `claude-2.0` / `claude-2.1`, and `claude-3-5-sonnet` covers both 3.5 Sonnet
|
|
39
|
+
* snapshots). Prefixes are only used for fully-retired families — never for the
|
|
40
|
+
* Claude 4 family, whose active and retired members share a prefix.
|
|
41
|
+
*
|
|
42
|
+
* @type {Array<{id: string, state: "retired"|"deprecated", reason: string, replacement: string}>}
|
|
43
|
+
*/
|
|
44
|
+
export const DEPRECATED_MODELS = [
|
|
45
|
+
// ── Anthropic — Claude 4 family (EXACT dated ids only; prefixes would hit
|
|
46
|
+
// the active claude-opus-4-8 / -4-7 / -4-6 / -4-5-20251101 / sonnet-4-6 /
|
|
47
|
+
// sonnet-4-5-20250929 / haiku-4-5-20251001 models) ──────────────────────
|
|
48
|
+
{
|
|
49
|
+
id: "claude-opus-4-1-20250805",
|
|
50
|
+
state: "deprecated",
|
|
51
|
+
reason: "Anthropic deprecated Claude Opus 4.1 (retires 2026-08-05)",
|
|
52
|
+
replacement: "claude-opus-4-8",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "claude-opus-4-20250514",
|
|
56
|
+
state: "retired",
|
|
57
|
+
reason: "Anthropic retired the original Claude Opus 4 snapshot",
|
|
58
|
+
replacement: "claude-opus-4-8",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "claude-sonnet-4-20250514",
|
|
62
|
+
state: "retired",
|
|
63
|
+
reason: "Anthropic retired the original Claude Sonnet 4 snapshot",
|
|
64
|
+
replacement: "claude-sonnet-4-6",
|
|
65
|
+
},
|
|
66
|
+
// ── Anthropic — fully-retired older families (family prefix is safe) ───────
|
|
67
|
+
{
|
|
68
|
+
id: "claude-3-7-sonnet",
|
|
69
|
+
state: "retired",
|
|
70
|
+
reason: "Anthropic retired Claude Sonnet 3.7",
|
|
71
|
+
replacement: "claude-sonnet-4-6",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "claude-3-5-sonnet",
|
|
75
|
+
state: "retired",
|
|
76
|
+
reason: "Anthropic retired the Claude 3.5 Sonnet snapshots",
|
|
77
|
+
replacement: "claude-sonnet-4-6",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "claude-3-5-haiku",
|
|
81
|
+
state: "retired",
|
|
82
|
+
reason: "Anthropic retired Claude Haiku 3.5",
|
|
83
|
+
replacement: "claude-haiku-4-5",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "claude-3-opus-20240229",
|
|
87
|
+
state: "retired",
|
|
88
|
+
reason: "Anthropic retired Claude 3 Opus",
|
|
89
|
+
replacement: "claude-opus-4-8",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "claude-3-sonnet-20240229",
|
|
93
|
+
state: "retired",
|
|
94
|
+
reason: "Anthropic retired Claude 3 Sonnet",
|
|
95
|
+
replacement: "claude-sonnet-4-6",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "claude-3-haiku-20240307",
|
|
99
|
+
state: "retired",
|
|
100
|
+
reason: "Anthropic retired Claude 3 Haiku",
|
|
101
|
+
replacement: "claude-haiku-4-5",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "claude-2",
|
|
105
|
+
state: "retired",
|
|
106
|
+
reason: "Anthropic retired the Claude 2 family",
|
|
107
|
+
replacement: "claude-sonnet-4-6 or claude-opus-4-8",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "claude-instant",
|
|
111
|
+
state: "retired",
|
|
112
|
+
reason: "Anthropic retired Claude Instant",
|
|
113
|
+
replacement: "claude-haiku-4-5",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "claude-1",
|
|
117
|
+
state: "retired",
|
|
118
|
+
reason: "Anthropic retired the Claude 1 family",
|
|
119
|
+
replacement: "claude-sonnet-4-6 or claude-opus-4-8",
|
|
120
|
+
},
|
|
121
|
+
// ── OpenAI (retired snapshots) ────────────────────────────────────────────
|
|
122
|
+
{
|
|
123
|
+
id: "gpt-4-vision-preview",
|
|
124
|
+
state: "retired",
|
|
125
|
+
reason: "OpenAI retired the gpt-4 vision preview",
|
|
126
|
+
replacement: "gpt-5 (natively multimodal)",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "gpt-4-1106-vision-preview",
|
|
130
|
+
state: "retired",
|
|
131
|
+
reason: "OpenAI retired the gpt-4 vision preview",
|
|
132
|
+
replacement: "gpt-5 (natively multimodal)",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "gpt-4-32k",
|
|
136
|
+
state: "retired",
|
|
137
|
+
reason: "OpenAI retired the gpt-4-32k snapshots",
|
|
138
|
+
replacement: "gpt-5 or gpt-4o",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "gpt-4-0314",
|
|
142
|
+
state: "retired",
|
|
143
|
+
reason: "OpenAI retired this gpt-4 snapshot",
|
|
144
|
+
replacement: "gpt-5 or gpt-4o",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "gpt-4-0613",
|
|
148
|
+
state: "retired",
|
|
149
|
+
reason: "OpenAI retired this gpt-4 snapshot",
|
|
150
|
+
replacement: "gpt-5 or gpt-4o",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "gpt-3.5-turbo-0301",
|
|
154
|
+
state: "retired",
|
|
155
|
+
reason: "OpenAI retired this gpt-3.5-turbo snapshot",
|
|
156
|
+
replacement: "gpt-4o-mini or gpt-5-mini",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "gpt-3.5-turbo-0613",
|
|
160
|
+
state: "retired",
|
|
161
|
+
reason: "OpenAI retired this gpt-3.5-turbo snapshot",
|
|
162
|
+
replacement: "gpt-4o-mini or gpt-5-mini",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "text-davinci-002",
|
|
166
|
+
state: "retired",
|
|
167
|
+
reason: "OpenAI retired the text-davinci completion models",
|
|
168
|
+
replacement: "gpt-4o-mini or gpt-5-mini",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "text-davinci-003",
|
|
172
|
+
state: "retired",
|
|
173
|
+
reason: "OpenAI retired the text-davinci completion models",
|
|
174
|
+
replacement: "gpt-4o-mini or gpt-5-mini",
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Look up a model id against the retired list.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} modelId
|
|
182
|
+
* @returns {{id: string, state: string, reason: string, replacement: string}|null}
|
|
183
|
+
* the matched entry (with `id` = the configured model), or null.
|
|
184
|
+
*/
|
|
185
|
+
export function checkModelDeprecation(modelId) {
|
|
186
|
+
if (!modelId || typeof modelId !== "string") return null;
|
|
187
|
+
const norm = modelId.trim().toLowerCase();
|
|
188
|
+
if (!norm) return null;
|
|
189
|
+
for (const entry of DEPRECATED_MODELS) {
|
|
190
|
+
const pat = entry.id.toLowerCase();
|
|
191
|
+
if (norm === pat || norm.startsWith(pat)) {
|
|
192
|
+
return { ...entry, id: modelId };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Human-readable one-line warning for a matched deprecation.
|
|
200
|
+
*
|
|
201
|
+
* @param {{id: string, state?: string, reason: string, replacement: string}} info
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
204
|
+
export function formatModelDeprecationWarning(info) {
|
|
205
|
+
const verb = info.state === "retired" ? "has been retired" : "is deprecated";
|
|
206
|
+
return (
|
|
207
|
+
`Warning: model "${info.id}" ${verb} — ${info.reason}. ` +
|
|
208
|
+
`Switch to ${info.replacement}. (CC_MODEL_NOTICE=0 to hide)`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Models already warned about in this process — one notice per id. */
|
|
213
|
+
export const _warned = new Set();
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Entry-point hook: warn once on stderr if `model` is a retired snapshot.
|
|
217
|
+
* Cheap, fail-open, never throws.
|
|
218
|
+
*
|
|
219
|
+
* @param {object} opts
|
|
220
|
+
* @param {string} opts.model the resolved model id
|
|
221
|
+
* @param {object} [opts.env] defaults to process.env
|
|
222
|
+
* @param {(line:string)=>void} [opts.print] defaults to a yellow stderr writer
|
|
223
|
+
* @param {Set} [opts.seen] dedup set (defaults to module `_warned`)
|
|
224
|
+
* @returns {{ warned: boolean, info: object|null }}
|
|
225
|
+
*/
|
|
226
|
+
export function maybeWarnDeprecatedModel(opts = {}) {
|
|
227
|
+
const env = opts.env || process.env;
|
|
228
|
+
const seen = opts.seen || _warned;
|
|
229
|
+
const print =
|
|
230
|
+
opts.print || ((line) => process.stderr.write(chalk.yellow(line) + "\n"));
|
|
231
|
+
const out = { warned: false, info: null };
|
|
232
|
+
try {
|
|
233
|
+
if (env.CC_MODEL_NOTICE === "0") return out;
|
|
234
|
+
const info = checkModelDeprecation(opts.model);
|
|
235
|
+
if (!info) return out;
|
|
236
|
+
const key = String(opts.model).toLowerCase();
|
|
237
|
+
if (seen.has(key)) {
|
|
238
|
+
out.info = info;
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
seen.add(key);
|
|
242
|
+
print(formatModelDeprecationWarning(info));
|
|
243
|
+
out.warned = true;
|
|
244
|
+
out.info = info;
|
|
245
|
+
} catch {
|
|
246
|
+
/* fail-open: a deprecation notice must never affect the run */
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|