chainlesschain 0.163.4 → 0.163.6
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/completions/_cc +1 -1
- package/completions/cc.bash +1 -1
- package/completions/cc.fish +1 -1
- package/completions/cc.ps1 +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DB5X7qqV.js → AIOps-DDIUofA8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-Bm-5xYRn.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-D5UH4ljI.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-D_ifjOOP.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-BY1mXdC7.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-Bujr4Ml3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-sioKSTcC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-DDOAK8RK.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-CtCsVVke.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-DpNj0vnd.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-DoHVNSXq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-DlVl7Qmv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-GIqoPYty.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-CIAPEVji.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-BN7JiI_h.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-CqUWCM9B.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-A4iPp7A4.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-B2zeXXWb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-BR2xmWr8.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-BzFDjrIv.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-DKtDo3yA.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-RNx43_YG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-BmRKvBTy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-CVxGTA-0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DfqbkiOP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-RuHyyj5O.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-Bn7uZ7rI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BYNdOiCd.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-JQYmDqM8.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-Cyx4yZ-9.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-VDuGZTN2.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-_yKo97gi.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-CXRXW3q7.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-acTGtfLH.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-ClpjXfkY.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-_6Tn3o96.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-CK7pbZzP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Czy6V_P8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Cqx-UVv3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-C69WDxGy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-DFnA9vLQ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-ReZkk2yT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-DzjSI0aI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-DKTf5xIP.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DhQ5klhR.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-CUL1iHrW.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-DU88iu4N.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-Bw-sYfyK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-Bq3Q5y0z.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-C8spzpho.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DhfifCKx.js → Projects-Dd-xuPIc.js} +1 -1
- package/src/assets/web-panel/assets/Providers-Bf4eTP9W.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-Dme9BDU1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-CIXfv1rE.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-CIxvdeZ-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-C6k7MExI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-CISDz2bl.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-CdnDxXyf.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-C-QpRHsl.js} +3 -3
- package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-DBjaGzSn.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-CQKukOeE.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-DZz86jWb.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-jb8RMK0d.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-CdY76fZG.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-C_CSiZX9.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-qvimGO0O.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-Dng5LPJC.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-Bwqq5Wll.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-DNWuwP7Z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-B-6Othpp.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DKpKZfzx.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-DzfGMd2J.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-BXwUi74J.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BNzUq4ui.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-BYugxww4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-DdauhtSJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-CSC9bCe_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-Q8AN3bTO.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-d-LBGCuA.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-BLVDCgsZ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-BZlPSEHl.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-IWseP6R6.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-DnVRV3Ao.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-DLQZA_Wn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-1jeAudHd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM724NgU.js → index-BEu2lMBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-BOZJBTWZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-BSjilZ9L.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-BZs5bBnk.js} +1 -1
- package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-B_N4-Gq9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-Bbj2NS_I.js} +3 -3
- package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BhA3QEni.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-Bxp7uDD8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-C1ifZtXo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-C2FqfMvU.js} +1 -1
- package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-C5dgnilC.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-CINJQEj6.js} +1 -1
- package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-COev0W2g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CT9vVN-X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-CZDekj7T.js} +1 -1
- package/src/assets/web-panel/assets/{index-301ogenv.js → index-CarMTIyQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CbQNL0ue.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-CpBaJ3TD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-DB0MJgF_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-DOoQpuQY.js} +1 -1
- package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-DTbxp4UH.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-DUq0c7gq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-DZpYeqrh.js} +1 -1
- package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-D_LQodeJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-DalA0TKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-DjWnONdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-Dkwsi1Kn.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-DocEhinm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-DpVrK6fr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-Dy0R5ZED.js} +1 -1
- package/src/assets/web-panel/assets/{index-frSojpHj.js → index-M7wxfAbm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-N_GoWZX4.js} +1 -1
- package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-YSw_gxlZ.js} +1 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +1 -0
- package/src/assets/web-panel/assets/index-fikqml4k.js +1 -0
- package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-gt8bNefW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-hwVDeoC0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-jWb70ZgZ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-Co67Kqmb.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-BMOxQSF_.js} +1 -1
- package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-C5fWDw-N.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-BJevpL0e.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-BCzNU8s6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-CybWvDPk.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-BMWS9HnW.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-BoL2UnGY.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-DWNfXraU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-BquTM3tX.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-BEkCleim.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CkDffJ4F.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-qbvKXesB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-C6v8n6mA.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-4CTeFILV.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/command-manifest.json +1 -1
- package/src/commands/agenda.js +125 -7
- package/src/commands/automation.js +462 -3
- package/src/commands/background-session.js +102 -1
- package/src/commands/cowork.js +30 -13
- package/src/commands/loop.js +167 -36
- package/src/commands/routine.js +50 -13
- package/src/commands/scheduler-daemon.js +450 -0
- package/src/data/changelog.json +27 -1
- package/src/lib/agent-schedule-store.js +509 -36
- package/src/lib/automation-center-routines.js +363 -0
- package/src/lib/automation-center.js +435 -0
- package/src/lib/automation-engine.js +74 -19
- package/src/lib/automation-execution-authority.js +736 -0
- package/src/lib/background-agent-supervisor.js +78 -0
- package/src/lib/background-needs-input-incident.js +221 -0
- package/src/lib/cowork-cron.js +555 -39
- package/src/lib/cowork-observe.js +5 -1
- package/src/lib/loop.js +37 -2
- package/src/lib/micro-compact.js +11 -5
- package/src/lib/permission-engine.js +14 -7
- package/src/lib/process-execution-broker/index.js +14 -3
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/routine-store.js +242 -68
- package/src/lib/scheduler-kernel/agenda-adapter.js +812 -0
- package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
- package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
- package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
- package/src/lib/scheduler-kernel/contract.js +11 -3
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +820 -0
- package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +812 -0
- package/src/lib/scheduler-kernel/runtime.js +527 -0
- package/src/lib/scheduler-kernel/service.js +328 -0
- package/src/lib/scheduler-kernel/store.js +940 -98
- package/src/lib/session-message-provenance.js +4 -1
- package/src/repl/agent-repl.js +24 -5
- package/src/runtime/agent-core.js +38 -17
- package/src/runtime/coding-agent-contract-shared.cjs +5 -0
- package/src/workers/background-agent-worker.js +91 -28
- package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
- package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-CGdzFIYw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +0 -1
- package/src/assets/web-panel/assets/index-CKB0vbJU.js +0 -1
- package/src/assets/web-panel/assets/index-DgoXmwqB.js +0 -1
|
@@ -70,7 +70,11 @@ export function _computeNextTriggers(schedules, from, limit = 5) {
|
|
|
70
70
|
const matchers = [];
|
|
71
71
|
for (const s of enabled) {
|
|
72
72
|
try {
|
|
73
|
-
matchers.push({
|
|
73
|
+
matchers.push({
|
|
74
|
+
id: s.id,
|
|
75
|
+
cron: s.cron,
|
|
76
|
+
match: parseCron(s.cron, { timeZone: s.timeZone }),
|
|
77
|
+
});
|
|
74
78
|
} catch (_e) {
|
|
75
79
|
// skip invalid cron
|
|
76
80
|
}
|
package/src/lib/loop.js
CHANGED
|
@@ -86,17 +86,49 @@ export function summarizeLoopEvents(events) {
|
|
|
86
86
|
let config = null;
|
|
87
87
|
let completedIterations = 0;
|
|
88
88
|
let lastExitCode = null;
|
|
89
|
+
const scheduled = new Map();
|
|
90
|
+
const completed = new Set();
|
|
89
91
|
for (const e of events || []) {
|
|
90
92
|
if (e?.type === "loop_config") {
|
|
91
93
|
config = e.data || null;
|
|
94
|
+
} else if (
|
|
95
|
+
e?.type === "loop_iteration_scheduled" &&
|
|
96
|
+
Number.isSafeInteger(e.data?.n) &&
|
|
97
|
+
e.data.n > 0 &&
|
|
98
|
+
Number.isSafeInteger(e.data?.scheduledFor) &&
|
|
99
|
+
e.data.scheduledFor >= 0
|
|
100
|
+
) {
|
|
101
|
+
scheduled.set(e.data.n, {
|
|
102
|
+
n: e.data.n,
|
|
103
|
+
scheduledFor: e.data.scheduledFor,
|
|
104
|
+
});
|
|
92
105
|
} else if (e?.type === "loop_iteration") {
|
|
93
106
|
completedIterations += 1;
|
|
107
|
+
if (Number.isSafeInteger(e.data?.n) && e.data.n > 0) {
|
|
108
|
+
completed.add(e.data.n);
|
|
109
|
+
}
|
|
94
110
|
if (e.data && typeof e.data.exitCode !== "undefined") {
|
|
95
111
|
lastExitCode = e.data.exitCode;
|
|
96
112
|
}
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
|
-
|
|
115
|
+
const pendingIteration =
|
|
116
|
+
[...scheduled.values()]
|
|
117
|
+
.filter((entry) => !completed.has(entry.n))
|
|
118
|
+
.sort((a, b) => a.n - b.n)[0] || null;
|
|
119
|
+
return { config, completedIterations, lastExitCode, pendingIteration };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Return durable terminal data for one loop iteration, if already recorded. */
|
|
123
|
+
export function findLoopIterationResult(events, iteration) {
|
|
124
|
+
if (!Number.isSafeInteger(iteration) || iteration < 1) return null;
|
|
125
|
+
for (let index = (events || []).length - 1; index >= 0; index -= 1) {
|
|
126
|
+
const event = events[index];
|
|
127
|
+
if (event?.type === "loop_iteration" && event.data?.n === iteration) {
|
|
128
|
+
return event.data || null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
100
132
|
}
|
|
101
133
|
|
|
102
134
|
/** Default abortable sleep — resolves early if the signal aborts. */
|
|
@@ -187,7 +219,10 @@ export async function runLoop({
|
|
|
187
219
|
if (untilExitZero && res.exitCode === 0) {
|
|
188
220
|
return { iterations: i, stoppedBy: "exit-zero", results };
|
|
189
221
|
}
|
|
190
|
-
if (
|
|
222
|
+
if (
|
|
223
|
+
untilRegex &&
|
|
224
|
+
(res.matchedUntil === true || untilRegex.test(res.output || ""))
|
|
225
|
+
) {
|
|
191
226
|
return { iterations: i, stoppedBy: "match", results };
|
|
192
227
|
}
|
|
193
228
|
if (maxIterations && i >= maxIterations) {
|
package/src/lib/micro-compact.js
CHANGED
|
@@ -23,10 +23,10 @@ export function microCompact(messages, opts = {}) {
|
|
|
23
23
|
return { messages, stats: { trimmed: 0, saved: 0, kept: 0 } };
|
|
24
24
|
}
|
|
25
25
|
const keepRecent = Number.isFinite(opts.keepRecent)
|
|
26
|
-
? opts.keepRecent
|
|
26
|
+
? Math.max(0, Math.trunc(opts.keepRecent))
|
|
27
27
|
: DEFAULT_KEEP_RECENT;
|
|
28
28
|
const maxChars = Number.isFinite(opts.maxToolChars)
|
|
29
|
-
? opts.maxToolChars
|
|
29
|
+
? Math.max(0, Math.trunc(opts.maxToolChars))
|
|
30
30
|
: DEFAULT_MAX_TOOL_CHARS;
|
|
31
31
|
// messages at index >= cutoff are "recent" and never touched.
|
|
32
32
|
const cutoff = Math.max(0, messages.length - keepRecent);
|
|
@@ -38,12 +38,18 @@ export function microCompact(messages, opts = {}) {
|
|
|
38
38
|
if (!m || m.role !== "tool") return m; // only old tool results
|
|
39
39
|
const content = typeof m.content === "string" ? m.content : "";
|
|
40
40
|
if (content.length <= maxChars) return m; // small enough already
|
|
41
|
+
const fullMarker = `\n… [tool result trimmed — ${content.length} chars]`;
|
|
42
|
+
const marker =
|
|
43
|
+
fullMarker.length <= maxChars
|
|
44
|
+
? fullMarker
|
|
45
|
+
: "…[trimmed]".slice(0, maxChars);
|
|
46
|
+
const head = content.slice(0, Math.max(0, maxChars - marker.length));
|
|
47
|
+
const compactedContent = `${head}${marker}`;
|
|
41
48
|
trimmed += 1;
|
|
42
|
-
saved += content.length -
|
|
43
|
-
const head = content.slice(0, maxChars);
|
|
49
|
+
saved += content.length - compactedContent.length;
|
|
44
50
|
return {
|
|
45
51
|
...m,
|
|
46
|
-
content:
|
|
52
|
+
content: compactedContent,
|
|
47
53
|
_microCompacted: true,
|
|
48
54
|
};
|
|
49
55
|
});
|
|
@@ -91,6 +91,8 @@ export const PERMISSION_SCOPES = [
|
|
|
91
91
|
"audit:write",
|
|
92
92
|
"config:read",
|
|
93
93
|
"config:write",
|
|
94
|
+
"automation:execute",
|
|
95
|
+
"automation:connector:*",
|
|
94
96
|
"system:admin",
|
|
95
97
|
];
|
|
96
98
|
|
|
@@ -296,7 +298,7 @@ function _grantActive(expiresAt, nowMs) {
|
|
|
296
298
|
/**
|
|
297
299
|
* Get all roles and direct permissions for a user.
|
|
298
300
|
*/
|
|
299
|
-
export function getUserPermissions(db, userDid) {
|
|
301
|
+
export function getUserPermissions(db, userDid, { nowMs = Date.now() } = {}) {
|
|
300
302
|
ensurePermissionTables(db);
|
|
301
303
|
|
|
302
304
|
// Get active role grants
|
|
@@ -305,7 +307,6 @@ export function getUserPermissions(db, userDid) {
|
|
|
305
307
|
.all(userDid);
|
|
306
308
|
|
|
307
309
|
// Filter out expired grants
|
|
308
|
-
const nowMs = Date.now();
|
|
309
310
|
const grants = allGrants.filter((g) => _grantActive(g.expires_at, nowMs));
|
|
310
311
|
|
|
311
312
|
// Get direct permissions (not expired)
|
|
@@ -347,8 +348,8 @@ export function getUserPermissions(db, userDid) {
|
|
|
347
348
|
/**
|
|
348
349
|
* Check if a user has a specific permission.
|
|
349
350
|
*/
|
|
350
|
-
export function checkPermission(db, userDid, permission) {
|
|
351
|
-
const userPerms = getUserPermissions(db, userDid);
|
|
351
|
+
export function checkPermission(db, userDid, permission, options) {
|
|
352
|
+
const userPerms = getUserPermissions(db, userDid, options);
|
|
352
353
|
|
|
353
354
|
// Admin wildcard
|
|
354
355
|
if (userPerms.isAdmin) return true;
|
|
@@ -356,9 +357,15 @@ export function checkPermission(db, userDid, permission) {
|
|
|
356
357
|
// Exact match
|
|
357
358
|
if (userPerms.effectivePermissions.includes(permission)) return true;
|
|
358
359
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
// Hierarchical wildcard match. In addition to `note:*`, this allows an
|
|
361
|
+
// explicitly narrow grant such as `automation:connector:*` to authorize
|
|
362
|
+
// `automation:connector:slack` without granting unrelated automation
|
|
363
|
+
// capabilities.
|
|
364
|
+
const segments = permission.split(":");
|
|
365
|
+
for (let length = segments.length - 1; length >= 1; length -= 1) {
|
|
366
|
+
const wildcard = `${segments.slice(0, length).join(":")}:*`;
|
|
367
|
+
if (userPerms.effectivePermissions.includes(wildcard)) return true;
|
|
368
|
+
}
|
|
362
369
|
|
|
363
370
|
return false;
|
|
364
371
|
}
|
|
@@ -66,7 +66,8 @@ const SUPPORTED_SANDBOX_PROFILES = new Set([
|
|
|
66
66
|
// evidence, not authority to invoke a privileged post-spawn closure.
|
|
67
67
|
const admittedWindowsMcpCodeSnapshotPlans = new WeakSet();
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Keep the broker on the shared CommonJS trace singleton so CommonJS and ESM
|
|
70
|
+
// consumers observe the same AsyncLocalStorage without loader warnings.
|
|
70
71
|
let _traceCtx = null;
|
|
71
72
|
const _ipcBus = null;
|
|
72
73
|
|
|
@@ -3354,7 +3355,12 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
3354
3355
|
delete spawnOpts.mcpStdioExecutableIdentityDigest;
|
|
3355
3356
|
if (traceCtx) {
|
|
3356
3357
|
spawnOpts.env = { ...(spawnOpts.env || process.env) };
|
|
3357
|
-
|
|
3358
|
+
Object.assign(
|
|
3359
|
+
spawnOpts.env,
|
|
3360
|
+
getTraceCtx()?.traceContext?.getPropagationEnv?.() || {
|
|
3361
|
+
TRACEPARENT: traceCtx.traceparent,
|
|
3362
|
+
},
|
|
3363
|
+
);
|
|
3358
3364
|
}
|
|
3359
3365
|
|
|
3360
3366
|
// P0-1: Credential filtering (default-on) — strip secrets from env/args
|
|
@@ -3658,7 +3664,12 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
3658
3664
|
this._stripAuditControlOptions(spawnOpts);
|
|
3659
3665
|
if (traceCtx) {
|
|
3660
3666
|
spawnOpts.env = { ...(spawnOpts.env || process.env) };
|
|
3661
|
-
|
|
3667
|
+
Object.assign(
|
|
3668
|
+
spawnOpts.env,
|
|
3669
|
+
getTraceCtx()?.traceContext?.getPropagationEnv?.() || {
|
|
3670
|
+
TRACEPARENT: traceCtx.traceparent,
|
|
3671
|
+
},
|
|
3672
|
+
);
|
|
3662
3673
|
}
|
|
3663
3674
|
|
|
3664
3675
|
// P0-1: Credential filtering agent — strip secrets from env/args before spawn
|
|
@@ -50,6 +50,7 @@ const VSCODE_COMMANDS = [
|
|
|
50
50
|
"chainlesschain.session.prStatus",
|
|
51
51
|
"chainlesschain.background.agents",
|
|
52
52
|
"chainlesschain.sessions.workbench",
|
|
53
|
+
"chainlesschain.automation.center",
|
|
53
54
|
"chainlesschain.remote.control",
|
|
54
55
|
"chainlesschain.usage.show",
|
|
55
56
|
"chainlesschain.plugins.manage",
|
|
@@ -101,6 +102,7 @@ const JETBRAINS_ACTIONS = [
|
|
|
101
102
|
"chainlesschain.bg.Agents",
|
|
102
103
|
"chainlesschain.remote.Control",
|
|
103
104
|
"chainlesschain.sessions.Workbench",
|
|
105
|
+
"chainlesschain.automation.Center",
|
|
104
106
|
"chainlesschain.usage.Show",
|
|
105
107
|
"chainlesschain.artifacts.Browse",
|
|
106
108
|
"chainlesschain.policy.Viewer",
|
package/src/lib/routine-store.js
CHANGED
|
@@ -27,6 +27,11 @@ import path from "node:path";
|
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import { randomBytes } from "node:crypto";
|
|
29
29
|
import { parseCron, nextCronTime } from "./agent-schedule-store.js";
|
|
30
|
+
import {
|
|
31
|
+
readSecurityStore,
|
|
32
|
+
writeSecurityStore,
|
|
33
|
+
} from "./durable-security-store.js";
|
|
34
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
30
35
|
|
|
31
36
|
export const ROUTINE_TRIGGER_KINDS = Object.freeze([
|
|
32
37
|
"cron",
|
|
@@ -45,11 +50,75 @@ function shortId() {
|
|
|
45
50
|
|
|
46
51
|
function parseWhen(value) {
|
|
47
52
|
if (value == null) return NaN;
|
|
53
|
+
if (typeof value === "string" && value.trim() === "") return NaN;
|
|
54
|
+
if (typeof value !== "string" && typeof value !== "number") return NaN;
|
|
48
55
|
const n = Number(value);
|
|
49
56
|
if (Number.isFinite(n)) return n;
|
|
50
57
|
return Date.parse(String(value));
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
function boundedDefinitionText(value, field, maximum) {
|
|
61
|
+
const text = String(value || "").trim();
|
|
62
|
+
if (!text) throw new Error(`routine needs a ${field}`);
|
|
63
|
+
if (Buffer.byteLength(text, "utf8") > maximum) {
|
|
64
|
+
throw new Error(`routine ${field} exceeds ${maximum} UTF-8 bytes`);
|
|
65
|
+
}
|
|
66
|
+
return text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function normalizeRoutineDefinition(def = {}) {
|
|
70
|
+
const name = boundedDefinitionText(def.name, "name", 512);
|
|
71
|
+
const prompt = boundedDefinitionText(def.prompt, "prompt", 64 * 1024);
|
|
72
|
+
const raw = def.trigger;
|
|
73
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
74
|
+
throw new Error("routine trigger is required");
|
|
75
|
+
}
|
|
76
|
+
if (!ROUTINE_TRIGGER_KINDS.includes(raw.kind)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
let trigger;
|
|
82
|
+
if (raw.kind === "cron") {
|
|
83
|
+
const cron = String(raw.cron || "").trim();
|
|
84
|
+
if (!parseCron(cron)) throw new Error(`invalid cron expression: ${cron}`);
|
|
85
|
+
trigger = { kind: "cron", cron };
|
|
86
|
+
} else if (raw.kind === "once") {
|
|
87
|
+
const at = parseWhen(raw.at);
|
|
88
|
+
if (!Number.isFinite(at) || !Number.isSafeInteger(at) || at < 0) {
|
|
89
|
+
throw new Error(`invalid --at time: ${raw.at}`);
|
|
90
|
+
}
|
|
91
|
+
trigger = { kind: "once", at };
|
|
92
|
+
} else if (raw.kind === "github") {
|
|
93
|
+
const repo = String(raw.repo || "").trim();
|
|
94
|
+
const parts = repo.split("/");
|
|
95
|
+
if (
|
|
96
|
+
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repo) ||
|
|
97
|
+
parts.some((part) => part === "." || part === "..")
|
|
98
|
+
) {
|
|
99
|
+
throw new Error("github trigger needs --repo <owner/name>");
|
|
100
|
+
}
|
|
101
|
+
const events = Array.isArray(raw.events)
|
|
102
|
+
? [...new Set(raw.events.map((value) => String(value || "").trim()))]
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.sort()
|
|
105
|
+
: [];
|
|
106
|
+
if (
|
|
107
|
+
events.length > 64 ||
|
|
108
|
+
events.some(
|
|
109
|
+
(event) =>
|
|
110
|
+
event.length > 128 || !/^[A-Za-z][A-Za-z0-9_.-]*$/u.test(event),
|
|
111
|
+
)
|
|
112
|
+
) {
|
|
113
|
+
throw new Error("github trigger events are invalid");
|
|
114
|
+
}
|
|
115
|
+
trigger = { kind: "github", repo, events };
|
|
116
|
+
} else {
|
|
117
|
+
trigger = { kind: "webhook" };
|
|
118
|
+
}
|
|
119
|
+
return { name, prompt, trigger };
|
|
120
|
+
}
|
|
121
|
+
|
|
53
122
|
export class RoutineStore {
|
|
54
123
|
constructor({ dir = null, now = () => Date.now() } = {}) {
|
|
55
124
|
this.dir = dir || routinesDir();
|
|
@@ -70,68 +139,58 @@ export class RoutineStore {
|
|
|
70
139
|
}
|
|
71
140
|
|
|
72
141
|
_readRoutines() {
|
|
73
|
-
|
|
74
|
-
const parsed = JSON.parse(fs.readFileSync(this._routinesFile(), "utf-8"));
|
|
75
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
76
|
-
} catch {
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
142
|
+
return readSecurityStore(this._routinesFile(), "routine definitions");
|
|
79
143
|
}
|
|
80
144
|
_writeRoutines(map) {
|
|
145
|
+
writeSecurityStore(this._routinesFile(), "routine definitions", map);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_withDefinitionLock(callback) {
|
|
81
149
|
this._ensureDir();
|
|
82
|
-
|
|
150
|
+
return withFileLock(
|
|
83
151
|
this._routinesFile(),
|
|
84
|
-
|
|
85
|
-
|
|
152
|
+
() => callback(this._readRoutines()),
|
|
153
|
+
{ failIfUnavailable: true },
|
|
86
154
|
);
|
|
87
155
|
}
|
|
88
156
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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>");
|
|
157
|
+
_resolveFromMap(map, id) {
|
|
158
|
+
if (map[id]) return map[id];
|
|
159
|
+
const matches = Object.values(map).filter(
|
|
160
|
+
(routine) => routine.id.startsWith(id) || routine.name === id,
|
|
161
|
+
);
|
|
162
|
+
if (matches.length === 1) return matches[0];
|
|
163
|
+
if (matches.length > 1) {
|
|
164
|
+
throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
|
|
117
165
|
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
118
168
|
|
|
169
|
+
_createInMap(map, def) {
|
|
170
|
+
const definition = normalizeRoutineDefinition(def);
|
|
119
171
|
const routine = {
|
|
120
172
|
id: shortId(),
|
|
121
|
-
|
|
122
|
-
prompt,
|
|
123
|
-
trigger,
|
|
173
|
+
...definition,
|
|
124
174
|
enabled: true,
|
|
125
175
|
createdAt: this._now(),
|
|
126
176
|
lastFiredAt: null,
|
|
127
177
|
lastSeenGithubEventId: null,
|
|
128
178
|
};
|
|
129
|
-
const map = this._readRoutines();
|
|
130
179
|
map[routine.id] = routine;
|
|
131
|
-
this._writeRoutines(map);
|
|
132
180
|
return routine;
|
|
133
181
|
}
|
|
134
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
|
|
185
|
+
*/
|
|
186
|
+
create(def = {}) {
|
|
187
|
+
return this._withDefinitionLock((map) => {
|
|
188
|
+
const routine = this._createInMap(map, def);
|
|
189
|
+
this._writeRoutines(map);
|
|
190
|
+
return routine;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
135
194
|
list() {
|
|
136
195
|
return Object.values(this._readRoutines()).sort(
|
|
137
196
|
(a, b) => (b.createdAt || 0) - (a.createdAt || 0),
|
|
@@ -140,26 +199,26 @@ export class RoutineStore {
|
|
|
140
199
|
|
|
141
200
|
get(id) {
|
|
142
201
|
const map = this._readRoutines();
|
|
143
|
-
|
|
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;
|
|
202
|
+
return this._resolveFromMap(map, id);
|
|
153
203
|
}
|
|
154
204
|
|
|
155
205
|
update(id, patch) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
206
|
+
return this._withDefinitionLock((map) => {
|
|
207
|
+
const routine = this._resolveFromMap(map, id);
|
|
208
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
209
|
+
const next = { ...routine, ...patch, id: routine.id };
|
|
210
|
+
map[routine.id] = next;
|
|
211
|
+
this._writeRoutines(map);
|
|
212
|
+
return next;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
updateDefinition(id, definition) {
|
|
217
|
+
const normalized = normalizeRoutineDefinition(definition);
|
|
218
|
+
return this.update(id, {
|
|
219
|
+
...normalized,
|
|
220
|
+
lastSeenGithubEventId: null,
|
|
221
|
+
});
|
|
163
222
|
}
|
|
164
223
|
|
|
165
224
|
setEnabled(id, enabled) {
|
|
@@ -167,12 +226,54 @@ export class RoutineStore {
|
|
|
167
226
|
}
|
|
168
227
|
|
|
169
228
|
remove(id) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
229
|
+
return this._withDefinitionLock((map) => {
|
|
230
|
+
const routine = this._resolveFromMap(map, id);
|
|
231
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
232
|
+
delete map[routine.id];
|
|
233
|
+
this._writeRoutines(map);
|
|
234
|
+
return routine;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
createIfRevision(expectedRevision, revisionOfMap, definition) {
|
|
239
|
+
return this._withDefinitionLock((map) => {
|
|
240
|
+
if (revisionOfMap(map) !== expectedRevision) {
|
|
241
|
+
const error = new Error("routine collection changed before create");
|
|
242
|
+
error.code = "ROUTINE_REVISION_CONFLICT";
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
const routine = this._createInMap(map, definition);
|
|
246
|
+
this._writeRoutines(map);
|
|
247
|
+
return routine;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
readIfRevision(id, expectedRevision, revisionOfRoutine) {
|
|
252
|
+
return this._withDefinitionLock((map) => {
|
|
253
|
+
const routine = map[id];
|
|
254
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
255
|
+
if (revisionOfRoutine(routine) !== expectedRevision) {
|
|
256
|
+
const error = new Error(`routine changed before action: ${id}`);
|
|
257
|
+
error.code = "ROUTINE_REVISION_CONFLICT";
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
return structuredClone(routine);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
mutateIfRevision(id, expectedRevision, revisionOfRoutine, mutation) {
|
|
265
|
+
return this._withDefinitionLock((map) => {
|
|
266
|
+
const routine = map[id];
|
|
267
|
+
if (!routine) throw new Error(`routine not found: ${id}`);
|
|
268
|
+
if (revisionOfRoutine(routine) !== expectedRevision) {
|
|
269
|
+
const error = new Error(`routine changed before mutation: ${id}`);
|
|
270
|
+
error.code = "ROUTINE_REVISION_CONFLICT";
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
const result = mutation(routine, map);
|
|
274
|
+
this._writeRoutines(map);
|
|
275
|
+
return result;
|
|
276
|
+
});
|
|
176
277
|
}
|
|
177
278
|
|
|
178
279
|
/** Routines the driver should fire now (cron due / once reached). */
|
|
@@ -204,13 +305,24 @@ export class RoutineStore {
|
|
|
204
305
|
}
|
|
205
306
|
|
|
206
307
|
recordRunStart(routineId, meta = {}) {
|
|
207
|
-
const runId =
|
|
308
|
+
const runId =
|
|
309
|
+
typeof meta.runId === "string" && meta.runId.trim()
|
|
310
|
+
? meta.runId.trim()
|
|
311
|
+
: `run-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
312
|
+
if (
|
|
313
|
+
runId.length > 512 ||
|
|
314
|
+
[...runId].some((char) => char.codePointAt(0) < 32)
|
|
315
|
+
) {
|
|
316
|
+
throw new Error("routine runId is invalid");
|
|
317
|
+
}
|
|
208
318
|
this._appendRun({
|
|
209
319
|
type: "start",
|
|
210
320
|
runId,
|
|
211
321
|
routineId,
|
|
212
322
|
trigger: meta.trigger || null,
|
|
213
323
|
pid: meta.pid || null,
|
|
324
|
+
schedulerOccurrenceId: meta.schedulerOccurrenceId || null,
|
|
325
|
+
schedulerSnapshotDigest: meta.schedulerSnapshotDigest || null,
|
|
214
326
|
startedAt: this._now(),
|
|
215
327
|
});
|
|
216
328
|
return runId;
|
|
@@ -263,6 +375,15 @@ export class RoutineStore {
|
|
|
263
375
|
.map(({ type: _t, ...rest }) => rest);
|
|
264
376
|
}
|
|
265
377
|
|
|
378
|
+
getRun(runId) {
|
|
379
|
+
if (typeof runId !== "string" || runId.length === 0) return null;
|
|
380
|
+
return (
|
|
381
|
+
this.listRuns({ limit: Number.MAX_SAFE_INTEGER }).find(
|
|
382
|
+
(run) => run.runId === runId,
|
|
383
|
+
) || null
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
266
387
|
/** Per-routine aggregate for `cc routine list`: runs, ok/failed, cost. */
|
|
267
388
|
summarize(routineId) {
|
|
268
389
|
const runs = this.listRuns({ routineId, limit: 1000 });
|
|
@@ -286,10 +407,51 @@ export class RoutineStore {
|
|
|
286
407
|
* lastFiredAt (and disable a fired `once`). `runAgent({prompt})` must resolve
|
|
287
408
|
* `{ exitCode, output, usage?, costUsd?, pid? }`.
|
|
288
409
|
*/
|
|
289
|
-
export async function
|
|
410
|
+
export async function executeRoutine(store, routine, runAgent, meta = {}) {
|
|
411
|
+
const requestedRunId =
|
|
412
|
+
typeof meta.runId === "string" && meta.runId.trim()
|
|
413
|
+
? meta.runId.trim()
|
|
414
|
+
: null;
|
|
415
|
+
const existing = requestedRunId ? store.getRun(requestedRunId) : null;
|
|
416
|
+
if (existing) {
|
|
417
|
+
if (existing.routineId !== routine.id) {
|
|
418
|
+
const error = new Error("routine runId is bound to another routine");
|
|
419
|
+
error.code = "ROUTINE_RUN_ID_CONFLICT";
|
|
420
|
+
error.retryable = false;
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
if (existing.status === "running") {
|
|
424
|
+
const error = new Error(
|
|
425
|
+
"routine run outcome is unknown; refusing duplicate execution",
|
|
426
|
+
);
|
|
427
|
+
error.code = "ROUTINE_RUN_OUTCOME_UNKNOWN";
|
|
428
|
+
error.retryable = false;
|
|
429
|
+
throw error;
|
|
430
|
+
}
|
|
431
|
+
const current = store.get(routine.id);
|
|
432
|
+
if (current) {
|
|
433
|
+
store.update(routine.id, {
|
|
434
|
+
lastFiredAt: existing.startedAt,
|
|
435
|
+
...(current.trigger.kind === "once" ? { enabled: false } : {}),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
runId: existing.runId,
|
|
440
|
+
status: existing.status,
|
|
441
|
+
exitCode: existing.exitCode,
|
|
442
|
+
usage: existing.usage || null,
|
|
443
|
+
costUsd: Number.isFinite(existing.costUsd) ? existing.costUsd : null,
|
|
444
|
+
durationMs: existing.durationMs ?? null,
|
|
445
|
+
recovered: true,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
290
449
|
const startedAt = store._now();
|
|
291
450
|
const runId = store.recordRunStart(routine.id, {
|
|
292
451
|
trigger: meta.trigger || routine.trigger.kind,
|
|
452
|
+
...(requestedRunId ? { runId: requestedRunId } : {}),
|
|
453
|
+
schedulerOccurrenceId: meta.schedulerOccurrenceId || null,
|
|
454
|
+
schedulerSnapshotDigest: meta.schedulerSnapshotDigest || null,
|
|
293
455
|
});
|
|
294
456
|
let result;
|
|
295
457
|
try {
|
|
@@ -316,7 +478,19 @@ export async function fireRoutine(store, routine, runAgent, meta = {}) {
|
|
|
316
478
|
lastFiredAt: startedAt,
|
|
317
479
|
...(routine.trigger.kind === "once" ? { enabled: false } : {}),
|
|
318
480
|
});
|
|
319
|
-
return
|
|
481
|
+
return {
|
|
482
|
+
runId,
|
|
483
|
+
status: result.exitCode === 0 ? "ok" : "failed",
|
|
484
|
+
exitCode: result.exitCode,
|
|
485
|
+
usage: result.usage || null,
|
|
486
|
+
costUsd: Number.isFinite(result.costUsd) ? result.costUsd : null,
|
|
487
|
+
durationMs: store._now() - startedAt,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function fireRoutine(store, routine, runAgent, meta = {}) {
|
|
492
|
+
const execution = await executeRoutine(store, routine, runAgent, meta);
|
|
493
|
+
return execution.runId;
|
|
320
494
|
}
|
|
321
495
|
|
|
322
496
|
/**
|