deepline 0.3.247 → 0.3.248

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 (65) 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 +142 -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/protocol.ts +9 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +40 -4
  23. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -5
  24. package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +3 -1
  25. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +28 -1
  26. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +14 -1
  27. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +56 -16
  28. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +184 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +327 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +87 -30
  31. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +24 -13
  32. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +23 -6
  33. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +76 -33
  34. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +90 -35
  35. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +16 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +62 -21
  37. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +368 -66
  38. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +22 -4
  39. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +105 -121
  40. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +1347 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +16 -19
  42. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +14 -6
  43. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +16 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +40 -0
  45. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +5 -2
  46. package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +11 -7
  47. package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +5 -2
  48. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +11 -2
  49. package/dist/cli/index.js +3504 -2368
  50. package/dist/cli/index.mjs +3188 -2052
  51. package/dist/{compiler-manifest-n8ZtPv2h.d.mts → compiler-manifest-DavmLa8y.d.mts} +1 -0
  52. package/dist/{compiler-manifest-n8ZtPv2h.d.ts → compiler-manifest-DavmLa8y.d.ts} +1 -0
  53. package/dist/index.d.mts +97 -5
  54. package/dist/index.d.ts +97 -5
  55. package/dist/index.js +135 -15
  56. package/dist/index.mjs +135 -15
  57. package/dist/install-integrity.json +9 -2
  58. package/dist/plays/bundle-play-file.d.mts +2 -2
  59. package/dist/plays/bundle-play-file.d.ts +2 -2
  60. package/dist/plays/bundle-play-file.mjs +1 -1
  61. package/dist/release.d.mts +1 -1
  62. package/dist/release.d.ts +1 -1
  63. package/dist/release.js +1 -1
  64. package/dist/release.mjs +1 -1
  65. package/package.json +1 -1
@@ -3,9 +3,11 @@ import type {
3
3
  PlayDatasetColumnTypes,
4
4
  PlaySheetContract,
5
5
  } from './contracts';
6
+ import { RUNTIME_SHEET_SYSTEM_COLUMNS } from './runtime-sheet-orm';
6
7
  import {
7
8
  legacyPostgresTruncatedPlayColumnName,
8
9
  sqlSafePlayColumnName,
10
+ sqlSafePlayStepColumnName,
9
11
  } from './column-names';
10
12
 
