chainlesschain 0.162.95 → 0.162.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/chainlesschain.js +8 -18
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DLvvkb9x.js → AIOps-C1YvIYh5.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BxhQTwqd.js → ActionButton-jqcXEakj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C4oUXzhG.js → Analytics-C8Xwqd3_.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CjzKJdv3.js → AppLayout-Dx5ypTfA.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-B-MrugUs.js → Audit-1GZ4Tv2f.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ecIfEpIZ.js → Backup-CexR2hyk.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BUcSfWnx.js → BaseInput-DwAruKdy.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DIc1n_Lk.js → Chat-W2FkU5BY.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BGzg7oVA.js → ChatBubbleRenderer-BCLnhLfu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BQ3Ueff-.js → Checkbox-Dkh5JY5B.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CvSGLnqE.js → Codegen-LfYOgsdA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Br9IXZcE.js → Col-DEgketAC.js} +1 -1
- package/src/assets/web-panel/assets/{Community-tPuLOf0r.js → Community-LeFQMaZm.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-vMWH73BE.js → Compact-CrlKYvBc.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ct9z699x.js → Compliance-ca_4PGYF.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bxs1kL1y.js → Cowork-BcMWqh2a.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-QEpr2k4U.js → Cron-pVVEA3dw.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-u4xbUw6O.js → Crosschain-CiKSGTqp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DXTEvyrW.js → DID-B2bGzdOj.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C4pZgzkg.js → Dashboard-sQ6jcgCn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-LRuqREGe.js → Dropdown-rFx_8P04.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D7XI1yBM.js → EmailListRenderer-smw2hh6f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-3y_SuAMQ.js → FamilyGuardDashboard-CZhg2_S2.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DbjbJJCW.js → Federation-DQE-368G.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-WPpMZcUw.js → FormItemContext-2XPb9izr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-nNT9Tn4E.js → GenericCardRenderer-DCGMz5E6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DqDaVzwV.js → Git-BFwrxK4Q.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BquBZpcg.js → Governance-ChxBRwG1.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-RcPPYijs.js → Inference-C7kyknu_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ByXfZV13.js → KnowledgeGraph-BsMd4XAt.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CHT3DUcq.js → Logs-Bs3yrpow.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Bi0HJ9pN.js → Marketplace-Cc29jdBv.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-QCBxPRRc.js → McpTools-zYpYsUsl.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D4RlxGXn.js → Memory-x9r4yKtS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CU_PV7e-.js → MobileBridge-CjUo8prx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-c1CMFD8z.js → MobileProjects-Cmz4wVrF.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BezDB1-s.js → Mtc-Y-SBnNxm.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CgLpiHpE.js → MtcAudit-ogiKnrxj.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-vx6bz-bA.js → Multisig-C1hygkFR.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CGOagg3-.js → NLProgramming-fWWYiTsY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BcuDGYRS.js → Notes-CkCNF4gC.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ZAGCDUpc.js → NotificationSettings-DThB7WQG.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-B5onE0eX.js → OrderTableRenderer-Dh64aMq6.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-y_y20-5Y.js → Organization-BSY16zvP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BEICS31D.js → Overflow-C_TDh18K.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D_WRMzh4.js → P2P-CvxZk6og.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-0JZ5JF4M.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-3aqCc_4f.js → Permissions-DV8ubpjM.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DT3R9g9Y.js → PersonalDataHub-g2Dgls8e.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-MiMWIhw0.js → Pipeline-DGp8zQ0C.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C3JsLuDa.js → Privacy-C0uHrjWR.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-EhHVi1ff.js → ProjectInit-2XGICaFt.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D_dNEA37.js → ProjectSettings-Cfy72e0Q.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CXydUXp8.js → Projects-Xsvk_kKT.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CPEhi2iZ.js → Providers-vuvDVg7U.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BFQpMleX.js → QuickAsk-BUy7kPyQ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DzbovRC8.js → Recommend-DlzpC_JE.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DoBeV3TD.js → Reputation-BiWFwPMh.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DthhwX_B.js → Row-dmfZBK-k.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dsf5U0lP.js → RssFeed-Cf6tlRKq.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CJPB8kKl.js → Search-DhzXDRAJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DI9MfpdD.js → Security-C-bTfgeJ.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BdG0TFGf.js → Services-D3KNwBqu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DSYazY_B.js → Skeleton-CHH8i1xY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BD309VTT.js → Skills-DHiONX0z.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CaUlw4_6.js → Sla-DwWMb-Ui.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZAPkR7H.js → SpeechSettings-DAq64iWB.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFRSowN3.js → SyncSettings-DJU-kHko.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Di8EOluc.js → Tasks-RAIzA3u-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B5FTziwi.js → Templates-By4cbadT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Dj3XYfBg.js → Tenant-kCAXsxWu.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CznH_rJT.js → Terminal-D8pup_CO.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DTHzaXPt.js → TimelineRenderer-Bk2c9TUI.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bo56nuML.js → Tokens-B0wTiZRa.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-pyVUVBwb.js → Trigger-Ca_9t0gk.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cp7jMkKN.js → Trust-BnfNDDDm.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-aPcZCdpz.js → UkeySign-fnlF2-Yr.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DAv5j--w.js → VideoEditing-Cfshhzyw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DRhK7IDR.js → Wallet-BWMpDOLG.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B2yQJuq8.js → WebAuthn-B6xeXZvw.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-D98DhR54.js → WorkflowEditor-Cb3-FPCE.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BXrRNi8C.js → chat-CP_opuMK.js} +1 -1
- package/src/assets/web-panel/assets/{colors-jQ94T_qn.js → colors-RLzfjxRQ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-KqUJLlF2.js → compact-item-BTRloNKV.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DxM3zuQW.js → createContext-gXgXTLlU.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CYlmApo_.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BGm946KV.js → hasIn-CrIpxkRC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQvgx2e.js → index-AsI1SqnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5NQai7O.js → index-B3L5vTSs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crw76vIF.js → index-B5sN_pwd.js} +1 -1
- package/src/assets/web-panel/assets/{index-1P0sXNhG.js → index-BEcRPNHv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce-YoL4x.js → index-BVrcoJe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaU_fSK7.js → index-BewxG36A.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGjCjsf1.js → index-Bi87H9yk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpVYD4j.js → index-Bl4OQ--4.js} +1 -1
- package/src/assets/web-panel/assets/{index-yavsyHif.js → index-BlE2lCS-.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4b7KjRq.js → index-BlS91Ac0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbS0cBU9.js → index-Bm0cNeeQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-LWTZAud0.js → index-Bo2Fl2j7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZMzj4ay.js → index-Bsz3X-aU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOYcemym.js → index-BurvDWMS.js} +1 -1
- package/src/assets/web-panel/assets/{index-T4UJiTi3.js → index-BzBLtthb.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Z2m28C.js → index-C0wmEN2j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSzb9VOG.js → index-C4bBR-Vd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfE3jxrT.js → index-COhACL0I.js} +1 -1
- package/src/assets/web-panel/assets/{index-FSYz5aww.js → index-CQ-cnhZM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXMZLfd8.js → index-CRr99L7q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWcd5zDG.js → index-CaTkUkJp.js} +3 -3
- package/src/assets/web-panel/assets/{index-D1VWL6Lc.js → index-CfOjPxso.js} +1 -1
- package/src/assets/web-panel/assets/index-Cf_SLgtj.js +1 -0
- package/src/assets/web-panel/assets/{index-mZNLT2SP.js → index-Ch_TYQNW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-2HpfWo.js → index-CisGGaF3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CghRL1dh.js → index-DA-_2qTs.js} +1 -1
- package/src/assets/web-panel/assets/index-DYBIDVGn.js +1 -0
- package/src/assets/web-panel/assets/{index-Dgzw6eKr.js → index-DZxK7MW9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CreXAr5Y.js → index-DiXnGU0A.js} +1 -1
- package/src/assets/web-panel/assets/{index-aOLXuHpG.js → index-DsiyfyBE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DboUBf6R.js → index-G_OgtHiC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEZfi5D.js → index-IjV-dnE8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAPk6ntc.js → index-Mltfj22U.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTUs9t5F.js → index-U-kmEJdu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMp41Q1Y.js → index-cgf-OVry.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_SFSix.js → index-ch8zcf-h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzsaWQy.js → index-cqOT5RiJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7sBLech.js → index-cywkZJ-I.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dib0FsTT.js → index-sE_I7gVL.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDoZJI7g.js → initDefaultProps-DMwnVZzH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-sceLjgp-.js → motion-Dnpjpvo7.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTWQpFa7.js → move-iuRh5U9Y.js} +1 -1
- package/src/assets/web-panel/assets/{omit-dr-NCrHZ.js → omit-Bl4WRV59.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DK-GX_Z2.js → pickAttrs-B9KSXaep.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIPf--18.js → placementArrow-TEGIow4r.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BzrWT5tV.js → responsiveObserve--BECM92v.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D5XhxlET.js → slide-Ca8G7XbL.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-6DI-k-9E.js → statusUtils-8QPG_T3D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DMw-eyn9.js → styleChecker-p3FsA6mg.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D4oVdliM.js → useFlexGapSupport-Ct0yIzrp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D3PPZnvy.js → useFs-3nlByl_3.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Gh5ba4KP.js → usePersonalDataHub-N3OsoSRM.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEM1u4x.js → vnode-Cfe3ywq3.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-ClT_-9RZ.js → zoom-DVBuDS_3.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/activitypub.js +4 -3
- package/src/commands/agent.js +25 -0
- package/src/commands/agents.js +18 -4
- package/src/commands/ask.js +13 -0
- package/src/commands/command.js +12 -2
- package/src/commands/config.js +17 -1
- package/src/commands/cost.js +6 -1
- package/src/commands/dao.js +4 -3
- package/src/commands/dlp.js +2 -1
- package/src/commands/hub.js +138 -0
- package/src/commands/mtc.js +37 -1
- package/src/gateways/discord/discord-formatter.js +18 -1
- package/src/gateways/telegram/telegram-formatter.js +6 -3
- package/src/gateways/ws/ws-server.js +19 -1
- package/src/harness/plugin-manager.js +20 -0
- package/src/harness/worktree-isolator.js +17 -1
- package/src/index.js +2 -0
- package/src/lib/__tests__/model-deprecation.test.js +194 -0
- package/src/lib/a2a-protocol.js +24 -9
- package/src/lib/agent-core.js +4 -5
- package/src/lib/audit-mtc.js +40 -2
- package/src/lib/chat-core.js +146 -18
- package/src/lib/cli-anything-bridge.js +5 -0
- package/src/lib/cli-numeric.js +38 -0
- package/src/lib/config-manager.js +37 -6
- package/src/lib/cowork/ab-comparator-cli.js +6 -2
- package/src/lib/cowork-cron.js +45 -34
- package/src/lib/cowork-learning.js +31 -1
- package/src/lib/cowork-share.js +39 -3
- package/src/lib/cowork-template-marketplace.js +54 -2
- package/src/lib/cowork-workflow.js +33 -6
- package/src/lib/cross-chain-mtc.js +41 -12
- package/src/lib/crypto-manager.js +12 -2
- package/src/lib/dbevo.js +19 -8
- package/src/lib/downloader.js +24 -9
- package/src/lib/evomap-governance.js +4 -4
- package/src/lib/evomap-manager.js +54 -7
- package/src/lib/fatal-handler.js +48 -0
- package/src/lib/file-checkpoint.js +26 -0
- package/src/lib/git-integration.js +28 -0
- package/src/lib/goal-store.js +36 -10
- package/src/lib/hierarchical-memory.js +6 -0
- package/src/lib/llm-pricing.js +52 -2
- package/src/lib/mcp-oauth.js +37 -7
- package/src/lib/memory-manager.js +23 -2
- package/src/lib/model-deprecation.js +249 -0
- package/src/lib/pdh-feedback-ledger.js +131 -0
- package/src/lib/permanent-memory.js +30 -2
- package/src/lib/permission-rules.cjs +11 -3
- package/src/lib/personal-data-hub-aichat-wizard.js +33 -3
- package/src/lib/personal-data-hub-wiring.js +39 -16
- package/src/lib/project-instructions.js +10 -4
- package/src/lib/runnable-provider.js +62 -31
- package/src/lib/session-usage.js +34 -1
- package/src/lib/skill-loader.js +4 -1
- package/src/lib/skill-packs/generator.js +25 -2
- package/src/lib/stream-retry.js +56 -0
- package/src/lib/sync-credentials.js +53 -17
- package/src/lib/token-tracker.js +12 -2
- package/src/lib/with-file-lock.js +87 -0
- package/src/repl/agent-repl.js +47 -7
- package/src/repl/chat-repl.js +19 -0
- package/src/repl/session-cost.js +31 -3
- package/src/runtime/agent-core.js +478 -53
- package/src/runtime/coding-agent-contract-shared.cjs +58 -1
- package/src/runtime/coding-agent-policy.cjs +10 -0
- package/src/runtime/file-ref-expander.js +29 -1
- package/src/runtime/headless-runner.js +12 -1
- package/src/runtime/headless-stream.js +200 -9
- package/src/skills/video-editing/tools/commit.js +15 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C8f5NbbI.js +0 -7
- package/src/assets/web-panel/assets/devWarning-Bo1r5_zX.js +0 -1
- package/src/assets/web-panel/assets/index-BCU6dVQ-.js +0 -1
- package/src/assets/web-panel/assets/index-N_oOFphx.js +0 -1
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
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
writeFileSync,
|
|
23
23
|
readdirSync,
|
|
24
24
|
unlinkSync,
|
|
25
|
+
renameSync,
|
|
25
26
|
} from "node:fs";
|
|
26
27
|
import { join } from "node:path";
|
|
27
28
|
|
|
@@ -32,10 +33,38 @@ export const _deps = {
|
|
|
32
33
|
writeFileSync,
|
|
33
34
|
readdirSync,
|
|
34
35
|
unlinkSync,
|
|
36
|
+
renameSync,
|
|
35
37
|
// Injected at runtime by CLI to avoid eager evomap-client load
|
|
36
38
|
evomapClient: null,
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Atomically write a JSON file via _deps. A user template (<id>.json) is
|
|
43
|
+
* persistent state; a crash mid-write truncates it and listUserTemplates /
|
|
44
|
+
* loadUserTemplate's JSON.parse then drops or throws on it. Temp sibling +
|
|
45
|
+
* rename (atomic within a filesystem). Graceful-degrade to a direct write when
|
|
46
|
+
* the injected fs lacks renameSync — the real fs always has it, so production
|
|
47
|
+
* is always atomic.
|
|
48
|
+
*/
|
|
49
|
+
function _atomicWriteJson(file, data) {
|
|
50
|
+
if (typeof _deps.renameSync !== "function") {
|
|
51
|
+
_deps.writeFileSync(file, data, "utf-8");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
55
|
+
try {
|
|
56
|
+
_deps.writeFileSync(tmp, data, "utf-8");
|
|
57
|
+
_deps.renameSync(tmp, file);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
try {
|
|
60
|
+
if (_deps.existsSync(tmp) && _deps.unlinkSync) _deps.unlinkSync(tmp);
|
|
61
|
+
} catch {
|
|
62
|
+
/* best-effort temp cleanup */
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
const EVOMAP_CATEGORY = "cowork-template";
|
|
40
69
|
|
|
41
70
|
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
@@ -48,6 +77,26 @@ function _userTemplateFile(cwd, id) {
|
|
|
48
77
|
return join(_userTemplatesDir(cwd), `${id}.json`);
|
|
49
78
|
}
|
|
50
79
|
|
|
80
|
+
/**
|
|
81
|
+
* A template id is interpolated into a filesystem path (user-templates/<id>.json)
|
|
82
|
+
* and arrives from installed templates whose payload comes from an external hub /
|
|
83
|
+
* MCP download. A malicious id like "../../.ssh/authorized_keys" or
|
|
84
|
+
* "C:\\Windows\\..." would escape the user-templates dir and overwrite/delete
|
|
85
|
+
* arbitrary files. Restrict it to a single safe path segment.
|
|
86
|
+
*/
|
|
87
|
+
function _isSafeTemplateId(id) {
|
|
88
|
+
return (
|
|
89
|
+
typeof id === "string" &&
|
|
90
|
+
id.length > 0 &&
|
|
91
|
+
id !== "." &&
|
|
92
|
+
id !== ".." &&
|
|
93
|
+
!id.includes("/") &&
|
|
94
|
+
!id.includes("\\") &&
|
|
95
|
+
!id.includes(":") &&
|
|
96
|
+
!id.includes("\0")
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
51
100
|
// ─── Serialization ───────────────────────────────────────────────────────────
|
|
52
101
|
|
|
53
102
|
const SHARED_FIELDS = [
|
|
@@ -148,18 +197,21 @@ export function listUserTemplates(cwd) {
|
|
|
148
197
|
/** Save a template to the local user-templates directory. */
|
|
149
198
|
export function saveUserTemplate(cwd, template) {
|
|
150
199
|
if (!template?.id) throw new Error("template.id is required");
|
|
200
|
+
if (!_isSafeTemplateId(template.id)) {
|
|
201
|
+
throw new Error(`Unsafe template id (path traversal): ${template.id}`);
|
|
202
|
+
}
|
|
151
203
|
const dir = _userTemplatesDir(cwd);
|
|
152
204
|
_deps.mkdirSync(dir, { recursive: true });
|
|
153
|
-
|
|
205
|
+
_atomicWriteJson(
|
|
154
206
|
_userTemplateFile(cwd, template.id),
|
|
155
207
|
JSON.stringify(template, null, 2),
|
|
156
|
-
"utf-8",
|
|
157
208
|
);
|
|
158
209
|
return template;
|
|
159
210
|
}
|
|
160
211
|
|
|
161
212
|
/** Remove an installed user template. Returns true if removed. */
|
|
162
213
|
export function removeUserTemplate(cwd, id) {
|
|
214
|
+
if (!_isSafeTemplateId(id)) return false;
|
|
163
215
|
const file = _userTemplateFile(cwd, id);
|
|
164
216
|
if (!_deps.existsSync(file)) return false;
|
|
165
217
|
_deps.unlinkSync(file);
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
readdirSync,
|
|
30
30
|
unlinkSync,
|
|
31
31
|
appendFileSync,
|
|
32
|
+
renameSync,
|
|
32
33
|
} from "node:fs";
|
|
33
34
|
import { join } from "node:path";
|
|
34
35
|
import { evaluate as evalExpr, resolveReference } from "./workflow-expr.js";
|
|
@@ -47,6 +48,7 @@ export const _deps = {
|
|
|
47
48
|
readdirSync,
|
|
48
49
|
unlinkSync,
|
|
49
50
|
appendFileSync,
|
|
51
|
+
renameSync,
|
|
50
52
|
now: () => Date.now(),
|
|
51
53
|
runTask: null, // injected by CLI
|
|
52
54
|
// Timer seams (injectable for deterministic retry/timeout tests).
|
|
@@ -55,6 +57,32 @@ export const _deps = {
|
|
|
55
57
|
clearTimeout: (t) => clearTimeout(t),
|
|
56
58
|
};
|
|
57
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Atomically write a JSON file via _deps. A saved workflow definition is
|
|
62
|
+
* persistent user state; a crash mid-write truncates <id>.json and getWorkflow
|
|
63
|
+
* then fails to parse it. Temp sibling + rename (atomic within a filesystem).
|
|
64
|
+
* Graceful-degrade to a direct write when the injected fs lacks renameSync — the
|
|
65
|
+
* real fs always has it, so production is always atomic.
|
|
66
|
+
*/
|
|
67
|
+
function _atomicWriteJson(file, data) {
|
|
68
|
+
if (typeof _deps.renameSync !== "function") {
|
|
69
|
+
_deps.writeFileSync(file, data, "utf-8");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
73
|
+
try {
|
|
74
|
+
_deps.writeFileSync(tmp, data, "utf-8");
|
|
75
|
+
_deps.renameSync(tmp, file);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
try {
|
|
78
|
+
if (_deps.existsSync(tmp) && _deps.unlinkSync) _deps.unlinkSync(tmp);
|
|
79
|
+
} catch {
|
|
80
|
+
/* best-effort temp cleanup */
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
59
87
|
|
|
60
88
|
function workflowsDir(cwd) {
|
|
@@ -302,7 +330,10 @@ export function resolveForEachItems(forEach, resultsById) {
|
|
|
302
330
|
export function substituteItem(template, item) {
|
|
303
331
|
if (typeof template !== "string") return template;
|
|
304
332
|
const repl = typeof item === "string" ? item : JSON.stringify(item);
|
|
305
|
-
|
|
333
|
+
// Insert via a function so $-sequences in the item ($&, $`, $', $$ — common in
|
|
334
|
+
// JSON values / shell snippets) are placed literally rather than interpreted
|
|
335
|
+
// as String.replace patterns, which would corrupt the substituted value.
|
|
336
|
+
return template.replace(/\$\{item\}/g, () => repl);
|
|
306
337
|
}
|
|
307
338
|
|
|
308
339
|
/** Evaluate a step's `when` expression. Missing expression → always true. */
|
|
@@ -376,11 +407,7 @@ export function saveWorkflow(cwd, wf) {
|
|
|
376
407
|
if (!valid) throw new Error(`Invalid workflow: ${errors.join("; ")}`);
|
|
377
408
|
const dir = workflowsDir(cwd);
|
|
378
409
|
_deps.mkdirSync(dir, { recursive: true });
|
|
379
|
-
|
|
380
|
-
workflowFile(cwd, wf.id),
|
|
381
|
-
JSON.stringify(wf, null, 2),
|
|
382
|
-
"utf-8",
|
|
383
|
-
);
|
|
410
|
+
_atomicWriteJson(workflowFile(cwd, wf.id), JSON.stringify(wf, null, 2));
|
|
384
411
|
return wf;
|
|
385
412
|
}
|
|
386
413
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import mtcLib from "@chainlesschain/core-mtc";
|
|
22
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
22
23
|
|
|
23
24
|
const {
|
|
24
25
|
assembleBatch,
|
|
@@ -95,6 +96,28 @@ function ensureDirs(dir) {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Atomically write a UTF-8 file. The cross-chain MTC store holds config, trust
|
|
101
|
+
* anchors (federation security material), staged bridge ops, the batch-seq
|
|
102
|
+
* counter, and closed batches — a crash mid-write corrupts ledger/security
|
|
103
|
+
* state. Temp sibling + rename (atomic within a filesystem); temp names end in
|
|
104
|
+
* `.tmp` (never `.json`), so a hard-crash leftover is ignored by listStagedOps.
|
|
105
|
+
*/
|
|
106
|
+
function atomicWriteFileSync(filePath, data) {
|
|
107
|
+
const tmp = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(tmp, data, "utf-8");
|
|
110
|
+
fs.renameSync(tmp, filePath);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
try {
|
|
113
|
+
if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
|
|
114
|
+
} catch {
|
|
115
|
+
/* best-effort temp cleanup */
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
// ─────────────────────────────────────────────────────────────────────
|
|
99
122
|
// Namespace
|
|
100
123
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -173,7 +196,7 @@ export function saveCrossChainMtcConfig(dir, patch) {
|
|
|
173
196
|
"config.mode must be independent, federated, or light-client",
|
|
174
197
|
);
|
|
175
198
|
}
|
|
176
|
-
|
|
199
|
+
atomicWriteFileSync(configPath(dir), JSON.stringify(merged, null, 2));
|
|
177
200
|
return merged;
|
|
178
201
|
}
|
|
179
202
|
|
|
@@ -201,11 +224,7 @@ export function loadTrustAnchors(dir) {
|
|
|
201
224
|
}
|
|
202
225
|
|
|
203
226
|
function saveTrustAnchorStore(dir, store) {
|
|
204
|
-
|
|
205
|
-
trustAnchorsPath(dir),
|
|
206
|
-
JSON.stringify(store, null, 2),
|
|
207
|
-
"utf-8",
|
|
208
|
-
);
|
|
227
|
+
atomicWriteFileSync(trustAnchorsPath(dir), JSON.stringify(store, null, 2));
|
|
209
228
|
}
|
|
210
229
|
|
|
211
230
|
/**
|
|
@@ -456,7 +475,7 @@ export function stageBridgeOp(dir, op, opts = {}) {
|
|
|
456
475
|
if (fs.existsSync(file)) {
|
|
457
476
|
return { staged: false, path: file, reason: "ALREADY_STAGED" };
|
|
458
477
|
}
|
|
459
|
-
|
|
478
|
+
atomicWriteFileSync(file, JSON.stringify(op, null, 2));
|
|
460
479
|
return { staged: true, path: file };
|
|
461
480
|
}
|
|
462
481
|
|
|
@@ -498,7 +517,7 @@ function nextBatchSeq(dir, pair) {
|
|
|
498
517
|
}
|
|
499
518
|
const next = (map[pair] || 0) + 1;
|
|
500
519
|
map[pair] = next;
|
|
501
|
-
|
|
520
|
+
atomicWriteFileSync(p, JSON.stringify(map, null, 2));
|
|
502
521
|
return next;
|
|
503
522
|
}
|
|
504
523
|
|
|
@@ -514,6 +533,18 @@ function nextBatchSeq(dir, pair) {
|
|
|
514
533
|
* skipped: { reason: string } | null }}
|
|
515
534
|
*/
|
|
516
535
|
export function closeBatch(dir, opts = {}) {
|
|
536
|
+
// Serialize the whole staging-scan → batch-write → staging-cleanup section
|
|
537
|
+
// across processes. Without it, two concurrent closes both read the same
|
|
538
|
+
// staged ops, allocate the same per-pair sequence (namespace collision), and
|
|
539
|
+
// batch the same ops twice before clearing staging. Best-effort lock (never
|
|
540
|
+
// hangs); generous timeout since the section includes batch assembly+signing.
|
|
541
|
+
ensureDirs(dir);
|
|
542
|
+
return withFileLock(batchesDir(dir), () => _closeBatchImpl(dir, opts), {
|
|
543
|
+
timeoutMs: 15000,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function _closeBatchImpl(dir, opts = {}) {
|
|
517
548
|
ensureDirs(dir);
|
|
518
549
|
const cfg = loadCrossChainMtcConfig(dir);
|
|
519
550
|
const grouped = listStagedOps(dir);
|
|
@@ -546,16 +577,14 @@ export function closeBatch(dir, opts = {}) {
|
|
|
546
577
|
`${pair}-${String(seq).padStart(6, "0")}`,
|
|
547
578
|
);
|
|
548
579
|
fs.mkdirSync(outDir, { recursive: true });
|
|
549
|
-
|
|
580
|
+
atomicWriteFileSync(
|
|
550
581
|
path.join(outDir, "landmark.json"),
|
|
551
582
|
JSON.stringify(result.landmark, null, 2),
|
|
552
|
-
"utf-8",
|
|
553
583
|
);
|
|
554
584
|
result.envelopes.forEach((env, i) => {
|
|
555
|
-
|
|
585
|
+
atomicWriteFileSync(
|
|
556
586
|
path.join(outDir, `envelope-${String(i).padStart(4, "0")}.json`),
|
|
557
587
|
JSON.stringify(env, null, 2),
|
|
558
|
-
"utf-8",
|
|
559
588
|
);
|
|
560
589
|
});
|
|
561
590
|
|
|
@@ -136,14 +136,24 @@ export function decryptFile(inputPath, password, outputPath) {
|
|
|
136
136
|
* Check if a file is encrypted with our format.
|
|
137
137
|
*/
|
|
138
138
|
export function isEncryptedFile(filePath) {
|
|
139
|
+
let fd;
|
|
139
140
|
try {
|
|
140
|
-
|
|
141
|
+
fd = fs.openSync(filePath, "r");
|
|
141
142
|
const buf = Buffer.alloc(MAGIC_HEADER.length);
|
|
142
143
|
fs.readSync(fd, buf, 0, MAGIC_HEADER.length, 0);
|
|
143
|
-
fs.closeSync(fd);
|
|
144
144
|
return buf.equals(MAGIC_HEADER);
|
|
145
145
|
} catch (_err) {
|
|
146
146
|
return false;
|
|
147
|
+
} finally {
|
|
148
|
+
// Close in finally so a readSync throw (permission change mid-op, OS error)
|
|
149
|
+
// can't leak the descriptor — repeated failures would otherwise exhaust FDs.
|
|
150
|
+
if (fd !== undefined) {
|
|
151
|
+
try {
|
|
152
|
+
fs.closeSync(fd);
|
|
153
|
+
} catch {
|
|
154
|
+
/* already closed / invalid fd — nothing to release */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
|
package/src/lib/dbevo.js
CHANGED
|
@@ -41,6 +41,17 @@ export const SUGGESTION_TYPE = Object.freeze({
|
|
|
41
41
|
|
|
42
42
|
let _migrations = []; // registered migration definitions
|
|
43
43
|
let _migrationHistory = []; // executed migration records from DB
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compare two version strings with NUMERIC collation so "10" sorts AFTER "2".
|
|
47
|
+
* A migration framework must order numeric versions correctly; plain
|
|
48
|
+
* localeCompare()/Array.sort() is lexicographic ("10" < "2"), which would run
|
|
49
|
+
* migration v10 before v2 and corrupt the schema. Numeric collation also leaves
|
|
50
|
+
* zero-padded / date / semver-ish versions ordered as expected.
|
|
51
|
+
*/
|
|
52
|
+
function cmpVersion(a, b) {
|
|
53
|
+
return String(a).localeCompare(String(b), undefined, { numeric: true });
|
|
54
|
+
}
|
|
44
55
|
let _queryLogs = [];
|
|
45
56
|
let _suggestions = new Map();
|
|
46
57
|
let _slowQueryThresholdMs = 100;
|
|
@@ -147,7 +158,7 @@ export function registerMigration(
|
|
|
147
158
|
});
|
|
148
159
|
|
|
149
160
|
// Keep sorted by version
|
|
150
|
-
_migrations.sort((a, b) => a.version
|
|
161
|
+
_migrations.sort((a, b) => cmpVersion(a.version, b.version));
|
|
151
162
|
|
|
152
163
|
return { registered: true, version, checksum };
|
|
153
164
|
}
|
|
@@ -178,13 +189,13 @@ export function getCurrentVersion(db) {
|
|
|
178
189
|
);
|
|
179
190
|
|
|
180
191
|
if (active.length === 0) return null;
|
|
181
|
-
return active.sort((a, b) => b
|
|
192
|
+
return active.sort((a, b) => cmpVersion(b, a))[0];
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
export function getPendingMigrations(db) {
|
|
185
196
|
const current = getCurrentVersion(db);
|
|
186
197
|
return _migrations.filter(
|
|
187
|
-
(m) => !current || m.version
|
|
198
|
+
(m) => !current || cmpVersion(m.version, current) > 0,
|
|
188
199
|
);
|
|
189
200
|
}
|
|
190
201
|
|
|
@@ -193,7 +204,7 @@ export function migrateUp(db, targetVersion) {
|
|
|
193
204
|
if (pending.length === 0) return { migrated: false, reason: "no_pending" };
|
|
194
205
|
|
|
195
206
|
const toRun = targetVersion
|
|
196
|
-
? pending.filter((m) => m.version
|
|
207
|
+
? pending.filter((m) => cmpVersion(m.version, targetVersion) <= 0)
|
|
197
208
|
: pending;
|
|
198
209
|
|
|
199
210
|
if (toRun.length === 0) return { migrated: false, reason: "no_pending" };
|
|
@@ -248,13 +259,13 @@ export function migrateDown(db, targetVersion) {
|
|
|
248
259
|
.filter((m) => {
|
|
249
260
|
if (targetVersion) {
|
|
250
261
|
return (
|
|
251
|
-
m.version
|
|
252
|
-
m.version
|
|
262
|
+
cmpVersion(m.version, current) <= 0 &&
|
|
263
|
+
cmpVersion(m.version, targetVersion) > 0
|
|
253
264
|
);
|
|
254
265
|
}
|
|
255
266
|
return m.version === current;
|
|
256
267
|
})
|
|
257
|
-
.sort((a, b) => b.version
|
|
268
|
+
.sort((a, b) => cmpVersion(b.version, a.version));
|
|
258
269
|
|
|
259
270
|
if (toRollBack.length === 0)
|
|
260
271
|
return { rolledBack: false, reason: "nothing_to_rollback" };
|
|
@@ -331,7 +342,7 @@ export function validateMigrations() {
|
|
|
331
342
|
if (_migrations.length === 0) return { valid: true, issues: [] };
|
|
332
343
|
|
|
333
344
|
const issues = [];
|
|
334
|
-
const versions = _migrations.map((m) => m.version).sort();
|
|
345
|
+
const versions = _migrations.map((m) => m.version).sort(cmpVersion);
|
|
335
346
|
|
|
336
347
|
// Check for gaps in version sequence
|
|
337
348
|
for (let i = 1; i < versions.length; i++) {
|
package/src/lib/downloader.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { pipeline } from "node:stream/promises";
|
|
10
|
-
import {
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
11
|
import ora from "ora";
|
|
12
12
|
import { GITHUB_RELEASES_URL } from "../constants.js";
|
|
13
13
|
import { getBinDir, ensureDir } from "./paths.js";
|
|
@@ -208,16 +208,31 @@ async function fetchRelease(url) {
|
|
|
208
208
|
return response.json();
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
function extractZip(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
export function extractZip(
|
|
212
|
+
zipPath,
|
|
213
|
+
destDir,
|
|
214
|
+
{ execFileImpl = execFileSync, platform = getPlatform() } = {},
|
|
215
|
+
) {
|
|
216
|
+
// Never interpolate the paths into a shell string — `zipPath`/`destDir` derive
|
|
217
|
+
// from the (remote) release asset name, so a `"`/`$()`/`'` in a path would be a
|
|
218
|
+
// command-injection vector. execFileSync runs the tool directly (no shell), and
|
|
219
|
+
// on Windows the paths go through the environment so the -Command script stays
|
|
220
|
+
// a constant the shell can't be tricked by.
|
|
221
|
+
if (platform === "win32") {
|
|
222
|
+
execFileImpl(
|
|
223
|
+
"powershell",
|
|
224
|
+
[
|
|
225
|
+
"-NoProfile",
|
|
226
|
+
"-Command",
|
|
227
|
+
"Expand-Archive -Path $env:CC_ZIP_SRC -DestinationPath $env:CC_ZIP_DEST -Force",
|
|
228
|
+
],
|
|
229
|
+
{
|
|
230
|
+
stdio: "ignore",
|
|
231
|
+
env: { ...process.env, CC_ZIP_SRC: zipPath, CC_ZIP_DEST: destDir },
|
|
232
|
+
},
|
|
218
233
|
);
|
|
219
234
|
} else {
|
|
220
|
-
|
|
235
|
+
execFileImpl("unzip", ["-o", zipPath, "-d", destDir], { stdio: "ignore" });
|
|
221
236
|
}
|
|
222
237
|
}
|
|
223
238
|
|