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
|
@@ -305,8 +305,8 @@ export function createGovernanceProposalV2(db, options = {}) {
|
|
|
305
305
|
throw new Error(`Unknown proposal type: ${proposalType}`);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
const quorumValue =
|
|
309
|
-
const thresholdValue =
|
|
308
|
+
const quorumValue = Number.isFinite(quorum) ? quorum : 3;
|
|
309
|
+
const thresholdValue = Number.isFinite(threshold) ? threshold : 0.5;
|
|
310
310
|
if (quorumValue < 1) throw new Error("Quorum must be >= 1");
|
|
311
311
|
if (thresholdValue <= 0 || thresholdValue > 1) {
|
|
312
312
|
throw new Error("Threshold must be in (0, 1]");
|
|
@@ -370,7 +370,7 @@ export function castVoteV2(db, options = {}) {
|
|
|
370
370
|
if (!Object.values(VOTE_DIRECTION).includes(direction)) {
|
|
371
371
|
throw new Error(`Unknown vote direction: ${direction}`);
|
|
372
372
|
}
|
|
373
|
-
const weightValue =
|
|
373
|
+
const weightValue = Number.isFinite(weight) ? weight : 1;
|
|
374
374
|
if (weightValue <= 0) throw new Error("Vote weight must be positive");
|
|
375
375
|
|
|
376
376
|
if (direction === VOTE_DIRECTION.FOR) {
|
|
@@ -456,7 +456,7 @@ export function cancelProposal(db, proposalId) {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
export function expireProposalsV2(db, now) {
|
|
459
|
-
const cutoff =
|
|
459
|
+
const cutoff = Number.isFinite(now) ? now : Date.now();
|
|
460
460
|
const expired = [];
|
|
461
461
|
for (const p of _proposals.values()) {
|
|
462
462
|
if (p.status === PROPOSAL_STATUS_V2.ACTIVE) {
|
|
@@ -14,6 +14,53 @@ export const _deps = {
|
|
|
14
14
|
path,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Atomically write a UTF-8 file via _deps.fs. A gene's gene.json / content.md
|
|
19
|
+
* are persistent store state; a crash mid-write truncates them and getGene's
|
|
20
|
+
* JSON.parse then throws. Temp sibling + rename (atomic within a filesystem).
|
|
21
|
+
* Graceful-degrade to a direct write when the injected fs has no renameSync
|
|
22
|
+
* (partial test mocks); the real fs always has it, so production is atomic.
|
|
23
|
+
*/
|
|
24
|
+
function _atomicWriteFile(filePath, data) {
|
|
25
|
+
const fsx = _deps.fs;
|
|
26
|
+
if (typeof fsx.renameSync !== "function") {
|
|
27
|
+
fsx.writeFileSync(filePath, data, "utf-8");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
31
|
+
try {
|
|
32
|
+
fsx.writeFileSync(tmp, data, "utf-8");
|
|
33
|
+
fsx.renameSync(tmp, filePath);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
try {
|
|
36
|
+
if (fsx.existsSync(tmp) && fsx.unlinkSync) fsx.unlinkSync(tmp);
|
|
37
|
+
} catch {
|
|
38
|
+
/* best-effort temp cleanup */
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A gene id is interpolated into a filesystem path (genesDir/<id>/...). Gene ids
|
|
46
|
+
* arrive from downloaded/installed genes (an external hub / MCP response), so a
|
|
47
|
+
* malicious `id` like "../../.ssh/authorized_keys" or "C:\\Windows\\..." would
|
|
48
|
+
* escape genesDir and read/overwrite/delete arbitrary files. Restrict it to a
|
|
49
|
+
* single safe path segment (no separators, no traversal, no drive/NUL).
|
|
50
|
+
*/
|
|
51
|
+
function _isSafeGeneId(id) {
|
|
52
|
+
return (
|
|
53
|
+
typeof id === "string" &&
|
|
54
|
+
id.length > 0 &&
|
|
55
|
+
id !== "." &&
|
|
56
|
+
id !== ".." &&
|
|
57
|
+
!id.includes("/") &&
|
|
58
|
+
!id.includes("\\") &&
|
|
59
|
+
!id.includes(":") &&
|
|
60
|
+
!id.includes("\0")
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
17
64
|
export class EvoMapManager {
|
|
18
65
|
/**
|
|
19
66
|
* @param {object} options
|
|
@@ -71,6 +118,9 @@ export class EvoMapManager {
|
|
|
71
118
|
saveGene(gene, content) {
|
|
72
119
|
this.initialize();
|
|
73
120
|
if (!gene || !gene.id) throw new Error("Gene ID required");
|
|
121
|
+
if (!_isSafeGeneId(gene.id)) {
|
|
122
|
+
throw new Error(`Unsafe gene id (path traversal): ${gene.id}`);
|
|
123
|
+
}
|
|
74
124
|
|
|
75
125
|
const geneDir = _deps.path.join(this.genesDir, gene.id);
|
|
76
126
|
if (!_deps.fs.existsSync(geneDir)) {
|
|
@@ -78,19 +128,14 @@ export class EvoMapManager {
|
|
|
78
128
|
}
|
|
79
129
|
|
|
80
130
|
// Save metadata
|
|
81
|
-
|
|
131
|
+
_atomicWriteFile(
|
|
82
132
|
_deps.path.join(geneDir, "gene.json"),
|
|
83
133
|
JSON.stringify(gene, null, 2),
|
|
84
|
-
"utf-8",
|
|
85
134
|
);
|
|
86
135
|
|
|
87
136
|
// Save content
|
|
88
137
|
if (content) {
|
|
89
|
-
_deps.
|
|
90
|
-
_deps.path.join(geneDir, "content.md"),
|
|
91
|
-
content,
|
|
92
|
-
"utf-8",
|
|
93
|
-
);
|
|
138
|
+
_atomicWriteFile(_deps.path.join(geneDir, "content.md"), content);
|
|
94
139
|
}
|
|
95
140
|
|
|
96
141
|
// Register in DB
|
|
@@ -170,6 +215,7 @@ export class EvoMapManager {
|
|
|
170
215
|
*/
|
|
171
216
|
getGene(geneId) {
|
|
172
217
|
this.initialize();
|
|
218
|
+
if (!_isSafeGeneId(geneId)) return null;
|
|
173
219
|
const geneDir = _deps.path.join(this.genesDir, geneId);
|
|
174
220
|
|
|
175
221
|
if (!_deps.fs.existsSync(geneDir)) return null;
|
|
@@ -192,6 +238,7 @@ export class EvoMapManager {
|
|
|
192
238
|
*/
|
|
193
239
|
removeGene(geneId) {
|
|
194
240
|
this.initialize();
|
|
241
|
+
if (!_isSafeGeneId(geneId)) return { removed: false };
|
|
195
242
|
const geneDir = _deps.path.join(this.genesDir, geneId);
|
|
196
243
|
|
|
197
244
|
if (_deps.fs.existsSync(geneDir)) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized friendly-error boundary for the CLI entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* An uncaught error thrown from a command action (malformed --json, a failed DB
|
|
5
|
+
* op) — or, crucially, one that escapes into an async event handler / detached
|
|
6
|
+
* task / third-party lib AFTER the top-level parse — otherwise surfaces as a raw
|
|
7
|
+
* stack trace via Node's default unhandledRejection/uncaughtException, which on
|
|
8
|
+
* modern Node terminates the process. Route all of those through one place that
|
|
9
|
+
* prints a clean `error: <message>` (full stack under --verbose or
|
|
10
|
+
* CC_DEBUG/DEBUG) and exits non-zero.
|
|
11
|
+
*
|
|
12
|
+
* Pure-ish: process bindings are injectable so this is unit-testable.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export function reportFatal(
|
|
16
|
+
err,
|
|
17
|
+
{
|
|
18
|
+
stderr = process.stderr,
|
|
19
|
+
exit = process.exit,
|
|
20
|
+
argv = process.argv,
|
|
21
|
+
env = process.env,
|
|
22
|
+
} = {},
|
|
23
|
+
) {
|
|
24
|
+
const verbose =
|
|
25
|
+
argv.includes("--verbose") || Boolean(env.CC_DEBUG) || Boolean(env.DEBUG);
|
|
26
|
+
if (verbose && err && err.stack) {
|
|
27
|
+
stderr.write(`${err.stack}\n`);
|
|
28
|
+
} else {
|
|
29
|
+
const msg = err && err.message ? err.message : String(err);
|
|
30
|
+
stderr.write(`error: ${msg}\n`);
|
|
31
|
+
}
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Install process-level safety nets that funnel unhandled rejections and
|
|
37
|
+
* uncaught exceptions through `report`. Node terminates on these by default, so
|
|
38
|
+
* this only swaps the ugly default stack for the friendly boundary — it is not
|
|
39
|
+
* a behavior change (still exits non-zero).
|
|
40
|
+
*/
|
|
41
|
+
export function installGlobalErrorHandlers(proc = process, report = reportFatal) {
|
|
42
|
+
proc.on("unhandledRejection", (reason) =>
|
|
43
|
+
report(reason instanceof Error ? reason : new Error(String(reason))),
|
|
44
|
+
);
|
|
45
|
+
proc.on("uncaughtException", (err) =>
|
|
46
|
+
report(err instanceof Error ? err : new Error(String(err))),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -80,6 +80,27 @@ function newId() {
|
|
|
80
80
|
return `cp-${Date.now()}-${rand}`;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* A checkpoint id is interpolated into filesystem paths (root/<id>.json and the
|
|
85
|
+
* blob dir root/<id>). The id comes from CLI args (`cc checkpoint show|delete
|
|
86
|
+
* <id>`), so an id like "../../etc/passwd" would read outside the checkpoint
|
|
87
|
+
* store, and "../../important" passed to delete would rmSync outside it. The git
|
|
88
|
+
* engine guards this via sanitizeSession(); mirror it for the copy fallback by
|
|
89
|
+
* restricting the id to a single safe path segment.
|
|
90
|
+
*/
|
|
91
|
+
function isSafeCheckpointId(id) {
|
|
92
|
+
return (
|
|
93
|
+
typeof id === "string" &&
|
|
94
|
+
id.length > 0 &&
|
|
95
|
+
id !== "." &&
|
|
96
|
+
id !== ".." &&
|
|
97
|
+
!id.includes("/") &&
|
|
98
|
+
!id.includes("\\") &&
|
|
99
|
+
!id.includes(":") &&
|
|
100
|
+
!id.includes("\0")
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
/**
|
|
84
105
|
* Recursively collect regular files under an absolute path, honoring SKIP_DIRS
|
|
85
106
|
* and a running maxFiles budget. Symlinks are not followed.
|
|
@@ -146,6 +167,9 @@ export function createCheckpoint(paths, opts = {}) {
|
|
|
146
167
|
const uniqueAbs = [...new Set(absFiles)];
|
|
147
168
|
|
|
148
169
|
const id = opts.id || newId();
|
|
170
|
+
if (!isSafeCheckpointId(id)) {
|
|
171
|
+
throw new Error(`Unsafe checkpoint id (path traversal): ${id}`);
|
|
172
|
+
}
|
|
149
173
|
const blobDir = path.join(root, id);
|
|
150
174
|
ensureDir(blobDir);
|
|
151
175
|
|
|
@@ -181,6 +205,7 @@ export function createCheckpoint(paths, opts = {}) {
|
|
|
181
205
|
|
|
182
206
|
/** Load a checkpoint manifest by id, or null. */
|
|
183
207
|
export function getCheckpoint(id, opts = {}) {
|
|
208
|
+
if (!isSafeCheckpointId(id)) return null;
|
|
184
209
|
const root = opts.root || defaultRoot();
|
|
185
210
|
const file = path.join(root, `${id}.json`);
|
|
186
211
|
if (!fs.existsSync(file)) return null;
|
|
@@ -310,6 +335,7 @@ export function restoreCheckpoint(id, opts = {}) {
|
|
|
310
335
|
|
|
311
336
|
/** Delete a checkpoint (manifest + blobs). Returns true if it existed. */
|
|
312
337
|
export function deleteCheckpoint(id, opts = {}) {
|
|
338
|
+
if (!isSafeCheckpointId(id)) return false;
|
|
313
339
|
const root = opts.root || defaultRoot();
|
|
314
340
|
const file = path.join(root, `${id}.json`);
|
|
315
341
|
const blobDir = path.join(root, id);
|
|
@@ -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
|
|