deepline 0.3.0 → 0.3.1

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.
@@ -2,6 +2,7 @@ import { tmpdir } from 'node:os';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { existsSync } from 'node:fs';
5
+ import { realpath } from 'node:fs/promises';
5
6
  import {
6
7
  bundlePlayFile as bundlePlayFileCore,
7
8
  type BundlePlayFileOptions,
@@ -145,10 +146,23 @@ export async function bundlePlayFile(
145
146
  filePath: string,
146
147
  options: BundlePlayFileOptions = {},
147
148
  ): Promise<BundledPlayFileResult> {
149
+ // The SDK sends this graph to a remote checker/runtime. Its file identities
150
+ // therefore must describe the authoring workspace, rather than the local
151
+ // machine paths used while building it. This is especially important on
152
+ // Windows: a raw `C:\\...` key cannot be reconciled beneath `/var/task`.
153
+ // Match the bundler's physical-path normalization. On macOS, for example,
154
+ // os.tmpdir() can report /var while the source graph resolves /private/var.
155
+ const localWorkspaceRoot = dirname(resolve(filePath));
156
+ const sourceIdentityRoot = await realpath(localWorkspaceRoot).catch(
157
+ () => localWorkspaceRoot,
158
+ );
148
159
  const result = await bundlePlayFileCore(filePath, {
149
160
  target: options.target ?? defaultPlayBundleTarget(),
150
161
  exportName: options.exportName,
151
- adapter: createSdkPlayBundlingAdapter(),
162
+ adapter: {
163
+ ...createSdkPlayBundlingAdapter(),
164
+ sourceIdentityRoot,
165
+ },
152
166
  });
153
167
  if (result.success)
154
168
  validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.0',
195
+ version: '0.3.1',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -51,6 +51,7 @@ export type AcquiredDaytonaSandbox = {
51
51
  daytonaOrganizationId: string;
52
52
  billingStartedAt: number;
53
53
  billingEndedAt?: number;
54
+ sandboxCapacityLeaseId?: string;
54
55
  };
55
56
 
56
57
  export type OneShotDaytonaSandboxLifecycle = {
@@ -118,6 +119,7 @@ type DaytonaCreateResult = {
118
119
  sandbox: DaytonaSandbox;
119
120
  attempt: number;
120
121
  attemptElapsedMs: number;
122
+ sandboxCapacityLeaseId?: string;
121
123
  };
122
124
 
123
125
  function daytonaCreateErrorClass(
@@ -133,6 +135,8 @@ function daytonaCreateErrorClass(
133
135
  async function rejectAcquiredSandbox(
134
136
  sandbox: DaytonaSandbox,
135
137
  reason: string,
138
+ reservation?: { leaseId: string },
139
+ releaseSandboxCapacity?: (leaseId: string) => Promise<void>,
136
140
  ): Promise<never> {
137
141
  try {
138
142
  await sandbox.delete(30);
@@ -143,6 +147,9 @@ async function rejectAcquiredSandbox(
143
147
  { cause: error },
144
148
  );
145
149
  }
150
+ if (reservation) {
151
+ await releaseSandboxCapacity?.(reservation.leaseId);
152
+ }
146
153
  throw new Error(reason);
147
154
  }
148
155
 
@@ -350,6 +357,8 @@ async function createRetriedOneShotDaytonaSandbox(input: {
350
357
  context: DaytonaExecutionContext;
351
358
  emitStage: DaytonaStageEmitter;
352
359
  observeCreateCall?: DaytonaCreateCallObserver;
360
+ reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
361
+ releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
353
362
  nextProviderAttempt: () => number;
354
363
  startedAt: number;
355
364
  }): Promise<DaytonaCreateResult> {
@@ -372,6 +381,7 @@ async function createRetriedOneShotDaytonaSandbox(input: {
372
381
  occurredAtMs: attemptStartedAt,
373
382
  providerAttempt: attempt,
374
383
  });
384
+ const reservation = await input.reserveSandboxCapacity?.();
375
385
  let sandbox: DaytonaSandbox;
376
386
  try {
377
387
  sandbox = await createOneShotDaytonaSandbox({
@@ -381,6 +391,12 @@ async function createRetriedOneShotDaytonaSandbox(input: {
381
391
  sandboxName,
382
392
  });
383
393
  } catch (error) {
394
+ // A normal rejected create has no durable provider resource. A timeout
395
+ // can be ambiguous, so retain its lease until the crash TTL rather than
396
+ // admitting another sandbox against an unknown provider outcome.
397
+ if (reservation && !isDaytonaSandboxStartTimeout(String(error))) {
398
+ await input.releaseSandboxCapacity?.(reservation.leaseId);
399
+ }
384
400
  await input.observeCreateCall?.({
385
401
  type: 'daytona_create_call_failed',
386
402
  occurredAtMs: Date.now(),
@@ -407,6 +423,13 @@ async function createRetriedOneShotDaytonaSandbox(input: {
407
423
  sandboxName,
408
424
  });
409
425
  if (deleted) {
426
+ // The provider-confirmed reconciliation means this reservation can
427
+ // no longer represent a live sandbox. Release before returning the
428
+ // acquisition-unavailable result so Modal fallback is not blocked
429
+ // behind the unbound crash TTL.
430
+ if (reservation) {
431
+ await input.releaseSandboxCapacity?.(reservation.leaseId);
432
+ }
410
433
  throw new DaytonaSandboxAcquisitionUnavailableError(
411
434
  'daytona_sandbox_start_timeout',
412
435
  `${message} Timed-out Daytona sandbox was reconciled and deleted before fallback.`,
@@ -448,6 +471,7 @@ async function createRetriedOneShotDaytonaSandbox(input: {
448
471
  sandbox,
449
472
  attempt,
450
473
  attemptElapsedMs: acquiredAt - attemptStartedAt,
474
+ ...(reservation ? { sandboxCapacityLeaseId: reservation.leaseId } : {}),
451
475
  };
452
476
  }
453
477
  const message = `Daytona sandbox create failed across ${errors.length} bounded attempts: ${errors.join('; ')}`;
@@ -468,6 +492,8 @@ async function acquireOneShotDaytonaSandbox(input: {
468
492
  context: DaytonaExecutionContext;
469
493
  emitStage: DaytonaStageEmitter;
470
494
  observeCreateCall?: DaytonaCreateCallObserver;
495
+ reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
496
+ releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
471
497
  nextProviderAttempt: () => number;
472
498
  startedAt: number;
473
499
  }): Promise<AcquiredDaytonaSandbox> {
@@ -493,6 +519,10 @@ async function acquireOneShotDaytonaSandbox(input: {
493
519
  await rejectAcquiredSandbox(
494
520
  result.sandbox,
495
521
  `Daytona sandbox resource boundary mismatch: expected cpu=${limits.cpu} memoryGiB=${limits.memoryGiB} diskGiB=${limits.diskGiB} gpu=${DAYTONA_SANDBOX_GPU}, granted cpu=${granted.cpu} memoryGiB=${granted.memoryGiB} diskGiB=${granted.diskGiB} gpu=${granted.gpu}`,
522
+ result.sandboxCapacityLeaseId
523
+ ? { leaseId: result.sandboxCapacityLeaseId }
524
+ : undefined,
525
+ input.releaseSandboxCapacity,
496
526
  );
497
527
  }
498
528
  const configuredOrganizationId =
@@ -506,6 +536,10 @@ async function acquireOneShotDaytonaSandbox(input: {
506
536
  await rejectAcquiredSandbox(
507
537
  result.sandbox,
508
538
  'Daytona sandbox organization routing mismatch. Refusing to run customer code in a sandbox whose observed organization differs from the configured organization.',
539
+ result.sandboxCapacityLeaseId
540
+ ? { leaseId: result.sandboxCapacityLeaseId }
541
+ : undefined,
542
+ input.releaseSandboxCapacity,
509
543
  );
510
544
  }
511
545
  let lookupOrganizationId: string | null = null;
@@ -528,6 +562,10 @@ async function acquireOneShotDaytonaSandbox(input: {
528
562
  return await rejectAcquiredSandbox(
529
563
  result.sandbox,
530
564
  'Daytona sandbox organization routing identity is missing. Refusing to run customer code without a durable cleanup routing domain.',
565
+ result.sandboxCapacityLeaseId
566
+ ? { leaseId: result.sandboxCapacityLeaseId }
567
+ : undefined,
568
+ input.releaseSandboxCapacity,
531
569
  );
532
570
  }
533
571
  const billingStartedAt = Date.now();
@@ -541,7 +579,14 @@ async function acquireOneShotDaytonaSandbox(input: {
541
579
  memoryGiB: granted.memoryGiB,
542
580
  diskGiB: granted.diskGiB,
543
581
  });
544
- return { sandbox, daytonaOrganizationId, billingStartedAt };
582
+ return {
583
+ sandbox,
584
+ daytonaOrganizationId,
585
+ billingStartedAt,
586
+ ...(result.sandboxCapacityLeaseId
587
+ ? { sandboxCapacityLeaseId: result.sandboxCapacityLeaseId }
588
+ : {}),
589
+ };
545
590
  }
546
591
 
547
592
  export function createOneShotDaytonaSandboxLifecycle(input: {
@@ -549,6 +594,8 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
549
594
  context: DaytonaExecutionContext;
550
595
  emitStage: DaytonaStageEmitter;
551
596
  observeCreateCall?: DaytonaCreateCallObserver;
597
+ reserveSandboxCapacity?: () => Promise<{ leaseId: string }>;
598
+ releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
552
599
  startedAt?: number;
553
600
  }): OneShotDaytonaSandboxLifecycle {
554
601
  const orgId = validateDaytonaExecutionContext(input.context);
@@ -564,6 +611,8 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
564
611
  context: input.context,
565
612
  emitStage: input.emitStage,
566
613
  observeCreateCall: input.observeCreateCall,
614
+ reserveSandboxCapacity: input.reserveSandboxCapacity,
615
+ releaseSandboxCapacity: input.releaseSandboxCapacity,
567
616
  nextProviderAttempt: () => {
568
617
  providerAttempt += 1;
569
618
  return providerAttempt;
@@ -594,6 +643,13 @@ export function createOneShotDaytonaSandboxLifecycle(input: {
594
643
  try {
595
644
  const acquired = await latestAcquiredSandboxPromise;
596
645
  await acquired.sandbox.delete(30);
646
+ // `dispose` has provider-confirmed that the sandbox is gone. This is
647
+ // normally the narrow cancellation window before the scheduler has
648
+ // durably recorded the resource, so no cleanup job will release an
649
+ // unbound lease on our behalf.
650
+ if (acquired.sandboxCapacityLeaseId) {
651
+ await input.releaseSandboxCapacity?.(acquired.sandboxCapacityLeaseId);
652
+ }
597
653
  } catch (error) {
598
654
  console.warn('[play-runner.daytona.dispose_failed_before_acquire]', {
599
655
  error: error instanceof Error ? error.message : String(error),
@@ -5,7 +5,10 @@ import type {
5
5
  PlayRunnerPreparedExecution,
6
6
  PlayRunnerPrepareInput,
7
7
  } from '../types';
8
- import { RuntimeResourceFenceLostError } from '../types';
8
+ import {
9
+ isRuntimeSandboxCapacityLimitError,
10
+ RuntimeResourceFenceLostError,
11
+ } from '../types';
9
12
  import { buildPlayRunnerBundle } from '../bundle';
10
13
  import { findPlayRunnerResult, parsePlayRunnerEvents } from '../runner-events';
11
14
  import type {
@@ -26,6 +29,7 @@ import {
26
29
  DaytonaSandboxAcquisitionUnavailableError,
27
30
  createDaytonaSandboxCleanupManager,
28
31
  createOneShotDaytonaSandboxLifecycle,
32
+ type AcquiredDaytonaSandbox,
29
33
  type DaytonaClient,
30
34
  type DaytonaExecutionContext,
31
35
  type DaytonaSandbox,
@@ -945,6 +949,10 @@ function prepareDaytonaExecution(
945
949
  emitStage: (stage, extra) =>
946
950
  emitDaytonaStage(callbacks, input.context, stage, extra),
947
951
  observeCreateCall: callbacks?.onRuntimeLifecycleEvent,
952
+ reserveSandboxCapacity: callbacks?.reserveSandboxCapacity
953
+ ? () => callbacks.reserveSandboxCapacity!('daytona')
954
+ : undefined,
955
+ releaseSandboxCapacity: callbacks?.releaseSandboxCapacity,
948
956
  });
949
957
  return {
950
958
  kind: 'daytona',
@@ -1055,22 +1063,12 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1055
1063
  };
1056
1064
  let cancellationRequested = false;
1057
1065
  let cancellationCleanupStarted = false;
1058
- let activeAcquiredResource: {
1059
- sandbox: DaytonaSandbox;
1060
- daytonaOrganizationId: string;
1061
- billingStartedAt: number;
1062
- billingEndedAt?: number;
1063
- } | null = null;
1066
+ let activeAcquiredResource: AcquiredDaytonaSandbox | null = null;
1064
1067
  const uploadDeadlineBreaches: DaytonaUploadAttemptTiming[] = [];
1065
1068
  const reportedSandboxIds = new Set<string>();
1066
1069
  const reportedSandboxEndTimes = new Map<string, number | null>();
1067
1070
  const runtimeResourceReportErrors = new Set<unknown>();
1068
- const reportRuntimeResource = async (acquired: {
1069
- sandbox: DaytonaSandbox;
1070
- daytonaOrganizationId: string;
1071
- billingStartedAt: number;
1072
- billingEndedAt?: number;
1073
- }) => {
1071
+ const reportRuntimeResource = async (acquired: AcquiredDaytonaSandbox) => {
1074
1072
  const sandbox = acquired.sandbox;
1075
1073
  const billingEndedAt = acquired.billingEndedAt ?? null;
1076
1074
  if (
@@ -1101,6 +1099,9 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1101
1099
  cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : null,
1102
1100
  memoryGiB: typeof sandbox.memory === 'number' ? sandbox.memory : null,
1103
1101
  diskGiB: typeof sandbox.disk === 'number' ? sandbox.disk : null,
1102
+ ...(acquired.sandboxCapacityLeaseId
1103
+ ? { sandboxCapacityLeaseId: acquired.sandboxCapacityLeaseId }
1104
+ : {}),
1104
1105
  });
1105
1106
  } catch (error) {
1106
1107
  runtimeResourceReportErrors.add(error);
@@ -1109,12 +1110,9 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1109
1110
  reportedSandboxIds.add(sandbox.id);
1110
1111
  reportedSandboxEndTimes.set(sandbox.id, billingEndedAt);
1111
1112
  };
1112
- const reportRetiringRuntimeResource = async (acquired: {
1113
- sandbox: DaytonaSandbox;
1114
- daytonaOrganizationId: string;
1115
- billingStartedAt: number;
1116
- billingEndedAt?: number;
1117
- }) => {
1113
+ const reportRetiringRuntimeResource = async (
1114
+ acquired: AcquiredDaytonaSandbox,
1115
+ ) => {
1118
1116
  try {
1119
1117
  await reportRuntimeResource(acquired);
1120
1118
  } catch (error) {
@@ -1429,6 +1427,9 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
1429
1427
  }
1430
1428
  throw error;
1431
1429
  }
1430
+ if (isRuntimeSandboxCapacityLimitError(error)) {
1431
+ throw error;
1432
+ }
1432
1433
  if (error instanceof DaytonaSandboxAcquisitionUnavailableError) {
1433
1434
  throw error;
1434
1435
  }
@@ -1,6 +1,9 @@
1
1
  import type { ModalClient } from 'modal';
2
2
  import type { PlayRunnerBackend, PlayRunnerCallbacks } from '../types';
3
- import { RuntimeResourceFenceLostError } from '../types';
3
+ import {
4
+ isRuntimeSandboxCapacityLimitError,
5
+ RuntimeResourceFenceLostError,
6
+ } from '../types';
4
7
  import { buildPlayRunnerBundle } from '../bundle';
5
8
  import type {
6
9
  PlayRunnerExecutionConfig,
@@ -23,6 +26,7 @@ import { stageRunnerPayload } from './daytona-payload-transport';
23
26
  import { captureDetachedDaytonaRunnerReadinessBaseline } from './daytona-session-execution';
24
27
 
25
28
  const MODAL_RUNNER_READY_TIMEOUT_MS = 30_000;
29
+ const MODAL_CAPACITY_RELEASE_RETRY_DELAYS_MS = [0, 100, 500, 2_000] as const;
26
30
  type ModalSandbox = Awaited<ReturnType<ModalClient['sandboxes']['create']>>;
27
31
 
28
32
  export function modalSandboxLifetimeMs(
@@ -99,13 +103,38 @@ async function confirmDetachedModalRunnerReady(input: {
99
103
  );
100
104
  }
101
105
 
102
- async function terminateModalSandbox(sandbox: ModalSandbox): Promise<void> {
103
- await sandbox.terminate().catch((error) => {
106
+ async function terminateModalSandbox(sandbox: ModalSandbox): Promise<boolean> {
107
+ try {
108
+ // The default terminate call only acknowledges the request. Capacity may
109
+ // be released only after Modal confirms the sandbox itself has stopped.
110
+ await sandbox.terminate({ wait: true });
111
+ return true;
112
+ } catch (error) {
104
113
  console.warn('[play-runner.modal.terminate_failed]', {
105
114
  sandboxId: sandbox.sandboxId,
106
115
  error: error instanceof Error ? error.message : String(error),
107
116
  });
108
- });
117
+ return false;
118
+ }
119
+ }
120
+
121
+ async function releaseModalSandboxCapacityAfterConfirmedTermination(input: {
122
+ leaseId: string;
123
+ release: NonNullable<PlayRunnerCallbacks['releaseSandboxCapacity']>;
124
+ }): Promise<void> {
125
+ let lastError: unknown;
126
+ for (const delayMs of MODAL_CAPACITY_RELEASE_RETRY_DELAYS_MS) {
127
+ if (delayMs > 0) {
128
+ await new Promise<void>((resolve) => setTimeout(resolve, delayMs));
129
+ }
130
+ try {
131
+ await input.release(input.leaseId);
132
+ return;
133
+ } catch (error) {
134
+ lastError = error;
135
+ }
136
+ }
137
+ throw lastError;
109
138
  }
110
139
 
111
140
  export const modalPlayRunnerBackend: PlayRunnerBackend = {
@@ -139,24 +168,35 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
139
168
  createIfMissing: true,
140
169
  });
141
170
  const image = modalConfig.client.images.fromRegistry(modalConfig.image);
142
- const sandbox = await modalConfig.client.sandboxes.create(app, image, {
143
- cpu: MODAL_SANDBOX_CPU_CORES,
144
- cpuLimit: MODAL_SANDBOX_CPU_CORES,
145
- memoryMiB: MODAL_SANDBOX_MEMORY_MIB,
146
- memoryLimitMiB: MODAL_SANDBOX_MEMORY_MIB,
147
- timeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
148
- idleTimeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
149
- workdir: modalConfig.workdir,
150
- tags: {
151
- source: 'deepline-play-runner',
152
- orgId: config.context.orgId ?? 'unknown',
153
- workflowId: config.context.workflowId ?? 'unknown',
154
- runId: config.context.runId ?? 'unknown',
155
- },
156
- ...(modalConfig.outboundCidrAllowlist
157
- ? { outboundCidrAllowlist: modalConfig.outboundCidrAllowlist }
158
- : {}),
159
- });
171
+ const capacityReservation =
172
+ await callbacks?.reserveSandboxCapacity?.('modal');
173
+ const sandbox = await modalConfig.client.sandboxes
174
+ .create(app, image, {
175
+ cpu: MODAL_SANDBOX_CPU_CORES,
176
+ cpuLimit: MODAL_SANDBOX_CPU_CORES,
177
+ memoryMiB: MODAL_SANDBOX_MEMORY_MIB,
178
+ memoryLimitMiB: MODAL_SANDBOX_MEMORY_MIB,
179
+ timeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
180
+ idleTimeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
181
+ workdir: modalConfig.workdir,
182
+ tags: {
183
+ source: 'deepline-play-runner',
184
+ orgId: config.context.orgId ?? 'unknown',
185
+ workflowId: config.context.workflowId ?? 'unknown',
186
+ runId: config.context.runId ?? 'unknown',
187
+ },
188
+ ...(modalConfig.outboundCidrAllowlist
189
+ ? { outboundCidrAllowlist: modalConfig.outboundCidrAllowlist }
190
+ : {}),
191
+ })
192
+ .catch(async (error) => {
193
+ if (capacityReservation) {
194
+ await callbacks?.releaseSandboxCapacity?.(
195
+ capacityReservation.leaseId,
196
+ );
197
+ }
198
+ throw error;
199
+ });
160
200
  const billingStartedAt = Date.now();
161
201
  runtimeTiming.modalCreateMs = billingStartedAt - startedAt;
162
202
  emitModalStage(config.context, 'create:done', {
@@ -164,6 +204,7 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
164
204
  elapsedMs: runtimeTiming.modalCreateMs,
165
205
  });
166
206
  let detached = false;
207
+ let setupError: unknown;
167
208
  let cancelExecution!: (error: Error) => void;
168
209
  const cancellationPromise = new Promise<never>((_resolve, reject) => {
169
210
  cancelExecution = reject;
@@ -192,6 +233,9 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
192
233
  cpu: MODAL_SANDBOX_CPU_CORES,
193
234
  memoryGiB: MODAL_SANDBOX_MEMORY_MIB / 1024,
194
235
  diskGiB: 0,
236
+ ...(capacityReservation
237
+ ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
238
+ : {}),
195
239
  });
196
240
  } catch (error) {
197
241
  runtimeResourceRegistrationError = error;
@@ -290,14 +334,45 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
290
334
  tableNamespace: null,
291
335
  runtimeTiming,
292
336
  };
337
+ } catch (error) {
338
+ setupError = error;
339
+ throw error;
293
340
  } finally {
294
341
  callbacks?.cancellationSignal?.removeEventListener('abort', onCancel);
295
- if (!detached) await terminateModalSandbox(sandbox);
342
+ if (!detached && (await terminateModalSandbox(sandbox))) {
343
+ // This branch synchronously confirms that the just-created resource
344
+ // is gone. Do not keep its admission lease until the cleanup loop
345
+ // sees a resource that no longer exists.
346
+ if (capacityReservation && callbacks?.releaseSandboxCapacity) {
347
+ try {
348
+ await releaseModalSandboxCapacityAfterConfirmedTermination({
349
+ leaseId: capacityReservation.leaseId,
350
+ release: callbacks.releaseSandboxCapacity,
351
+ });
352
+ } catch (releaseError) {
353
+ // The scheduler fence is the primary correctness signal. Keep
354
+ // it intact if its best-effort early capacity release is
355
+ // transiently unavailable; durable cleanup will retry release.
356
+ if (setupError) {
357
+ console.error('[play-runner.modal.capacity_release_failed]', {
358
+ sandboxId: sandbox.sandboxId,
359
+ error:
360
+ releaseError instanceof Error
361
+ ? releaseError.message
362
+ : String(releaseError),
363
+ });
364
+ } else {
365
+ throw releaseError;
366
+ }
367
+ }
368
+ }
369
+ }
296
370
  }
297
371
  } catch (error) {
298
372
  if (
299
373
  error === runtimeResourceRegistrationError ||
300
- error instanceof RuntimeResourceFenceLostError
374
+ error instanceof RuntimeResourceFenceLostError ||
375
+ isRuntimeSandboxCapacityLimitError(error)
301
376
  ) {
302
377
  throw error;
303
378
  }
@@ -335,7 +410,7 @@ export async function deleteModalSandboxById(input: {
335
410
  appId: expectedAppId,
336
411
  })) {
337
412
  if (sandbox.sandboxId !== input.sandboxId) continue;
338
- await sandbox.terminate();
413
+ await sandbox.terminate({ wait: true });
339
414
  return { kind: 'deleted', appId: expectedAppId };
340
415
  }
341
416
  // Cleanup means ensure absent. An exact app-scoped inventory proving the
@@ -28,6 +28,8 @@ export type PlayRunnerRuntimeResource = {
28
28
  cpu?: number | null;
29
29
  memoryGiB?: number | null;
30
30
  diskGiB?: number | null;
31
+ /** Scheduler-only soft-capacity reservation made before provider create. */
32
+ sandboxCapacityLeaseId?: string;
31
33
  };
32
34
 
33
35
  export type RuntimeResourceTerminalReason =
@@ -64,6 +66,23 @@ export class RuntimeResourceFenceLostError extends Error {
64
66
  }
65
67
  }
66
68
 
69
+ /**
70
+ * A scheduler-owned admission result crosses the provider-backend boundary.
71
+ * Keep it typed by name here so the shared backend can rethrow it without an
72
+ * import from the app-owned scheduler implementation.
73
+ */
74
+ export const RUNTIME_SANDBOX_CAPACITY_LIMIT_ERROR_NAME =
75
+ 'RuntimeSandboxCapacityLimitError';
76
+
77
+ export function isRuntimeSandboxCapacityLimitError(
78
+ error: unknown,
79
+ ): error is Error {
80
+ return (
81
+ error instanceof Error &&
82
+ error.name === RUNTIME_SANDBOX_CAPACITY_LIMIT_ERROR_NAME
83
+ );
84
+ }
85
+
67
86
  export interface PlayRunnerCallbacks {
68
87
  onLog?: (event: PlayRunnerLogEvent) => void;
69
88
  onCheckpoint?: (checkpoint: PlayCheckpoint) => void;
@@ -72,6 +91,12 @@ export interface PlayRunnerCallbacks {
72
91
  onRuntimeResourceAcquired?: (
73
92
  resource: PlayRunnerRuntimeResource,
74
93
  ) => void | Promise<void>;
94
+ /** Reserve one physical-sandbox slot immediately before a provider create. */
95
+ reserveSandboxCapacity?: (
96
+ provider: 'daytona' | 'modal',
97
+ ) => Promise<{ leaseId: string }>;
98
+ /** Release an unbound reservation when provider create never produced a resource. */
99
+ releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
75
100
  /**
76
101
  * Durable scheduler-owned evidence for a provider create call. Backends
77
102
  * await this callback at the create boundary; it is not best-effort logging.
@@ -13,7 +13,6 @@ export const RUNTIME_CAPACITY_POLICY = {
13
13
  /** 32 Machines x 8 claim slots permits 256 concurrent launch/resume legs. */
14
14
  maxActiveLaneMachines: 32,
15
15
  workerSlotsPerMachine: 8,
16
- perOrgConcurrency: 4,
17
16
  /** Queue age is an escape hatch for small-but-stuck backlogs. */
18
17
  scaleUpQueueAgeMs: 10_000,
19
18
  /** Bound one Fly reconciliation without turning backlog into a stampede. */
@@ -113,14 +112,6 @@ export function assertRuntimeCapacityPolicy(): void {
113
112
  'Receipt claim operations must leave at least five seconds for cancellation and response delivery.',
114
113
  );
115
114
  }
116
- if (
117
- RUNTIME_CAPACITY_POLICY.absurd.perOrgConcurrency >
118
- ABSURD_GLOBAL_RUN_CONCURRENCY
119
- ) {
120
- throw new Error(
121
- 'Per-org runtime concurrency cannot exceed global runtime concurrency.',
122
- );
123
- }
124
115
  if (
125
116
  RUNTIME_CAPACITY_POLICY.absurd.activeLaneMachines >
126
117
  RUNTIME_CAPACITY_POLICY.absurd.maxActiveLaneMachines
@@ -8,11 +8,12 @@ export function transientServiceUnavailableDetail(
8
8
  if (error instanceof TypeError && /^fetch failed$/i.test(detail.trim())) {
9
9
  return detail;
10
10
  }
11
- // Convex uses both an explicit ServiceUnavailable response and this generic
12
- // InternalServerError envelope for short control-plane outages. The latter
13
- // text is intentionally exact: application exceptions must not become
14
- // retryable merely because they mention an internal-server error.
15
- return /ServiceUnavailable|Service temporarily unavailable|temporarily unavailable \(Code:\s*\d+\)|"code"\s*:\s*"InternalServerError"[\s\S]*(?:Your request couldn't be completed\. Try again later\.|Your request timed out performing too many system operations\.)/i.test(
11
+ // Convex uses explicit ServiceUnavailable and ExpiredInQueue responses, as
12
+ // well as this generic InternalServerError envelope for short control-plane
13
+ // outages. The latter text is intentionally exact: application exceptions
14
+ // must not become retryable merely because they mention an internal-server
15
+ // error.
16
+ return /ServiceUnavailable|Service temporarily unavailable|temporarily unavailable \(Code:\s*\d+\)|"code"\s*:\s*"ExpiredInQueue"|"code"\s*:\s*"InternalServerError"[\s\S]*(?:Your request couldn't be completed\. Try again later\.|Your request timed out performing too many system operations\.)/i.test(
16
17
  detail,
17
18
  )
18
19
  ? detail
@@ -71,6 +71,7 @@ export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
71
71
  'play_authoring_fetch_key_reused_in_loop',
72
72
  'play_authoring_binding_invalid',
73
73
  'play_authoring_input_schema_unresolved',
74
+ 'tool_response_raw_access',
74
75
  'check_error',
75
76
  'docflow_compile_error',
76
77
  'docflow_binding_drift',
@@ -223,9 +223,12 @@ function sourceIdentityPath(
223
223
  ): string {
224
224
  if (!sourceIdentityRoot) return filePath;
225
225
  const identityRoot = resolve(sourceIdentityRoot);
226
- const logicalPath = relative(identityRoot, resolve(filePath));
226
+ const resolvedFilePath = resolve(filePath);
227
+ const logicalPath = relative(identityRoot, resolvedFilePath);
228
+ if (!logicalPath) {
229
+ return basename(resolvedFilePath);
230
+ }
227
231
  if (
228
- !logicalPath ||
229
232
  logicalPath === '..' ||
230
233
  logicalPath.startsWith(`..${sep}`) ||
231
234
  isAbsolute(logicalPath)
package/dist/cli/index.js CHANGED
@@ -185,7 +185,7 @@ function configureProxyFromEnv() {
185
185
  configureProxyFromEnv();
186
186
 
187
187
  // src/cli/index.ts
188
- var import_promises9 = require("fs/promises");
188
+ var import_promises10 = require("fs/promises");
189
189
  var import_node_path26 = require("path");
190
190
  var import_node_os19 = require("os");
191
191
  var import_commander4 = require("commander");
@@ -1047,7 +1047,7 @@ var SDK_RELEASE = {
1047
1047
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1048
1048
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1049
1049
  // getters keep their established compatibility behavior.
1050
- version: "0.3.0",
1050
+ version: "0.3.1",
1051
1051
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1052
1052
  contracts: {
1053
1053
  api: {
@@ -11068,7 +11068,7 @@ Examples:
11068
11068
  }
11069
11069
 
11070
11070
  // src/cli/commands/enrich.ts
11071
- var import_promises6 = require("fs/promises");
11071
+ var import_promises7 = require("fs/promises");
11072
11072
  var import_node_os10 = require("os");
11073
11073
  var import_node_path15 = require("path");
11074
11074
  var import_commander2 = require("commander");
@@ -11076,7 +11076,7 @@ var import_commander2 = require("commander");
11076
11076
  // src/cli/commands/play.ts
11077
11077
  var import_node_crypto6 = require("crypto");
11078
11078
  var import_node_fs12 = require("fs");
11079
- var import_promises5 = require("fs/promises");
11079
+ var import_promises6 = require("fs/promises");
11080
11080
  var import_node_path14 = require("path");
11081
11081
  var import_sync5 = require("csv-parse/sync");
11082
11082
 
@@ -12784,6 +12784,7 @@ var import_node_os9 = require("os");
12784
12784
  var import_node_path13 = require("path");
12785
12785
  var import_node_url = require("url");
12786
12786
  var import_node_fs11 = require("fs");
12787
+ var import_promises5 = require("fs/promises");
12787
12788
 
12788
12789
  // ../shared_libs/plays/bundling/index.ts
12789
12790
  var import_node_crypto4 = require("crypto");
@@ -18314,7 +18315,7 @@ var PLAY_RUN_RESERVED_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
18314
18315
  ]);
18315
18316
  async function pathExistsIncludingSymlink(path) {
18316
18317
  try {
18317
- await (0, import_promises5.lstat)(path);
18318
+ await (0, import_promises6.lstat)(path);
18318
18319
  return true;
18319
18320
  } catch (error) {
18320
18321
  if (error.code === "ENOENT") {
@@ -18331,7 +18332,7 @@ function runIdFileTempPath(destination) {
18331
18332
  }
18332
18333
  async function removeRunIdTempFile(path) {
18333
18334
  try {
18334
- await (0, import_promises5.unlink)(path);
18335
+ await (0, import_promises6.unlink)(path);
18335
18336
  } catch (error) {
18336
18337
  if (error.code !== "ENOENT") {
18337
18338
  throw error;
@@ -18348,7 +18349,7 @@ async function preflightRunIdFile(path) {
18348
18349
  const tempPath = runIdFileTempPath(destination);
18349
18350
  let handle = null;
18350
18351
  try {
18351
- handle = await (0, import_promises5.open)(tempPath, "wx", 384);
18352
+ handle = await (0, import_promises6.open)(tempPath, "wx", 384);
18352
18353
  await handle.sync();
18353
18354
  } catch (error) {
18354
18355
  throw new Error(
@@ -18366,7 +18367,7 @@ async function readRunIdFile(destination) {
18366
18367
  }
18367
18368
  let parsed;
18368
18369
  try {
18369
- parsed = JSON.parse(await (0, import_promises5.readFile)(destination, "utf8"));
18370
+ parsed = JSON.parse(await (0, import_promises6.readFile)(destination, "utf8"));
18370
18371
  } catch (error) {
18371
18372
  throw new Error(
18372
18373
  `--run-id-file destination contains invalid JSON: ${destination}. Refusing to overwrite it (${error instanceof Error ? error.message : String(error)}).`
@@ -18390,7 +18391,7 @@ async function writePlayRunIdFile(destination, runId) {
18390
18391
  const tempPath = runIdFileTempPath(destination);
18391
18392
  let handle = null;
18392
18393
  try {
18393
- handle = await (0, import_promises5.open)(tempPath, "wx", 384);
18394
+ handle = await (0, import_promises6.open)(tempPath, "wx", 384);
18394
18395
  await handle.writeFile(
18395
18396
  `${JSON.stringify({ version: 1, runId })}
18396
18397
  `,
@@ -18400,8 +18401,8 @@ async function writePlayRunIdFile(destination, runId) {
18400
18401
  await handle.close();
18401
18402
  handle = null;
18402
18403
  try {
18403
- await (0, import_promises5.link)(tempPath, destination);
18404
- const directory = await (0, import_promises5.open)((0, import_node_path14.dirname)(destination), "r");
18404
+ await (0, import_promises6.link)(tempPath, destination);
18405
+ const directory = await (0, import_promises6.open)((0, import_node_path14.dirname)(destination), "r");
18405
18406
  try {
18406
18407
  await directory.sync();
18407
18408
  } finally {
@@ -27693,6 +27694,7 @@ var ENRICH_DEBUG_T0 = Date.now();
27693
27694
  var GENERATED_ENRICH_ROWS_TABLE_NAMESPACE = "deepline_enrich_rows";
27694
27695
  var SDK_ENRICH_TELEMETRY_TIMEOUT_MS = 1e4;
27695
27696
  var SDK_ENRICH_TELEMETRY_COMMAND_MAX_LENGTH = 2e4;
27697
+ var ENRICH_DEPRECATION_NOTICE = "DEPRECATION: `deepline enrich` is deprecated and no longer actively maintained. Use `deepline plays` for the supported workflow.\n";
27696
27698
  var ENRICH_AI_RUNTIME_COMPACT_TOOLS = /* @__PURE__ */ new Set([
27697
27699
  "ai_inference",
27698
27700
  "aiinference",
@@ -27866,7 +27868,7 @@ async function readAtFileReference(value, argumentName, strip = true) {
27866
27868
  throw new Error(`Invalid ${argumentName} value: empty @file path.`);
27867
27869
  }
27868
27870
  try {
27869
- const text = await (0, import_promises6.readFile)(filePath, "utf8");
27871
+ const text = await (0, import_promises7.readFile)(filePath, "utf8");
27870
27872
  const normalized = text.replace(/^\uFEFF/, "");
27871
27873
  return strip ? normalized.trim() : normalized;
27872
27874
  } catch (error) {
@@ -28164,7 +28166,7 @@ async function buildPlanArgs(args) {
28164
28166
  async function assertInputCsvExists(inputCsv) {
28165
28167
  const path = (0, import_node_path15.resolve)(inputCsv);
28166
28168
  try {
28167
- const info = await (0, import_promises6.stat)(path);
28169
+ const info = await (0, import_promises7.stat)(path);
28168
28170
  if (info.isFile()) {
28169
28171
  return;
28170
28172
  }
@@ -28185,8 +28187,8 @@ async function assertSafeOutputPath(inputCsv, outputPath) {
28185
28187
  }
28186
28188
  try {
28187
28189
  const [inputInfo, outputInfo] = await Promise.all([
28188
- (0, import_promises6.stat)(input2),
28189
- (0, import_promises6.stat)(output2)
28190
+ (0, import_promises7.stat)(input2),
28191
+ (0, import_promises7.stat)(output2)
28190
28192
  ]);
28191
28193
  if (inputInfo.dev === outputInfo.dev && inputInfo.ino === outputInfo.ino) {
28192
28194
  throw new Error(
@@ -28206,7 +28208,7 @@ async function assertSafeOutputPath(inputCsv, outputPath) {
28206
28208
  }
28207
28209
  async function regularFileExists(path) {
28208
28210
  try {
28209
- const info = await (0, import_promises6.stat)((0, import_node_path15.resolve)(path));
28211
+ const info = await (0, import_promises7.stat)((0, import_node_path15.resolve)(path));
28210
28212
  return info.isFile();
28211
28213
  } catch (error) {
28212
28214
  const code = error && typeof error === "object" ? error.code : void 0;
@@ -28217,7 +28219,7 @@ async function regularFileExists(path) {
28217
28219
  }
28218
28220
  }
28219
28221
  async function readConfig(path) {
28220
- const source = await (0, import_promises6.readFile)((0, import_node_path15.resolve)(path), "utf8");
28222
+ const source = await (0, import_promises7.readFile)((0, import_node_path15.resolve)(path), "utf8");
28221
28223
  let parsed;
28222
28224
  try {
28223
28225
  parsed = JSON.parse(source);
@@ -28638,7 +28640,7 @@ async function writeOutputCsv(outputPath, status, options) {
28638
28640
  ]),
28639
28641
  options?.config
28640
28642
  );
28641
- await (0, import_promises6.writeFile)(
28643
+ await (0, import_promises7.writeFile)(
28642
28644
  (0, import_node_path15.resolve)(outputPath),
28643
28645
  csvStringFromRows(merged.rows, columns),
28644
28646
  "utf8"
@@ -30318,7 +30320,7 @@ async function persistEnrichFailureReport(input2) {
30318
30320
  }
30319
30321
  const stateDir = (0, import_node_path15.join)((0, import_node_os10.homedir)(), ".local", "deepline", "runtime", "state");
30320
30322
  const reportPrefix = input2.jobs.length > 0 ? "run-block-failures" : "enrich-issues";
30321
- await (0, import_promises6.mkdir)(stateDir, { recursive: true });
30323
+ await (0, import_promises7.mkdir)(stateDir, { recursive: true });
30322
30324
  const reportPath = (0, import_node_path15.join)(
30323
30325
  stateDir,
30324
30326
  `${reportPrefix}-${Math.floor(Date.now() / 1e3)}-${process.pid}.json`
@@ -30344,7 +30346,7 @@ async function persistEnrichFailureReport(input2) {
30344
30346
  if (input2.rows.rowStart !== null && input2.rows.rowEnd !== null) {
30345
30347
  report.rows = { start: input2.rows.rowStart, end: input2.rows.rowEnd };
30346
30348
  }
30347
- await (0, import_promises6.writeFile)(reportPath, `${JSON.stringify(report, null, 2)}
30349
+ await (0, import_promises7.writeFile)(reportPath, `${JSON.stringify(report, null, 2)}
30348
30350
  `, "utf8");
30349
30351
  return reportPath;
30350
30352
  }
@@ -31162,6 +31164,7 @@ function registerEnrichCommand(program) {
31162
31164
  "--max-credits-per-run <credits>",
31163
31165
  "Set a hard Deepline-credit ceiling enforced by the runtime for this run."
31164
31166
  ).action(async (options, _command) => {
31167
+ process.stderr.write(ENRICH_DEPRECATION_NOTICE);
31165
31168
  if (currentEnrichArgs().some(
31166
31169
  (arg) => arg === "--no-open" || arg.startsWith("--no-open=")
31167
31170
  )) {
@@ -31247,13 +31250,13 @@ function registerEnrichCommand(program) {
31247
31250
  sdkEnrichTelemetryCompleted = true;
31248
31251
  await completeSdkEnrichTelemetry(sdkEnrichTelemetry, input2);
31249
31252
  };
31250
- const tempDir = await (0, import_promises6.mkdtemp)((0, import_node_path15.join)((0, import_node_os10.tmpdir)(), "deepline-enrich-play-"));
31253
+ const tempDir = await (0, import_promises7.mkdtemp)((0, import_node_path15.join)((0, import_node_os10.tmpdir)(), "deepline-enrich-play-"));
31251
31254
  await emitSdkEnrichTelemetry(sdkEnrichTelemetry, "enrich_started");
31252
31255
  const tempPlay = (0, import_node_path15.join)(tempDir, "deepline-enrich.play.ts");
31253
31256
  let inPlaceTempDir = null;
31254
31257
  let inPlaceTempOutputPath = null;
31255
31258
  const inPlaceFinalOutputPath = options.inPlace ? (0, import_node_path15.resolve)(inputCsv) : null;
31256
- const inPlaceCommitOutputPath = options.inPlace ? (await (0, import_promises6.lstat)(inputCsv)).isSymbolicLink() ? await (0, import_promises6.realpath)(inputCsv) : inPlaceFinalOutputPath : null;
31259
+ const inPlaceCommitOutputPath = options.inPlace ? (await (0, import_promises7.lstat)(inputCsv)).isSymbolicLink() ? await (0, import_promises7.realpath)(inputCsv) : inPlaceFinalOutputPath : null;
31257
31260
  const failureReportOutputPath = options.inPlace ? inPlaceFinalOutputPath : null;
31258
31261
  const enrichIssueFollowUpOutputPath = options.inPlace && inPlaceFinalOutputPath ? sidecarEnrichRowsExportPath(inPlaceFinalOutputPath) : null;
31259
31262
  let activeRunId2 = null;
@@ -31274,16 +31277,16 @@ function registerEnrichCommand(program) {
31274
31277
  return;
31275
31278
  }
31276
31279
  if (inPlaceTempDir) {
31277
- await (0, import_promises6.rm)(inPlaceTempDir, { recursive: true, force: true });
31280
+ await (0, import_promises7.rm)(inPlaceTempDir, { recursive: true, force: true });
31278
31281
  }
31279
- inPlaceTempDir = await (0, import_promises6.mkdtemp)(
31282
+ inPlaceTempDir = await (0, import_promises7.mkdtemp)(
31280
31283
  (0, import_node_path15.join)(
31281
31284
  (0, import_node_path15.dirname)(inPlaceCommitOutputPath ?? (0, import_node_path15.resolve)(inputCsv)),
31282
31285
  ".deepline-enrich-in-place-"
31283
31286
  )
31284
31287
  );
31285
31288
  inPlaceTempOutputPath = (0, import_node_path15.join)(inPlaceTempDir, "output.csv");
31286
- await (0, import_promises6.copyFile)((0, import_node_path15.resolve)(inputCsv), inPlaceTempOutputPath);
31289
+ await (0, import_promises7.copyFile)((0, import_node_path15.resolve)(inputCsv), inPlaceTempOutputPath);
31287
31290
  outputPath = inPlaceTempOutputPath;
31288
31291
  };
31289
31292
  const commitInPlaceOutput = async (exportResult) => {
@@ -31291,12 +31294,12 @@ function registerEnrichCommand(program) {
31291
31294
  return exportResult;
31292
31295
  }
31293
31296
  const committedTempDir = inPlaceTempDir;
31294
- await (0, import_promises6.rename)(inPlaceTempOutputPath, inPlaceCommitOutputPath);
31297
+ await (0, import_promises7.rename)(inPlaceTempOutputPath, inPlaceCommitOutputPath);
31295
31298
  inPlaceTempDir = null;
31296
31299
  inPlaceTempOutputPath = null;
31297
31300
  outputPath = inPlaceFinalOutputPath;
31298
31301
  if (committedTempDir) {
31299
- await (0, import_promises6.rm)(committedTempDir, { recursive: true, force: true });
31302
+ await (0, import_promises7.rm)(committedTempDir, { recursive: true, force: true });
31300
31303
  }
31301
31304
  if (!exportResult) {
31302
31305
  return null;
@@ -31309,7 +31312,7 @@ function registerEnrichCommand(program) {
31309
31312
  try {
31310
31313
  process.once("SIGINT", onSigint);
31311
31314
  process.once("SIGTERM", onSigterm);
31312
- await (0, import_promises6.writeFile)(tempPlay, playSource, "utf8");
31315
+ await (0, import_promises7.writeFile)(tempPlay, playSource, "utf8");
31313
31316
  if (options.inPlace) {
31314
31317
  await prepareInPlaceOutput();
31315
31318
  }
@@ -31503,11 +31506,11 @@ function registerEnrichCommand(program) {
31503
31506
  process.removeListener("SIGINT", onSigint);
31504
31507
  process.removeListener("SIGTERM", onSigterm);
31505
31508
  if (inPlaceTempDir) {
31506
- await (0, import_promises6.rm)(inPlaceTempDir, { recursive: true, force: true });
31509
+ await (0, import_promises7.rm)(inPlaceTempDir, { recursive: true, force: true });
31507
31510
  } else if (inPlaceTempOutputPath) {
31508
- await (0, import_promises6.rm)(inPlaceTempOutputPath, { force: true });
31511
+ await (0, import_promises7.rm)(inPlaceTempOutputPath, { force: true });
31509
31512
  }
31510
- await (0, import_promises6.rm)(tempDir, { recursive: true, force: true });
31513
+ await (0, import_promises7.rm)(tempDir, { recursive: true, force: true });
31511
31514
  }
31512
31515
  });
31513
31516
  }
@@ -32399,7 +32402,7 @@ Examples:
32399
32402
 
32400
32403
  // src/cli/commands/monitors.ts
32401
32404
  var import_node_fs14 = require("fs");
32402
- var import_promises7 = require("readline/promises");
32405
+ var import_promises8 = require("readline/promises");
32403
32406
  var JSON_OPTION_DESCRIPTION = "Emit JSON output. Also automatic when stdout is piped";
32404
32407
  function withJsonOption(command) {
32405
32408
  return command.option("--json", JSON_OPTION_DESCRIPTION);
@@ -33168,7 +33171,7 @@ async function handleMonitorsValidate(key, options) {
33168
33171
  if (result.valid === false) process.exitCode = 7;
33169
33172
  }
33170
33173
  async function confirmMonitorDelete(key, options) {
33171
- const rl = (0, import_promises7.createInterface)({
33174
+ const rl = (0, import_promises8.createInterface)({
33172
33175
  input: process.stdin,
33173
33176
  output: process.stderr
33174
33177
  });
@@ -40185,7 +40188,7 @@ Examples:
40185
40188
  }
40186
40189
 
40187
40190
  // src/cli/commands/workflow.ts
40188
- var import_promises8 = require("fs/promises");
40191
+ var import_promises9 = require("fs/promises");
40189
40192
  var import_node_path24 = require("path");
40190
40193
 
40191
40194
  // src/cli/workflow-to-play.ts
@@ -40403,7 +40406,7 @@ function readStatus(payload) {
40403
40406
  }
40404
40407
  async function readJsonOption(payload, file) {
40405
40408
  if (file) {
40406
- const raw = await (0, import_promises8.readFile)((0, import_node_path24.resolve)(file), "utf8");
40409
+ const raw = await (0, import_promises9.readFile)((0, import_node_path24.resolve)(file), "utf8");
40407
40410
  return JSON.parse(raw);
40408
40411
  }
40409
40412
  if (payload) {
@@ -40438,8 +40441,8 @@ async function transformOne(api, workflowId, outDir, publish) {
40438
40441
  { workflowName: workflow.name, version: revision.version }
40439
40442
  );
40440
40443
  const file = (0, import_node_path24.join)((0, import_node_path24.resolve)(outDir), `${compiled.playName}.play.ts`);
40441
- await (0, import_promises8.mkdir)((0, import_node_path24.dirname)(file), { recursive: true });
40442
- await (0, import_promises8.writeFile)(file, compiled.sourceCode, "utf8");
40444
+ await (0, import_promises9.mkdir)((0, import_node_path24.dirname)(file), { recursive: true });
40445
+ await (0, import_promises9.writeFile)(file, compiled.sourceCode, "utf8");
40443
40446
  let published = false;
40444
40447
  if (publish) {
40445
40448
  const code = await handlePlayPublish([file]);
@@ -41573,10 +41576,10 @@ function topLevelCommandKnown(program, commandName) {
41573
41576
  );
41574
41577
  }
41575
41578
  async function runPlayRunnerHealthCheck() {
41576
- const dir = await (0, import_promises9.mkdtemp)((0, import_node_path26.join)((0, import_node_os19.tmpdir)(), "deepline-health-play-"));
41579
+ const dir = await (0, import_promises10.mkdtemp)((0, import_node_path26.join)((0, import_node_os19.tmpdir)(), "deepline-health-play-"));
41577
41580
  const file = (0, import_node_path26.join)(dir, "health-check.play.ts");
41578
41581
  try {
41579
- await (0, import_promises9.writeFile)(
41582
+ await (0, import_promises10.writeFile)(
41580
41583
  file,
41581
41584
  [
41582
41585
  "import { definePlay } from 'deepline';",
@@ -41625,7 +41628,7 @@ async function runPlayRunnerHealthCheck() {
41625
41628
  }
41626
41629
  };
41627
41630
  } finally {
41628
- await (0, import_promises9.rm)(dir, { recursive: true, force: true });
41631
+ await (0, import_promises10.rm)(dir, { recursive: true, force: true });
41629
41632
  }
41630
41633
  }
41631
41634
  function pickString(value, ...keys) {
@@ -1033,7 +1033,7 @@ var SDK_RELEASE = {
1033
1033
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1034
1034
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1035
1035
  // getters keep their established compatibility behavior.
1036
- version: "0.3.0",
1036
+ version: "0.3.1",
1037
1037
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1038
1038
  contracts: {
1039
1039
  api: {
@@ -11080,7 +11080,7 @@ import {
11080
11080
  mkdir as mkdir4,
11081
11081
  mkdtemp,
11082
11082
  readFile as readFile3,
11083
- realpath as realpath2,
11083
+ realpath as realpath3,
11084
11084
  rename,
11085
11085
  rm,
11086
11086
  stat as stat3,
@@ -12828,6 +12828,7 @@ import { tmpdir as tmpdir3 } from "os";
12828
12828
  import { dirname as dirname8, join as join9, resolve as resolve10 } from "path";
12829
12829
  import { fileURLToPath } from "url";
12830
12830
  import { existsSync as existsSync8 } from "fs";
12831
+ import { realpath as realpath2 } from "fs/promises";
12831
12832
 
12832
12833
  // ../shared_libs/plays/bundling/index.ts
12833
12834
  import { createHash as createHash2 } from "crypto";
@@ -27753,6 +27754,7 @@ var ENRICH_DEBUG_T0 = Date.now();
27753
27754
  var GENERATED_ENRICH_ROWS_TABLE_NAMESPACE = "deepline_enrich_rows";
27754
27755
  var SDK_ENRICH_TELEMETRY_TIMEOUT_MS = 1e4;
27755
27756
  var SDK_ENRICH_TELEMETRY_COMMAND_MAX_LENGTH = 2e4;
27757
+ var ENRICH_DEPRECATION_NOTICE = "DEPRECATION: `deepline enrich` is deprecated and no longer actively maintained. Use `deepline plays` for the supported workflow.\n";
27756
27758
  var ENRICH_AI_RUNTIME_COMPACT_TOOLS = /* @__PURE__ */ new Set([
27757
27759
  "ai_inference",
27758
27760
  "aiinference",
@@ -31222,6 +31224,7 @@ function registerEnrichCommand(program) {
31222
31224
  "--max-credits-per-run <credits>",
31223
31225
  "Set a hard Deepline-credit ceiling enforced by the runtime for this run."
31224
31226
  ).action(async (options, _command) => {
31227
+ process.stderr.write(ENRICH_DEPRECATION_NOTICE);
31225
31228
  if (currentEnrichArgs().some(
31226
31229
  (arg) => arg === "--no-open" || arg.startsWith("--no-open=")
31227
31230
  )) {
@@ -31313,7 +31316,7 @@ function registerEnrichCommand(program) {
31313
31316
  let inPlaceTempDir = null;
31314
31317
  let inPlaceTempOutputPath = null;
31315
31318
  const inPlaceFinalOutputPath = options.inPlace ? resolve12(inputCsv) : null;
31316
- const inPlaceCommitOutputPath = options.inPlace ? (await lstat2(inputCsv)).isSymbolicLink() ? await realpath2(inputCsv) : inPlaceFinalOutputPath : null;
31319
+ const inPlaceCommitOutputPath = options.inPlace ? (await lstat2(inputCsv)).isSymbolicLink() ? await realpath3(inputCsv) : inPlaceFinalOutputPath : null;
31317
31320
  const failureReportOutputPath = options.inPlace ? inPlaceFinalOutputPath : null;
31318
31321
  const enrichIssueFollowUpOutputPath = options.inPlace && inPlaceFinalOutputPath ? sidecarEnrichRowsExportPath(inPlaceFinalOutputPath) : null;
31319
31322
  let activeRunId2 = null;
package/dist/index.js CHANGED
@@ -783,7 +783,7 @@ var SDK_RELEASE = {
783
783
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
784
784
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
785
785
  // getters keep their established compatibility behavior.
786
- version: "0.3.0",
786
+ version: "0.3.1",
787
787
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
788
788
  contracts: {
789
789
  api: {
package/dist/index.mjs CHANGED
@@ -706,7 +706,7 @@ var SDK_RELEASE = {
706
706
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
707
707
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
708
708
  // getters keep their established compatibility behavior.
709
- version: "0.3.0",
709
+ version: "0.3.1",
710
710
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
711
711
  contracts: {
712
712
  api: {
@@ -9,6 +9,7 @@ import { tmpdir as tmpdir2 } from "os";
9
9
  import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
10
10
  import { fileURLToPath } from "url";
11
11
  import { existsSync as existsSync2 } from "fs";
12
+ import { realpath as realpath2 } from "fs/promises";
12
13
 
13
14
  // ../shared_libs/plays/bundling/index.ts
14
15
  import { createHash } from "crypto";
@@ -5286,8 +5287,12 @@ function sha256(value) {
5286
5287
  function sourceIdentityPath(filePath, sourceIdentityRoot) {
5287
5288
  if (!sourceIdentityRoot) return filePath;
5288
5289
  const identityRoot = resolve(sourceIdentityRoot);
5289
- const logicalPath = relative(identityRoot, resolve(filePath));
5290
- if (!logicalPath || logicalPath === ".." || logicalPath.startsWith(`..${sep}`) || isAbsolute(logicalPath)) {
5290
+ const resolvedFilePath = resolve(filePath);
5291
+ const logicalPath = relative(identityRoot, resolvedFilePath);
5292
+ if (!logicalPath) {
5293
+ return basename(resolvedFilePath);
5294
+ }
5295
+ if (logicalPath === ".." || logicalPath.startsWith(`..${sep}`) || isAbsolute(logicalPath)) {
5291
5296
  return filePath;
5292
5297
  }
5293
5298
  return logicalPath.split(/[\\/]+/).join("/");
@@ -7201,10 +7206,17 @@ function createSdkPlayBundlingAdapter() {
7201
7206
  };
7202
7207
  }
7203
7208
  async function bundlePlayFile2(filePath, options = {}) {
7209
+ const localWorkspaceRoot = dirname3(resolve3(filePath));
7210
+ const sourceIdentityRoot = await realpath2(localWorkspaceRoot).catch(
7211
+ () => localWorkspaceRoot
7212
+ );
7204
7213
  const result = await bundlePlayFile(filePath, {
7205
7214
  target: options.target ?? defaultPlayBundleTarget(),
7206
7215
  exportName: options.exportName,
7207
- adapter: createSdkPlayBundlingAdapter()
7216
+ adapter: {
7217
+ ...createSdkPlayBundlingAdapter(),
7218
+ sourceIdentityRoot
7219
+ }
7208
7220
  });
7209
7221
  if (result.success)
7210
7222
  validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",