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
package/src/lib/agent-core.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated Re-export shim; canonical impl is `../runtime/agent-core.js`
|
|
3
|
-
* (CLI Runtime Convergence, Phase 6b). Import from there in new code.
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
/** @deprecated Thin re-export shim; canonical impl is `../runtime/agent-core.js` (CLI Runtime Convergence, Phase 6b) — import from there in new code. */
|
|
6
2
|
export {
|
|
7
3
|
AGENT_TOOLS,
|
|
8
4
|
AGENT_TOOL_REGISTRY,
|
|
@@ -33,6 +29,7 @@ export {
|
|
|
33
29
|
_streamErrorDisposition,
|
|
34
30
|
_isRetryableStreamError,
|
|
35
31
|
_retryStreamingChat,
|
|
32
|
+
_iterateStreamWithStall,
|
|
36
33
|
_toAnthropicMessages,
|
|
37
34
|
_anthropicThinkingParams,
|
|
38
35
|
_normalizeAnthropicResponse,
|
package/src/lib/audit-mtc.js
CHANGED
|
@@ -22,6 +22,7 @@ import path from "node:path";
|
|
|
22
22
|
import crypto from "node:crypto";
|
|
23
23
|
import { ed25519 as nobleEd25519 } from "@noble/curves/ed25519.js";
|
|
24
24
|
import mtcLib from "@chainlesschain/core-mtc";
|
|
25
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
25
26
|
|
|
26
27
|
const { sha256, jcs, encodeHashStr, ed25519, assembleBatch } = mtcLib;
|
|
27
28
|
|
|
@@ -70,6 +71,30 @@ function ensureDirs(dir) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Atomically write a UTF-8 file (temp sibling + rename). Used for the config
|
|
76
|
+
* and per-event staging records: a crash mid-write would truncate config.json
|
|
77
|
+
* or leave a partial event that closeBatch silently drops as malformed. Temp
|
|
78
|
+
* names end in `.tmp` (never `.json`), so a hard-crash leftover is ignored by
|
|
79
|
+
* listStagingEvents' `.json` filter. NOTE: the issuer key (flag "wx" exclusive
|
|
80
|
+
* create) and closeBatch (whole tmp-dir rename) have their own atomicity and
|
|
81
|
+
* are intentionally left untouched.
|
|
82
|
+
*/
|
|
83
|
+
function atomicWriteFileSync(filePath, data) {
|
|
84
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
85
|
+
try {
|
|
86
|
+
fs.writeFileSync(tmp, data, "utf-8");
|
|
87
|
+
fs.renameSync(tmp, filePath);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
91
|
+
} catch {
|
|
92
|
+
/* best-effort temp cleanup */
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
// ─────────────────────────────────────────────────────────────────────
|
|
74
99
|
// Config
|
|
75
100
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -107,7 +132,7 @@ export function saveAuditMtcConfig(dir, patch) {
|
|
|
107
132
|
if (typeof merged.issuer !== "string" || !merged.issuer) {
|
|
108
133
|
throw new TypeError("config.issuer must be non-empty string");
|
|
109
134
|
}
|
|
110
|
-
|
|
135
|
+
atomicWriteFileSync(configPath(dir), JSON.stringify(merged, null, 2));
|
|
111
136
|
return merged;
|
|
112
137
|
}
|
|
113
138
|
|
|
@@ -227,7 +252,7 @@ export function emitEvent(dir, body, opts = {}) {
|
|
|
227
252
|
};
|
|
228
253
|
|
|
229
254
|
const filePath = path.join(stagingDir(dir), `${eventId}.json`);
|
|
230
|
-
|
|
255
|
+
atomicWriteFileSync(filePath, JSON.stringify(record, null, 2));
|
|
231
256
|
return { eventId, path: filePath, record };
|
|
232
257
|
}
|
|
233
258
|
|
|
@@ -293,6 +318,19 @@ function listStagingEvents(dir) {
|
|
|
293
318
|
* @param {string} [opts.issuer] - override config.issuer
|
|
294
319
|
*/
|
|
295
320
|
export function closeBatch(dir, opts = {}) {
|
|
321
|
+
// Serialize the whole staging-scan → batch-write → staging-cleanup section
|
|
322
|
+
// across processes. Without it, two concurrent closes (e.g. overlapping
|
|
323
|
+
// `cc audit mtc reconcile` runs) both scan the same staging events, allocate
|
|
324
|
+
// the same batch id, and the defensive rmSync(finalDir) lets the second
|
|
325
|
+
// silently overwrite the first's audit batch. Best-effort lock (never hangs);
|
|
326
|
+
// generous timeout since the section includes Merkle assembly + signing.
|
|
327
|
+
ensureDirs(dir);
|
|
328
|
+
return withFileLock(batchesDir(dir), () => _closeBatchImpl(dir, opts), {
|
|
329
|
+
timeoutMs: 15000,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function _closeBatchImpl(dir, opts = {}) {
|
|
296
334
|
ensureDirs(dir);
|
|
297
335
|
const cfg = loadAuditMtcConfig(dir);
|
|
298
336
|
const events = listStagingEvents(dir);
|
package/src/lib/chat-core.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
13
13
|
import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
14
|
+
import {
|
|
15
|
+
isRetryableStreamError,
|
|
16
|
+
STREAM_RETRY_MAX,
|
|
17
|
+
STREAM_RETRY_BASE_MS,
|
|
18
|
+
} from "./stream-retry.js";
|
|
14
19
|
|
|
15
20
|
// A streaming chat call must not hang forever if the API accepts the connection
|
|
16
21
|
// but then goes silent (TCP up, no bytes). Abort the request if no data arrives
|
|
@@ -19,18 +24,43 @@ import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
|
19
24
|
// generous (and env-overridable) so a slow local model's first token isn't cut
|
|
20
25
|
// off; raise CC_CHAT_STALL_MS if you run very large local models.
|
|
21
26
|
export const STREAM_STALL_MS = Number(process.env.CC_CHAT_STALL_MS) || 180000;
|
|
27
|
+
// Before the hard abort above, surface a "waiting for API response" hint once
|
|
28
|
+
// per silent gap (cc agent parity — agent-core STREAM_STALL_MS). Purely
|
|
29
|
+
// informational; never aborts. Env-overridable; disabled if it would exceed the
|
|
30
|
+
// abort threshold.
|
|
31
|
+
export const STREAM_STALL_HINT_MS =
|
|
32
|
+
Number(process.env.CC_CHAT_STALL_HINT_MS) || 20000;
|
|
22
33
|
|
|
23
|
-
function makeStallGuard(
|
|
34
|
+
function makeStallGuard(
|
|
35
|
+
stallMs = STREAM_STALL_MS,
|
|
36
|
+
{ onHint, hintMs = STREAM_STALL_HINT_MS } = {},
|
|
37
|
+
) {
|
|
24
38
|
const controller = new AbortController();
|
|
25
39
|
let timer = null;
|
|
40
|
+
let hintTimer = null;
|
|
41
|
+
const hintArmed =
|
|
42
|
+
typeof onHint === "function" && hintMs > 0 && hintMs < stallMs;
|
|
26
43
|
const bump = () => {
|
|
27
44
|
if (timer) clearTimeout(timer);
|
|
28
45
|
timer = setTimeout(() => controller.abort(), stallMs);
|
|
29
46
|
if (timer && typeof timer.unref === "function") timer.unref();
|
|
47
|
+
if (hintArmed) {
|
|
48
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
49
|
+
hintTimer = setTimeout(() => {
|
|
50
|
+
try {
|
|
51
|
+
onHint(hintMs);
|
|
52
|
+
} catch {
|
|
53
|
+
/* hint is best-effort — never affect the stream */
|
|
54
|
+
}
|
|
55
|
+
}, hintMs);
|
|
56
|
+
if (hintTimer && typeof hintTimer.unref === "function") hintTimer.unref();
|
|
57
|
+
}
|
|
30
58
|
};
|
|
31
59
|
const stop = () => {
|
|
32
60
|
if (timer) clearTimeout(timer);
|
|
61
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
33
62
|
timer = null;
|
|
63
|
+
hintTimer = null;
|
|
34
64
|
};
|
|
35
65
|
return {
|
|
36
66
|
signal: controller.signal,
|
|
@@ -40,13 +70,35 @@ function makeStallGuard(stallMs = STREAM_STALL_MS) {
|
|
|
40
70
|
};
|
|
41
71
|
}
|
|
42
72
|
|
|
73
|
+
// OpenAI-compatible cached-prompt-token count (mirrors agent-core's
|
|
74
|
+
// _openaiCachedTokens). OpenAI/volcengine report it as
|
|
75
|
+
// usage.prompt_tokens_details.cached_tokens; DeepSeek as
|
|
76
|
+
// usage.prompt_cache_hit_tokens. prompt_tokens INCLUDES the cached count, so
|
|
77
|
+
// callers subtract it to recover uncached input (avoids over-pricing in
|
|
78
|
+
// `cc cost` for cc chat sessions).
|
|
79
|
+
function _cachedPromptTokens(usage) {
|
|
80
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
81
|
+
const detailed = Number(usage.prompt_tokens_details?.cached_tokens);
|
|
82
|
+
if (Number.isFinite(detailed) && detailed > 0) return detailed;
|
|
83
|
+
const deepseek = Number(usage.prompt_cache_hit_tokens);
|
|
84
|
+
if (Number.isFinite(deepseek) && deepseek > 0) return deepseek;
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
43
88
|
/**
|
|
44
89
|
* Stream a response from Ollama.
|
|
45
90
|
* If `onUsage` is provided, it's called with `{inputTokens, outputTokens}`
|
|
46
91
|
* derived from Ollama's terminal `prompt_eval_count` / `eval_count` fields.
|
|
47
92
|
*/
|
|
48
|
-
export async function streamOllama(
|
|
49
|
-
|
|
93
|
+
export async function streamOllama(
|
|
94
|
+
messages,
|
|
95
|
+
model,
|
|
96
|
+
baseUrl,
|
|
97
|
+
onToken,
|
|
98
|
+
onUsage,
|
|
99
|
+
onStall,
|
|
100
|
+
) {
|
|
101
|
+
const guard = makeStallGuard(STREAM_STALL_MS, { onHint: onStall });
|
|
50
102
|
guard.bump();
|
|
51
103
|
let response;
|
|
52
104
|
try {
|
|
@@ -129,8 +181,9 @@ export async function streamOpenAI(
|
|
|
129
181
|
apiKey,
|
|
130
182
|
onToken,
|
|
131
183
|
onUsage,
|
|
184
|
+
onStall,
|
|
132
185
|
) {
|
|
133
|
-
const guard = makeStallGuard();
|
|
186
|
+
const guard = makeStallGuard(STREAM_STALL_MS, { onHint: onStall });
|
|
134
187
|
guard.bump();
|
|
135
188
|
let response;
|
|
136
189
|
try {
|
|
@@ -189,10 +242,14 @@ export async function streamOpenAI(
|
|
|
189
242
|
onToken(content);
|
|
190
243
|
}
|
|
191
244
|
if (json.usage && onUsage) {
|
|
192
|
-
|
|
245
|
+
// Split cached prompt tokens out of prompt_tokens so cc cost
|
|
246
|
+
// prices the cached prefix correctly (matches agent-core).
|
|
247
|
+
const cacheReadTokens = _cachedPromptTokens(json.usage);
|
|
248
|
+
const prompt = Number(json.usage.prompt_tokens) || 0;
|
|
249
|
+
const inputTokens = Math.max(0, prompt - cacheReadTokens);
|
|
193
250
|
const outputTokens = Number(json.usage.completion_tokens) || 0;
|
|
194
|
-
if (inputTokens || outputTokens) {
|
|
195
|
-
onUsage({ inputTokens, outputTokens });
|
|
251
|
+
if (inputTokens || outputTokens || cacheReadTokens) {
|
|
252
|
+
onUsage({ inputTokens, outputTokens, cacheReadTokens });
|
|
196
253
|
}
|
|
197
254
|
}
|
|
198
255
|
} catch {
|
|
@@ -224,6 +281,7 @@ export async function streamAnthropic(
|
|
|
224
281
|
apiKey,
|
|
225
282
|
onToken,
|
|
226
283
|
onUsage,
|
|
284
|
+
onStall,
|
|
227
285
|
) {
|
|
228
286
|
// Split out a leading system prompt (Anthropic requires it as top-level
|
|
229
287
|
// `system`, not an OpenAI-style role=system message).
|
|
@@ -237,7 +295,7 @@ export async function streamAnthropic(
|
|
|
237
295
|
}
|
|
238
296
|
}
|
|
239
297
|
|
|
240
|
-
const guard = makeStallGuard();
|
|
298
|
+
const guard = makeStallGuard(STREAM_STALL_MS, { onHint: onStall });
|
|
241
299
|
guard.bump();
|
|
242
300
|
let response;
|
|
243
301
|
try {
|
|
@@ -279,6 +337,8 @@ export async function streamAnthropic(
|
|
|
279
337
|
let buf = "";
|
|
280
338
|
let inputTokens = 0;
|
|
281
339
|
let outputTokens = 0;
|
|
340
|
+
let cacheReadTokens = 0;
|
|
341
|
+
let cacheCreationTokens = 0;
|
|
282
342
|
|
|
283
343
|
try {
|
|
284
344
|
while (true) {
|
|
@@ -302,10 +362,13 @@ export async function streamAnthropic(
|
|
|
302
362
|
onToken(delta);
|
|
303
363
|
}
|
|
304
364
|
} else if (obj.type === "message_start") {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
outputTokens =
|
|
308
|
-
|
|
365
|
+
const u = obj.message?.usage || {};
|
|
366
|
+
inputTokens = Number(u.input_tokens) || inputTokens;
|
|
367
|
+
outputTokens = Number(u.output_tokens) || outputTokens;
|
|
368
|
+
cacheReadTokens =
|
|
369
|
+
Number(u.cache_read_input_tokens) || cacheReadTokens;
|
|
370
|
+
cacheCreationTokens =
|
|
371
|
+
Number(u.cache_creation_input_tokens) || cacheCreationTokens;
|
|
309
372
|
} else if (obj.type === "message_delta") {
|
|
310
373
|
outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
|
|
311
374
|
}
|
|
@@ -324,8 +387,16 @@ export async function streamAnthropic(
|
|
|
324
387
|
guard.stop();
|
|
325
388
|
}
|
|
326
389
|
|
|
327
|
-
if (
|
|
328
|
-
onUsage
|
|
390
|
+
if (
|
|
391
|
+
onUsage &&
|
|
392
|
+
(inputTokens || outputTokens || cacheReadTokens || cacheCreationTokens)
|
|
393
|
+
) {
|
|
394
|
+
onUsage({
|
|
395
|
+
inputTokens,
|
|
396
|
+
outputTokens,
|
|
397
|
+
cacheReadTokens,
|
|
398
|
+
cacheCreationTokens,
|
|
399
|
+
});
|
|
329
400
|
}
|
|
330
401
|
|
|
331
402
|
return fullResponse;
|
|
@@ -362,7 +433,12 @@ export async function* chatStream(messages, options) {
|
|
|
362
433
|
waiter = null;
|
|
363
434
|
w();
|
|
364
435
|
};
|
|
436
|
+
// Count of tokens the provider has actually emitted this turn. A connection
|
|
437
|
+
// drop is only safe to retry while this is 0 — once any token has been
|
|
438
|
+
// streamed, re-issuing would duplicate visible output.
|
|
439
|
+
let tokensEmitted = 0;
|
|
365
440
|
const onToken = (token) => {
|
|
441
|
+
tokensEmitted++;
|
|
366
442
|
queue.push(token);
|
|
367
443
|
wake();
|
|
368
444
|
};
|
|
@@ -376,7 +452,7 @@ export async function* chatStream(messages, options) {
|
|
|
376
452
|
let providerCall;
|
|
377
453
|
if (provider === "ollama") {
|
|
378
454
|
providerCall = () =>
|
|
379
|
-
streamOllama(messages, model, baseUrl, onToken, onUsage);
|
|
455
|
+
streamOllama(messages, model, baseUrl, onToken, onUsage, options.onStall);
|
|
380
456
|
} else if (provider === "anthropic") {
|
|
381
457
|
const providerDef = BUILT_IN_PROVIDERS.anthropic;
|
|
382
458
|
const url =
|
|
@@ -392,7 +468,15 @@ export async function* chatStream(messages, options) {
|
|
|
392
468
|
);
|
|
393
469
|
}
|
|
394
470
|
providerCall = () =>
|
|
395
|
-
streamAnthropic(
|
|
471
|
+
streamAnthropic(
|
|
472
|
+
messages,
|
|
473
|
+
model,
|
|
474
|
+
url,
|
|
475
|
+
key,
|
|
476
|
+
onToken,
|
|
477
|
+
onUsage,
|
|
478
|
+
options.onStall,
|
|
479
|
+
);
|
|
396
480
|
} else {
|
|
397
481
|
const providerDef = BUILT_IN_PROVIDERS[provider];
|
|
398
482
|
const url =
|
|
@@ -408,7 +492,15 @@ export async function* chatStream(messages, options) {
|
|
|
408
492
|
);
|
|
409
493
|
}
|
|
410
494
|
providerCall = () =>
|
|
411
|
-
streamOpenAI(
|
|
495
|
+
streamOpenAI(
|
|
496
|
+
messages,
|
|
497
|
+
model,
|
|
498
|
+
url,
|
|
499
|
+
key,
|
|
500
|
+
onToken,
|
|
501
|
+
onUsage,
|
|
502
|
+
options.onStall,
|
|
503
|
+
);
|
|
412
504
|
}
|
|
413
505
|
|
|
414
506
|
// Run the provider stream concurrently with the queue-drain loop. finally
|
|
@@ -416,7 +508,36 @@ export async function* chatStream(messages, options) {
|
|
|
416
508
|
// any token still terminates the generator cleanly.
|
|
417
509
|
const streamPromise = (async () => {
|
|
418
510
|
try {
|
|
419
|
-
|
|
511
|
+
let attempt = 0;
|
|
512
|
+
for (;;) {
|
|
513
|
+
try {
|
|
514
|
+
return await providerCall();
|
|
515
|
+
} catch (err) {
|
|
516
|
+
// Retry ONLY a transient connection drop that hit before any token
|
|
517
|
+
// reached the user (tokensEmitted === 0) — otherwise the re-issue
|
|
518
|
+
// would duplicate the visible answer. Mirrors agent-core's
|
|
519
|
+
// zero-output retry safety (cc agent parity). Stall aborts (180s) and
|
|
520
|
+
// HTTP/auth errors are not retryable and surface immediately.
|
|
521
|
+
if (
|
|
522
|
+
attempt >= STREAM_RETRY_MAX ||
|
|
523
|
+
tokensEmitted > 0 ||
|
|
524
|
+
!isRetryableStreamError(err)
|
|
525
|
+
) {
|
|
526
|
+
throw err;
|
|
527
|
+
}
|
|
528
|
+
attempt++;
|
|
529
|
+
if (typeof options.onStreamRetry === "function") {
|
|
530
|
+
try {
|
|
531
|
+
options.onStreamRetry(attempt, err);
|
|
532
|
+
} catch {
|
|
533
|
+
/* retry notice is best-effort */
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
await new Promise((r) =>
|
|
537
|
+
setTimeout(r, STREAM_RETRY_BASE_MS * 2 ** (attempt - 1)),
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
420
541
|
} finally {
|
|
421
542
|
done = true;
|
|
422
543
|
wake();
|
|
@@ -451,6 +572,8 @@ export async function* chatStream(messages, options) {
|
|
|
451
572
|
usage: {
|
|
452
573
|
input_tokens: capturedUsage.inputTokens,
|
|
453
574
|
output_tokens: capturedUsage.outputTokens,
|
|
575
|
+
cache_read_input_tokens: capturedUsage.cacheReadTokens || 0,
|
|
576
|
+
cache_creation_input_tokens: capturedUsage.cacheCreationTokens || 0,
|
|
454
577
|
},
|
|
455
578
|
});
|
|
456
579
|
} catch {
|
|
@@ -120,6 +120,11 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
120
120
|
const startTime = Date.now();
|
|
121
121
|
const outputChunks = [];
|
|
122
122
|
const errorChunks = [];
|
|
123
|
+
// Streaming decoders so a multi-byte UTF-8 char split across two stdout/
|
|
124
|
+
// stderr chunks is reassembled, not corrupted into U+FFFD (in both the
|
|
125
|
+
// live `output` emit and the final join).
|
|
126
|
+
const outDecoder = new TextDecoder("utf-8");
|
|
127
|
+
const errDecoder = new TextDecoder("utf-8");
|
|
123
128
|
let timedOut = false;
|
|
124
129
|
|
|
125
130
|
const proc = _deps.spawn(this.cliCommand, args, {
|
|
@@ -145,13 +150,20 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
145
150
|
}, timeout);
|
|
146
151
|
|
|
147
152
|
proc.stdout.on("data", (data) => {
|
|
148
|
-
const chunk =
|
|
153
|
+
const chunk =
|
|
154
|
+
typeof data === "string"
|
|
155
|
+
? data
|
|
156
|
+
: outDecoder.decode(data, { stream: true });
|
|
149
157
|
outputChunks.push(chunk);
|
|
150
158
|
this.emit("output", { agentId: this.id, chunk });
|
|
151
159
|
});
|
|
152
160
|
|
|
153
161
|
proc.stderr.on("data", (data) => {
|
|
154
|
-
errorChunks.push(
|
|
162
|
+
errorChunks.push(
|
|
163
|
+
typeof data === "string"
|
|
164
|
+
? data
|
|
165
|
+
: errDecoder.decode(data, { stream: true }),
|
|
166
|
+
);
|
|
155
167
|
});
|
|
156
168
|
|
|
157
169
|
proc.on("close", (code) => {
|
|
@@ -258,6 +258,11 @@ module.exports = {
|
|
|
258
258
|
async execute(task, context) {
|
|
259
259
|
const input = (task?.params?.input || task?.action || "").trim();
|
|
260
260
|
if (!input) return { success: false, error: "No input provided" };
|
|
261
|
+
// Security: input is interpolated into a shell command, so reject shell
|
|
262
|
+
// metacharacters to prevent command injection from untrusted skill input.
|
|
263
|
+
if (/[;&|\`$<>()\\n\\r]/.test(input)) {
|
|
264
|
+
return { success: false, error: "Input contains unsafe shell characters; refused." };
|
|
265
|
+
}
|
|
261
266
|
try {
|
|
262
267
|
const output = execSync(\`${command} \${input}\`, {
|
|
263
268
|
encoding: "utf-8",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-numeric — validated parsing for user-supplied numeric CLI options.
|
|
3
|
+
*
|
|
4
|
+
* `parseInt`/`Number` on a bad arg yields NaN that silently corrupts whatever
|
|
5
|
+
* consumes it (a `LIMIT NaN`, a `Date.now()+NaN` deadline, a stored NaN config).
|
|
6
|
+
* This centralizes parse → validate → fall-back-or-throw so a command either
|
|
7
|
+
* fails LOUDLY with a clear message or falls back to a sane default — never
|
|
8
|
+
* threads NaN downstream.
|
|
9
|
+
*
|
|
10
|
+
* @param {*} raw the option value (string from commander, or undefined)
|
|
11
|
+
* @param {object} [opts]
|
|
12
|
+
* @param {string} [opts.name="value"] label for error messages (e.g. "--limit")
|
|
13
|
+
* @param {boolean} [opts.integer=false] parse as integer (parseInt) vs float (Number)
|
|
14
|
+
* @param {number} [opts.min] inclusive lower bound
|
|
15
|
+
* @param {number} [opts.max] inclusive upper bound
|
|
16
|
+
* @param {number} [opts.fallback] returned for missing/invalid input; when
|
|
17
|
+
* omitted, invalid input THROWS instead
|
|
18
|
+
* @returns {number}
|
|
19
|
+
*/
|
|
20
|
+
export function numericOption(raw, opts = {}) {
|
|
21
|
+
const { name = "value", integer = false, min, max } = opts;
|
|
22
|
+
const hasFallback = Object.prototype.hasOwnProperty.call(opts, "fallback");
|
|
23
|
+
const fail = (msg) => {
|
|
24
|
+
if (hasFallback) return opts.fallback;
|
|
25
|
+
throw new Error(msg);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (raw == null || raw === "") return fail(`${name} is required`);
|
|
29
|
+
const n = integer ? parseInt(raw, 10) : Number(raw);
|
|
30
|
+
if (!Number.isFinite(n)) {
|
|
31
|
+
return fail(
|
|
32
|
+
`${name} must be a ${integer ? "whole " : ""}number (got ${JSON.stringify(String(raw))})`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (min != null && n < min) return fail(`${name} must be >= ${min}`);
|
|
36
|
+
if (max != null && n > max) return fail(`${name} must be <= ${max}`);
|
|
37
|
+
return n;
|
|
38
|
+
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
readFileSync,
|
|
3
|
+
writeFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
} from "node:fs";
|
|
2
9
|
import { dirname } from "node:path";
|
|
3
10
|
import { getConfigPath } from "./paths.js";
|
|
4
11
|
import { DEFAULT_CONFIG } from "../constants.js";
|
|
12
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
5
13
|
|
|
6
14
|
export function loadConfig() {
|
|
7
15
|
const configPath = getConfigPath();
|
|
@@ -20,7 +28,24 @@ export function loadConfig() {
|
|
|
20
28
|
export function saveConfig(config) {
|
|
21
29
|
const configPath = getConfigPath();
|
|
22
30
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
23
|
-
|
|
31
|
+
// Atomic write: config.json holds API keys + LLM settings, so a crash (or two
|
|
32
|
+
// concurrent `cc config set`) mid-write must never leave a truncated/corrupt
|
|
33
|
+
// file — that would break every cc command and could lose credentials. Write a
|
|
34
|
+
// temp sibling, then rename over the target (rename is atomic within a
|
|
35
|
+
// filesystem), so a reader/crash sees either the old file or the complete new
|
|
36
|
+
// one, never a half-written one.
|
|
37
|
+
const tmp = `${configPath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
38
|
+
try {
|
|
39
|
+
writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
40
|
+
renameSync(tmp, configPath);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
try {
|
|
43
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
44
|
+
} catch {
|
|
45
|
+
/* best-effort temp cleanup */
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
24
49
|
}
|
|
25
50
|
|
|
26
51
|
export function getConfigValue(key) {
|
|
@@ -29,10 +54,16 @@ export function getConfigValue(key) {
|
|
|
29
54
|
}
|
|
30
55
|
|
|
31
56
|
export function setConfigValue(key, value) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
// Serialize the read-modify-write across processes: two concurrent
|
|
58
|
+
// `cc config set` invocations would otherwise each load, mutate, and write
|
|
59
|
+
// back, silently losing one update. Best-effort lock (see with-file-lock):
|
|
60
|
+
// on contention timeout it proceeds anyway, so the CLI never hangs.
|
|
61
|
+
return withFileLock(getConfigPath(), () => {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
setNestedValue(config, key, parseValue(value));
|
|
64
|
+
saveConfig(config);
|
|
65
|
+
return config;
|
|
66
|
+
});
|
|
36
67
|
}
|
|
37
68
|
|
|
38
69
|
export function resetConfig() {
|
|
@@ -160,13 +160,17 @@ REASON: (1-2 sentence justification)`;
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
function parseScores(text, variants, criteria) {
|
|
163
|
+
export function parseScores(text, variants, criteria) {
|
|
164
164
|
const scores = [];
|
|
165
165
|
for (let i = 0; i < variants.length; i++) {
|
|
166
166
|
const variantScores = {};
|
|
167
167
|
for (const c of criteria) {
|
|
168
|
+
// The criterion is matched LITERALLY — escape regex metacharacters so a
|
|
169
|
+
// name like "latency(ms)" or "f.score" doesn't silently mis-parse (and an
|
|
170
|
+
// unbalanced one like "a)b(" doesn't throw and crash the whole compare).
|
|
171
|
+
const cEsc = String(c).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
168
172
|
const pattern = new RegExp(
|
|
169
|
-
`variant\\s*${i + 1}[^\\n]*${
|
|
173
|
+
`variant\\s*${i + 1}[^\\n]*${cEsc}\\s*=\\s*(\\d+)`,
|
|
170
174
|
"i",
|
|
171
175
|
);
|
|
172
176
|
const match = text.match(pattern);
|
package/src/lib/cowork-cron.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import crypto from "node:crypto";
|
|
33
33
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
34
34
|
import { join } from "node:path";
|
|
35
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
35
36
|
|
|
36
37
|
export const _deps = {
|
|
37
38
|
existsSync,
|
|
@@ -260,49 +261,59 @@ export function addSchedule(cwd, input) {
|
|
|
260
261
|
const err = validateCron(cron);
|
|
261
262
|
if (err) throw new Error(`invalid cron: ${err}`);
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
264
|
+
// Serialize the read-modify-write across processes (cc+cc / cc+desktop share
|
|
265
|
+
// the cowork dir) so concurrently-added schedules don't clobber each other.
|
|
266
|
+
return withFileLock(_scheduleFile(cwd), () => {
|
|
267
|
+
const schedules = loadSchedules(cwd);
|
|
268
|
+
const entry = {
|
|
269
|
+
id: `sch-${crypto.randomUUID().slice(0, 12)}`,
|
|
270
|
+
cron: cron.trim(),
|
|
271
|
+
templateId,
|
|
272
|
+
userMessage,
|
|
273
|
+
files: Array.isArray(files) ? files : [],
|
|
274
|
+
enabled: true,
|
|
275
|
+
createdAt: _deps.now().toISOString(),
|
|
276
|
+
lastRunAt: null,
|
|
277
|
+
lastStatus: null,
|
|
278
|
+
};
|
|
279
|
+
schedules.push(entry);
|
|
280
|
+
saveSchedules(cwd, schedules);
|
|
281
|
+
return entry;
|
|
282
|
+
});
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
export function removeSchedule(cwd, id) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
return withFileLock(_scheduleFile(cwd), () => {
|
|
287
|
+
const schedules = loadSchedules(cwd);
|
|
288
|
+
const idx = schedules.findIndex((s) => s.id === id);
|
|
289
|
+
if (idx === -1) return false;
|
|
290
|
+
schedules.splice(idx, 1);
|
|
291
|
+
saveSchedules(cwd, schedules);
|
|
292
|
+
return true;
|
|
293
|
+
});
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
export function setScheduleEnabled(cwd, id, enabled) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
return withFileLock(_scheduleFile(cwd), () => {
|
|
298
|
+
const schedules = loadSchedules(cwd);
|
|
299
|
+
const s = schedules.find((x) => x.id === id);
|
|
300
|
+
if (!s) return false;
|
|
301
|
+
s.enabled = !!enabled;
|
|
302
|
+
saveSchedules(cwd, schedules);
|
|
303
|
+
return true;
|
|
304
|
+
});
|
|
296
305
|
}
|
|
297
306
|
|
|
298
307
|
export function updateScheduleRunState(cwd, id, { lastRunAt, lastStatus }) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
308
|
+
return withFileLock(_scheduleFile(cwd), () => {
|
|
309
|
+
const schedules = loadSchedules(cwd);
|
|
310
|
+
const s = schedules.find((x) => x.id === id);
|
|
311
|
+
if (!s) return false;
|
|
312
|
+
if (lastRunAt) s.lastRunAt = lastRunAt;
|
|
313
|
+
if (lastStatus) s.lastStatus = lastStatus;
|
|
314
|
+
saveSchedules(cwd, schedules);
|
|
315
|
+
return true;
|
|
316
|
+
});
|
|
306
317
|
}
|
|
307
318
|
|
|
308
319
|
// ─── Scheduler ───────────────────────────────────────────────────────────────
|