deepline 0.3.128 → 0.3.129

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 (128) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +34 -9
  2. package/dist/bundling-sources/sdk/src/http.ts +22 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  17. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  27. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  30. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  31. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  32. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  35. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  42. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  52. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  67. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  83. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  103. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  104. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  109. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  110. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  111. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  112. package/dist/cli/index.js +250 -38
  113. package/dist/cli/index.mjs +250 -38
  114. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  115. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  116. package/dist/index.d.mts +4 -2
  117. package/dist/index.d.ts +4 -2
  118. package/dist/index.js +231 -35
  119. package/dist/index.mjs +231 -35
  120. package/dist/install-integrity.json +31 -2
  121. package/dist/plays/bundle-play-file.d.mts +2 -2
  122. package/dist/plays/bundle-play-file.d.ts +2 -2
  123. package/dist/plays/bundle-play-file.mjs +65 -1
  124. package/dist/release.d.mts +1 -1
  125. package/dist/release.d.ts +1 -1
  126. package/dist/release.js +1 -1
  127. package/dist/release.mjs +1 -1
  128. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  import type { PlayRunnerEvent } from '../protocol';
2
- import {
3
- RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
4
- } from '../runtime-contract';
2
+ import { jsonByteLengthUpTo } from '../output-size-limits';
3
+ import { RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS } from '../runtime-contract';
5
4
 
6
5
  /**
7
6
  * Push-execution live progress channel (runs INSIDE the Daytona sandbox).
@@ -31,6 +30,11 @@ export const RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS = 5_000;
31
30
  const APP_RUNTIME_COMPAT_PATH = '/api/v2/plays/runtime';
32
31
 
33
32
  export interface RunnerProgressPusher {
33
+ snapshot: () => {
34
+ retainedBytes: number;
35
+ retainedEvents: number;
36
+ omittedEvents: number;
37
+ };
34
38
  /** Enqueue one progress event for the next batch flush. */
35
39
  enqueue: (event: PlayRunnerEvent) => void;
36
40
  /** Stop delivery and return all observations not known to be delivered. */
