chainlesschain 0.162.158 → 0.162.160
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-CkRHjW8e.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-BP3MeNi2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-CjbjQsOr.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-i0adH1M8.js} +5 -5
- package/src/assets/web-panel/assets/Artifacts-CCCG2GOU.js +1 -0
- package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DrsgvwHC.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-DkXNkbZM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-DarTWCex.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DaDzhcto.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-C4EzGw6V.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-lkavIWMS.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-gddNgaQi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-BKldFWf9.js} +1 -1
- package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-ChR-brDs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DlHtZX86.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-BAhfcBpC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-gIrKkm6m.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BYnzLBO3.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-ByUDIljI.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DOdNbHFp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-BJHaNWD2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-Drm_tClz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-CA1WLzGN.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-Di1SKjNO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-CubPwv_N.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-CsUFe5bU.js} +1 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +1 -0
- package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-D-Qx9dhO.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-_YMYaU4n.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-J7Yzh-K_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-oMTSYfGY.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-DKUqYDaF.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-BEv3Gx61.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-CtA8XD2y.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-ug7c1mMj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-WJaMs4i2.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-DXnK_-ru.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-C4Ev7726.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-BMFb9_zk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Bl93nvBX.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-BN2W5cl6.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-h1A0XB2a.js → OrderTableRenderer-Dxc9oN4o.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-CAs48637.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-Bhq27Zoc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-D61-2542.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C1j4iUxz.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-rJ2-fScM.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-DFIlTok6.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-BjIgEWoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-BQoH5wcX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-CYR_-_Ub.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-nkatCq-9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-BnKM9fsF.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Cfe82dbz.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-r1RbPufV.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-CjCbM2WE.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-BuMGPH6V.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-Bmj3NY1F.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-BIsNcE_1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-BObmojD2.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-BRmbkQNS.js} +1 -1
- package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-_rF3DRpY.js} +2 -2
- package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D0If9ZV4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-o7zIksUg.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-oUR7zi9u.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-fqKyGi03.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-B1wZ_gxx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-B3qJWYXq.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-BH5_dUfr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-CCIVpjRP.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-2GZtTGnB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-BlMkfs_u.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-U6hi5OMc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-C-_BerGJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DaKzBgN3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-CLvcsco3.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-BFn_Y1zZ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-B0V18dLT.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-BbmP7Imu.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-Dewj_0kt.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-B1OIpZnh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-CSGNVdri.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-8S0vcg0u.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-vZyc27TN.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DWw3-UhA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-B4pP2ycr.js} +1 -1
- package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-BDHyovnK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-BKEmQQvR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BQssf0EQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-BZnauCqN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-BdyokEau.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-BeBv896m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-BjC-TrOb.js} +1 -1
- package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-BkWd6YL7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-BoIPHP4R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-BuB5P0TR.js} +1 -1
- package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-BwKrTVV0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C6r27A-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-CB2MOS_8.js} +3 -3
- package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CFQWZvak.js} +1 -1
- package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-CI3slBKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCyP071m.js → index-CJgKP2cm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-CLPr2vC_.js} +1 -1
- package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-CbZqw7pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-CfEvacz7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-CwXBblm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-D1i_8oop.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-D8qXVfoV.js} +1 -1
- package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-DKwl1AkQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23L1V76.js → index-DLAvUtOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-DLgkAhEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-DPgY5GLy.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-Dc40pX3F.js} +1 -1
- package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-DcfLXAW7.js} +1 -1
- package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-DeCS12Mh.js} +1 -1
- package/src/assets/web-panel/assets/{index-B30NvR21.js → index-Dg0Izipr.js} +1 -1
- package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
- package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-DtHWHJnj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-Q6oyLnKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-dxocDVD_.js} +1 -1
- package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-t-q3rkVm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-tr1x96W9.js} +1 -1
- package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
- package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-xx5M5Rbj.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-Di5YFId0.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-BVSN0aeL.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-DHNiaP2l.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-xJPf3gQD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-C7qzUdkN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-pSpjg3Le.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-cs_jL_UC.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-DkS_Zs4r.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-B5O1Kpxo.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-BX02GnZp.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-EmBYRftd.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-CfP7M1m7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-7xh0If_O.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-DmitvTR2.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +17 -3
- package/src/commands/agent.js +72 -6
- package/src/commands/background-session.js +173 -3
- package/src/commands/browse.js +86 -0
- package/src/commands/cloud.js +232 -0
- package/src/commands/doctor.js +61 -6
- package/src/commands/mcp.js +107 -0
- package/src/commands/plugin.js +63 -1
- package/src/commands/routine.js +367 -0
- package/src/commands/session.js +396 -0
- package/src/data/changelog.json +12 -2
- package/src/harness/jsonl-session-store.js +152 -3
- package/src/harness/mcp-client.js +50 -0
- package/src/harness/session-index.js +295 -0
- package/src/harness/session-mirror.js +142 -0
- package/src/harness/transcript-integrity.js +182 -0
- package/src/index.js +8 -0
- package/src/lib/agent-sandbox.js +68 -0
- package/src/lib/agents.js +12 -4
- package/src/lib/api-key-helper.js +62 -0
- package/src/lib/background-agent-supervisor.js +158 -2
- package/src/lib/channels/channel-manager.js +126 -0
- package/src/lib/channels/telegram-channel.js +112 -0
- package/src/lib/channels/webhook-channel.js +140 -0
- package/src/lib/chrome-connector.js +386 -0
- package/src/lib/cloud/bundle.js +110 -0
- package/src/lib/cloud/cloud-client.js +152 -0
- package/src/lib/computer-use/authorization.js +173 -0
- package/src/lib/computer-use/computer-use-server.js +276 -0
- package/src/lib/computer-use/control-backend.js +150 -0
- package/src/lib/dependency-install-policy.js +130 -0
- package/src/lib/doctor-checkup.js +652 -0
- package/src/lib/exit-codes.cjs +77 -0
- package/src/lib/headless-manifest.js +175 -0
- package/src/lib/hook-runner.cjs +70 -4
- package/src/lib/ide-context-redaction.js +130 -0
- package/src/lib/ide-context.js +89 -13
- package/src/lib/jsonl-session-store.js +6 -0
- package/src/lib/llm-config-defaults.js +24 -8
- package/src/lib/lsp/lsp-server-registry.js +48 -21
- package/src/lib/plugin-runtime/governance.js +174 -0
- package/src/lib/pr-link-ledger.js +221 -0
- package/src/lib/project-mcp-trust.js +87 -0
- package/src/lib/routine-store.js +352 -0
- package/src/lib/sensitive-file-guard.js +51 -0
- package/src/lib/session-store-guard.js +40 -0
- package/src/lib/session-usage.js +318 -2
- package/src/lib/sub-agent-context.js +30 -3
- package/src/repl/agent-repl.js +361 -3
- package/src/repl/bg-dashboard.js +355 -0
- package/src/repl/fast-mode.js +103 -0
- package/src/repl/fullscreen-tui.js +197 -0
- package/src/repl/slash-command-registry.js +11 -3
- package/src/repl/voice-dictation.js +132 -0
- package/src/runtime/agent-core.js +344 -7
- package/src/runtime/coding-agent-contract-shared.cjs +107 -2
- package/src/runtime/coding-agent-policy.cjs +17 -0
- package/src/runtime/headless-runner.js +137 -14
- package/src/runtime/headless-stream.js +43 -9
- package/src/runtime/mcp-config.js +32 -1
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BAV9V-t-.js +0 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-DxxEdn2r.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CVtTqgtg.js +0 -1
- package/src/assets/web-panel/assets/Projects-COATmHxF.js +0 -1
- package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +0 -1
- package/src/assets/web-panel/assets/index-B1_pRTtB.js +0 -1
- package/src/assets/web-panel/assets/index-TMM9FuW-.js +0 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routines (gap-2026-07-11 P1#8) — the durable layer above `cc agenda`:
|
|
3
|
+
* named, enable/disable-able agent tasks with four trigger kinds, a run
|
|
4
|
+
* history, and per-run result/usage summaries.
|
|
5
|
+
*
|
|
6
|
+
* trigger kinds:
|
|
7
|
+
* cron — 5-field schedule, fired by the `cc routine run` driver
|
|
8
|
+
* once — a single ISO/epoch time, fired by the driver then disabled
|
|
9
|
+
* webhook — fired externally via `cc routine trigger <id>` (that command
|
|
10
|
+
* IS the API/webhook entry point — wire any HTTP receiver or
|
|
11
|
+
* inbound channel to invoke it)
|
|
12
|
+
* github — driver polls `gh api repos/<repo>/events` and fires when new
|
|
13
|
+
* events of the requested types appear (needs gh auth)
|
|
14
|
+
*
|
|
15
|
+
* Storage: ~/.chainlesschain/routines/
|
|
16
|
+
* routines.json definitions (id → routine)
|
|
17
|
+
* runs.jsonl append-only run history
|
|
18
|
+
* logs/<runId>.log full agent output per run
|
|
19
|
+
*
|
|
20
|
+
* Execution is delegated to the injected `runAgent` (production: spawn
|
|
21
|
+
* `cc agent -p <prompt> --output-format json` and parse the result envelope
|
|
22
|
+
* for usage/cost). Everything here is dependency-injected and unit-testable.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import os from "node:os";
|
|
28
|
+
import { randomBytes } from "node:crypto";
|
|
29
|
+
import { parseCron, nextCronTime } from "./agent-schedule-store.js";
|
|
30
|
+
|
|
31
|
+
export const ROUTINE_TRIGGER_KINDS = Object.freeze([
|
|
32
|
+
"cron",
|
|
33
|
+
"once",
|
|
34
|
+
"webhook",
|
|
35
|
+
"github",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export function routinesDir(homedir = os.homedir()) {
|
|
39
|
+
return path.join(homedir, ".chainlesschain", "routines");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shortId() {
|
|
43
|
+
return `rt-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseWhen(value) {
|
|
47
|
+
if (value == null) return NaN;
|
|
48
|
+
const n = Number(value);
|
|
49
|
+
if (Number.isFinite(n)) return n;
|
|
50
|
+
return Date.parse(String(value));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class RoutineStore {
|
|
54
|
+
constructor({ dir = null, now = () => Date.now() } = {}) {
|
|
55
|
+
this.dir = dir || routinesDir();
|
|
56
|
+
this._now = typeof now === "function" ? now : () => now;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_routinesFile() {
|
|
60
|
+
return path.join(this.dir, "routines.json");
|
|
61
|
+
}
|
|
62
|
+
_runsFile() {
|
|
63
|
+
return path.join(this.dir, "runs.jsonl");
|
|
64
|
+
}
|
|
65
|
+
logFile(runId) {
|
|
66
|
+
return path.join(this.dir, "logs", `${runId}.log`);
|
|
67
|
+
}
|
|
68
|
+
_ensureDir() {
|
|
69
|
+
fs.mkdirSync(path.join(this.dir, "logs"), { recursive: true, mode: 0o700 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_readRoutines() {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(fs.readFileSync(this._routinesFile(), "utf-8"));
|
|
75
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
76
|
+
} catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_writeRoutines(map) {
|
|
81
|
+
this._ensureDir();
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
this._routinesFile(),
|
|
84
|
+
JSON.stringify(map, null, 2),
|
|
85
|
+
"utf-8",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
|
|
91
|
+
*/
|
|
92
|
+
create(def = {}) {
|
|
93
|
+
const name = String(def.name || "").trim();
|
|
94
|
+
const prompt = String(def.prompt || "").trim();
|
|
95
|
+
const trigger = def.trigger || {};
|
|
96
|
+
if (!name) throw new Error("routine needs a name");
|
|
97
|
+
if (!prompt) throw new Error("routine needs a prompt");
|
|
98
|
+
if (!ROUTINE_TRIGGER_KINDS.includes(trigger.kind)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (trigger.kind === "cron") {
|
|
104
|
+
if (!parseCron(trigger.cron || "")) {
|
|
105
|
+
throw new Error(`invalid cron expression: ${trigger.cron}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (trigger.kind === "once") {
|
|
109
|
+
const at = parseWhen(trigger.at);
|
|
110
|
+
if (!Number.isFinite(at)) {
|
|
111
|
+
throw new Error(`invalid --at time: ${trigger.at}`);
|
|
112
|
+
}
|
|
113
|
+
trigger.at = at;
|
|
114
|
+
}
|
|
115
|
+
if (trigger.kind === "github" && !trigger.repo) {
|
|
116
|
+
throw new Error("github trigger needs --repo <owner/name>");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const routine = {
|
|
120
|
+
id: shortId(),
|
|
121
|
+
name,
|
|
122
|
+
prompt,
|
|
123
|
+
trigger,
|
|
124
|
+
enabled: true,
|
|
125
|
+
createdAt: this._now(),
|
|
126
|
+
lastFiredAt: null,
|
|
127
|
+
lastSeenGithubEventId: null,
|
|
128
|
+
};
|
|
129
|
+
const map = this._readRoutines();
|
|
130
|
+
map[routine.id] = routine;
|
|
131
|
+
this._writeRoutines(map);
|
|
132
|
+
return routine;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
list() {
|
|
136
|
+
return Object.values(this._readRoutines()).sort(
|
|
137
|
+
(a, b) => (b.createdAt || 0) - (a.createdAt || 0),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get(id) {
|
|
142
|
+
const map = this._readRoutines();
|
|
143
|
+
if (map[id]) return map[id];
|
|
144
|
+
// prefix / name match for CLI ergonomics
|
|
145
|
+
const matches = Object.values(map).filter(
|
|
146
|
+
(r) => r.id.startsWith(id) || r.name === id,
|
|
147
|
+
);
|
|
148
|
+
if (matches.length === 1) return matches[0];
|
|
149
|
+
if (matches.length > 1) {
|
|
150
|
+
throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
update(id, patch) {
|
|
156
|
+
const map = this._readRoutines();
|
|
157
|
+
const routine = this.get(id);
|
|
158
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
159
|
+
const next = { ...routine, ...patch, id: routine.id };
|
|
160
|
+
map[routine.id] = next;
|
|
161
|
+
this._writeRoutines(map);
|
|
162
|
+
return next;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setEnabled(id, enabled) {
|
|
166
|
+
return this.update(id, { enabled: enabled === true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
remove(id) {
|
|
170
|
+
const map = this._readRoutines();
|
|
171
|
+
const routine = this.get(id);
|
|
172
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
173
|
+
delete map[routine.id];
|
|
174
|
+
this._writeRoutines(map);
|
|
175
|
+
return routine;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Routines the driver should fire now (cron due / once reached). */
|
|
179
|
+
due(nowMs = this._now()) {
|
|
180
|
+
return this.list().filter((r) => {
|
|
181
|
+
if (!r.enabled) return false;
|
|
182
|
+
if (r.trigger.kind === "cron") {
|
|
183
|
+
const from = r.lastFiredAt || r.createdAt || 0;
|
|
184
|
+
const next = nextCronTime(r.trigger.cron, from);
|
|
185
|
+
return next != null && next <= nowMs;
|
|
186
|
+
}
|
|
187
|
+
if (r.trigger.kind === "once") {
|
|
188
|
+
return !r.lastFiredAt && r.trigger.at <= nowMs;
|
|
189
|
+
}
|
|
190
|
+
return false; // webhook/github fire through other paths
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** GitHub-triggered routines the driver should poll. */
|
|
195
|
+
githubRoutines() {
|
|
196
|
+
return this.list().filter((r) => r.enabled && r.trigger.kind === "github");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── run history ──────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
_appendRun(record) {
|
|
202
|
+
this._ensureDir();
|
|
203
|
+
fs.appendFileSync(this._runsFile(), JSON.stringify(record) + "\n", "utf-8");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
recordRunStart(routineId, meta = {}) {
|
|
207
|
+
const runId = `run-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
208
|
+
this._appendRun({
|
|
209
|
+
type: "start",
|
|
210
|
+
runId,
|
|
211
|
+
routineId,
|
|
212
|
+
trigger: meta.trigger || null,
|
|
213
|
+
pid: meta.pid || null,
|
|
214
|
+
startedAt: this._now(),
|
|
215
|
+
});
|
|
216
|
+
return runId;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
recordRunEnd(runId, result = {}) {
|
|
220
|
+
this._appendRun({
|
|
221
|
+
type: "end",
|
|
222
|
+
runId,
|
|
223
|
+
endedAt: this._now(),
|
|
224
|
+
status: result.status || (result.exitCode === 0 ? "ok" : "failed"),
|
|
225
|
+
exitCode: result.exitCode ?? null,
|
|
226
|
+
summary: String(result.summary || "").slice(0, 500),
|
|
227
|
+
usage: result.usage || null,
|
|
228
|
+
costUsd: Number.isFinite(result.costUsd) ? result.costUsd : null,
|
|
229
|
+
durationMs: result.durationMs ?? null,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Merged run rows (start+end), newest first. */
|
|
234
|
+
listRuns({ routineId = null, limit = 20 } = {}) {
|
|
235
|
+
let lines = [];
|
|
236
|
+
try {
|
|
237
|
+
lines = fs
|
|
238
|
+
.readFileSync(this._runsFile(), "utf-8")
|
|
239
|
+
.split("\n")
|
|
240
|
+
.filter((l) => l.trim());
|
|
241
|
+
} catch {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const runs = new Map();
|
|
245
|
+
for (const line of lines) {
|
|
246
|
+
let rec;
|
|
247
|
+
try {
|
|
248
|
+
rec = JSON.parse(line);
|
|
249
|
+
} catch {
|
|
250
|
+
continue; // per-row resilience
|
|
251
|
+
}
|
|
252
|
+
if (rec.type === "start") {
|
|
253
|
+
runs.set(rec.runId, { ...rec, status: "running" });
|
|
254
|
+
} else if (rec.type === "end") {
|
|
255
|
+
const prev = runs.get(rec.runId) || { runId: rec.runId };
|
|
256
|
+
runs.set(rec.runId, { ...prev, ...rec, type: undefined });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return [...runs.values()]
|
|
260
|
+
.filter((r) => !routineId || r.routineId === routineId)
|
|
261
|
+
.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0))
|
|
262
|
+
.slice(0, limit)
|
|
263
|
+
.map(({ type: _t, ...rest }) => rest);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Per-routine aggregate for `cc routine list`: runs, ok/failed, cost. */
|
|
267
|
+
summarize(routineId) {
|
|
268
|
+
const runs = this.listRuns({ routineId, limit: 1000 });
|
|
269
|
+
const done = runs.filter((r) => r.status && r.status !== "running");
|
|
270
|
+
return {
|
|
271
|
+
totalRuns: runs.length,
|
|
272
|
+
ok: done.filter((r) => r.status === "ok").length,
|
|
273
|
+
failed: done.filter((r) => r.status === "failed").length,
|
|
274
|
+
running: runs.filter((r) => r.status === "running").length,
|
|
275
|
+
costUsd: done.reduce(
|
|
276
|
+
(sum, r) => sum + (Number.isFinite(r.costUsd) ? r.costUsd : 0),
|
|
277
|
+
0,
|
|
278
|
+
),
|
|
279
|
+
lastRun: runs[0] || null,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Fire one routine: record history, run the agent, persist the log, update
|
|
286
|
+
* lastFiredAt (and disable a fired `once`). `runAgent({prompt})` must resolve
|
|
287
|
+
* `{ exitCode, output, usage?, costUsd?, pid? }`.
|
|
288
|
+
*/
|
|
289
|
+
export async function fireRoutine(store, routine, runAgent, meta = {}) {
|
|
290
|
+
const startedAt = store._now();
|
|
291
|
+
const runId = store.recordRunStart(routine.id, {
|
|
292
|
+
trigger: meta.trigger || routine.trigger.kind,
|
|
293
|
+
});
|
|
294
|
+
let result;
|
|
295
|
+
try {
|
|
296
|
+
result = await runAgent({ prompt: routine.prompt, routine, runId });
|
|
297
|
+
} catch (err) {
|
|
298
|
+
result = { exitCode: -1, output: `runner error: ${err.message}` };
|
|
299
|
+
}
|
|
300
|
+
const output = String(result.output || "");
|
|
301
|
+
try {
|
|
302
|
+
store._ensureDir();
|
|
303
|
+
fs.writeFileSync(store.logFile(runId), output, "utf-8");
|
|
304
|
+
} catch {
|
|
305
|
+
/* log persistence is best-effort */
|
|
306
|
+
}
|
|
307
|
+
store.recordRunEnd(runId, {
|
|
308
|
+
exitCode: result.exitCode,
|
|
309
|
+
status: result.exitCode === 0 ? "ok" : "failed",
|
|
310
|
+
summary: output.trim().split("\n").slice(-3).join(" ⏎ "),
|
|
311
|
+
usage: result.usage || null,
|
|
312
|
+
costUsd: result.costUsd,
|
|
313
|
+
durationMs: store._now() - startedAt,
|
|
314
|
+
});
|
|
315
|
+
store.update(routine.id, {
|
|
316
|
+
lastFiredAt: startedAt,
|
|
317
|
+
...(routine.trigger.kind === "once" ? { enabled: false } : {}),
|
|
318
|
+
});
|
|
319
|
+
return runId;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Poll a github-triggered routine: fetch the repo's latest events via the
|
|
324
|
+
* injected `fetchEvents(repo)` (production: `gh api repos/<repo>/events`),
|
|
325
|
+
* fire when new events of the requested types appeared. Returns fired runIds.
|
|
326
|
+
*/
|
|
327
|
+
export async function pollGithubRoutine(
|
|
328
|
+
store,
|
|
329
|
+
routine,
|
|
330
|
+
{ fetchEvents, runAgent },
|
|
331
|
+
) {
|
|
332
|
+
const events = (await fetchEvents(routine.trigger.repo)) || [];
|
|
333
|
+
const wanted = Array.isArray(routine.trigger.events)
|
|
334
|
+
? routine.trigger.events
|
|
335
|
+
: null;
|
|
336
|
+
const fresh = events.filter((e) => {
|
|
337
|
+
if (wanted && wanted.length > 0 && !wanted.includes(e.type)) return false;
|
|
338
|
+
return (
|
|
339
|
+
!routine.lastSeenGithubEventId ||
|
|
340
|
+
String(e.id) > String(routine.lastSeenGithubEventId)
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
const newestId = events.length ? String(events[0].id) : null;
|
|
344
|
+
if (newestId) {
|
|
345
|
+
store.update(routine.id, { lastSeenGithubEventId: newestId });
|
|
346
|
+
}
|
|
347
|
+
if (fresh.length === 0) return [];
|
|
348
|
+
const runId = await fireRoutine(store, routine, runAgent, {
|
|
349
|
+
trigger: `github:${fresh.map((e) => e.type).join(",")}`,
|
|
350
|
+
});
|
|
351
|
+
return [runId];
|
|
352
|
+
}
|
|
@@ -66,3 +66,54 @@ export function sensitiveFileReason(targetPath) {
|
|
|
66
66
|
}
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Auto-exec CONFIG write guard — the CLI-side twin of the IDE extension's
|
|
72
|
+
* `classifyAutoExecTarget` (vscode-extension/src/auto-exec-guard.js, kept as
|
|
73
|
+
* an independent pure function: no cross-package import). These files are not
|
|
74
|
+
* shells or hooks, but writing them still plants code that RUNS without an
|
|
75
|
+
* explicit user action: VS Code tasks auto-run on folder open, launch configs
|
|
76
|
+
* fire on debug, `.mcp.json` spawns arbitrary server processes the agent then
|
|
77
|
+
* auto-loads, JetBrains run configurations execute on the next Run, and
|
|
78
|
+
* devcontainer postCreate/postStart commands run on container open. Same
|
|
79
|
+
* conservative name-based posture as `sensitiveFileReason`; an unknown path is
|
|
80
|
+
* simply not risky (null), never a false positive.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} targetPath path as the tool received it (rel or abs)
|
|
83
|
+
* @returns {string|null} human reason when auto-exec-risky, null otherwise
|
|
84
|
+
*/
|
|
85
|
+
export function autoExecConfigReason(targetPath) {
|
|
86
|
+
const p = String(targetPath || "");
|
|
87
|
+
if (!p) return null;
|
|
88
|
+
const norm = p.replace(/\\/g, "/").toLowerCase();
|
|
89
|
+
const base = norm.split("/").pop() || "";
|
|
90
|
+
if (!base) return null;
|
|
91
|
+
if (base === ".mcp.json" || base === "mcp.json") {
|
|
92
|
+
return `auto-exec config: MCP server config (${base}) — can spawn arbitrary server processes the agent auto-loads`;
|
|
93
|
+
}
|
|
94
|
+
if (/(^|\/)\.vscode\/tasks\.json$/.test(norm)) {
|
|
95
|
+
return "auto-exec config: VS Code tasks (.vscode/tasks.json) — tasks can auto-run on folder open";
|
|
96
|
+
}
|
|
97
|
+
if (/(^|\/)\.vscode\/launch\.json$/.test(norm)) {
|
|
98
|
+
return "auto-exec config: VS Code launch/debug config (.vscode/launch.json) — runs commands on the next debug session";
|
|
99
|
+
}
|
|
100
|
+
if (/(^|\/)\.vscode\/settings\.json$/.test(norm)) {
|
|
101
|
+
return "auto-exec config: VS Code workspace settings (.vscode/settings.json) — can rebind shells/tools the user then runs";
|
|
102
|
+
}
|
|
103
|
+
if (/(^|\/)\.idea\/runconfigurations\//.test(norm)) {
|
|
104
|
+
return `auto-exec config: JetBrains run configuration (${base}) — executes on the user's next Run action`;
|
|
105
|
+
}
|
|
106
|
+
if (/(^|\/)\.idea\/workspace\.xml$/.test(norm)) {
|
|
107
|
+
return "auto-exec config: JetBrains workspace.xml — can embed run/startup tasks";
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
base === "devcontainer.json" ||
|
|
111
|
+
/(^|\/)\.devcontainer\/devcontainer\.json$/.test(norm)
|
|
112
|
+
) {
|
|
113
|
+
return "auto-exec config: devcontainer.json — postCreate/postStart commands run on container open";
|
|
114
|
+
}
|
|
115
|
+
if (base.endsWith(".code-workspace")) {
|
|
116
|
+
return `auto-exec config: VS Code workspace file (${base}) — can carry auto-run tasks and settings`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-store write guard — transcript tamper protection (agent tool layer).
|
|
3
|
+
*
|
|
4
|
+
* Session transcripts under `~/.chainlesschain/sessions` are hash-chained and
|
|
5
|
+
* tamper-evident (see harness/transcript-integrity.js). An agent writing into
|
|
6
|
+
* that directory could rewrite the very history that `cc session verify`,
|
|
7
|
+
* resume, and audit trust — so write tools confirm first, exactly like the
|
|
8
|
+
* sensitive-file guard. Pure path logic lives here so it is unit-testable
|
|
9
|
+
* without the agent runtime.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { resolve, relative, isAbsolute, join } from "node:path";
|
|
13
|
+
import { getHomeDir } from "./paths.js";
|
|
14
|
+
|
|
15
|
+
export function sessionStoreDir() {
|
|
16
|
+
return join(getHomeDir(), "sessions");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True when targetPath (relative paths resolve against opts.cwd) lands inside
|
|
21
|
+
* the session transcript store. path.win32.relative compares
|
|
22
|
+
* case-insensitively, so mixed-case Windows paths are handled.
|
|
23
|
+
*/
|
|
24
|
+
export function isWithinSessionStore(targetPath, opts = {}) {
|
|
25
|
+
const p = String(targetPath || "");
|
|
26
|
+
if (!p) return false;
|
|
27
|
+
const abs = resolve(opts.cwd || process.cwd(), p);
|
|
28
|
+
const rel = relative(resolve(sessionStoreDir()), abs);
|
|
29
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} targetPath path as the tool received it (rel or abs)
|
|
34
|
+
* @returns {string|null} human reason when the path is a transcript-store
|
|
35
|
+
* write, null otherwise
|
|
36
|
+
*/
|
|
37
|
+
export function sessionStorePathReason(targetPath, opts = {}) {
|
|
38
|
+
if (!isWithinSessionStore(targetPath, opts)) return null;
|
|
39
|
+
return "session transcript store — transcripts are hash-chained and tamper-evident; direct edits break the chain";
|
|
40
|
+
}
|