@shipfox/api-runners 12.3.0 → 12.4.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 (134) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +22 -0
  3. package/dist/config.d.ts +1 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +9 -2
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/demand.d.ts +4 -2
  8. package/dist/core/demand.d.ts.map +1 -1
  9. package/dist/core/demand.js +22 -6
  10. package/dist/core/demand.js.map +1 -1
  11. package/dist/core/entities/reservation.d.ts +1 -0
  12. package/dist/core/entities/reservation.d.ts.map +1 -1
  13. package/dist/core/entities/reservation.js.map +1 -1
  14. package/dist/core/entities/runner-instance.d.ts +3 -0
  15. package/dist/core/entities/runner-instance.d.ts.map +1 -1
  16. package/dist/core/entities/runner-instance.js.map +1 -1
  17. package/dist/core/runner-activation.d.ts +4 -1
  18. package/dist/core/runner-activation.d.ts.map +1 -1
  19. package/dist/core/runner-activation.js +32 -3
  20. package/dist/core/runner-activation.js.map +1 -1
  21. package/dist/core/runner-control-sessions.d.ts +8 -4
  22. package/dist/core/runner-control-sessions.d.ts.map +1 -1
  23. package/dist/core/runner-control-sessions.js +55 -9
  24. package/dist/core/runner-control-sessions.js.map +1 -1
  25. package/dist/core/runner-instances.d.ts +1 -0
  26. package/dist/core/runner-instances.d.ts.map +1 -1
  27. package/dist/core/runner-instances.js +1 -0
  28. package/dist/core/runner-instances.js.map +1 -1
  29. package/dist/db/db.d.ts +68 -0
  30. package/dist/db/db.d.ts.map +1 -1
  31. package/dist/db/ephemeral-registration-tokens.d.ts.map +1 -1
  32. package/dist/db/ephemeral-registration-tokens.js +1 -1
  33. package/dist/db/ephemeral-registration-tokens.js.map +1 -1
  34. package/dist/db/reservation-locks.d.ts +6 -0
  35. package/dist/db/reservation-locks.d.ts.map +1 -0
  36. package/dist/db/reservation-locks.js +15 -0
  37. package/dist/db/reservation-locks.js.map +1 -0
  38. package/dist/db/reservations.d.ts +17 -3
  39. package/dist/db/reservations.d.ts.map +1 -1
  40. package/dist/db/reservations.js +246 -51
  41. package/dist/db/reservations.js.map +1 -1
  42. package/dist/db/runner-assignments.d.ts +17 -0
  43. package/dist/db/runner-assignments.d.ts.map +1 -1
  44. package/dist/db/runner-assignments.js +103 -8
  45. package/dist/db/runner-assignments.js.map +1 -1
  46. package/dist/db/runner-instances.d.ts +2 -2
  47. package/dist/db/runner-instances.d.ts.map +1 -1
  48. package/dist/db/runner-instances.js +124 -24
  49. package/dist/db/runner-instances.js.map +1 -1
  50. package/dist/db/runner-states.d.ts +2 -0
  51. package/dist/db/runner-states.d.ts.map +1 -0
  52. package/dist/db/runner-states.js +7 -0
  53. package/dist/db/runner-states.js.map +1 -0
  54. package/dist/db/schema/reservations.d.ts +18 -0
  55. package/dist/db/schema/reservations.d.ts.map +1 -1
  56. package/dist/db/schema/reservations.js +7 -1
  57. package/dist/db/schema/reservations.js.map +1 -1
  58. package/dist/db/schema/runner-instances.d.ts +18 -0
  59. package/dist/db/schema/runner-instances.d.ts.map +1 -1
  60. package/dist/db/schema/runner-instances.js +8 -0
  61. package/dist/db/schema/runner-instances.js.map +1 -1
  62. package/dist/metrics/index.d.ts +2 -2
  63. package/dist/metrics/index.d.ts.map +1 -1
  64. package/dist/metrics/index.js +1 -1
  65. package/dist/metrics/index.js.map +1 -1
  66. package/dist/metrics/instance.d.ts +24 -2
  67. package/dist/metrics/instance.d.ts.map +1 -1
  68. package/dist/metrics/instance.js +23 -0
  69. package/dist/metrics/instance.js.map +1 -1
  70. package/dist/presentation/dto/poll-demand.d.ts.map +1 -1
  71. package/dist/presentation/dto/poll-demand.js +3 -0
  72. package/dist/presentation/dto/poll-demand.js.map +1 -1
  73. package/dist/presentation/dto/runner-instances.d.ts +4 -1
  74. package/dist/presentation/dto/runner-instances.d.ts.map +1 -1
  75. package/dist/presentation/dto/runner-instances.js +4 -1
  76. package/dist/presentation/dto/runner-instances.js.map +1 -1
  77. package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
  78. package/dist/presentation/routes/assign-runner-instances.js +3 -3
  79. package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
  80. package/dist/presentation/routes/poll-demand.d.ts.map +1 -1
  81. package/dist/presentation/routes/poll-demand.js +1 -0
  82. package/dist/presentation/routes/poll-demand.js.map +1 -1
  83. package/dist/presentation/routes/reconcile-runner-instances.d.ts.map +1 -1
  84. package/dist/presentation/routes/reconcile-runner-instances.js +4 -2
  85. package/dist/presentation/routes/reconcile-runner-instances.js.map +1 -1
  86. package/dist/presentation/routes/runner-enrollment.d.ts +4 -1
  87. package/dist/presentation/routes/runner-enrollment.d.ts.map +1 -1
  88. package/dist/presentation/routes/runner-enrollment.js +11 -6
  89. package/dist/presentation/routes/runner-enrollment.js.map +1 -1
  90. package/dist/tsconfig.test.tsbuildinfo +1 -1
  91. package/drizzle/0005_previous_lenny_balinger.sql +5 -0
  92. package/drizzle/meta/0005_snapshot.json +2220 -0
  93. package/drizzle/meta/_journal.json +7 -0
  94. package/package.json +2 -2
  95. package/src/config.test.ts +22 -0
  96. package/src/config.ts +15 -2
  97. package/src/core/demand.test.ts +192 -3
  98. package/src/core/demand.ts +44 -4
  99. package/src/core/entities/reservation.ts +1 -0
  100. package/src/core/entities/runner-instance.ts +4 -0
  101. package/src/core/runner-activation.ts +48 -2
  102. package/src/core/runner-control-sessions.test.ts +64 -3
  103. package/src/core/runner-control-sessions.ts +62 -9
  104. package/src/core/runner-instances.test.ts +19 -0
  105. package/src/core/runner-instances.ts +2 -0
  106. package/src/core/runner-sessions.test.ts +121 -1
  107. package/src/db/ephemeral-registration-tokens.test.ts +20 -1
  108. package/src/db/ephemeral-registration-tokens.ts +1 -0
  109. package/src/db/reservation-locks.ts +22 -0
  110. package/src/db/reservations.test.ts +733 -22
  111. package/src/db/reservations.ts +408 -144
  112. package/src/db/runner-assignments.test.ts +105 -1
  113. package/src/db/runner-assignments.ts +178 -9
  114. package/src/db/runner-instances.test.ts +265 -2
  115. package/src/db/runner-instances.ts +215 -55
  116. package/src/db/runner-states.ts +7 -0
  117. package/src/db/schema/reservations.ts +5 -1
  118. package/src/db/schema/runner-instances.ts +9 -0
  119. package/src/metrics/index.ts +10 -1
  120. package/src/metrics/instance.ts +59 -2
  121. package/src/metrics/service.test.ts +8 -1
  122. package/src/presentation/dto/poll-demand.ts +3 -0
  123. package/src/presentation/dto/runner-instances.ts +8 -1
  124. package/src/presentation/routes/assign-runner-instances.ts +6 -2
  125. package/src/presentation/routes/late-runner-enrollment.test.ts +219 -2
  126. package/src/presentation/routes/poll-demand.test.ts +56 -0
  127. package/src/presentation/routes/poll-demand.ts +1 -0
  128. package/src/presentation/routes/reconcile-runner-instances.test.ts +34 -2
  129. package/src/presentation/routes/reconcile-runner-instances.ts +7 -1
  130. package/src/presentation/routes/runner-enrollment.test.ts +316 -3
  131. package/src/presentation/routes/runner-enrollment.ts +8 -5
  132. package/test/factories/reservation.ts +2 -0
  133. package/test/factories/runner-instance.ts +6 -0
  134. package/tsconfig.build.tsbuildinfo +1 -1
