deepline 0.3.247 → 0.3.249

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 (67) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +177 -13
  2. package/dist/bundling-sources/sdk/src/index.ts +8 -0
  3. package/dist/bundling-sources/sdk/src/plays/input-example.ts +40 -0
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/runs/overview.ts +457 -0
  6. package/dist/bundling-sources/sdk/src/runs/receipts.ts +23 -0
  7. package/dist/bundling-sources/sdk/src/tool-calls.ts +21 -1
  8. package/dist/bundling-sources/sdk/src/types.ts +16 -1
  9. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +19 -1
  10. package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +17 -3
  11. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -8
  12. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +26 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -14
  14. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +6 -3
  16. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +26 -21
  17. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +23 -21
  18. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +17 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +105 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +8 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +40 -4
  25. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -5
  26. package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +3 -1
  27. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +28 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +14 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +56 -16
  30. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +184 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +327 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +87 -30
  33. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +24 -13
  34. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +23 -6
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +76 -33
  36. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +90 -35
  37. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +16 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +112 -50
  39. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +447 -77
  40. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +22 -4
  41. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +105 -121
  42. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +1648 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +16 -19
  44. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +14 -6
  45. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +16 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +40 -0
  47. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +5 -2
  48. package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +11 -7
  49. package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +5 -2
  50. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +11 -2
  51. package/dist/cli/index.js +3504 -2368
  52. package/dist/cli/index.mjs +3188 -2052
  53. package/dist/{compiler-manifest-n8ZtPv2h.d.mts → compiler-manifest-DavmLa8y.d.mts} +1 -0
  54. package/dist/{compiler-manifest-n8ZtPv2h.d.ts → compiler-manifest-DavmLa8y.d.ts} +1 -0
  55. package/dist/index.d.mts +97 -5
  56. package/dist/index.d.ts +97 -5
  57. package/dist/index.js +135 -15
  58. package/dist/index.mjs +135 -15
  59. package/dist/install-integrity.json +9 -2
  60. package/dist/plays/bundle-play-file.d.mts +2 -2
  61. package/dist/plays/bundle-play-file.d.ts +2 -2
  62. package/dist/plays/bundle-play-file.mjs +1 -1
  63. package/dist/release.d.mts +1 -1
  64. package/dist/release.d.ts +1 -1
  65. package/dist/release.js +1 -1
  66. package/dist/release.mjs +1 -1
  67. package/package.json +1 -1
@@ -1,4 +1,10 @@
1
- import { Daytona } from '@daytonaio/sdk';
1
+ import { createDaytonaLaunchClient } from './daytona-launch-transport';
2
+ import type { DaytonaClientOptions } from '../../daytona-runtime-config';
3
+ import {
4
+ createDaytonaControlClient,
5
+ DAYTONA_CONTROL_TIMEOUT_MS,
6
+ DaytonaControlTimeoutError,
7
+ } from './daytona-control-transport';
2
8
  import { randomUUID } from 'node:crypto';
