chainlesschain 0.163.4 → 0.163.5

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.
Files changed (172) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DB5X7qqV.js → AIOps-Cv2EE1Zy.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-CcpxMLuW.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-DE8mJoMI.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-DKn9dk9L.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-Cp3mG3Py.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-zvsQfF2C.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-Bi85SsjN.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-Cw47Dj6s.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-C5Ip2afH.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-C0iAFkJv.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-BV9Wv7wO.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-3KZZCiAF.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-C8OMbBAz.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-B-MB84LJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-DpR7Dxao.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-CyDtz0OS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-5tiRa2An.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-DnQaKZ5x.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-BaWfoEDf.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-Bu0HoHhb.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-31zk8upX.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-BmVne-e2.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-B4PkoOWd.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-BZ6tyFtI.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-Osozn4Ds.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-DaH8TOAP.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-tbU0uwis.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-Df8LQBrL.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DsMB6-14.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-BnH0dZzd.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-DhnN1N_P.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DnNshAgn.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BmBFX3_N.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-DCslfQU3.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-CPEgWDc3.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-C36qjgF9.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-tNHvrgqN.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-Xaf6cKjc.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-D2E5TTeO.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-D4AJ2dwV.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-Df-gtn2_.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-DTCHLWld.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-DruZ_1Jb.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Dx3pdGDs.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Bi3L69oL.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-BHY33m7C.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-BG2ZVEV-.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-Viwtg4Mn.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-Clm_yc1Z.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-NKmmDiOw.js} +5 -5
  52. package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DIL8u9Nk.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-B-jM4wt-.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-627I__H5.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-2DN2qrkW.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-CN4wC0sF.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-K35vsh5H.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-B_wQadhX.js +1 -0
  59. package/src/assets/web-panel/assets/Providers-MckaQUhQ.js +1 -0
  60. package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-BS3mtA_O.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-BHp_nofL.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-DlmnPuZj.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-tsyFbvpy.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-Ba1iWv2D.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-NnlGC--f.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-CjThSIRv.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-CojUyC5x.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-c-sxeJEc.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-CtcKEEmF.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-CIBBXflN.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-D1XOOK9n.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-hRfEp5Sn.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-CGdzFIYw.js → SpeechSettings-Dexb1f_6.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-BJsSFGYy.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-CfSZ8A1L.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-idDf47W-.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-C-UsX6w6.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-BgJtX5n1.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DP0BLKTl.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-LwCzqETp.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-D_9tVch0.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BH2UlEWh.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-CIpD0nYb.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-9ShhYxK_.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-C7afGNIG.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-bUKn6i5R.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-CxgQaGyp.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-DMVkmJwx.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-CDzQh5nS.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-C51kEF4D.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-BTk9bXBn.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-CMje33rb.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-0Auljb10.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-frSojpHj.js → index-B0-zMtKA.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-BAfY7m7i.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-BBz9LIMS.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-BCRvG9lq.js} +1 -1
  99. package/src/assets/web-panel/assets/index-BEeOdfEe.js +1 -0
  100. package/src/assets/web-panel/assets/index-BTe0TOu6.js +1 -0
  101. package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-BTlsE8Df.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BVePe_Rf.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-BW4F5YDF.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-BXQBPLyE.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-BeEaAEIp.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-BtlEqHLP.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-BzCkNylB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-C1oZQmAA.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-C1oZReaY.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-C4rQCS8T.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-C5IWTMVl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CCLj9hcD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-CDLkNkLj.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-Cd0hmPxD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-CfC-piWZ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-CtM8OT_7.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-CvSeVzqe.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CwGPwjUu.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-CypsfcEq.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-D9rC-fGW.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-DJfnCuMq.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-DRG-aqOs.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-DRLQyp2A.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-DgivOKHA.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-DpcrFxMI.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-DqU9njjT.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-301ogenv.js → index-Ld_4mFmC.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-OS-vfbTA.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-h31MwrME.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DM724NgU.js → index-hg_emBsi.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-togc_uYn.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-x53Vf7la.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-vrA8K-ac.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-ByEzwIAl.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-Ck4AWjw-.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-6p0l9SrB.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-B1CUPQ6R.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-B1STg1iW.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-DTTllkBV.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-AIrrCfBb.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-C_fPMjdR.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-D5ow2yIP.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-Dy3hEA--.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-WfzcfSh4.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CKSejfas.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-od3zpPBN.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-DRD_2DFA.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-DmNrEtgf.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +52 -6
  151. package/src/commands/cowork.js +24 -12
  152. package/src/commands/routine.js +40 -10
  153. package/src/data/changelog.json +13 -0
  154. package/src/lib/agent-schedule-store.js +197 -4
  155. package/src/lib/cowork-cron.js +243 -25
  156. package/src/lib/micro-compact.js +11 -5
  157. package/src/lib/process-execution-broker/index.js +14 -3
  158. package/src/lib/routine-store.js +76 -3
  159. package/src/lib/scheduler-kernel/agenda-adapter.js +542 -0
  160. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +774 -0
  161. package/src/lib/scheduler-kernel/routine-adapter.js +517 -0
  162. package/src/lib/scheduler-kernel/runtime.js +527 -0
  163. package/src/lib/scheduler-kernel/store.js +112 -2
  164. package/src/lib/session-message-provenance.js +4 -1
  165. package/src/repl/agent-repl.js +24 -5
  166. package/src/runtime/agent-core.js +36 -16
  167. package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
  168. package/src/assets/web-panel/assets/Projects-DhfifCKx.js +0 -1
  169. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +0 -1
  170. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +0 -1
  171. package/src/assets/web-panel/assets/index-CKB0vbJU.js +0 -1
  172. package/src/assets/web-panel/assets/index-DgoXmwqB.js +0 -1
@@ -0,0 +1,527 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ OCCURRENCE_STATUS,
4
+ SchedulerKernelError,
5
+ normalizeEpochMs,
6
+ normalizeIdentifier,
7
+ normalizeJson,
8
+ } from "./contract.js";
9
+
10
+ export const DEFAULT_RUNTIME_LEASE_MS = 60_000;
11
+ export const MIN_RUNTIME_LEASE_MS = 1_000;
12
+ export const DEFAULT_RUN_LIMIT = 100;
13
+ export const MAX_RUN_LIMIT = 10_000;
14
+
15
+ const TERMINAL_OCCURRENCE_STATUSES = new Set([
16
+ OCCURRENCE_STATUS.SUCCEEDED,
17
+ OCCURRENCE_STATUS.DEAD_LETTER,
18
+ ]);
19
+
20
+ function runtimeError(code, message, details = undefined, cause = undefined) {
21
+ return new SchedulerKernelError(
22
+ code,
23
+ message,
24
+ details,
25
+ cause ? { cause } : undefined,
26
+ );
27
+ }
28
+
29
+ function assertStore(store) {
30
+ const required = [
31
+ "claimNext",
32
+ "claimOccurrence",
33
+ "getJob",
34
+ "getOccurrence",
35
+ "renew",
36
+ "settle",
37
+ ];
38
+ if (
39
+ !store ||
40
+ required.some((method) => typeof store[method] !== "function")
41
+ ) {
42
+ throw runtimeError(
43
+ "SCHEDULER_RUNTIME_INVALID_STORE",
44
+ "Scheduler runtime requires a compatible scheduler store",
45
+ );
46
+ }
47
+ return store;
48
+ }
49
+
50
+ function normalizeLeaseMs(value = DEFAULT_RUNTIME_LEASE_MS) {
51
+ if (!Number.isSafeInteger(value) || value < MIN_RUNTIME_LEASE_MS) {
52
+ throw runtimeError(
53
+ "SCHEDULER_RUNTIME_INVALID_LEASE",
54
+ `leaseMs must be an integer >= ${MIN_RUNTIME_LEASE_MS}`,
55
+ );
56
+ }
57
+ return value;
58
+ }
59
+
60
+ function normalizeRenewInterval(value, leaseMs) {
61
+ const interval =
62
+ value === undefined ? Math.max(250, Math.floor(leaseMs / 3)) : value;
63
+ if (!Number.isSafeInteger(interval) || interval < 1 || interval >= leaseMs) {
64
+ throw runtimeError(
65
+ "SCHEDULER_RUNTIME_INVALID_RENEW_INTERVAL",
66
+ "renewIntervalMs must be a positive integer smaller than leaseMs",
67
+ );
68
+ }
69
+ return interval;
70
+ }
71
+
72
+ function normalizeRunLimit(value = DEFAULT_RUN_LIMIT) {
73
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_RUN_LIMIT) {
74
+ throw runtimeError(
75
+ "SCHEDULER_RUNTIME_INVALID_LIMIT",
76
+ `limit must be an integer between 1 and ${MAX_RUN_LIMIT}`,
77
+ );
78
+ }
79
+ return value;
80
+ }
81
+
82
+ function normalizeAdapter(adapter) {
83
+ if (!adapter || typeof adapter !== "object" || Array.isArray(adapter)) {
84
+ throw runtimeError(
85
+ "SCHEDULER_RUNTIME_INVALID_ADAPTER",
86
+ "Scheduler adapter must be an object",
87
+ );
88
+ }
89
+ const kind = normalizeIdentifier(adapter.kind, "adapter.kind", {
90
+ maxLength: 128,
91
+ });
92
+ if (typeof adapter.execute !== "function") {
93
+ throw runtimeError(
94
+ "SCHEDULER_RUNTIME_INVALID_ADAPTER",
95
+ `Scheduler adapter must implement execute(): ${kind}`,
96
+ );
97
+ }
98
+ return { kind, adapter };
99
+ }
100
+
101
+ function normalizeAdapters(adapters = []) {
102
+ const map = new Map();
103
+ const values = adapters instanceof Map ? adapters.values() : adapters;
104
+ if (!values || typeof values[Symbol.iterator] !== "function") {
105
+ throw runtimeError(
106
+ "SCHEDULER_RUNTIME_INVALID_ADAPTER",
107
+ "adapters must be iterable",
108
+ );
109
+ }
110
+ for (const value of values) {
111
+ const { kind, adapter } = normalizeAdapter(value);
112
+ if (map.has(kind)) {
113
+ throw runtimeError(
114
+ "SCHEDULER_RUNTIME_DUPLICATE_ADAPTER",
115
+ `Scheduler adapter is registered more than once: ${kind}`,
116
+ );
117
+ }
118
+ map.set(kind, adapter);
119
+ }
120
+ return map;
121
+ }
122
+
123
+ function safeError(error, fallbackCode = "execution_failed") {
124
+ const code =
125
+ typeof error?.code === "string" && error.code.trim()
126
+ ? error.code.trim().slice(0, 128)
127
+ : fallbackCode;
128
+ const message =
129
+ typeof error?.message === "string" && error.message
130
+ ? error.message.slice(0, 2_000)
131
+ : String(error ?? "Execution failed").slice(0, 2_000);
132
+ let details;
133
+ try {
134
+ if (error?.details !== undefined) {
135
+ details = normalizeJson(error.details, "runtime.error.details");
136
+ }
137
+ } catch {
138
+ details = { omitted: "invalid_error_details" };
139
+ }
140
+ const base = {
141
+ code,
142
+ message,
143
+ ...(typeof error?.name === "string"
144
+ ? { name: error.name.slice(0, 128) }
145
+ : {}),
146
+ };
147
+ try {
148
+ return normalizeJson(
149
+ { ...base, ...(details === undefined ? {} : { details }) },
150
+ "runtime.error",
151
+ );
152
+ } catch {
153
+ return normalizeJson(
154
+ { ...base, details: { omitted: "error_details_too_large" } },
155
+ "runtime.error",
156
+ );
157
+ }
158
+ }
159
+
160
+ function executionPolicy(error, adapter, context) {
161
+ let policy = null;
162
+ if (typeof adapter?.classifyError === "function") {
163
+ policy = adapter.classifyError(error, context);
164
+ }
165
+ if (policy !== null && policy !== undefined) {
166
+ if (typeof policy !== "object" || Array.isArray(policy)) {
167
+ throw runtimeError(
168
+ "SCHEDULER_RUNTIME_INVALID_ERROR_POLICY",
169
+ "adapter.classifyError() must return an object",
170
+ );
171
+ }
172
+ return {
173
+ retryable: policy.retryable !== false,
174
+ ...(policy.retryAt === undefined
175
+ ? {}
176
+ : { retryAt: normalizeEpochMs(policy.retryAt, "retryAt") }),
177
+ };
178
+ }
179
+ return {
180
+ retryable: error?.retryable !== false,
181
+ ...(error?.retryAt === undefined
182
+ ? {}
183
+ : { retryAt: normalizeEpochMs(error.retryAt, "retryAt") }),
184
+ };
185
+ }
186
+
187
+ function createLinkedAbortController(signal) {
188
+ const controller = new AbortController();
189
+ const abort = () => controller.abort(signal?.reason);
190
+ if (signal?.aborted) abort();
191
+ else signal?.addEventListener?.("abort", abort, { once: true });
192
+ return {
193
+ controller,
194
+ dispose() {
195
+ signal?.removeEventListener?.("abort", abort);
196
+ },
197
+ };
198
+ }
199
+
200
+ function settledResult(occurrence, extra = {}) {
201
+ return {
202
+ status: occurrence.status,
203
+ occurrence,
204
+ result: occurrence.result,
205
+ error: occurrence.lastError,
206
+ ...extra,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Host-owned execution service for scheduler occurrences.
212
+ *
213
+ * The runtime is deliberately stricter than the SQLite store: it resolves the
214
+ * exact job revision, requires an explicit authorization decision, selects a
215
+ * registered adapter, renews the durable lease while the adapter is running,
216
+ * and settles only under the same owner/fence token. Adapters receive the
217
+ * occurrence snapshots, never mutable host-side schedule definitions by
218
+ * implication.
219
+ */
220
+ export class SchedulerRuntime {
221
+ constructor({
222
+ store,
223
+ adapters = [],
224
+ authorize,
225
+ ownerId = `scheduler-runtime:${process.pid}:${randomUUID()}`,
226
+ leaseMs = DEFAULT_RUNTIME_LEASE_MS,
227
+ renewIntervalMs,
228
+ setIntervalFn = setInterval,
229
+ clearIntervalFn = clearInterval,
230
+ } = {}) {
231
+ this.store = assertStore(store);
232
+ this.adapters = normalizeAdapters(adapters);
233
+ if (typeof authorize !== "function") {
234
+ throw runtimeError(
235
+ "SCHEDULER_RUNTIME_AUTHORIZER_REQUIRED",
236
+ "Scheduler runtime requires an explicit authorize() callback",
237
+ );
238
+ }
239
+ this.authorize = authorize;
240
+ this.ownerId = normalizeIdentifier(ownerId, "ownerId");
241
+ this.leaseMs = normalizeLeaseMs(leaseMs);
242
+ this.renewIntervalMs = normalizeRenewInterval(
243
+ renewIntervalMs,
244
+ this.leaseMs,
245
+ );
246
+ if (
247
+ typeof setIntervalFn !== "function" ||
248
+ typeof clearIntervalFn !== "function"
249
+ ) {
250
+ throw runtimeError(
251
+ "SCHEDULER_RUNTIME_INVALID_TIMER",
252
+ "Scheduler runtime requires timer functions",
253
+ );
254
+ }
255
+ this.setIntervalFn = setIntervalFn;
256
+ this.clearIntervalFn = clearIntervalFn;
257
+ }
258
+
259
+ registerAdapter(adapter) {
260
+ const normalized = normalizeAdapter(adapter);
261
+ if (this.adapters.has(normalized.kind)) {
262
+ throw runtimeError(
263
+ "SCHEDULER_RUNTIME_DUPLICATE_ADAPTER",
264
+ `Scheduler adapter is registered more than once: ${normalized.kind}`,
265
+ );
266
+ }
267
+ this.adapters.set(normalized.kind, normalized.adapter);
268
+ return this;
269
+ }
270
+
271
+ async _settleRejected(occurrence, error, retryable = false) {
272
+ const settled = this.store.settle({
273
+ occurrenceId: occurrence.id,
274
+ ownerId: this.ownerId,
275
+ fence: occurrence.fence,
276
+ outcome: "failed",
277
+ error: safeError(error, "scheduler_rejected"),
278
+ retryable,
279
+ });
280
+ return settledResult(settled);
281
+ }
282
+
283
+ async _executeClaim(occurrence, { signal } = {}) {
284
+ const job = this.store.getJob(occurrence.jobId);
285
+ if (!job) {
286
+ return this._settleRejected(
287
+ occurrence,
288
+ runtimeError(
289
+ "SCHEDULER_RUNTIME_JOB_MISSING",
290
+ `Scheduler job disappeared before execution: ${occurrence.jobId}`,
291
+ ),
292
+ );
293
+ }
294
+ if (job.revision !== occurrence.jobRevision) {
295
+ return this._settleRejected(
296
+ occurrence,
297
+ runtimeError(
298
+ "SCHEDULER_RUNTIME_STALE_REVISION",
299
+ `Occurrence references stale job revision: ${occurrence.id}`,
300
+ {
301
+ occurrenceRevision: occurrence.jobRevision,
302
+ currentRevision: job.revision,
303
+ },
304
+ ),
305
+ );
306
+ }
307
+ const adapter = this.adapters.get(job.kind);
308
+ if (!adapter) {
309
+ return this._settleRejected(
310
+ occurrence,
311
+ runtimeError(
312
+ "SCHEDULER_RUNTIME_ADAPTER_MISSING",
313
+ `No scheduler adapter is registered for job kind: ${job.kind}`,
314
+ ),
315
+ );
316
+ }
317
+
318
+ let decision;
319
+ let allowed = false;
320
+ let deniedReason = "not_allowed";
321
+ try {
322
+ decision = await this.authorize({ job, occurrence, signal });
323
+ allowed =
324
+ decision !== null &&
325
+ typeof decision === "object" &&
326
+ decision.allowed === true;
327
+ if (typeof decision?.reason === "string") {
328
+ deniedReason = decision.reason.slice(0, 256);
329
+ }
330
+ } catch (error) {
331
+ const authError = runtimeError(
332
+ "SCHEDULER_RUNTIME_AUTHORIZATION_UNAVAILABLE",
333
+ "Scheduler authorization could not be completed",
334
+ undefined,
335
+ error,
336
+ );
337
+ authError.retryable = true;
338
+ return this._settleRejected(occurrence, authError, true);
339
+ }
340
+ if (!allowed) {
341
+ return this._settleRejected(
342
+ occurrence,
343
+ runtimeError(
344
+ "SCHEDULER_RUNTIME_AUTHORIZATION_DENIED",
345
+ "Scheduler occurrence was denied by the authority resolver",
346
+ { reason: deniedReason },
347
+ ),
348
+ );
349
+ }
350
+
351
+ // Authorization may involve remote policy evaluation. Revalidate ownership
352
+ // immediately before any adapter side effect so an expired/reclaimed claim
353
+ // cannot execute under a stale fence.
354
+ occurrence = this.store.renew({
355
+ occurrenceId: occurrence.id,
356
+ ownerId: this.ownerId,
357
+ fence: occurrence.fence,
358
+ leaseMs: this.leaseMs,
359
+ });
360
+
361
+ const linked = createLinkedAbortController(signal);
362
+ if (linked.controller.signal.aborted) {
363
+ linked.dispose();
364
+ const aborted = runtimeError(
365
+ "SCHEDULER_RUNTIME_ABORTED",
366
+ "Scheduler occurrence was aborted before adapter execution",
367
+ );
368
+ aborted.retryable = true;
369
+ return this._settleRejected(occurrence, aborted, true);
370
+ }
371
+ let leaseError = null;
372
+ let renewing = false;
373
+ const renew = () => {
374
+ if (renewing || leaseError || linked.controller.signal.aborted) return;
375
+ renewing = true;
376
+ try {
377
+ this.store.renew({
378
+ occurrenceId: occurrence.id,
379
+ ownerId: this.ownerId,
380
+ fence: occurrence.fence,
381
+ leaseMs: this.leaseMs,
382
+ });
383
+ } catch (error) {
384
+ leaseError = error;
385
+ linked.controller.abort(error);
386
+ } finally {
387
+ renewing = false;
388
+ }
389
+ };
390
+ let timer;
391
+ try {
392
+ timer = this.setIntervalFn(renew, this.renewIntervalMs);
393
+ } catch (error) {
394
+ linked.dispose();
395
+ return this._settleRejected(
396
+ occurrence,
397
+ runtimeError(
398
+ "SCHEDULER_RUNTIME_TIMER_FAILED",
399
+ "Scheduler lease-renewal timer could not be started",
400
+ undefined,
401
+ error,
402
+ ),
403
+ true,
404
+ );
405
+ }
406
+ timer?.unref?.();
407
+ const context = {
408
+ job,
409
+ occurrence,
410
+ authority: occurrence.authority,
411
+ decision,
412
+ signal: linked.controller.signal,
413
+ renewLease: renew,
414
+ };
415
+ try {
416
+ const result = await adapter.execute(context);
417
+ if (leaseError) throw leaseError;
418
+ if (linked.controller.signal.aborted) {
419
+ const aborted = runtimeError(
420
+ "SCHEDULER_RUNTIME_ABORTED",
421
+ "Scheduler occurrence was aborted before settlement",
422
+ );
423
+ aborted.retryable = true;
424
+ throw aborted;
425
+ }
426
+ const settled = this.store.settle({
427
+ occurrenceId: occurrence.id,
428
+ ownerId: this.ownerId,
429
+ fence: occurrence.fence,
430
+ outcome: "succeeded",
431
+ result: result ?? null,
432
+ });
433
+ return settledResult(settled);
434
+ } catch (error) {
435
+ if (leaseError) throw leaseError;
436
+ let policy;
437
+ let settlementError = error;
438
+ try {
439
+ policy = executionPolicy(error, adapter, context);
440
+ } catch (classificationError) {
441
+ settlementError = runtimeError(
442
+ "SCHEDULER_RUNTIME_ERROR_POLICY_FAILED",
443
+ "Scheduler adapter error policy failed",
444
+ undefined,
445
+ classificationError,
446
+ );
447
+ policy = { retryable: false };
448
+ }
449
+ const settled = this.store.settle({
450
+ occurrenceId: occurrence.id,
451
+ ownerId: this.ownerId,
452
+ fence: occurrence.fence,
453
+ outcome: "failed",
454
+ error: safeError(settlementError),
455
+ retryable: policy.retryable,
456
+ ...(policy.retryAt === undefined ? {} : { retryAt: policy.retryAt }),
457
+ });
458
+ return settledResult(settled);
459
+ } finally {
460
+ this.clearIntervalFn(timer);
461
+ linked.dispose();
462
+ }
463
+ }
464
+
465
+ async runOccurrence(occurrenceId, options = {}) {
466
+ const id = normalizeIdentifier(occurrenceId, "occurrenceId");
467
+ const current = this.store.getOccurrence(id);
468
+ if (!current) {
469
+ throw runtimeError(
470
+ "SCHEDULER_NOT_FOUND",
471
+ `Scheduler occurrence does not exist: ${id}`,
472
+ );
473
+ }
474
+ if (TERMINAL_OCCURRENCE_STATUSES.has(current.status)) {
475
+ return settledResult(current, { alreadySettled: true });
476
+ }
477
+ if (options.signal?.aborted) {
478
+ return { status: "aborted", occurrence: current };
479
+ }
480
+ const claimed = this.store.claimOccurrence({
481
+ occurrenceId: id,
482
+ ownerId: this.ownerId,
483
+ leaseMs: this.leaseMs,
484
+ });
485
+ if (!claimed) {
486
+ return {
487
+ status: "busy",
488
+ occurrence: this.store.getOccurrence(id),
489
+ };
490
+ }
491
+ return this._executeClaim(claimed, options);
492
+ }
493
+
494
+ async runNext(options = {}) {
495
+ if (options.signal?.aborted) return { status: "aborted" };
496
+ const claimed = this.store.claimNext({
497
+ ownerId: this.ownerId,
498
+ leaseMs: this.leaseMs,
499
+ ...(options.jobKind === undefined ? {} : { jobKind: options.jobKind }),
500
+ ...(options.workspaceId === undefined
501
+ ? {}
502
+ : { workspaceId: options.workspaceId }),
503
+ });
504
+ if (!claimed) return { status: "idle" };
505
+ return this._executeClaim(claimed, options);
506
+ }
507
+
508
+ async runUntilIdle({ limit, signal, jobKind, workspaceId } = {}) {
509
+ const bounded = normalizeRunLimit(limit);
510
+ const results = [];
511
+ while (results.length < bounded && !signal?.aborted) {
512
+ const result = await this.runNext({ signal, jobKind, workspaceId });
513
+ if (result.status === "idle" || result.status === "aborted") {
514
+ return { status: result.status, results };
515
+ }
516
+ results.push(result);
517
+ }
518
+ return {
519
+ status: signal?.aborted ? "aborted" : "limit_reached",
520
+ results,
521
+ };
522
+ }
523
+ }
524
+
525
+ export function createSchedulerRuntime(options) {
526
+ return new SchedulerRuntime(options);
527
+ }
@@ -1003,9 +1003,17 @@ export class SchedulerStore {
1003
1003
  }
1004
1004
  }
1005
1005
 
1006
- claimNext({ ownerId, leaseMs } = {}) {
1006
+ claimNext({ ownerId, leaseMs, jobKind, workspaceId } = {}) {
1007
1007
  const owner = normalizeIdentifier(ownerId, "ownerId");
1008
1008
  const lease = normalizeLeaseMs(leaseMs);
1009
+ const kind =
1010
+ jobKind === undefined
1011
+ ? null
1012
+ : normalizeIdentifier(jobKind, "jobKind", { maxLength: 128 });
1013
+ const workspace =
1014
+ workspaceId === undefined
1015
+ ? null
1016
+ : normalizeIdentifier(workspaceId, "workspaceId", { maxLength: 256 });
1009
1017
  const now = this._now();
1010
1018
  const leaseExpiresAt = now + lease;
1011
1019
  if (!Number.isSafeInteger(leaseExpiresAt)) {
@@ -1020,6 +1028,11 @@ export class SchedulerStore {
1020
1028
  FROM occurrences o
1021
1029
  JOIN jobs j ON j.job_id = o.job_id
1022
1030
  WHERE j.enabled = 1
1031
+ AND (@jobKind IS NULL OR j.kind = @jobKind)
1032
+ AND (
1033
+ @workspaceId IS NULL
1034
+ OR json_extract(j.authority_json, '$.workspaceId') = @workspaceId
1035
+ )
1023
1036
  AND o.attempt < o.max_attempts
1024
1037
  AND (
1025
1038
  (o.status IN ('queued', 'retry_wait') AND o.available_at <= @now)
@@ -1033,7 +1046,7 @@ export class SchedulerStore {
1033
1046
  LIMIT 1
1034
1047
  `,
1035
1048
  )
1036
- .get({ now });
1049
+ .get({ now, jobKind: kind, workspaceId: workspace });
1037
1050
  if (!candidate) return null;
1038
1051
  const previousStatus = candidate.status;
1039
1052
  const previousOwner = candidate.lease_owner;
@@ -1094,6 +1107,103 @@ export class SchedulerStore {
1094
1107
  });
1095
1108
  }
1096
1109
 
1110
+ /**
1111
+ * Claim one known occurrence without consuming unrelated scheduler work.
1112
+ *
1113
+ * Command adapters use this after durably enqueueing a user-selected job.
1114
+ * Returning `null` means the occurrence exists but is not currently
1115
+ * claimable (another live owner holds it, it is not due yet, or it is
1116
+ * terminal). A missing occurrence remains a hard error so callers cannot
1117
+ * confuse a lost durable enqueue with ordinary contention.
1118
+ */
1119
+ claimOccurrence({ occurrenceId, ownerId, leaseMs } = {}) {
1120
+ const id = normalizeIdentifier(occurrenceId, "occurrenceId");
1121
+ const owner = normalizeIdentifier(ownerId, "ownerId");
1122
+ const lease = normalizeLeaseMs(leaseMs);
1123
+ const now = this._now();
1124
+ const leaseExpiresAt = now + lease;
1125
+ if (!Number.isSafeInteger(leaseExpiresAt)) {
1126
+ throw invalidArgument("lease expiry exceeds the safe integer range");
1127
+ }
1128
+ return this._write(() => {
1129
+ this._deadLetterExpiredLeases(now);
1130
+ const candidate = this.statements.getOccurrence.get(id);
1131
+ if (!candidate) {
1132
+ throw new SchedulerKernelError(
1133
+ "SCHEDULER_NOT_FOUND",
1134
+ `Scheduler occurrence does not exist: ${id}`,
1135
+ );
1136
+ }
1137
+ const job = this.statements.getJob.get(candidate.job_id);
1138
+ const eligible =
1139
+ job?.enabled === 1 &&
1140
+ candidate.attempt < candidate.max_attempts &&
1141
+ (([OCCURRENCE_STATUS.QUEUED, OCCURRENCE_STATUS.RETRY_WAIT].includes(
1142
+ candidate.status,
1143
+ ) &&
1144
+ candidate.available_at <= now) ||
1145
+ (candidate.status === OCCURRENCE_STATUS.RUNNING &&
1146
+ candidate.lease_expires_at <= now));
1147
+ if (!eligible) return null;
1148
+
1149
+ const previousStatus = candidate.status;
1150
+ const previousOwner = candidate.lease_owner;
1151
+ const result = this.db
1152
+ .prepare(
1153
+ `
1154
+ UPDATE occurrences
1155
+ SET status = 'running',
1156
+ attempt = attempt + 1,
1157
+ fence = fence + 1,
1158
+ lease_owner = @ownerId,
1159
+ lease_expires_at = @leaseExpiresAt,
1160
+ updated_at = @now,
1161
+ settled_at = NULL
1162
+ WHERE occurrence_id = @occurrenceId
1163
+ AND fence = @expectedFence
1164
+ AND attempt < max_attempts
1165
+ AND (
1166
+ (status IN ('queued', 'retry_wait') AND available_at <= @now)
1167
+ OR
1168
+ (status = 'running' AND lease_expires_at <= @now)
1169
+ )
1170
+ `,
1171
+ )
1172
+ .run({
1173
+ occurrenceId: id,
1174
+ expectedFence: candidate.fence,
1175
+ ownerId: owner,
1176
+ leaseExpiresAt,
1177
+ now,
1178
+ });
1179
+ if (result.changes !== 1) {
1180
+ throw new SchedulerKernelError(
1181
+ "SCHEDULER_CLAIM_CONFLICT",
1182
+ "Occurrence eligibility changed during targeted claim",
1183
+ );
1184
+ }
1185
+ const claimed = this.statements.getOccurrence.get(id);
1186
+ this._appendEvent({
1187
+ jobId: candidate.job_id,
1188
+ occurrenceId: id,
1189
+ type:
1190
+ previousStatus === OCCURRENCE_STATUS.RUNNING
1191
+ ? "occurrence_reclaimed"
1192
+ : "occurrence_claimed",
1193
+ occurredAt: now,
1194
+ ownerId: owner,
1195
+ fence: claimed.fence,
1196
+ data: {
1197
+ attempt: claimed.attempt,
1198
+ previousOwner,
1199
+ previousStatus,
1200
+ targeted: true,
1201
+ },
1202
+ });
1203
+ return mapOccurrence(claimed);
1204
+ });
1205
+ }
1206
+
1097
1207
  renew({ occurrenceId, ownerId, fence, leaseMs } = {}) {
1098
1208
  const id = normalizeIdentifier(occurrenceId, "occurrenceId");
1099
1209
  const owner = normalizeIdentifier(ownerId, "ownerId");
@@ -14,6 +14,8 @@ export const SESSION_FORK_AUTHORITY_FIELD = "_cc_fork_authority";
14
14
  export const SESSION_MESSAGE_PROVENANCE_SCHEMA =
15
15
  "chainlesschain.session-message-provenance/v1";
16
16
 
17
+ const MICRO_COMPACTED_RUNTIME_FIELD = "_microCompacted";
18
+
17
19
  export const DURABLE_SYSTEM_MESSAGE_KINDS = Object.freeze({
18
20
  COMPACT_SUMMARY: "compact-summary",
19
21
  COMPACT_TOOL_COLLAPSE: "compact-tool-collapse",
@@ -190,7 +192,8 @@ function cloneWithoutWireTag(snapshot) {
190
192
  for (const key of snapshot.keys) {
191
193
  if (
192
194
  key === SESSION_MESSAGE_PROVENANCE_FIELD ||
193
- key === SESSION_FORK_AUTHORITY_FIELD
195
+ key === SESSION_FORK_AUTHORITY_FIELD ||
196
+ key === MICRO_COMPACTED_RUNTIME_FIELD
194
197
  ) {
195
198
  continue;
196
199
  }