deepline 0.1.171 → 0.1.172

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.
package/README.md CHANGED
@@ -195,7 +195,7 @@ Full sequence from zero to hitting + surviving rate limits via v2 plays.
195
195
 
196
196
  ```bash
197
197
  # Production
198
- bash <(curl -sS https://code.deepline.com/api/v2/sdk/install)
198
+ bash <(curl -fsSL https://deepline.com/api/v2/sdk/install)
199
199
 
200
200
  # Local dev (no install needed)
201
201
  bun run dev # start server
@@ -1719,7 +1719,10 @@ class RuntimeReceiptPersistenceError extends Error {
1719
1719
  function isRuntimeReceiptPersistenceError(error: unknown): boolean {
1720
1720
  if (!error || typeof error !== 'object') return false;
1721
1721
  if (error instanceof RuntimeReceiptPersistenceError) return true;
1722
- if (error instanceof Error && error.name === 'RuntimeReceiptPersistenceError') {
1722
+ if (
1723
+ error instanceof Error &&
1724
+ error.name === 'RuntimeReceiptPersistenceError'
1725
+ ) {
1723
1726
  return true;
1724
1727
  }
1725
1728
  const nestedErrors = (error as { errors?: unknown }).errors;
@@ -2559,8 +2562,7 @@ class WorkerToolBatchScheduler {
2559
2562
  { id: request.id, toolId, input: request.input },
2560
2563
  request.workflowStep,
2561
2564
  this.callbacks,
2562
- (retryAfterMs) =>
2563
- this.reportBackpressure(toolId, retryAfterMs),
2565
+ (retryAfterMs) => this.reportBackpressure(toolId, retryAfterMs),
2564
2566
  () => this.governor.chargeBudget('retry'),
2565
2567
  toolContract?.retrySafeTransientHttp === true,
2566
2568
  this.abortSignal,
@@ -4559,18 +4561,15 @@ function createMinimalWorkerCtx(
4559
4561
  if (!workflowStep) {
4560
4562
  return await executeWithRuntimeReceipt(name, execute);
4561
4563
  }
4562
- return await executeWithRuntimeReceipt(
4563
- name,
4564
- async () => {
4565
- const serialized = await (
4566
- workflowStep.do as unknown as (
4567
- name: string,
4568
- callback: () => Promise<unknown>,
4569
- ) => Promise<unknown>
4570
- )(name, async () => serializeDurableStepValue(await execute()));
4571
- return deserializeDurableStepValue(serialized) as T;
4572
- },
4573
- );
4564
+ return await executeWithRuntimeReceipt(name, async () => {
4565
+ const serialized = await (
4566
+ workflowStep.do as unknown as (
4567
+ name: string,
4568
+ callback: () => Promise<unknown>,
4569
+ ) => Promise<unknown>
4570
+ )(name, async () => serializeDurableStepValue(await execute()));
4571
+ return deserializeDurableStepValue(serialized) as T;
4572
+ });
4574
4573
  };
4575
4574
  const nextCtxStepReceiptKey = (
4576
4575
  name: string,
@@ -4653,7 +4652,6 @@ function createMinimalWorkerCtx(
4653
4652
  nodeId: mapNodeId,
4654
4653
  progress: {
4655
4654
  artifactTableNamespace: name,
4656
- failed: 0,
4657
4655
  ...progress,
4658
4656
  updatedAt: progress.updatedAt ?? nowMs(),
4659
4657
  },
@@ -5037,6 +5035,7 @@ function createMinimalWorkerCtx(
5037
5035
  {
5038
5036
  completed,
5039
5037
  total: progressTotalRows,
5038
+ failed: failedExecutedRows,
5040
5039
  startedAt: mapStartedAt,
5041
5040
  message: formatMapProgressMessage(completed, progressTotalRows),
5042
5041
  },
@@ -5081,7 +5080,7 @@ function createMinimalWorkerCtx(
5081
5080
  let stepCellsCompleted = 0;
5082
5081
  let stepCellsSkipped = 0;
5083
5082
  const generatedOutputFields = new Set<string>();
5084
- const pendingLiveRowUpdates: Array<
5083
+ const pendingLiveUpdates: Array<
5085
5084
  Omit<PlayRowUpdate, 'rowId'> & { runId?: string }
5086
5085
  > = [];
5087
5086
  const persistedExecutedIndexes = new Set<number>();
@@ -5091,9 +5090,9 @@ function createMinimalWorkerCtx(
5091
5090
  let scheduledPersistTimer: ReturnType<typeof setTimeout> | null = null;
5092
5091
  let persistFlushChain: Promise<void> = Promise.resolve();
5093
5092
  let persistFailure: unknown = null;
5094
- let scheduledLiveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
5095
- let liveUpdateFlushChain: Promise<void> = Promise.resolve();
5096
- let liveUpdateFailureCount = 0;
5093
+ let liveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
5094
+ let liveFlushChain: Promise<void> = Promise.resolve();
5095
+ let liveUpdateFailures = 0;
5097
5096
 
5098
5097
  const clearScheduledPersistTimer = () => {
5099
5098
  if (scheduledPersistTimer) {
@@ -5102,10 +5101,10 @@ function createMinimalWorkerCtx(
5102
5101
  }
5103
5102
  };
5104
5103
 
5105
- const clearScheduledLiveUpdateTimer = () => {
5106
- if (scheduledLiveUpdateTimer) {
5107
- clearTimeout(scheduledLiveUpdateTimer);
5108
- scheduledLiveUpdateTimer = null;
5104
+ const clearLiveUpdateTimer = () => {
5105
+ if (liveUpdateTimer) {
5106
+ clearTimeout(liveUpdateTimer);
5107
+ liveUpdateTimer = null;
5109
5108
  }
5110
5109
  };
5111
5110
 
@@ -5223,14 +5222,36 @@ function createMinimalWorkerCtx(
5223
5222
  return task;
5224
5223
  };
5225
5224
 
5225
+ const coalesceLiveUpdates = (
5226
+ updates: Array<Omit<PlayRowUpdate, 'rowId'> & { runId?: string }>,
5227
+ ): Array<Omit<PlayRowUpdate, 'rowId'> & { runId?: string }> => {
5228
+ const mergedByKey = new Map<
5229
+ string,
5230
+ Omit<PlayRowUpdate, 'rowId'> & { runId?: string }
5231
+ >();
5232
+ for (const update of updates) {
5233
+ const existing = mergedByKey.get(update.key);
5234
+ mergedByKey.set(update.key, {
5235
+ ...existing,
5236
+ ...update,
5237
+ dataPatch: { ...existing?.dataPatch, ...update.dataPatch },
5238
+ cellMetaPatch: {
5239
+ ...existing?.cellMetaPatch,
5240
+ ...update.cellMetaPatch,
5241
+ },
5242
+ });
5243
+ }
5244
+ return [...mergedByKey.values()];
5245
+ };
5246
+
5226
5247
  const flushLiveRowUpdates = (): Promise<void> => {
5227
- clearScheduledLiveUpdateTimer();
5228
- if (pendingLiveRowUpdates.length === 0) {
5229
- return liveUpdateFlushChain;
5248
+ clearLiveUpdateTimer();
5249
+ if (pendingLiveUpdates.length === 0) {
5250
+ return liveFlushChain;
5230
5251
  }
5231
- const updates = pendingLiveRowUpdates.splice(0);
5252
+ const updates = coalesceLiveUpdates(pendingLiveUpdates.splice(0));
5232
5253
  const extraOutputFields = Array.from(generatedOutputFields);
5233
- const task = liveUpdateFlushChain.then(async () => {
5254
+ const task = liveFlushChain.then(async () => {
5234
5255
  try {
5235
5256
  await applyLiveMapRowUpdates({
5236
5257
  req,
@@ -5240,33 +5261,29 @@ function createMinimalWorkerCtx(
5240
5261
  updates,
5241
5262
  });
5242
5263
  } catch (error) {
5243
- liveUpdateFailureCount += 1;
5244
- if (liveUpdateFailureCount <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5264
+ liveUpdateFailures += 1;
5265
+ if (liveUpdateFailures <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5245
5266
  emitEvent({
5246
5267
  type: 'log',
5247
5268
  level: 'warn',
5248
- message:
5249
- `Live row update flush failed for ctx.dataset("${name}") ` +
5250
- `(non-fatal; terminal rows still persist): ${formatWorkerRowFailureMessage(error)}`,
5269
+ message: formatWorkerRowFailureMessage(error),
5251
5270
  ts: nowMs(),
5252
5271
  });
5253
5272
  }
5254
5273
  }
5255
5274
  });
5256
- liveUpdateFlushChain = task.catch(() => undefined);
5275
+ liveFlushChain = task.catch(() => undefined);
5257
5276
  return task;
5258
5277
  };
5259
5278
 
5260
5279
  const scheduleLiveRowUpdates = () => {
5261
- if (
5262
- pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5263
- ) {
5280
+ if (pendingLiveUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS) {
5264
5281
  void flushLiveRowUpdates().catch(() => undefined);
5265
5282
  return;
5266
5283
  }
5267
- if (scheduledLiveUpdateTimer) return;
5268
- scheduledLiveUpdateTimer = setTimeout(() => {
5269
- scheduledLiveUpdateTimer = null;
5284
+ if (liveUpdateTimer) return;
5285
+ liveUpdateTimer = setTimeout(() => {
5286
+ liveUpdateTimer = null;
5270
5287
  void flushLiveRowUpdates().catch(() => undefined);
5271
5288
  }, MAP_INCREMENTAL_PERSIST_INTERVAL_MS);
5272
5289
  };
@@ -5274,10 +5291,8 @@ function createMinimalWorkerCtx(
5274
5291
  const enqueueLiveRowUpdate = (
5275
5292
  update: Omit<PlayRowUpdate, 'rowId'> & { runId?: string },
5276
5293
  ): Promise<void> => {
5277
- pendingLiveRowUpdates.push(update);
5278
- if (
5279
- pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5280
- ) {
5294
+ pendingLiveUpdates.push(update);
5295
+ if (pendingLiveUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS) {
5281
5296
  void flushLiveRowUpdates().catch(() => undefined);
5282
5297
  return Promise.resolve();
5283
5298
  }
@@ -5402,10 +5417,6 @@ function createMinimalWorkerCtx(
5402
5417
  else stepCellsCompleted += 1;
5403
5418
  await enqueueLiveRowUpdate({
5404
5419
  key: entry.rowKey,
5405
- tableNamespace: name,
5406
- runId: req.runId,
5407
- status: 'running',
5408
- stage: stepOutput.stepId,
5409
5420
  dataPatch: {
5410
5421
  [stepOutput.columnName]: stepOutput.value,
5411
5422
  },
@@ -5436,6 +5447,12 @@ function createMinimalWorkerCtx(
5436
5447
  completedAt: nowMs(),
5437
5448
  };
5438
5449
  }
5450
+ if (fieldEntries.length > 1) {
5451
+ await enqueueLiveRowUpdate({
5452
+ key: entry.rowKey,
5453
+ dataPatch: { [key]: resolved.value },
5454
+ });
5455
+ }
5439
5456
  activeField = null;
5440
5457
  }
5441
5458
  for (const stepOutput of stepProgramOutputs) {
@@ -5612,7 +5629,7 @@ function createMinimalWorkerCtx(
5612
5629
  });
5613
5630
  try {
5614
5631
  await flushLiveRowUpdates();
5615
- await liveUpdateFlushChain;
5632
+ await liveFlushChain;
5616
5633
  await enqueuePersistExecutedRows();
5617
5634
  await persistFlushChain;
5618
5635
  if (persistFailure) throw persistFailure;
@@ -7864,7 +7881,9 @@ function extractMaxCreditsPerRun(contractSnapshot: unknown): number | null {
7864
7881
  }
7865
7882
 
7866
7883
  function shouldSkipWorkerComputeBilling(req: RunRequest): boolean {
7867
- return req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub';
7884
+ return (
7885
+ req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub'
7886
+ );
7868
7887
  }
7869
7888
 
7870
7889
  async function finalizeWorkerComputeBilling(input: {
@@ -21,7 +21,6 @@ export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET = 200;
21
21
  // Fresh unbatched tool calls use one RUNTIME_API integration execute RPC and
22
22
  // one HARNESS durable-receipt completion RPC. Batch-cap rows by both.
23
23
  export const SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 2;
24
-
25
24
  export type WorkerMapChunkPlanInput = {
26
25
  mapName: string;
27
26
  rowCountHint: number | null;
@@ -45,7 +44,7 @@ type ChunkSizingPlan = {
45
44
  softWorkflowStepBudget?: number | null;
46
45
  };
47
46
 
48
- type MapToolStats = [totalToolCount: number, unbatchedToolCount: number];
47
+ type MapToolStats = [totalToolCount: number, unbatchedSubrequestCount: number];
49
48
 
50
49
  function fieldRoot(field: string | null | undefined): string {
51
50
  return String(field ?? '').split('.', 1)[0] ?? '';
@@ -70,41 +69,42 @@ function declarationBelongsToMapOutput(
70
69
  );
71
70
  }
72
71
 
73
- function isUnbatchedTool(toolId: string): boolean {
74
- return getPlayRuntimeBatchStrategy(toolId) === null;
72
+ function countTool(toolId: string): MapToolStats {
73
+ if (getPlayRuntimeBatchStrategy(toolId) !== null) return [1, 0];
74
+ return [1, toolId === 'test_high_single' ? 5 : 2];
75
+ }
76
+
77
+ function addStats(
78
+ [total, subrequests]: MapToolStats,
79
+ stats: MapToolStats,
80
+ ): MapToolStats {
81
+ return [total + stats[0], subrequests + stats[1]];
75
82
  }
76
83
 
77
84
  function countToolSubsteps(
78
85
  substeps: readonly PlayStaticSubstep[],
79
86
  ): MapToolStats {
80
- let totalToolCount = 0;
81
- let unbatchedToolCount = 0;
87
+ let stats: MapToolStats = [0, 0];
82
88
 
83
89
  for (const substep of substeps) {
84
- const [nestedTotal, nestedUnbatched] = countToolSubstep(substep);
85
- totalToolCount += nestedTotal;
86
- unbatchedToolCount += nestedUnbatched;
90
+ stats = addStats(stats, countToolSubstep(substep));
87
91
  }
88
92
 
89
- return [totalToolCount, unbatchedToolCount];
93
+ return stats;
90
94
  }
91
95
 
92
96
  function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
93
97
  if (substep.type === 'tool') {
94
- return [1, isUnbatchedTool(substep.toolId) ? 1 : 0];
98
+ return countTool(substep.toolId);
95
99
  }
96
100
 
97
101
  if (substep.type === 'waterfall') {
98
- let totalToolCount = 0;
99
- let unbatchedToolCount = 0;
102
+ let stats: MapToolStats = [0, 0];
100
103
  for (const step of substep.steps ?? []) {
101
104
  if (!step.toolId) continue;
102
- totalToolCount += 1;
103
- if (isUnbatchedTool(step.toolId)) {
104
- unbatchedToolCount += 1;
105
- }
105
+ stats = addStats(stats, countTool(step.toolId));
106
106
  }
107
- return [totalToolCount, unbatchedToolCount];
107
+ return stats;
108
108
  }
109
109
 
110
110
  if (substep.type === 'step_suite') {
@@ -117,9 +117,9 @@ function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
117
117
  countToolSubsteps(branch.steps),
118
118
  );
119
119
  return branchStats.reduce<MapToolStats>(
120
- ([totalMax, unbatchedMax], [total, unbatched]) => [
120
+ ([totalMax, subrequestsMax], [total, subrequests]) => [
121
121
  Math.max(totalMax, total),
122
- Math.max(unbatchedMax, unbatched),
122
+ Math.max(subrequestsMax, subrequests),
123
123
  ],
124
124
  [0, 0],
125
125
  );
@@ -151,9 +151,9 @@ function countStepSuiteTools(
151
151
  conditionalChildren.length < childStats.length / 2
152
152
  ) {
153
153
  return childStats.reduce<MapToolStats>(
154
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
154
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
155
155
  totalSum + total,
156
- unbatchedSum + unbatched,
156
+ subrequestsSum + subrequests,
157
157
  ],
158
158
  [0, 0],
159
159
  );
@@ -166,9 +166,9 @@ function countStepSuiteTools(
166
166
  });
167
167
  if (!sameRoot) {
168
168
  return childStats.reduce<MapToolStats>(
169
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
169
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
170
170
  totalSum + total,
171
- unbatchedSum + unbatched,
171
+ subrequestsSum + subrequests,
172
172
  ],
173
173
  [0, 0],
174
174
  );
@@ -181,52 +181,41 @@ function countStepSuiteTools(
181
181
  !(step.type === 'control_flow' && step.kind === 'conditional'),
182
182
  )
183
183
  .reduce<MapToolStats>(
184
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
184
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
185
185
  totalSum + total,
186
- unbatchedSum + unbatched,
186
+ subrequestsSum + subrequests,
187
187
  ],
188
188
  [0, 0],
189
189
  );
190
190
  const conditionalStats = conditionalChildren.reduce<MapToolStats>(
191
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
191
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
192
192
  totalSum + total,
193
- unbatchedSum + unbatched,
193
+ subrequestsSum + subrequests,
194
194
  ],
195
195
  [0, 0],
196
196
  );
197
197
 
198
- return [
199
- unconditionalStats[0] + conditionalStats[0],
200
- unconditionalStats[1] + conditionalStats[1],
201
- ];
198
+ return addStats(unconditionalStats, conditionalStats);
202
199
  }
203
200
 
204
201
  function countProducerTools(
205
202
  producers: readonly PlayStaticColumnProducer[],
206
203
  ): MapToolStats {
207
- let totalToolCount = 0;
208
- let unbatchedToolCount = 0;
204
+ let stats: MapToolStats = [0, 0];
209
205
 
210
206
  for (const producer of producers) {
211
207
  if (producer.kind === 'tool' && producer.toolId) {
212
- totalToolCount += 1;
213
- if (isUnbatchedTool(producer.toolId)) {
214
- unbatchedToolCount += 1;
215
- }
208
+ stats = addStats(stats, countTool(producer.toolId));
216
209
  }
217
210
  if (producer.substep.type === 'waterfall') {
218
- const [nestedTotal, nestedUnbatched] = countToolSubstep(producer.substep);
219
- totalToolCount += nestedTotal;
220
- unbatchedToolCount += nestedUnbatched;
211
+ stats = addStats(stats, countToolSubstep(producer.substep));
221
212
  }
222
213
  if (producer.steps?.length) {
223
- const [nestedTotal, nestedUnbatched] = countProducerTools(producer.steps);
224
- totalToolCount += nestedTotal;
225
- unbatchedToolCount += nestedUnbatched;
214
+ stats = addStats(stats, countProducerTools(producer.steps));
226
215
  }
227
216
  }
228
217
 
229
- return [totalToolCount, unbatchedToolCount];
218
+ return stats;
230
219
  }
231
220
 
232
221
  function datasetBelongsToPlanMap(
@@ -278,8 +267,7 @@ function countFallbackDeclarationTools(input: {
278
267
  outputFields: readonly string[];
279
268
  externalStepFields?: readonly string[];
280
269
  }): MapToolStats {
281
- let totalToolCount = 0;
282
- let unbatchedToolCount = 0;
270
+ let stats: MapToolStats = [0, 0];
283
271
 
284
272
  for (const declaration of input.declarations) {
285
273
  if (
@@ -291,23 +279,19 @@ function countFallbackDeclarationTools(input: {
291
279
  ) {
292
280
  continue;
293
281
  }
294
- totalToolCount += 1;
295
- if (isUnbatchedTool(declaration.toolId)) {
296
- unbatchedToolCount += 1;
297
- }
282
+ stats = addStats(stats, countTool(declaration.toolId));
298
283
  }
299
284
 
300
- return [totalToolCount, unbatchedToolCount];
285
+ return stats;
301
286
  }
302
287
 
303
288
  function countDeclarationTools(
304
289
  declarations: readonly { toolId: string; field?: string | null }[],
305
290
  ): MapToolStats {
306
- return [
307
- declarations.length,
308
- declarations.filter((declaration) => isUnbatchedTool(declaration.toolId))
309
- .length,
310
- ];
291
+ return declarations.reduce<MapToolStats>(
292
+ (stats, declaration) => addStats(stats, countTool(declaration.toolId)),
293
+ [0, 0],
294
+ );
311
295
  }
312
296
 
313
297
  function countNestedExecutionSteps(
@@ -409,12 +393,13 @@ export function chooseWorkerMapRowsPerChunk(
409
393
  }
410
394
 
411
395
  if (mapToolStats[1] > 0) {
412
- const unbatchedToolCallsPerRow =
396
+ const unbatchedSubrequestsPerRow =
413
397
  staticMapToolStats !== null
414
398
  ? mapToolStats[1]
415
- : Math.max(planMap?.stepsPerChunk ?? 1, mapToolStats[1]);
416
- const unbatchedSubrequestsPerRow =
417
- unbatchedToolCallsPerRow * SUBREQUESTS_PER_UNBATCHED_TOOL_CALL;
399
+ : Math.max(
400
+ (planMap?.stepsPerChunk ?? 1) * SUBREQUESTS_PER_UNBATCHED_TOOL_CALL,
401
+ mapToolStats[1],
402
+ );
418
403
  const unbatchedToolRowsPerChunk = Math.max(
419
404
  1,
420
405
  Math.floor(
@@ -104,10 +104,10 @@ export const SDK_RELEASE = {
104
104
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
- version: '0.1.171',
107
+ version: '0.1.172',
108
108
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
109
109
  supportPolicy: {
110
- latest: '0.1.171',
110
+ latest: '0.1.172',
111
111
  minimumSupported: '0.1.53',
112
112
  deprecatedBelow: '0.1.53',
113
113
  commandMinimumSupported: [
@@ -114,6 +114,9 @@ const RUNTIME_DB_SESSION_ROW_LIMIT_FLOOR = DIRECT_POSTGRES_BATCH_SIZE;
114
114
  const APPEND_KEY_SUFFIX_LENGTH = 12;
115
115
  const RUNTIME_DB_SESSION_TTL_SECONDS = 10 * 60;
116
116
  const RUNTIME_SHEET_ENSURE_CACHE_TTL_MS = 10 * 60_000;
117
+ const RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS = [
118
+ 100, 250, 500, 1_000, 2_000,
119
+ ] as const;
117
120
  const RUNTIME_DB_SESSION_RENEWAL_WINDOW_MS = 60_000;
118
121
  const RUNTIME_API_RETRY_DELAYS_MS = [
119
122
  250, 500, 1_000, 2_000, 4_000, 8_000, 8_000,
@@ -529,6 +532,41 @@ async function isRuntimeSheetSchemaReady(
529
532
  }
530
533
  }
531
534
 
535
+ async function waitForRuntimeSheetSchemaReadyAfterEnsure(
536
+ session: RuntimePostgresSession,
537
+ input: {
538
+ sheetContract: PlaySheetContract;
539
+ timings?: RuntimeSheetTiming[];
540
+ },
541
+ ): Promise<boolean> {
542
+ for (
543
+ let attempt = 0;
544
+ attempt <= RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS.length;
545
+ attempt += 1
546
+ ) {
547
+ const recheckStartedAt = Date.now();
548
+ const ready = await isRuntimeSheetSchemaReady(session, {
549
+ sheetContract: input.sheetContract,
550
+ });
551
+ input.timings?.push({
552
+ phase: 'schema_check_after_preloaded_session_ensure',
553
+ ms: Date.now() - recheckStartedAt,
554
+ ready,
555
+ retried: attempt > 0,
556
+ });
557
+ if (ready) {
558
+ return true;
559
+ }
560
+ const delay =
561
+ RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS[attempt] ?? null;
562
+ if (delay === null) {
563
+ return false;
564
+ }
565
+ await sleep(delay);
566
+ }
567
+ return false;
568
+ }
569
+
532
570
  async function ensureRuntimeSheetForPreloadedSession(
533
571
  context: RuntimeApiContext & { playName: string },
534
572
  input: {
@@ -597,38 +635,35 @@ async function ensureRuntimeSheetForPreloadedSession(
597
635
  return;
598
636
  }
599
637
  const ensureStartedAt = Date.now();
600
- const promise = ensureRuntimeSheet(context, {
601
- playName: context.playName,
602
- tableNamespace: input.tableNamespace,
603
- sheetContract: input.sheetContract,
604
- });
605
- runtimeSheetEnsureCache.set(cacheKey, {
606
- expiresAt: now + RUNTIME_SHEET_ENSURE_CACHE_TTL_MS,
607
- promise,
608
- });
609
- try {
610
- await promise;
611
- input.timings?.push({
612
- phase: 'ensure_sheet_for_preloaded_session',
613
- ms: Date.now() - ensureStartedAt,
614
- });
615
- const recheckStartedAt = Date.now();
616
- const readyAfterEnsure = await isRuntimeSheetSchemaReady(input.session, {
638
+ const promise = (async () => {
639
+ await ensureRuntimeSheet(context, {
640
+ playName: context.playName,
641
+ tableNamespace: input.tableNamespace,
617
642
  sheetContract: input.sheetContract,
618
643
  });
619
644
  input.timings?.push({
620
- phase: 'schema_check_after_preloaded_session_ensure',
621
- ms: Date.now() - recheckStartedAt,
622
- ready: readyAfterEnsure,
645
+ phase: 'ensure_sheet_for_preloaded_session',
646
+ ms: Date.now() - ensureStartedAt,
623
647
  });
648
+ const readyAfterEnsure = await waitForRuntimeSheetSchemaReadyAfterEnsure(
649
+ input.session,
650
+ {
651
+ sheetContract: input.sheetContract,
652
+ timings: input.timings,
653
+ },
654
+ );
624
655
  if (!readyAfterEnsure) {
625
- if (runtimeSheetEnsureCache.get(cacheKey)?.promise === promise) {
626
- runtimeSheetEnsureCache.delete(cacheKey);
627
- }
628
656
  throw new Error(
629
657
  `Runtime sheet schema for ctx.dataset("${input.tableNamespace}") is still not ready after ensure_sheet.`,
630
658
  );
631
659
  }
660
+ })();
661
+ runtimeSheetEnsureCache.set(cacheKey, {
662
+ expiresAt: now + RUNTIME_SHEET_ENSURE_CACHE_TTL_MS,
663
+ promise,
664
+ });
665
+ try {
666
+ await promise;
632
667
  } catch (error) {
633
668
  if (runtimeSheetEnsureCache.get(cacheKey)?.promise === promise) {
634
669
  runtimeSheetEnsureCache.delete(cacheKey);
package/dist/cli/index.js CHANGED
@@ -622,10 +622,10 @@ var SDK_RELEASE = {
622
622
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
- version: "0.1.171",
625
+ version: "0.1.172",
626
626
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
627
627
  supportPolicy: {
628
- latest: "0.1.171",
628
+ latest: "0.1.172",
629
629
  minimumSupported: "0.1.53",
630
630
  deprecatedBelow: "0.1.53",
631
631
  commandMinimumSupported: [
@@ -4389,10 +4389,50 @@ end run
4389
4389
  `;