3
9
  import type {
4
10
  PlayRunnerBackend,
@@ -51,8 +57,6 @@ import { RUNTIME_RELIABILITY_POLICY } from '../../runtime-reliability-policy';
51
57
  import { daytonaSandboxStartAdmission } from './daytona-start-admission';
52
58
  import { RUNNER_MEMORY_CGROUP_ROOT } from './daytona-runtime-watchdog';
53
59
 
54
- const DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS =
55
- RUNTIME_RELIABILITY_POLICY.sandbox.daytonaCommandRecoveryTimeoutMs;
56
60
  const DAYTONA_COMMAND_RECOVERY_POLL_MS =
57
61
  RUNTIME_RELIABILITY_POLICY.sandbox.daytonaCommandRecoveryPollMs;
58
62
  const DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS = 2;
@@ -275,6 +279,7 @@ type DaytonaPreparedExecution = PlayRunnerPreparedExecution & {
275
279
  kind: 'daytona';
276
280
  bundlePromise: Promise<string>;
277
281
  sandboxLifecycle: OneShotDaytonaSandboxLifecycle;
282
+ daytona: DaytonaClient;
278
283
  };
279
284
 
280
285
  class DaytonaUploadDeadlineBreachError extends Error {
@@ -295,13 +300,11 @@ class DaytonaUploadDeadlineBreachError extends Error {
295
300
  }
296
301
 
297
302
  export const daytonaSdkClientFactory = {
298
- create(input: ConstructorParameters<typeof Daytona>[0]): DaytonaClient {
299
- return new Daytona(input);
300
- },
301
- /** Full client (get/delete by id) for best-effort GC of orphaned sandboxes. */
302
- createFull(input: ConstructorParameters<typeof Daytona>[0]): Daytona {
303
- return new Daytona(input);
303
+ create(input: DaytonaClientOptions, signal?: AbortSignal): DaytonaClient {
304
+ return createDaytonaLaunchClient(input, signal);
304
305
  },
306
+ /** Request-scoped native deadlines for detached recovery and cleanup. */
307
+ createControl: createDaytonaControlClient,
305
308
  };
306
309
 
307
310
  function stringProperty(value: unknown, key: string): string | null {
@@ -667,11 +670,11 @@ export async function readDetachedDaytonaCrashDiagnostic(input: {
667
670
  resourceSnapshot: null,
668
671
  collectionFailure,
669
672
  });
673
+ let control: ReturnType<typeof createDaytonaControlClient> | undefined;
670
674
  try {
671
675
  const { clientOptions } = loadDaytonaRequiredConfig();
672
- const sandbox = (await daytonaSdkClientFactory
673
- .createFull(clientOptions)
674
- .get(input.sandboxId)) as DaytonaSandbox;
676
+ control = daytonaSdkClientFactory.createControl(clientOptions);
677
+ const sandbox = (await control.get(input.sandboxId)) as DaytonaSandbox;
675
678
  const expectedOrganizationId = input.expectedOrganizationId?.trim() || null;
676
679
  const observedOrganizationId = sandbox.organizationId?.trim() || null;
677
680
  if (
@@ -781,6 +784,8 @@ export async function readDetachedDaytonaCrashDiagnostic(input: {
781
784
  };
782
785
  } catch (error) {
783
786
  return unavailable(describeDaytonaLookupFailure(error));
787
+ } finally {
788
+ control?.dispose();
784
789
  }
785
790
  }
786
791
 
@@ -829,10 +834,11 @@ export async function inspectDetachedDaytonaRunner(input: {
829
834
  detail: string | null;
830
835
  };
831
836
  }> {
837
+ let control: ReturnType<typeof createDaytonaControlClient> | undefined;
832
838
  try {
833
839
  const { clientOptions } = loadDaytonaRequiredConfig();
834
- const daytona = daytonaSdkClientFactory.createFull(clientOptions);
835
- const sandbox = (await daytona.get(input.sandboxId)) as DaytonaSandbox;
840
+ control = daytonaSdkClientFactory.createControl(clientOptions);
841
+ const sandbox = (await control.get(input.sandboxId)) as DaytonaSandbox;
836
842
  const expectedOrganizationId = input.expectedOrganizationId?.trim() || null;
837
843
  const observedOrganizationId = sandbox.organizationId?.trim() || null;
838
844
  if (
@@ -868,11 +874,15 @@ export async function inspectDetachedDaytonaRunner(input: {
868
874
  error: error instanceof Error ? error.message : String(error),
869
875
  });
870
876
  }
877
+ // The full captured stdout file can scale with customer output. Metadata
878
+ // time must not consume its existing independent file-recovery budget.
879
+ const recoveryFiles = control.legacyOutputRecovery(sandbox);
880
+ control.dispose();
871
881
  const recovered = await recoverDaytonaCommandExecution({
872
- sandbox,
882
+ sandbox: recoveryFiles,
873
883
  outputPath: input.outputPath,
874
884
  exitCodePath: input.exitCodePath,
875
- timeoutMs: 10_000,
885
+ deadlineAtMs: Date.now() + 10_000,
876
886
  });
877
887
  if (!recovered.execution) {
878
888
  return {
@@ -916,6 +926,8 @@ export async function inspectDetachedDaytonaRunner(input: {
916
926
  detail: failure.detail,
917
927
  },
918
928
  };
929
+ } finally {
930
+ control?.dispose();
919
931
  }
920
932
  }
921
933
 
@@ -925,11 +937,11 @@ export async function readDetachedDaytonaRuntimeCompletion(input: {
925
937
  runtimeCompletedPath: string;
926
938
  expectedOrganizationId?: string | null;
927
939
  }): Promise<number | null> {
940
+ let control: ReturnType<typeof createDaytonaControlClient> | undefined;
928
941
  try {
929
942
  const { clientOptions } = loadDaytonaRequiredConfig();
930
- const sandbox = (await daytonaSdkClientFactory
931
- .createFull(clientOptions)
932
- .get(input.sandboxId)) as DaytonaSandbox;
943
+ control = daytonaSdkClientFactory.createControl(clientOptions);
944
+ const sandbox = (await control.get(input.sandboxId)) as DaytonaSandbox;
933
945
  const expectedOrganizationId = input.expectedOrganizationId?.trim() || null;
934
946
  const observedOrganizationId = sandbox.organizationId?.trim() || null;
935
947
  if (
@@ -965,6 +977,8 @@ export async function readDetachedDaytonaRuntimeCompletion(input: {
965
977
  },
966
978
  );
967
979
  return null;
980
+ } finally {
981
+ control?.dispose();
968
982
  }
969
983
  }
970
984
 
@@ -996,10 +1010,14 @@ export async function deleteDaytonaSandboxByIdWithOutcome(input: {
996
1010
  detail: 'Sandbox ID is required.',
997
1011
  };
998
1012
  }
1013
+ let control: ReturnType<typeof createDaytonaControlClient> | undefined;
999
1014
  try {
1000
1015
  const { clientOptions } = loadDaytonaRequiredConfig();
1001
- const daytona = daytonaSdkClientFactory.createFull(clientOptions);
1002
- const sandbox = await daytona.get(sandboxId);
1016
+ control = daytonaSdkClientFactory.createControl(
1017
+ clientOptions,
1018
+ (input.timeoutSeconds ?? 30) * 1_000,
1019
+ );
1020
+ const sandbox = await control.get(sandboxId);
1003
1021
  const observedOrganizationId = sandbox.organizationId?.trim() || null;
1004
1022
  if (
1005
1023
  expectedOrganizationId &&
@@ -1012,7 +1030,7 @@ export async function deleteDaytonaSandboxByIdWithOutcome(input: {
1012
1030
  detail: 'Sandbox belongs to a different Daytona organization.',
1013
1031
  };
1014
1032
  }
1015
- await daytona.delete(sandbox, input.timeoutSeconds ?? 30);
1033
+ await control.delete(sandbox);
1016
1034
  return {
1017
1035
  kind: 'deleted',
1018
1036
  organizationId: observedOrganizationId,
@@ -1077,6 +1095,8 @@ export async function deleteDaytonaSandboxByIdWithOutcome(input: {
1077
1095
  (failure.httpStatus ? `http_${failure.httpStatus}` : 'delete_failed'),
1078
1096
  detail: failure.detail,
1079
1097
  };
1098
+ } finally {
1099
+ control?.dispose();
1080
1100
  }
1081
1101
  }
1082
1102
 
@@ -1140,7 +1160,7 @@ function emitDaytonaStage(
1140
1160
  }
1141
1161
 
1142
1162
  async function downloadDaytonaTextFile(
1143
- sandbox: DaytonaSandbox,
1163
+ sandbox: Pick<DaytonaSandbox, 'fs'>,
1144
1164
  remotePath: string,
1145
1165
  ): Promise<string> {
1146
1166
  const file = await sandbox.fs.downloadFile(remotePath, 15);
@@ -1148,13 +1168,12 @@ async function downloadDaytonaTextFile(
1148
1168
  }
1149
1169
 
1150
1170
  async function recoverDaytonaCommandExecution(input: {
1151
- sandbox: DaytonaSandbox;
1171
+ sandbox: Pick<DaytonaSandbox, 'fs'>;
1152
1172
  outputPath: string;
1153
1173
  exitCodePath: string;
1154
- timeoutMs?: number;
1174
+ deadlineAtMs: number;
1155
1175
  }): Promise<{ execution: DaytonaExecution | null; lastError: unknown }> {
1156
- const deadline =
1157
- Date.now() + (input.timeoutMs ?? DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS);
1176
+ const deadline = input.deadlineAtMs;
1158
1177
  let lastError: unknown = null;
1159
1178
 
1160
1179
  while (Date.now() < deadline) {
@@ -1177,7 +1196,9 @@ async function recoverDaytonaCommandExecution(input: {
1177
1196
  };
1178
1197
  } catch (error) {
1179
1198
  lastError = error;
1180
- await sleep(DAYTONA_COMMAND_RECOVERY_POLL_MS);
1199
+ const remainingMs = deadline - Date.now();
1200
+ if (remainingMs <= 0) break;
1201
+ await sleep(Math.min(DAYTONA_COMMAND_RECOVERY_POLL_MS, remainingMs));
1181
1202
  }
1182
1203
  }
1183
1204
 
@@ -1273,6 +1294,7 @@ export function retryableRuntimeInitializationFailureReason(
1273
1294
 
1274
1295
  function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
1275
1296
  if (error instanceof DaytonaRunnerInitializationError) return false;
1297
+ if (error instanceof DaytonaControlTimeoutError) return true;
1276
1298
  const message = error instanceof Error ? error.message : String(error);
1277
1299
  if (!message || /cancelled|aborted/i.test(message)) {
1278
1300
  return false;
@@ -1304,7 +1326,10 @@ function prepareDaytonaExecution(
1304
1326
  callbacks: Parameters<PlayRunnerBackend['execute']>[1],
1305
1327
  ): DaytonaPreparedExecution {
1306
1328
  const { clientOptions: daytonaOptions } = loadDaytonaRequiredConfig();
1307
- const daytona = daytonaSdkClientFactory.create(daytonaOptions);
1329
+ const daytona = daytonaSdkClientFactory.create(
1330
+ daytonaOptions,
1331
+ callbacks?.cancellationSignal,
1332
+ );
1308
1333
  const bundlePromise = buildPlayRunnerBundle();
1309
1334
  const sandboxLifecycle = createOneShotDaytonaSandboxLifecycle({
1310
1335
  daytona,
@@ -1318,9 +1343,11 @@ function prepareDaytonaExecution(
1318
1343
  : undefined,
1319
1344
  releaseSandboxCapacity: callbacks?.releaseSandboxCapacity,
1320
1345
  startAdmission: daytonaSandboxStartAdmission,
1346
+ cancellationSignal: callbacks?.cancellationSignal,
1321
1347
  });
1322
1348
  return {
1323
1349
  kind: 'daytona',
1350
+ daytona,
1324
1351
  bundlePromise,
1325
1352
  sandboxLifecycle,
1326
1353
  dispose: sandboxLifecycle.dispose,
@@ -1566,6 +1593,17 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1566
1593
  sandbox,
1567
1594
  sessionId,
1568
1595
  readiness: readRunnerReadiness,
1596
+ runControl: async (operation) => {
1597
+ const scope = preparedExecution.daytona.scope(
1598
+ sandbox,
1599
+ DAYTONA_CONTROL_TIMEOUT_MS,
1600
+ );
1601
+ try {
1602
+ return await operation(scope.sandbox);
1603
+ } finally {
1604
+ scope.dispose();
1605
+ }
1606
+ },
1569
1607
  cancellation: cancellationPromise,
1570
1608
  onCommandAccepted: async () => {
1571
1609
  await callbacks?.onRunnerCommandAccepted?.({
@@ -1610,10 +1648,14 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1610
1648
  let stagedPayload: Awaited<
1611
1649
  ReturnType<typeof stageDaytonaRunnerPayload>
1612
1650
  >;
1651
+ const uploadScope = preparedExecution.daytona.scope(
1652
+ sandbox,
1653
+ DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS,
1654
+ );
1613
1655
  try {
1614
1656
  stagedPayload = await Promise.race([
1615
1657
  stageDaytonaRunnerPayload({
1616
- sandbox,
1658
+ sandbox: uploadScope.sandbox,
1617
1659
  bundlePromise,
1618
1660
  config,
1619
1661
  workDir,
@@ -1639,7 +1681,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1639
1681
  if (executionAttempt < DAYTONA_UPLOAD_MAX_ATTEMPTS) {
1640
1682
  acquiredSandbox.billingEndedAt = Date.now();
1641
1683
  await reportRetiringRuntimeResource(acquiredSandbox);
1642
- sandboxCleanup.stashActiveSandboxForRetry();
1684
+ await sandboxCleanup.stashActiveSandboxForRetry();
1643
1685
  emitDaytonaStage(callbacks, config.context, 'recreate:start', {
1644
1686
  previousSandboxId: sandbox.id,
1645
1687
  attempt: executionAttempt + 1,
@@ -1652,6 +1694,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1652
1694
  }
1653
1695
  throw error;
1654
1696
  } finally {
1697
+ uploadScope.dispose();
1655
1698
  if (uploadDeadlineTimer) {
1656
1699
  clearTimeout(uploadDeadlineTimer);
1657
1700
  }
@@ -1768,7 +1811,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1768
1811
  if (sandboxForAttempt) {
1769
1812
  activeAcquiredResource!.billingEndedAt = Date.now();
1770
1813
  await reportRetiringRuntimeResource(activeAcquiredResource!);
1771
- sandboxCleanup.stashActiveSandboxForRetry();
1814
+ await sandboxCleanup.stashActiveSandboxForRetry();
1772
1815
  }
1773
1816
  acquiredSandboxPromise = sandboxLifecycle.createFreshSandbox();
1774
1817
  continue;
@@ -1796,7 +1839,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1796
1839
  const sandbox = sandboxCleanup.currentSandbox();
1797
1840
  if (sandbox) {
1798
1841
  try {
1799
- await sandbox.delete(30);
1842
+ await preparedExecution.daytona.delete(sandbox, 30);
1800
1843
  console.info(
1801
1844
  '[play-runner.daytona.resource_report_failure_cleanup_done]',
1802
1845
  { sandboxId: sandbox.id },
@@ -2885,21 +2885,33 @@ async function executeAssignedAbsurdAttempt(input: {
2885
2885
  ),
2886
2886
  ),
2887
2887
  });
2888
+ const finalizeSuspensionProgress = () =>
2889
+ runWithAbsurdExecutionLeaseHeartbeat({
2890
+ ctx,
2891
+ runId,
2892
+ operation: () =>
2893
+ input.progressReporter.finalize(input.runnerResult, {
2894
+ status: 'running',
2895
+ required: suspension.kind !== 'detached_runner',
2896
+ replayAllTerminalLogs: input.fromDetachedRunner,
2897
+ terminalLogProducerAttempt: input.terminalLogProducerAttempt,
2898
+ }),
2899
+ });
2900
+ // A real suspension closes an invocation's counter segment before its
2901
+ // wait or handoff. Detached terminal recovery can replay the retained
2902
+ // result and its idempotent final segment after a crash. A detached_runner
2903
+ // placeholder still owns a live runner; preserve its park-first recovery.
2904
+ if (suspension.kind !== 'detached_runner') {
2905
+ await finalizeSuspensionProgress();
2906
+ }
2888
2907
  const outcome = await createPlayRunAttemptSuspensionJob({
2889
2908
  store: attemptStore,
2890
2909
  engine,
2891
2910
  lifecycle: {
2892
2911
  waiting: async (lifecycle) => {
2893
- await runWithAbsurdExecutionLeaseHeartbeat({
2894
- ctx,
2895
- runId,
2896
- operation: () =>
2897
- input.progressReporter.finalize(input.runnerResult, {
2898
- status: 'running',
2899
- replayAllTerminalLogs: input.fromDetachedRunner,
2900
- terminalLogProducerAttempt: input.terminalLogProducerAttempt,
2901
- }),
2902
- });
2912
+ if (suspension.kind === 'detached_runner') {
2913
+ await finalizeSuspensionProgress();
2914
+ }
2903
2915
  await projectLifecycleOutbox({
2904
2916
  ctx,
2905
2917
  deps,
@@ -6996,30 +7008,27 @@ async function reconcileReservedDaytonaCancellationOnce(input: {
6996
7008
  ),
6997
7009
  );
6998
7010
  if (!operation) return;
6999
- const [{ Daytona }, { loadDaytonaRequiredConfig }] = await Promise.all([
7000
- import('@daytonaio/sdk'),
7001
- import('../daytona-runtime-config'),
7002
- ]);
7003
- const daytona = new Daytona(
7011
+ const [{ createDaytonaControlClient }, { loadDaytonaRequiredConfig }] =
7012
+ await Promise.all([
7013
+ import('../runner-backends/backends/daytona-control-transport'),
7014
+ import('../daytona-runtime-config'),
7015
+ ]);
7016
+ const control = createDaytonaControlClient(
7004
7017
  loadDaytonaRequiredConfig({ environment: input.environment }).clientOptions,
7018
+ ABSURD_SANDBOX_GC_TIMEOUT_MS,
7005
7019
  );
7006
7020
  const sandboxName = `dl-${operation.providerOperationToken}`;
7007
- let sandbox: Awaited<ReturnType<typeof daytona.get>>;
7021
+ let sandbox: Awaited<ReturnType<typeof control.get>>;
7008
7022
  try {
7009
- sandbox = await deleteRuntimeSandboxBounded({
7010
- deleteRuntimeSandbox: (name) => daytona.get(name),
7011
- sandboxId: sandboxName,
7012
- timeoutMs: ABSURD_SANDBOX_GC_TIMEOUT_MS,
7013
- });
7023
+ sandbox = await control.get(sandboxName);
7014
7024
  } catch (error) {
7015
7025
  const message = error instanceof Error ? error.message : String(error);
7016
7026
  if (!/\b(?:sandbox )?not found\b|\b404\b/i.test(message)) throw error;
7017
7027
  if (
7018
7028
  Date.now() - operation.createdAtMs <
7019
7029
  RUNTIME_RELIABILITY_POLICY.recovery.horizonMs
7020
- ) {
7030
+ )
7021
7031
  return;
7022
- }
7023
7032
  await input.runWithClient((client) =>
7024
7033
  abandonRuntimeSandboxOperationReservation(
7025
7034
  client,
@@ -7028,6 +7037,8 @@ async function reconcileReservedDaytonaCancellationOnce(input: {
7028
7037
  ),
7029
7038
  );
7030
7039
  return;
7040
+ } finally {
7041
+ control.dispose();
7031
7042
  }
7032
7043
  if (sandbox.labels?.operationKey !== operation.providerOperationToken) {
7033
7044
  throw new Error(
@@ -7422,6 +7433,49 @@ export type RuntimeWorkerHost = {
7422
7433
  close(): Promise<void>;
7423
7434
  };
7424
7435
 
7436
+ /** Own shutdown ordering across the primary queue, listener and maintenance.
7437
+ * Stop accepting claims synchronously; accepted work and every controller
7438
+ * must settle before shutdown succeeds or reports its collected failures. */
7439
+ export function createAbsurdRuntimeWorkerHostLifecycle(input: {
7440
+ worker: RuntimeWorkerHost;
7441
+ closeWake: () => void;
7442
+ stopControllers: () => void;
7443
+ controllers: readonly (Promise<unknown> | null)[];
7444
+ }): RuntimeWorkerHost {
7445
+ let closing: Promise<void> | null = null;
7446
+ return {
7447
+ close: () => {
7448
+ if (closing) return closing;
7449
+ const start = (operation: () => unknown): Promise<unknown> => {
7450
+ try {
7451
+ return Promise.resolve(operation());
7452
+ } catch (error) {
7453
+ return Promise.reject(error);
7454
+ }
7455
+ };
7456
+ // Invoke all stop operations now, before awaiting any controller. Even
7457
+ // a synchronous listener failure must not leave the queue claiming.
7458
+ const stops = [
7459
+ start(input.stopControllers),
7460
+ start(() => input.worker.close()),
7461
+ start(input.closeWake),
7462
+ ];
7463
+ closing = Promise.allSettled([...stops, ...input.controllers]).then(
7464
+ (results) => {
7465
+ const errors = results.flatMap((result) =>
7466
+ result.status === 'rejected' ? [result.reason] : [],
7467
+ );
7468
+ if (errors.length === 1) throw errors[0];
7469
+ if (errors.length > 1) {
7470
+ throw new AggregateError(errors, 'Absurd worker shutdown failed.');
7471
+ }
7472
+ },
7473
+ );
7474
+ return closing;
7475
+ },
7476
+ };
7477
+ }
7478
+
7425
7479
  function resolveAbsurdDrainExit(explicit?: boolean | null): boolean {
7426
7480
  if (explicit != null) return explicit;
7427
7481
  return process.env[ABSURD_DRAIN_EXIT_ENV]?.trim() === '1';
@@ -7777,23 +7831,24 @@ export async function startRuntimeWorkerHost(
7777
7831
  workerName: workerBase,
7778
7832
  });
7779
7833
 
7780
- return {
7781
- close: async () => {
7782
- // Accepted handlers and maintenance can take minutes to settle. Stop
7783
- // owning a direct LISTEN session before waiting for that drain, so a
7784
- // replacement can elect its listener without exhausting primary slots.
7785
- onWake?.close();
7834
+ return createAbsurdRuntimeWorkerHostLifecycle({
7835
+ worker,
7836
+ // Release LISTEN before joining accepted work, so a replacement can elect
7837
+ // its listener without exhausting primary scheduler sessions.
7838
+ closeWake: () => onWake?.close(),
7839
+ stopControllers: () => {
7786
7840
  drainStopped = true;
7787
7841
  billingStopped = true;
7788
7842
  cleanupStopped = true;
7789
7843
  adoptionStopped = true;
7790
- await drainWatch;
7791
- await adoptionSupervisor;
7792
- await billingController;
7793
- await cleanupController;
7794
- await worker.close();
7795
7844
  },
7796
- };
7845
+ controllers: [
7846
+ drainWatch,
7847
+ adoptionSupervisor,
7848
+ billingController,
7849
+ cleanupController,
7850
+ ],
7851
+ });
7797
7852
  }
7798
7853
 
7799
7854
  // Re-export for the entrypoint script's telemetry/observability wiring.
@@ -69,6 +69,22 @@ export function normalizePostgresConnectionStringForPg(url: string): string {
69
69
  }
70
70
  }
71
71
 
72
+ /** PlanetScale's connection login includes a branch suffix; SQL sees the role before it. */
73
+ export function planetScalePooledSqlRole(connectionString: string): string {
74
+ const url = new URL(connectionString);
75
+ const username = decodeURIComponent(url.username);
76
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\.[A-Za-z0-9_]+$/.exec(username);
77
+ if (
78
+ !['postgres:', 'postgresql:'].includes(url.protocol) ||
79
+ !url.hostname.endsWith('.psdb.cloud') ||
80
+ url.port !== '6432' ||
81
+ !match
82
+ ) {
83
+ throw new Error('Expected a pooled PlanetScale Postgres role URL.');
84
+ }
85
+ return match[1];
86
+ }
87
+
72
88
  /**
73
89
  * Canonical env var for the workflow runtime control-plane Postgres
74
90
  * (scheduler queue, leases, receipts, outbox). Hard-cutover runtime wiring