@@ -100,19 +104,20 @@ async function defaultSendBatch(input: {
100
104
  await response.text().catch(() => '');
101
105
  }
102
106
 
103
- type ProgressBatch = {
104
- events: PlayRunnerEvent[];
105
- /** Number of durable play-authored log lines before this batch. */
106
- liveLogOffset: number;
107
+ export const RUNNER_PROGRESS_MAX_RETAINED_BYTES = 2 * 1024 * 1024;
108
+ export const RUNNER_PROGRESS_MAX_RETAINED_EVENTS = 4_000;
109
+ const RUNNER_PROGRESS_BATCH_MAX_BYTES = 128 * 1024;
110
+ const RUNNER_PROGRESS_SEND_TIMEOUT_MS = 5_000;
111
+
112
+ type PendingEvent = {
113
+ event: PlayRunnerEvent;
114
+ bytes: number;
115
+ logOffset: number;
107
116
  };
108
117
 
109
- function durableLogCount(events: readonly PlayRunnerEvent[]): number {
110
- return events.reduce(
111
- (count, event) =>
112
- event.type === 'log' && event.source !== 'worker' && event.line.length > 0
113
- ? count + 1
114
- : count,
115
- 0,
118
+ function isDurableLog(event: PlayRunnerEvent): boolean {
119
+ return (
120
+ event.type === 'log' && event.source !== 'worker' && event.line.length > 0
116
121
  );
117
122
  }
118
123
 
@@ -120,202 +125,224 @@ export function createRunnerProgressPusher(
120
125
  options: RunnerProgressPushOptions,
121
126
  ): RunnerProgressPusher {
122
127
  const sendBatch = options.sendBatch ?? defaultSendBatch;
128
+ const warn =
129
+ options.warn ??
130
+ ((message, detail) =>
131
+ console.warn('[play-runner.progress-push]', message, detail));
123
132
  const setIntervalImpl =
124
- options.setIntervalImpl ??
125
- ((fn, ms) => setInterval(fn, ms) as unknown as { unref?: () => void });
133
+ options.setIntervalImpl ?? ((fn, ms) => setInterval(fn, ms));
126
134
  const clearIntervalImpl =
127
135
  options.clearIntervalImpl ??
128
136
  ((handle) => clearInterval(handle as ReturnType<typeof setInterval>));
129
- const warn =
130
- options.warn ??
131
- ((message, detail) =>
132
- console.warn(`[play-runner.progress-push] ${message}`, detail));
133
- const batchMs = options.batchMs ?? RUNNER_PROGRESS_PUSH_BATCH_MS;
134
137
  const maxBatch = options.maxBatch ?? RUNNER_PROGRESS_PUSH_MAX_BATCH;
135
- const stopTimeoutMs =
136
- options.stopTimeoutMs ?? RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS;
137
-
138
- const pending: PlayRunnerEvent[] = [];
139
- const queuedBatches: ProgressBatch[] = [];
140
- // A gateway acknowledgement is the durability boundary. Preserve any batch
141
- // that failed before that boundary so the terminal request can
142
- // carry it to whichever healthy replica Fly selects next.
143
- const failedBatches: ProgressBatch[] = [];
144
- const activeBatches = new Map<AbortController, ProgressBatch>();
145
- let nextBatchLiveLogOffset = 0;
146
- let deliveryBroken = false;
138
+ const pending: PendingEvent[] = [];
139
+ let active: PendingEvent[] = [];
140
+ let failed: PendingEvent[] = [];
141
+ let retainedBytes = 0;
142
+ let nextLogOffset = 0;
143
+ let omittedEvents = 0;
147
144
  let stopped = false;
148
145
  let abandoned = false;
149
- let suppressWarnings = false;
150
- let inFlight: Promise<void> = Promise.resolve();
151
- let handle: { unref?: () => void } | null = null;
146
+ let deliveryBroken = false;
147
+ let controller: AbortController | null = null;
148
+ let inFlight: Promise<void> | null = null;
149
+
150
+ const forget = (entries: PendingEvent[]) => {
151
+ for (const entry of entries) retainedBytes -= entry.bytes;
152
+ };
153
+ const enforceBudget = () => {
154
+ let dropped = 0;
155
+ // Never evict the active request: its ambiguous outcome must remain owned.
156
+ // Evict only a prefix, preserving absolute log offsets for the existing
157
+ // durable ingestion gap marker.
158
+ while (
159
+ retainedBytes > RUNNER_PROGRESS_MAX_RETAINED_BYTES ||
160
+ active.length + failed.length + pending.length >
161
+ RUNNER_PROGRESS_MAX_RETAINED_EVENTS
162
+ ) {
163
+ const entry = failed.shift() ?? pending.shift();
164
+ if (!entry) break;
165
+ retainedBytes -= entry.bytes;
166
+ dropped++;
167
+ }
168
+ if (dropped) {
169
+ omittedEvents += dropped;
170
+ if (omittedEvents === dropped)
171
+ warn('progress_buffer_limited', {
172
+ runId: options.runId,
173
+ omittedEvents,
174
+ });
175
+ }
176
+ };
152
177
 
153
178
  const flush = (): Promise<void> => {
154
- if (abandoned) {
155
- pending.length = 0;
156
- return inFlight;
179
+ if (inFlight) return inFlight;
180
+ if (abandoned || deliveryBroken || !pending.length)
181
+ return Promise.resolve();
182
+ let bytes = 0;
183
+ let count = 0;
184
+ while (count < pending.length && count < maxBatch) {
185
+ const next = pending[count]!;
186
+ const previous = pending[count - 1];
187
+ if (
188
+ previous &&
189
+ next.logOffset !==
190
+ previous.logOffset + Number(isDurableLog(previous.event))
191
+ )
192
+ break;
193
+ if (count && bytes + next.bytes > RUNNER_PROGRESS_BATCH_MAX_BYTES) break;
194
+ bytes += next.bytes;
195
+ count++;
157
196
  }
158
- if (pending.length === 0 || deliveryBroken) return inFlight;
159
- const events = pending.splice(0, Math.min(pending.length, maxBatch));
160
- const batch: ProgressBatch = {
161
- events,
162
- liveLogOffset: nextBatchLiveLogOffset,
163
- };
164
- nextBatchLiveLogOffset += durableLogCount(events);
165
- queuedBatches.push(batch);
166
- inFlight = inFlight
167
- .catch(() => {})
168
- .then(async () => {
169
- // stop() can time out while this batch waits behind an earlier upload.
170
- // Do not start it after the authoritative terminal has been emitted.
171
- if (abandoned) return;
172
- if (deliveryBroken) {
173
- const queuedIndex = queuedBatches.indexOf(batch);
174
- if (queuedIndex >= 0) queuedBatches.splice(queuedIndex, 1);
175
- failedBatches.push(batch);
176
- return;
177
- }
178
- const controller = new AbortController();
179
- const queuedIndex = queuedBatches.indexOf(batch);
180
- if (queuedIndex >= 0) queuedBatches.splice(queuedIndex, 1);
181
- activeBatches.set(controller, batch);
182
- try {
183
- await sendBatch({
197
+ active = pending.splice(0, count);
198
+ const batch = active;
199
+ const requestController = new AbortController();
200
+ controller = requestController;
201
+ let timeout: ReturnType<typeof setTimeout> | undefined;
202
+ inFlight = Promise.resolve().then(async () => {
203
+ if (abandoned) return;
204
+ try {
205
+ await Promise.race([
206
+ sendBatch({
184
207
  gatewayBaseUrl: options.gatewayBaseUrl,
185
208
  executorToken: options.executorToken,
186
209
  runId: options.runId,
187
210
  playName: options.playName,
188
- events: batch.events,
189
- liveLogOffset: batch.liveLogOffset,
190
- signal: controller.signal,
211
+ events: batch.map(({ event }) => event),
212
+ liveLogOffset: batch[0]!.logOffset,
213
+ signal: requestController.signal,
214
+ }),
215
+ new Promise<never>((_, reject) => {
216
+ timeout = setTimeout(() => {
217
+ requestController.abort();
218
+ reject(new Error('Progress delivery timed out.'));
219
+ }, RUNNER_PROGRESS_SEND_TIMEOUT_MS);
220
+ }),
221
+ ]);
222
+ if (!abandoned) forget(batch);
223
+ } catch (error) {
224
+ if (!abandoned) {
225
+ failed = batch;
226
+ deliveryBroken = true;
227
+ warn('batch_failed', {
228
+ runId: options.runId,
229
+ count: batch.length,
230
+ error: error instanceof Error ? error.message : String(error),
191
231
  });
192
- } catch (error) {
193
- if (!abandoned) {
194
- failedBatches.push(batch);
195
- // Offsets are positional. After one unknown delivery outcome, keep
196
- // the remaining contiguous suffix for the terminal request rather
197
- // than sending holes that another replica could misinterpret.
198
- deliveryBroken = true;
199
- }
200
- if (!suppressWarnings) {
201
- warn('batch_failed', {
202
- runId: options.runId,
203
- count: batch.events.length,
204
- error: error instanceof Error ? error.message : String(error),
205
- });
206
- }
207
- } finally {
208
- activeBatches.delete(controller);
209
232
  }
210
- });
233
+ } finally {
234
+ if (timeout) clearTimeout(timeout);
235
+ active = [];
236
+ controller = null;
237
+ inFlight = null;
238
+ enforceBudget();
239
+ if (!stopped && pending.length >= maxBatch) void flush();
240
+ }
241
+ });
211
242
  return inFlight;
212
243
  };
213
-
214
- handle = setIntervalImpl(() => {
244
+ const timer = setIntervalImpl(() => {
215
245
  void flush();
216
- }, batchMs);
217
- handle.unref?.();
246
+ }, options.batchMs ?? RUNNER_PROGRESS_PUSH_BATCH_MS);
247
+ timer.unref?.();
218
248
 
249
+ const abandon = () => {
250
+ abandoned = true;
251
+ controller?.abort();
252
+ pending.length = 0;
253
+ failed = [];
254
+ active = [];
255
+ retainedBytes = 0;
256
+ };
219
257
  return {
220
258
  enqueue(event) {
221
259
  if (stopped) return;
222
- pending.push(event);
223
- if (pending.length >= maxBatch) {
224
- void flush();
260
+ const logOffset = nextLogOffset;
261
+ if (isDurableLog(event)) nextLogOffset++;
262
+ // Short-circuit size inspection avoids serializing an arbitrarily large
263
+ // row-update merely to decide it cannot fit observability.
264
+ const measured = jsonByteLengthUpTo(
265
+ event,
266
+ RUNNER_PROGRESS_BATCH_MAX_BYTES,
267
+ );
268
+ if (measured.exceeded) {
269
+ omittedEvents++;
270
+ if (omittedEvents === 1)
271
+ warn('progress_event_too_large', { runId: options.runId });
272
+ return;
225
273
  }
274
+ const bytes = measured.bytes;
275
+ pending.push({ event, bytes, logOffset });
276
+ retainedBytes += bytes;
277
+ enforceBudget();
278
+ if (pending.length >= maxBatch) void flush();
279
+ },
280
+ snapshot() {
281
+ return {
282
+ retainedBytes,
283
+ retainedEvents: active.length + failed.length + pending.length,
284
+ omittedEvents,
285
+ };
226
286
  },
227
287
  sealForTerminal() {
228
- if (stopped) return { events: [], liveLogOffset: nextBatchLiveLogOffset };
288
+ if (stopped) return { events: [], liveLogOffset: nextLogOffset };
229
289
  stopped = true;
230
- if (handle) {
231
- clearIntervalImpl(handle);
232
- handle = null;
233
- }
234
- // Preserve every batch removed from `pending`, including batches queued
235
- // behind an earlier upload. The terminal request is the final owner of
236
- // these observations; do not drop a burst merely because it has not yet
237
- // acquired an AbortController.
238
- const retainedBatches: ProgressBatch[] = [
239
- ...failedBatches,
240
- ...activeBatches.values(),
241
- ...queuedBatches,
242
- ...(pending.length
243
- ? [{ events: [...pending], liveLogOffset: nextBatchLiveLogOffset }]
244
- : []),
245
- ];
246
- const unboundedTail = retainedBatches.flatMap((batch) => batch.events);
247
- const droppedEvents = Math.max(
290
+ clearIntervalImpl(timer);
291
+ const retained = [...failed, ...active, ...pending];
292
+ let start = Math.max(
248
293
  0,
249
- unboundedTail.length - RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
250
- );
251
- const droppedPrefix = unboundedTail.slice(0, droppedEvents);
252
- const tail = unboundedTail.slice(
253
- -RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
294
+ retained.length - RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
254
295
  );
255
- const liveLogOffset =
256
- (retainedBatches[0]?.liveLogOffset ?? nextBatchLiveLogOffset) +
257
- durableLogCount(droppedPrefix);
258
- if (tail.length < unboundedTail.length) {
296
+ for (let index = start + 1; index < retained.length; index++) {
297
+ const previous = retained[index - 1]!;
298
+ if (
299
+ retained[index]!.logOffset !==
300
+ previous.logOffset + Number(isDurableLog(previous.event))
301
+ )
302
+ start = index;
303
+ }
304
+ const tail = retained.slice(start);
305
+ if (tail.length < retained.length || omittedEvents)
259
306
  warn('terminal_tail_truncated', {
260
307
  runId: options.runId,
261
308
  retainedEvents: tail.length,
262
- droppedEvents: unboundedTail.length - tail.length,
309
+ droppedEvents: retained.length - tail.length + omittedEvents,
263
310
  });
264
- }
265
- failedBatches.length = 0;
266
- queuedBatches.length = 0;
267
- pending.length = 0;
268
- abandoned = true;
269
- suppressWarnings = true;
270
- for (const controller of activeBatches.keys()) controller.abort();
271
- return { events: tail, liveLogOffset };
311
+ const result = {
312
+ events: tail.map(({ event }) => event),
313
+ liveLogOffset: tail[0]?.logOffset ?? nextLogOffset,
314
+ };
315
+ abandon();
316
+ return result;
272
317
  },
273
318
  async stop() {
274
319
  if (stopped) return;
275
320
  stopped = true;
276
- if (handle) {
277
- clearIntervalImpl(handle);
278
- handle = null;
279
- }
321
+ clearIntervalImpl(timer);
322
+ let timeout: ReturnType<typeof setTimeout> | undefined;
280
323
  const drain = async () => {
281
- // Drain everything still buffered.
282
- while (pending.length > 0 && !deliveryBroken) {
324
+ while (!abandoned && !deliveryBroken && (pending.length || inFlight))
283
325
  await flush();
284
- }
285
- await inFlight.catch(() => {});
286
- if (deliveryBroken) pending.length = 0;
287
326
  };
288
- let stopTimeoutHandle: ReturnType<typeof setTimeout> | null = null;
289
327
  const outcome = await Promise.race([
290
- drain().then(() => 'drained' as const),
291
- new Promise<'timed_out'>((resolve) => {
292
- stopTimeoutHandle = setTimeout(
328
+ drain().then(() => 'drained'),
329
+ new Promise<string>((resolve) => {
330
+ timeout = setTimeout(
293
331
  () => resolve('timed_out'),
294
- stopTimeoutMs,
332
+ options.stopTimeoutMs ?? RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS,
295
333
  );
296
334
  }),
297
335
  ]);
298
- if (stopTimeoutHandle) clearTimeout(stopTimeoutHandle);
299
- if (outcome === 'drained') return;
300
-
301
- // The authoritative terminal follows this method. Abort ordinary fetches
302
- // and drop non-cooperative test/transport promises without letting their
303
- // later rejection write a post-terminal diagnostic.
304
- abandoned = true;
305
- suppressWarnings = true;
306
- const pendingEvents = pending.length;
307
- pending.length = 0;
308
- const inFlightEvents = [...activeBatches.values()].reduce(
309
- (sum, batch) => sum + batch.events.length,
310
- 0,
311
- );
312
- for (const controller of activeBatches.keys()) controller.abort();
313
- warn('drain_timed_out', {
314
- runId: options.runId,
315
- timeoutMs: stopTimeoutMs,
316
- pendingEvents,
317
- inFlightEvents,
318
- });
336
+ if (timeout) clearTimeout(timeout);
337
+ if (outcome === 'timed_out')
338
+ warn('drain_timed_out', {
339
+ runId: options.runId,
340
+ timeoutMs:
341
+ options.stopTimeoutMs ?? RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS,
342
+ pendingEvents: pending.length,
343
+ inFlightEvents: active.length,
344
+ });
345
+ abandon();
319
346
  },
320
347
  };
321
348
  }
@@ -657,6 +657,12 @@ async function acquireOneShotDaytonaSandbox(input: {
657
657
  createRetriedOneShotDaytonaSandbox(input),
658
658
  )
659
659
  : await createRetriedOneShotDaytonaSandbox(input);
660
+ // The runner's egress probe measures the sandbox-to-estate round trip; the
661
+ // sandbox's target names the region that round trip crosses.
662
+ console.info('[play-runner.daytona.sandbox_acquired]', {
663
+ sandboxId: result.sandbox.id,
664
+ target: (result.sandbox as { target?: unknown }).target ?? null,
665
+ });
660
666
  const granted = {
661
667
  cpu: result.sandbox.cpu,
662
668
  memoryGiB: result.sandbox.memory,
@@ -24,6 +24,8 @@ import {
24
24
  } from '../../output-size-limits';
25
25
  import {
26
26
  RUNTIME_SANDBOX_KILLED_MESSAGE,
27
+ RUNTIME_SANDBOX_MEMORY_ABORT_CODE,
28
+ RUNTIME_SANDBOX_MEMORY_ABORT_MESSAGE,
27
29
  RUNTIME_SANDBOX_OOM_MESSAGE,
28
30
  } from '../../run-failure';
29
31
  import {
@@ -37,7 +39,11 @@ import type {
37
39
  DaytonaSandbox,
38
40
  DaytonaStageEmitter,
39
41
  } from './daytona-lifecycle';
40
- import { buildDaytonaRuntimeWatchdogSource } from './daytona-runtime-watchdog';
42
+ import {
43
+ buildDaytonaRuntimeWatchdogSource,
44
+ PLAY_RUNNER_UV_THREADPOOL_SIZE,
45
+ RUNTIME_SANDBOX_MEMORY_ABORT_REASON,
46
+ } from './daytona-runtime-watchdog';
41
47
 
42
48
  type DaytonaRemoteRuntimeContext = PlayRunnerExecutionConfig['context'];
43
49
 
@@ -51,6 +57,12 @@ type DaytonaRemoteRuntimeContext = PlayRunnerExecutionConfig['context'];
51
57
  */
52
58
  export const DAYTONA_CRASH_PUSHER_POST_EVENT_DIAGNOSTIC_HEADROOM_BYTES =
53
59
  RUNNER_POST_TERMINAL_DIAGNOSTIC_MAX_BYTES;
60
+
61
+ /**
62
+ * Ledger evidence carried on a memory-abort cause. The ledger is diagnostic,
63
+ * not the terminal itself, so it is bounded well below the terminal envelope.
64
+ */
65
+ export const DAYTONA_CRASH_PUSHER_MEMORY_LEDGER_MAX_BYTES = 8 * 1024;
54
66
  export const DAYTONA_CRASH_PUSHER_OUTPUT_TAIL_MAX_BYTES =
55
67
  RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES +
56
68
  DAYTONA_CRASH_PUSHER_POST_EVENT_DIAGNOSTIC_HEADROOM_BYTES;
@@ -68,6 +80,8 @@ export type StagedDaytonaPayload = {
68
80
 
69
81
  export type RemoteRunnerPayloadSandbox = {
70
82
  id: string;
83
+ /** Daytona target (region) the sandbox runs in, when the provider reports it. */
84
+ target?: string;
71
85
  uploadFile(content: Buffer, path: string): Promise<void>;
72
86
  };
73
87
 
@@ -255,6 +269,48 @@ function watchdogObservedChildSigkill() {
255
269
  return false;
256
270
  }
257
271
 
272
+ // The watchdog stopped the runner at the memory abort threshold. Its
273
+ // diagnostic is the only place the observed usage/limit and the runtime's byte
274
+ // ledger survive the child's death, so the terminal is synthesized from it.
275
+ function watchdogMemoryAbortDiagnostic() {
276
+ try {
277
+ if (!terminationDiagnosticPath) return null;
278
+ const diagnostic = JSON.parse(
279
+ fs.readFileSync(terminationDiagnosticPath, 'utf8'),
280
+ );
281
+ if (
282
+ diagnostic &&
283
+ diagnostic.schemaVersion === 1 &&
284
+ diagnostic.reason === ${JSON.stringify(RUNTIME_SANDBOX_MEMORY_ABORT_REASON)}
285
+ ) {
286
+ return diagnostic;
287
+ }
288
+ } catch {}
289
+ return null;
290
+ }
291
+
292
+ function memoryAbortCause(diagnostic) {
293
+ const parts = [
294
+ ${JSON.stringify(`${RUNTIME_SANDBOX_MEMORY_ABORT_CODE}: ${RUNTIME_SANDBOX_MEMORY_ABORT_MESSAGE}`)},
295
+ 'usage=' + String(diagnostic.memoryUsageBytes),
296
+ 'limit=' + String(diagnostic.memoryLimitBytes),
297
+ 'ratio=' + String(diagnostic.memoryUsageRatio),
298
+ 'source=' + String(diagnostic.memorySource),
299
+ ];
300
+ if (diagnostic.memoryLedger !== null && diagnostic.memoryLedger !== undefined) {
301
+ try {
302
+ parts.push(
303
+ 'ledger=' +
304
+ truncateUtf8(
305
+ JSON.stringify(diagnostic.memoryLedger),
306
+ ${DAYTONA_CRASH_PUSHER_MEMORY_LEDGER_MAX_BYTES},
307
+ ),
308
+ );
309
+ } catch {}
310
+ }
311
+ return parts.join(' ');
312
+ }
313
+
258
314
  function terminalResultForTransport(result) {
259
315
  if (
260
316
  result &&
@@ -312,10 +368,15 @@ async function main() {
312
368
  // runner child's signal. Read the watchdog's strict marker so a child
313
369
  // SIGKILL does not collapse into the wrapper's generic exit code 1.
314
370
  const sandboxKilled = exitCode === 137 || watchdogObservedChildSigkill();
371
+ const memoryAbort = watchdogMemoryAbortDiagnostic();
315
372
  const sandboxOom =
316
- sandboxKilled && (outputHasExplicitOomSignature() || cgroupOomKillObserved());
373
+ !memoryAbort &&
374
+ sandboxKilled &&
375
+ (outputHasExplicitOomSignature() || cgroupOomKillObserved());
317
376
  const synthesizedError = runtimeLimitExceeded
318
377
  ? 'RUNTIME_LIMIT_EXCEEDED: ' + ${JSON.stringify(runtimeLimitMessage)}
378
+ : memoryAbort
379
+ ? memoryAbortCause(memoryAbort)
319
380
  : sandboxOom
320
381
  ? 'RUNTIME_SANDBOX_OOM: ' + ${JSON.stringify(RUNTIME_SANDBOX_OOM_MESSAGE)}
321
382
  : sandboxKilled
@@ -336,6 +397,15 @@ async function main() {
336
397
  cause: synthesizedError,
337
398
  }]
338
399
  : undefined,
400
+ ...(memoryAbort && !runtimeLimitExceeded
401
+ ? { errors: [{
402
+ code: ${JSON.stringify(RUNTIME_SANDBOX_MEMORY_ABORT_CODE)},
403
+ phase: 'infrastructure',
404
+ message: ${JSON.stringify(RUNTIME_SANDBOX_MEMORY_ABORT_MESSAGE)},
405
+ retryable: false,
406
+ cause: synthesizedError,
407
+ }] }
408
+ : {}),
339
409
  ...(sandboxOom
340
410
  ? { errors: [{
341
411
  code: 'RUNTIME_SANDBOX_OOM',
@@ -345,7 +415,7 @@ async function main() {
345
415
  cause: synthesizedError,
346
416
  }] }
347
417
  : {}),
348
- ...(sandboxKilled && !sandboxOom
418
+ ...(sandboxKilled && !sandboxOom && !memoryAbort
349
419
  ? { errors: [{
350
420
  code: 'RUNTIME_SANDBOX_KILLED',
351
421
  phase: 'infrastructure',
@@ -590,6 +660,8 @@ export async function stageRunnerPayload(input: {
590
660
  const runtimeCompletedPath = `${workDir}/deepline-play-runtime-completed-${randomUUID()}.json`;
591
661
  const runtimeLimitMarkerPath = `${workDir}/deepline-play-runtime-limit-${randomUUID()}.txt`;
592
662
  const terminationDiagnosticPath = `${workDir}/deepline-play-termination-${randomUUID()}.json`;
663
+ const memoryLedgerPath = `${workDir}/deepline-play-memory-ledger-${randomUUID()}.json`;
664
+ const memoryPressurePath = `${workDir}/deepline-play-memory-pressure-${randomUUID()}.json`;
593
665
  const progressEventPath = `${workDir}/deepline-play-progress-${randomUUID()}.jsonl`;
594
666
  const startupDiagnosticPath = `${workDir}/deepline-play-startup-${randomUUID()}.json`;
595
667
  const runnerTraceEnv =
@@ -597,6 +669,13 @@ export async function stageRunnerPayload(input: {
597
669
  ? 'DEEPLINE_RUNTIME_RECEIPT_TRACE=1 '
598
670
  : '';
599
671
  const runtimeReliabilityEnv = runtimeReliabilityEnvironmentPrefix();
672
+ // The runner's egress probe prints this next to its round-trip timings so
673
+ // a slow sandbox-to-estate path can be read against the region it crosses.
674
+ const sandboxTarget = input.sandbox.target;
675
+ const sandboxTargetEnv =
676
+ typeof sandboxTarget === 'string' && sandboxTarget.trim()
677
+ ? `DEEPLINE_SANDBOX_TARGET=${shellQuote(sandboxTarget.trim())} `
678
+ : '';
600
679
  const watchedRunnerCommand = [
601
680
  'node',
602
681
  '-e',
@@ -610,6 +689,8 @@ export async function stageRunnerPayload(input: {
610
689
  String(PLAY_RUNNER_TERMINAL_GRACE_SECONDS * 1_000),
611
690
  shellQuote(runtimeLimitMarkerPath),
612
691
  shellQuote(terminationDiagnosticPath),
692
+ shellQuote(memoryLedgerPath),
693
+ shellQuote(memoryPressurePath),
613
694
  ].join(' ');
614
695
  const runnerCommand = `${nodeMaterializePayloadCommand({
615
696
  envelopePath,
@@ -618,7 +699,7 @@ export async function stageRunnerPayload(input: {
618
699
  artifactCodePath,
619
700
  artifactSourceMapPath,
620
701
  crashPusherPath,
621
- })} && DEEPLINE_PLAY_RUNNER_RUNTIME_STARTED_PATH=${shellQuote(runtimeStartedPath)} DEEPLINE_PLAY_RUNNER_RUNTIME_COMPLETED_PATH=${shellQuote(runtimeCompletedPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STARTUP_DIAGNOSTIC_PATH=${shellQuote(startupDiagnosticPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runtimeReliabilityEnv}${runnerTraceEnv}${watchedRunnerCommand}`;
702
+ })} && DEEPLINE_PLAY_RUNNER_RUNTIME_STARTED_PATH=${shellQuote(runtimeStartedPath)} DEEPLINE_PLAY_RUNNER_RUNTIME_COMPLETED_PATH=${shellQuote(runtimeCompletedPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STARTUP_DIAGNOSTIC_PATH=${shellQuote(startupDiagnosticPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact UV_THREADPOOL_SIZE=${PLAY_RUNNER_UV_THREADPOOL_SIZE} ${runtimeReliabilityEnv}${runnerTraceEnv}${sandboxTargetEnv}${watchedRunnerCommand}`;
622
703
  // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
623
704
  // pushes the parsed (or synthesized) terminal to the gateway so the parked
624
705
  // worker wakes within seconds of ANY runner death — process.exit abuse, OOM
@@ -627,7 +708,7 @@ export async function stageRunnerPayload(input: {
627
708
  // Its own retry diagnostics are kept in a separate log: appending them to
628
709
  // `outputPath` after a near-gateway-sized result could hide that result from
629
710
  // a later bounded-tail recovery read.
630
- const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; ${runtimeReliabilityEnv}node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
711
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(memoryLedgerPath)} ${shellQuote(`${memoryLedgerPath}.tmp`)} ${shellQuote(memoryPressurePath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; ${runtimeReliabilityEnv}node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
631
712
 
632
713
  return {
633
714
  workDir: input.workDir,
@@ -650,6 +731,10 @@ export async function stageDaytonaRunnerPayload(
650
731
  ...input,
651
732
  sandbox: {
652
733
  id: input.sandbox.id,
734
+ target:
735
+ typeof (input.sandbox as { target?: unknown }).target === 'string'
736
+ ? ((input.sandbox as { target: string }).target ?? undefined)
737
+ : undefined,
653
738
  uploadFile: (content, path) => input.sandbox.fs.uploadFile(content, path),
654
739
  },
655
740
  });