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
|
@@ -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
|
+
}
|
|
@@ -43,6 +43,33 @@ function ensureDir(dir) {
|
|
|
43
43
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Write a file atomically: write to a unique temp sibling, then rename over the
|
|
48
|
+
* target. rename() is atomic within a filesystem, so a crash mid-write leaves
|
|
49
|
+
* the previous file (or nothing) intact — never a half-written manifest/blob.
|
|
50
|
+
* Without this, a checkpoint manifest truncated by a crash makes the whole
|
|
51
|
+
* checkpoint unrecoverable (getCheckpoint's JSON.parse fails → null) while its
|
|
52
|
+
* blobs are orphaned on disk. The temp lives in the SAME dir as the target so
|
|
53
|
+
* the rename stays on one filesystem; on failure the temp is best-effort
|
|
54
|
+
* removed. Temp names end in `.tmp` (never `.json`), so a leftover from a hard
|
|
55
|
+
* crash is ignored by listCheckpoints.
|
|
56
|
+
*/
|
|
57
|
+
function atomicWriteFileSync(filePath, data) {
|
|
58
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
59
|
+
const tmp = `${filePath}.${process.pid}.${rand}.tmp`;
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(tmp, data);
|
|
62
|
+
fs.renameSync(tmp, filePath);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
66
|
+
} catch {
|
|
67
|
+
/* best-effort temp cleanup */
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
function sha256(buf) {
|
|
47
74
|
return createHash("sha256").update(buf).digest("hex");
|
|
48
75
|
}
|
|
@@ -53,6 +80,27 @@ function newId() {
|
|
|
53
80
|
return `cp-${Date.now()}-${rand}`;
|
|
54
81
|
}
|
|
55
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
|
+
|
|
56
104
|
/**
|
|
57
105
|
* Recursively collect regular files under an absolute path, honoring SKIP_DIRS
|
|
58
106
|
* and a running maxFiles budget. Symlinks are not followed.
|
|
@@ -119,6 +167,9 @@ export function createCheckpoint(paths, opts = {}) {
|
|
|
119
167
|
const uniqueAbs = [...new Set(absFiles)];
|
|
120
168
|
|
|
121
169
|
const id = opts.id || newId();
|
|
170
|
+
if (!isSafeCheckpointId(id)) {
|
|
171
|
+
throw new Error(`Unsafe checkpoint id (path traversal): ${id}`);
|
|
172
|
+
}
|
|
122
173
|
const blobDir = path.join(root, id);
|
|
123
174
|
ensureDir(blobDir);
|
|
124
175
|
|
|
@@ -127,7 +178,7 @@ export function createCheckpoint(paths, opts = {}) {
|
|
|
127
178
|
const buf = fs.readFileSync(abs);
|
|
128
179
|
const hash = sha256(buf);
|
|
129
180
|
const blobPath = path.join(blobDir, hash);
|
|
130
|
-
if (!fs.existsSync(blobPath))
|
|
181
|
+
if (!fs.existsSync(blobPath)) atomicWriteFileSync(blobPath, buf);
|
|
131
182
|
files.push({
|
|
132
183
|
rel: path.relative(cwd, abs) || path.basename(abs),
|
|
133
184
|
abs,
|
|
@@ -145,16 +196,16 @@ export function createCheckpoint(paths, opts = {}) {
|
|
|
145
196
|
files,
|
|
146
197
|
};
|
|
147
198
|
ensureDir(root);
|
|
148
|
-
|
|
199
|
+
atomicWriteFileSync(
|
|
149
200
|
path.join(root, `${id}.json`),
|
|
150
201
|
JSON.stringify(manifest, null, 2),
|
|
151
|
-
"utf-8",
|
|
152
202
|
);
|
|
153
203
|
return manifest;
|
|
154
204
|
}
|
|
155
205
|
|
|
156
206
|
/** Load a checkpoint manifest by id, or null. */
|
|
157
207
|
export function getCheckpoint(id, opts = {}) {
|
|
208
|
+
if (!isSafeCheckpointId(id)) return null;
|
|
158
209
|
const root = opts.root || defaultRoot();
|
|
159
210
|
const file = path.join(root, `${id}.json`);
|
|
160
211
|
if (!fs.existsSync(file)) return null;
|
|
@@ -275,7 +326,7 @@ export function restoreCheckpoint(id, opts = {}) {
|
|
|
275
326
|
|
|
276
327
|
for (const w of toWrite) {
|
|
277
328
|
ensureDir(path.dirname(w.abs));
|
|
278
|
-
|
|
329
|
+
atomicWriteFileSync(w.abs, w.blob);
|
|
279
330
|
restored.push(w.rel);
|
|
280
331
|
}
|
|
281
332
|
|
|
@@ -284,6 +335,7 @@ export function restoreCheckpoint(id, opts = {}) {
|
|
|
284
335
|
|
|
285
336
|
/** Delete a checkpoint (manifest + blobs). Returns true if it existed. */
|
|
286
337
|
export function deleteCheckpoint(id, opts = {}) {
|
|
338
|
+
if (!isSafeCheckpointId(id)) return false;
|
|
287
339
|
const root = opts.root || defaultRoot();
|
|
288
340
|
const file = path.join(root, `${id}.json`);
|
|
289
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
|
|