4390
4390
  return runAppleScript(script, [targetUrl, host], runner);
4391
4391
  }
4392
+ function hasOnlyHeadlessMacosBrowserInstances(appName, runner = defaultBrowserCommandRunner) {
4393
+ try {
4394
+ const output2 = runner.execFileSync("ps", ["-axo", "args="], {
4395
+ encoding: "utf-8",
4396
+ stdio: ["ignore", "pipe", "ignore"]
4397
+ });
4398
+ const executableSuffix = `/Contents/MacOS/${appName}`;
4399
+ const lines = String(output2).split(/\r?\n/).map((line) => line.trim()).filter((line) => {
4400
+ const executableIndex = line.indexOf(executableSuffix);
4401
+ if (executableIndex < 0 || !line.includes(".app/Contents/MacOS/")) {
4402
+ return false;
4403
+ }
4404
+ const rest = line.slice(executableIndex + executableSuffix.length);
4405
+ return rest === "" || /^\s+-/.test(rest);
4406
+ });
4407
+ if (lines.length === 0) return false;
4408
+ return lines.every(
4409
+ (line) => line.includes("--headless") || line.includes("--no-startup-window")
4410
+ );
4411
+ } catch {
4412
+ return false;
4413
+ }
4414
+ }
4415
+ function openNewVisibleMacosBrowserInstance(appName, targetUrl, runner = defaultBrowserCommandRunner) {
4416
+ try {
4417
+ runner.execFileSync(
4418
+ "open",
4419
+ ["-na", appName, "--args", "--new-window", targetUrl],
4420
+ {
4421
+ stdio: "ignore"
4422
+ }
4423
+ );
4424
+ return true;
4425
+ } catch {
4426
+ return false;
4427
+ }
4428
+ }
4392
4429
  function openUrlMacos(targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
4393
4430
  const defaultBundleId = readDefaultMacBrowserBundleId(runner);
4394
4431
  const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
4395
4432
  const strategy = browserStrategyForBundleId(defaultBundleId);
4433
+ if (allowFocus && appName && strategy === "chromium" && hasOnlyHeadlessMacosBrowserInstances(appName, runner) && openNewVisibleMacosBrowserInstance(appName, targetUrl, runner)) {
4434
+ return true;
4435
+ }
4396
4436
  if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus, runner)) {
4397
4437
  return true;
4398
4438
  }
