trigger.dev 0.0.0-v3-prerelease-20250108142426 → 0.0.0-v4-prerelease-20250916125920

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 (292) hide show
  1. package/README.md +28 -1
  2. package/dist/esm/apiClient.d.ts +158 -74
  3. package/dist/esm/apiClient.js +341 -76
  4. package/dist/esm/apiClient.js.map +1 -1
  5. package/dist/esm/build/buildWorker.d.ts +10 -6
  6. package/dist/esm/build/buildWorker.js +22 -38
  7. package/dist/esm/build/buildWorker.js.map +1 -1
  8. package/dist/esm/build/bundle.d.ts +18 -1
  9. package/dist/esm/build/bundle.js +142 -52
  10. package/dist/esm/build/bundle.js.map +1 -1
  11. package/dist/esm/build/entryPoints.d.ts +12 -0
  12. package/dist/esm/build/entryPoints.js +127 -0
  13. package/dist/esm/build/entryPoints.js.map +1 -0
  14. package/dist/esm/build/extensions.js +17 -0
  15. package/dist/esm/build/extensions.js.map +1 -1
  16. package/dist/esm/build/externals.js +184 -2
  17. package/dist/esm/build/externals.js.map +1 -1
  18. package/dist/esm/build/packageModules.d.ts +15 -5
  19. package/dist/esm/build/packageModules.js +154 -36
  20. package/dist/esm/build/packageModules.js.map +1 -1
  21. package/dist/esm/cli/common.d.ts +1 -1
  22. package/dist/esm/cli/common.js +38 -45
  23. package/dist/esm/cli/common.js.map +1 -1
  24. package/dist/esm/cli/index.js +19 -3
  25. package/dist/esm/cli/index.js.map +1 -1
  26. package/dist/esm/commands/analyze.d.ts +23 -0
  27. package/dist/esm/commands/analyze.js +122 -0
  28. package/dist/esm/commands/analyze.js.map +1 -0
  29. package/dist/esm/commands/deploy.d.ts +5 -2
  30. package/dist/esm/commands/deploy.js +271 -160
  31. package/dist/esm/commands/deploy.js.map +1 -1
  32. package/dist/esm/commands/dev.d.ts +36 -6
  33. package/dist/esm/commands/dev.js +71 -2
  34. package/dist/esm/commands/dev.js.map +1 -1
  35. package/dist/esm/commands/env.d.ts +2 -0
  36. package/dist/esm/commands/env.js +298 -0
  37. package/dist/esm/commands/env.js.map +1 -0
  38. package/dist/esm/commands/init.d.ts +9 -1
  39. package/dist/esm/commands/init.js +132 -65
  40. package/dist/esm/commands/init.js.map +1 -1
  41. package/dist/esm/commands/install-mcp.d.ts +50 -0
  42. package/dist/esm/commands/install-mcp.js +497 -0
  43. package/dist/esm/commands/install-mcp.js.map +1 -0
  44. package/dist/esm/commands/install-rules.d.ts +11 -0
  45. package/dist/esm/commands/install-rules.js +381 -0
  46. package/dist/esm/commands/install-rules.js.map +1 -0
  47. package/dist/esm/commands/list-profiles.d.ts +3 -7
  48. package/dist/esm/commands/list-profiles.js +7 -4
  49. package/dist/esm/commands/list-profiles.js.map +1 -1
  50. package/dist/esm/commands/login.d.ts +9 -5
  51. package/dist/esm/commands/login.js +40 -7
  52. package/dist/esm/commands/login.js.map +1 -1
  53. package/dist/esm/commands/logout.d.ts +1 -1
  54. package/dist/esm/commands/mcp.d.ts +38 -0
  55. package/dist/esm/commands/mcp.js +82 -0
  56. package/dist/esm/commands/mcp.js.map +1 -0
  57. package/dist/esm/commands/preview.d.ts +5 -0
  58. package/dist/esm/commands/preview.js +93 -0
  59. package/dist/esm/commands/preview.js.map +1 -0
  60. package/dist/esm/commands/promote.d.ts +3 -0
  61. package/dist/esm/commands/promote.js +86 -0
  62. package/dist/esm/commands/promote.js.map +1 -0
  63. package/dist/esm/commands/switch.d.ts +19 -0
  64. package/dist/esm/commands/switch.js +93 -0
  65. package/dist/esm/commands/switch.js.map +1 -0
  66. package/dist/esm/commands/trigger.d.ts +33 -0
  67. package/dist/esm/commands/trigger.js +88 -0
  68. package/dist/esm/commands/trigger.js.map +1 -0
  69. package/dist/esm/commands/update.d.ts +3 -2
  70. package/dist/esm/commands/update.js +23 -9
  71. package/dist/esm/commands/update.js.map +1 -1
  72. package/dist/esm/commands/whoami.d.ts +12 -1
  73. package/dist/esm/commands/whoami.js +36 -6
  74. package/dist/esm/commands/whoami.js.map +1 -1
  75. package/dist/esm/commands/workers/build.d.ts +4 -0
  76. package/dist/esm/commands/workers/build.js +345 -0
  77. package/dist/esm/commands/workers/build.js.map +1 -0
  78. package/dist/esm/commands/workers/create.d.ts +2 -0
  79. package/dist/esm/commands/workers/create.js +91 -0
  80. package/dist/esm/commands/workers/create.js.map +1 -0
  81. package/dist/esm/commands/workers/index.d.ts +2 -0
  82. package/dist/esm/commands/workers/index.js +13 -0
  83. package/dist/esm/commands/workers/index.js.map +1 -0
  84. package/dist/esm/commands/workers/list.d.ts +2 -0
  85. package/dist/esm/commands/workers/list.js +80 -0
  86. package/dist/esm/commands/workers/list.js.map +1 -0
  87. package/dist/esm/commands/workers/run.d.ts +2 -0
  88. package/dist/esm/commands/workers/run.js +105 -0
  89. package/dist/esm/commands/workers/run.js.map +1 -0
  90. package/dist/esm/config.d.ts +2 -1
  91. package/dist/esm/config.js +35 -8
  92. package/dist/esm/config.js.map +1 -1
  93. package/dist/esm/deploy/buildImage.d.ts +12 -11
  94. package/dist/esm/deploy/buildImage.js +360 -115
  95. package/dist/esm/deploy/buildImage.js.map +1 -1
  96. package/dist/esm/dev/backgroundWorker.d.ts +6 -245
  97. package/dist/esm/dev/backgroundWorker.js +11 -320
  98. package/dist/esm/dev/backgroundWorker.js.map +1 -1
  99. package/dist/esm/dev/devOutput.js +48 -9
  100. package/dist/esm/dev/devOutput.js.map +1 -1
  101. package/dist/esm/dev/devSession.d.ts +2 -1
  102. package/dist/esm/dev/devSession.js +68 -65
  103. package/dist/esm/dev/devSession.js.map +1 -1
  104. package/dist/esm/dev/devSupervisor.d.ts +12 -0
  105. package/dist/esm/dev/devSupervisor.js +568 -0
  106. package/dist/esm/dev/devSupervisor.js.map +1 -0
  107. package/dist/esm/dev/lock.d.ts +1 -0
  108. package/dist/esm/dev/lock.js +80 -0
  109. package/dist/esm/dev/lock.js.map +1 -0
  110. package/dist/esm/dev/mcpServer.d.ts +10 -0
  111. package/dist/esm/dev/mcpServer.js +201 -0
  112. package/dist/esm/dev/mcpServer.js.map +1 -0
  113. package/dist/esm/dev/taskRunProcessPool.d.ts +39 -0
  114. package/dist/esm/dev/taskRunProcessPool.js +220 -0
  115. package/dist/esm/dev/taskRunProcessPool.js.map +1 -0
  116. package/dist/esm/dev/workerRuntime.d.ts +2 -2
  117. package/dist/esm/dev/workerRuntime.js +1 -265
  118. package/dist/esm/dev/workerRuntime.js.map +1 -1
  119. package/dist/esm/entryPoints/dev-index-worker.js +45 -8
  120. package/dist/esm/entryPoints/dev-index-worker.js.map +1 -1
  121. package/dist/esm/entryPoints/dev-run-controller.d.ts +58 -0
  122. package/dist/esm/entryPoints/dev-run-controller.js +652 -0
  123. package/dist/esm/entryPoints/dev-run-controller.js.map +1 -0
  124. package/dist/esm/entryPoints/dev-run-worker.js +394 -199
  125. package/dist/esm/entryPoints/dev-run-worker.js.map +1 -1
  126. package/dist/esm/entryPoints/managed/controller.d.ts +63 -0
  127. package/dist/esm/entryPoints/managed/controller.js +482 -0
  128. package/dist/esm/entryPoints/managed/controller.js.map +1 -0
  129. package/dist/esm/entryPoints/managed/env.d.ts +170 -0
  130. package/dist/esm/entryPoints/managed/env.js +199 -0
  131. package/dist/esm/entryPoints/managed/env.js.map +1 -0
  132. package/dist/esm/entryPoints/managed/execution.d.ts +141 -0
  133. package/dist/esm/entryPoints/managed/execution.js +869 -0
  134. package/dist/esm/entryPoints/managed/execution.js.map +1 -0
  135. package/dist/esm/entryPoints/managed/logger.d.ts +30 -0
  136. package/dist/esm/entryPoints/managed/logger.js +47 -0
  137. package/dist/esm/entryPoints/managed/logger.js.map +1 -0
  138. package/dist/esm/entryPoints/managed/notifier.d.ts +30 -0
  139. package/dist/esm/entryPoints/managed/notifier.js +63 -0
  140. package/dist/esm/entryPoints/managed/notifier.js.map +1 -0
  141. package/dist/esm/entryPoints/managed/overrides.d.ts +18 -0
  142. package/dist/esm/entryPoints/managed/overrides.js +19 -0
  143. package/dist/esm/entryPoints/managed/overrides.js.map +1 -0
  144. package/dist/esm/entryPoints/managed/poller.d.ts +31 -0
  145. package/dist/esm/entryPoints/managed/poller.js +89 -0
  146. package/dist/esm/entryPoints/managed/poller.js.map +1 -0
  147. package/dist/esm/entryPoints/managed/snapshot.d.ts +54 -0
  148. package/dist/esm/entryPoints/managed/snapshot.js +293 -0
  149. package/dist/esm/entryPoints/managed/snapshot.js.map +1 -0
  150. package/dist/esm/entryPoints/managed/taskRunProcessProvider.d.ts +62 -0
  151. package/dist/esm/entryPoints/managed/taskRunProcessProvider.js +252 -0
  152. package/dist/esm/entryPoints/managed/taskRunProcessProvider.js.map +1 -0
  153. package/dist/esm/entryPoints/{deploy-index-controller.js → managed-index-controller.js} +27 -3
  154. package/dist/esm/entryPoints/managed-index-controller.js.map +1 -0
  155. package/dist/esm/entryPoints/{deploy-index-worker.js → managed-index-worker.js} +50 -21
  156. package/dist/esm/entryPoints/managed-index-worker.js.map +1 -0
  157. package/dist/esm/entryPoints/managed-run-controller.js +13 -0
  158. package/dist/esm/entryPoints/managed-run-controller.js.map +1 -0
  159. package/dist/esm/entryPoints/managed-run-worker.js +512 -0
  160. package/dist/esm/entryPoints/managed-run-worker.js.map +1 -0
  161. package/dist/esm/executions/taskRunProcess.d.ts +28 -82
  162. package/dist/esm/executions/taskRunProcess.js +130 -74
  163. package/dist/esm/executions/taskRunProcess.js.map +1 -1
  164. package/dist/esm/indexing/indexWorkerManifest.d.ts +21 -5
  165. package/dist/esm/indexing/indexWorkerManifest.js +4 -4
  166. package/dist/esm/indexing/indexWorkerManifest.js.map +1 -1
  167. package/dist/esm/indexing/registerResources.d.ts +5 -0
  168. package/dist/esm/indexing/registerResources.js +44 -0
  169. package/dist/esm/indexing/registerResources.js.map +1 -0
  170. package/dist/esm/mcp/auth.d.ts +12 -0
  171. package/dist/esm/mcp/auth.js +152 -0
  172. package/dist/esm/mcp/auth.js.map +1 -0
  173. package/dist/esm/mcp/capabilities.d.ts +4 -0
  174. package/dist/esm/mcp/capabilities.js +22 -0
  175. package/dist/esm/mcp/capabilities.js.map +1 -0
  176. package/dist/esm/mcp/config.d.ts +82 -0
  177. package/dist/esm/mcp/config.js +87 -0
  178. package/dist/esm/mcp/config.js.map +1 -0
  179. package/dist/esm/mcp/context.d.ts +45 -0
  180. package/dist/esm/mcp/context.js +129 -0
  181. package/dist/esm/mcp/context.js.map +1 -0
  182. package/dist/esm/mcp/formatters.d.ts +7 -0
  183. package/dist/esm/mcp/formatters.js +330 -0
  184. package/dist/esm/mcp/formatters.js.map +1 -0
  185. package/dist/esm/mcp/logger.d.ts +11 -0
  186. package/dist/esm/mcp/logger.js +34 -0
  187. package/dist/esm/mcp/logger.js.map +1 -0
  188. package/dist/esm/mcp/mintlifyClient.d.ts +1 -0
  189. package/dist/esm/mcp/mintlifyClient.js +65 -0
  190. package/dist/esm/mcp/mintlifyClient.js.map +1 -0
  191. package/dist/esm/mcp/schemas.d.ts +324 -0
  192. package/dist/esm/mcp/schemas.js +144 -0
  193. package/dist/esm/mcp/schemas.js.map +1 -0
  194. package/dist/esm/mcp/tools/deploys.d.ts +174 -0
  195. package/dist/esm/mcp/tools/deploys.js +161 -0
  196. package/dist/esm/mcp/tools/deploys.js.map +1 -0
  197. package/dist/esm/mcp/tools/docs.d.ts +77 -0
  198. package/dist/esm/mcp/tools/docs.js +18 -0
  199. package/dist/esm/mcp/tools/docs.js.map +1 -0
  200. package/dist/esm/mcp/tools/orgs.d.ts +172 -0
  201. package/dist/esm/mcp/tools/orgs.js +172 -0
  202. package/dist/esm/mcp/tools/orgs.js.map +1 -0
  203. package/dist/esm/mcp/tools/previewBranches.d.ts +78 -0
  204. package/dist/esm/mcp/tools/previewBranches.js +28 -0
  205. package/dist/esm/mcp/tools/previewBranches.js.map +1 -0
  206. package/dist/esm/mcp/tools/runs.d.ts +335 -0
  207. package/dist/esm/mcp/tools/runs.js +160 -0
  208. package/dist/esm/mcp/tools/runs.js.map +1 -0
  209. package/dist/esm/mcp/tools/tasks.d.ts +200 -0
  210. package/dist/esm/mcp/tools/tasks.js +117 -0
  211. package/dist/esm/mcp/tools/tasks.js.map +1 -0
  212. package/dist/esm/mcp/tools.d.ts +2 -0
  213. package/dist/esm/mcp/tools.js +40 -0
  214. package/dist/esm/mcp/tools.js.map +1 -0
  215. package/dist/esm/mcp/types.d.ts +6 -0
  216. package/dist/esm/mcp/types.js +2 -0
  217. package/dist/esm/mcp/types.js.map +1 -0
  218. package/dist/esm/mcp/utils.d.ts +89 -0
  219. package/dist/esm/mcp/utils.js +95 -0
  220. package/dist/esm/mcp/utils.js.map +1 -0
  221. package/dist/esm/rules/install.d.ts +1 -0
  222. package/dist/esm/rules/install.js +2 -0
  223. package/dist/esm/rules/install.js.map +1 -0
  224. package/dist/esm/rules/manifest.d.ts +145 -0
  225. package/dist/esm/rules/manifest.js +110 -0
  226. package/dist/esm/rules/manifest.js.map +1 -0
  227. package/dist/esm/rules/types.d.ts +3 -0
  228. package/dist/esm/rules/types.js +3 -0
  229. package/dist/esm/rules/types.js.map +1 -0
  230. package/dist/esm/utilities/accessTokens.d.ts +12 -0
  231. package/dist/esm/utilities/accessTokens.js +30 -0
  232. package/dist/esm/utilities/accessTokens.js.map +1 -0
  233. package/dist/esm/utilities/analyze.d.ts +13 -0
  234. package/dist/esm/utilities/analyze.js +463 -0
  235. package/dist/esm/utilities/analyze.js.map +1 -0
  236. package/dist/esm/utilities/cliOutput.d.ts +6 -1
  237. package/dist/esm/utilities/cliOutput.js +11 -2
  238. package/dist/esm/utilities/cliOutput.js.map +1 -1
  239. package/dist/esm/utilities/configFiles.d.ts +65 -15
  240. package/dist/esm/utilities/configFiles.js +124 -26
  241. package/dist/esm/utilities/configFiles.js.map +1 -1
  242. package/dist/esm/utilities/eventBus.d.ts +7 -3
  243. package/dist/esm/utilities/eventBus.js.map +1 -1
  244. package/dist/esm/utilities/fileSystem.d.ts +7 -1
  245. package/dist/esm/utilities/fileSystem.js +42 -4
  246. package/dist/esm/utilities/fileSystem.js.map +1 -1
  247. package/dist/esm/utilities/gitMeta.d.ts +2 -0
  248. package/dist/esm/utilities/gitMeta.js +220 -0
  249. package/dist/esm/utilities/gitMeta.js.map +1 -0
  250. package/dist/esm/utilities/githubActions.d.ts +4 -0
  251. package/dist/esm/utilities/githubActions.js +18 -0
  252. package/dist/esm/utilities/githubActions.js.map +1 -0
  253. package/dist/esm/utilities/initialBanner.js +18 -6
  254. package/dist/esm/utilities/initialBanner.js.map +1 -1
  255. package/dist/esm/utilities/localEnvVars.d.ts +3 -0
  256. package/dist/esm/utilities/localEnvVars.js +19 -0
  257. package/dist/esm/utilities/localEnvVars.js.map +1 -0
  258. package/dist/esm/utilities/sanitizeEnvVars.d.ts +16 -3
  259. package/dist/esm/utilities/sanitizeEnvVars.js +15 -0
  260. package/dist/esm/utilities/sanitizeEnvVars.js.map +1 -1
  261. package/dist/esm/utilities/session.d.ts +14 -0
  262. package/dist/esm/utilities/session.js +44 -47
  263. package/dist/esm/utilities/session.js.map +1 -1
  264. package/dist/esm/utilities/supportsHyperlinks.d.ts +15 -0
  265. package/dist/esm/utilities/supportsHyperlinks.js +122 -0
  266. package/dist/esm/utilities/supportsHyperlinks.js.map +1 -0
  267. package/dist/esm/utilities/tempDirectories.d.ts +1 -0
  268. package/dist/esm/utilities/tempDirectories.js +19 -2
  269. package/dist/esm/utilities/tempDirectories.js.map +1 -1
  270. package/dist/esm/utilities/terminalLink.d.ts +56 -0
  271. package/dist/esm/utilities/terminalLink.js +76 -0
  272. package/dist/esm/utilities/terminalLink.js.map +1 -0
  273. package/dist/esm/utilities/windows.js +51 -1
  274. package/dist/esm/utilities/windows.js.map +1 -1
  275. package/dist/esm/version.js +1 -1
  276. package/package.json +44 -25
  277. package/dist/esm/entryPoints/deploy-index-controller.js.map +0 -1
  278. package/dist/esm/entryPoints/deploy-index-worker.js.map +0 -1
  279. package/dist/esm/entryPoints/deploy-run-controller.js +0 -1099
  280. package/dist/esm/entryPoints/deploy-run-controller.js.map +0 -1
  281. package/dist/esm/entryPoints/deploy-run-worker.js +0 -366
  282. package/dist/esm/entryPoints/deploy-run-worker.js.map +0 -1
  283. package/dist/esm/indexing/registerTasks.d.ts +0 -2
  284. package/dist/esm/indexing/registerTasks.js +0 -62
  285. package/dist/esm/indexing/registerTasks.js.map +0 -1
  286. package/dist/esm/telemetry/tracing.d.ts +0 -3
  287. package/dist/esm/telemetry/tracing.js +0 -58
  288. package/dist/esm/telemetry/tracing.js.map +0 -1
  289. /package/dist/esm/entryPoints/{deploy-index-controller.d.ts → managed-index-controller.d.ts} +0 -0
  290. /package/dist/esm/entryPoints/{deploy-index-worker.d.ts → managed-index-worker.d.ts} +0 -0
  291. /package/dist/esm/entryPoints/{deploy-run-controller.d.ts → managed-run-controller.d.ts} +0 -0
  292. /package/dist/esm/entryPoints/{deploy-run-worker.d.ts → managed-run-worker.d.ts} +0 -0