@@ -146,6 +146,28 @@ describe('reportRunnerInstances', () => {
146
146
  expect(rows[0]?.state).toBe('running');
147
147
  });
148
148
 
149
+ it('preserves launch kind when a provider report updates the runner row', async () => {
150
+ await providerRunnerFactory.create({
151
+ workspaceId,
152
+ provisionerId,
153
+ providerRunnerId: 'demand-runner',
154
+ launchKind: 'demand',
155
+ });
156
+
157
+ await reportRunnerInstances({
158
+ scope: 'workspace',
159
+ workspaceId,
160
+ provisionerId,
161
+ events: [event({providerRunnerId: 'demand-runner', state: 'running'})],
162
+ });
163
+
164
+ const [row] = await db()
165
+ .select({launchKind: providerRunners.launchKind})
166
+ .from(providerRunners)
167
+ .where(eq(providerRunners.providerRunnerId, 'demand-runner'));
168
+ expect(row?.launchKind).toBe('demand');
169
+ });
170
+
149
171
  it('uses state progression to dedupe equal-timestamp provisioned runner reports', async () => {
150
172
  const reportedAt = new Date();
151
173
 
@@ -366,6 +388,58 @@ describe('reportRunnerInstances', () => {
366
388
  expect(rows[0]?.reservationId).toBe(reservationId);
367
389
  });
368
390
 
391
+ it('does not adopt a bound reservation id from an unclaimed report', async () => {
392
+ const reservationId = await createReservation(1, {kind: 'bound'});
393
+ await db()
394
+ .insert(providerRunners)
395
+ .values({
396
+ workspaceId,
397
+ provisionerId,
398
+ providerRunnerId: 'bound-reservation-runner',
399
+ state: 'starting',
400
+ reportedAt: new Date('2025-01-01T00:00:00.000Z'),
401
+ });
402
+
403
+ await reportRunnerInstances({
404
+ scope: 'workspace',
405
+ workspaceId,
406
+ provisionerId,
407
+ events: [
408
+ event({
409
+ providerRunnerId: 'bound-reservation-runner',
410
+ reservationId,
411
+ state: 'running',
412
+ reportedAt: new Date('2025-01-01T00:01:00.000Z'),
413
+ }),
414
+ ],
415
+ });
416
+
417
+ const rows = await providerRunnerRowsFor({workspaceId, provisionerId});
418
+ expect(rows[0]?.reservationId).toBeNull();
419
+ });
420
+
421
+ it('does not let reports exceed reservation capacity when creating projection rows', async () => {
422
+ const reservationId = await createReservation(1);
423
+
424
+ await reportRunnerInstances({
425
+ scope: 'workspace',
426
+ workspaceId,
427
+ provisionerId,
428
+ events: [
429
+ event({providerRunnerId: 'provisioned-runner-1', reservationId}),
430
+ event({providerRunnerId: 'provisioned-runner-2', reservationId}),
431
+ ],
432
+ });
433
+
434
+ const rows = await providerRunnerRowsFor({workspaceId, provisionerId});
435
+ expect(rows.find((row) => row.providerRunnerId === 'provisioned-runner-1')?.reservationId).toBe(
436
+ reservationId,
437
+ );
438
+ expect(
439
+ rows.find((row) => row.providerRunnerId === 'provisioned-runner-2')?.reservationId,
440
+ ).toBeNull();
441
+ });
442
+
369
443
  it('does not let equal-timestamp lower-priority reports flip terminal state', async () => {
370
444
  const reservationId = await createReservation(1);
371
445
  const reportedAt = new Date('2025-01-01T00:00:00.000Z');
@@ -554,8 +628,15 @@ describe('reportRunnerInstances', () => {
554
628
  expect(active).toEqual([]);
555
629
  });
556
630
 
557
- it('releases one reservation unit for a terminal unclaimed provisioned runner', async () => {
631
+ it('releases one reservation unit for a terminal runner with an intended reservation', async () => {
558
632
  const reservationId = await createReservation(2);
633
+ await providerRunnerFactory.create({
634
+ workspaceId,
635
+ provisionerId,
636
+ providerRunnerId: 'provisioned-runner-1',
637
+ intendedReservationId: reservationId,
638
+ state: 'running',
639
+ });
559
640
 
560
641
  const result = await reportRunnerInstances({
561
642
  scope: 'workspace',
@@ -571,6 +652,30 @@ describe('reportRunnerInstances', () => {
571
652
  expect(providerRunnerRows[0]?.reservationReleasedAt).toBeInstanceOf(Date);
572
653
  });
573
654
 
655
+ it('does not let an unrecognized terminal report consume reservation capacity', async () => {
656
+ const reservationId = await createReservation(1);
657
+
658
+ const result = await reportRunnerInstances({
659
+ scope: 'workspace',
660
+ workspaceId,
661
+ provisionerId,
662
+ events: [event({providerRunnerId: 'unrecognized-runner', reservationId, state: 'failed'})],
663
+ });
664
+
665
+ const reservationRows = await reservationRowsFor({workspaceId, provisionerId});
666
+ const providerRunnerRows = await providerRunnerRowsFor({workspaceId, provisionerId});
667
+ expect(result).toEqual({accepted: 1, reservationsReleased: 0, terminateIntentsHonored: []});
668
+ expect(reservationRows[0]?.count).toBe(1);
669
+ expect(providerRunnerRows).toHaveLength(1);
670
+ expect(providerRunnerRows[0]).toMatchObject({
671
+ providerRunnerId: 'unrecognized-runner',
672
+ reservationId: null,
673
+ intendedReservationId: null,
674
+ reservationReleasedAt: null,
675
+ state: 'failed',
676
+ });
677
+ });
678
+
574
679
  it('releases an intended reservation for a runner that dies before enrollment', async () => {
575
680
  const reservationId = await createReservation(2);
576
681
  const [runner] = await db()
@@ -741,6 +846,13 @@ describe('reportRunnerInstances', () => {
741
846
 
742
847
  it('releases a reservation only once across repeated terminal reports', async () => {
743
848
  const reservationId = await createReservation(2);
849
+ await providerRunnerFactory.create({
850
+ workspaceId,
851
+ provisionerId,
852
+ providerRunnerId: 'provisioned-runner-1',
853
+ intendedReservationId: reservationId,
854
+ state: 'running',
855
+ });
744
856
 
745
857
  await reportRunnerInstances({
746
858
  scope: 'workspace',
@@ -762,6 +874,13 @@ describe('reportRunnerInstances', () => {
762
874
 
763
875
  it('does not let a newer running report revive a terminal provisioned runner', async () => {
764
876
  const reservationId = await createReservation(2);
877
+ await providerRunnerFactory.create({
878
+ workspaceId,
879
+ provisionerId,
880
+ providerRunnerId: 'provisioned-runner-1',
881
+ intendedReservationId: reservationId,
882
+ state: 'running',
883
+ });
765
884
  const failedAt = new Date();
766
885
  await reportRunnerInstances({
767
886
  scope: 'workspace',
@@ -800,6 +919,13 @@ describe('reportRunnerInstances', () => {
800
919
 
801
920
  it('tracks provider cleanup as terminated', async () => {
802
921
  const reservationId = await createReservation(2);
922
+ await providerRunnerFactory.create({
923
+ workspaceId,
924
+ provisionerId,
925
+ providerRunnerId: 'provisioned-runner-1',
926
+ intendedReservationId: reservationId,
927
+ state: 'running',
928
+ });
803
929
 
804
930
  const result = await reportRunnerInstances({
805
931
  scope: 'workspace',
@@ -821,6 +947,20 @@ describe('reportRunnerInstances', () => {
821
947
 
822
948
  it('releases multiple units from the same reservation in one batch', async () => {
823
949
  const reservationId = await createReservation(3);
950
+ await providerRunnerFactory.create({
951
+ workspaceId,
952
+ provisionerId,
953
+ providerRunnerId: 'provisioned-runner-1',
954
+ intendedReservationId: reservationId,
955
+ state: 'running',
956
+ });
957
+ await providerRunnerFactory.create({
958
+ workspaceId,
959
+ provisionerId,
960
+ providerRunnerId: 'provisioned-runner-2',
961
+ intendedReservationId: reservationId,
962
+ state: 'running',
963
+ });
824
964
 
825
965
  const result = await reportRunnerInstances({
826
966
  scope: 'workspace',
@@ -839,6 +979,13 @@ describe('reportRunnerInstances', () => {
839
979
 
840
980
  it('deletes a one-unit reservation instead of violating the positive count check', async () => {
841
981
  const reservationId = await createReservation(1);
982
+ await providerRunnerFactory.create({
983
+ workspaceId,
984
+ provisionerId,
985
+ providerRunnerId: 'provisioned-runner-1',
986
+ intendedReservationId: reservationId,
987
+ state: 'running',
988
+ });
842
989
 
843
990
  const result = await reportRunnerInstances({
844
991
  scope: 'workspace',
@@ -862,6 +1009,13 @@ describe('reportRunnerInstances', () => {
862
1009
  });
863
1010
  const [reservation] = await reservationRowsFor({workspaceId, provisionerId});
864
1011
  if (!reservation) throw new Error('Expected reservation');
1012
+ await providerRunnerFactory.create({
1013
+ workspaceId,
1014
+ provisionerId,
1015
+ providerRunnerId: 'provisioned-runner-1',
1016
+ intendedReservationId: reservation.id,
1017
+ state: 'running',
1018
+ });
865
1019
 
866
1020
  const result = await reportRunnerInstances({
867
1021
  scope: 'workspace',
@@ -1012,13 +1166,14 @@ describe('reportRunnerInstances', () => {
1012
1166
 
1013
1167
  async function createReservation(
1014
1168
  count: number,
1015
- overrides?: {workspaceId?: string; provisionerId?: string},
1169
+ overrides?: {kind?: 'bound' | 'launch'; workspaceId?: string; provisionerId?: string},
1016
1170
  ): Promise<string> {
1017
1171
  await reservationFactory.create({
1018
1172
  workspaceId: overrides?.workspaceId ?? workspaceId,
1019
1173
  provisionerId: overrides?.provisionerId ?? provisionerId,
1020
1174
  requiredLabels: ['linux'],
1021
1175
  count,
1176
+ kind: overrides?.kind ?? 'launch',
1022
1177
  expiresAt: new Date(Date.now() + 60_000),
1023
1178
  });
1024
1179
  const [reservation] = await db()
@@ -1157,6 +1312,106 @@ describe('listProvisionerTerminateIntents', () => {
1157
1312
  expect(result).toEqual([{providerRunnerId: 'provisioned-runner-1', reason: 'job-cancelled'}]);
1158
1313
  });
1159
1314
 
1315
+ it('returns an activation-timeout intent for a stale demand-backed runner', async () => {
1316
+ await createRunnerInstance({
1317
+ providerRunnerId: 'stale-demand-runner',
1318
+ launchKind: 'demand',
1319
+ createdAt: new Date(Date.now() - 301_000),
1320
+ });
1321
+
1322
+ const result = await db().transaction((tx) =>
1323
+ listProvisionerTerminateIntentRowsTx(tx, {workspaceId, provisionerId, limit: 1000}),
1324
+ );
1325
+ const [runner] = await providerRunnerRowsFor({workspaceId, provisionerId});
1326
+
1327
+ expect(result).toEqual([
1328
+ {providerRunnerId: 'stale-demand-runner', reason: 'activation-timeout'},
1329
+ ]);
1330
+ expect(runner?.reservationReleasedAt).toBeInstanceOf(Date);
1331
+ });
1332
+
1333
+ it('does not reclaim warm, manual, active, or already-activated runners', async () => {
1334
+ const [liveReservation] = await db()
1335
+ .insert(reservations)
1336
+ .values({
1337
+ workspaceId,
1338
+ provisionerId,
1339
+ requiredLabels: ['linux'],
1340
+ count: 1,
1341
+ expiresAt: new Date(Date.now() + 60_000),
1342
+ })
1343
+ .returning({id: reservations.id});
1344
+ if (!liveReservation) throw new Error('Expected live reservation');
1345
+
1346
+ const staleCreatedAt = new Date(Date.now() - 301_000);
1347
+ await createRunnerInstance({
1348
+ providerRunnerId: 'stale-warm-runner',
1349
+ launchKind: 'warm',
1350
+ createdAt: staleCreatedAt,
1351
+ });
1352
+ await createRunnerInstance({
1353
+ providerRunnerId: 'stale-manual-runner',
1354
+ launchKind: 'manual',
1355
+ createdAt: staleCreatedAt,
1356
+ });
1357
+ await createRunnerInstance({
1358
+ providerRunnerId: 'live-demand-runner',
1359
+ launchKind: 'demand',
1360
+ reservationId: liveReservation.id,
1361
+ createdAt: staleCreatedAt,
1362
+ });
1363
+ await db()
1364
+ .insert(providerRunners)
1365
+ .values({
1366
+ workspaceId,
1367
+ provisionerId,
1368
+ intendedReservationId: liveReservation.id,
1369
+ providerRunnerId: 'live-intended-demand-runner',
1370
+ launchKind: 'demand',
1371
+ state: 'running',
1372
+ labels: ['linux'],
1373
+ reportedAt: staleCreatedAt,
1374
+ createdAt: staleCreatedAt,
1375
+ updatedAt: staleCreatedAt,
1376
+ });
1377
+ await createRunnerInstance({
1378
+ providerRunnerId: 'activated-demand-runner',
1379
+ launchKind: 'demand',
1380
+ runnerSessionId: crypto.randomUUID(),
1381
+ createdAt: staleCreatedAt,
1382
+ });
1383
+
1384
+ const result = await listProvisionerTerminateIntents({workspaceId, provisionerId, limit: 1000});
1385
+
1386
+ expect(result).toEqual([]);
1387
+ });
1388
+
1389
+ it('marks activation-timeout retries after releasing the runner reservation', async () => {
1390
+ await createRunnerInstance({
1391
+ providerRunnerId: 'retryable-demand-runner',
1392
+ launchKind: 'demand',
1393
+ createdAt: new Date(Date.now() - 301_000),
1394
+ });
1395
+
1396
+ const first = await db().transaction((tx) =>
1397
+ listProvisionerTerminateIntentRowsTx(tx, {workspaceId, provisionerId, limit: 1000}),
1398
+ );
1399
+ const second = await db().transaction((tx) =>
1400
+ listProvisionerTerminateIntentRowsTx(tx, {workspaceId, provisionerId, limit: 1000}),
1401
+ );
1402
+
1403
+ expect(first).toEqual([
1404
+ {providerRunnerId: 'retryable-demand-runner', reason: 'activation-timeout'},
1405
+ ]);
1406
+ expect(second).toEqual([
1407
+ {
1408
+ providerRunnerId: 'retryable-demand-runner',
1409
+ reason: 'activation-timeout',
1410
+ activationTimeoutRetry: true,
1411
+ },
1412
+ ]);
1413
+ });
1414
+
1160
1415
  it('excludes active provisioned runners whose latest bound job is healthy', async () => {
1161
1416
  await createRunnerInstance({providerRunnerId: 'provisioned-runner-1'});
1162
1417
  await insertRunningJobRow({
@@ -1272,12 +1527,20 @@ describe('listProvisionerTerminateIntents', () => {
1272
1527
  providerRunnerId: string;
1273
1528
  provisionerId?: string;
1274
1529
  state?: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'terminated';
1530
+ launchKind?: 'demand' | 'warm' | 'manual';
1531
+ createdAt?: Date;
1532
+ reservationId?: string | null;
1533
+ runnerSessionId?: string | null;
1275
1534
  }) {
1276
1535
  return await providerRunnerFactory.create({
1277
1536
  workspaceId,
1278
1537
  provisionerId: params.provisionerId ?? provisionerId,
1279
1538
  providerRunnerId: params.providerRunnerId,
1280
1539
  state: params.state ?? 'running',
1540
+ launchKind: params.launchKind ?? 'manual',
1541
+ createdAt: params.createdAt ?? new Date(),
1542
+ reservationId: params.reservationId ?? null,
1543
+ runnerSessionId: params.runnerSessionId ?? null,
1281
1544
  });
1282
1545
  }
1283
1546
  });