trigger.dev 3.3.16 → 4.0.0-v4-beta.0

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 (126) hide show
  1. package/README.md +31 -0
  2. package/dist/esm/apiClient.d.ts +75 -72
  3. package/dist/esm/apiClient.js +207 -31
  4. package/dist/esm/apiClient.js.map +1 -1
  5. package/dist/esm/build/buildWorker.d.ts +7 -6
  6. package/dist/esm/build/buildWorker.js +10 -36
  7. package/dist/esm/build/buildWorker.js.map +1 -1
  8. package/dist/esm/build/bundle.d.ts +12 -1
  9. package/dist/esm/build/bundle.js +61 -19
  10. package/dist/esm/build/bundle.js.map +1 -1
  11. package/dist/esm/build/entryPoints.js +17 -6
  12. package/dist/esm/build/entryPoints.js.map +1 -1
  13. package/dist/esm/build/packageModules.d.ts +14 -5
  14. package/dist/esm/build/packageModules.js +135 -35
  15. package/dist/esm/build/packageModules.js.map +1 -1
  16. package/dist/esm/cli/common.js +5 -3
  17. package/dist/esm/cli/common.js.map +1 -1
  18. package/dist/esm/cli/index.js +6 -0
  19. package/dist/esm/cli/index.js.map +1 -1
  20. package/dist/esm/commands/deploy.js +25 -3
  21. package/dist/esm/commands/deploy.js.map +1 -1
  22. package/dist/esm/commands/dev.d.ts +9 -0
  23. package/dist/esm/commands/dev.js +10 -1
  24. package/dist/esm/commands/dev.js.map +1 -1
  25. package/dist/esm/commands/list-profiles.d.ts +2 -6
  26. package/dist/esm/commands/list-profiles.js +7 -4
  27. package/dist/esm/commands/list-profiles.js.map +1 -1
  28. package/dist/esm/commands/login.js +3 -2
  29. package/dist/esm/commands/login.js.map +1 -1
  30. package/dist/esm/commands/promote.d.ts +3 -0
  31. package/dist/esm/commands/promote.js +75 -0
  32. package/dist/esm/commands/promote.js.map +1 -0
  33. package/dist/esm/commands/switch.d.ts +19 -0
  34. package/dist/esm/commands/switch.js +68 -0
  35. package/dist/esm/commands/switch.js.map +1 -0
  36. package/dist/esm/commands/trigger.d.ts +33 -0
  37. package/dist/esm/commands/trigger.js +88 -0
  38. package/dist/esm/commands/trigger.js.map +1 -0
  39. package/dist/esm/commands/workers/build.d.ts +4 -0
  40. package/dist/esm/commands/workers/build.js +340 -0
  41. package/dist/esm/commands/workers/build.js.map +1 -0
  42. package/dist/esm/commands/workers/create.d.ts +2 -0
  43. package/dist/esm/commands/workers/create.js +91 -0
  44. package/dist/esm/commands/workers/create.js.map +1 -0
  45. package/dist/esm/commands/workers/index.d.ts +2 -0
  46. package/dist/esm/commands/workers/index.js +13 -0
  47. package/dist/esm/commands/workers/index.js.map +1 -0
  48. package/dist/esm/commands/workers/list.d.ts +2 -0
  49. package/dist/esm/commands/workers/list.js +80 -0
  50. package/dist/esm/commands/workers/list.js.map +1 -0
  51. package/dist/esm/commands/workers/run.d.ts +2 -0
  52. package/dist/esm/commands/workers/run.js +105 -0
  53. package/dist/esm/commands/workers/run.js.map +1 -0
  54. package/dist/esm/config.js +11 -1
  55. package/dist/esm/config.js.map +1 -1
  56. package/dist/esm/deploy/buildImage.d.ts +1 -1
  57. package/dist/esm/deploy/buildImage.js +54 -34
  58. package/dist/esm/deploy/buildImage.js.map +1 -1
  59. package/dist/esm/dev/backgroundWorker.d.ts +2 -240
  60. package/dist/esm/dev/backgroundWorker.js +8 -305
  61. package/dist/esm/dev/backgroundWorker.js.map +1 -1
  62. package/dist/esm/dev/devOutput.js +13 -5
  63. package/dist/esm/dev/devOutput.js.map +1 -1
  64. package/dist/esm/dev/devSession.js +25 -48
  65. package/dist/esm/dev/devSession.js.map +1 -1
  66. package/dist/esm/dev/devSupervisor.d.ts +12 -0
  67. package/dist/esm/dev/devSupervisor.js +515 -0
  68. package/dist/esm/dev/devSupervisor.js.map +1 -0
  69. package/dist/esm/dev/lock.d.ts +1 -0
  70. package/dist/esm/dev/lock.js +80 -0
  71. package/dist/esm/dev/lock.js.map +1 -0
  72. package/dist/esm/dev/mcpServer.d.ts +10 -0
  73. package/dist/esm/dev/mcpServer.js +201 -0
  74. package/dist/esm/dev/mcpServer.js.map +1 -0
  75. package/dist/esm/dev/workerRuntime.d.ts +0 -1
  76. package/dist/esm/dev/workerRuntime.js +1 -322
  77. package/dist/esm/dev/workerRuntime.js.map +1 -1
  78. package/dist/esm/entryPoints/dev-index-worker.js +9 -7
  79. package/dist/esm/entryPoints/dev-index-worker.js.map +1 -1
  80. package/dist/esm/entryPoints/dev-run-controller.d.ts +53 -0
  81. package/dist/esm/entryPoints/dev-run-controller.js +615 -0
  82. package/dist/esm/entryPoints/dev-run-controller.js.map +1 -0
  83. package/dist/esm/entryPoints/dev-run-worker.js +252 -169
  84. package/dist/esm/entryPoints/dev-run-worker.js.map +1 -1
  85. package/dist/esm/entryPoints/{deploy-index-controller.js → managed-index-controller.js} +3 -1
  86. package/dist/esm/entryPoints/managed-index-controller.js.map +1 -0
  87. package/dist/esm/entryPoints/{deploy-index-worker.js → managed-index-worker.js} +12 -24
  88. package/dist/esm/entryPoints/managed-index-worker.js.map +1 -0
  89. package/dist/esm/entryPoints/managed-run-controller.js +1350 -0
  90. package/dist/esm/entryPoints/managed-run-controller.js.map +1 -0
  91. package/dist/esm/entryPoints/{deploy-run-worker.js → managed-run-worker.js} +113 -68
  92. package/dist/esm/entryPoints/managed-run-worker.js.map +1 -0
  93. package/dist/esm/executions/taskRunProcess.d.ts +18 -73
  94. package/dist/esm/executions/taskRunProcess.js +76 -28
  95. package/dist/esm/executions/taskRunProcess.js.map +1 -1
  96. package/dist/esm/indexing/indexWorkerManifest.d.ts +9 -2
  97. package/dist/esm/indexing/registerResources.d.ts +2 -0
  98. package/dist/esm/indexing/registerResources.js +40 -0
  99. package/dist/esm/indexing/registerResources.js.map +1 -0
  100. package/dist/esm/utilities/configFiles.d.ts +36 -15
  101. package/dist/esm/utilities/configFiles.js +73 -26
  102. package/dist/esm/utilities/configFiles.js.map +1 -1
  103. package/dist/esm/utilities/eventBus.d.ts +6 -3
  104. package/dist/esm/utilities/eventBus.js.map +1 -1
  105. package/dist/esm/utilities/githubActions.d.ts +4 -0
  106. package/dist/esm/utilities/githubActions.js +18 -0
  107. package/dist/esm/utilities/githubActions.js.map +1 -0
  108. package/dist/esm/utilities/initialBanner.js +18 -6
  109. package/dist/esm/utilities/initialBanner.js.map +1 -1
  110. package/dist/esm/utilities/sanitizeEnvVars.d.ts +16 -3
  111. package/dist/esm/utilities/sanitizeEnvVars.js +15 -0
  112. package/dist/esm/utilities/sanitizeEnvVars.js.map +1 -1
  113. package/dist/esm/version.js +1 -1
  114. package/package.json +12 -6
  115. package/dist/esm/entryPoints/deploy-index-controller.js.map +0 -1
  116. package/dist/esm/entryPoints/deploy-index-worker.js.map +0 -1
  117. package/dist/esm/entryPoints/deploy-run-controller.js +0 -1099
  118. package/dist/esm/entryPoints/deploy-run-controller.js.map +0 -1
  119. package/dist/esm/entryPoints/deploy-run-worker.js.map +0 -1
  120. package/dist/esm/indexing/registerTasks.d.ts +0 -2
  121. package/dist/esm/indexing/registerTasks.js +0 -62
  122. package/dist/esm/indexing/registerTasks.js.map +0 -1
  123. /package/dist/esm/entryPoints/{deploy-index-controller.d.ts → managed-index-controller.d.ts} +0 -0
  124. /package/dist/esm/entryPoints/{deploy-index-worker.d.ts → managed-index-worker.d.ts} +0 -0
  125. /package/dist/esm/entryPoints/{deploy-run-controller.d.ts → managed-run-controller.d.ts} +0 -0
  126. /package/dist/esm/entryPoints/{deploy-run-worker.d.ts → managed-run-worker.d.ts} +0 -0
