chainlesschain 0.162.158 → 0.162.159
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-BwpTJTN_.js → AIOps-ByIiF5bT.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BtQOoo3l.js → ActionButton-NlgSFZq5.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Duf_rXZa.js → Analytics-D17cOUcb.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-XQK3wjvB.js → AppLayout-B4mEZcbl.js} +4 -4
- package/src/assets/web-panel/assets/Artifacts-CASsmyQw.js +1 -0
- package/src/assets/web-panel/assets/{Audit-DNjOGQLh.js → Audit-DYAia2e2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-FH9RAfB5.js → BackgroundAgents-C1lvYUSu.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-ZIbrR4-U.js → Backup-B6agK6bA.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DSt3NaND.js → BaseInput-DM43R2vy.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CY-wvbjv.js → Chat-Dtdp3eXp.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BAV9V-t-.js → ChatBubbleRenderer-BL5c_OQ1.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-8JVadE_B.js → Checkbox-D2xf7uZt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CN83nPYI.js → Codegen-D3j45jJL.js} +1 -1
- package/src/assets/web-panel/assets/{Col-xPSvqdN8.js → Col-voPJ9fUZ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D78cnIjG.js → Community-DEsyTESF.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CSw6Csbp.js → Compact-OHV_QXLD.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-E_HhjcYR.js → Compliance-eDTJWC-J.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DybeQjmm.js → Cowork-BBvy-6d1.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DS0BJeLY.js → Cron-Dw2eoJxu.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOwVgkbs.js → Crosschain-DQHms_Bh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRUr9YuX.js → DID-Cyu7Id1c.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cht-yUKW.js → Dashboard-sIDzkG92.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLr_EJ9X.js → Dropdown-DUW3T2ey.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Drexuh5W.js → EmailListRenderer-CECELXW8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D9Q2Kq63.js → FamilyGuardDashboard-BIZR37ov.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C-Z1xI-T.js → Federation-BEZIQX84.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DLHPMkQF.js → FormItemContext-BcdUMznL.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DxxEdn2r.js → GenericCardRenderer-DsN_fr5H.js} +1 -1
- package/src/assets/web-panel/assets/{Git-syATcaBt.js → Git-63LhjBx2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYe_r8Zf.js → Governance-CYtQtIcH.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-GWxErB7k.js → Inference-BwR6UUox.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-qQvVCZoi.js → KnowledgeGraph-D9lVNTUA.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BenAU2W8.js → Logs-C4q5h7Ut.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-7agE3Or1.js → MarkdownRenderer-DexLlXfN.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D4Jn5fpD.js → Marketplace-Plksliuw.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DtAwoqiQ.js → McpTools-DZUFfeuG.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BI_O50I8.js → Memory-NoDYt29V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CQWE2lqx.js → MobileBridge-CEXGTkdh.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CVtTqgtg.js → MobileProjects-DnbJkbZo.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dnr16qsn.js → Mtc-DTAa0cwy.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CQ5Xe4-a.js → MtcAudit-CVV7LnNS.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bjr8O-wy.js → Multisig-BqqGrjt_.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CEe-KirU.js → NLProgramming-C4WP8DRp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-spKJIKF9.js → Notes-Zo4DJUVI.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DRp83LFG.js → NotificationSettings-DBcjRaz2.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BlgB0IYg.js → Organization-BT9WPBOX.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-a_35HqZI.js → Overflow-D6b4bZ3q.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CSNHCNPC.js → P2P-Cw3OsYx-.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C8M1JH-1.js → PdhVaultBrowser-C0nyiK2b.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DmjJi9Ms.js → Permissions-zVBZEB_C.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DfuwRyf4.js → PersonalDataHub-BOspsFew.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-B2-S4ekE.js → Pipeline-CQWfZ4XH.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B1dOiBLC.js → Privacy-C2oGJ_WL.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-_pNmc-DX.js → ProjectInit-BZMR4qY1.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DmwG2Ii1.js → ProjectSettings-DAujoP63.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-COATmHxF.js → Projects-DvH_P2Uq.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-C6HSjg1d.js → Providers-D_RGHM-v.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CrDmS7l1.js → QrScannerModal-Mf6KfL2J.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CfKvWt-x.js → QuickAsk-5D8URoI_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DMa6Kuzv.js → Recommend-Df4kC8Gy.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DrktUo5x.js → RemoteSession-DGi3f3vO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BTPQ1vjL.js → Reputation-jmkGFNWb.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B6Q7QkMG.js → Row-DP6Pcu3m.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B1IJhAmX.js → RssFeed-D60hSuPO.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xbea1kGD.js → Search-0mf1BQUK.js} +1 -1
- package/src/assets/web-panel/assets/{Security-7rs0cO3F.js → Security-CfZtLFYV.js} +4 -4
- package/src/assets/web-panel/assets/{Services-0fjixK0S.js → Services-D9zpZLWz.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B_rVq9Cu.js → Skeleton-D1_44eMD.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C5uM90b9.js → Skills-cVFa8zkz.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-U4pYCIZH.js → Sla-Cinpzc3L.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cdz4yTWx.js → SpeechSettings-bqdiJE_P.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Me3aetHv.js → SyncSettings-Dgl_SLgj.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cf6aNkL_.js → Tasks-DawWAEhy.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CYatk4tU.js → Templates-Ca0nY651.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DIMKLw7o.js → Tenant-CgaGEhLm.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C_cp-uSJ.js → Terminal-D-9EnpRx.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-cUPVNADp.js → TimelineRenderer-CSOxDHZ6.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CiaEIXVf.js → Tokens-BpwLZpL3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DB1WU5lQ.js → Trigger-DgowwBK5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Cfht9Ijy.js → Trust-BZutapjE.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-K3P4Vt9S.js → UkeySign-QZPt8_Sp.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DWjhjXRh.js → VideoEditing-DlQx5UDJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D9-Q1Kan.js → Wallet-ZfdTgtWA.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BDszy-oV.js → WebAuthn-CAp0y_Cr.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DIhij4rA.js → WorkflowEditor-BVITZEmr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D7VCiKbh.js → chat-CC2tPaNg.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BvOd9pVn.js → colors-31brc9CA.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DoqXBiAO.js → compact-item-DLMkC9C-.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CrKRzUtp.js → createContext-BMtveI6F.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BWAev4WC.js → hasIn-DbD6WHW2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B30NvR21.js → index-3oBsXZmF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx7M7PSF.js → index-ALvoNTRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHOcVYrx.js → index-AaIKG60c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXyl8O23.js → index-BFRLmCR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-YGDy7nNq.js → index-BKAB6-6V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoYrS_L2.js → index-BOM-VFlv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq_ctRpz.js → index-BPpS-lGb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDGLdn12.js → index-Bbc2JeQG.js} +1 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +1 -0
- package/src/assets/web-panel/assets/{index-ISvPZ05r.js → index-BiRQUoyE.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0vGEbJT.js → index-BnFLzejT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj7EgmOp.js → index-BoO4WEJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZGk8AbZ.js → index-C-cGlCDW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkgnGX7v.js → index-C1PfeI88.js} +1 -1
- package/src/assets/web-panel/assets/{index-HwHdTusY.js → index-C21pdBqs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf9YlbDn.js → index-C7LHNw_N.js} +1 -1
- package/src/assets/web-panel/assets/{index-oY45XrkG.js → index-CEXa-4zd.js} +1 -1
- package/src/assets/web-panel/assets/{index-KR1-Em9C.js → index-CEwjBA9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5kRmj3P.js → index-CFXKbOek.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN9m4Ux2.js → index-CJhREOlG.js} +1 -1
- package/src/assets/web-panel/assets/{index-PDxGjKRu.js → index-COHHQ9cU.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23L1V76.js → index-CUmLMFsy.js} +1 -1
- package/src/assets/web-panel/assets/{index-zH-tuuGZ.js → index-CWgfipcC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmJ6xKBO.js → index-C_eUKGYP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEPHMSih.js → index-CcVszn2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV-Rfmfx.js → index-Cod97Urs.js} +3 -3
- package/src/assets/web-panel/assets/{index-BWWsaimP.js → index-Cwbqh0KZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-8g5TJCZ3.js → index-D67kIagl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXfKSiI.js → index-D7cV0tu0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoD31Ndm.js → index-DJvxFyIz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSb-tnIo.js → index-DZl-UXB6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvDOpsJB.js → index-Dm0byjm1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlGu7As6.js → index-DndNnYc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-gmxqu5Kk.js → index-WQUO2vvK.js} +1 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +1 -0
- package/src/assets/web-panel/assets/{index-ww1QtLnX.js → index-bdaJwTXJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-JzUmJMvn.js → index-ep6JchNG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCyP071m.js → index-jG6h22Qg.js} +1 -1
- package/src/assets/web-panel/assets/{index-1poMpMU0.js → index-ntVQqSML.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-XW-OR3mL.js → initDefaultProps-D5UoBRhN.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CeuEHh20.js → motion-DltOw0CR.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dva79Keg.js → move-_oPkk5zj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C0B6bD2j.js → mtc-parser-EES8ROCS.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AkZvO-a2.js → omit-B-_39x_M.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4r55qGc.js → pickAttrs-l6D0yJHz.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLH-z7YE.js → placementArrow-DIeUK31L.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ksEGZ2Wf.js → responsiveObserve-dG4EzUQo.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cth4OD1Z.js → slide-DWFzYGdH.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-9S83ezxw.js → statusUtils-CECdTA5H.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-7yQmQGZ4.js → styleChecker-D6kP-mMK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DpGnMmPc.js → useFlexGapSupport-BWfna_rd.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C4zDZIXH.js → useFs-Bb-j6rzo.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CoLICdUZ.js → usePersonalDataHub-D5OIsYM4.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CKyH-35X.js → vnode-DNpwzFTM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DRDGit17.js → zoom-vNLpFQBW.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +17 -3
- package/src/commands/agent.js +7 -0
- package/src/commands/background-session.js +143 -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 +61 -0
- package/src/commands/plugin.js +63 -1
- package/src/commands/routine.js +367 -0
- package/src/commands/session.js +326 -0
- package/src/data/changelog.json +7 -2
- package/src/harness/jsonl-session-store.js +82 -2
- 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/background-agent-supervisor.js +126 -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/doctor-checkup.js +652 -0
- package/src/lib/jsonl-session-store.js +4 -0
- 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/routine-store.js +352 -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 +23 -0
- 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 +242 -1
- package/src/runtime/coding-agent-contract-shared.cjs +88 -0
- package/src/runtime/coding-agent-policy.cjs +17 -0
- package/src/runtime/headless-runner.js +71 -7
- package/src/runtime/headless-stream.js +14 -8
- 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/OrderTableRenderer-h1A0XB2a.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
|
+
}
|
|
@@ -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
|
+
}
|