chainlesschain 0.162.179 → 0.162.181
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/package.json +6 -3
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BF13YdB-.js → AIOps-cq0dwbnf.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-B9y67AjF.js → ActionButton-CvcgGl3w.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-FcdDHoSC.js → Analytics-hM2-jFqg.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CRm_zwSp.js → AppLayout-B9_Gsd_g.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Cbo7-usf.js → Artifacts-B7bu52Og.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-vMaLpkHC.js → Audit-DThfDgqB.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-FiUY692t.js → BackgroundAgents-DjlFiD4v.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BK80j_GQ.js → Backup-CK9xJKnG.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-ChNMuYR1.js → BaseInput-DEiSU8K8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-928UREpm.js → Chat-CpT96czj.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BKPPNmZI.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-dFW-zDbe.js → Checkbox-C1GfDnO0.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BCcNJUj2.js → Codegen-3DAKgiLx.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DcEkLv70.js → Col-74cOwZU1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DihBRRqg.js → Community-tn9Kkz9j.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DGYxwWET.js → Compact-BcoXH7m4.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-OnYe1vS4.js → Compliance-DIPGUBgi.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CmWB54c2.js → Cowork-doD5_91c.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BrANb2uw.js → Cron-C-IDvubz.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-2HgWzSxg.js → Crosschain-CdcCoBdj.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DdDwNI6K.js → DID-DdH5aMMA.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D6PnE78-.js → Dashboard-DHLA4mm0.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-ClzvSN4O.js → Dropdown-dEeCt7Md.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BUk9B727.js → EmailListRenderer-BMaecFv9.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-C8hi3v7i.js → FamilyGuardDashboard-DUaUukTN.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B5SP8xkg.js → Federation-CMBE0_rp.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWzJ8AU5.js → FormItemContext-PG-JDbXq.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-3E3Q3rjG.js → GenericCardRenderer-Bcb0p88m.js} +1 -1
- package/src/assets/web-panel/assets/{Git-z8IdCMId.js → Git-Bgv15myB.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CgqCF9lX.js → Governance-DB8YTArI.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-zgS-AuH9.js → Inference-BQMwCWaS.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BdbgmESJ.js → KnowledgeGraph-B1_ZziiB.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BHpknnjy.js → Logs-DovEEnt_.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-D201OL4T.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Di9sAR7j.js → Marketplace-BlCXSpyB.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DtnR9Z_A.js → McpTools-CsCR09Wj.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BaX9uuXD.js → Memory-CBVRLKno.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-xelyKWTY.js → MobileBridge-C9OIafgA.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BiFTOGBN.js → MobileProjects-D_zoJ8Xs.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-bV3OzTiO.js → Mtc-vE-HdvLj.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BkwvFTdZ.js → MtcAudit-CPLaekCd.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-_6HQQ-oC.js → Multisig-C_UP9C2o.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BBYhmWQZ.js → NLProgramming-2nODJcU1.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cq-6dQnU.js → Notes-BhE-SJGu.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ILZ9zkX6.js → NotificationSettings-D21q7BNU.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DgmTBEvk.js → OrderTableRenderer-0b62GYOD.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-CkLnWwCk.js → Organization-CEqmw_sb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CWVHZbhf.js → Overflow-MW-gMDO_.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BXbqETkr.js → P2P-CY0hjI_G.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser--0JR-0ex.js → PdhVaultBrowser-BbWh7Man.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Cz9_tfSy.js → Permissions-ChqEA_4Q.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dq7OWYtx.js → PersonalDataHub-B3YrbYfD.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DHPUNcc-.js → Pipeline-B8WBhhlW.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CEAbrDkS.js → Privacy-Cmb0PXNx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CJYfclXE.js → ProjectInit-hF6zW3Nz.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-oCTgPCgp.js → ProjectSettings-BFSVfpW0.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DAWkVFkc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BLTZtzTG.js → Providers-B8yux8Dx.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C0b0TQB3.js → QrScannerModal-N76cQluO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-tOl6N_gM.js → QuickAsk-ClvZ0KYR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DPzKr8yr.js → Recommend-B-GfgBpT.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-mlglCdQi.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-Cji1Vl79.js → Reputation-DJIZzQeG.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D-NERWf2.js → Row-COzxu_bb.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DzlgmAlB.js → RssFeed-CoYsJQuE.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Cs0SWSa4.js → Search-DC77Mq9S.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DL7EzcUO.js → Security-Loy2EdMO.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CT-PzgTd.js → Services-GUD07Eu0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-tByJWdJg.js → Skeleton-DWzkptYU.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Dnr_6TTl.js → Skills-CEDn1r-Z.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-4O-yq0LY.js → Sla-ub_k4pXh.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BYBVg1oR.js → SpeechSettings-D7HLX9CA.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CpOYPbE5.js → SyncSettings-DbwOvl6m.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CGVhOVh6.js → Tasks-CBQzoAu5.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BuHFxR4X.js → Templates-BNVeIhud.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DxDxHzgf.js → Tenant-PS9AzhXU.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B9g_nISg.js → Terminal-Cgb4eLB_.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DlrBC_4A.js → TimelineRenderer-CrmXMOkg.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DvBlYZ6i.js → Tokens-DD-VDHw1.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dg8G9bhF.js → Trigger-BsoN_n-n.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-COICx4E-.js → Trust-n8LmN4ZV.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B4pOBv7T.js → UkeySign-C9_7MImA.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Qt4NMA8Q.js → VideoEditing-DyBteNgn.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-X1EkQc2Z.js → Wallet-BS5ixeXI.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CyeEFBE5.js → WebAuthn-spc4UImU.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-nwzbMiVQ.js → WorkflowEditor-D7iVvaBO.js} +1 -1
- package/src/assets/web-panel/assets/chat-6CmBkHrL.js +1 -0
- package/src/assets/web-panel/assets/{colors-5Isz5lhJ.js → colors-BGoCLkxI.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DsFQS3Oy.js → compact-item-fYZBetMO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-OcEXp2uz.js → createContext-BeM-1UVj.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BzPwHnw8.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CZvC4p09.js → hasIn-CE7ZuEnV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BF1sab0Q.js → index--lap6cf7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7yeL3rf.js → index-ATcSr_2O.js} +1 -1
- package/src/assets/web-panel/assets/{index-VkFXA9N-.js → index-B2U-ObMq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSmGGg5h.js → index-B3CDgvNW.js} +1 -1
- package/src/assets/web-panel/assets/{index-jrNIrpq4.js → index-B4wiTEOM.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8ibl7bc.js → index-B8BsdNV_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOLozRk8.js → index-BEVm0lCS.js} +10 -10
- package/src/assets/web-panel/assets/{index-BPgc_bdR.js → index-BNZDILcO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTZZzlMO.js → index-BU_2qiDk.js} +1 -1
- package/src/assets/web-panel/assets/{index-Vubi6Tf5.js → index-BV551878.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXrnztXp.js → index-BfPL-m46.js} +1 -1
- package/src/assets/web-panel/assets/{index-CN8lPVpQ.js → index-BowH1eLQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgE2BZl2.js → index-BsMfoaeo.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd5b2JHB.js → index-BunaQCa0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQlGw0nQ.js → index-C0v6YKKO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Do6FJK32.js → index-C4SJVjs4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqvo1f9T.js → index-C9Y3k0HI.js} +1 -1
- package/src/assets/web-panel/assets/{index-LUdgPZg9.js → index-CIpVrf5P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpD2O7_p.js → index-CRLjEP1H.js} +1 -1
- package/src/assets/web-panel/assets/{index-r_9NG6P7.js → index-CRnUE8Cb.js} +1 -1
- package/src/assets/web-panel/assets/{index-FIgMAfBh.js → index-Cip8emOQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-RsQ1lFDV.js → index-CpIGIB85.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGJupV0S.js → index-D1r9tT3J.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpglwPWl.js → index-DT3zPSVX.js} +1 -1
- package/src/assets/web-panel/assets/index-DacrAdc-.js +1 -0
- package/src/assets/web-panel/assets/{index-CG3B2wCF.js → index-De0quFDw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxA2PDK1.js → index-DoXk5KJ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaGKOVvQ.js → index-DsvfrESp.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7CtJYXq.js → index-Dw1PyYPm.js} +1 -1
- package/src/assets/web-panel/assets/index-EHzNJozo.js +1 -0
- package/src/assets/web-panel/assets/{index-Bu9NXvIa.js → index-MtOuq2i6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsZkTaa3.js → index-Puq6Luf1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPE4zHXF.js → index-QIpnpZgz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bsb-iylv.js → index-RMZ8a7Yt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMtGRQqw.js → index-WUrJ6S1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3lWvhk4.js → index-YjcumOZ4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmVbCGx_.js → index-lJBjB1QX.js} +1 -1
- package/src/assets/web-panel/assets/{index-A5SHQW_M.js → index-ukg99D1V.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFJgt6qM.js → index-zb7rVOhV.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Cdgep9YT.js → initDefaultProps-CYHMs1ag.js} +1 -1
- package/src/assets/web-panel/assets/{motion-7yn1ACEm.js → motion-DDB2Ffk5.js} +1 -1
- package/src/assets/web-panel/assets/{move-D8o133Xc.js → move-Bg3cD-dX.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CClHIG9V.js → mtc-parser-B5nedjTS.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CLnrPE2d.js → omit-BHyuUILu.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-1CTWoZWl.js → pickAttrs-C9YQnEN9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-xNNtS4Sp.js → placementArrow-DBUFYUe7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ypzAofs7.js → responsiveObserve-nBs2Obnk.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QsrY9UwD.js → slide-DkCEaXNU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DSdThzOB.js → statusUtils-aqE1Wi6U.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B_kKM8Ob.js → styleChecker-CN5bhfHB.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CcdTBNV9.js → useFlexGapSupport-B_oX_kvJ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BuAH2XFU.js → useFs-rCuXHeme.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B3zWiG4A.js → usePersonalDataHub-DZR9Abyd.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B45Rc-CQ.js → vnode-ConCYQlX.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DaYZDU5f.js → zoom-CUdDxG5M.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +4 -1
- package/src/commands/background-session.js +2 -1
- package/src/commands/context.js +69 -2
- package/src/commands/hub.js +30 -24
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/background-agent-protocol.js +38 -0
- package/src/gateways/ws/message-dispatcher.js +1 -0
- package/src/gateways/ws/remote-session-protocol.js +65 -6
- package/src/gateways/ws/ws-server.js +8 -1
- package/src/harness/feature-flags.js +15 -8
- package/src/harness/mcp-client.js +618 -43
- package/src/lazy-dispatch.js +30 -1
- package/src/lib/agent-authority.js +22 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +161 -0
- package/src/lib/agent-team/team-runner.js +22 -0
- package/src/lib/artifact-store.js +35 -0
- package/src/lib/async-hook-supervisor.cjs +13 -2
- package/src/lib/audit-mtc.js +3 -2
- package/src/lib/background-agent-supervisor.js +104 -3
- package/src/lib/background-interaction-journal.js +407 -0
- package/src/lib/background-interaction-resolver.js +23 -34
- package/src/lib/background-session-transport.js +12 -2
- package/src/lib/capability-manifest.js +41 -3
- package/src/lib/capability-negotiation.js +1 -1
- package/src/lib/channels/channel-manager.js +33 -0
- package/src/lib/channels/telegram-channel.js +23 -5
- package/src/lib/channels/webhook-channel.js +38 -15
- package/src/lib/chrome-connector.js +169 -23
- package/src/lib/config-manager.js +23 -9
- package/src/lib/context-breakdown.js +31 -0
- package/src/lib/cowork-cron.js +274 -32
- package/src/lib/cross-chain-mtc.js +3 -2
- package/src/lib/durable-security-store.js +124 -0
- package/src/lib/event-runtime-host.js +67 -6
- package/src/lib/event-runtime-store.js +163 -0
- package/src/lib/goal-store.js +32 -12
- package/src/lib/headless-manifest.js +2 -1
- package/src/lib/hook-environment.cjs +58 -0
- package/src/lib/hook-manager.js +12 -5
- package/src/lib/hook-runner.cjs +122 -28
- package/src/lib/hook-runner.js +1 -0
- package/src/lib/hooks-v2-producers.js +36 -0
- package/src/lib/hooks-v2-runtime.js +329 -52
- package/src/lib/interaction-adapter.js +60 -19
- package/src/lib/interaction-binding.js +64 -0
- package/src/lib/json-schema-output.js +439 -14
- package/src/lib/json-schema-validate.js +514 -883
- package/src/lib/lan-pairing-tokens.js +115 -57
- package/src/lib/lsp/__tests__/lsp-client.test.js +32 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +25 -0
- package/src/lib/lsp/lsp-client.js +4 -0
- package/src/lib/lsp/lsp-manager.js +3 -0
- package/src/lib/lsp/lsp-server-registry.js +18 -0
- package/src/lib/mcp-client.js +1 -0
- package/src/lib/mcp-oauth.js +35 -21
- package/src/lib/packer/pkg-config-generator.js +1 -0
- package/src/lib/permission-decision.js +99 -0
- package/src/lib/plugin-monitor-supervisor.js +3 -0
- package/src/lib/plugin-runtime/capabilities.js +50 -0
- package/src/lib/plugin-runtime/capability-consent.js +42 -35
- package/src/lib/plugin-runtime/hooks.js +36 -9
- package/src/lib/plugin-runtime/install.js +23 -7
- package/src/lib/plugin-runtime/lsp.js +4 -0
- package/src/lib/plugin-runtime/manifest.js +50 -2
- package/src/lib/plugin-runtime/mcp.js +30 -5
- package/src/lib/plugin-runtime/monitors.js +11 -0
- package/src/lib/plugin-runtime/plugin-options.js +94 -38
- package/src/lib/plugin-runtime/policy.js +29 -2
- package/src/lib/plugin-runtime/sandbox-policy.js +84 -0
- package/src/lib/plugin-runtime/scopes.js +7 -3
- package/src/lib/plugin-runtime/trust.js +36 -31
- package/src/lib/process-execution-broker/credential-agent.js +114 -16
- package/src/lib/process-execution-broker/credential-transport-worker.js +312 -0
- package/src/lib/process-execution-broker/credential-transport.js +378 -0
- package/src/lib/process-execution-broker/index.js +408 -19
- package/src/lib/process-execution-broker/platform-sandbox.js +561 -21
- package/src/lib/process-execution-broker/windows-sandbox.ps1 +999 -0
- package/src/lib/project-mcp-trust.js +21 -26
- package/src/lib/protocol-replay.js +19 -12
- package/src/lib/settings-hooks.cjs +13 -0
- package/src/lib/skill-loader.js +83 -10
- package/src/lib/sync-credentials.js +5 -4
- package/src/lib/turn-binding.js +8 -6
- package/src/repl/agent-repl.js +113 -28
- package/src/repl/repl-turn-binding.js +5 -2
- package/src/runtime/agent-core.js +331 -26
- package/src/runtime/coding-agent-contract-shared.cjs +3 -3
- package/src/runtime/headless-runner.js +101 -4
- package/src/runtime/headless-stream.js +209 -23
- package/src/runtime/mcp-config.js +29 -17
- package/src/runtime/policies/agent-policy.js +2 -0
- package/src/workers/background-agent-worker.js +128 -13
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DvySUzxJ.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DkxCweb1.js +0 -1
- package/src/assets/web-panel/assets/Projects-ClomCrN0.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-pRgaWsUK.js +0 -4
- package/src/assets/web-panel/assets/chat-Bvl-ojg2.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CSVgo71Z.js +0 -1
- package/src/assets/web-panel/assets/index-DMIWh9fu.js +0 -1
- package/src/assets/web-panel/assets/index-ssTvV1l6.js +0 -1
package/src/lib/cowork-cron.js
CHANGED
|
@@ -30,7 +30,14 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import crypto from "node:crypto";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
existsSync,
|
|
35
|
+
mkdirSync,
|
|
36
|
+
readFileSync,
|
|
37
|
+
renameSync,
|
|
38
|
+
unlinkSync,
|
|
39
|
+
writeFileSync,
|
|
40
|
+
} from "node:fs";
|
|
34
41
|
import { join } from "node:path";
|
|
35
42
|
import { withFileLock } from "./with-file-lock.js";
|
|
36
43
|
|
|
@@ -38,7 +45,10 @@ export const _deps = {
|
|
|
38
45
|
existsSync,
|
|
39
46
|
mkdirSync,
|
|
40
47
|
readFileSync,
|
|
48
|
+
renameSync,
|
|
49
|
+
unlinkSync,
|
|
41
50
|
writeFileSync,
|
|
51
|
+
withFileLock,
|
|
42
52
|
now: () => new Date(),
|
|
43
53
|
runTask: null, // injected at runtime to avoid circular import
|
|
44
54
|
};
|
|
@@ -225,7 +235,7 @@ function _scheduleFile(cwd) {
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
/** Load all schedules from disk. Returns [] if the file doesn't exist. */
|
|
228
|
-
export function loadSchedules(cwd) {
|
|
238
|
+
export function loadSchedules(cwd, { failOnMalformed = false } = {}) {
|
|
229
239
|
const file = _scheduleFile(cwd);
|
|
230
240
|
if (!_deps.existsSync(file)) return [];
|
|
231
241
|
const raw = _deps.readFileSync(file, "utf-8");
|
|
@@ -235,20 +245,57 @@ export function loadSchedules(cwd) {
|
|
|
235
245
|
if (!trimmed) continue;
|
|
236
246
|
try {
|
|
237
247
|
out.push(JSON.parse(trimmed));
|
|
238
|
-
} catch (
|
|
239
|
-
|
|
248
|
+
} catch (cause) {
|
|
249
|
+
if (failOnMalformed) {
|
|
250
|
+
const error = new Error(
|
|
251
|
+
`cowork schedule store contains malformed JSON: ${file}`,
|
|
252
|
+
{ cause },
|
|
253
|
+
);
|
|
254
|
+
error.code = "COWORK_SCHEDULE_STORE_CORRUPT";
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
// Advisory readers can still display the remaining valid records.
|
|
240
258
|
}
|
|
241
259
|
}
|
|
242
260
|
return out;
|
|
243
261
|
}
|
|
244
262
|
|
|
245
|
-
|
|
246
|
-
export function saveSchedules(cwd, schedules) {
|
|
263
|
+
function _prepareScheduleDir(cwd) {
|
|
247
264
|
const dir = join(cwd, ".chainlesschain", "cowork");
|
|
248
265
|
_deps.mkdirSync(dir, { recursive: true });
|
|
266
|
+
return dir;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function _saveSchedulesUnlocked(cwd, schedules) {
|
|
270
|
+
_prepareScheduleDir(cwd);
|
|
249
271
|
const file = _scheduleFile(cwd);
|
|
250
272
|
const body = schedules.map((s) => JSON.stringify(s)).join("\n");
|
|
251
|
-
|
|
273
|
+
const tmp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
274
|
+
try {
|
|
275
|
+
_deps.writeFileSync(tmp, body ? body + "\n" : "", "utf-8");
|
|
276
|
+
_deps.renameSync(tmp, file);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
try {
|
|
279
|
+
_deps.unlinkSync(tmp);
|
|
280
|
+
} catch {
|
|
281
|
+
// Preserve the original persistence error.
|
|
282
|
+
}
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function _withScheduleLock(cwd, body) {
|
|
288
|
+
_prepareScheduleDir(cwd);
|
|
289
|
+
return _deps.withFileLock(_scheduleFile(cwd), body, {
|
|
290
|
+
failIfUnavailable: true,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Atomically replace the full schedule list under a fail-closed lock. */
|
|
295
|
+
export function saveSchedules(cwd, schedules) {
|
|
296
|
+
return _withScheduleLock(cwd, () =>
|
|
297
|
+
_saveSchedulesUnlocked(cwd, schedules),
|
|
298
|
+
);
|
|
252
299
|
}
|
|
253
300
|
|
|
254
301
|
// ─── CRUD ────────────────────────────────────────────────────────────────────
|
|
@@ -263,8 +310,8 @@ export function addSchedule(cwd, input) {
|
|
|
263
310
|
|
|
264
311
|
// Serialize the read-modify-write across processes (cc+cc / cc+desktop share
|
|
265
312
|
// the cowork dir) so concurrently-added schedules don't clobber each other.
|
|
266
|
-
return
|
|
267
|
-
const schedules = loadSchedules(cwd);
|
|
313
|
+
return _withScheduleLock(cwd, () => {
|
|
314
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
268
315
|
const entry = {
|
|
269
316
|
id: `sch-${crypto.randomUUID().slice(0, 12)}`,
|
|
270
317
|
cron: cron.trim(),
|
|
@@ -277,41 +324,146 @@ export function addSchedule(cwd, input) {
|
|
|
277
324
|
lastStatus: null,
|
|
278
325
|
};
|
|
279
326
|
schedules.push(entry);
|
|
280
|
-
|
|
327
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
281
328
|
return entry;
|
|
282
329
|
});
|
|
283
330
|
}
|
|
284
331
|
|
|
285
332
|
export function removeSchedule(cwd, id) {
|
|
286
|
-
return
|
|
287
|
-
const schedules = loadSchedules(cwd);
|
|
333
|
+
return _withScheduleLock(cwd, () => {
|
|
334
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
288
335
|
const idx = schedules.findIndex((s) => s.id === id);
|
|
289
336
|
if (idx === -1) return false;
|
|
290
337
|
schedules.splice(idx, 1);
|
|
291
|
-
|
|
338
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
292
339
|
return true;
|
|
293
340
|
});
|
|
294
341
|
}
|
|
295
342
|
|
|
296
343
|
export function setScheduleEnabled(cwd, id, enabled) {
|
|
297
|
-
return
|
|
298
|
-
const schedules = loadSchedules(cwd);
|
|
344
|
+
return _withScheduleLock(cwd, () => {
|
|
345
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
299
346
|
const s = schedules.find((x) => x.id === id);
|
|
300
347
|
if (!s) return false;
|
|
301
348
|
s.enabled = !!enabled;
|
|
302
|
-
|
|
349
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
303
350
|
return true;
|
|
304
351
|
});
|
|
305
352
|
}
|
|
306
353
|
|
|
307
354
|
export function updateScheduleRunState(cwd, id, { lastRunAt, lastStatus }) {
|
|
308
|
-
return
|
|
309
|
-
const schedules = loadSchedules(cwd);
|
|
355
|
+
return _withScheduleLock(cwd, () => {
|
|
356
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
310
357
|
const s = schedules.find((x) => x.id === id);
|
|
311
358
|
if (!s) return false;
|
|
312
359
|
if (lastRunAt) s.lastRunAt = lastRunAt;
|
|
313
360
|
if (lastStatus) s.lastStatus = lastStatus;
|
|
314
|
-
|
|
361
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
362
|
+
return true;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Claim one concrete cron fire before launching its side effect. The persisted
|
|
368
|
+
* delivery id prevents two scheduler processes from running the same fire; an
|
|
369
|
+
* expired claim can be recovered after a crashed owner.
|
|
370
|
+
*/
|
|
371
|
+
export function claimScheduleFire(
|
|
372
|
+
cwd,
|
|
373
|
+
id,
|
|
374
|
+
deliveryId,
|
|
375
|
+
{ ownerId, now = _deps.now(), leaseMs = 120_000 } = {},
|
|
376
|
+
) {
|
|
377
|
+
if (!ownerId || !deliveryId) {
|
|
378
|
+
throw new Error("claimScheduleFire requires ownerId and deliveryId");
|
|
379
|
+
}
|
|
380
|
+
return _withScheduleLock(cwd, () => {
|
|
381
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
382
|
+
const schedule = schedules.find((entry) => entry.id === id);
|
|
383
|
+
if (!schedule) return null;
|
|
384
|
+
if (schedule.lastDeliveryId === deliveryId) return null;
|
|
385
|
+
|
|
386
|
+
const nowMs = now.getTime();
|
|
387
|
+
const active = schedule.activeDelivery;
|
|
388
|
+
const expiresAt = Date.parse(active?.leaseExpiresAt || "");
|
|
389
|
+
if (active && Number.isFinite(expiresAt) && expiresAt > nowMs) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const fence =
|
|
394
|
+
Math.max(
|
|
395
|
+
Number(schedule.deliveryFence) || 0,
|
|
396
|
+
Number(active?.fence) || 0,
|
|
397
|
+
) + 1;
|
|
398
|
+
const claim = {
|
|
399
|
+
deliveryId,
|
|
400
|
+
ownerId,
|
|
401
|
+
fence,
|
|
402
|
+
claimedAt: now.toISOString(),
|
|
403
|
+
leaseExpiresAt: new Date(nowMs + Math.max(1000, leaseMs)).toISOString(),
|
|
404
|
+
};
|
|
405
|
+
schedule.deliveryFence = fence;
|
|
406
|
+
schedule.activeDelivery = claim;
|
|
407
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
408
|
+
return { ...claim };
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Renew a live claim. A stale owner can never renew after its fence is stolen. */
|
|
413
|
+
export function renewScheduleFire(
|
|
414
|
+
cwd,
|
|
415
|
+
id,
|
|
416
|
+
claim,
|
|
417
|
+
{ now = _deps.now(), leaseMs = 120_000 } = {},
|
|
418
|
+
) {
|
|
419
|
+
return _withScheduleLock(cwd, () => {
|
|
420
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
421
|
+
const schedule = schedules.find((entry) => entry.id === id);
|
|
422
|
+
const active = schedule?.activeDelivery;
|
|
423
|
+
if (
|
|
424
|
+
!active ||
|
|
425
|
+
active.deliveryId !== claim?.deliveryId ||
|
|
426
|
+
active.ownerId !== claim?.ownerId ||
|
|
427
|
+
active.fence !== claim?.fence
|
|
428
|
+
) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
active.leaseExpiresAt = new Date(
|
|
432
|
+
now.getTime() + Math.max(1000, leaseMs),
|
|
433
|
+
).toISOString();
|
|
434
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
435
|
+
return true;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Commit the delivery outcome only when the owner/fence still matches. This is
|
|
441
|
+
* the scheduler equivalent of compare-and-swap: a late crashed owner cannot
|
|
442
|
+
* overwrite the successor's task state.
|
|
443
|
+
*/
|
|
444
|
+
export function settleScheduleFire(
|
|
445
|
+
cwd,
|
|
446
|
+
id,
|
|
447
|
+
claim,
|
|
448
|
+
{ lastRunAt, lastStatus } = {},
|
|
449
|
+
) {
|
|
450
|
+
return _withScheduleLock(cwd, () => {
|
|
451
|
+
const schedules = loadSchedules(cwd, { failOnMalformed: true });
|
|
452
|
+
const schedule = schedules.find((entry) => entry.id === id);
|
|
453
|
+
const active = schedule?.activeDelivery;
|
|
454
|
+
if (
|
|
455
|
+
!active ||
|
|
456
|
+
active.deliveryId !== claim?.deliveryId ||
|
|
457
|
+
active.ownerId !== claim?.ownerId ||
|
|
458
|
+
active.fence !== claim?.fence
|
|
459
|
+
) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
schedule.lastDeliveryId = claim.deliveryId;
|
|
463
|
+
schedule.lastRunAt = lastRunAt || _deps.now().toISOString();
|
|
464
|
+
schedule.lastStatus = lastStatus || "completed";
|
|
465
|
+
schedule.activeDelivery = null;
|
|
466
|
+
_saveSchedulesUnlocked(cwd, schedules);
|
|
315
467
|
return true;
|
|
316
468
|
});
|
|
317
469
|
}
|
|
@@ -329,6 +481,10 @@ export class CoworkCronScheduler {
|
|
|
329
481
|
this._intervalPinned = typeof options.intervalMs === "number";
|
|
330
482
|
this.intervalMs = options.intervalMs || 60_000;
|
|
331
483
|
this.onEvent = options.onEvent || null; // (event) => void
|
|
484
|
+
this.ownerId =
|
|
485
|
+
options.ownerId ||
|
|
486
|
+
`cowork-cron:${process.pid}:${crypto.randomUUID().slice(0, 12)}`;
|
|
487
|
+
this.leaseMs = Math.max(1000, Number(options.leaseMs) || 120_000);
|
|
332
488
|
this._timer = null;
|
|
333
489
|
this._firedKeys = new Set();
|
|
334
490
|
this._running = new Set();
|
|
@@ -416,9 +572,27 @@ export class CoworkCronScheduler {
|
|
|
416
572
|
const isDue = matcher(now);
|
|
417
573
|
if (!isDue) continue;
|
|
418
574
|
|
|
575
|
+
let claim;
|
|
576
|
+
try {
|
|
577
|
+
claim = claimScheduleFire(this.cwd, s.id, fireKey, {
|
|
578
|
+
ownerId: this.ownerId,
|
|
579
|
+
now,
|
|
580
|
+
leaseMs: this.leaseMs,
|
|
581
|
+
});
|
|
582
|
+
} catch (err) {
|
|
583
|
+
this._emit({
|
|
584
|
+
type: "schedule-claim-failed",
|
|
585
|
+
id: s.id,
|
|
586
|
+
deliveryId: fireKey,
|
|
587
|
+
error: err.message,
|
|
588
|
+
});
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
419
592
|
this._firedKeys.add(fireKey);
|
|
593
|
+
if (!claim) continue;
|
|
420
594
|
this._running.add(s.id);
|
|
421
|
-
this._runSchedule(s).finally(() => {
|
|
595
|
+
this._runSchedule(s, claim).finally(() => {
|
|
422
596
|
this._running.delete(s.id);
|
|
423
597
|
});
|
|
424
598
|
}
|
|
@@ -429,12 +603,25 @@ export class CoworkCronScheduler {
|
|
|
429
603
|
}
|
|
430
604
|
}
|
|
431
605
|
|
|
432
|
-
async _runSchedule(schedule) {
|
|
606
|
+
async _runSchedule(schedule, claim) {
|
|
433
607
|
const runTask = _deps.runTask;
|
|
434
608
|
if (typeof runTask !== "function") {
|
|
609
|
+
try {
|
|
610
|
+
settleScheduleFire(this.cwd, schedule.id, claim, {
|
|
611
|
+
lastStatus: "failed",
|
|
612
|
+
});
|
|
613
|
+
} catch (err) {
|
|
614
|
+
this._emit({
|
|
615
|
+
type: "schedule-settlement-failed",
|
|
616
|
+
id: schedule.id,
|
|
617
|
+
deliveryId: claim.deliveryId,
|
|
618
|
+
error: err.message,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
435
621
|
this._emit({
|
|
436
622
|
type: "run-error",
|
|
437
623
|
id: schedule.id,
|
|
624
|
+
deliveryId: claim.deliveryId,
|
|
438
625
|
error: "runTask not injected",
|
|
439
626
|
});
|
|
440
627
|
return;
|
|
@@ -444,35 +631,90 @@ export class CoworkCronScheduler {
|
|
|
444
631
|
id: schedule.id,
|
|
445
632
|
cron: schedule.cron,
|
|
446
633
|
templateId: schedule.templateId,
|
|
634
|
+
deliveryId: claim.deliveryId,
|
|
447
635
|
});
|
|
636
|
+
|
|
637
|
+
const renewal = setInterval(() => {
|
|
638
|
+
try {
|
|
639
|
+
const renewed = renewScheduleFire(this.cwd, schedule.id, claim, {
|
|
640
|
+
leaseMs: this.leaseMs,
|
|
641
|
+
});
|
|
642
|
+
if (!renewed) {
|
|
643
|
+
this._emit({
|
|
644
|
+
type: "schedule-lease-lost",
|
|
645
|
+
id: schedule.id,
|
|
646
|
+
deliveryId: claim.deliveryId,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
} catch (err) {
|
|
650
|
+
this._emit({
|
|
651
|
+
type: "schedule-lease-renewal-failed",
|
|
652
|
+
id: schedule.id,
|
|
653
|
+
deliveryId: claim.deliveryId,
|
|
654
|
+
error: err.message,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}, Math.max(1000, Math.floor(this.leaseMs / 3)));
|
|
658
|
+
renewal.unref?.();
|
|
659
|
+
|
|
660
|
+
let result = null;
|
|
661
|
+
let failure = null;
|
|
448
662
|
try {
|
|
449
|
-
|
|
663
|
+
result = await runTask({
|
|
450
664
|
templateId: schedule.templateId,
|
|
451
665
|
userMessage: schedule.userMessage,
|
|
452
666
|
files: schedule.files,
|
|
453
667
|
cwd: this.cwd,
|
|
668
|
+
scheduleId: schedule.id,
|
|
669
|
+
deliveryId: claim.deliveryId,
|
|
454
670
|
});
|
|
455
|
-
|
|
671
|
+
} catch (err) {
|
|
672
|
+
failure = err;
|
|
673
|
+
} finally {
|
|
674
|
+
clearInterval(renewal);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let settled;
|
|
678
|
+
try {
|
|
679
|
+
settled = settleScheduleFire(this.cwd, schedule.id, claim, {
|
|
456
680
|
lastRunAt: _deps.now().toISOString(),
|
|
457
|
-
lastStatus: result?.status || "completed",
|
|
681
|
+
lastStatus: failure ? "failed" : result?.status || "completed",
|
|
458
682
|
});
|
|
683
|
+
} catch (err) {
|
|
459
684
|
this._emit({
|
|
460
|
-
type: "schedule-
|
|
685
|
+
type: "schedule-settlement-failed",
|
|
461
686
|
id: schedule.id,
|
|
462
|
-
|
|
463
|
-
|
|
687
|
+
deliveryId: claim.deliveryId,
|
|
688
|
+
error: err.message,
|
|
464
689
|
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (!settled) {
|
|
693
|
+
this._emit({
|
|
694
|
+
type: "schedule-settlement-rejected",
|
|
695
|
+
id: schedule.id,
|
|
696
|
+
deliveryId: claim.deliveryId,
|
|
469
697
|
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (failure) {
|
|
470
702
|
this._emit({
|
|
471
703
|
type: "schedule-failed",
|
|
472
704
|
id: schedule.id,
|
|
473
|
-
|
|
705
|
+
deliveryId: claim.deliveryId,
|
|
706
|
+
error: failure.message,
|
|
474
707
|
});
|
|
708
|
+
return;
|
|
475
709
|
}
|
|
710
|
+
|
|
711
|
+
this._emit({
|
|
712
|
+
type: "schedule-completed",
|
|
713
|
+
id: schedule.id,
|
|
714
|
+
deliveryId: claim.deliveryId,
|
|
715
|
+
taskId: result?.taskId,
|
|
716
|
+
status: result?.status,
|
|
717
|
+
});
|
|
476
718
|
}
|
|
477
719
|
}
|
|
478
720
|
|
|
@@ -536,11 +536,12 @@ export function closeBatch(dir, opts = {}) {
|
|
|
536
536
|
// Serialize the whole staging-scan → batch-write → staging-cleanup section
|
|
537
537
|
// across processes. Without it, two concurrent closes both read the same
|
|
538
538
|
// staged ops, allocate the same per-pair sequence (namespace collision), and
|
|
539
|
-
// batch the same ops twice before clearing staging.
|
|
540
|
-
//
|
|
539
|
+
// batch the same ops twice before clearing staging. Ledger state is critical:
|
|
540
|
+
// bounded contention must fail closed rather than assemble an unlocked batch.
|
|
541
541
|
ensureDirs(dir);
|
|
542
542
|
return withFileLock(batchesDir(dir), () => _closeBatchImpl(dir, opts), {
|
|
543
543
|
timeoutMs: 15000,
|
|
544
|
+
failIfUnavailable: true,
|
|
544
545
|
});
|
|
545
546
|
}
|
|
546
547
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-safe, fail-closed persistence for security metadata.
|
|
3
|
+
*
|
|
4
|
+
* Trust, consent and credential references are durable security decisions
|
|
5
|
+
* shared by every CLI process. A plain read-modify-write can silently lose a
|
|
6
|
+
* grant or a revocation, while treating a corrupt file as an empty object can
|
|
7
|
+
* overwrite the only evidence that the store is unavailable. This helper
|
|
8
|
+
* keeps the entire mutation under the canonical cross-process lock and
|
|
9
|
+
* replaces the JSON file atomically.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
16
|
+
|
|
17
|
+
function storeError(label, operation, filePath, cause) {
|
|
18
|
+
const detail = cause?.message || String(cause || "unknown error");
|
|
19
|
+
const error = new Error(
|
|
20
|
+
`${label} store ${operation} failed (${filePath}): ${detail}`,
|
|
21
|
+
{ cause },
|
|
22
|
+
);
|
|
23
|
+
error.name = "DurableSecurityStoreError";
|
|
24
|
+
error.code = `DURABLE_SECURITY_STORE_${operation.toUpperCase()}_FAILED`;
|
|
25
|
+
error.filePath = filePath;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readSecurityStore(filePath, label) {
|
|
30
|
+
let serialized;
|
|
31
|
+
try {
|
|
32
|
+
serialized = fs.readFileSync(filePath, "utf8");
|
|
33
|
+
} catch (cause) {
|
|
34
|
+
if (cause?.code === "ENOENT") return {};
|
|
35
|
+
throw storeError(label, "read", filePath, cause);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(serialized);
|
|
40
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41
|
+
throw new TypeError("top-level value must be an object");
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
} catch (cause) {
|
|
45
|
+
throw storeError(label, "corrupt", filePath, cause);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function writeSecurityStore(filePath, label, store) {
|
|
50
|
+
const directory = path.dirname(filePath);
|
|
51
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
52
|
+
const temporaryPath = path.join(
|
|
53
|
+
directory,
|
|
54
|
+
`.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
|
|
55
|
+
);
|
|
56
|
+
let descriptor = null;
|
|
57
|
+
let renamed = false;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
descriptor = fs.openSync(temporaryPath, "wx", 0o600);
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
descriptor,
|
|
63
|
+
`${JSON.stringify(store, null, 2)}\n`,
|
|
64
|
+
"utf8",
|
|
65
|
+
);
|
|
66
|
+
fs.fsyncSync(descriptor);
|
|
67
|
+
fs.closeSync(descriptor);
|
|
68
|
+
descriptor = null;
|
|
69
|
+
fs.renameSync(temporaryPath, filePath);
|
|
70
|
+
renamed = true;
|
|
71
|
+
|
|
72
|
+
if (process.platform !== "win32") {
|
|
73
|
+
const directoryDescriptor = fs.openSync(directory, "r");
|
|
74
|
+
try {
|
|
75
|
+
fs.fsyncSync(directoryDescriptor);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.closeSync(directoryDescriptor);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
if (descriptor != null) {
|
|
82
|
+
try {
|
|
83
|
+
fs.closeSync(descriptor);
|
|
84
|
+
} catch {
|
|
85
|
+
// Preserve the original persistence error.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!renamed) {
|
|
89
|
+
try {
|
|
90
|
+
fs.unlinkSync(temporaryPath);
|
|
91
|
+
} catch {
|
|
92
|
+
// Best-effort orphan cleanup.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw storeError(label, "write", filePath, cause);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function mutateSecurityStore(
|
|
100
|
+
filePath,
|
|
101
|
+
label,
|
|
102
|
+
mutator,
|
|
103
|
+
{ lock = withFileLock, timeoutMs = 2000, staleMs = 30000 } = {},
|
|
104
|
+
) {
|
|
105
|
+
// withFileLock expects the target's parent to exist. Directory creation is
|
|
106
|
+
// idempotent and safe outside the critical section.
|
|
107
|
+
try {
|
|
108
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw storeError(label, "prepare", filePath, cause);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return lock(
|
|
114
|
+
filePath,
|
|
115
|
+
() => {
|
|
116
|
+
const current = readSecurityStore(filePath, label);
|
|
117
|
+
const draft = structuredClone(current);
|
|
118
|
+
const result = mutator(draft);
|
|
119
|
+
writeSecurityStore(filePath, label, draft);
|
|
120
|
+
return result;
|
|
121
|
+
},
|
|
122
|
+
{ timeoutMs, staleMs, failIfUnavailable: true },
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -35,12 +35,24 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
35
35
|
worker = null,
|
|
36
36
|
setTimeoutFn = setTimeout,
|
|
37
37
|
clearTimeoutFn = clearTimeout,
|
|
38
|
+
id = store.owner,
|
|
39
|
+
role = process.env.CC_EVENT_RUNTIME_HOST_ROLE || "cli",
|
|
40
|
+
pid = process.pid,
|
|
41
|
+
heartbeatStaleMs = null,
|
|
38
42
|
} = {}) {
|
|
39
43
|
super();
|
|
40
44
|
this.store = store;
|
|
41
45
|
this.intervalMs = Math.max(1, Number(intervalMs) || 1000);
|
|
42
46
|
this.setTimeoutFn = setTimeoutFn;
|
|
43
47
|
this.clearTimeoutFn = clearTimeoutFn;
|
|
48
|
+
this.id = String(id || store.owner || `${process.pid}`);
|
|
49
|
+
this.role = String(role || "cli");
|
|
50
|
+
this.pid = Number.isFinite(Number(pid)) ? Number(pid) : null;
|
|
51
|
+
this.startedAt = null;
|
|
52
|
+
this.heartbeatStaleMs =
|
|
53
|
+
heartbeatStaleMs == null
|
|
54
|
+
? Math.max(5000, this.intervalMs * 3)
|
|
55
|
+
: Math.max(1, Number(heartbeatStaleMs) || 5000);
|
|
44
56
|
this.handlers = new Map();
|
|
45
57
|
this.running = false;
|
|
46
58
|
this.timer = null;
|
|
@@ -58,6 +70,25 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
58
70
|
});
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
_reportHost(state, { stats = this.lastStats, error = this.lastError } = {}) {
|
|
74
|
+
if (typeof this.store.reportHost !== "function") return null;
|
|
75
|
+
try {
|
|
76
|
+
return this.store.reportHost({
|
|
77
|
+
id: this.id,
|
|
78
|
+
pid: this.pid,
|
|
79
|
+
role: this.role,
|
|
80
|
+
state,
|
|
81
|
+
startedAt: this.startedAt,
|
|
82
|
+
staleAfterMs: this.heartbeatStaleMs,
|
|
83
|
+
lastStats: stats,
|
|
84
|
+
lastError: error?.message || error || null,
|
|
85
|
+
});
|
|
86
|
+
} catch (reportError) {
|
|
87
|
+
this.emit("host-status-error", reportError);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
61
92
|
registerHandler(
|
|
62
93
|
handler,
|
|
63
94
|
{ queue = "inbox", type = "*", origin = "*" } = {},
|
|
@@ -125,11 +156,13 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
125
156
|
.then((stats) => {
|
|
126
157
|
this.lastStats = stats;
|
|
127
158
|
this.lastError = null;
|
|
159
|
+
this._reportHost("running", { stats, error: null });
|
|
128
160
|
this.emit("tick", stats);
|
|
129
161
|
return stats;
|
|
130
162
|
})
|
|
131
163
|
.catch((error) => {
|
|
132
164
|
this.lastError = error;
|
|
165
|
+
this._reportHost("running", { error });
|
|
133
166
|
this.emit("runtime-error", error);
|
|
134
167
|
throw error;
|
|
135
168
|
})
|
|
@@ -158,6 +191,8 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
158
191
|
start({ immediate = true } = {}) {
|
|
159
192
|
if (this.running) return false;
|
|
160
193
|
this.running = true;
|
|
194
|
+
this.startedAt = Date.now();
|
|
195
|
+
this._reportHost("running", { stats: null, error: null });
|
|
161
196
|
if (immediate) {
|
|
162
197
|
void this.runOnce()
|
|
163
198
|
.catch(() => {})
|
|
@@ -169,18 +204,42 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
169
204
|
return true;
|
|
170
205
|
}
|
|
171
206
|
|
|
172
|
-
async stop({ drain = true } = {}) {
|
|
207
|
+
async stop({ drain = true, maxDrainTicks = 10 } = {}) {
|
|
173
208
|
const wasRunning = this.running;
|
|
174
209
|
this.running = false;
|
|
175
210
|
if (this.timer != null) {
|
|
176
211
|
this.clearTimeoutFn(this.timer);
|
|
177
212
|
this.timer = null;
|
|
178
213
|
}
|
|
179
|
-
if (drain && this.inFlight) {
|
|
214
|
+
if (drain && (wasRunning || this.inFlight)) {
|
|
215
|
+
const maxTicks = Math.max(1, Number(maxDrainTicks) || 10);
|
|
216
|
+
for (let tick = 0; tick < maxTicks; tick += 1) {
|
|
217
|
+
let stats;
|
|
218
|
+
try {
|
|
219
|
+
stats = this.inFlight ? await this.inFlight : await this.runOnce();
|
|
220
|
+
} catch {
|
|
221
|
+
// Error already exposed through runtime-error/lastError. A later
|
|
222
|
+
// process can recover the durable records after the lease expires.
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
const claimed =
|
|
226
|
+
Number(stats?.inboxClaimed || 0) +
|
|
227
|
+
Number(stats?.outboxClaimed || 0);
|
|
228
|
+
if (claimed === 0) break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (typeof this.store.stopHost === "function") {
|
|
180
232
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
this.store.stopHost(this.id, {
|
|
234
|
+
pid: this.pid,
|
|
235
|
+
role: this.role,
|
|
236
|
+
startedAt: this.startedAt,
|
|
237
|
+
staleAfterMs: this.heartbeatStaleMs,
|
|
238
|
+
lastStats: this.lastStats,
|
|
239
|
+
lastError: this.lastError?.message || null,
|
|
240
|
+
});
|
|
241
|
+
} catch (reportError) {
|
|
242
|
+
this.emit("host-status-error", reportError);
|
|
184
243
|
}
|
|
185
244
|
}
|
|
186
245
|
if (wasRunning) this.emit("stopped");
|
|
@@ -190,6 +249,9 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
190
249
|
status() {
|
|
191
250
|
return {
|
|
192
251
|
running: this.running,
|
|
252
|
+
id: this.id,
|
|
253
|
+
role: this.role,
|
|
254
|
+
pid: this.pid,
|
|
193
255
|
inFlight: this.inFlight != null,
|
|
194
256
|
handlerRoutes: this.handlers.size,
|
|
195
257
|
lastStats: this.lastStats,
|
|
@@ -222,4 +284,3 @@ export async function stopDefaultEventRuntimeHost(options) {
|
|
|
222
284
|
export function _resetDefaultEventRuntimeHostForTests() {
|
|
223
285
|
defaultHost = null;
|
|
224
286
|
}
|
|
225
|
-
|