chainlesschain 0.162.95 → 0.162.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/chainlesschain.js +8 -18
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DLvvkb9x.js → AIOps-Bo9Ux24z.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxhQTwqd.js → ActionButton-CExgXSQT.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C4oUXzhG.js → Analytics-BNnZhRTZ.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CjzKJdv3.js → AppLayout-D9xfqFCT.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-B-MrugUs.js → Audit-Cpgu8uCw.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ecIfEpIZ.js → Backup-CHdEJnNE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUcSfWnx.js → BaseInput-Cbw-PxBq.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DIc1n_Lk.js → Chat-ChHTZK_6.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BGzg7oVA.js → ChatBubbleRenderer-Dl6z6ZH7.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQ3Ueff-.js → Checkbox-Cj8d9bru.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvSGLnqE.js → Codegen-C2YobW4j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Br9IXZcE.js → Col-CxfV5FGT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tPuLOf0r.js → Community-CiwOASMj.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-vMWH73BE.js → Compact-CTg_2iKW.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ct9z699x.js → Compliance-HMbsTmtw.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bxs1kL1y.js → Cowork-DyNDqGR2.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-QEpr2k4U.js → Cron-9-x5M7qy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-u4xbUw6O.js → Crosschain-4msgDaYJ.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DXTEvyrW.js → DID--2Beb5Po.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C4pZgzkg.js → Dashboard-T6nq0Lzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-LRuqREGe.js → Dropdown-D_Q6T10v.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D7XI1yBM.js → EmailListRenderer-TnN21bJx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-3y_SuAMQ.js → FamilyGuardDashboard-kky_uIij.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DbjbJJCW.js → Federation-C1GkNY4B.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-WPpMZcUw.js → FormItemContext-5-_Fc5YV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-nNT9Tn4E.js → GenericCardRenderer-CmHiB7sr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DqDaVzwV.js → Git-Cuci_BPI.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BquBZpcg.js → Governance-BU0cO2LD.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RcPPYijs.js → Inference-BDFNgEcx.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ByXfZV13.js → KnowledgeGraph-DITXUzHz.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CHT3DUcq.js → Logs-Ea-07CRd.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Bi0HJ9pN.js → Marketplace-D6OBbRVG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-QCBxPRRc.js → McpTools-eiKpIJMi.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D4RlxGXn.js → Memory-M-X-T8UF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CU_PV7e-.js → MobileBridge-xcZb9Ekv.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-c1CMFD8z.js → MobileProjects-DCfoTPRC.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BezDB1-s.js → Mtc-BWfGDzD8.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CgLpiHpE.js → MtcAudit-imBuld-U.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-vx6bz-bA.js → Multisig-C2kcsq7I.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CGOagg3-.js → NLProgramming-DObvXSXP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BcuDGYRS.js → Notes-C-6OGJMe.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-ZAGCDUpc.js → NotificationSettings-ub7Ozxci.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-B5onE0eX.js → OrderTableRenderer-CR2W89Vk.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-y_y20-5Y.js → Organization-DNA_Wty7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BEICS31D.js → Overflow-rT4Wb4rQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D_WRMzh4.js → P2P-CK-hARKv.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-C2pbtNbT.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-3aqCc_4f.js → Permissions-B8EIEqGJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DT3R9g9Y.js → PersonalDataHub-BxNVNU3s.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-MiMWIhw0.js → Pipeline-CT9pbkyY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C3JsLuDa.js → Privacy-DhCNd2CS.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-EhHVi1ff.js → ProjectInit-Qngnx-vQ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D_dNEA37.js → ProjectSettings-Dfn-nQc1.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BKdiSaPR.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CPEhi2iZ.js → Providers-4o9B_n-X.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BFQpMleX.js → QuickAsk-bs1qvF30.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DzbovRC8.js → Recommend-CuEnKA2K.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoBeV3TD.js → Reputation-DbfvmRb2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DthhwX_B.js → Row-DvMJGAI3.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dsf5U0lP.js → RssFeed-oJ0BZvmA.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CJPB8kKl.js → Search-DnIVrY9O.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DI9MfpdD.js → Security-CIWhI_Tc.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BdG0TFGf.js → Services-DLrnpjwm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DSYazY_B.js → Skeleton-CYmqgtVb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BD309VTT.js → Skills-BUzCkoeu.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CaUlw4_6.js → Sla-DYRE4bnS.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZAPkR7H.js → SpeechSettings-_6MurdOg.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFRSowN3.js → SyncSettings-UIdnF5Li.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Di8EOluc.js → Tasks-COx2CjT8.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B5FTziwi.js → Templates-BcHAmkZo.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Dj3XYfBg.js → Tenant-CjYQS8yZ.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CznH_rJT.js → Terminal-BmfOPcWt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DTHzaXPt.js → TimelineRenderer-D5HuarNQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bo56nuML.js → Tokens-C_fbProA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-pyVUVBwb.js → Trigger-JPulpRx8.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cp7jMkKN.js → Trust-CnO-062O.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aPcZCdpz.js → UkeySign-HpNovvau.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DAv5j--w.js → VideoEditing-yMncxH7y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DRhK7IDR.js → Wallet-Cug-t7Np.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2yQJuq8.js → WebAuthn--U1-NR-3.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D98DhR54.js → WorkflowEditor-R-XvSJ3S.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BXrRNi8C.js → chat-CCI1i6Rp.js} +1 -1
- package/src/assets/web-panel/assets/{colors-jQ94T_qn.js → colors-C-mqoaLt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-KqUJLlF2.js → compact-item-De_N2iRR.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DxM3zuQW.js → createContext-DbTM6sjZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-OcLPHSl5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BGm946KV.js → hasIn-D0Adylyc.js} +1 -1
- package/src/assets/web-panel/assets/index-7E_mHkDU.js +1 -0
- package/src/assets/web-panel/assets/{index-Dib0FsTT.js → index-AY937BxS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5NQai7O.js → index-B6hgy5VC.js} +1 -1
- package/src/assets/web-panel/assets/{index-LWTZAud0.js → index-B7XnpLHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXMZLfd8.js → index-B7gw-tDm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTUs9t5F.js → index-BBgDgkwW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce-YoL4x.js → index-BMmeiw88.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaU_fSK7.js → index-BU6XtnSx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMp41Q1Y.js → index-Bi7VUG0R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQvgx2e.js → index-BtE3cw9m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWcd5zDG.js → index-C9ej-MF0.js} +3 -3
- package/src/assets/web-panel/assets/{index-BbS0cBU9.js → index-CIEiQl1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VWL6Lc.js → index-CKgaW9E5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzsaWQy.js → index-CY0KKR5f.js} +1 -1
- package/src/assets/web-panel/assets/index-CbV3-WlB.js +1 -0
- package/src/assets/web-panel/assets/{index-DSzb9VOG.js → index-Co1UkcFX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-2HpfWo.js → index-CzP5d5LW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboUBf6R.js → index-D0NJ5lRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZMzj4ay.js → index-D0gBsqTX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CghRL1dh.js → index-D5F6shtn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_SFSix.js → index-DVtamtlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-T4UJiTi3.js → index-DWT18hDG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CreXAr5Y.js → index-DaXb4B9P.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Z2m28C.js → index-DjQA0Vez.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crw76vIF.js → index-Ds8NJVEL.js} +1 -1
- package/src/assets/web-panel/assets/{index-mZNLT2SP.js → index-DuP-8HWu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEZfi5D.js → index-DwLTiJ31.js} +1 -1
- package/src/assets/web-panel/assets/{index-1P0sXNhG.js → index-T3VhH8cO.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4b7KjRq.js → index-TDY1o0w3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOYcemym.js → index-TRy2Gc3V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAPk6ntc.js → index-W1OMxCmI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dgzw6eKr.js → index-ftFRZbuc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sBLech.js → index-iGPGaORA.js} +1 -1
- package/src/assets/web-panel/assets/{index-FSYz5aww.js → index-jhy5KQvg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpVYD4j.js → index-qUSZJcgw.js} +1 -1
- package/src/assets/web-panel/assets/{index-yavsyHif.js → index-w3f6lqkD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfE3jxrT.js → index-xdMc71Kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-aOLXuHpG.js → index-xhX5eg2Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGjCjsf1.js → index-zkTiW6d6.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDoZJI7g.js → initDefaultProps-CIHqouWk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-sceLjgp-.js → motion-D1iUKftk.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTWQpFa7.js → move-YOpWjmjL.js} +1 -1
- package/src/assets/web-panel/assets/{omit-dr-NCrHZ.js → omit-C_1357UY.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DK-GX_Z2.js → pickAttrs-CDb4dq6o.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIPf--18.js → placementArrow-D-OGYyhd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BzrWT5tV.js → responsiveObserve-hS-j69A0.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D5XhxlET.js → slide-tnjuvphU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-6DI-k-9E.js → statusUtils-BERfMMcU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DMw-eyn9.js → styleChecker-DKpl2K9E.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D4oVdliM.js → useFlexGapSupport-C_sX4D3N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3PPZnvy.js → useFs-DBcxC1vK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Gh5ba4KP.js → usePersonalDataHub-CeoDf2XI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEM1u4x.js → vnode-DC9Pmj_u.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClT_-9RZ.js → zoom-CZA58J7M.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/activitypub.js +19 -3
- package/src/commands/agent.js +25 -0
- package/src/commands/ask.js +13 -0
- package/src/commands/config.js +17 -1
- package/src/commands/cost.js +6 -1
- package/src/commands/dao.js +16 -3
- package/src/commands/dlp.js +7 -1
- package/src/commands/hub.js +13 -0
- package/src/commands/mtc.js +37 -1
- package/src/gateways/discord/discord-formatter.js +18 -1
- package/src/gateways/telegram/telegram-formatter.js +6 -3
- package/src/gateways/ws/ws-server.js +19 -1
- package/src/harness/plugin-manager.js +20 -0
- package/src/harness/worktree-isolator.js +17 -1
- package/src/lib/__tests__/model-deprecation.test.js +194 -0
- package/src/lib/a2a-protocol.js +23 -9
- package/src/lib/agent-core.js +2 -5
- package/src/lib/audit-mtc.js +40 -2
- package/src/lib/chat-core.js +141 -18
- package/src/lib/cli-anything-bridge.js +5 -0
- package/src/lib/cli-numeric.js +38 -0
- package/src/lib/config-manager.js +37 -6
- package/src/lib/cowork/ab-comparator-cli.js +6 -2
- package/src/lib/cowork-cron.js +45 -34
- package/src/lib/cowork-learning.js +31 -1
- package/src/lib/cowork-share.js +39 -3
- package/src/lib/cowork-template-marketplace.js +54 -2
- package/src/lib/cowork-workflow.js +33 -6
- package/src/lib/cross-chain-mtc.js +41 -12
- package/src/lib/crypto-manager.js +12 -2
- package/src/lib/dbevo.js +19 -8
- package/src/lib/downloader.js +24 -9
- package/src/lib/evomap-governance.js +4 -4
- package/src/lib/evomap-manager.js +54 -7
- package/src/lib/fatal-handler.js +48 -0
- package/src/lib/file-checkpoint.js +26 -0
- package/src/lib/git-integration.js +28 -0
- package/src/lib/goal-store.js +36 -10
- package/src/lib/hierarchical-memory.js +6 -0
- package/src/lib/llm-pricing.js +52 -2
- package/src/lib/mcp-oauth.js +37 -7
- package/src/lib/memory-manager.js +23 -2
- package/src/lib/model-deprecation.js +249 -0
- package/src/lib/pdh-feedback-ledger.js +131 -0
- package/src/lib/permanent-memory.js +30 -2
- package/src/lib/permission-rules.cjs +11 -3
- package/src/lib/personal-data-hub-aichat-wizard.js +33 -3
- package/src/lib/personal-data-hub-wiring.js +36 -13
- package/src/lib/project-instructions.js +10 -4
- package/src/lib/runnable-provider.js +62 -31
- package/src/lib/session-usage.js +34 -1
- package/src/lib/skill-loader.js +4 -1
- package/src/lib/skill-packs/generator.js +25 -2
- package/src/lib/stream-retry.js +56 -0
- package/src/lib/sync-credentials.js +53 -17
- package/src/lib/token-tracker.js +12 -2
- package/src/lib/with-file-lock.js +87 -0
- package/src/repl/agent-repl.js +39 -7
- package/src/repl/chat-repl.js +10 -0
- package/src/repl/session-cost.js +31 -3
- package/src/runtime/agent-core.js +474 -53
- package/src/runtime/coding-agent-contract-shared.cjs +58 -1
- package/src/runtime/coding-agent-policy.cjs +10 -0
- package/src/runtime/file-ref-expander.js +29 -1
- package/src/runtime/headless-runner.js +12 -1
- package/src/runtime/headless-stream.js +131 -5
- package/src/skills/video-editing/tools/commit.js +15 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C8f5NbbI.js +0 -7
- package/src/assets/web-panel/assets/Projects-CXydUXp8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bo1r5_zX.js +0 -1
- package/src/assets/web-panel/assets/index-BCU6dVQ-.js +0 -1
- package/src/assets/web-panel/assets/index-N_oOFphx.js +0 -1
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 {
|
|
@@ -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 ───────────────────────────────────────────────────────────────
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
writeFileSync,
|
|
21
21
|
mkdirSync,
|
|
22
22
|
appendFileSync,
|
|
23
|
+
renameSync,
|
|
24
|
+
unlinkSync,
|
|
23
25
|
} from "node:fs";
|
|
24
26
|
import { join } from "node:path";
|
|
25
27
|
|
|
@@ -29,9 +31,37 @@ export const _deps = {
|
|
|
29
31
|
writeFileSync,
|
|
30
32
|
mkdirSync,
|
|
31
33
|
appendFileSync,
|
|
34
|
+
renameSync,
|
|
35
|
+
unlinkSync,
|
|
32
36
|
now: () => new Date(),
|
|
33
37
|
};
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Atomically write a learned-template JSON via _deps. A crash mid-write would
|
|
41
|
+
* truncate `<templateId>.json`; loadUserTemplate's catch then drops it, losing
|
|
42
|
+
* the learned prompt extension. Temp sibling + rename (atomic within a
|
|
43
|
+
* filesystem). Graceful-degrade to a direct write when the injected fs lacks
|
|
44
|
+
* renameSync — the real fs always has it, so production is always atomic.
|
|
45
|
+
*/
|
|
46
|
+
function _atomicWriteJson(file, data) {
|
|
47
|
+
if (typeof _deps.renameSync !== "function") {
|
|
48
|
+
_deps.writeFileSync(file, data, "utf-8");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
52
|
+
try {
|
|
53
|
+
_deps.writeFileSync(tmp, data, "utf-8");
|
|
54
|
+
_deps.renameSync(tmp, file);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
try {
|
|
57
|
+
if (_deps.existsSync(tmp) && _deps.unlinkSync) _deps.unlinkSync(tmp);
|
|
58
|
+
} catch {
|
|
59
|
+
/* best-effort temp cleanup */
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
/** Minimum historical runs before a template qualifies for a patch suggestion. */
|
|
36
66
|
export const MIN_RUNS_FOR_PATCH = 10;
|
|
37
67
|
/** Minimum distinct failures needed to trigger a suggestion. */
|
|
@@ -414,7 +444,7 @@ export function applyPromptPatch(cwd, patch) {
|
|
|
414
444
|
updatedAt: _deps.now().toISOString(),
|
|
415
445
|
};
|
|
416
446
|
const file = join(dir, `${patch.templateId}.json`);
|
|
417
|
-
|
|
447
|
+
_atomicWriteJson(file, JSON.stringify(doc, null, 2));
|
|
418
448
|
|
|
419
449
|
// Audit trail — never pruned automatically
|
|
420
450
|
_deps.appendFileSync(
|
package/src/lib/cowork-share.js
CHANGED
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
* @module cowork-share
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
existsSync,
|
|
20
|
+
mkdirSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
renameSync,
|
|
24
|
+
unlinkSync,
|
|
25
|
+
} from "node:fs";
|
|
19
26
|
import { join } from "node:path";
|
|
20
27
|
import crypto, { createHash } from "node:crypto";
|
|
21
28
|
import {
|
|
@@ -29,9 +36,38 @@ export const _deps = {
|
|
|
29
36
|
mkdirSync,
|
|
30
37
|
readFileSync,
|
|
31
38
|
writeFileSync,
|
|
39
|
+
renameSync,
|
|
40
|
+
unlinkSync,
|
|
32
41
|
now: () => new Date().toISOString(),
|
|
33
42
|
};
|
|
34
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Atomically write a JSON file via _deps. Imported result packets are persistent
|
|
46
|
+
* local state (shared-results/<taskId>.json) and exported packets are read by
|
|
47
|
+
* other tools — a crash mid-write must not leave a truncated/invalid packet
|
|
48
|
+
* (verifyPacket/readPacket would reject it). Temp sibling + rename (atomic
|
|
49
|
+
* within a filesystem). Graceful-degrade to a direct write when the injected fs
|
|
50
|
+
* lacks renameSync — the real fs always has it, so production is always atomic.
|
|
51
|
+
*/
|
|
52
|
+
function _atomicWriteJson(file, data) {
|
|
53
|
+
if (typeof _deps.renameSync !== "function") {
|
|
54
|
+
_deps.writeFileSync(file, data, "utf-8");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
58
|
+
try {
|
|
59
|
+
_deps.writeFileSync(tmp, data, "utf-8");
|
|
60
|
+
_deps.renameSync(tmp, file);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
try {
|
|
63
|
+
if (_deps.existsSync(tmp) && _deps.unlinkSync) _deps.unlinkSync(tmp);
|
|
64
|
+
} catch {
|
|
65
|
+
/* best-effort temp cleanup */
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
const SUPPORTED_SIG_ALG = "Ed25519";
|
|
36
72
|
|
|
37
73
|
const PACKET_VERSION = 1;
|
|
@@ -256,7 +292,7 @@ export function findHistoryRecord(cwd, taskId) {
|
|
|
256
292
|
* Write a packet to disk as pretty-printed JSON.
|
|
257
293
|
*/
|
|
258
294
|
export function writePacket(filePath, packet) {
|
|
259
|
-
|
|
295
|
+
_atomicWriteJson(filePath, JSON.stringify(packet, null, 2));
|
|
260
296
|
return filePath;
|
|
261
297
|
}
|
|
262
298
|
|
|
@@ -317,7 +353,7 @@ export function importResultPacket(cwd, packet) {
|
|
|
317
353
|
const dir = join(cwd, ".chainlesschain", "cowork", "shared-results");
|
|
318
354
|
_deps.mkdirSync(dir, { recursive: true });
|
|
319
355
|
const file = join(dir, `${packet.payload.taskId}.json`);
|
|
320
|
-
|
|
356
|
+
_atomicWriteJson(file, JSON.stringify(packet, null, 2));
|
|
321
357
|
return { file, taskId: packet.payload.taskId };
|
|
322
358
|
}
|
|
323
359
|
|