@@ -0,0 +1,53 @@
1
+ import { DequeuedMessage, LogLevel } from "@trigger.dev/core/v3";
2
+ import { CliApiClient } from "../apiClient.js";
3
+ import { BackgroundWorker } from "../dev/backgroundWorker.js";
4
+ type DevRunControllerOptions = {
5
+ runFriendlyId: string;
6
+ worker: BackgroundWorker;
7
+ httpClient: CliApiClient;
8
+ logLevel: LogLevel;
9
+ heartbeatIntervalSeconds?: number;
10
+ onSubscribeToRunNotifications: (run: Run, snapshot: Snapshot) => void;
11
+ onUnsubscribeFromRunNotifications: (run: Run, snapshot: Snapshot) => void;
12
+ onFinished: () => void;
13
+ };
14
+ type Run = {
15
+ friendlyId: string;
16
+ attemptNumber?: number | null;
17
+ };
18
+ type Snapshot = {
19
+ friendlyId: string;
20
+ };
21
+ export declare class DevRunController {
22
+ private readonly opts;
23
+ private taskRunProcess?;
24
+ private readonly worker;
25
+ private readonly httpClient;
26
+ private readonly runHeartbeat;
27
+ private readonly heartbeatIntervalSeconds;
28
+ private readonly snapshotPoller;
29
+ private readonly snapshotPollIntervalSeconds;
30
+ private state;
31
+ private enterRunPhase;
32
+ constructor(opts: DevRunControllerOptions);
33
+ private sigterm;
34
+ private updateRunPhase;
35
+ private onExitRunPhase;
36
+ private subscribeToRunNotifications;
37
+ private unsubscribeFromRunNotifications;
38
+ private get runFriendlyId();
39
+ private get snapshotFriendlyId();
40
+ get workerFriendlyId(): string;
41
+ private handleSnapshotChangeLock;
42
+ private handleSnapshotChange;
43
+ private startAndExecuteRunAttempt;
44
+ private executeRun;
45
+ private handleCompletionResult;
46
+ private runFinished;
47
+ private cancelAttempt;
48
+ start(dequeueMessage: DequeuedMessage): Promise<void>;
49
+ stop(): Promise<void>;
50
+ getLatestSnapshot(): Promise<void>;
51
+ resubscribeToRunNotifications(): void;
52
+ }
53
+ export {};
@@ -0,0 +1,615 @@
1
+ import { HeartbeatService, } from "@trigger.dev/core/v3";
2
+ import { setTimeout as sleep } from "timers/promises";
3
+ import { TaskRunProcess } from "../executions/taskRunProcess.js";
4
+ import { assertExhaustive } from "../utilities/assertExhaustive.js";
5
+ import { logger } from "../utilities/logger.js";
6
+ import { sanitizeEnvVars } from "../utilities/sanitizeEnvVars.js";
7
+ import { join } from "node:path";
8
+ import { eventBus } from "../utilities/eventBus.js";
9
+ export class DevRunController {
10
+ opts;
11
+ taskRunProcess;
12
+ worker;
13
+ httpClient;
14
+ runHeartbeat;
15
+ heartbeatIntervalSeconds;
16
+ snapshotPoller;
17
+ snapshotPollIntervalSeconds;
18
+ state = { phase: "IDLE" };
19
+ enterRunPhase(run, snapshot) {
20
+ this.onExitRunPhase(run);
21
+ this.state = { phase: "RUN", run, snapshot };
22
+ this.runHeartbeat.start();
23
+ this.snapshotPoller.start();
24
+ }
25
+ constructor(opts) {
26
+ this.opts = opts;
27
+ logger.debug("[DevRunController] Creating controller", {
28
+ run: opts.runFriendlyId,
29
+ });
30
+ this.worker = opts.worker;
31
+ this.heartbeatIntervalSeconds = opts.heartbeatIntervalSeconds || 20;
32
+ this.snapshotPollIntervalSeconds = 5;
33
+ this.httpClient = opts.httpClient;
34
+ this.snapshotPoller = new HeartbeatService({
35
+ heartbeat: async () => {
36
+ if (!this.runFriendlyId) {
37
+ logger.debug("[DevRunController] Skipping snapshot poll, no run ID");
38
+ return;
39
+ }
40
+ logger.debug("[DevRunController] Polling for latest snapshot");
41
+ this.httpClient.dev.sendDebugLog(this.runFriendlyId, {
42
+ time: new Date(),
43
+ message: `snapshot poll: started`,
44
+ properties: {
45
+ snapshotId: this.snapshotFriendlyId,
46
+ },
47
+ });
48
+ const response = await this.httpClient.dev.getRunExecutionData(this.runFriendlyId);
49
+ if (!response.success) {
50
+ logger.debug("[DevRunController] Snapshot poll failed", { error: response.error });
51
+ this.httpClient.dev.sendDebugLog(this.runFriendlyId, {
52
+ time: new Date(),
53
+ message: `snapshot poll: failed`,
54
+ properties: {
55
+ snapshotId: this.snapshotFriendlyId,
56
+ error: response.error,
57
+ },
58
+ });
59
+ return;
60
+ }
61
+ await this.handleSnapshotChange(response.data.execution);
62
+ },
63
+ intervalMs: this.snapshotPollIntervalSeconds * 1000,
64
+ leadingEdge: false,
65
+ onError: async (error) => {
66
+ logger.debug("[DevRunController] Failed to poll for snapshot", { error });
67
+ },
68
+ });
69
+ this.runHeartbeat = new HeartbeatService({
70
+ heartbeat: async () => {
71
+ if (!this.runFriendlyId || !this.snapshotFriendlyId) {
72
+ logger.debug("[DevRunController] Skipping heartbeat, no run ID or snapshot ID");
73
+ return;
74
+ }
75
+ logger.debug("[DevRunController] Sending heartbeat");
76
+ const response = await this.httpClient.dev.heartbeatRun(this.runFriendlyId, this.snapshotFriendlyId, {
77
+ cpu: 0,
78
+ memory: 0,
79
+ });
80
+ if (!response.success) {
81
+ logger.debug("[DevRunController] Heartbeat failed", { error: response.error });
82
+ }
83
+ },
84
+ intervalMs: this.heartbeatIntervalSeconds * 1000,
85
+ leadingEdge: false,
86
+ onError: async (error) => {
87
+ logger.debug("[DevRunController] Failed to send heartbeat", { error });
88
+ },
89
+ });
90
+ process.on("SIGTERM", this.sigterm);
91
+ }
92
+ async sigterm() {
93
+ logger.debug("[DevRunController] Received SIGTERM, stopping worker");
94
+ await this.stop();
95
+ }
96
+ // This should only be used when we're already executing a run. Attempt number changes are not allowed.
97
+ updateRunPhase(run, snapshot) {
98
+ if (this.state.phase !== "RUN") {
99
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
100
+ time: new Date(),
101
+ message: `updateRunPhase: Invalid phase for updating snapshot: ${this.state.phase}`,
102
+ properties: {
103
+ currentPhase: this.state.phase,
104
+ snapshotId: snapshot.friendlyId,
105
+ },
106
+ });
107
+ throw new Error(`Invalid phase for updating snapshot: ${this.state.phase}`);
108
+ }
109
+ if (this.state.run.friendlyId !== run.friendlyId) {
110
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
111
+ time: new Date(),
112
+ message: `updateRunPhase: Mismatched run IDs`,
113
+ properties: {
114
+ currentRunId: this.state.run.friendlyId,
115
+ newRunId: run.friendlyId,
116
+ currentSnapshotId: this.state.snapshot.friendlyId,
117
+ newSnapshotId: snapshot.friendlyId,
118
+ },
119
+ });
120
+ throw new Error("Mismatched run IDs");
121
+ }
122
+ if (this.state.snapshot.friendlyId === snapshot.friendlyId) {
123
+ logger.debug("updateRunPhase: Snapshot not changed", { run, snapshot });
124
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
125
+ time: new Date(),
126
+ message: `updateRunPhase: Snapshot not changed`,
127
+ properties: {
128
+ snapshotId: snapshot.friendlyId,
129
+ },
130
+ });
131
+ return;
132
+ }
133
+ if (this.state.run.attemptNumber !== run.attemptNumber) {
134
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
135
+ time: new Date(),
136
+ message: `updateRunPhase: Attempt number changed`,
137
+ properties: {
138
+ oldAttemptNumber: this.state.run.attemptNumber ?? undefined,
139
+ newAttemptNumber: run.attemptNumber ?? undefined,
140
+ },
141
+ });
142
+ throw new Error("Attempt number changed");
143
+ }
144
+ this.state = {
145
+ phase: "RUN",
146
+ run: {
147
+ friendlyId: run.friendlyId,
148
+ attemptNumber: run.attemptNumber,
149
+ },
150
+ snapshot: {
151
+ friendlyId: snapshot.friendlyId,
152
+ },
153
+ };
154
+ }
155
+ onExitRunPhase(newRun = undefined) {
156
+ // We're not in a run phase, nothing to do
157
+ if (this.state.phase !== "RUN") {
158
+ logger.debug("onExitRunPhase: Not in run phase, skipping", { phase: this.state.phase });
159
+ return;
160
+ }
161
+ // This is still the same run, so we're not exiting the phase
162
+ if (newRun?.friendlyId === this.state.run.friendlyId) {
163
+ logger.debug("onExitRunPhase: Same run, skipping", { newRun });
164
+ return;
165
+ }
166
+ logger.debug("onExitRunPhase: Exiting run phase", { newRun });
167
+ this.runHeartbeat.stop();
168
+ this.snapshotPoller.stop();
169
+ const { run, snapshot } = this.state;
170
+ this.unsubscribeFromRunNotifications({ run, snapshot });
171
+ }
172
+ subscribeToRunNotifications({ run, snapshot }) {
173
+ logger.debug("[DevRunController] Subscribing to run notifications", { run, snapshot });
174
+ this.opts.onSubscribeToRunNotifications(run, snapshot);
175
+ }
176
+ unsubscribeFromRunNotifications({ run, snapshot }) {
177
+ logger.debug("[DevRunController] Unsubscribing from run notifications", { run, snapshot });
178
+ this.opts.onUnsubscribeFromRunNotifications(run, snapshot);
179
+ }
180
+ get runFriendlyId() {
181
+ if (this.state.phase !== "RUN") {
182
+ return undefined;
183
+ }
184
+ return this.state.run.friendlyId;
185
+ }
186
+ get snapshotFriendlyId() {
187
+ if (this.state.phase !== "RUN") {
188
+ return;
189
+ }
190
+ return this.state.snapshot.friendlyId;
191
+ }
192
+ get workerFriendlyId() {
193
+ if (!this.opts.worker.serverWorker) {
194
+ throw new Error("No version for dev worker");
195
+ }
196
+ return this.opts.worker.serverWorker.id;
197
+ }
198
+ handleSnapshotChangeLock = false;
199
+ async handleSnapshotChange({ run, snapshot, completedWaitpoints, }) {
200
+ if (this.handleSnapshotChangeLock) {
201
+ logger.debug("handleSnapshotChange: already in progress");
202
+ return;
203
+ }
204
+ this.handleSnapshotChangeLock = true;
205
+ // Reset the (fallback) snapshot poll interval so we don't do unnecessary work
206
+ this.snapshotPoller.resetCurrentInterval();
207
+ try {
208
+ if (!this.snapshotFriendlyId) {
209
+ logger.debug("handleSnapshotChange: Missing snapshot ID", {
210
+ runId: run.friendlyId,
211
+ snapshotId: this.snapshotFriendlyId,
212
+ });
213
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
214
+ time: new Date(),
215
+ message: `snapshot change: missing snapshot ID`,
216
+ properties: {
217
+ newSnapshotId: snapshot.friendlyId,
218
+ newSnapshotStatus: snapshot.executionStatus,
219
+ },
220
+ });
221
+ return;
222
+ }
223
+ if (this.snapshotFriendlyId === snapshot.friendlyId) {
224
+ logger.debug("handleSnapshotChange: snapshot not changed, skipping", { snapshot });
225
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
226
+ time: new Date(),
227
+ message: `snapshot change: skipping, no change`,
228
+ properties: {
229
+ snapshotId: this.snapshotFriendlyId,
230
+ snapshotStatus: snapshot.executionStatus,
231
+ },
232
+ });
233
+ return;
234
+ }
235
+ logger.debug(`handleSnapshotChange: ${snapshot.executionStatus}`, {
236
+ run,
237
+ oldSnapshotId: this.snapshotFriendlyId,
238
+ newSnapshot: snapshot,
239
+ completedWaitpoints: completedWaitpoints.length,
240
+ });
241
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
242
+ time: new Date(),
243
+ message: `snapshot change: ${snapshot.executionStatus}`,
244
+ properties: {
245
+ oldSnapshotId: this.snapshotFriendlyId,
246
+ newSnapshotId: snapshot.friendlyId,
247
+ completedWaitpoints: completedWaitpoints.length,
248
+ },
249
+ });
250
+ try {
251
+ this.updateRunPhase(run, snapshot);
252
+ }
253
+ catch (error) {
254
+ logger.debug("handleSnapshotChange: failed to update run phase", {
255
+ run,
256
+ snapshot,
257
+ error,
258
+ });
259
+ this.runFinished();
260
+ return;
261
+ }
262
+ switch (snapshot.executionStatus) {
263
+ case "PENDING_CANCEL": {
264
+ try {
265
+ await this.cancelAttempt();
266
+ }
267
+ catch (error) {
268
+ logger.debug("Failed to cancel attempt, killing task run process", {
269
+ error,
270
+ });
271
+ try {
272
+ await this.taskRunProcess?.kill("SIGKILL");
273
+ }
274
+ catch (error) {
275
+ logger.debug("Failed to cancel attempt, failed to kill task run process", { error });
276
+ }
277
+ return;
278
+ }
279
+ return;
280
+ }
281
+ case "FINISHED": {
282
+ logger.debug("Run is finished, nothing to do");
283
+ return;
284
+ }
285
+ case "EXECUTING_WITH_WAITPOINTS": {
286
+ logger.debug("Run is executing with waitpoints", { snapshot });
287
+ try {
288
+ await this.taskRunProcess?.cleanup(false);
289
+ }
290
+ catch (error) {
291
+ logger.debug("Failed to cleanup task run process", { error });
292
+ }
293
+ if (snapshot.friendlyId !== this.snapshotFriendlyId) {
294
+ logger.debug("Snapshot changed after cleanup, abort", {
295
+ oldSnapshotId: snapshot.friendlyId,
296
+ newSnapshotId: this.snapshotFriendlyId,
297
+ });
298
+ return;
299
+ }
300
+ //no snapshots in DEV, so we just return.
301
+ return;
302
+ }
303
+ case "SUSPENDED": {
304
+ logger.debug("Run shouldn't be suspended in DEV", {
305
+ run,
306
+ snapshot,
307
+ });
308
+ return;
309
+ }
310
+ case "PENDING_EXECUTING": {
311
+ logger.debug("Run is pending execution", { run, snapshot });
312
+ if (completedWaitpoints.length === 0) {
313
+ logger.log("No waitpoints to complete, nothing to do");
314
+ return;
315
+ }
316
+ logger.debug("Run shouldn't be PENDING_EXECUTING with completedWaitpoints in DEV", {
317
+ run,
318
+ snapshot,
319
+ });
320
+ return;
321
+ }
322
+ case "EXECUTING": {
323
+ logger.debug("Run is now executing", { run, snapshot });
324
+ if (completedWaitpoints.length === 0) {
325
+ return;
326
+ }
327
+ logger.debug("Processing completed waitpoints", { completedWaitpoints });
328
+ if (!this.taskRunProcess) {
329
+ logger.debug("No task run process, ignoring completed waitpoints", {
330
+ completedWaitpoints,
331
+ });
332
+ return;
333
+ }
334
+ for (const waitpoint of completedWaitpoints) {
335
+ this.taskRunProcess.waitpointCompleted(waitpoint);
336
+ }
337
+ return;
338
+ }
339
+ case "RUN_CREATED":
340
+ case "QUEUED_EXECUTING":
341
+ case "QUEUED": {
342
+ logger.debug("Status change not handled", { status: snapshot.executionStatus });
343
+ return;
344
+ }
345
+ default: {
346
+ assertExhaustive(snapshot.executionStatus);
347
+ }
348
+ }
349
+ }
350
+ catch (error) {
351
+ logger.debug("handleSnapshotChange: unexpected error", { error });
352
+ this.httpClient.dev.sendDebugLog(run.friendlyId, {
353
+ time: new Date(),
354
+ message: `snapshot change: unexpected error`,
355
+ properties: {
356
+ snapshotId: snapshot.friendlyId,
357
+ error: error instanceof Error ? error.message : String(error),
358
+ },
359
+ });
360
+ }
361
+ finally {
362
+ this.handleSnapshotChangeLock = false;
363
+ }
364
+ }
365
+ async startAndExecuteRunAttempt({ runFriendlyId, snapshotFriendlyId, dequeuedAt, isWarmStart = false, }) {
366
+ this.subscribeToRunNotifications({
367
+ run: { friendlyId: runFriendlyId },
368
+ snapshot: { friendlyId: snapshotFriendlyId },
369
+ });
370
+ const attemptStartedAt = Date.now();
371
+ const start = await this.httpClient.dev.startRunAttempt(runFriendlyId, snapshotFriendlyId);
372
+ if (!start.success) {
373
+ logger.debug("[DevRunController] Failed to start run", { error: start.error });
374
+ this.runFinished();
375
+ return;
376
+ }
377
+ const attemptDuration = Date.now() - attemptStartedAt;
378
+ const { run, snapshot, execution, envVars } = start.data;
379
+ eventBus.emit("runStarted", this.opts.worker, execution);
380
+ logger.debug("[DevRunController] Started run", {
381
+ runId: run.friendlyId,
382
+ snapshot: snapshot.friendlyId,
383
+ });
384
+ this.enterRunPhase(run, snapshot);
385
+ const metrics = [
386
+ {
387
+ name: "start",
388
+ event: "create_attempt",
389
+ timestamp: attemptStartedAt,
390
+ duration: attemptDuration,
391
+ },
392
+ ].concat(dequeuedAt
393
+ ? [
394
+ {
395
+ name: "start",
396
+ event: "dequeue",
397
+ timestamp: dequeuedAt.getTime(),
398
+ duration: 0,
399
+ },
400
+ ]
401
+ : []);
402
+ try {
403
+ return await this.executeRun({ run, snapshot, execution, envVars, metrics });
404
+ }
405
+ catch (error) {
406
+ logger.debug("Error while executing attempt", {
407
+ error,
408
+ });
409
+ logger.debug("Submitting attempt completion", {
410
+ runId: run.friendlyId,
411
+ snapshotId: snapshot.friendlyId,
412
+ updatedSnapshotId: this.snapshotFriendlyId,
413
+ });
414
+ const completion = {
415
+ id: execution.run.id,
416
+ ok: false,
417
+ retry: undefined,
418
+ error: TaskRunProcess.parseExecuteError(error),
419
+ };
420
+ const completionResult = await this.httpClient.dev.completeRunAttempt(run.friendlyId, this.snapshotFriendlyId ?? snapshot.friendlyId, { completion });
421
+ if (!completionResult.success) {
422
+ logger.debug("Failed to submit completion after error", {
423
+ error: completionResult.error,
424
+ });
425
+ this.runFinished();
426
+ return;
427
+ }
428
+ logger.debug("Attempt completion submitted after error", completionResult.data.result);
429
+ try {
430
+ await this.handleCompletionResult(completion, completionResult.data.result, execution);
431
+ }
432
+ catch (error) {
433
+ logger.debug("Failed to handle completion result after error", { error });
434
+ this.runFinished();
435
+ return;
436
+ }
437
+ }
438
+ }
439
+ async executeRun({ run, snapshot, execution, envVars, metrics, }) {
440
+ if (!this.opts.worker.serverWorker) {
441
+ throw new Error(`No server worker for Dev ${run.friendlyId}`);
442
+ }
443
+ if (!this.opts.worker.manifest) {
444
+ throw new Error(`No worker manifest for Dev ${run.friendlyId}`);
445
+ }
446
+ this.snapshotPoller.start();
447
+ this.taskRunProcess = new TaskRunProcess({
448
+ workerManifest: this.opts.worker.manifest,
449
+ env: {
450
+ ...sanitizeEnvVars(envVars ?? {}),
451
+ ...sanitizeEnvVars(this.opts.worker.params.env),
452
+ TRIGGER_WORKER_MANIFEST_PATH: join(this.opts.worker.build.outputPath, "index.json"),
453
+ RUN_WORKER_SHOW_LOGS: this.opts.logLevel === "debug" ? "true" : "false",
454
+ },
455
+ serverWorker: {
456
+ id: "unmanaged",
457
+ contentHash: this.opts.worker.build.contentHash,
458
+ version: this.opts.worker.serverWorker?.version,
459
+ engine: "V2",
460
+ },
461
+ machine: execution.machine,
462
+ }).initialize();
463
+ logger.debug("executing task run process", {
464
+ attemptId: execution.attempt.id,
465
+ runId: execution.run.id,
466
+ });
467
+ const completion = await this.taskRunProcess.execute({
468
+ payload: {
469
+ execution,
470
+ traceContext: execution.run.traceContext ?? {},
471
+ metrics,
472
+ },
473
+ messageId: run.friendlyId,
474
+ });
475
+ logger.debug("Completed run", completion);
476
+ try {
477
+ await this.taskRunProcess.cleanup(true);
478
+ this.taskRunProcess = undefined;
479
+ }
480
+ catch (error) {
481
+ logger.debug("Failed to cleanup task run process, submitting completion anyway", {
482
+ error,
483
+ });
484
+ }
485
+ if (!this.runFriendlyId || !this.snapshotFriendlyId) {
486
+ logger.debug("executeRun: Missing run ID or snapshot ID after execution", {
487
+ runId: this.runFriendlyId,
488
+ snapshotId: this.snapshotFriendlyId,
489
+ });
490
+ this.runFinished();
491
+ return;
492
+ }
493
+ const completionResult = await this.httpClient.dev.completeRunAttempt(this.runFriendlyId, this.snapshotFriendlyId, {
494
+ completion,
495
+ });
496
+ if (!completionResult.success) {
497
+ logger.debug("Failed to submit completion", {
498
+ error: completionResult.error,
499
+ });
500
+ this.runFinished();
501
+ return;
502
+ }
503
+ logger.debug("Attempt completion submitted", completionResult.data.result);
504
+ try {
505
+ await this.handleCompletionResult(completion, completionResult.data.result, execution);
506
+ }
507
+ catch (error) {
508
+ logger.debug("Failed to handle completion result", { error });
509
+ this.runFinished();
510
+ return;
511
+ }
512
+ }
513
+ async handleCompletionResult(completion, result, execution) {
514
+ logger.debug("[DevRunController] Handling completion result", { completion, result });
515
+ const { attemptStatus, snapshot: completionSnapshot, run } = result;
516
+ try {
517
+ this.updateRunPhase(run, completionSnapshot);
518
+ }
519
+ catch (error) {
520
+ logger.debug("Failed to update run phase after completion", { error });
521
+ this.runFinished();
522
+ return;
523
+ }
524
+ if (attemptStatus === "RETRY_QUEUED") {
525
+ logger.debug("Retry queued");
526
+ this.runFinished();
527
+ return;
528
+ }
529
+ if (attemptStatus === "RETRY_IMMEDIATELY") {
530
+ if (completion.ok) {
531
+ throw new Error("Should retry but completion OK.");
532
+ }
533
+ if (!completion.retry) {
534
+ throw new Error("Should retry but missing retry params.");
535
+ }
536
+ await sleep(completion.retry.delay);
537
+ if (!this.snapshotFriendlyId) {
538
+ throw new Error("Missing snapshot ID after retry");
539
+ }
540
+ this.startAndExecuteRunAttempt({
541
+ runFriendlyId: run.friendlyId,
542
+ snapshotFriendlyId: this.snapshotFriendlyId,
543
+ }).finally(() => { });
544
+ return;
545
+ }
546
+ if (attemptStatus === "RUN_PENDING_CANCEL") {
547
+ logger.debug("Run pending cancel");
548
+ return;
549
+ }
550
+ eventBus.emit("runCompleted", this.opts.worker, execution, completion, completion.usage?.durationMs ?? 0);
551
+ if (attemptStatus === "RUN_FINISHED") {
552
+ logger.debug("Run finished");
553
+ this.runFinished();
554
+ return;
555
+ }
556
+ assertExhaustive(attemptStatus);
557
+ }
558
+ async runFinished() {
559
+ // Kill the run process
560
+ try {
561
+ await this.taskRunProcess?.kill("SIGKILL");
562
+ }
563
+ catch (error) {
564
+ logger.debug("Failed to kill task run process", { error });
565
+ }
566
+ this.runHeartbeat.stop();
567
+ this.snapshotPoller.stop();
568
+ this.opts.onFinished();
569
+ }
570
+ async cancelAttempt() {
571
+ logger.debug("Cancelling attempt", { runId: this.runFriendlyId });
572
+ await this.taskRunProcess?.cancel();
573
+ }
574
+ async start(dequeueMessage) {
575
+ logger.debug("[DevRunController] Starting up");
576
+ await this.startAndExecuteRunAttempt({
577
+ runFriendlyId: dequeueMessage.run.friendlyId,
578
+ snapshotFriendlyId: dequeueMessage.snapshot.friendlyId,
579
+ dequeuedAt: dequeueMessage.dequeuedAt,
580
+ }).finally(async () => { });
581
+ }
582
+ async stop() {
583
+ logger.debug("[DevRunController] Shutting down");
584
+ process.off("SIGTERM", this.sigterm);
585
+ if (this.taskRunProcess && !this.taskRunProcess.isBeingKilled) {
586
+ try {
587
+ await this.taskRunProcess.cleanup(true);
588
+ }
589
+ catch (error) {
590
+ logger.debug("Failed to cleanup task run process", { error });
591
+ }
592
+ }
593
+ this.runHeartbeat.stop();
594
+ this.snapshotPoller.stop();
595
+ }
596
+ async getLatestSnapshot() {
597
+ if (!this.runFriendlyId) {
598
+ return;
599
+ }
600
+ logger.debug("[DevRunController] Received notification, manually getting the latest snapshot.");
601
+ const response = await this.httpClient.dev.getRunExecutionData(this.runFriendlyId);
602
+ if (!response.success) {
603
+ logger.debug("Failed to get latest snapshot", { error: response.error });
604
+ return;
605
+ }
606
+ await this.handleSnapshotChange(response.data.execution);
607
+ }
608
+ resubscribeToRunNotifications() {
609
+ if (this.state.phase !== "RUN") {
610
+ return;
611
+ }
612
+ this.subscribeToRunNotifications(this.state);
613
+ }
614
+ }
615
+ //# sourceMappingURL=dev-run-controller.js.map