11
13
  export type PhysicalSheetColumnProjection = {
@@ -20,6 +22,7 @@ export type LegacyPhysicalSheetColumnBackfill = {
20
22
  };
21
23
 
22
24
  const RUNTIME_SHEET_SYSTEM_FIELDS = new Set([
25
+ ...RUNTIME_SHEET_SYSTEM_COLUMNS,
23
26
  '_key',
24
27
  '_status',
25
28
  '_run_id',
@@ -51,8 +54,18 @@ export function physicalSheetColumnProjections(
51
54
  const fieldName =
52
55
  column.source === 'input' || column.source === 'datasetColumn'
53
56
  ? column.field?.trim() || column.id.trim() || sqlName
54
- : sqlName;
55
- if (!sqlName || sqlName.startsWith('_') || seen.has(sqlName)) {
57
+ : column.source === 'waterfallStep' &&
58
+ column.stepId &&
59
+ sqlName ===
60
+ sqlSafePlayStepColumnName(
61
+ column.field ?? column.waterfallId ?? column.id,
62
+ column.stepId,
63
+ )
64
+ ? // The runtime's logical patch/checkpoint keys are intentionally stable.
65
+ // Only newly compiled physical storage names use the debug prefix.
66
+ sqlSafePlayColumnName(column.id)
67
+ : sqlName;
68
+ if (!isDatasetPayloadField(sqlName) || seen.has(sqlName)) {
56
69
  continue;
57
70
  }
58
71
  seen.add(sqlName);
@@ -65,7 +78,7 @@ export function physicalSheetColumnProjections(
65
78
  // A prior run may have introduced fields absent from the current artifact.
66
79
  // Keep their live storage projection so reads and writes do not hide them.
67
80
  for (const sqlName of physicalColumns ?? []) {
68
- if (!sqlName || sqlName.startsWith('_') || seen.has(sqlName)) continue;
81
+ if (!isDatasetPayloadField(sqlName) || seen.has(sqlName)) continue;
69
82
  seen.add(sqlName);
70
83
  columns.push({ sqlName, fieldName: sqlName });
71
84
  }
@@ -90,7 +103,7 @@ export function legacyPhysicalSheetColumnBackfills(
90
103
  const backfills: LegacyPhysicalSheetColumnBackfill[] = [];
91
104
  for (const column of sheetContract.columns) {
92
105
  const sqlName = column.sqlName.trim();
93
- if (!sqlName || sqlName.startsWith('_')) {
106
+ if (!isDatasetPayloadField(sqlName)) {
94
107
  continue;
95
108
  }
96
109
  const candidates = [
@@ -99,6 +112,8 @@ export function legacyPhysicalSheetColumnBackfills(
99
112
  ];
100
113
  for (const candidate of candidates) {
101
114
  if (!candidate) continue;
115
+ // Prefix-preserving columns must not borrow an unrelated non-debug field.
116
+ if (candidate.startsWith('_') && sqlName.startsWith('_')) continue;
102
117
  const legacySqlName = legacyPostgresTruncatedPlayColumnName(candidate);
103
118
  const key = legacySqlName ? `${sqlName}:${legacySqlName}` : '';
104
119
  if (!legacySqlName || legacySqlName === sqlName || seen.has(key)) {
@@ -148,8 +163,7 @@ export function outputPhysicalSheetColumnProjections(
148
163
  if (
149
164
  column.source === 'input' ||
150
165
  typeof column.field !== 'string' ||
151
- !sqlName ||
152
- sqlName.startsWith('_') ||
166
+ !isDatasetPayloadField(sqlName) ||
153
167
  seen.has(sqlName)
154
168
  ) {
155
169
  continue;
@@ -194,6 +208,14 @@ export function augmentSheetContractWithDatasetFields(input: {
194
208
  }
195
209
 
196
210
  const existingDatasetPayloadFields = new Set<string>();
211
+ const existingPatchFields = new Set(
212
+ physicalSheetColumnProjections({
213
+ ...input.contract,
214
+ columns: input.contract.columns.filter(
215
+ (column) => column.source === 'waterfallStep',
216
+ ),
217
+ }).map((column) => column.fieldName),
218
+ );
197
219
  const existingSqlNames = new Set<string>();
198
220
  const inputColumns: PlaySheetContract['columns'] = [];
199
221
  const outputColumns: PlaySheetContract['columns'] = [];
@@ -239,8 +261,18 @@ export function augmentSheetContractWithDatasetFields(input: {
239
261
  }
240
262
 
241
263
  for (const field of candidateFields) {
242
- if (existingDatasetPayloadFields.has(field)) continue;
243
- const sqlName = sqlSafePlayColumnName(field);
264
+ if (
265
+ existingDatasetPayloadFields.has(field) ||
266
+ existingPatchFields.has(field)
267
+ )
268
+ continue;
269
+ // A stored contract is authoritative, including legacy underscore aliases.
270
+ const existingColumn = input.contract.columns.find(
271
+ (column) =>
272
+ (column.source === 'input' || column.source === 'datasetColumn') &&
273
+ (column.field ?? column.id) === field,
274
+ );
275
+ const sqlName = existingColumn?.sqlName ?? sqlSafePlayColumnName(field);
244
276
  if (existingSqlNames.has(sqlName)) continue;
245
277
  appendColumn(outputFields.has(field) ? outputColumns : inputColumns, {
246
278
  id: `runtime:${input.contract.tableNamespace}:${field}`,
@@ -9,6 +9,7 @@ import type { AsyncPlayRunRef } from '../plays/authoring-contract';
9
9
  import type { PlayRowUpdate } from './/ctx-types';
10
10
  import type { PlayRunTimelineEntry } from './/live-events';
11
11
  import type { PlayDatasetBornFrom } from './/cell-provenance';
12
+ import type { PlayLogContext } from './/log-context';
12
13
  import {
13
14
  routePlayActivityObservation,
14
15
  type PlayActivityObservation,
@@ -104,6 +105,8 @@ export type RuntimeStatusUpdate = {
104
105
  activeBoundaryId?: string | null;
105
106
  lastCheckpointAt?: number | null;
106
107
  liveLogs?: string[];
108
+ /** Search-only context aligned to `liveLogs`; stripped before Convex. */
109
+ liveLogContexts?: Array<PlayLogContext | null>;
107
110
  /**
108
111
  * Monotonic count of log lines the producer ever emitted on this run's
109
112
  * channel. `liveLogs` is a rotating tail of those lines; the total lets the
@@ -148,6 +151,7 @@ export type RuntimeStatusUpdate = {
148
151
  */
149
152
  terminalLogReplay?: {
150
153
  lines: string[];
154
+ lineContexts?: Array<PlayLogContext | null>;
151
155
  totalCount?: number;
152
156
  dedupeMode?: 'semantic' | 'exact';
153
157
  producerAttempt?: number;
@@ -254,6 +258,7 @@ type RuntimeApiRequest =
254
258
  runtimeBackend?: string | null;
255
259
  schedulerBackend?: string | null;
256
260
  schedulerSchema?: string | null;
261
+ runLogStore?: 'planet_scale';
257
262
  executionProfile?: string | null;
258
263
  runtimeReleaseId?: string | null;
259
264
  runtimeDeployVersion?: string | null;
@@ -2190,6 +2195,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2190
2195
  const logSlice = Array.isArray(update.liveLogs)
2191
2196
  ? slicePositionalLogLines({
2192
2197
  bufferLines: update.liveLogs,
2198
+ bufferContexts: update.liveLogContexts,
2193
2199
  bufferTotalCount: update.liveLogTotalCount ?? update.liveLogs.length,
2194
2200
  sentCount,
2195
2201
  })
@@ -2200,11 +2206,28 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2200
2206
  : isTerminalRuntimeStatus(update.status) && Array.isArray(update.liveLogs)
2201
2207
  ? {
2202
2208
  lines: update.liveLogs,
2209
+ lineContexts: update.liveLogContexts,
2203
2210
  totalCount: update.liveLogTotalCount,
2204
2211
  producerAttempt: update.liveLogProducerAttempt,
2205
2212
  }
2206
2213
  : null;
2207
- const statusPatchLogSlice = terminalLogReplay ? null : logSlice;
2214
+ let statusPatchLogSlice = terminalLogReplay ? null : logSlice;
2215
+ // A direct worker's terminal replay can contain only its summary suffix.
2216
+ // Do not discard live lines that were observed but have not yet been sent.
2217
+ // Preserve the non-overlapping prefix; recovery still owns the replay range.
2218
+ if (
2219
+ terminalLogReplay &&
2220
+ logSlice &&
2221
+ terminalLogReplay.totalCount !== undefined
2222
+ ) {
2223
+ const replayOffset =
2224
+ terminalLogReplay.totalCount - terminalLogReplay.lines.length;
2225
+ const prefix = logSlice.lines.slice(
2226
+ 0,
2227
+ Math.max(0, replayOffset - logSlice.channelOffset),
2228
+ );
2229
+ if (prefix.length) statusPatchLogSlice = { ...logSlice, lines: prefix };
2230
+ }
2208
2231
  const events = buildPlayRunLedgerEventsFromStatusPatch({
2209
2232
  patch: {
2210
2233
  runId: update.playId,
@@ -2213,6 +2236,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2213
2236
  runtimeBackend: update.runtimeBackend ?? null,
2214
2237
  lastCheckpointAt: update.lastCheckpointAt ?? null,
2215
2238
  liveLogs: statusPatchLogSlice?.lines ?? null,
2239
+ liveLogContexts: statusPatchLogSlice?.lineContexts ?? null,
2216
2240
  liveLogsChannelOffset: statusPatchLogSlice?.channelOffset ?? null,
2217
2241
  liveLogsProducerAttempt: update.liveLogProducerAttempt ?? null,
2218
2242
  liveNodeProgress: update.liveNodeProgress ?? null,
@@ -2297,6 +2321,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2297
2321
  source: 'worker',
2298
2322
  occurredAt: update.lastCheckpointAt ?? Date.now(),
2299
2323
  lines: terminalLogReplay.lines,
2324
+ lineContexts: terminalLogReplay.lineContexts,
2300
2325
  liveLogTotalCount: terminalLogReplay.totalCount,
2301
2326
  dedupeMode: terminalLogReplay.dedupeMode,
2302
2327
  producerAttempt: terminalLogReplay.producerAttempt,
@@ -13,6 +13,7 @@
13
13
  * - onBatchComplete: called after each provider batch for scheduler checkpointing
14
14
  */
15
15
  import { AsyncLocalStorage } from 'async_hooks';
16
+ import { isDatasetPayloadField } from '../play-data-plane/sheet-contract';
16
17
  import {
17
18
  createDeferredPlayDataset,
18
19
  deserializeLegacyPlayDataset,
@@ -447,7 +448,12 @@ import type {
447
448
  PlayAsyncCallOptions,
448
449
  } from './ctx-types';
449
450
  import { assertKnownResolvedBoundary } from './checkpoint-boundaries';
450
- import { RunLogBuffer } from './run-log-buffer';
451
+ import {
452
+ boundRunLogLine,
453
+ RunLogBuffer,
454
+ RUNNER_LOG_CONTEXT_MAX_BYTES,
455
+ } from './run-log-buffer';
456
+ import { normalizePlayLogContext, type PlayLogContext } from './log-context';
451
457
  import type { AsyncPlayRunRef } from '../plays/authoring-contract';
452
458
  import { admitAsyncPlayLineage } from './async-play-invocation';
453
459
  import {
@@ -1896,7 +1902,7 @@ const ACTIVE_MAP_CHECKPOINT_ROW_META_LEDGER_KEY = Symbol(
1896
1902
  );
1897
1903
 
1898
1904
  function shouldPersistMapCellField(fieldName: string): boolean {
1899
- return !fieldName.startsWith('_') || fieldName === '_metadata';
1905
+ return isDatasetPayloadField(fieldName);
1900
1906
  }
1901
1907
 
1902
1908
  function runtimeSheetPatchFieldName(fieldName: string): string {
@@ -1991,6 +1997,7 @@ type RuntimeMapRowPersistence = {
1991
1997
  function createRuntimeMapRowPersistence(
1992
1998
  persistRows: (rows: PersistableMapRow[]) => Promise<void>,
1993
1999
  options?: {
2000
+ prepareTerminalRow?: (row: PersistableMapRow) => PersistableMapRow;
1994
2001
  persistCheckpoint?: (updates: PlayRowUpdate[]) => Promise<void>;
1995
2002
  onFailure?: (error: unknown) => void;
1996
2003
  /**
@@ -2066,7 +2073,9 @@ function createRuntimeMapRowPersistence(
2066
2073
  continue;
2067
2074
  }
2068
2075
  if (identity) queued.add(identity);
2069
- const settlement = writer.settle(row);
2076
+ const settlement = writer.settle(
2077
+ options?.prepareTerminalRow?.(row) ?? row,
2078
+ );
2070
2079
  admissions.push(
2071
2080
  settlement.admitted.catch((error) => {
2072
2081
  clearQueuedState(identity);
@@ -2672,6 +2681,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2672
2681
  private readonly rootRuntimeContract: PlayArtifactRuntimeContract;
2673
2682
  private readonly executionScope: RunExecutionScope;
2674
2683
  private readonly logBuffer = new RunLogBuffer();
2684
+ private logContextBudgetWarningLogged = false;
2675
2685
  private readonly logRateLimitState = new Map<
2676
2686
  Exclude<LogLevel, 'warn' | 'error'>,
2677
2687
  LogRateLimitState
@@ -3438,6 +3448,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3438
3448
  // read. Same reasoning as `__deeplineDocflowHit` above: injected at compile
3439
3449
  // time, so the run is the only place left to decide.
3440
3450
  if (!this.docflowCaptureEnabled) return execute();
3451
+ // Inline composition changes logical identity, but its gateway token and
3452
+ // durable observations remain owned by the executing receipt scope.
3453
+ const runId = this.currentReceiptOwnerRunId;
3441
3454
  const attempt = this.currentRunAttempt;
3442
3455
  const inputs = buildPlayDocflowNodeInputPreviewMap(inputCaptures, {
3443
3456
  redactor: this.secretRedactor,
@@ -3455,7 +3468,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3455
3468
  at: Date.now(),
3456
3469
  });
3457
3470
  this.emitDocflowObservation({
3458
- runId: this.currentGovernance.currentRunId,
3471
+ runId,
3459
3472
  attempt,
3460
3473
  nodeId: observationNodeId,
3461
3474
  invocationSeq,
@@ -3501,7 +3514,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3501
3514
  at: Date.now(),
3502
3515
  });
3503
3516
  this.emitDocflowObservation({
3504
- runId: this.currentGovernance.currentRunId,
3517
+ runId,
3505
3518
  attempt,
3506
3519
  nodeId: observationNodeId,
3507
3520
  invocationSeq,
@@ -3533,7 +3546,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3533
3546
  at: Date.now(),
3534
3547
  });
3535
3548
  this.emitDocflowObservation({
3536
- runId: this.currentGovernance.currentRunId,
3549
+ runId,
3537
3550
  attempt,
3538
3551
  nodeId: observationNodeId,
3539
3552
  invocationSeq,
@@ -3770,6 +3783,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3770
3783
  });
3771
3784
  }
3772
3785
 
3786
+ private withPendingMapRowData(
3787
+ row: PersistableMapRow,
3788
+ tableNamespace: string,
3789
+ ): PersistableMapRow {
3790
+ const patch = this.activeMapCheckpointUpdates?.get(
3791
+ `${tableNamespace}\u0000${row.key}`,
3792
+ )?.dataPatch;
3793
+ // Nested step values exist in cell patches, not the authored return row.
3794
+ // Commit them with the terminal row before clearing its checkpoint state,
3795
+ // without adding internal fields to the immediate authored result snapshot.
3796
+ return patch ? { ...row, data: { ...row.data, ...patch } } : row;
3797
+ }
3798
+
3773
3799
  private clearActiveMapCheckpointUpdate(
3774
3800
  key: string,
3775
3801
  tableNamespace: string,
@@ -8646,6 +8672,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8646
8672
  if (!this.#options.onMapRowsCompleted || rows.length === 0) {
8647
8673
  return;
8648
8674
  }
8675
+ rows = rows.map((row) =>
8676
+ this.withPendingMapRowData(row, resolvedTableNamespace),
8677
+ );
8649
8678
  for (const row of rows) {
8650
8679
  assertPersistableMapRowWithinCustomerOutputLimits({
8651
8680
  mapName: normalizedMapNamespace,
@@ -8870,6 +8899,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8870
8899
  const incrementalPersistence = createRuntimeMapRowPersistence(
8871
8900
  persistMapRows,
8872
8901
  {
8902
+ prepareTerminalRow: (row) =>
8903
+ this.withPendingMapRowData(row, resolvedTableNamespace),
8873
8904
  persistCheckpoint: this.#options.onMapRowsCheckpoint
8874
8905
  ? (updates) =>
8875
8906
  this.#options.onMapRowsCheckpoint!({
@@ -9717,6 +9748,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9717
9748
  if (!this.#options.onMapRowsCompleted || rows.length === 0) {
9718
9749
  return;
9719
9750
  }
9751
+ rows = rows.map((row) =>
9752
+ this.withPendingMapRowData(row, resolvedTableNamespace),
9753
+ );
9720
9754
  for (const row of rows) {
9721
9755
  assertPersistableMapRowWithinCustomerOutputLimits({
9722
9756
  mapName: normalizedMapNamespace,
@@ -9770,6 +9804,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9770
9804
  };
9771
9805
  const incrementalPersistence = this.#options.onMapRowsCompleted
9772
9806
  ? createRuntimeMapRowPersistence(persistMapRows, {
9807
+ prepareTerminalRow: (row) =>
9808
+ this.withPendingMapRowData(row, resolvedTableNamespace),
9773
9809
  persistCheckpoint: this.#options.onMapRowsCheckpoint
9774
9810
  ? (updates) =>
9775
9811
  this.#options.onMapRowsCheckpoint!({
@@ -11750,12 +11786,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11750
11786
  key: rowStore.rowKey ?? null,
11751
11787
  tableNamespace: rowStore.tableNamespace ?? null,
11752
11788
  fieldName,
11753
- status: 'completed',
11789
+ status: stageExecution.skipped ? 'skipped' : 'completed',
11754
11790
  rowStatus: 'running',
11755
- stage: 'completed',
11791
+ stage: stageExecution.skipped ? 'skipped' : 'completed',
11756
11792
  provider: null,
11757
11793
  error: null,
11758
- completedAt: Date.now(),
11794
+ ...(stageExecution.skipped ? {} : { completedAt: Date.now() }),
11759
11795
  dataPatch: { [patchFieldName]: cellValue },
11760
11796
  });
11761
11797
  }
@@ -12773,6 +12809,62 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12773
12809
  toolId: string,
12774
12810
  input: Record<string, unknown>,
12775
12811
  options?: ToolCallOptions,
12812
+ ): Promise<unknown> {
12813
+ const startedAt = Date.now();
12814
+ const row = rowContext.getStore();
12815
+ // Capture caller scope before asynchronous dispatch. Provider batches do
12816
+ // not own a single row, and direct calls have no dataset scope at all.
12817
+ const context = {
12818
+ run_id: this.currentRunId,
12819
+ ...(row?.tableNamespace ? { dataset: row.tableNamespace } : {}),
12820
+ ...(row?.rowKey ? { row_key: row.rowKey } : {}),
12821
+ ...(row?.fieldName ? { column: row.fieldName } : {}),
12822
+ call_key: this.normalizeContextKey(key, 'tool'),
12823
+ tool: toolId,
12824
+ };
12825
+ let result: unknown;
12826
+ try {
12827
+ result = await this.executeToolInternal(key, toolId, input, options);
12828
+ } catch (error) {
12829
+ // Parking is control flow, not a failed tool execution.
12830
+ if (
12831
+ !isPlayExecutionSuspendedError(error) &&
12832
+ !isPlayRowExecutionSuspendedError(error) &&
12833
+ !(error instanceof PlayRowProviderBackpressureError)
12834
+ ) {
12835
+ this.runtimeLog(
12836
+ JSON.stringify({
12837
+ event: 'tool_call_failed',
12838
+ ...context,
12839
+ status: 'failed',
12840
+ duration_ms: Math.max(0, Date.now() - startedAt),
12841
+ }),
12842
+ );
12843
+ }
12844
+ throw error;
12845
+ }
12846
+ const toolResult = isToolExecuteResult(result) ? result : undefined;
12847
+ const execution = toolResult?._metadata?.execution;
12848
+ this.runtimeLog(
12849
+ JSON.stringify({
12850
+ event: 'tool_call_completed',
12851
+ ...context,
12852
+ ...(execution?.receiptKey ? { receipt_key: execution.receiptKey } : {}),
12853
+ ...(execution?.source ? { execution_source: execution.source } : {}),
12854
+ status: toolResult?.status ?? 'completed',
12855
+ // Caller wall time includes queueing/reuse and result delivery. It is
12856
+ // deliberately not described as provider request duration.
12857
+ duration_ms: Math.max(0, Date.now() - startedAt),
12858
+ }),
12859
+ );
12860
+ return result;
12861
+ }
12862
+
12863
+ private async executeToolInternal(
12864
+ key: string,
12865
+ toolId: string,
12866
+ input: Record<string, unknown>,
12867
+ options?: ToolCallOptions,
12776
12868
  ): Promise<unknown> {
12777
12869
  if (!rowContext.getStore()) this.assertRowAdmissionOpen();
12778
12870
  this.assertNoPendingDurableSuspension();
@@ -14420,15 +14512,33 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14420
14512
  );
14421
14513
  }
14422
14514
 
14423
- private appendRunLog(line: string, terminal = false): void {
14424
- for (const retained of this.logBuffer.append(line, terminal)) {
14425
- this.#options.onLog?.(retained);
14515
+ private appendRunLog(
14516
+ line: string,
14517
+ terminal = false,
14518
+ context?: PlayLogContext | null,
14519
+ ): void {
14520
+ const boundedLine = boundRunLogLine(line);
14521
+ const omittedBefore = this.logBuffer.snapshot().omittedLines;
14522
+ for (const retained of this.logBuffer.append(line, terminal, context)) {
14523
+ const originalLineWasRetained =
14524
+ retained === boundedLine &&
14525
+ this.logBuffer.snapshot().omittedLines === omittedBefore;
14526
+ this.#options.onLog?.(
14527
+ retained,
14528
+ originalLineWasRetained
14529
+ ? (this.logBuffer.contexts.at(-1) ?? undefined)
14530
+ : undefined,
14531
+ );
14426
14532
  if (this.#options.verbose) console.log(retained);
14427
14533
  }
14428
14534
  }
14429
14535
 
14430
- log(msg: string, options?: { level?: LogLevel }): void {
14536
+ log(
14537
+ msg: string,
14538
+ options?: { level?: LogLevel; context?: PlayLogContext },
14539
+ ): void {
14431
14540
  assertNoSecretTaint(msg, 'ctx.log');
14541
+ assertNoSecretTaint(options?.context, 'ctx.log context');
14432
14542
  const requestedLevel = options?.level;
14433
14543
  if (requestedLevel !== undefined && !LOG_LEVELS.includes(requestedLevel)) {
14434
14544
  throw new Error(
@@ -14436,12 +14546,26 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14436
14546
  );
14437
14547
  }
14438
14548
  const level = requestedLevel ?? 'info';
14549
+ const context = normalizePlayLogContext(options?.context);
14550
+ const redactedContext = context
14551
+ ? normalizePlayLogContext(this.secretRedactor.redact(context))
14552
+ : null;
14439
14553
  const timestamped = `[${new Date().toISOString()}] ${this.secretRedactor.redactRegisteredSecrets(msg)}`;
14440
14554
  // Stamp the default too: a customer message that happens to begin with
14441
14555
  // "[error]" is still info unless the author selected a higher severity.
14442
14556
  const line = tagLogEntry('user', level, timestamped);
14443
14557
  if (!this.shouldKeepRateLimitedLog(level)) return;
14444
- this.appendRunLog(line);
14558
+ const omittedContextsBefore = this.logBuffer.snapshot().omittedContexts;
14559
+ this.appendRunLog(line, false, redactedContext);
14560
+ if (
14561
+ !this.logContextBudgetWarningLogged &&
14562
+ this.logBuffer.snapshot().omittedContexts > omittedContextsBefore
14563
+ ) {
14564
+ this.logContextBudgetWarningLogged = true;
14565
+ this.appendRunLog(
14566
+ `[warn] [structured log context budget reached: later ctx.log messages are retained, but their context fields are omitted for this run; budget ${RUNNER_LOG_CONTEXT_MAX_BYTES} bytes.]`,
14567
+ );
14568
+ }
14445
14569
  }
14446
14570
 
14447
14571
  /**
@@ -15087,6 +15211,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15087
15211
  return this.logBuffer.lines;
15088
15212
  }
15089
15213
 
15214
+ getLogContexts(): Array<PlayLogContext | null> {
15215
+ return this.logBuffer.contexts.slice();
15216
+ }
15217
+
15090
15218
  assertRowAdmissionOpen(): void {
15091
15219
  this.#options.rowAdmissionSignal?.throwIfAborted();
15092
15220
  }
@@ -10,6 +10,7 @@ import type { PlayRunContractSnapshot } from '../plays/contracts';
10
10
  import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
11
11
  import type { PlayStructuredDefinition } from '../plays/definition';
12
12
  import type { PlayStaticPipeline } from '../plays/static-pipeline';
13
+ import type { PlayLogContext } from './log-context';
13
14
  import type {
14
15
  AsyncPlayRunRef,
15
16
  DurableCallStaleAfterSeconds,
@@ -842,7 +843,7 @@ export interface ContextOptions {
842
843
  /** Called after each durable batch completes for scheduler checkpointing. */
843
844
  onBatchComplete?: (checkpoint: PlayCheckpoint) => void;
844
845
  /** Called when the runtime emits a new execution log line. */
845
- onLog?: (line: string) => void;
846
+ onLog?: (line: string, context?: PlayLogContext) => void;
846
847
  /**
847
848
  * Runtime-provided HTTP transport for ctx.fetch. Node runtimes should provide
848
849
  * a transport that validates resolved IP addresses at connect time.
@@ -16,11 +16,14 @@ export type DaytonaRunnerPathsConfig = {
16
16
  };
17
17
 
18
18
  export function daytonaClientOptions(apiKey: string): DaytonaClientOptions {
19
+ // Preserve the SDK's legacy URL fallback in the shared configuration owner,
20
+ // including for generated control clients that do not read SDK env files.
21
+ const apiUrl =
22
+ process.env.DAYTONA_API_URL?.trim() ||
23
+ process.env.DAYTONA_SERVER_URL?.trim();
19
24
  return {
20
25
  apiKey,
21
- ...(process.env.DAYTONA_API_URL?.trim()
22
- ? { apiUrl: process.env.DAYTONA_API_URL.trim() }
23
- : {}),
26
+ ...(apiUrl ? { apiUrl } : {}),
24
27
  ...(process.env.DAYTONA_TARGET?.trim()
25
28
  ? { target: process.env.DAYTONA_TARGET.trim() }
26
29
  : {}),
@@ -44,38 +44,43 @@ export function startDaytonaSandboxJanitor(
44
44
  running = true;
45
45
  try {
46
46
  const [
47
- { Daytona },
47
+ { listDaytonaControlSandboxes },
48
48
  { loadDaytonaRequiredConfig },
49
49
  {
50
50
  createPostgresDaytonaJanitorOwnershipPort,
51
51
  reapOrphanDaytonaSandboxes,
52
52
  },
53
53
  ] = await Promise.all([
54
- import('@daytonaio/sdk'),
54
+ import('./runner-backends/backends/daytona-control-transport'),
55
55
  import('.//daytona-runtime-config'),
56
56
  import('./daytona-sandbox-reaper'),
57
57
  ]);
58
58
  const { clientOptions } = loadDaytonaRequiredConfig();
59
- const daytona = new Daytona(clientOptions);
60
- await withRuntimeSchedulerClient(
61
- {
62
- url: options.url,
63
- applicationName: 'deepline-daytona-janitor',
64
- maxClients:
65
- options.maxClients ??
66
- RUNTIME_CAPACITY_POLICY.runtimePostgres.schedulerPoolMaxClients,
59
+ await reapOrphanDaytonaSandboxes({
60
+ daytona: {
61
+ list: (query) => listDaytonaControlSandboxes(clientOptions, query),
67
62
  },
68
- (client) =>
69
- reapOrphanDaytonaSandboxes({
70
- daytona: {
71
- list: (query) => daytona.list(query),
72
- },
73
- ownership: createPostgresDaytonaJanitorOwnershipPort({
74
- client,
75
- schema: options.schema,
76
- }),
77
- }),
78
- );
63
+ ownership: createPostgresDaytonaJanitorOwnershipPort({
64
+ // Provider pagination and per-sandbox work must never retain one of
65
+ // the controller's shared scheduler connections. Each ownership
66
+ // operation is one self-contained SQL statement (including its CTE).
67
+ client: {
68
+ query: (sql, params) =>
69
+ withRuntimeSchedulerClient(
70
+ {
71
+ url: options.url,
72
+ applicationName: 'deepline-daytona-janitor',
73
+ maxClients:
74
+ options.maxClients ??
75
+ RUNTIME_CAPACITY_POLICY.runtimePostgres
76
+ .schedulerPoolMaxClients,
77
+ },
78
+ (client) => client.query(sql, params),
79
+ ),
80
+ },
81
+ schema: options.schema,
82
+ }),
83
+ });
79
84
  } catch (error) {
80
85
  // This is a best-effort resource backstop. A failed sweep must never
81
86
  // terminate the controller that scales and recovers customer work.
@@ -66,8 +66,8 @@ export type ReapableSandbox = {
66
66
  };
67
67
 
68
68
  /**
69
- * Injected Daytona port. `list` yields our label-scoped sandboxes (the SDK
70
- * `list` returns an async iterator that auto-pages, so we consume it lazily).
69
+ * Injected Daytona port. `list` yields our label-scoped sandboxes through an
70
+ * async iterator that auto-pages, so we consume it lazily.
71
71
  * Inventory is the only Daytona surface the janitor touches.
72
72
  */
73
73
  export type DaytonaJanitorClientPort = {
@@ -659,7 +659,9 @@ export function createPostgresDaytonaJanitorOwnershipPort(input: {
659
659
  // CLI entry.
660
660
  // ---------------------------------------------------------------------------
661
661
 
662
- export async function runDaytonaSandboxJanitorCli(argv: string[]): Promise<void> {
662
+ export async function runDaytonaSandboxJanitorCli(
663
+ argv: string[],
664
+ ): Promise<void> {
663
665
  const dryRun = argv.includes('--dry-run');
664
666
  // Escape hatch for deliberate debugging only: reaping against an isolated
665
667
  // schema is unsafe because the `source`-scoped fleet is shared across lanes
@@ -669,13 +671,13 @@ export async function runDaytonaSandboxJanitorCli(argv: string[]): Promise<void>
669
671
  // Lazy imports so the pure core above stays free of heavy runtime deps and
670
672
  // remains trivially unit-testable with fake ports.
671
673
  const [
672
- { Daytona },
674
+ { listDaytonaControlSandboxes },
673
675
  { loadDaytonaRequiredConfig },
674
676
  poolModule,
675
677
  schemaModule,
676
678
  { isIsolatedRuntimeSchedulerSchema },
677
679
  ] = await Promise.all([
678
- import('@daytonaio/sdk'),
680
+ import('./runner-backends/backends/daytona-control-transport'),
679
681
  import('.//daytona-runtime-config'),
680
682
  import('.//scheduler-backends/postgres-pool'),
681
683
  import('.//scheduler-backends/runtime-scheduler-schema'),
@@ -704,22 +706,22 @@ export async function runDaytonaSandboxJanitorCli(argv: string[]): Promise<void>
704
706
  }
705
707
 
706
708
  const { clientOptions } = loadDaytonaRequiredConfig();
707
- const daytona = new Daytona(clientOptions);
708
-
709
- const summary = await poolModule.withRuntimeSchedulerClient(
710
- { url, applicationName: 'deepline-daytona-janitor' },
711
- (client) =>
712
- reapOrphanDaytonaSandboxes({
713
- daytona: {
714
- list: (query) => daytona.list(query),
715
- },
716
- ownership: createPostgresDaytonaJanitorOwnershipPort({
717
- client,
718
- schema,
719
- }),
720
- options: { dryRun },
721
- }),
722
- );
709
+ const summary = await reapOrphanDaytonaSandboxes({
710
+ daytona: {
711
+ list: (query) => listDaytonaControlSandboxes(clientOptions, query),
712
+ },
713
+ ownership: createPostgresDaytonaJanitorOwnershipPort({
714
+ client: {
715
+ query: (sql, params) =>
716
+ poolModule.withRuntimeSchedulerClient(
717
+ { url, applicationName: 'deepline-daytona-janitor' },
718
+ (client) => client.query(sql, params),
719
+ ),
720
+ },
721
+ schema,
722
+ }),
723
+ options: { dryRun },
724
+ });
723
725
 
724
726
  console.info('[daytona-janitor.cli_done]', { ...summary, schema, dryRun });
725
727
  }