@@ -0,0 +1,869 @@
1
+ import { SuspendedProcessError, } from "@trigger.dev/core/v3";
2
+ import { TaskRunProcess } from "../../executions/taskRunProcess.js";
3
+ import { setTimeout as sleep } from "timers/promises";
4
+ import { RunExecutionSnapshotPoller } from "./poller.js";
5
+ import { assertExhaustive, tryCatch } from "@trigger.dev/core/utils";
6
+ import { MetadataClient } from "./overrides.js";
7
+ import { randomBytes } from "node:crypto";
8
+ import { SnapshotManager } from "./snapshot.js";
9
+ import { RunNotifier } from "./notifier.js";
10
+ class ExecutionAbortError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ExecutionAbortError";
14
+ }
15
+ }
16
+ export class RunExecution {
17
+ id;
18
+ executionAbortController;
19
+ _runFriendlyId;
20
+ currentAttemptNumber;
21
+ currentTaskRunEnv;
22
+ snapshotManager;
23
+ dequeuedAt;
24
+ podScheduledAt;
25
+ workerManifest;
26
+ env;
27
+ httpClient;
28
+ logger;
29
+ restoreCount;
30
+ taskRunProcess;
31
+ snapshotPoller;
32
+ lastHeartbeat;
33
+ isShuttingDown = false;
34
+ shutdownReason;
35
+ isCompletingRun = false;
36
+ ignoreSnapshotChanges = false;
37
+ supervisorSocket;
38
+ notifier;
39
+ metadataClient;
40
+ taskRunProcessProvider;
41
+ constructor(opts) {
42
+ this.id = randomBytes(4).toString("hex");
43
+ this.workerManifest = opts.workerManifest;
44
+ this.env = opts.env;
45
+ this.httpClient = opts.httpClient;
46
+ this.logger = opts.logger;
47
+ this.supervisorSocket = opts.supervisorSocket;
48
+ this.taskRunProcessProvider = opts.taskRunProcessProvider;
49
+ this.restoreCount = 0;
50
+ this.executionAbortController = new AbortController();
51
+ if (this.env.TRIGGER_METADATA_URL) {
52
+ this.metadataClient = new MetadataClient(this.env.TRIGGER_METADATA_URL);
53
+ }
54
+ }
55
+ /**
56
+ * Cancels the current execution.
57
+ */
58
+ async cancel() {
59
+ if (this.isShuttingDown) {
60
+ throw new Error("cancel called after execution shut down");
61
+ }
62
+ this.sendDebugLog("cancelling attempt", { runId: this.runFriendlyId });
63
+ await this.taskRunProcess?.cancel();
64
+ }
65
+ /**
66
+ * Kills the current execution.
67
+ */
68
+ async kill({ exitExecution = true } = {}) {
69
+ if (this.taskRunProcess) {
70
+ await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
71
+ }
72
+ if (exitExecution) {
73
+ this.shutdownExecution("kill");
74
+ }
75
+ }
76
+ async shutdown() {
77
+ if (this.taskRunProcess) {
78
+ await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
79
+ }
80
+ this.shutdownExecution("shutdown");
81
+ }
82
+ /**
83
+ * Prepares the execution with task run environment variables.
84
+ * This should be called before executing, typically after a successful run to prepare for the next one.
85
+ */
86
+ async prepareForExecution(opts) {
87
+ if (this.isShuttingDown) {
88
+ throw new Error("prepareForExecution called after execution shut down");
89
+ }
90
+ if (this.taskRunProcess) {
91
+ throw new Error("prepareForExecution called after process was already created");
92
+ }
93
+ // Set the task run environment so canExecute returns true
94
+ this.currentTaskRunEnv = opts.taskRunEnv;
95
+ this.taskRunProcess = await this.taskRunProcessProvider.getProcess({
96
+ taskRunEnv: opts.taskRunEnv,
97
+ isWarmStart: true,
98
+ });
99
+ }
100
+ attachTaskRunProcessHandlers(taskRunProcess) {
101
+ taskRunProcess.unsafeDetachEvtHandlers();
102
+ taskRunProcess.onTaskRunHeartbeat.attach(async (runId) => {
103
+ if (!this.runFriendlyId) {
104
+ this.sendDebugLog("onTaskRunHeartbeat: missing run ID", { heartbeatRunId: runId });
105
+ return;
106
+ }
107
+ if (runId !== this.runFriendlyId) {
108
+ this.sendDebugLog("onTaskRunHeartbeat: mismatched run ID", {
109
+ heartbeatRunId: runId,
110
+ expectedRunId: this.runFriendlyId,
111
+ });
112
+ return;
113
+ }
114
+ const [error] = await tryCatch(this.onHeartbeat());
115
+ if (error) {
116
+ this.sendDebugLog("onTaskRunHeartbeat: failed", { error: error.message });
117
+ }
118
+ });
119
+ taskRunProcess.onSendDebugLog.attach(async (debugLog) => {
120
+ this.sendRuntimeDebugLog(debugLog.message, debugLog.properties);
121
+ });
122
+ taskRunProcess.onSetSuspendable.attach(async ({ suspendable }) => {
123
+ this.suspendable = suspendable;
124
+ });
125
+ }
126
+ /**
127
+ * Returns true if no run has been started yet and we're prepared for the next run.
128
+ */
129
+ get canExecute() {
130
+ if (this.taskRunProcessProvider.hasPersistentProcess) {
131
+ return true;
132
+ }
133
+ // If we've ever had a run ID, this execution can't be reused
134
+ if (this._runFriendlyId) {
135
+ return false;
136
+ }
137
+ // We can execute if we have the task run environment ready
138
+ return !!this.currentTaskRunEnv;
139
+ }
140
+ /**
141
+ * Called by the RunController when it receives a websocket notification
142
+ * or when the snapshot poller detects a change.
143
+ *
144
+ * This is the main entry point for snapshot changes, but processing is deferred to the snapshot manager.
145
+ */
146
+ async enqueueSnapshotChangesAndWait(snapshots) {
147
+ if (this.isShuttingDown) {
148
+ this.sendDebugLog("enqueueSnapshotChangeAndWait: shutting down, skipping");
149
+ return;
150
+ }
151
+ if (!this.snapshotManager) {
152
+ this.sendDebugLog("enqueueSnapshotChangeAndWait: missing snapshot manager");
153
+ return;
154
+ }
155
+ await this.snapshotManager.handleSnapshotChanges(snapshots);
156
+ }
157
+ async processSnapshotChange(runData, deprecated) {
158
+ const { run, snapshot, completedWaitpoints } = runData;
159
+ const snapshotMetadata = {
160
+ incomingSnapshotId: snapshot.friendlyId,
161
+ completedWaitpoints: completedWaitpoints.length,
162
+ };
163
+ if (this.ignoreSnapshotChanges) {
164
+ this.sendDebugLog("processSnapshotChange: ignoring snapshot change", {
165
+ incomingSnapshotId: snapshot.friendlyId,
166
+ completedWaitpoints: completedWaitpoints.length,
167
+ currentAttemptNumber: this.currentAttemptNumber,
168
+ newAttemptNumber: run.attemptNumber,
169
+ });
170
+ return;
171
+ }
172
+ if (!this.snapshotManager) {
173
+ this.sendDebugLog("handleSnapshotChange: missing snapshot manager", snapshotMetadata);
174
+ return;
175
+ }
176
+ if (this.currentAttemptNumber && this.currentAttemptNumber !== run.attemptNumber) {
177
+ this.sendDebugLog("error: attempt number mismatch", snapshotMetadata);
178
+ // This is a rogue execution, a new one will already have been created elsewhere
179
+ await this.exitTaskRunProcessWithoutFailingRun({
180
+ flush: false,
181
+ reason: "attempt number mismatch",
182
+ });
183
+ return;
184
+ }
185
+ // DO NOT REMOVE (very noisy, but helpful for debugging)
186
+ // this.sendDebugLog(`processing snapshot change: ${snapshot.executionStatus}`, snapshotMetadata);
187
+ // Reset the snapshot poll interval so we don't do unnecessary work
188
+ this.snapshotPoller?.updateSnapshotId(snapshot.friendlyId);
189
+ this.snapshotPoller?.resetCurrentInterval();
190
+ if (deprecated) {
191
+ this.sendDebugLog("run execution is deprecated", { incomingSnapshot: snapshot });
192
+ await this.exitTaskRunProcessWithoutFailingRun({
193
+ flush: false,
194
+ reason: "deprecated execution",
195
+ });
196
+ return;
197
+ }
198
+ switch (snapshot.executionStatus) {
199
+ case "PENDING_CANCEL": {
200
+ this.sendDebugLog("run was cancelled", snapshotMetadata);
201
+ const [error] = await tryCatch(this.cancel());
202
+ if (error) {
203
+ this.sendDebugLog("snapshot change: failed to cancel attempt", {
204
+ ...snapshotMetadata,
205
+ error: error.message,
206
+ });
207
+ }
208
+ this.abortExecution();
209
+ return;
210
+ }
211
+ case "QUEUED": {
212
+ this.sendDebugLog("run was re-queued", snapshotMetadata);
213
+ await this.exitTaskRunProcessWithoutFailingRun({ flush: true, reason: "re-queued" });
214
+ return;
215
+ }
216
+ case "FINISHED": {
217
+ this.sendDebugLog("run is finished", snapshotMetadata);
218
+ // We are finishing the run in handleCompletionResult, so we don't need to do anything here
219
+ if (this.isCompletingRun) {
220
+ this.sendDebugLog("run is finished but we're completing it, skipping", snapshotMetadata);
221
+ return;
222
+ }
223
+ await this.exitTaskRunProcessWithoutFailingRun({ flush: true, reason: "already-finished" });
224
+ return;
225
+ }
226
+ case "QUEUED_EXECUTING":
227
+ case "EXECUTING_WITH_WAITPOINTS": {
228
+ this.sendDebugLog("run is executing with waitpoints", snapshotMetadata);
229
+ // Wait for next status change - suspension is handled by the snapshot manager
230
+ return;
231
+ }
232
+ case "SUSPENDED": {
233
+ this.sendDebugLog("run was suspended", snapshotMetadata);
234
+ // This will kill the process and fail the execution with a SuspendedProcessError
235
+ // We don't flush because we already did before suspending
236
+ await this.exitTaskRunProcessWithoutFailingRun({ flush: false, reason: "suspended" });
237
+ return;
238
+ }
239
+ case "PENDING_EXECUTING": {
240
+ this.sendDebugLog("run is pending execution", snapshotMetadata);
241
+ if (completedWaitpoints.length === 0) {
242
+ this.sendDebugLog("no waitpoints to complete, nothing to do", snapshotMetadata);
243
+ return;
244
+ }
245
+ const [error] = await tryCatch(this.restore());
246
+ if (error) {
247
+ this.sendDebugLog("failed to restore execution", {
248
+ ...snapshotMetadata,
249
+ error: error.message,
250
+ });
251
+ this.abortExecution();
252
+ return;
253
+ }
254
+ return;
255
+ }
256
+ case "EXECUTING": {
257
+ if (completedWaitpoints.length === 0) {
258
+ this.sendDebugLog("run is executing without completed waitpoints", snapshotMetadata);
259
+ return;
260
+ }
261
+ this.sendDebugLog("run is executing with completed waitpoints", snapshotMetadata);
262
+ if (!this.taskRunProcess) {
263
+ this.sendDebugLog("no task run process, ignoring completed waitpoints", snapshotMetadata);
264
+ this.abortExecution();
265
+ return;
266
+ }
267
+ for (const waitpoint of completedWaitpoints) {
268
+ this.taskRunProcess.waitpointCompleted(waitpoint);
269
+ }
270
+ return;
271
+ }
272
+ case "RUN_CREATED": {
273
+ this.sendDebugLog("aborting execution: invalid status change: RUN_CREATED", snapshotMetadata);
274
+ this.abortExecution();
275
+ return;
276
+ }
277
+ default: {
278
+ assertExhaustive(snapshot.executionStatus);
279
+ }
280
+ }
281
+ }
282
+ async startAttempt({ isWarmStart, }) {
283
+ if (!this.runFriendlyId || !this.snapshotManager) {
284
+ throw new Error("Cannot start attempt: missing run or snapshot manager");
285
+ }
286
+ // Reset this for the new attempt
287
+ this.isCompletingRun = false;
288
+ this.sendDebugLog("starting attempt", { isWarmStart: String(isWarmStart) });
289
+ const attemptStartedAt = Date.now();
290
+ // Check for abort before each major async operation
291
+ if (this.executionAbortController.signal.aborted) {
292
+ throw new ExecutionAbortError("Execution aborted before start");
293
+ }
294
+ const start = await this.httpClient.startRunAttempt(this.runFriendlyId, this.snapshotManager.snapshotId, { isWarmStart });
295
+ if (this.executionAbortController.signal.aborted) {
296
+ throw new ExecutionAbortError("Execution aborted after start");
297
+ }
298
+ if (!start.success) {
299
+ throw new Error(`Start API call failed: ${start.error}`);
300
+ }
301
+ // A snapshot was just created, so update the snapshot ID
302
+ this.snapshotManager.updateSnapshot(start.data.snapshot.friendlyId, start.data.snapshot.executionStatus);
303
+ // Also set or update the attempt number - we do this to detect illegal attempt number changes, e.g. from stalled runners coming back online
304
+ const attemptNumber = start.data.run.attemptNumber;
305
+ if (attemptNumber && attemptNumber > 0) {
306
+ this.currentAttemptNumber = attemptNumber;
307
+ }
308
+ else {
309
+ this.sendDebugLog("error: invalid attempt number returned from start attempt", {
310
+ attemptNumber: String(attemptNumber),
311
+ });
312
+ }
313
+ const metrics = this.measureExecutionMetrics({
314
+ attemptCreatedAt: attemptStartedAt,
315
+ dequeuedAt: this.dequeuedAt?.getTime(),
316
+ podScheduledAt: this.podScheduledAt?.getTime(),
317
+ });
318
+ this.sendDebugLog("started attempt", { start: start.data });
319
+ return { ...start.data, metrics };
320
+ }
321
+ /**
322
+ * Executes the run. This will return when the execution is complete and we should warm start.
323
+ * When this returns, the child process will have been cleaned up.
324
+ */
325
+ async execute(runOpts) {
326
+ if (this.isShuttingDown) {
327
+ throw new Error("execute called after execution shut down");
328
+ }
329
+ // Setup initial state
330
+ this.runFriendlyId = runOpts.runFriendlyId;
331
+ // Create snapshot manager
332
+ this.snapshotManager = new SnapshotManager({
333
+ runFriendlyId: runOpts.runFriendlyId,
334
+ runnerId: this.env.TRIGGER_RUNNER_ID,
335
+ initialSnapshotId: runOpts.snapshotFriendlyId,
336
+ // We're just guessing here, but "PENDING_EXECUTING" is probably fine
337
+ initialStatus: "PENDING_EXECUTING",
338
+ logger: this.logger,
339
+ metadataClient: this.metadataClient,
340
+ onSnapshotChange: this.processSnapshotChange.bind(this),
341
+ onSuspendable: this.handleSuspendable.bind(this),
342
+ });
343
+ this.dequeuedAt = runOpts.dequeuedAt;
344
+ this.podScheduledAt = runOpts.podScheduledAt;
345
+ // Create and start services
346
+ this.snapshotPoller = new RunExecutionSnapshotPoller({
347
+ runFriendlyId: this.runFriendlyId,
348
+ snapshotFriendlyId: this.snapshotManager.snapshotId,
349
+ logger: this.logger,
350
+ snapshotPollIntervalSeconds: this.env.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS,
351
+ onPoll: this.fetchAndProcessSnapshotChanges.bind(this),
352
+ }).start();
353
+ this.notifier = new RunNotifier({
354
+ runFriendlyId: this.runFriendlyId,
355
+ supervisorSocket: this.supervisorSocket,
356
+ onNotify: this.fetchAndProcessSnapshotChanges.bind(this),
357
+ logger: this.logger,
358
+ }).start();
359
+ const [startError, start] = await tryCatch(this.startAttempt({ isWarmStart: runOpts.isWarmStart }));
360
+ if (startError) {
361
+ this.sendDebugLog("failed to start attempt", { error: startError.message });
362
+ this.shutdownExecution("failed to start attempt");
363
+ return;
364
+ }
365
+ const [executeError] = await tryCatch(this.executeRunWrapper({ ...start, isWarmStart: runOpts.isWarmStart }));
366
+ if (executeError) {
367
+ this.sendDebugLog("failed to execute run", { error: executeError.message });
368
+ this.shutdownExecution("failed to execute run");
369
+ return;
370
+ }
371
+ // This is here for safety, but it
372
+ this.shutdownExecution("execute call finished");
373
+ }
374
+ async executeRunWrapper({ run, snapshot, envVars, execution, metrics, isWarmStart, isImmediateRetry, }) {
375
+ this.currentTaskRunEnv = envVars;
376
+ const [executeError] = await tryCatch(this.executeRun({
377
+ run,
378
+ snapshot,
379
+ envVars,
380
+ execution,
381
+ metrics,
382
+ isWarmStart,
383
+ isImmediateRetry,
384
+ }));
385
+ if (!executeError) {
386
+ return;
387
+ }
388
+ if (executeError instanceof SuspendedProcessError) {
389
+ this.sendDebugLog("execution was suspended", {
390
+ run: run.friendlyId,
391
+ snapshot: snapshot.friendlyId,
392
+ error: executeError.message,
393
+ });
394
+ return;
395
+ }
396
+ if (executeError instanceof ExecutionAbortError) {
397
+ this.sendDebugLog("execution was aborted", {
398
+ run: run.friendlyId,
399
+ snapshot: snapshot.friendlyId,
400
+ error: executeError.message,
401
+ });
402
+ return;
403
+ }
404
+ this.sendDebugLog("error while executing attempt", {
405
+ error: executeError.message,
406
+ runId: run.friendlyId,
407
+ snapshotId: snapshot.friendlyId,
408
+ });
409
+ const completion = {
410
+ id: execution.run.id,
411
+ ok: false,
412
+ retry: undefined,
413
+ error: TaskRunProcess.parseExecuteError(executeError),
414
+ };
415
+ const [completeError] = await tryCatch(this.complete({ completion }));
416
+ if (completeError) {
417
+ this.sendDebugLog("failed to complete run", { error: completeError.message });
418
+ }
419
+ }
420
+ async executeRun({ run, snapshot, envVars, execution, metrics, isWarmStart, isImmediateRetry, }) {
421
+ if (isImmediateRetry) {
422
+ await this.taskRunProcessProvider.handleImmediateRetry();
423
+ }
424
+ const taskRunEnv = this.currentTaskRunEnv ?? envVars;
425
+ if (!this.taskRunProcess || this.taskRunProcess.isBeingKilled) {
426
+ this.sendDebugLog("getting new task run process", { runId: execution.run.id });
427
+ this.taskRunProcess = await this.taskRunProcessProvider.getProcess({
428
+ taskRunEnv: { ...taskRunEnv, TRIGGER_PROJECT_REF: execution.project.ref },
429
+ isWarmStart,
430
+ });
431
+ }
432
+ else {
433
+ this.sendDebugLog("using prepared task run process", { runId: execution.run.id });
434
+ }
435
+ this.attachTaskRunProcessHandlers(this.taskRunProcess);
436
+ this.sendDebugLog("executing task run process", { runId: execution.run.id });
437
+ const abortHandler = async () => {
438
+ this.sendDebugLog("execution aborted during task run, cleaning up process", {
439
+ runId: execution.run.id,
440
+ });
441
+ if (this.taskRunProcess) {
442
+ await this.taskRunProcessProvider.handleProcessAbort(this.taskRunProcess);
443
+ }
444
+ };
445
+ // Set up an abort handler that will cleanup the task run process
446
+ this.executionAbortController.signal.addEventListener("abort", abortHandler);
447
+ const completion = await this.taskRunProcess.execute({
448
+ payload: {
449
+ execution,
450
+ traceContext: execution.run.traceContext ?? {},
451
+ metrics,
452
+ },
453
+ messageId: run.friendlyId,
454
+ env: envVars,
455
+ }, isWarmStart);
456
+ this.executionAbortController.signal.removeEventListener("abort", abortHandler);
457
+ // If we get here, the task completed normally
458
+ this.sendDebugLog("completed run attempt", { attemptSuccess: completion.ok });
459
+ // Return the process to the provider - this handles all cleanup logic
460
+ const [returnError] = await tryCatch(this.taskRunProcessProvider.returnProcess(this.taskRunProcess));
461
+ if (returnError) {
462
+ this.sendDebugLog("failed to return task run process, submitting completion anyway", {
463
+ error: returnError.message,
464
+ });
465
+ }
466
+ const [completionError] = await tryCatch(this.complete({ completion }));
467
+ if (completionError) {
468
+ this.sendDebugLog("failed to complete run", { error: completionError.message });
469
+ }
470
+ }
471
+ async complete({ completion }) {
472
+ if (!this.runFriendlyId || !this.snapshotManager) {
473
+ throw new Error("cannot complete run: missing run or snapshot manager");
474
+ }
475
+ this.isCompletingRun = true;
476
+ const completionResult = await this.httpClient.completeRunAttempt(this.runFriendlyId, this.snapshotManager.snapshotId, { completion });
477
+ if (!completionResult.success) {
478
+ throw new Error(`failed to submit completion: ${completionResult.error}`);
479
+ }
480
+ await this.handleCompletionResult({
481
+ completion,
482
+ result: completionResult.data.result,
483
+ });
484
+ }
485
+ async handleCompletionResult({ completion, result, }) {
486
+ this.sendDebugLog(`completion result: ${result.attemptStatus}`, {
487
+ attemptSuccess: completion.ok,
488
+ attemptStatus: result.attemptStatus,
489
+ snapshotId: result.snapshot.friendlyId,
490
+ runId: result.run.friendlyId,
491
+ });
492
+ const snapshotStatus = this.convertAttemptStatusToSnapshotStatus(result.attemptStatus);
493
+ // Update our snapshot ID to match the completion result to ensure any subsequent API calls use the correct snapshot
494
+ this.updateSnapshotAfterCompletion(result.snapshot.friendlyId, snapshotStatus);
495
+ const { attemptStatus } = result;
496
+ switch (attemptStatus) {
497
+ case "RUN_FINISHED":
498
+ case "RUN_PENDING_CANCEL":
499
+ case "RETRY_QUEUED": {
500
+ return;
501
+ }
502
+ case "RETRY_IMMEDIATELY": {
503
+ if (attemptStatus !== "RETRY_IMMEDIATELY") {
504
+ return;
505
+ }
506
+ if (completion.ok) {
507
+ throw new Error("Should retry but completion OK.");
508
+ }
509
+ if (!completion.retry) {
510
+ throw new Error("Should retry but missing retry params.");
511
+ }
512
+ await this.retryImmediately({ retryOpts: completion.retry });
513
+ return;
514
+ }
515
+ default: {
516
+ assertExhaustive(attemptStatus);
517
+ }
518
+ }
519
+ }
520
+ updateSnapshotAfterCompletion(snapshotId, status) {
521
+ this.snapshotManager?.updateSnapshot(snapshotId, status);
522
+ this.snapshotPoller?.updateSnapshotId(snapshotId);
523
+ }
524
+ convertAttemptStatusToSnapshotStatus(attemptStatus) {
525
+ switch (attemptStatus) {
526
+ case "RUN_FINISHED":
527
+ return "FINISHED";
528
+ case "RUN_PENDING_CANCEL":
529
+ return "PENDING_CANCEL";
530
+ case "RETRY_QUEUED":
531
+ return "QUEUED";
532
+ case "RETRY_IMMEDIATELY":
533
+ return "EXECUTING";
534
+ default:
535
+ assertExhaustive(attemptStatus);
536
+ }
537
+ }
538
+ measureExecutionMetrics({ attemptCreatedAt, dequeuedAt, podScheduledAt, }) {
539
+ const metrics = [
540
+ {
541
+ name: "start",
542
+ event: "create_attempt",
543
+ timestamp: attemptCreatedAt,
544
+ duration: Date.now() - attemptCreatedAt,
545
+ },
546
+ ];
547
+ if (dequeuedAt) {
548
+ metrics.push({
549
+ name: "start",
550
+ event: "dequeue",
551
+ timestamp: dequeuedAt,
552
+ duration: 0,
553
+ });
554
+ }
555
+ if (podScheduledAt) {
556
+ metrics.push({
557
+ name: "start",
558
+ event: "pod_scheduled",
559
+ timestamp: podScheduledAt,
560
+ duration: 0,
561
+ });
562
+ }
563
+ return metrics;
564
+ }
565
+ async retryImmediately({ retryOpts }) {
566
+ this.sendDebugLog("retrying run immediately", {
567
+ timestamp: retryOpts.timestamp,
568
+ delay: retryOpts.delay,
569
+ });
570
+ const delay = retryOpts.timestamp - Date.now();
571
+ if (delay > 0) {
572
+ // Wait for retry delay to pass
573
+ await sleep(delay);
574
+ }
575
+ // Start and execute next attempt
576
+ const [startError, start] = await tryCatch(this.enableIgnoreSnapshotChanges(() => this.startAttempt({ isWarmStart: true })));
577
+ if (startError) {
578
+ this.sendDebugLog("failed to start attempt for retry", { error: startError.message });
579
+ this.shutdownExecution("retryImmediately: failed to start attempt");
580
+ return;
581
+ }
582
+ const [executeError] = await tryCatch(this.executeRunWrapper({ ...start, isWarmStart: true, isImmediateRetry: true }));
583
+ if (executeError) {
584
+ this.sendDebugLog("failed to execute run for retry", { error: executeError.message });
585
+ this.shutdownExecution("retryImmediately: failed to execute run");
586
+ return;
587
+ }
588
+ }
589
+ async enableIgnoreSnapshotChanges(fn) {
590
+ this.ignoreSnapshotChanges = true;
591
+ try {
592
+ return await fn();
593
+ }
594
+ finally {
595
+ this.ignoreSnapshotChanges = false;
596
+ }
597
+ }
598
+ /**
599
+ * Restores a suspended execution from PENDING_EXECUTING
600
+ */
601
+ async restore() {
602
+ this.sendDebugLog("restoring execution");
603
+ if (!this.runFriendlyId || !this.snapshotManager) {
604
+ throw new Error("Cannot restore: missing run or snapshot manager");
605
+ }
606
+ // Short delay to give websocket time to reconnect
607
+ await sleep(100);
608
+ // Process any env overrides
609
+ await this.processEnvOverrides("restore");
610
+ const continuationResult = await this.httpClient.continueRunExecution(this.runFriendlyId, this.snapshotManager.snapshotId);
611
+ if (!continuationResult.success) {
612
+ throw new Error(continuationResult.error);
613
+ }
614
+ // Track restore count
615
+ this.restoreCount++;
616
+ }
617
+ async exitTaskRunProcessWithoutFailingRun({ flush, reason, }) {
618
+ await this.taskRunProcessProvider.suspendProcess(flush, this.taskRunProcess);
619
+ // No services should be left running after this line - let's make sure of it
620
+ this.shutdownExecution(`exitTaskRunProcessWithoutFailingRun: ${reason}`);
621
+ }
622
+ /**
623
+ * Processes env overrides from the metadata service. Generally called when we're resuming from a suspended state.
624
+ */
625
+ async processEnvOverrides(reason, shouldPollForSnapshotChanges) {
626
+ if (!this.metadataClient) {
627
+ return null;
628
+ }
629
+ const [error, overrides] = await this.metadataClient.getEnvOverrides();
630
+ if (error) {
631
+ this.sendDebugLog("[override] failed to fetch", {
632
+ reason,
633
+ error: error.message,
634
+ });
635
+ return null;
636
+ }
637
+ if (overrides.TRIGGER_RUN_ID && overrides.TRIGGER_RUN_ID !== this.runFriendlyId) {
638
+ this.sendDebugLog("[override] run ID mismatch, ignoring overrides", {
639
+ reason,
640
+ currentRunId: this.runFriendlyId,
641
+ incomingRunId: overrides.TRIGGER_RUN_ID,
642
+ });
643
+ return null;
644
+ }
645
+ this.sendDebugLog(`[override] processing: ${reason}`, {
646
+ overrides,
647
+ currentEnv: this.env.raw,
648
+ });
649
+ // Override the env with the new values
650
+ this.env.override(overrides);
651
+ // Update services with new values
652
+ if (overrides.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS) {
653
+ this.snapshotPoller?.updateInterval(this.env.TRIGGER_SNAPSHOT_POLL_INTERVAL_SECONDS * 1000);
654
+ }
655
+ if (overrides.TRIGGER_SUPERVISOR_API_PROTOCOL ||
656
+ overrides.TRIGGER_SUPERVISOR_API_DOMAIN ||
657
+ overrides.TRIGGER_SUPERVISOR_API_PORT) {
658
+ this.httpClient.updateApiUrl(this.env.TRIGGER_SUPERVISOR_API_URL);
659
+ }
660
+ if (overrides.TRIGGER_RUNNER_ID) {
661
+ this.httpClient.updateRunnerId(this.env.TRIGGER_RUNNER_ID);
662
+ }
663
+ // Poll for snapshot changes immediately
664
+ if (shouldPollForSnapshotChanges) {
665
+ this.sendDebugLog("[override] polling for snapshot changes", { reason });
666
+ this.fetchAndProcessSnapshotChanges("restore").catch(() => { });
667
+ }
668
+ return {
669
+ overrides,
670
+ };
671
+ }
672
+ async onHeartbeat() {
673
+ if (!this.runFriendlyId) {
674
+ this.sendDebugLog("heartbeat: missing run ID");
675
+ return;
676
+ }
677
+ if (!this.snapshotManager) {
678
+ this.sendDebugLog("heartbeat: missing snapshot manager");
679
+ return;
680
+ }
681
+ this.sendDebugLog("heartbeat");
682
+ const response = await this.httpClient.heartbeatRun(this.runFriendlyId, this.snapshotManager.snapshotId);
683
+ if (!response.success) {
684
+ this.sendDebugLog("heartbeat: failed", { error: response.error });
685
+ }
686
+ this.lastHeartbeat = new Date();
687
+ }
688
+ sendDebugLog(message, properties, runIdOverride) {
689
+ this.logger.sendDebugLog({
690
+ runId: runIdOverride ?? this.runFriendlyId,
691
+ message: `[execution] ${message}`,
692
+ properties: {
693
+ ...properties,
694
+ runId: this.runFriendlyId,
695
+ snapshotId: this.currentSnapshotFriendlyId,
696
+ executionId: this.id,
697
+ executionRestoreCount: this.restoreCount,
698
+ lastHeartbeat: this.lastHeartbeat?.toISOString(),
699
+ },
700
+ });
701
+ }
702
+ sendRuntimeDebugLog(message, properties, runIdOverride) {
703
+ this.logger.sendDebugLog({
704
+ runId: runIdOverride ?? this.runFriendlyId,
705
+ message: `[runtime] ${message}`,
706
+ print: false,
707
+ properties: {
708
+ ...properties,
709
+ runId: this.runFriendlyId,
710
+ snapshotId: this.currentSnapshotFriendlyId,
711
+ executionId: this.id,
712
+ executionRestoreCount: this.restoreCount,
713
+ lastHeartbeat: this.lastHeartbeat?.toISOString(),
714
+ },
715
+ });
716
+ }
717
+ set suspendable(suspendable) {
718
+ this.snapshotManager?.setSuspendable(suspendable).catch((error) => {
719
+ this.sendDebugLog("failed to set suspendable", { error: error.message });
720
+ });
721
+ }
722
+ // Ensure we can only set this once
723
+ set runFriendlyId(id) {
724
+ if (this._runFriendlyId) {
725
+ throw new Error("Run ID already set");
726
+ }
727
+ this._runFriendlyId = id;
728
+ }
729
+ get runFriendlyId() {
730
+ return this._runFriendlyId;
731
+ }
732
+ get currentSnapshotFriendlyId() {
733
+ return this.snapshotManager?.snapshotId;
734
+ }
735
+ get taskRunEnv() {
736
+ return this.currentTaskRunEnv;
737
+ }
738
+ get metrics() {
739
+ return {
740
+ execution: {
741
+ restoreCount: this.restoreCount,
742
+ lastHeartbeat: this.lastHeartbeat,
743
+ },
744
+ poller: this.snapshotPoller?.metrics,
745
+ notifier: this.notifier?.metrics,
746
+ };
747
+ }
748
+ get isAborted() {
749
+ return this.executionAbortController.signal.aborted;
750
+ }
751
+ abortExecution() {
752
+ if (this.isAborted) {
753
+ this.sendDebugLog("execution already aborted");
754
+ return;
755
+ }
756
+ this.executionAbortController.abort();
757
+ this.shutdownExecution("abortExecution");
758
+ }
759
+ shutdownExecution(reason) {
760
+ if (this.isShuttingDown) {
761
+ this.sendDebugLog(`[shutdown] ${reason} (already shutting down)`, {
762
+ firstShutdownReason: this.shutdownReason,
763
+ });
764
+ return;
765
+ }
766
+ this.sendDebugLog(`[shutdown] ${reason}`);
767
+ this.isShuttingDown = true;
768
+ this.shutdownReason = reason;
769
+ this.snapshotPoller?.stop();
770
+ this.snapshotManager?.stop();
771
+ this.notifier?.stop();
772
+ this.taskRunProcess?.unsafeDetachEvtHandlers();
773
+ }
774
+ async handleSuspendable(suspendableSnapshot) {
775
+ this.sendDebugLog("handleSuspendable", { suspendableSnapshot });
776
+ if (!this.snapshotManager) {
777
+ this.sendDebugLog("handleSuspendable: missing snapshot manager", { suspendableSnapshot });
778
+ return;
779
+ }
780
+ // Ensure this is the current snapshot
781
+ if (suspendableSnapshot.id !== this.currentSnapshotFriendlyId) {
782
+ this.sendDebugLog("snapshot changed before cleanup, abort", {
783
+ suspendableSnapshot,
784
+ currentSnapshotId: this.currentSnapshotFriendlyId,
785
+ });
786
+ this.abortExecution();
787
+ return;
788
+ }
789
+ // First cleanup the task run process
790
+ const [error] = await tryCatch(this.taskRunProcess?.cleanup(false));
791
+ if (error) {
792
+ this.sendDebugLog("failed to cleanup task run process, carrying on", {
793
+ suspendableSnapshot,
794
+ error: error.message,
795
+ });
796
+ }
797
+ // Double check snapshot hasn't changed after cleanup
798
+ if (suspendableSnapshot.id !== this.currentSnapshotFriendlyId) {
799
+ this.sendDebugLog("snapshot changed after cleanup, abort", {
800
+ suspendableSnapshot,
801
+ currentSnapshotId: this.currentSnapshotFriendlyId,
802
+ });
803
+ this.abortExecution();
804
+ return;
805
+ }
806
+ if (!this.runFriendlyId) {
807
+ this.sendDebugLog("missing run ID for suspension, abort", { suspendableSnapshot });
808
+ this.abortExecution();
809
+ return;
810
+ }
811
+ // Call the suspend API with the current snapshot ID
812
+ const suspendResult = await this.httpClient.suspendRun(this.runFriendlyId, suspendableSnapshot.id);
813
+ if (!suspendResult.success) {
814
+ this.sendDebugLog("suspension request failed, staying alive 🎶", {
815
+ suspendableSnapshot,
816
+ error: suspendResult.error,
817
+ });
818
+ // This is fine, we'll wait for the next status change
819
+ return;
820
+ }
821
+ if (!suspendResult.data.ok) {
822
+ this.sendDebugLog("suspension request returned error, staying alive 🎶", {
823
+ suspendableSnapshot,
824
+ error: suspendResult.data.error,
825
+ });
826
+ // This is fine, we'll wait for the next status change
827
+ return;
828
+ }
829
+ this.sendDebugLog("suspending, any day now 🚬", { suspendableSnapshot });
830
+ }
831
+ /**
832
+ * Fetches the latest execution data and enqueues snapshot changes. Used by both poller and notification handlers.
833
+ * @param source string - where this call originated (e.g. 'poller', 'notification')
834
+ */
835
+ async fetchAndProcessSnapshotChanges(source) {
836
+ if (!this.runFriendlyId) {
837
+ this.sendDebugLog(`fetchAndProcessSnapshotChanges: missing runFriendlyId`, { source });
838
+ return;
839
+ }
840
+ // Use the last processed snapshot as the since parameter
841
+ const sinceSnapshotId = this.currentSnapshotFriendlyId;
842
+ if (!sinceSnapshotId) {
843
+ this.sendDebugLog(`fetchAndProcessSnapshotChanges: missing sinceSnapshotId`, { source });
844
+ return;
845
+ }
846
+ const response = await this.httpClient.getSnapshotsSince(this.runFriendlyId, sinceSnapshotId);
847
+ if (!response.success) {
848
+ this.sendDebugLog(`fetchAndProcessSnapshotChanges: failed to get snapshots since`, {
849
+ source,
850
+ error: response.error,
851
+ });
852
+ await this.processEnvOverrides("snapshots since error");
853
+ return;
854
+ }
855
+ const { snapshots } = response.data;
856
+ if (!snapshots.length) {
857
+ return;
858
+ }
859
+ const [error] = await tryCatch(this.enqueueSnapshotChangesAndWait(snapshots));
860
+ if (error) {
861
+ this.sendDebugLog(`fetchAndProcessSnapshotChanges: failed to enqueue and process snapshot change`, {
862
+ source,
863
+ error: error.message,
864
+ });
865
+ return;
866
+ }
867
+ }
868
+ }
869
+ //# sourceMappingURL=execution.js.map