@@ -607,10 +607,10 @@ var SDK_RELEASE = {
607
607
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
- version: "0.1.171",
610
+ version: "0.1.172",
611
611
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
612
612
  supportPolicy: {
613
- latest: "0.1.171",
613
+ latest: "0.1.172",
614
614
  minimumSupported: "0.1.53",
615
615
  deprecatedBelow: "0.1.53",
616
616
  commandMinimumSupported: [
@@ -4386,10 +4386,50 @@ end run
4386
4386
  `;
4387
4387
  return runAppleScript(script, [targetUrl, host], runner);
4388
4388
  }
4389
+ function hasOnlyHeadlessMacosBrowserInstances(appName, runner = defaultBrowserCommandRunner) {
4390
+ try {
4391
+ const output2 = runner.execFileSync("ps", ["-axo", "args="], {
4392
+ encoding: "utf-8",
4393
+ stdio: ["ignore", "pipe", "ignore"]
4394
+ });
4395
+ const executableSuffix = `/Contents/MacOS/${appName}`;
4396
+ const lines = String(output2).split(/\r?\n/).map((line) => line.trim()).filter((line) => {
4397
+ const executableIndex = line.indexOf(executableSuffix);
4398
+ if (executableIndex < 0 || !line.includes(".app/Contents/MacOS/")) {
4399
+ return false;
4400
+ }
4401
+ const rest = line.slice(executableIndex + executableSuffix.length);
4402
+ return rest === "" || /^\s+-/.test(rest);
4403
+ });
4404
+ if (lines.length === 0) return false;
4405
+ return lines.every(
4406
+ (line) => line.includes("--headless") || line.includes("--no-startup-window")
4407
+ );
4408
+ } catch {
4409
+ return false;
4410
+ }
4411
+ }
4412
+ function openNewVisibleMacosBrowserInstance(appName, targetUrl, runner = defaultBrowserCommandRunner) {
4413
+ try {
4414
+ runner.execFileSync(
4415
+ "open",
4416
+ ["-na", appName, "--args", "--new-window", targetUrl],
4417
+ {
4418
+ stdio: "ignore"
4419
+ }
4420
+ );
4421
+ return true;
4422
+ } catch {
4423
+ return false;
4424
+ }
4425
+ }
4389
4426
  function openUrlMacos(targetUrl, allowFocus, runner = defaultBrowserCommandRunner) {
4390
4427
  const defaultBundleId = readDefaultMacBrowserBundleId(runner);
4391
4428
  const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
4392
4429
  const strategy = browserStrategyForBundleId(defaultBundleId);
4430
+ if (allowFocus && appName && strategy === "chromium" && hasOnlyHeadlessMacosBrowserInstances(appName, runner) && openNewVisibleMacosBrowserInstance(appName, targetUrl, runner)) {
4431
+ return true;
4432
+ }
4393
4433
  if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus, runner)) {
4394
4434
  return true;
4395
4435
  }
package/dist/index.js CHANGED
@@ -421,10 +421,10 @@ var SDK_RELEASE = {
421
421
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
- version: "0.1.171",
424
+ version: "0.1.172",
425
425
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
426
426
  supportPolicy: {
427
- latest: "0.1.171",
427
+ latest: "0.1.172",
428
428
  minimumSupported: "0.1.53",
429
429
  deprecatedBelow: "0.1.53",
430
430
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -351,10 +351,10 @@ var SDK_RELEASE = {
351
351
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
- version: "0.1.171",
354
+ version: "0.1.172",
355
355
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
356
356
  supportPolicy: {
357
- latest: "0.1.171",
357
+ latest: "0.1.172",
358
358
  minimumSupported: "0.1.53",
359
359
  deprecatedBelow: "0.1.53",
360
360
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.171",
3
+ "version": "0.1.172",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {