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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "../lib/collaboration-run-store.js";
|
|
10
10
|
import { readSessionHostResumeState } from "../lib/session-host-snapshot.js";
|
|
11
11
|
import executionBroker from "../lib/process-execution-broker/index.js";
|
|
12
|
+
import { registerSchedulerDaemonCommands } from "./scheduler-daemon.js";
|
|
12
13
|
|
|
13
14
|
export function buildBackgroundDispatchArgs(prompt) {
|
|
14
15
|
return ["agent", "--bg", `--print=${prompt}`];
|
|
@@ -100,6 +101,21 @@ export function formatBackgroundAgentDetails(
|
|
|
100
101
|
);
|
|
101
102
|
}
|
|
102
103
|
if (session.phase) lines.push(` phase: ${session.phase}`);
|
|
104
|
+
if (session.pendingQuestion) {
|
|
105
|
+
const prompt = String(
|
|
106
|
+
session.pendingQuestion.prompt || session.pendingQuestion.question || "",
|
|
107
|
+
)
|
|
108
|
+
.replace(/\s+/gu, " ")
|
|
109
|
+
.trim()
|
|
110
|
+
.slice(0, 240);
|
|
111
|
+
lines.push(` needsInput: ${prompt || "question pending"}`);
|
|
112
|
+
}
|
|
113
|
+
if (session.needsInputIncident) {
|
|
114
|
+
const incident = session.needsInputIncident;
|
|
115
|
+
lines.push(
|
|
116
|
+
` needsInputIncident: ${incident.incidentId || "-"} status=${incident.status || "?"} notification=${incident.notification?.status || "?"}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
103
119
|
if (session.pr?.number) {
|
|
104
120
|
lines.push(
|
|
105
121
|
` pr: #${session.pr.number}${session.pr.state ? ` ${session.pr.state}` : ""}${session.pr.url ? ` ${session.pr.url}` : ""}`,
|
|
@@ -120,6 +136,19 @@ export function formatBackgroundAgentDetails(
|
|
|
120
136
|
lines.push("", "Commands:");
|
|
121
137
|
lines.push(` cc attach ${session.id}`);
|
|
122
138
|
lines.push(` cc logs ${session.id} -n 100`);
|
|
139
|
+
if (
|
|
140
|
+
["failed", "unconfigured"].includes(
|
|
141
|
+
session.needsInputIncident?.notification?.status,
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
lines.push(` cc daemon notify-needs-input ${session.id}`);
|
|
145
|
+
} else if (
|
|
146
|
+
["delivering", "outcome_unknown", "partial"].includes(
|
|
147
|
+
session.needsInputIncident?.notification?.status,
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
lines.push(` cc daemon notify-needs-input ${session.id} --force`);
|
|
151
|
+
}
|
|
123
152
|
if (session.status === "running") {
|
|
124
153
|
lines.push(` cc daemon stop ${session.id}`);
|
|
125
154
|
}
|
|
@@ -275,6 +304,49 @@ export async function replyBackgroundAgent(id, prompt, dependencies = {}) {
|
|
|
275
304
|
}
|
|
276
305
|
}
|
|
277
306
|
|
|
307
|
+
export async function retryBackgroundNeedsInputNotification(
|
|
308
|
+
id,
|
|
309
|
+
options = {},
|
|
310
|
+
dependencies = {},
|
|
311
|
+
) {
|
|
312
|
+
const sessionId = String(id || "").trim();
|
|
313
|
+
if (!sessionId) throw new Error("background agent id is required");
|
|
314
|
+
const supervisor = dependencies.supervisor || (await loadSupervisor());
|
|
315
|
+
const state = supervisor.readBackgroundAgentState(sessionId);
|
|
316
|
+
if (!state) throw new Error(`Background agent not found: ${sessionId}`);
|
|
317
|
+
if (!state.needsInputIncident) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`Background agent ${sessionId} has no needs-input incident`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const result = await supervisor.deliverBackgroundNeedsInputNotification(
|
|
323
|
+
sessionId,
|
|
324
|
+
{
|
|
325
|
+
retry: true,
|
|
326
|
+
force: options.force === true,
|
|
327
|
+
...(dependencies.notify ? { notify: dependencies.notify } : {}),
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
if (!result.applied) {
|
|
331
|
+
const error = new Error(
|
|
332
|
+
result.reason === "delivery_in_progress"
|
|
333
|
+
? `Needs-input notification is already being delivered for ${sessionId}`
|
|
334
|
+
: result.reason === "delivery_not_retryable"
|
|
335
|
+
? `Needs-input notification for ${sessionId} is not safely retryable; use --force only after accepting possible duplicate delivery`
|
|
336
|
+
: `Background agent ${sessionId} no longer has a pending needs-input incident`,
|
|
337
|
+
);
|
|
338
|
+
error.code = "BACKGROUND_NEEDS_INPUT_NOTIFICATION_NOT_RETRYABLE";
|
|
339
|
+
error.reason = result.reason;
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
id: sessionId,
|
|
344
|
+
incidentId: result.incident?.incidentId || null,
|
|
345
|
+
incidentStatus: result.incident?.status || null,
|
|
346
|
+
notification: result.incident?.notification || null,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
278
350
|
export const LOG_TRUNCATION_NOTICE =
|
|
279
351
|
"--- log truncated/rotated, resuming from tail ---";
|
|
280
352
|
const TRUNCATION_RESUME_TAIL_BYTES = 4096;
|
|
@@ -825,7 +897,36 @@ export function registerBackgroundSessionCommands(program) {
|
|
|
825
897
|
|
|
826
898
|
const daemon = program
|
|
827
899
|
.command("daemon")
|
|
828
|
-
.description("
|
|
900
|
+
.description("Manage background agent and scheduler services");
|
|
901
|
+
|
|
902
|
+
registerSchedulerDaemonCommands(daemon);
|
|
903
|
+
|
|
904
|
+
daemon
|
|
905
|
+
.command("notify-needs-input <id>")
|
|
906
|
+
.description(
|
|
907
|
+
"Retry a durable needs-input notification for a background run",
|
|
908
|
+
)
|
|
909
|
+
.option(
|
|
910
|
+
"--force",
|
|
911
|
+
"Retry an ambiguous or already-delivered notification (may duplicate)",
|
|
912
|
+
)
|
|
913
|
+
.option("--json", "Output as JSON")
|
|
914
|
+
.action(async (id, options) => {
|
|
915
|
+
try {
|
|
916
|
+
const result = await retryBackgroundNeedsInputNotification(id, options);
|
|
917
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
918
|
+
else {
|
|
919
|
+
logger.log(
|
|
920
|
+
chalk.green(
|
|
921
|
+
`Needs-input notification ${result.notification?.status || "settled"} for ${id}`,
|
|
922
|
+
),
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
} catch (error) {
|
|
926
|
+
logger.error(chalk.red(error.message));
|
|
927
|
+
process.exitCode = 1;
|
|
928
|
+
}
|
|
929
|
+
});
|
|
829
930
|
|
|
830
931
|
daemon
|
|
831
932
|
.command("status")
|
package/src/commands/cowork.js
CHANGED
|
@@ -475,7 +475,7 @@ export function registerCoworkCommand(program) {
|
|
|
475
475
|
for (const s of schedules) {
|
|
476
476
|
const flag = s.enabled ? chalk.green("✓") : chalk.gray("✗");
|
|
477
477
|
logger.log(
|
|
478
|
-
` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)} [${s.templateId || "free"}]`,
|
|
478
|
+
` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)}${s.timeZone ? ` [${s.timeZone}]` : ""} [${s.templateId || "free"}]`,
|
|
479
479
|
);
|
|
480
480
|
logger.log(chalk.gray(` ${s.userMessage.slice(0, 80)}`));
|
|
481
481
|
if (s.lastRunAt) {
|
|
@@ -495,6 +495,7 @@ export function registerCoworkCommand(program) {
|
|
|
495
495
|
"5-field cron expression (e.g. '0 9 * * 1-5')",
|
|
496
496
|
)
|
|
497
497
|
.requiredOption("--message <text>", "Task prompt / user message")
|
|
498
|
+
.option("--timezone <iana>", "IANA time zone (for example Asia/Shanghai)")
|
|
498
499
|
.option(
|
|
499
500
|
"--template <id>",
|
|
500
501
|
"Template id (e.g. doc-convert); omit for free mode",
|
|
@@ -505,6 +506,7 @@ export function registerCoworkCommand(program) {
|
|
|
505
506
|
try {
|
|
506
507
|
const entry = addSchedule(process.cwd(), {
|
|
507
508
|
cron: options.cron,
|
|
509
|
+
timeZone: options.timezone || null,
|
|
508
510
|
templateId: options.template || null,
|
|
509
511
|
userMessage: options.message,
|
|
510
512
|
files: options.files
|
|
@@ -516,6 +518,9 @@ export function registerCoworkCommand(program) {
|
|
|
516
518
|
});
|
|
517
519
|
logger.log(chalk.green(`✓ Added schedule ${entry.id}`));
|
|
518
520
|
logger.log(chalk.gray(` cron: ${entry.cron}`));
|
|
521
|
+
if (entry.timeZone) {
|
|
522
|
+
logger.log(chalk.gray(` timezone: ${entry.timeZone}`));
|
|
523
|
+
}
|
|
519
524
|
logger.log(chalk.gray(` template: ${entry.templateId || "free"}`));
|
|
520
525
|
} catch (err) {
|
|
521
526
|
logger.error(err.message);
|
|
@@ -565,19 +570,30 @@ export function registerCoworkCommand(program) {
|
|
|
565
570
|
cron
|
|
566
571
|
.command("run")
|
|
567
572
|
.description("Start the cron scheduler in the foreground (Ctrl-C to stop)")
|
|
568
|
-
.option(
|
|
573
|
+
.option(
|
|
574
|
+
"--interval <ms>",
|
|
575
|
+
"Tick interval in ms (auto: 1000 for six-field cron, otherwise 60000)",
|
|
576
|
+
)
|
|
569
577
|
.action(async (options) => {
|
|
570
|
-
const [
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
578
|
+
const [
|
|
579
|
+
{ CoworkCronKernelScheduler },
|
|
580
|
+
{ openSchedulerStore },
|
|
581
|
+
{ runCoworkTask },
|
|
582
|
+
] = await Promise.all([
|
|
583
|
+
import("../lib/scheduler-kernel/cowork-cron-adapter.js"),
|
|
584
|
+
import("../lib/scheduler-kernel/store.js"),
|
|
585
|
+
import("../lib/cowork-task-runner.js"),
|
|
586
|
+
]);
|
|
587
|
+
const schedulerStore = openSchedulerStore();
|
|
588
|
+
const intervalMs =
|
|
589
|
+
options.interval === undefined
|
|
590
|
+
? undefined
|
|
591
|
+
: parseInt(options.interval, 10) || 60_000;
|
|
592
|
+
const scheduler = new CoworkCronKernelScheduler({
|
|
579
593
|
cwd: process.cwd(),
|
|
580
|
-
|
|
594
|
+
schedulerStore,
|
|
595
|
+
runTask: runCoworkTask,
|
|
596
|
+
...(intervalMs === undefined ? {} : { intervalMs }),
|
|
581
597
|
onEvent: (e) => {
|
|
582
598
|
const ts = new Date().toISOString();
|
|
583
599
|
logger.log(chalk.gray(`[${ts}] ${JSON.stringify(e)}`));
|
|
@@ -587,8 +603,9 @@ export function registerCoworkCommand(program) {
|
|
|
587
603
|
logger.log(
|
|
588
604
|
chalk.green("Cowork cron scheduler running. Press Ctrl-C to stop."),
|
|
589
605
|
);
|
|
590
|
-
process.
|
|
606
|
+
process.once("SIGINT", () => {
|
|
591
607
|
scheduler.stop();
|
|
608
|
+
schedulerStore.close();
|
|
592
609
|
process.exit(0);
|
|
593
610
|
});
|
|
594
611
|
});
|
package/src/commands/loop.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* cc loop — repeatedly run a command or agent prompt on a fixed interval
|
|
3
3
|
* (Claude-Code `/loop` parity, MVP). Lightweight by design: unlike `cc ccron`
|
|
4
4
|
* (in-memory profile governance, runs nothing) or `cc automation` (DB-backed
|
|
5
|
-
* flow/trigger engine), this
|
|
6
|
-
*
|
|
5
|
+
* flow/trigger engine), this re-runs ONE thing until a stop condition fires or
|
|
6
|
+
* you Ctrl-C. Each iteration is claimed and settled by the durable scheduler
|
|
7
|
+
* kernel; the foreground command remains the lifecycle owner between rounds.
|
|
7
8
|
*
|
|
8
9
|
* cc loop "check if CI passed, summarize failures" # wraps `cc agent -p`
|
|
9
10
|
* cc loop --every 30s -- npm test # external command
|
|
@@ -20,9 +21,11 @@
|
|
|
20
21
|
* - with `--` → the operands after it are an EXTERNAL command (shell-resolved)
|
|
21
22
|
*
|
|
22
23
|
* The loop driver lives in src/lib/loop.js (pure, clock-injected). This layer
|
|
23
|
-
*
|
|
24
|
+
* builds the concrete iteration (spawn + tee output), binds scheduler identity,
|
|
25
|
+
* persists resumable iteration plans, and wires SIGINT.
|
|
24
26
|
*/
|
|
25
27
|
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
26
29
|
import { fileURLToPath } from "node:url";
|
|
27
30
|
import chalk from "chalk";
|
|
28
31
|
import { logger } from "../lib/logger.js";
|
|
@@ -34,16 +37,22 @@ import {
|
|
|
34
37
|
makeSleep,
|
|
35
38
|
parseLoopDirectives,
|
|
36
39
|
summarizeLoopEvents,
|
|
40
|
+
findLoopIterationResult,
|
|
37
41
|
} from "../lib/loop.js";
|
|
38
42
|
import {
|
|
39
43
|
startSession,
|
|
40
44
|
appendEvent,
|
|
45
|
+
appendEventIfHead,
|
|
41
46
|
readEvents,
|
|
42
47
|
sessionExists,
|
|
43
48
|
} from "../harness/jsonl-session-store.js";
|
|
49
|
+
import { openSchedulerStore } from "../lib/scheduler-kernel/store.js";
|
|
50
|
+
import { LoopSchedulerBridge } from "../lib/scheduler-kernel/loop-adapter.js";
|
|
44
51
|
|
|
45
52
|
export const _deps = {
|
|
46
53
|
spawn: executionBroker.spawn.bind(executionBroker),
|
|
54
|
+
openSchedulerStore,
|
|
55
|
+
createLoopSchedulerBridge: (options) => new LoopSchedulerBridge(options),
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
/**
|
|
@@ -68,10 +77,11 @@ const BIN_PATH = fileURLToPath(
|
|
|
68
77
|
* the user sees live output) while capturing it, so `--until <regex>` can match
|
|
69
78
|
* against what was printed. Resolves with { exitCode, output }.
|
|
70
79
|
*/
|
|
71
|
-
function spawnIteration(cmd, args, { shell, onChild, capture }) {
|
|
80
|
+
function spawnIteration(cmd, args, { shell, cwd, onChild, capture, signal }) {
|
|
72
81
|
return new Promise((resolve) => {
|
|
73
82
|
const child = _deps.spawn(cmd, args, {
|
|
74
83
|
shell,
|
|
84
|
+
cwd,
|
|
75
85
|
stdio: capture ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
76
86
|
env: process.env,
|
|
77
87
|
origin: "loop:iteration",
|
|
@@ -80,6 +90,17 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
|
|
|
80
90
|
});
|
|
81
91
|
if (onChild) onChild(child);
|
|
82
92
|
|
|
93
|
+
const onAbort = () => {
|
|
94
|
+
if (child.exitCode == null) {
|
|
95
|
+
try {
|
|
96
|
+
child.kill("SIGINT");
|
|
97
|
+
} catch {
|
|
98
|
+
// The child may have exited between the state check and kill.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
103
|
+
|
|
83
104
|
const outChunks = [];
|
|
84
105
|
if (capture) {
|
|
85
106
|
child.stdout?.on("data", (d) => {
|
|
@@ -93,16 +114,22 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
|
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
// `close` (not `exit`) so piped stdio is fully drained before we resolve.
|
|
96
|
-
child.on("close", (code,
|
|
117
|
+
child.on("close", (code, childSignal) => {
|
|
118
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
97
119
|
// Decode the captured bytes once, at the end. Accumulating with
|
|
98
120
|
// `output += chunk` would decode each stdout chunk independently, so a
|
|
99
121
|
// multi-byte UTF-8 character split across a chunk boundary corrupts the
|
|
100
122
|
// captured string that `--until <regex>` / `--dynamic` directive parsing
|
|
101
123
|
// reads. (The raw passthrough writes above are unaffected.)
|
|
102
124
|
const output = Buffer.concat(outChunks).toString("utf-8");
|
|
103
|
-
resolve({
|
|
125
|
+
resolve({
|
|
126
|
+
exitCode: code == null ? null : code,
|
|
127
|
+
output,
|
|
128
|
+
signal: childSignal,
|
|
129
|
+
});
|
|
104
130
|
});
|
|
105
131
|
child.on("error", (err) => {
|
|
132
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
106
133
|
resolve({
|
|
107
134
|
exitCode: 127,
|
|
108
135
|
output: String(err.message || err),
|
|
@@ -112,6 +139,73 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
|
|
|
112
139
|
});
|
|
113
140
|
}
|
|
114
141
|
|
|
142
|
+
function recoveredIterationResult(data) {
|
|
143
|
+
return {
|
|
144
|
+
exitCode: data?.exitCode ?? null,
|
|
145
|
+
signal: data?.signal ?? null,
|
|
146
|
+
durationMs: data?.durationMs ?? null,
|
|
147
|
+
done: data?.done === true,
|
|
148
|
+
nextDelayMs: data?.nextDelayMs ?? null,
|
|
149
|
+
matchedUntil: data?.matchedUntil === true,
|
|
150
|
+
outputBytes: data?.outputBytes ?? 0,
|
|
151
|
+
outputDigest: data?.outputDigest ?? null,
|
|
152
|
+
schedulerOccurrenceId: data?.schedulerOccurrenceId ?? null,
|
|
153
|
+
recovered: true,
|
|
154
|
+
output: "",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Publish one stable iteration schedule under the canonical session writer
|
|
160
|
+
* lock. A concurrent resumer either observes the same schedule or the already
|
|
161
|
+
* completed terminal record; it never invents a second scheduledFor identity.
|
|
162
|
+
*/
|
|
163
|
+
function claimPersistedIteration(sessionId, iteration, now = Date.now()) {
|
|
164
|
+
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
165
|
+
const events = readEvents(sessionId);
|
|
166
|
+
const completed = findLoopIterationResult(events, iteration);
|
|
167
|
+
if (completed) return { completed, scheduledFor: null };
|
|
168
|
+
const summary = summarizeLoopEvents(events);
|
|
169
|
+
if (summary.pendingIteration?.n === iteration) {
|
|
170
|
+
return {
|
|
171
|
+
completed: null,
|
|
172
|
+
scheduledFor: summary.pendingIteration.scheduledFor,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (summary.pendingIteration) {
|
|
176
|
+
const error = new Error(
|
|
177
|
+
`loop session has unresolved iteration ${summary.pendingIteration.n}`,
|
|
178
|
+
);
|
|
179
|
+
error.code = "LOOP_PENDING_ITERATION_CONFLICT";
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
const expectedHeadHash = events[events.length - 1]?.hash || null;
|
|
183
|
+
try {
|
|
184
|
+
appendEventIfHead(
|
|
185
|
+
sessionId,
|
|
186
|
+
"loop_iteration_scheduled",
|
|
187
|
+
{ n: iteration, scheduledFor: now },
|
|
188
|
+
expectedHeadHash,
|
|
189
|
+
);
|
|
190
|
+
return { completed: null, scheduledFor: now };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error?.code === "SESSION_REVISION_STALE") continue;
|
|
193
|
+
// An append classified as outcome-unknown may still have committed.
|
|
194
|
+
const after = readEvents(sessionId);
|
|
195
|
+
const recovered = findLoopIterationResult(after, iteration);
|
|
196
|
+
if (recovered) return { completed: recovered, scheduledFor: null };
|
|
197
|
+
const pending = summarizeLoopEvents(after).pendingIteration;
|
|
198
|
+
if (pending?.n === iteration) {
|
|
199
|
+
return { completed: null, scheduledFor: pending.scheduledFor };
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const error = new Error("loop iteration schedule changed too many times");
|
|
205
|
+
error.code = "LOOP_ITERATION_SCHEDULE_CONTENTION";
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
|
|
115
209
|
/**
|
|
116
210
|
* Build the concrete child invocation from the resolved operands + mode.
|
|
117
211
|
* Shared by fresh runs and `--resume` (which reconstructs it from saved config).
|
|
@@ -283,6 +377,7 @@ export function registerLoopCommand(program) {
|
|
|
283
377
|
savedConfig && !fromCli("untilExitZero")
|
|
284
378
|
? Boolean(savedConfig.untilExitZero)
|
|
285
379
|
: Boolean(options.untilExitZero);
|
|
380
|
+
const executionCwd = savedConfig?.cwd || process.cwd();
|
|
286
381
|
|
|
287
382
|
// --- build the child invocation (shared with resume) ---
|
|
288
383
|
const { cmd, args, shell, label } = buildInvocation({
|
|
@@ -308,6 +403,7 @@ export function registerLoopCommand(program) {
|
|
|
308
403
|
maxIterations: maxIterations ?? null,
|
|
309
404
|
untilExitZero,
|
|
310
405
|
until: untilRaw || null,
|
|
406
|
+
cwd: executionCwd,
|
|
311
407
|
});
|
|
312
408
|
}
|
|
313
409
|
|
|
@@ -346,7 +442,54 @@ export function registerLoopCommand(program) {
|
|
|
346
442
|
|
|
347
443
|
const startedAt = Date.now();
|
|
348
444
|
let summary;
|
|
445
|
+
let schedulerStore;
|
|
349
446
|
try {
|
|
447
|
+
const executionId = sessionId || `loop-${randomUUID()}`;
|
|
448
|
+
schedulerStore = _deps.openSchedulerStore();
|
|
449
|
+
const loopScheduler = _deps.createLoopSchedulerBridge({
|
|
450
|
+
schedulerStore,
|
|
451
|
+
definition: {
|
|
452
|
+
executionId,
|
|
453
|
+
cwd: executionCwd,
|
|
454
|
+
execMode,
|
|
455
|
+
operands,
|
|
456
|
+
dynamic,
|
|
457
|
+
},
|
|
458
|
+
runIteration: async (n, { signal } = {}) => {
|
|
459
|
+
logger.log(chalk.gray(`\n▸ iteration ${n} — ${label}`));
|
|
460
|
+
const t0 = Date.now();
|
|
461
|
+
const res = await spawnIteration(cmd, args, {
|
|
462
|
+
shell,
|
|
463
|
+
cwd: executionCwd,
|
|
464
|
+
capture,
|
|
465
|
+
signal,
|
|
466
|
+
onChild: (c) => {
|
|
467
|
+
activeChild = c;
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
res.durationMs = Date.now() - t0;
|
|
471
|
+
res.matchedUntil = Boolean(
|
|
472
|
+
untilRegex && untilRegex.test(res.output || ""),
|
|
473
|
+
);
|
|
474
|
+
if (dynamic) {
|
|
475
|
+
const directive = parseLoopDirectives(res.output);
|
|
476
|
+
res.done = directive.done;
|
|
477
|
+
if (directive.nextDelayMs != null) {
|
|
478
|
+
res.nextDelayMs = directive.nextDelayMs;
|
|
479
|
+
}
|
|
480
|
+
if (directive.done) {
|
|
481
|
+
logger.log(chalk.gray(" directive: stop"));
|
|
482
|
+
} else if (directive.nextDelayMs != null) {
|
|
483
|
+
logger.log(
|
|
484
|
+
chalk.gray(
|
|
485
|
+
` directive: next in ${formatDuration(directive.nextDelayMs)}`,
|
|
486
|
+
),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return res;
|
|
491
|
+
},
|
|
492
|
+
});
|
|
350
493
|
summary = await runLoop({
|
|
351
494
|
intervalMs,
|
|
352
495
|
maxIterations,
|
|
@@ -370,46 +513,34 @@ export function registerLoopCommand(program) {
|
|
|
370
513
|
durationMs: res.durationMs ?? null,
|
|
371
514
|
done: Boolean(res.done),
|
|
372
515
|
nextDelayMs: res.nextDelayMs ?? null,
|
|
516
|
+
matchedUntil: Boolean(res.matchedUntil),
|
|
517
|
+
signal: res.signal ?? null,
|
|
518
|
+
outputBytes: res.outputBytes ?? 0,
|
|
519
|
+
outputDigest: res.outputDigest ?? null,
|
|
520
|
+
schedulerOccurrenceId: res.schedulerOccurrenceId ?? null,
|
|
521
|
+
recovered: Boolean(res.recovered),
|
|
373
522
|
});
|
|
374
523
|
}
|
|
375
524
|
},
|
|
376
525
|
runIteration: async (n) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
res.durationMs = Date.now() - t0;
|
|
387
|
-
// --dynamic: read the iteration's [[loop:next]] / [[loop:stop]]
|
|
388
|
-
// directive and surface it to runLoop as done / nextDelayMs.
|
|
389
|
-
// Use the RESOLVED `dynamic` (not raw options.dynamic): on
|
|
390
|
-
// `cc loop --resume`, dynamic is rehydrated from saved config while
|
|
391
|
-
// options.dynamic is false — gating on the raw flag would skip
|
|
392
|
-
// directive parsing and the model's [[loop:stop]] would be ignored,
|
|
393
|
-
// running the loop forever.
|
|
394
|
-
if (dynamic) {
|
|
395
|
-
const d = parseLoopDirectives(res.output);
|
|
396
|
-
res.done = d.done;
|
|
397
|
-
if (d.nextDelayMs != null) res.nextDelayMs = d.nextDelayMs;
|
|
398
|
-
if (d.done) {
|
|
399
|
-
logger.log(chalk.gray(` ↺ directive: stop`));
|
|
400
|
-
} else if (d.nextDelayMs != null) {
|
|
401
|
-
logger.log(
|
|
402
|
-
chalk.gray(
|
|
403
|
-
` ↺ directive: next in ${formatDuration(d.nextDelayMs)}`,
|
|
404
|
-
),
|
|
405
|
-
);
|
|
526
|
+
let scheduledFor = Date.now();
|
|
527
|
+
if (persist) {
|
|
528
|
+
const claim = claimPersistedIteration(
|
|
529
|
+
sessionId,
|
|
530
|
+
n,
|
|
531
|
+
scheduledFor,
|
|
532
|
+
);
|
|
533
|
+
if (claim.completed) {
|
|
534
|
+
return recoveredIterationResult(claim.completed);
|
|
406
535
|
}
|
|
536
|
+
scheduledFor = claim.scheduledFor;
|
|
407
537
|
}
|
|
408
|
-
return
|
|
538
|
+
return loopScheduler.runIteration(n, { scheduledFor });
|
|
409
539
|
},
|
|
410
540
|
});
|
|
411
541
|
} finally {
|
|
412
542
|
process.removeListener("SIGINT", onSigint);
|
|
543
|
+
schedulerStore?.close();
|
|
413
544
|
}
|
|
414
545
|
|
|
415
546
|
const elapsed = formatDuration(Date.now() - startedAt);
|
package/src/commands/routine.js
CHANGED
|
@@ -18,6 +18,11 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
import { logger } from "../lib/logger.js";
|
|
20
20
|
import { executionBroker } from "../lib/process-execution-broker/index.js";
|
|
21
|
+
import { openSchedulerStore } from "../lib/scheduler-kernel/store.js";
|
|
22
|
+
import {
|
|
23
|
+
RoutineSchedulerBridge,
|
|
24
|
+
routineBridgeRunId,
|
|
25
|
+
} from "../lib/scheduler-kernel/routine-adapter.js";
|
|
21
26
|
|
|
22
27
|
export const _deps = {
|
|
23
28
|
spawn: (...args) => executionBroker.spawn(...args),
|
|
@@ -107,6 +112,7 @@ export function defaultFetchEvents(repo) {
|
|
|
107
112
|
(Array.isArray(rows) ? rows : []).map((r) => ({
|
|
108
113
|
id: r.id,
|
|
109
114
|
type: r.type,
|
|
115
|
+
created_at: r.created_at,
|
|
110
116
|
})),
|
|
111
117
|
);
|
|
112
118
|
} catch {
|
|
@@ -281,14 +287,24 @@ export function registerRoutineCommand(program) {
|
|
|
281
287
|
.description("Fire a routine NOW (the webhook/API entry point)")
|
|
282
288
|
.option("--json", "Output as JSON")
|
|
283
289
|
.action(async (id, options) => {
|
|
290
|
+
let schedulerStore;
|
|
284
291
|
try {
|
|
285
292
|
const store = await loadStore();
|
|
286
293
|
const r = store.get(id);
|
|
287
294
|
if (!r) throw new Error(`routine not found: ${id}`);
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
295
|
+
schedulerStore = openSchedulerStore();
|
|
296
|
+
const bridge = new RoutineSchedulerBridge({
|
|
297
|
+
routineStore: store,
|
|
298
|
+
schedulerStore,
|
|
299
|
+
runAgent: defaultRunAgent,
|
|
291
300
|
});
|
|
301
|
+
const fired = await bridge.trigger(r);
|
|
302
|
+
const runId = routineBridgeRunId(fired);
|
|
303
|
+
if (!runId) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`routine occurrence did not execute: ${fired.result?.status || "unknown"}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
292
308
|
const run = store.listRuns({ routineId: r.id, limit: 1 })[0];
|
|
293
309
|
if (options.json) return console.log(JSON.stringify(run, null, 2));
|
|
294
310
|
logger.success(`Fired ${r.id} → ${runId} (${run?.status})`);
|
|
@@ -296,6 +312,8 @@ export function registerRoutineCommand(program) {
|
|
|
296
312
|
} catch (err) {
|
|
297
313
|
logger.error(err.message);
|
|
298
314
|
process.exitCode = 1;
|
|
315
|
+
} finally {
|
|
316
|
+
schedulerStore?.close();
|
|
299
317
|
}
|
|
300
318
|
});
|
|
301
319
|
|
|
@@ -304,25 +322,42 @@ export function registerRoutineCommand(program) {
|
|
|
304
322
|
.description("Driver: fire due cron/once routines and poll github triggers")
|
|
305
323
|
.option("--json", "Output as JSON")
|
|
306
324
|
.action(async (options) => {
|
|
325
|
+
let schedulerStore;
|
|
307
326
|
try {
|
|
308
327
|
const store = await loadStore();
|
|
309
|
-
|
|
310
|
-
|
|
328
|
+
schedulerStore = openSchedulerStore();
|
|
329
|
+
const bridge = new RoutineSchedulerBridge({
|
|
330
|
+
routineStore: store,
|
|
331
|
+
schedulerStore,
|
|
332
|
+
runAgent: defaultRunAgent,
|
|
333
|
+
});
|
|
311
334
|
const fired = [];
|
|
312
|
-
for (const
|
|
335
|
+
for (const scheduled of await bridge.runDue()) {
|
|
336
|
+
const runId = routineBridgeRunId(scheduled);
|
|
337
|
+
if (!runId) {
|
|
338
|
+
if (scheduled.result?.status === "busy") continue;
|
|
339
|
+
throw new Error(
|
|
340
|
+
`routine occurrence did not execute: ${scheduled.routine} (${scheduled.result?.status || "unknown"})`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
313
343
|
fired.push({
|
|
314
|
-
routine:
|
|
315
|
-
runId
|
|
316
|
-
trigger: r.trigger.kind,
|
|
317
|
-
}),
|
|
344
|
+
routine: scheduled.routine,
|
|
345
|
+
runId,
|
|
318
346
|
});
|
|
319
347
|
}
|
|
320
348
|
for (const r of store.githubRoutines()) {
|
|
321
|
-
const
|
|
349
|
+
const github = await bridge.pollGithub(r, {
|
|
322
350
|
fetchEvents: defaultFetchEvents,
|
|
323
|
-
runAgent: defaultRunAgent,
|
|
324
351
|
});
|
|
325
|
-
|
|
352
|
+
if (!github) continue;
|
|
353
|
+
const runId = routineBridgeRunId(github);
|
|
354
|
+
if (!runId) {
|
|
355
|
+
if (github.result?.status === "busy") continue;
|
|
356
|
+
throw new Error(
|
|
357
|
+
`routine GitHub occurrence did not execute: ${github.routine} (${github.result?.status || "unknown"})`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
fired.push({ routine: github.routine, runId });
|
|
326
361
|
}
|
|
327
362
|
if (options.json)
|
|
328
363
|
return console.log(JSON.stringify({ fired }, null, 2));
|
|
@@ -334,6 +369,8 @@ export function registerRoutineCommand(program) {
|
|
|
334
369
|
} catch (err) {
|
|
335
370
|
logger.error(err.message);
|
|
336
371
|
process.exitCode = 1;
|
|
372
|
+
} finally {
|
|
373
|
+
schedulerStore?.close();
|
|
337
374
|
}
|
|
338
375
|
});
|
|
339
376
|
|