deepline 0.1.210 → 0.1.212

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 (40) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +203 -12
  2. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +15 -0
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +90 -0
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +162 -100
  6. package/dist/bundling-sources/sdk/src/client.ts +118 -3
  7. package/dist/bundling-sources/sdk/src/play.ts +2 -0
  8. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  9. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  10. package/dist/bundling-sources/sdk/src/types.ts +47 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +253 -30
  12. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +365 -103
  13. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
  15. package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +45 -8
  17. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
  18. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
  19. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
  21. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +5 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +37 -5
  24. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
  25. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +63 -7
  26. package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
  27. package/dist/bundling-sources/shared_libs/play-runtime/single-flight.ts +31 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +12 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
  30. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
  31. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
  32. package/dist/bundling-sources/shared_libs/plays/dataset.ts +41 -5
  33. package/dist/cli/index.js +728 -239
  34. package/dist/cli/index.mjs +728 -239
  35. package/dist/index.d.mts +93 -7
  36. package/dist/index.d.ts +93 -7
  37. package/dist/index.js +245 -46
  38. package/dist/index.mjs +245 -46
  39. package/dist/plays/bundle-play-file.mjs +65 -4
  40. package/package.json +1 -1
@@ -66,6 +66,22 @@ export type RuntimeStatusUpdate = {
66
66
  * positional cursor stay correct once the buffer has rotated.
67
67
  */
68
68
  liveLogTotalCount?: number;
69
+ /**
70
+ * Monotonic scheduler attempt that owns the positional log channel. A new
71
+ * attempt starts at offset zero without colliding with earlier attempts.
72
+ */
73
+ liveLogProducerAttempt?: number;
74
+ datasetLifecycleEvents?: Array<{
75
+ datasetId: string;
76
+ path: string;
77
+ tableNamespace: string;
78
+ phase: 'registered' | 'available' | 'failed';
79
+ persistedRows?: number;
80
+ succeededRows?: number;
81
+ failedRows?: number;
82
+ complete?: boolean;
83
+ at: number;
84
+ }>;
69
85
  /**
70
86
  * Explicit terminal-output replay for final runner logs when the caller must
71
87
  * keep `status` nonterminal. Direct-worker/Postgres progress finalizers do this so
@@ -75,6 +91,8 @@ export type RuntimeStatusUpdate = {
75
91
  terminalLogReplay?: {
76
92
  lines: string[];
77
93
  totalCount?: number;
94
+ dedupeMode?: 'semantic' | 'exact';
95
+ producerAttempt?: number;
78
96
  } | null;
79
97
  liveTimeline?: PlayRunTimelineEntry[];
80
98
  liveNodeProgress?: PlayVisualNodeProgressMap;
@@ -347,12 +365,84 @@ function applyRetryJitter(delayMs: number): number {
347
365
  const APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
348
366
  const RUN_STATUS_LEDGER_SNAPSHOT_CACHE_LIMIT = 1_000;
349
367
  const runStatusLedgerSnapshots = new Map<string, PlayRunLedgerSnapshot>();
350
- // Positional log cursor per run: count of liveLogs lines already forwarded to
351
- // the ledger from this process. Process-local on purpose a fresh process
352
- // re-sends from offset 0 and Run Log Stream ingestion skips the overlap
353
- // positionally (exactly-once on the channel, repeated identical lines kept).
368
+ // Positional log cursor per run attempt: count of liveLogs lines already
369
+ // forwarded from this process. A fresh process re-sends from offset 0; durable
370
+ // ingestion skips overlap within that same attempt.
354
371
  const runLogChannelSentCounts = new Map<string, number>();
355
372
  const runStatusUpdateChains = new Map<string, Promise<void>>();
373
+ const APP_RUNTIME_LOG_EVENT_MAX_JSON_BYTES = 128 * 1024;
374
+ const APP_RUNTIME_EVENT_BATCH_MAX_JSON_BYTES = 512 * 1024;
375
+ const appRuntimeTextEncoder = new TextEncoder();
376
+
377
+ function splitRunEventForAppRuntime(
378
+ event: PlayRunLedgerEvent,
379
+ ): PlayRunLedgerEvent[] {
380
+ if (event.type !== 'log.appended' || event.lines.length === 0) return [event];
381
+ const split: PlayRunLedgerEvent[] = [];
382
+ let index = 0;
383
+ while (index < event.lines.length) {
384
+ const start = index;
385
+ let bytes = 2;
386
+ while (index < event.lines.length) {
387
+ const lineBytes = appRuntimeTextEncoder.encode(
388
+ JSON.stringify(event.lines[index]!),
389
+ ).length;
390
+ if (
391
+ index > start &&
392
+ bytes + lineBytes + 1 > APP_RUNTIME_LOG_EVENT_MAX_JSON_BYTES
393
+ ) {
394
+ break;
395
+ }
396
+ bytes += lineBytes + 1;
397
+ index += 1;
398
+ }
399
+ split.push({
400
+ ...event,
401
+ lines: event.lines.slice(start, index),
402
+ ...(typeof event.channelOffset === 'number'
403
+ ? { channelOffset: event.channelOffset + start }
404
+ : {}),
405
+ });
406
+ }
407
+ return split;
408
+ }
409
+
410
+ export function partitionRunEventsForAppRuntime(
411
+ events: readonly PlayRunLedgerEvent[],
412
+ ): PlayRunLedgerEvent[][] {
413
+ const batches: PlayRunLedgerEvent[][] = [];
414
+ let current: PlayRunLedgerEvent[] = [];
415
+ let currentBytes = 2;
416
+ for (const event of events.flatMap(splitRunEventForAppRuntime)) {
417
+ const eventBytes = appRuntimeTextEncoder.encode(
418
+ JSON.stringify(event),
419
+ ).length;
420
+ if (
421
+ current.length > 0 &&
422
+ currentBytes + eventBytes + 1 > APP_RUNTIME_EVENT_BATCH_MAX_JSON_BYTES
423
+ ) {
424
+ batches.push(current);
425
+ current = [];
426
+ currentBytes = 2;
427
+ }
428
+ current.push(event);
429
+ currentBytes += eventBytes + 1;
430
+ }
431
+ if (current.length > 0) batches.push(current);
432
+ return batches;
433
+ }
434
+
435
+ async function appendRunEventBatchesViaAppRuntime(
436
+ context: WorkerRuntimeApiContext,
437
+ input: { playId: string; events: readonly PlayRunLedgerEvent[] },
438
+ ): Promise<void> {
439
+ for (const events of partitionRunEventsForAppRuntime(input.events)) {
440
+ await appendRunEventsViaAppRuntime(context, {
441
+ playId: input.playId,
442
+ events,
443
+ });
444
+ }
445
+ }
356
446
 
357
447
  function isTerminalRuntimeStatus(status: string): boolean {
358
448
  return (
@@ -364,6 +454,10 @@ function isTerminalRuntimeStatus(status: string): boolean {
364
454
  );
365
455
  }
366
456
 
457
+ function runLogChannelKey(runId: string, producerAttempt?: number): string {
458
+ return `${runId}:${producerAttempt ?? 'legacy'}`;
459
+ }
460
+
367
461
  function rememberRunStatusLedgerSnapshot(
368
462
  runId: string,
369
463
  snapshot: PlayRunLedgerSnapshot,
@@ -376,7 +470,11 @@ function rememberRunStatusLedgerSnapshot(
376
470
  const oldestRunId = runStatusLedgerSnapshots.keys().next().value;
377
471
  if (!oldestRunId) break;
378
472
  runStatusLedgerSnapshots.delete(oldestRunId);
379
- runLogChannelSentCounts.delete(oldestRunId);
473
+ for (const key of runLogChannelSentCounts.keys()) {
474
+ if (key.startsWith(`${oldestRunId}:`)) {
475
+ runLogChannelSentCounts.delete(key);
476
+ }
477
+ }
380
478
  runStatusUpdateChains.delete(oldestRunId);
381
479
  }
382
480
  }
@@ -399,12 +497,16 @@ function isRetryableAppRuntimeResponse(input: {
399
497
  if (input.status === 500 && isTransientAppRuntimeFailureBody(input.body)) {
400
498
  return true;
401
499
  }
500
+ return isRetryableAppRuntimeResponseStatus(input.status);
501
+ }
502
+
503
+ function isRetryableAppRuntimeResponseStatus(status: number): boolean {
402
504
  return (
403
- input.status === 429 ||
404
- (input.status >= 520 && input.status <= 524) ||
405
- input.status === 502 ||
406
- input.status === 503 ||
407
- input.status === 504
505
+ status === 429 ||
506
+ (status >= 520 && status <= 524) ||
507
+ status === 502 ||
508
+ status === 503 ||
509
+ status === 504
408
510
  );
409
511
  }
410
512
 
@@ -601,6 +703,38 @@ function sleep(ms: number): Promise<void> {
601
703
  return new Promise((resolve) => setTimeout(resolve, ms));
602
704
  }
603
705
 
706
+ async function retryAppRuntimeBodyTimeoutOrThrow(input: {
707
+ action: RuntimeApiRequest['action'];
708
+ attempt: number;
709
+ maxAttempts: number;
710
+ error: unknown;
711
+ retryAfterMs?: number | null;
712
+ }): Promise<void> {
713
+ if (!isRequestTimeoutAbort(input.error)) {
714
+ throw input.error;
715
+ }
716
+ if (
717
+ input.attempt < input.maxAttempts &&
718
+ isRetryableAppRuntimeFetchError({
719
+ action: input.action,
720
+ error: input.error,
721
+ })
722
+ ) {
723
+ await sleep(
724
+ Math.max(
725
+ applyRetryJitter(appRuntimeRetryDelayMs(input.action, input.attempt)),
726
+ input.retryAfterMs ?? 0,
727
+ ),
728
+ );
729
+ return;
730
+ }
731
+ throw new AppRuntimeApiTransportError({
732
+ action: input.action,
733
+ attempts: input.attempt,
734
+ cause: input.error,
735
+ });
736
+ }
737
+
604
738
  export class AppRuntimeApiTransportError extends Error {
605
739
  readonly action: RuntimeApiRequest['action'];
606
740
  readonly attempts: number;
@@ -691,9 +825,40 @@ async function postAppRuntimeApi<TResponse>(
691
825
  });
692
826
  }
693
827
  if (response.ok) {
694
- return (await response.json()) as TResponse;
828
+ try {
829
+ return (await response.json()) as TResponse;
830
+ } catch (error) {
831
+ await retryAppRuntimeBodyTimeoutOrThrow({
832
+ action: body.action,
833
+ attempt,
834
+ maxAttempts,
835
+ error,
836
+ });
837
+ continue;
838
+ }
839
+ }
840
+ let responseText: string;
841
+ try {
842
+ responseText = await response.text();
843
+ } catch (error) {
844
+ if (
845
+ isRequestTimeoutAbort(error) &&
846
+ !isRetryableAppRuntimeResponseStatus(response.status)
847
+ ) {
848
+ throw new Error(
849
+ `App runtime API ${body.action} failed with status ${response.status}: response body timed out`,
850
+ { cause: error },
851
+ );
852
+ }
853
+ await retryAppRuntimeBodyTimeoutOrThrow({
854
+ action: body.action,
855
+ attempt,
856
+ maxAttempts,
857
+ error,
858
+ retryAfterMs: appRuntimeRetryAfterMs(response, ''),
859
+ });
860
+ continue;
695
861
  }
696
- const responseText = await response.text();
697
862
  if (
698
863
  attempt < maxAttempts &&
699
864
  isRetryableAppRuntimeResponse({
@@ -917,7 +1082,9 @@ async function updateRunStatusViaAppRuntimeUnlocked(
917
1082
  // durable stream can recover any progress flush that was lost in transit.
918
1083
  const sentCount = isTerminalRuntimeStatus(update.status)
919
1084
  ? 0
920
- : (runLogChannelSentCounts.get(update.playId) ?? 0);
1085
+ : (runLogChannelSentCounts.get(
1086
+ runLogChannelKey(update.playId, update.liveLogProducerAttempt),
1087
+ ) ?? 0);
921
1088
  const logSlice = Array.isArray(update.liveLogs)
922
1089
  ? slicePositionalLogLines({
923
1090
  bufferLines: update.liveLogs,
@@ -932,6 +1099,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
932
1099
  ? {
933
1100
  lines: update.liveLogs,
934
1101
  totalCount: update.liveLogTotalCount,
1102
+ producerAttempt: update.liveLogProducerAttempt,
935
1103
  }
936
1104
  : null;
937
1105
  const statusPatchLogSlice = terminalLogReplay ? null : logSlice;
@@ -944,6 +1112,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
944
1112
  lastCheckpointAt: update.lastCheckpointAt ?? null,
945
1113
  liveLogs: statusPatchLogSlice?.lines ?? null,
946
1114
  liveLogsChannelOffset: statusPatchLogSlice?.channelOffset ?? null,
1115
+ liveLogsProducerAttempt: update.liveLogProducerAttempt ?? null,
947
1116
  liveNodeProgress: update.liveNodeProgress ?? null,
948
1117
  result: update.result,
949
1118
  },
@@ -951,33 +1120,87 @@ async function updateRunStatusViaAppRuntimeUnlocked(
951
1120
  now: update.lastCheckpointAt ?? Date.now(),
952
1121
  source: 'worker',
953
1122
  });
954
- if (terminalLogReplay && terminalLogReplay.lines.length > 0) {
955
- events.push(
956
- ...buildTerminalLogReplayEvents({
957
- runId: update.playId,
958
- source: 'worker',
959
- occurredAt: update.lastCheckpointAt ?? Date.now(),
960
- lines: terminalLogReplay.lines,
961
- liveLogTotalCount: terminalLogReplay.totalCount,
962
- }),
963
- );
1123
+ for (const event of update.datasetLifecycleEvents ?? []) {
1124
+ const current = previousSnapshot.datasetsById[event.datasetId];
1125
+ const changesSnapshot =
1126
+ !current ||
1127
+ current.path !== event.path ||
1128
+ current.tableNamespace !== event.tableNamespace ||
1129
+ current.phase !== event.phase ||
1130
+ (event.persistedRows ?? 0) > current.persistedRows ||
1131
+ (event.succeededRows ?? 0) > current.succeededRows ||
1132
+ (event.failedRows ?? 0) > current.failedRows ||
1133
+ (event.complete === true && current.complete !== true);
1134
+ if (!changesSnapshot) continue;
1135
+ events.push({
1136
+ type: 'dataset.lifecycle',
1137
+ runId: update.playId,
1138
+ source: 'worker',
1139
+ occurredAt: event.at,
1140
+ datasetId: event.datasetId,
1141
+ path: event.path,
1142
+ tableNamespace: event.tableNamespace,
1143
+ phase: event.phase,
1144
+ ...(event.persistedRows === undefined
1145
+ ? {}
1146
+ : { persistedRows: event.persistedRows }),
1147
+ ...(event.succeededRows === undefined
1148
+ ? {}
1149
+ : { succeededRows: event.succeededRows }),
1150
+ ...(event.failedRows === undefined
1151
+ ? {}
1152
+ : { failedRows: event.failedRows }),
1153
+ ...(event.complete === undefined ? {} : { complete: event.complete }),
1154
+ });
964
1155
  }
965
- if (events.length === 0) {
1156
+ const terminalLogEvents =
1157
+ terminalLogReplay && terminalLogReplay.lines.length > 0
1158
+ ? buildTerminalLogReplayEvents({
1159
+ runId: update.playId,
1160
+ source: 'worker',
1161
+ occurredAt: update.lastCheckpointAt ?? Date.now(),
1162
+ lines: terminalLogReplay.lines,
1163
+ liveLogTotalCount: terminalLogReplay.totalCount,
1164
+ dedupeMode: terminalLogReplay.dedupeMode,
1165
+ producerAttempt: terminalLogReplay.producerAttempt,
1166
+ })
1167
+ : [];
1168
+ if (events.length === 0 && terminalLogEvents.length === 0) {
966
1169
  return;
967
1170
  }
968
- await appendRunEventsViaAppRuntime(context, {
969
- playId: update.playId,
970
- events,
971
- });
1171
+ const combinedEvents = [...events, ...terminalLogEvents];
1172
+ if (partitionRunEventsForAppRuntime(combinedEvents).length === 1) {
1173
+ // Preserve the single-mutation fast path for ordinary bounded updates.
1174
+ await appendRunEventsViaAppRuntime(context, {
1175
+ playId: update.playId,
1176
+ events: combinedEvents,
1177
+ });
1178
+ } else {
1179
+ // Persist terminal lifecycle first so an oversized or interrupted
1180
+ // best-effort log heal can never prevent settlement. Each bounded log
1181
+ // batch is a separate mutation; retries are safe because ingestion owns
1182
+ // positional reconciliation.
1183
+ await appendRunEventBatchesViaAppRuntime(context, {
1184
+ playId: update.playId,
1185
+ events,
1186
+ });
1187
+ await appendRunEventBatchesViaAppRuntime(context, {
1188
+ playId: update.playId,
1189
+ events: terminalLogEvents,
1190
+ });
1191
+ }
972
1192
  if (statusPatchLogSlice) {
973
1193
  runLogChannelSentCounts.set(
974
- update.playId,
1194
+ runLogChannelKey(update.playId, update.liveLogProducerAttempt),
975
1195
  statusPatchLogSlice.channelOffset + statusPatchLogSlice.lines.length,
976
1196
  );
977
1197
  }
978
1198
  rememberRunStatusLedgerSnapshot(
979
1199
  update.playId,
980
- reducePlayRunLedgerEvents(previousSnapshot, events),
1200
+ reducePlayRunLedgerEvents(previousSnapshot, [
1201
+ ...events,
1202
+ ...terminalLogEvents,
1203
+ ]),
981
1204
  );
982
1205
  }
983
1206