@wjarka/cezarion 0.14.8 → 0.14.9-dev.1004

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 (274) hide show
  1. package/README.md +129 -2
  2. package/dist/application-update/bin-recovery.d.ts +5 -0
  3. package/dist/application-update/bin-recovery.js +96 -0
  4. package/dist/application-update/bin-recovery.js.map +1 -0
  5. package/dist/application-update/discovery.d.ts +22 -0
  6. package/dist/application-update/discovery.js +91 -0
  7. package/dist/application-update/discovery.js.map +1 -0
  8. package/dist/application-update/helper.d.ts +57 -0
  9. package/dist/application-update/helper.js +374 -0
  10. package/dist/application-update/helper.js.map +1 -0
  11. package/dist/application-update/launcher.d.ts +8 -0
  12. package/dist/application-update/launcher.js +53 -0
  13. package/dist/application-update/launcher.js.map +1 -0
  14. package/dist/application-update/lock.d.ts +4 -0
  15. package/dist/application-update/lock.js +70 -0
  16. package/dist/application-update/lock.js.map +1 -0
  17. package/dist/application-update/npm-process.d.ts +17 -0
  18. package/dist/application-update/npm-process.js +133 -0
  19. package/dist/application-update/npm-process.js.map +1 -0
  20. package/dist/application-update/service.d.ts +69 -0
  21. package/dist/application-update/service.js +456 -0
  22. package/dist/application-update/service.js.map +1 -0
  23. package/dist/artifacts/cli.d.ts +1 -0
  24. package/dist/artifacts/cli.js +32 -0
  25. package/dist/artifacts/cli.js.map +1 -0
  26. package/dist/artifacts/lifecycle.d.ts +4 -0
  27. package/dist/artifacts/lifecycle.js +42 -0
  28. package/dist/artifacts/lifecycle.js.map +1 -0
  29. package/dist/artifacts/resolve.d.ts +18 -0
  30. package/dist/artifacts/resolve.js +102 -0
  31. package/dist/artifacts/resolve.js.map +1 -0
  32. package/dist/artifacts/store.d.ts +8 -0
  33. package/dist/artifacts/store.js +323 -0
  34. package/dist/artifacts/store.js.map +1 -0
  35. package/dist/automations/scheduler.d.ts +6 -0
  36. package/dist/automations/scheduler.js +45 -17
  37. package/dist/automations/scheduler.js.map +1 -1
  38. package/dist/automations/store.d.ts +12 -4
  39. package/dist/automations/store.js +199 -22
  40. package/dist/automations/store.js.map +1 -1
  41. package/dist/automations/types.d.ts +2 -0
  42. package/dist/automations/types.js +1 -0
  43. package/dist/automations/types.js.map +1 -1
  44. package/dist/ci-wait/mcp.d.ts +3 -0
  45. package/dist/ci-wait/mcp.js +14 -3
  46. package/dist/ci-wait/mcp.js.map +1 -1
  47. package/dist/contract/application-update.d.ts +32 -0
  48. package/dist/contract/artifacts.d.ts +83 -0
  49. package/dist/contract/ask-schema.d.ts +32 -0
  50. package/dist/contract/ask.d.ts +2 -32
  51. package/dist/contract/automations.d.ts +6 -0
  52. package/dist/contract/conversations.d.ts +165 -0
  53. package/dist/contract/delegation.d.ts +68 -0
  54. package/dist/contract/discovery.d.ts +134 -0
  55. package/dist/contract/events.d.ts +49 -0
  56. package/dist/contract/health.d.ts +12 -0
  57. package/dist/contract/index.d.ts +3 -0
  58. package/dist/contract/index.js +1654 -1454
  59. package/dist/contract/projects.d.ts +40 -0
  60. package/dist/contract/runs.d.ts +186 -0
  61. package/dist/contract/skills.d.ts +33 -0
  62. package/dist/core/agent-runner.d.ts +33 -4
  63. package/dist/core/agent-runner.js +5 -0
  64. package/dist/core/agent-runner.js.map +1 -1
  65. package/dist/core/claude-cli-runner.d.ts +6 -12
  66. package/dist/core/claude-cli-runner.js +90 -20
  67. package/dist/core/claude-cli-runner.js.map +1 -1
  68. package/dist/core/codex-app-server-runner.d.ts +2 -1
  69. package/dist/core/codex-app-server-runner.js +327 -92
  70. package/dist/core/codex-app-server-runner.js.map +1 -1
  71. package/dist/core/codex-app-server-transport.d.ts +13 -1
  72. package/dist/core/codex-app-server-transport.js +58 -17
  73. package/dist/core/codex-app-server-transport.js.map +1 -1
  74. package/dist/core/cursor-acp-runner.d.ts +6 -1
  75. package/dist/core/cursor-acp-runner.js +162 -26
  76. package/dist/core/cursor-acp-runner.js.map +1 -1
  77. package/dist/core/cursor-provider-error.d.ts +3 -0
  78. package/dist/core/cursor-provider-error.js +16 -1
  79. package/dist/core/cursor-provider-error.js.map +1 -1
  80. package/dist/core/input-submissions.d.ts +14 -0
  81. package/dist/core/input-submissions.js +26 -0
  82. package/dist/core/input-submissions.js.map +1 -0
  83. package/dist/core/opencode-server-runner.d.ts +4 -1
  84. package/dist/core/opencode-server-runner.js +156 -26
  85. package/dist/core/opencode-server-runner.js.map +1 -1
  86. package/dist/core/pi-runner.d.ts +2 -1
  87. package/dist/core/pi-runner.js +88 -10
  88. package/dist/core/pi-runner.js.map +1 -1
  89. package/dist/core/provider-auth.d.ts +99 -1
  90. package/dist/core/provider-auth.js +203 -2
  91. package/dist/core/provider-auth.js.map +1 -1
  92. package/dist/delegation/cli.d.ts +1 -0
  93. package/dist/delegation/cli.js +103 -7
  94. package/dist/delegation/cli.js.map +1 -1
  95. package/dist/delegation/conversations.js +13 -5
  96. package/dist/delegation/conversations.js.map +1 -1
  97. package/dist/delegation/input.d.ts +2 -1
  98. package/dist/delegation/input.js +6 -1
  99. package/dist/delegation/input.js.map +1 -1
  100. package/dist/delegation/policy.d.ts +3 -2
  101. package/dist/delegation/policy.js +3 -1
  102. package/dist/delegation/policy.js.map +1 -1
  103. package/dist/delegation/process-liveness.d.ts +66 -0
  104. package/dist/delegation/process-liveness.js +215 -0
  105. package/dist/delegation/process-liveness.js.map +1 -0
  106. package/dist/delegation/provision.d.ts +1 -1
  107. package/dist/delegation/provision.js +10 -5
  108. package/dist/delegation/provision.js.map +1 -1
  109. package/dist/delegation/questions.d.ts +19 -0
  110. package/dist/delegation/questions.js +45 -0
  111. package/dist/delegation/questions.js.map +1 -0
  112. package/dist/delegation/results.d.ts +1 -1
  113. package/dist/delegation/results.js +33 -19
  114. package/dist/delegation/results.js.map +1 -1
  115. package/dist/delegation/routes.d.ts +185 -11
  116. package/dist/delegation/routes.js +6 -2
  117. package/dist/delegation/routes.js.map +1 -1
  118. package/dist/delegation/service.d.ts +16 -5
  119. package/dist/delegation/service.js +161 -13
  120. package/dist/delegation/service.js.map +1 -1
  121. package/dist/delegation/transport.d.ts +228 -65
  122. package/dist/discovery/catalog.d.ts +14 -0
  123. package/dist/discovery/catalog.js +31 -0
  124. package/dist/discovery/catalog.js.map +1 -0
  125. package/dist/discovery/cli.d.ts +8 -0
  126. package/dist/discovery/cli.js +104 -0
  127. package/dist/discovery/cli.js.map +1 -0
  128. package/dist/git-worktree.d.ts +3 -1
  129. package/dist/git-worktree.js +26 -4
  130. package/dist/git-worktree.js.map +1 -1
  131. package/dist/index.js +92 -26
  132. package/dist/index.js.map +1 -1
  133. package/dist/open-url.d.ts +2 -0
  134. package/dist/open-url.js +20 -0
  135. package/dist/open-url.js.map +1 -0
  136. package/dist/release/snapshot.d.ts +3 -3
  137. package/dist/release/snapshot.js +6 -6
  138. package/dist/release/snapshot.js.map +1 -1
  139. package/dist/runs/agent-tmpdir.d.ts +2 -0
  140. package/dist/runs/agent-tmpdir.js +5 -2
  141. package/dist/runs/agent-tmpdir.js.map +1 -1
  142. package/dist/runs/client-request.d.ts +11 -0
  143. package/dist/runs/client-request.js +26 -0
  144. package/dist/runs/client-request.js.map +1 -0
  145. package/dist/runs/delegation-state.d.ts +17 -0
  146. package/dist/runs/event-history.js +6 -0
  147. package/dist/runs/event-history.js.map +1 -1
  148. package/dist/runs/retention.d.ts +7 -2
  149. package/dist/runs/retention.js +39 -7
  150. package/dist/runs/retention.js.map +1 -1
  151. package/dist/runs/store.d.ts +100 -6
  152. package/dist/runs/store.js +245 -2
  153. package/dist/runs/store.js.map +1 -1
  154. package/dist/runs/webhook.d.ts +125 -0
  155. package/dist/runs/webhook.js +256 -0
  156. package/dist/runs/webhook.js.map +1 -0
  157. package/dist/server/provider-auth-runtime.d.ts +30 -4
  158. package/dist/server/provider-auth-runtime.js +71 -6
  159. package/dist/server/provider-auth-runtime.js.map +1 -1
  160. package/dist/server/server.d.ts +8226 -4860
  161. package/dist/server/server.js +309 -15
  162. package/dist/server/server.js.map +1 -1
  163. package/dist/server-install/platforms/ubuntu-vps.d.ts +5 -1
  164. package/dist/server-install/platforms/ubuntu-vps.js +160 -18
  165. package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
  166. package/dist/server-install/steps.d.ts +4 -2
  167. package/dist/server-install/steps.js +5 -4
  168. package/dist/server-install/steps.js.map +1 -1
  169. package/dist/skills-remote.d.ts +30 -5
  170. package/dist/skills-remote.js +122 -32
  171. package/dist/skills-remote.js.map +1 -1
  172. package/dist/skills.js +15 -6
  173. package/dist/skills.js.map +1 -1
  174. package/dist/task-cli/cli.d.ts +38 -0
  175. package/dist/task-cli/cli.js +580 -0
  176. package/dist/task-cli/cli.js.map +1 -0
  177. package/dist/task-cli/discovery.d.ts +22 -0
  178. package/dist/task-cli/discovery.js +96 -0
  179. package/dist/task-cli/discovery.js.map +1 -0
  180. package/dist/task-cli/http.d.ts +46 -0
  181. package/dist/task-cli/http.js +101 -0
  182. package/dist/task-cli/http.js.map +1 -0
  183. package/dist/task-cli/projections.d.ts +40 -0
  184. package/dist/task-cli/projections.js +45 -0
  185. package/dist/task-cli/projections.js.map +1 -0
  186. package/dist/task-cli/watch.d.ts +78 -0
  187. package/dist/task-cli/watch.js +336 -0
  188. package/dist/task-cli/watch.js.map +1 -0
  189. package/dist/workflows/run.d.ts +160 -2
  190. package/dist/workflows/run.js +1141 -108
  191. package/dist/workflows/run.js.map +1 -1
  192. package/dist/workspace/agent-profiles.d.ts +22 -0
  193. package/dist/workspace/agent-profiles.js +15 -0
  194. package/dist/workspace/agent-profiles.js.map +1 -1
  195. package/dist/workspace/config.d.ts +9 -0
  196. package/dist/workspace/config.js +13 -1
  197. package/dist/workspace/config.js.map +1 -1
  198. package/dist/workspace/projects-cli.js +1 -1
  199. package/dist/workspace/projects.d.ts +24 -1
  200. package/dist/workspace/projects.js +14 -1
  201. package/dist/workspace/projects.js.map +1 -1
  202. package/package.json +5 -5
  203. package/scripts/mock-claude.mjs +64 -2
  204. package/scripts/mock-codex-app-server.mjs +124 -1
  205. package/scripts/mock-cursor-acp.mjs +54 -3
  206. package/scripts/mock-cursor-hang-stdin-late.mjs +35 -0
  207. package/scripts/mock-cursor-hang-stdin.mjs +30 -0
  208. package/scripts/mock-opencode-serve.mjs +102 -2
  209. package/scripts/mock-pi-rpc.mjs +82 -3
  210. package/web/dist/assets/agent-accounts-CerclGiO.js +1 -0
  211. package/web/dist/assets/centered-state-DH8qtCz-.js +43 -0
  212. package/web/dist/assets/collapsible-De2G0xqQ.js +1 -0
  213. package/web/dist/assets/commit-list-CQubP1iS.js +1 -0
  214. package/web/dist/assets/compare-variants-C_NijJvg.js +1 -0
  215. package/web/dist/assets/{diff-tRE59UNb2.js → diff-8M5HlDxq2.js} +2 -2
  216. package/web/dist/assets/diff-controls-B6ok0TtQ.js +1 -0
  217. package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-D1AODN2a.js} +1 -1
  218. package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-Bs5Gh7UF.js} +2 -2
  219. package/web/dist/assets/dropdown-menu-7KGQMoFj.js +1 -0
  220. package/web/dist/assets/git-toolbar-DqbapMXr.js +1 -0
  221. package/web/dist/assets/github-lMHiuYSx.js +1 -0
  222. package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-jzHoEyLM.js} +1 -1
  223. package/web/dist/assets/index-D4OJTArJ.js +7 -0
  224. package/web/dist/assets/index-D5NkwqY7.css +1 -0
  225. package/web/dist/assets/markdown-DvbymqXz.js +2 -0
  226. package/web/dist/assets/pill-BL6ugwXo.js +1 -0
  227. package/web/dist/assets/project-router-ChC5Rb-Y.js +1 -0
  228. package/web/dist/assets/prompt-templates-j8qlK7c-.js +15 -0
  229. package/web/dist/assets/repo-git-BbPAgkqb.js +1 -0
  230. package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-BuATgZpx.js} +2 -2
  231. package/web/dist/assets/run-header-DuAWswYM.js +1 -0
  232. package/web/dist/assets/skills-DiC2XrU1.js +1 -0
  233. package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-BmGKj9Rt.js} +1 -1
  234. package/web/dist/assets/task-changes-CP9kPnsy.js +1 -0
  235. package/web/dist/assets/task-commits-D6NfTw5r.js +1 -0
  236. package/web/dist/assets/task-files-W-b_gtcb.js +2 -0
  237. package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
  238. package/web/dist/assets/task-thread-Q7Nj33DH.js +9 -0
  239. package/web/dist/assets/textarea-ByZ-txS7.js +1 -0
  240. package/web/dist/assets/thread-loading-DHT9p5o5.js +1 -0
  241. package/web/dist/assets/token-metrics-k4XoovDS.js +1 -0
  242. package/web/dist/assets/utils-BTFBhZ55.js +64 -0
  243. package/web/dist/assets/workflows-DqQszPHJ.js +11 -0
  244. package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-Ba6y6Wyl.js} +1 -1
  245. package/web/dist/index.html +14 -14
  246. package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
  247. package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
  248. package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
  249. package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
  250. package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
  251. package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
  252. package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
  253. package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
  254. package/web/dist/assets/github-D-MrkbJ7.js +0 -1
  255. package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
  256. package/web/dist/assets/index-BszfYvBq.js +0 -7
  257. package/web/dist/assets/index-CWh4pKP3.css +0 -1
  258. package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
  259. package/web/dist/assets/pill-BsbO6G7y.js +0 -1
  260. package/web/dist/assets/project-router-DbLkll25.js +0 -1
  261. package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
  262. package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
  263. package/web/dist/assets/run-header-IFyIGCey.js +0 -1
  264. package/web/dist/assets/skills-C-C4DVFY.js +0 -1
  265. package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
  266. package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
  267. package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
  268. package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
  269. package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
  270. package/web/dist/assets/textarea-FjaU370i.js +0 -1
  271. package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
  272. package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
  273. package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
  274. package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
@@ -0,0 +1,102 @@
1
+ import { constants } from 'node:fs';
2
+ import { open, realpath, lstat } from 'node:fs/promises';
3
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { FILE_CONTENT_CAP, imageMimeType, isOsOpenableImage } from '../server/git-changes.js';
6
+ import { artifactDirectory, listArtifacts, readArtifact } from './store.js';
7
+ const IMAGE_CAP = 10 * 1024 * 1024;
8
+ const contained = (root, path) => path === root || path.startsWith(root + sep);
9
+ function privatePath(root, target) {
10
+ const segments = relative(root, target).split(sep);
11
+ return segments.includes('.git') || segments.some((part, i) => part === '.ai' && segments[i + 1] === 'cezar');
12
+ }
13
+ export function rasterMime(path) { return isOsOpenableImage(path) ? imageMimeType(path) : null; }
14
+ export function artifactPreview(artifact, bytes) {
15
+ const binary = bytes.subarray(0, 8192).includes(0);
16
+ const tooLarge = bytes.length > FILE_CONTENT_CAP;
17
+ return { type: 'file', source: 'artifact', artifact, path: artifact.name, size: bytes.length, binary, tooLarge,
18
+ ...(!binary && !tooLarge ? { content: bytes.toString('utf8') } : {}) };
19
+ }
20
+ /** No outside source is opened or even stat'ed: it can only match a published snapshot. */
21
+ export async function loadFileLink(ctx) {
22
+ const unavailable = (reason) => ({ preview: { type: 'unavailable', path: ctx.path, reason } });
23
+ let path;
24
+ try {
25
+ if (!ctx.path || ctx.path.length > 8192 || /[\u0000-\u001f]/.test(ctx.path))
26
+ return unavailable('Invalid file path.');
27
+ if (/^file:/i.test(ctx.path))
28
+ path = fileURLToPath(new URL(ctx.path));
29
+ else {
30
+ if (/^[a-z][a-z\d+.-]*:/i.test(ctx.path) && !/^[a-z]:[\\/]/i.test(ctx.path))
31
+ return unavailable('Not a local file path.');
32
+ // Query decoding has already happened. This is a literal filesystem path;
33
+ // decoding it again would turn a filename containing %20 into a space.
34
+ path = ctx.path;
35
+ }
36
+ if (/[\u0000-\u001f]/.test(path) || path.startsWith('//') || path.startsWith('\\\\'))
37
+ return unavailable('Network file paths are not supported.');
38
+ if (process.platform !== 'win32' && /^[a-z]:[\\/]/i.test(path))
39
+ return unavailable('This path belongs to a different operating system.');
40
+ }
41
+ catch {
42
+ return unavailable('Invalid local file URL or encoding.');
43
+ }
44
+ if (!isAbsolute(path) && !ctx.workingDirectory)
45
+ return unavailable('This task has no working directory.');
46
+ const target = resolve(ctx.workingDirectory ?? ctx.projectRoot, path);
47
+ const working = ctx.workingDirectory ? resolve(ctx.workingDirectory) : undefined;
48
+ const project = resolve(ctx.projectRoot);
49
+ const root = working && contained(working, target) ? working : contained(project, target) ? project : undefined;
50
+ if (!root) {
51
+ const directory = artifactDirectory(ctx.dataDir, ctx.runId);
52
+ const matches = (await listArtifacts(directory, ctx.runId)).filter(item => item.sourcePath === target)
53
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
54
+ const entry = matches[0] ? await readArtifact(directory, ctx.runId, matches[0].id) : null;
55
+ if (entry)
56
+ return { preview: artifactPreview(entry.metadata, entry.bytes), ...(rasterMime(entry.metadata.name) ? { imageBytes: entry.bytes } : {}) };
57
+ return { preview: { type: 'unpublished', path: ctx.path, reason: 'This file is outside the project. Ask the agent to publish it with cez artifact publish <path> and share the returned link.' } };
58
+ }
59
+ if (privatePath(root, target)) {
60
+ return unavailable('Git internals and private Cezar state are not available through file links.');
61
+ }
62
+ try {
63
+ const info = await lstat(target);
64
+ if (info.isDirectory())
65
+ return unavailable('Choose a file inside this directory.');
66
+ if (!info.isFile() || info.isSymbolicLink())
67
+ return unavailable('Only regular files can be previewed; symlinks are not served.');
68
+ const [realRoot, actual] = await Promise.all([realpath(root), realpath(target)]);
69
+ if (!contained(realRoot, actual) || privatePath(realRoot, actual))
70
+ return unavailable('The file resolves outside the permitted directory or into private state.');
71
+ const handle = await open(actual, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
72
+ try {
73
+ const stat = await handle.stat();
74
+ if (!stat.isFile())
75
+ return unavailable('Not a regular file.');
76
+ const raster = rasterMime(target);
77
+ const cap = raster ? IMAGE_CAP : FILE_CONTENT_CAP;
78
+ const bytes = Buffer.alloc(Math.min(stat.size, cap) + 1);
79
+ let length = 0;
80
+ while (length < bytes.length) {
81
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
82
+ if (!bytesRead)
83
+ break;
84
+ length += bytesRead;
85
+ }
86
+ const sample = bytes.subarray(0, length);
87
+ const binary = sample.subarray(0, 8192).includes(0);
88
+ const tooLarge = stat.size > FILE_CONTENT_CAP || length > FILE_CONTENT_CAP;
89
+ const preview = { type: 'file', source: root === working ? 'worktree' : 'project', path: target, size: stat.size, binary, tooLarge,
90
+ ...(!binary && !tooLarge ? { content: sample.toString('utf8') } : {}) };
91
+ return { preview, ...(raster && stat.size <= IMAGE_CAP && length <= IMAGE_CAP ? { imageBytes: sample } : {}) };
92
+ }
93
+ finally {
94
+ await handle.close();
95
+ }
96
+ }
97
+ catch {
98
+ return unavailable('The file or its working directory is missing or cannot be read.');
99
+ }
100
+ }
101
+ export async function resolveFileLink(ctx) { return (await loadFileLink(ctx)).preview; }
102
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/artifacts/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type PublishedArtifact } from '../contract/index.js';
2
+ export declare function artifactDirectory(dataDir: string, runId: string): string;
3
+ export declare function publishArtifact(directory: string, runId: string, source: string): Promise<PublishedArtifact>;
4
+ export declare function readArtifact(directory: string, runId: string, id: string): Promise<{
5
+ metadata: PublishedArtifact;
6
+ bytes: Buffer;
7
+ } | null>;
8
+ export declare function listArtifacts(directory: string, runId: string): Promise<PublishedArtifact[]>;
@@ -0,0 +1,323 @@
1
+ import { randomUUID, createHash } from 'node:crypto';
2
+ import { constants, realpathSync } from 'node:fs';
3
+ import { mkdir, open, opendir, realpath, lstat, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { basename, isAbsolute, join, parse, resolve, sep } from 'node:path';
5
+ import { hostname } from 'node:os';
6
+ import { setTimeout as delay } from 'node:timers/promises';
7
+ import { z } from 'zod';
8
+ import { publishedArtifactSchema } from '../contract/index.js';
9
+ const MAX_FILE_BYTES = 10 * 1024 * 1024;
10
+ const MAX_METADATA_BYTES = 32 * 1024;
11
+ const MAX_RUN_BYTES = 64 * 1024 * 1024;
12
+ const MAX_ARTIFACTS = 64;
13
+ const MAX_DIRECTORY_ENTRIES = 1024;
14
+ const readFlags = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
15
+ function validateContext(directory, runId) {
16
+ z.uuid().parse(runId);
17
+ if (!isAbsolute(directory) || basename(directory) !== `${runId}-artifacts`)
18
+ throw new Error('Invalid artifact task directory');
19
+ }
20
+ export function artifactDirectory(dataDir, runId) {
21
+ z.uuid().parse(runId);
22
+ // The server's trusted data root may have an OS alias (e.g. macOS /var).
23
+ // Only canonicalize that root; runs/task entry redirects remain forbidden.
24
+ let root = resolve(dataDir);
25
+ try {
26
+ root = realpathSync(root);
27
+ }
28
+ catch { /* absent storage reads as unavailable */ }
29
+ return join(root, 'runs', `${runId}-artifacts`);
30
+ }
31
+ function sameFile(a, b) {
32
+ return a.dev === b.dev && a.ino === b.ino;
33
+ }
34
+ function sameSnapshot(a, b) {
35
+ return sameFile(a, b) && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs && a.nlink === b.nlink;
36
+ }
37
+ /** Never use readFile on untrusted descriptors: a growing file otherwise bypasses the size check. */
38
+ async function boundedRead(path, limit, stored = false) {
39
+ const file = await open(path, readFlags);
40
+ try {
41
+ const before = await file.stat({ bigint: true });
42
+ if (!before.isFile() || before.size > BigInt(limit) || (stored && before.nlink !== 1n))
43
+ throw new Error('Artifact must be a bounded regular file');
44
+ const buffer = Buffer.alloc(Number(before.size) + 1);
45
+ let count = 0;
46
+ while (count < buffer.length) {
47
+ const { bytesRead } = await file.read(buffer, count, buffer.length - count, count);
48
+ if (bytesRead === 0)
49
+ break;
50
+ count += bytesRead;
51
+ }
52
+ const after = await file.stat({ bigint: true });
53
+ const current = await lstat(path, { bigint: true });
54
+ if (count !== Number(before.size) || !sameSnapshot(before, after) || !sameFile(after, current) || current.isSymbolicLink())
55
+ throw new Error('File changed while publishing or reading artifact');
56
+ return buffer.subarray(0, count);
57
+ }
58
+ finally {
59
+ await file.close();
60
+ }
61
+ }
62
+ /** Reject symlinks in every storage component. Linux operations stay anchored to open directory
63
+ * descriptors; other platforms revalidate identity before/after use, not a same-user sandbox. */
64
+ async function storageDirectory(path, create = false) {
65
+ const absolute = resolve(path);
66
+ let file;
67
+ const root = parse(absolute).root;
68
+ let current = root;
69
+ try {
70
+ file = await open(root, readFlags | constants.O_DIRECTORY);
71
+ for (const part of absolute.slice(root.length).split(sep).filter(Boolean)) {
72
+ current = join(current, part);
73
+ const anchored = process.platform === 'linux' ? join(`/proc/self/fd/${file.fd}`, part) : current;
74
+ if (create)
75
+ await mkdir(anchored, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
76
+ throw error; });
77
+ const next = await open(anchored, readFlags | constants.O_DIRECTORY);
78
+ await file.close();
79
+ file = next;
80
+ }
81
+ const handle = file;
82
+ const original = await handle.stat({ bigint: true });
83
+ const check = async () => {
84
+ if (await realpath(absolute) !== absolute || !sameFile(original, await lstat(absolute, { bigint: true })))
85
+ throw new Error('Redirected artifact storage');
86
+ };
87
+ await check();
88
+ return { path: process.platform === 'linux' ? `/proc/self/fd/${handle.fd}` : absolute, check, close: () => handle.close() };
89
+ }
90
+ catch (error) {
91
+ await file?.close();
92
+ throw error;
93
+ }
94
+ }
95
+ async function* entries(path) {
96
+ const stream = await opendir(path);
97
+ let count = 0;
98
+ for await (const entry of stream) {
99
+ if (++count > MAX_DIRECTORY_ENTRIES)
100
+ throw new Error('Artifact directory entry limit exceeded');
101
+ yield entry.name;
102
+ }
103
+ }
104
+ const ownerSchema = z.object({ pid: z.number().int().positive().max(2147483647), host: z.string().min(1).max(255) }).strict();
105
+ async function reclaimDeadLock(root) {
106
+ let lock;
107
+ try {
108
+ const path = join(root.path, '.publish.lock');
109
+ lock = await open(path, readFlags | constants.O_DIRECTORY);
110
+ const pinned = process.platform === 'linux' ? `/proc/self/fd/${lock.fd}` : path;
111
+ const owner = ownerSchema.parse(JSON.parse((await boundedRead(join(pinned, 'owner.json'), 1024, true)).toString('utf8')));
112
+ if (owner.host !== hostname())
113
+ return;
114
+ try {
115
+ process.kill(owner.pid, 0);
116
+ return;
117
+ }
118
+ catch (error) {
119
+ if (error.code !== 'ESRCH')
120
+ return;
121
+ }
122
+ // Only one reaper can claim this inode. Keep its marker through the rename, so another
123
+ // observer of the old lock cannot remove a newly acquired lock (the stale-unlink race).
124
+ await mkdir(join(pinned, '.reaping'), { mode: 0o700 });
125
+ if (!sameFile(await lock.stat({ bigint: true }), await lstat(path, { bigint: true })))
126
+ return;
127
+ await root.check();
128
+ const retired = join(root.path, `.retired-lock-${randomUUID()}`);
129
+ await rename(path, retired);
130
+ await rm(retired, { recursive: true, force: true });
131
+ }
132
+ catch { /* Unknown owner, permission failure, or another reaper: fail closed and retry. */ }
133
+ finally {
134
+ await lock?.close();
135
+ }
136
+ }
137
+ async function acquireLock(root) {
138
+ const candidate = join(root.path, `.lock-candidate-${randomUUID()}`);
139
+ const path = join(root.path, '.publish.lock');
140
+ await mkdir(candidate, { mode: 0o700 });
141
+ try {
142
+ await writeFile(join(candidate, 'owner.json'), JSON.stringify({ pid: process.pid, host: hostname() }), { flag: 'wx', mode: 0o600 });
143
+ const identity = await lstat(candidate, { bigint: true });
144
+ const deadline = performance.now() + 5000;
145
+ for (;;) {
146
+ await root.check();
147
+ try {
148
+ // rename may replace an empty directory; an existing ownerless lock is not permission
149
+ // to publish. Cooperating publishers only introduce non-empty locks atomically.
150
+ const exists = await lstat(path).then(() => true, (error) => {
151
+ if (error.code === 'ENOENT')
152
+ return false;
153
+ throw error;
154
+ });
155
+ if (!exists) {
156
+ await rename(candidate, path);
157
+ return async () => {
158
+ if (sameFile(identity, await lstat(path, { bigint: true })))
159
+ await rm(path, { recursive: true, force: true });
160
+ };
161
+ }
162
+ }
163
+ catch (error) {
164
+ if (!['EEXIST', 'ENOTEMPTY'].includes(error.code ?? ''))
165
+ throw error;
166
+ }
167
+ if (performance.now() >= deadline)
168
+ throw new Error('Artifact publication lock is busy or has an unverifiable owner');
169
+ await reclaimDeadLock(root);
170
+ await delay(20);
171
+ }
172
+ }
173
+ finally {
174
+ await rm(candidate, { recursive: true, force: true });
175
+ }
176
+ }
177
+ async function checkCapacity(root, incomingBytes) {
178
+ let count = 0;
179
+ let bytes = 0;
180
+ for await (const id of entries(root.path)) {
181
+ if (!z.uuid().safeParse(id).success && !/^\.pending-[0-9a-f-]{36}$/i.test(id))
182
+ continue;
183
+ if (++count >= MAX_ARTIFACTS)
184
+ throw new Error('Artifact count limit exceeded');
185
+ let entry;
186
+ try {
187
+ entry = await open(join(root.path, id), readFlags | constants.O_DIRECTORY);
188
+ const pinned = process.platform === 'linux' ? `/proc/self/fd/${entry.fd}` : join(root.path, id);
189
+ const stat = await lstat(join(pinned, 'content'), { bigint: true });
190
+ if (!stat.isFile() || stat.nlink !== 1n || stat.size > BigInt(MAX_FILE_BYTES))
191
+ throw new Error('Unsafe stored artifact');
192
+ bytes += Number(stat.size);
193
+ }
194
+ catch (error) {
195
+ if (error.code !== 'ENOENT')
196
+ throw error;
197
+ }
198
+ finally {
199
+ await entry?.close();
200
+ }
201
+ if (bytes + incomingBytes > MAX_RUN_BYTES)
202
+ throw new Error('Artifact task byte limit exceeded');
203
+ }
204
+ }
205
+ export async function publishArtifact(directory, runId, source) {
206
+ validateContext(directory, runId);
207
+ const root = await storageDirectory(directory, true);
208
+ let release;
209
+ let pending;
210
+ let staging;
211
+ try {
212
+ release = await acquireLock(root);
213
+ // With the publication lock held, no cooperating writer can own a temporary entry.
214
+ // Completed UUID entries are never evicted, even if their metadata is corrupt.
215
+ for await (const id of entries(root.path)) {
216
+ if (/^\.pending-[0-9a-f-]{36}$/i.test(id))
217
+ await rm(join(root.path, id), { recursive: true, force: true });
218
+ }
219
+ const bytes = await boundedRead(source, MAX_FILE_BYTES);
220
+ // A same-size rewrite inside one timestamp tick leaves the stat snapshot above
221
+ // unchanged, so reread and compare: any mutation that landed before this second
222
+ // read surfaces as differing bytes. Later mutations stay covered by the snapshot.
223
+ const reread = await boundedRead(source, MAX_FILE_BYTES);
224
+ if (!bytes.equals(reread))
225
+ throw new Error('File changed while publishing or reading artifact');
226
+ await checkCapacity(root, bytes.length);
227
+ const metadata = publishedArtifactSchema.parse({ id: randomUUID(), runId, name: basename(source), sourcePath: resolve(source), createdAt: new Date().toISOString(), size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') });
228
+ pending = join(root.path, `.pending-${metadata.id}`);
229
+ await root.check();
230
+ await mkdir(pending, { mode: 0o700 });
231
+ staging = await open(pending, readFlags | constants.O_DIRECTORY);
232
+ const pinned = process.platform === 'linux' ? `/proc/self/fd/${staging.fd}` : pending;
233
+ await writeFile(join(pinned, 'content'), bytes, { mode: 0o600, flag: 'wx' });
234
+ await writeFile(join(pinned, 'metadata.json'), JSON.stringify(metadata), { mode: 0o600, flag: 'wx' });
235
+ if (!sameFile(await staging.stat({ bigint: true }), await lstat(pending, { bigint: true })))
236
+ throw new Error('Redirected artifact staging');
237
+ await root.check();
238
+ await rename(pending, join(root.path, metadata.id));
239
+ await root.check();
240
+ return metadata;
241
+ }
242
+ finally {
243
+ try {
244
+ await staging?.close();
245
+ if (pending)
246
+ await rm(pending, { recursive: true, force: true });
247
+ }
248
+ finally {
249
+ try {
250
+ await release?.();
251
+ }
252
+ finally {
253
+ await root.close();
254
+ }
255
+ }
256
+ }
257
+ }
258
+ async function readEntry(root, runId, id) {
259
+ if (!z.uuid().safeParse(id).success)
260
+ return null;
261
+ let entry;
262
+ try {
263
+ await root.check();
264
+ entry = await open(join(root.path, id), readFlags | constants.O_DIRECTORY);
265
+ const path = process.platform === 'linux' ? `/proc/self/fd/${entry.fd}` : join(root.path, id);
266
+ const metadata = publishedArtifactSchema.parse(JSON.parse((await boundedRead(join(path, 'metadata.json'), MAX_METADATA_BYTES, true)).toString('utf8')));
267
+ if (metadata.runId !== runId || metadata.id !== id || metadata.size > MAX_FILE_BYTES)
268
+ return null;
269
+ const bytes = await boundedRead(join(path, 'content'), MAX_FILE_BYTES, true);
270
+ if (bytes.length !== metadata.size || createHash('sha256').update(bytes).digest('hex') !== metadata.sha256)
271
+ return null;
272
+ if (!sameFile(await entry.stat({ bigint: true }), await lstat(join(root.path, id), { bigint: true })))
273
+ return null;
274
+ await root.check();
275
+ return { metadata, bytes };
276
+ }
277
+ catch {
278
+ return null;
279
+ }
280
+ finally {
281
+ await entry?.close();
282
+ }
283
+ }
284
+ export async function readArtifact(directory, runId, id) {
285
+ let root;
286
+ try {
287
+ validateContext(directory, runId);
288
+ root = await storageDirectory(directory);
289
+ return await readEntry(root, runId, id);
290
+ }
291
+ catch {
292
+ return null;
293
+ }
294
+ finally {
295
+ await root?.close();
296
+ }
297
+ }
298
+ export async function listArtifacts(directory, runId) {
299
+ let root;
300
+ try {
301
+ validateContext(directory, runId);
302
+ root = await storageDirectory(directory);
303
+ const artifacts = [];
304
+ let bytes = 0;
305
+ for await (const id of entries(root.path)) {
306
+ const entry = await readEntry(root, runId, id);
307
+ if (entry) {
308
+ bytes += entry.metadata.size;
309
+ artifacts.push(entry.metadata);
310
+ if (artifacts.length > MAX_ARTIFACTS || bytes > MAX_RUN_BYTES)
311
+ return [];
312
+ }
313
+ }
314
+ return artifacts.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
315
+ }
316
+ catch {
317
+ return [];
318
+ }
319
+ finally {
320
+ await root?.close();
321
+ }
322
+ }
323
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/artifacts/store.ts"],"names":[],"mappings":"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"}
@@ -15,11 +15,16 @@ export interface ProjectAutomationHandle {
15
15
  launch?: AutomationLauncher;
16
16
  onChange?: (automationId: string, revision: number) => void;
17
17
  }
18
+ /** A different process currently owns this project's poll lock. */
19
+ export declare class LeaseHeldError extends Error {
20
+ constructor();
21
+ }
18
22
  export declare class ProjectAutomationScheduler {
19
23
  private readonly handle;
20
24
  constructor(handle: ProjectAutomationHandle);
21
25
  check(definition: AutomationDefinition, mode?: 'preview' | 'execute'): Promise<GithubPollResult>;
22
26
  private launch;
27
+ private recordSkip;
23
28
  private recordFailure;
24
29
  }
25
30
  export interface WorkspaceAutomationSchedulerOptions {
@@ -33,6 +38,7 @@ export declare class WorkspaceAutomationScheduler {
33
38
  private timer?;
34
39
  private stopped;
35
40
  private scheduleGeneration;
41
+ private readonly retryAfter;
36
42
  constructor(options: WorkspaceAutomationSchedulerOptions);
37
43
  start(): Promise<void>;
38
44
  reschedule(): Promise<void>;
@@ -9,6 +9,10 @@ class GithubRequestArbiter {
9
9
  }
10
10
  }
11
11
  const githubRequests = new GithubRequestArbiter();
12
+ /** A different process currently owns this project's poll lock. */
13
+ export class LeaseHeldError extends Error {
14
+ constructor() { super('automation polling lease is held by another process'); }
15
+ }
12
16
  export class ProjectAutomationScheduler {
13
17
  handle;
14
18
  constructor(handle) {
@@ -19,12 +23,13 @@ export class ProjectAutomationScheduler {
19
23
  if (detectionOnly)
20
24
  mode = 'preview';
21
25
  const { store } = this.handle;
22
- const lease = store.acquireLease();
23
- if (!lease)
24
- throw new Error('automation polling lease is held by another process');
25
26
  const started = Date.now();
26
27
  let completion;
28
+ let lease;
27
29
  try {
30
+ lease = store.acquireLease();
31
+ if (!lease)
32
+ throw new LeaseHeldError();
28
33
  const state = store.state(definition.id) ?? {};
29
34
  if (state.backoffUntil && Date.parse(state.backoffUntil) > Date.now()) {
30
35
  throw new Error(`automation is backed off until ${state.backoffUntil}`);
@@ -76,39 +81,53 @@ export class ProjectAutomationScheduler {
76
81
  return { ...result, candidates: eligible };
77
82
  }
78
83
  catch (error) {
79
- if (mode === 'execute')
80
- this.recordFailure(definition, error);
84
+ if (error instanceof LeaseHeldError)
85
+ await this.recordSkip(definition, error);
86
+ else if (mode === 'execute')
87
+ await this.recordFailure(definition, error);
81
88
  else
82
- store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
89
+ await store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
83
90
  throw error;
84
91
  }
85
92
  finally {
86
- if (completion)
87
- store.appendLog({ automationId: definition.id, revision: definition.revision, ...completion, durationMs: Date.now() - started });
88
93
  try {
89
- store.maybeCompact();
94
+ if (completion)
95
+ await store.appendLog({ automationId: definition.id, revision: definition.revision, ...completion, durationMs: Date.now() - started });
96
+ if (lease) {
97
+ try {
98
+ await store.maybeCompact();
99
+ }
100
+ catch { /* append-only state remains readable; next check retries */ }
101
+ }
102
+ }
103
+ finally {
104
+ lease?.release();
90
105
  }
91
- catch { /* append-only state remains readable; next check retries */ }
92
- lease.release();
93
106
  }
94
107
  }
95
108
  async launch(definition, candidate) {
96
109
  const receipt = this.handle.store.reserveReceipt({ automationId: definition.id, revision: definition.revision, eventId: candidate.eventId, candidate });
97
110
  if (!receipt) {
98
- this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'duplicate', reason: 'A durable receipt already exists for this automation and event.', githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
111
+ await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'duplicate', reason: 'A durable receipt already exists for this automation and event.', githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
99
112
  return;
100
113
  }
101
114
  try {
102
115
  const launched = await this.handle.launch(definition, candidate, receipt.receiptId);
103
116
  this.handle.store.appendReceipt({ ...receipt, status: 'launched', runId: launched.runId, updatedAt: new Date().toISOString() });
104
- this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'launched', receiptId: receipt.receiptId, runId: launched.runId, githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
117
+ await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, event: candidate.event, result: 'launched', receiptId: receipt.receiptId, runId: launched.runId, githubNumber: candidate.number, githubTitle: candidate.title, githubUrl: candidate.url });
105
118
  }
106
119
  catch (error) {
107
120
  this.handle.store.appendReceipt({ ...receipt, status: 'launch-error', error: error instanceof Error ? error.message : String(error), updatedAt: new Date().toISOString() });
108
121
  throw error;
109
122
  }
110
123
  }
111
- recordFailure(definition, error) {
124
+ async recordSkip(definition, error) {
125
+ const { store } = this.handle;
126
+ await store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'skipped', reason: error.message });
127
+ // The lease owner may be writing state. The workspace retry floor handles our next attempt.
128
+ this.handle.onChange?.(definition.id, definition.revision);
129
+ }
130
+ async recordFailure(definition, error) {
112
131
  const state = this.handle.store.state(definition.id) ?? {};
113
132
  const failures = (state.consecutiveFailures ?? 0) + 1;
114
133
  const delay = Math.min(6 * 60 * 60_000, 60_000 * 2 ** (failures - 1));
@@ -118,7 +137,7 @@ export class ProjectAutomationScheduler {
118
137
  backoffUntil: new Date(Date.now() + delay).toISOString(),
119
138
  nextCheckAt: new Date(Date.now() + delay).toISOString(),
120
139
  });
121
- this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
140
+ await this.handle.store.appendLog({ automationId: definition.id, revision: definition.revision, result: 'error', reason: error instanceof Error ? error.message : String(error) });
122
141
  this.handle.onChange?.(definition.id, definition.revision);
123
142
  }
124
143
  }
@@ -131,12 +150,14 @@ function laterCursor(current, observed) {
131
150
  || observed.tieBreaker.localeCompare(current.tieBreaker ?? '');
132
151
  return order > 0 ? observed : current;
133
152
  }
153
+ const MIN_RETRY_MS = 60_000;
134
154
  /** One workspace timer, created only while at least one enabled definition exists. */
135
155
  export class WorkspaceAutomationScheduler {
136
156
  options;
137
157
  timer;
138
158
  stopped = true;
139
159
  scheduleGeneration = 0;
160
+ retryAfter = new Map();
140
161
  constructor(options) {
141
162
  this.options = options;
142
163
  }
@@ -168,6 +189,7 @@ export class WorkspaceAutomationScheduler {
168
189
  if (this.stopped)
169
190
  return;
170
191
  const due = [];
192
+ const live = new Set();
171
193
  for (const projectId of this.options.coordinator.enabledProjectIds()) {
172
194
  const store = this.options.coordinator.store(projectId);
173
195
  if (!store)
@@ -176,16 +198,22 @@ export class WorkspaceAutomationScheduler {
176
198
  if (!handle)
177
199
  continue;
178
200
  for (const definition of store.list().filter((item) => item.enabled)) {
179
- due.push({ at: Date.parse(store.state(definition.id)?.nextCheckAt ?? new Date().toISOString()), definition, scheduler: new ProjectAutomationScheduler(handle) });
201
+ const key = `${projectId}:${definition.id}`;
202
+ live.add(key);
203
+ const at = Date.parse(store.state(definition.id)?.nextCheckAt ?? new Date().toISOString());
204
+ due.push({ key, at: Math.max(at, this.retryAfter.get(key) ?? 0), retryAfterMs: Math.max(definition.intervalSeconds * 1_000, MIN_RETRY_MS), definition, scheduler: new ProjectAutomationScheduler(handle) });
180
205
  }
181
206
  }
207
+ for (const key of this.retryAfter.keys())
208
+ if (!live.has(key))
209
+ this.retryAfter.delete(key);
182
210
  if (!due.length)
183
211
  return;
184
212
  due.sort((a, b) => a.at - b.at);
185
213
  const next = due[0];
186
214
  this.timer = setTimeout(() => {
187
215
  this.timer = undefined;
188
- void next.scheduler.check(next.definition).catch(() => undefined).finally(() => this.schedule());
216
+ void next.scheduler.check(next.definition).then(() => { this.retryAfter.delete(next.key); }, () => { this.retryAfter.set(next.key, (this.options.now?.() ?? Date.now()) + next.retryAfterMs); }).finally(() => this.schedule());
189
217
  }, Math.max(0, next.at - (this.options.now?.() ?? Date.now())));
190
218
  }
191
219
  }