deepline 0.3.333 → 0.3.335

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 (122) hide show
  1. package/dist/bundling-sources/sdk/src/play.ts +1 -1
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +227 -61
  4. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +18 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +8 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +64 -7
  7. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +14 -14
  8. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +12 -5
  9. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +6 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +55 -6
  11. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +1 -0
  12. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +7 -0
  13. package/dist/cli/auth-7K5HVWDU.js +31 -0
  14. package/dist/cli/{auth-OQ356CEE.mjs → auth-UAPR5UHG.mjs} +4 -4
  15. package/dist/cli/{billing-7YVHLDMO.mjs → billing-2M2MOKZ2.mjs} +4 -4
  16. package/dist/cli/{billing-3APFTHNY.js → billing-7MFJB4YT.js} +71 -71
  17. package/dist/cli/{chunk-DROPDYOD.js → chunk-2BKBPQYB.js} +1 -1
  18. package/dist/cli/{chunk-N5KGBQPG.mjs → chunk-5CYUDMCR.mjs} +3 -3
  19. package/dist/cli/{chunk-H5SORD7Q.mjs → chunk-BC5OUOUV.mjs} +2 -2
  20. package/dist/cli/{chunk-KREEYWVL.mjs → chunk-DPTGX6LJ.mjs} +23 -10
  21. package/dist/cli/{chunk-JNNOCMQY.js → chunk-EBU7LQ64.js} +18 -6
  22. package/dist/cli/{chunk-NI4FJRFJ.mjs → chunk-F67PGWBO.mjs} +5 -5
  23. package/dist/cli/{chunk-HNX3RC6V.mjs → chunk-FTX3SMNS.mjs} +1 -1
  24. package/dist/cli/{chunk-2CLENWOQ.mjs → chunk-G7YDITCA.mjs} +1 -1
  25. package/dist/cli/{chunk-AFVETOG3.js → chunk-GP5TSYAJ.js} +15 -15
  26. package/dist/cli/{chunk-F2QKAGST.js → chunk-H2QPAGD4.js} +9 -9
  27. package/dist/cli/{chunk-V77J5ISS.js → chunk-H3ZK3C4S.js} +4 -4
  28. package/dist/cli/{chunk-AGGDEQDF.mjs → chunk-H6HO3EX2.mjs} +1 -1
  29. package/dist/cli/{chunk-KBXHB5P3.js → chunk-HNWBP7F4.js} +26 -26
  30. package/dist/cli/{chunk-3WQJWQ5T.js → chunk-L45TG5XU.js} +60 -60
  31. package/dist/cli/{chunk-F2XAQHQC.mjs → chunk-LD5NKLNP.mjs} +4 -4
  32. package/dist/cli/{chunk-64CZWDWT.js → chunk-MGQODOU7.js} +8 -8
  33. package/dist/cli/{chunk-6JPZJ5W7.mjs → chunk-NFZEBZN5.mjs} +2 -2
  34. package/dist/cli/{chunk-ABVOFJAE.mjs → chunk-OYWLA5NH.mjs} +1 -1
  35. package/dist/cli/{chunk-4DUEB2WO.mjs → chunk-R6HWDMKH.mjs} +2 -2
  36. package/dist/cli/{chunk-STVJ7ZPD.js → chunk-ROTTZP5Y.js} +6 -6
  37. package/dist/cli/{chunk-UHHTRBJF.js → chunk-T4A2PPAD.js} +45 -45
  38. package/dist/cli/{chunk-PRZ4S4NU.js → chunk-TMGNPDHK.js} +6 -6
  39. package/dist/cli/{chunk-SXX4ST3D.mjs → chunk-U5MMLE2K.mjs} +1 -1
  40. package/dist/cli/{chunk-IYFKFNEB.js → chunk-V67UHLXQ.js} +327 -314
  41. package/dist/cli/{chunk-SFLARO4Y.mjs → chunk-WIYWSYDS.mjs} +18 -6
  42. package/dist/cli/{chunk-5A44CWQE.js → chunk-YJWCFMIA.js} +48 -48
  43. package/dist/cli/{chunk-5UN6KLD2.mjs → chunk-Z3KXO6AE.mjs} +1 -1
  44. package/dist/cli/{chunk-IDEK4DND.js → chunk-ZY6VIQF3.js} +2 -2
  45. package/dist/cli/client-JEP4W4GO.js +20 -0
  46. package/dist/cli/{client-WKS7HQRP.mjs → client-O7SUBEWH.mjs} +3 -3
  47. package/dist/cli/{csv-ZFY2LD2K.mjs → csv-3QEFBKVA.mjs} +3 -3
  48. package/dist/cli/{csv-MMW6EKKD.js → csv-RKCZ32SM.js} +12 -12
  49. package/dist/cli/{db-FMOQU6QW.js → db-6NL2DXCZ.js} +29 -29
  50. package/dist/cli/{db-Q2NJVILC.mjs → db-YA77SZH2.mjs} +4 -4
  51. package/dist/cli/{describe-4KG2JAQV.js → describe-3T6SY4OF.js} +15 -15
  52. package/dist/cli/{describe-EWP7DMNU.mjs → describe-WRGAPAY4.mjs} +8 -8
  53. package/dist/cli/{executions-Y4XSGKN6.js → executions-4QH4WOAS.js} +6 -6
  54. package/dist/cli/{executions-UOD7FWEE.mjs → executions-QAXAJTVH.mjs} +4 -4
  55. package/dist/cli/{feedback-3F3EURZC.mjs → feedback-27PPTJMV.mjs} +3 -3
  56. package/dist/cli/{feedback-TEJJUXDI.js → feedback-LE2I5YC6.js} +6 -6
  57. package/dist/cli/getting-started.d.mts +47 -9
  58. package/dist/cli/getting-started.d.ts +47 -9
  59. package/dist/cli/getting-started.js +2 -2
  60. package/dist/cli/getting-started.mjs +1 -1
  61. package/dist/cli/{git-5YM22RGA.mjs → git-C7WLGDRA.mjs} +4 -4
  62. package/dist/cli/git-PY2E4CUB.js +23 -0
  63. package/dist/cli/index.js +79 -79
  64. package/dist/cli/index.mjs +31 -31
  65. package/dist/cli/{legacy-noops-T5OUSXMR.js → legacy-noops-LK44HVCZ.js} +7 -7
  66. package/dist/cli/{legacy-noops-D5XBVUHS.mjs → legacy-noops-MENMLXEB.mjs} +4 -4
  67. package/dist/cli/{monitors-VI7VBQRP.js → monitors-D2MOP24X.js} +85 -85
  68. package/dist/cli/{monitors-LPZNYNQW.mjs → monitors-EPYJCNDI.mjs} +4 -4
  69. package/dist/cli/{org-BYIDOMJB.mjs → org-N5YSFPOM.mjs} +4 -4
  70. package/dist/cli/{org-OBGIV5QE.js → org-ZEXEZJWX.js} +60 -60
  71. package/dist/cli/{play-Y5PQQRBO.mjs → play-EJJKDHUY.mjs} +7 -7
  72. package/dist/cli/play-ZLRJLPLS.js +153 -0
  73. package/dist/cli/{providers-GEGIGKOL.mjs → providers-5UPKT6WL.mjs} +4 -4
  74. package/dist/cli/{providers-VH6J7FFY.js → providers-BE7A6VON.js} +7 -7
  75. package/dist/cli/{quickstart-GHITFT27.mjs → quickstart-FPBWZ3XR.mjs} +4 -4
  76. package/dist/cli/{quickstart-ZQX5XNHG.js → quickstart-QYWCXL3Z.js} +12 -12
  77. package/dist/cli/{search-AFX3DYAC.mjs → search-WN6UAW3I.mjs} +5 -5
  78. package/dist/cli/search-XM4JURG3.js +26 -0
  79. package/dist/cli/{secrets-F37M7D7R.js → secrets-2NBEJSA7.js} +13 -13
  80. package/dist/cli/{secrets-4EOVXQJM.mjs → secrets-DGY65IAZ.mjs} +4 -4
  81. package/dist/cli/{sessions-T57Q72GD.js → sessions-EPSCU52L.js} +5 -5
  82. package/dist/cli/{sessions-RX5SFHDI.mjs → sessions-FQTEUX6Q.mjs} +4 -4
  83. package/dist/cli/{settings-764O5ZEH.mjs → settings-AVW7W6KG.mjs} +7 -7
  84. package/dist/cli/{settings-3FVWW7GL.js → settings-QWUYZKVG.js} +74 -74
  85. package/dist/cli/{setup-X7OAHFTO.mjs → setup-D3ZMOSVR.mjs} +7 -7
  86. package/dist/cli/{setup-FQVRVTR6.js → setup-G7F5LIFR.js} +49 -49
  87. package/dist/cli/{skills-O6ILHPV5.mjs → skills-744HOKKT.mjs} +4 -4
  88. package/dist/cli/skills-FNGT3DEV.js +28 -0
  89. package/dist/cli/tools-CPNEQIQP.js +40 -0
  90. package/dist/cli/{tools-HL4SQG35.mjs → tools-DJRG7RTC.mjs} +6 -6
  91. package/dist/cli/update-K7FVDY7F.js +28 -0
  92. package/dist/cli/{update-A3P2RSIB.mjs → update-ORTHR3FA.mjs} +6 -6
  93. package/dist/cli/{workflow-62AGKI6T.js → workflow-5IIN7CW2.js} +15 -15
  94. package/dist/cli/{workflow-44424AVE.mjs → workflow-XD7CRWCQ.mjs} +5 -5
  95. package/dist/{client-l-IoLh8Z.d.ts → client-DkoxT4wF.d.ts} +1 -1
  96. package/dist/{client-D5XOQKsA.d.mts → client-PDAfbylh.d.mts} +1 -1
  97. package/dist/{compiler-manifest-82q4jtfQ.d.ts → compiler-manifest-CButyeZ8.d.mts} +7 -0
  98. package/dist/{compiler-manifest-82q4jtfQ.d.mts → compiler-manifest-CButyeZ8.d.ts} +7 -0
  99. package/dist/{index-BPbW2XHF.d.mts → index-BMsNUoL5.d.mts} +1 -1
  100. package/dist/{index-k0rquHIm.d.ts → index-LDRzwo09.d.ts} +1 -1
  101. package/dist/index.d.mts +6 -6
  102. package/dist/index.d.ts +6 -6
  103. package/dist/index.js +6 -6
  104. package/dist/index.mjs +3 -3
  105. package/dist/install-integrity.json +82 -82
  106. package/dist/plays/bundle-play-file.d.mts +3 -3
  107. package/dist/plays/bundle-play-file.d.ts +3 -3
  108. package/dist/plays/run-request.d.mts +2 -2
  109. package/dist/plays/run-request.d.ts +2 -2
  110. package/dist/release.d.mts +1 -1
  111. package/dist/release.d.ts +1 -1
  112. package/dist/release.js +2 -2
  113. package/dist/release.mjs +1 -1
  114. package/package.json +1 -1
  115. package/dist/cli/auth-26GA3RM6.js +0 -31
  116. package/dist/cli/client-IZRTEBU4.js +0 -20
  117. package/dist/cli/git-GNEZSTVF.js +0 -23
  118. package/dist/cli/play-5JYRSXAL.js +0 -153
  119. package/dist/cli/search-YIXGQFXA.js +0 -26
  120. package/dist/cli/skills-DPGB67XX.js +0 -28
  121. package/dist/cli/tools-WZCXSXER.js +0 -40
  122. package/dist/cli/update-63MANY7U.js +0 -28
@@ -477,7 +477,7 @@ export type CsvOptions = PlayAuthoringCsvOptions;
477
477
  * // Emit a log line (visible in `play tail`)
478
478
  * ctx.log(`Loaded ${await leads.count()} leads`);
479
479
  *
480
- * // Pause execution
480
+ * // Wait at least one second before continuing.
481
481
  * await ctx.sleep(1000);
482
482
  *
483
483
  * // Access submitted input through the handler's second argument.
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.333',
203
+ version: '0.3.335',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -493,6 +493,7 @@ import {
493
493
  createRuntimeSheetDatasetPageReader,
494
494
  readRuntimeSheetDatasetRowKeys,
495
495
  RuntimeSheetAttemptSupersededError,
496
+ type RuntimeSheetDatasetPageReader,
496
497
  } from './runtime-api';
497
498
  import { executionGatewayDecodedBodyBytes } from './execution-gateway-response-size';
498
499
  import {
@@ -654,6 +655,7 @@ const TOOL_RETRY_AFTER_FALLBACK_MS = 1_000;
654
655
  export const TOOL_EXECUTE_RESPONSE_BODY_TIMEOUT_MS = 30_000;
655
656
  const TOOL_RETRY_HEARTBEAT_INTERVAL_MS = 30_000;
656
657
  const TOOL_PROVIDER_RESIDENT_WAIT_BUDGET_MS = 5_000;
658
+ const AUTHORED_SLEEP_RESIDENT_LIMIT_MS = 10_000;
657
659
  const DEEPLINEAGENT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000;
658
660
  // Every runtime-API tool fetch needs a client-side deadline. Without one, a
659
661
  // stalled execute request (e.g. a credential-less call whose gateway connection
@@ -2929,6 +2931,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2929
2931
  */
2930
2932
  private readonly persistenceLatch: RuntimePersistenceLatch =
2931
2933
  createRuntimePersistenceLatch();
2934
+ private sleepMetadataPersistenceFailed = false;
2932
2935
  /**
2933
2936
  * The single source of every concurrency, budget, and pacing decision for this
2934
2937
  * run-attempt. Tool/play/row slots are blocking semaphores; budgets are charged
@@ -3154,6 +3157,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3154
3157
  */
3155
3158
  assertNoPendingDurableSuspension(): void {
3156
3159
  this.#options.executionSignal?.throwIfAborted();
3160
+ if (this.sleepMetadataPersistenceFailed) {
3161
+ throw new RuntimePersistenceCircuitOpenError(this.persistenceLatch);
3162
+ }
3157
3163
  this.assertNoSlackHitlTimeoutFailure();
3158
3164
  this.assertNoRowProviderBackpressure();
3159
3165
  if (this.pendingDurableSuspension) {
@@ -3299,6 +3305,35 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3299
3305
  base64: (value: SecretValue): SecretValue => createBase64SecretValue(value),
3300
3306
  };
3301
3307
 
3308
+ private createRuntimeSheetDatasetPageReader(
3309
+ tableNamespace: string,
3310
+ rowMode?: 'output' | 'all',
3311
+ ): RuntimeSheetDatasetPageReader {
3312
+ if (this.#options.createRuntimeSheetDatasetPageReader) {
3313
+ return this.#options.createRuntimeSheetDatasetPageReader({
3314
+ orgId: this.#options.orgId,
3315
+ executorToken: this.#options.executorToken!,
3316
+ playName: this.#options.playName!,
3317
+ runId: this.#options.runId!,
3318
+ tableNamespace,
3319
+ rowMode,
3320
+ });
3321
+ }
3322
+ return createRuntimeSheetDatasetPageReader({
3323
+ context: {
3324
+ baseUrl: this.#options.baseUrl!,
3325
+ executorToken: this.#options.executorToken!,
3326
+ dbSessionStrategy: this.#options.dbSessionStrategy,
3327
+ playName: this.#options.playName!,
3328
+ userEmail: this.#options.userEmail,
3329
+ runId: this.#options.runId!,
3330
+ },
3331
+ tableNamespace,
3332
+ runId: this.#options.runId!,
3333
+ rowMode,
3334
+ });
3335
+ }
3336
+
3302
3337
  constructor(options: ContextOptions) {
3303
3338
  this.rootRuntimeContract =
3304
3339
  options.artifactRuntimeContract ??
@@ -6235,7 +6270,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
6235
6270
  runningReceiptWaitMaxAttempts?: number;
6236
6271
  runningReceiptWaitDelayMs?: number;
6237
6272
  reclaimRunning?: boolean;
6238
- markSkipped?: (output: T) => Promise<void> | void;
6273
+ markSkipped?: (
6274
+ output: T,
6275
+ receipt: RuntimeStepReceipt,
6276
+ ) => Promise<void> | void;
6239
6277
  onRecovered?: (
6240
6278
  output: T,
6241
6279
  receipt: RuntimeStepReceipt,
@@ -9009,18 +9047,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9009
9047
  at: Date.now(),
9010
9048
  });
9011
9049
 
9012
- const runtimeBackedMapPageReader = createRuntimeSheetDatasetPageReader({
9013
- context: {
9014
- baseUrl: this.#options.baseUrl!,
9015
- executorToken: this.#options.executorToken!,
9016
- dbSessionStrategy: this.#options.dbSessionStrategy,
9017
- playName: this.#options.playName!,
9018
- userEmail: this.#options.userEmail,
9019
- runId: this.#options.runId!,
9020
- },
9021
- tableNamespace: resolvedTableNamespace,
9022
- runId: this.#options.runId!,
9023
- });
9050
+ const runtimeBackedMapPageReader =
9051
+ this.createRuntimeSheetDatasetPageReader(resolvedTableNamespace);
9024
9052
  const readRuntimeBackedMapRows = async (input: {
9025
9053
  limit: number;
9026
9054
  offset: number;
@@ -9750,19 +9778,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9750
9778
  // Output mode selects enriched rows when this replay's earlier
9751
9779
  // invocation also persisted isolated failures.
9752
9780
  const finalizationRowMode = resumedPendingRowsOnly ? 'output' : 'all';
9753
- const finalizationPageReader = createRuntimeSheetDatasetPageReader({
9754
- context: {
9755
- baseUrl: this.#options.baseUrl!,
9756
- executorToken: this.#options.executorToken!,
9757
- dbSessionStrategy: this.#options.dbSessionStrategy,
9758
- playName: this.#options.playName!,
9759
- userEmail: this.#options.userEmail,
9760
- runId: this.#options.runId!,
9761
- },
9762
- tableNamespace: resolvedTableNamespace,
9763
- runId: this.#options.runId!,
9764
- rowMode: finalizationRowMode,
9765
- });
9781
+ const finalizationPageReader = this.createRuntimeSheetDatasetPageReader(
9782
+ resolvedTableNamespace,
9783
+ finalizationRowMode,
9784
+ );
9766
9785
  results = await reconcileNodeRuntimeMapResultsWithPersistedSheet({
9767
9786
  mapName: normalizedMapNamespace,
9768
9787
  tableNamespace: resolvedTableNamespace,
@@ -9833,18 +9852,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9833
9852
  this.#options.runId,
9834
9853
  );
9835
9854
  const fullPersistedPageReader = runtimeSheetBacked
9836
- ? createRuntimeSheetDatasetPageReader({
9837
- context: {
9838
- baseUrl: this.#options.baseUrl!,
9839
- executorToken: this.#options.executorToken!,
9840
- dbSessionStrategy: this.#options.dbSessionStrategy,
9841
- playName: this.#options.playName!,
9842
- userEmail: this.#options.userEmail,
9843
- runId: this.#options.runId!,
9844
- },
9845
- tableNamespace: resolvedTableNamespace,
9846
- runId: this.#options.runId!,
9847
- })
9855
+ ? this.createRuntimeSheetDatasetPageReader(resolvedTableNamespace)
9848
9856
  : null;
9849
9857
  const materializeFullPersistedMapRows = async (limit?: number) => {
9850
9858
  if (!runtimeSheetBacked) {
@@ -10744,9 +10752,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10744
10752
  return [
10745
10753
  {
10746
10754
  inputIndex: executionRowIndex(index),
10747
- data: this.toPersistedOutputRow(
10748
- this.toOutputRow(item as Record<string, unknown>),
10749
- ),
10755
+ // Checkpoint replay must keep the admitted identity even
10756
+ // when restored output columns change the default row hash.
10757
+ data: {
10758
+ ...this.toPersistedOutputRow(
10759
+ this.toOutputRow(item as Record<string, unknown>),
10760
+ ),
10761
+ ...mapRowOutcomeRuntimeFields({ key: rowKey }),
10762
+ },
10750
10763
  ...(runtimeOptions?.authoredInputDigests?.[index]
10751
10764
  ? {
10752
10765
  authoredInputDigest:
@@ -11147,7 +11160,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11147
11160
  return [
11148
11161
  {
11149
11162
  inputIndex: executionRowIndex(index),
11150
- data: this.toPersistedOutputRow(baseRow),
11163
+ // Preserve the admitted key rather than rehashing the
11164
+ // waiting row after persisted output fields are restored.
11165
+ data: {
11166
+ ...this.toPersistedOutputRow(baseRow),
11167
+ ...mapRowOutcomeRuntimeFields({
11168
+ key: keyForIndex(index),
11169
+ }),
11170
+ },
11151
11171
  ...(runtimeOptions?.authoredInputDigests?.[index]
11152
11172
  ? {
11153
11173
  authoredInputDigest:
@@ -14581,6 +14601,111 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14581
14601
  this.suspendDurably({ kind: 'sleep', boundaryId, delayMs });
14582
14602
  }
14583
14603
 
14604
+ private sleepMetadataReceiptKey(
14605
+ boundaryId: string,
14606
+ intent: 'wake' | 'count',
14607
+ ): string {
14608
+ return durableCtxKey({
14609
+ orgId: this.#options.orgId,
14610
+ playId: this.currentExecutionScope.receipt.namespace,
14611
+ operation: 'step',
14612
+ id: `__deepline_sleep_${intent}__`,
14613
+ semanticKey: stableStringify({
14614
+ runId: this.currentReceiptOwnerRunId,
14615
+ boundaryId,
14616
+ }),
14617
+ });
14618
+ }
14619
+
14620
+ private async readStepSleepCount(boundaryId: string): Promise<number | null> {
14621
+ try {
14622
+ const receipt = await this.getRuntimeStepReceipt(
14623
+ this.sleepMetadataReceiptKey(boundaryId, 'count'),
14624
+ );
14625
+ if (receipt?.status !== 'completed') return null;
14626
+ const output = receipt.output;
14627
+ const sleepCount =
14628
+ output && typeof output === 'object' && 'sleepCount' in output
14629
+ ? output.sleepCount
14630
+ : undefined;
14631
+ if (
14632
+ typeof sleepCount !== 'number' ||
14633
+ !Number.isSafeInteger(sleepCount) ||
14634
+ sleepCount < 0
14635
+ ) {
14636
+ throw new Error(`Durable step ${boundaryId} has no valid sleep count.`);
14637
+ }
14638
+ return sleepCount;
14639
+ } catch (error) {
14640
+ this.sleepMetadataPersistenceFailed = true;
14641
+ tripRuntimePersistenceLatch(this.persistenceLatch, error);
14642
+ throw error;
14643
+ }
14644
+ }
14645
+
14646
+ private async persistStepSleepCount(
14647
+ boundaryId: string,
14648
+ sleepCount: number,
14649
+ ): Promise<void> {
14650
+ try {
14651
+ await this.completeRuntimeStepReceipt(
14652
+ this.sleepMetadataReceiptKey(boundaryId, 'count'),
14653
+ this.currentReceiptOwnerRunId,
14654
+ { sleepCount },
14655
+ COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
14656
+ );
14657
+ if ((await this.readStepSleepCount(boundaryId)) === null) {
14658
+ throw new Error(
14659
+ `Durable step ${boundaryId} did not persist its sleep count.`,
14660
+ );
14661
+ }
14662
+ } catch (error) {
14663
+ this.sleepMetadataPersistenceFailed = true;
14664
+ tripRuntimePersistenceLatch(this.persistenceLatch, error);
14665
+ throw error;
14666
+ }
14667
+ }
14668
+
14669
+ private async resolveCanonicalSleepWakeAtMs(
14670
+ boundaryId: string,
14671
+ delayMs: number,
14672
+ ): Promise<number> {
14673
+ const key = this.sleepMetadataReceiptKey(boundaryId, 'wake');
14674
+ const proposedWakeAtMs = Date.now() + delayMs;
14675
+ try {
14676
+ let receipt = await this.getRuntimeStepReceipt(key);
14677
+ if (receipt?.status !== 'completed') {
14678
+ await this.completeRuntimeStepReceipt(
14679
+ key,
14680
+ this.currentReceiptOwnerRunId,
14681
+ { wakeAtMs: proposedWakeAtMs },
14682
+ COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
14683
+ );
14684
+ receipt = await this.getRuntimeStepReceipt(key);
14685
+ }
14686
+ const output = receipt?.output;
14687
+ const wakeAtMs =
14688
+ output && typeof output === 'object' && 'wakeAtMs' in output
14689
+ ? output.wakeAtMs
14690
+ : undefined;
14691
+ if (
14692
+ receipt?.status !== 'completed' ||
14693
+ typeof wakeAtMs !== 'number' ||
14694
+ !Number.isSafeInteger(wakeAtMs) ||
14695
+ wakeAtMs < 0
14696
+ ) {
14697
+ throw new Error(
14698
+ `Durable sleep intent ${boundaryId} has no valid wake timestamp.`,
14699
+ );
14700
+ }
14701
+ return wakeAtMs;
14702
+ } catch (error) {
14703
+ this.sleepMetadataPersistenceFailed = true;
14704
+ tripRuntimePersistenceLatch(this.persistenceLatch, error);
14705
+ throw error;
14706
+ }
14707
+ }
14708
+
14584
14709
  async sleep(ms: number): Promise<void> {
14585
14710
  if (!rowContext.getStore()) this.assertRowAdmissionOpen();
14586
14711
  this.assertNoPendingDurableSuspension();
@@ -14654,17 +14779,49 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14654
14779
  if (existing?.kind === 'sleep' && existing.completedAt !== undefined) {
14655
14780
  return;
14656
14781
  }
14782
+ const wakeAtMs =
14783
+ existing?.kind === 'sleep' && existing.wakeAtMs !== undefined
14784
+ ? existing.wakeAtMs
14785
+ : this.durableDirectToolResultsBackedByReceipts
14786
+ ? await this.resolveCanonicalSleepWakeAtMs(boundaryId, delayMs)
14787
+ : undefined;
14788
+ this.assertNoPendingDurableSuspension();
14657
14789
  this.checkpoint.resolvedBoundaries = {
14658
14790
  ...(this.checkpoint.resolvedBoundaries ?? {}),
14659
14791
  [boundaryId]: {
14792
+ ...existing,
14660
14793
  kind: 'sleep',
14661
14794
  delayMs,
14795
+ ...(wakeAtMs !== undefined ? { wakeAtMs } : {}),
14662
14796
  },
14663
14797
  };
14798
+ if (
14799
+ wakeAtMs !== undefined &&
14800
+ !rowStore &&
14801
+ this.durableDirectToolResultsBackedByReceipts &&
14802
+ (wakeAtMs <= Date.now() ||
14803
+ (delayMs <= AUTHORED_SLEEP_RESIDENT_LIMIT_MS &&
14804
+ wakeAtMs < (this.#options.executionDrainAtMs ?? Infinity)))
14805
+ ) {
14806
+ const remainingMs = Math.max(0, wakeAtMs - Date.now());
14807
+ if (remainingMs > 0)
14808
+ await this.sleepWithCheckpointHeartbeat(remainingMs);
14809
+ this.assertNoPendingDurableSuspension();
14810
+ this.checkpoint.resolvedBoundaries[boundaryId] = {
14811
+ ...this.checkpoint.resolvedBoundaries[boundaryId],
14812
+ kind: 'sleep',
14813
+ delayMs,
14814
+ wakeAtMs,
14815
+ completedAt: Date.now(),
14816
+ };
14817
+ this.#options.onBatchComplete?.(this.checkpoint);
14818
+ return;
14819
+ }
14664
14820
  this.suspendDurably({
14665
14821
  kind: 'sleep',
14666
14822
  boundaryId,
14667
14823
  delayMs,
14824
+ ...(wakeAtMs !== undefined ? { wakeAtMs } : {}),
14668
14825
  });
14669
14826
  }
14670
14827
  return new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -15001,30 +15158,33 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15001
15158
  const boundaryId = this.durableBoundaryId(
15002
15159
  `step-${scope}:${normalizedKey}${boundarySuffix}`,
15003
15160
  );
15161
+ const advanceRecoveredSleepCursor = async (
15162
+ receipt?: RuntimeStepReceipt,
15163
+ ) => {
15164
+ if (rowStore?.replayCallIndexes?.recoverStep(boundaryId)) return;
15165
+ const existing = assertKnownResolvedBoundary(
15166
+ boundaryId,
15167
+ this.checkpoint.resolvedBoundaries?.[boundaryId],
15168
+ );
15169
+ const sleepCount =
15170
+ existing?.kind === 'step' && existing.sleepCount !== undefined
15171
+ ? existing.sleepCount
15172
+ : receipt?.runId === this.currentReceiptOwnerRunId
15173
+ ? ((await this.readStepSleepCount(boundaryId)) ?? 0)
15174
+ : 0;
15175
+ if (Number.isSafeInteger(sleepCount) && sleepCount > 0)
15176
+ this.sleepBoundaryIndex += sleepCount;
15177
+ };
15004
15178
  const executeStep = async (): Promise<T> => {
15005
15179
  const existing = assertKnownResolvedBoundary(
15006
15180
  boundaryId,
15007
15181
  this.checkpoint.resolvedBoundaries?.[boundaryId],
15008
15182
  );
15009
15183
  if (existing?.kind === 'step' && 'output' in existing) {
15010
- const replayedCalls =
15011
- rowStore?.replayCallIndexes?.recoverStep(boundaryId) ?? false;
15012
15184
  this.runtimeLog(
15013
15185
  `ctx.step(${normalizedKey}): recovered result from checkpoint`,
15014
15186
  );
15015
- // The step callback is not evaluated on replay, so any sleeps that it
15016
- // contained would otherwise disappear from the positional sleep
15017
- // cursor. Preserve the authored boundary sequence before evaluating
15018
- // the next sibling operation. This is deliberately run-local state;
15019
- // it is not a cross-run sleep cache or an input-derived identity.
15020
- if (
15021
- !replayedCalls &&
15022
- Number.isSafeInteger(existing.sleepCount) &&
15023
- existing.sleepCount !== undefined &&
15024
- existing.sleepCount > 0
15025
- ) {
15026
- this.sleepBoundaryIndex += existing.sleepCount;
15027
- }
15187
+ await advanceRecoveredSleepCursor();
15028
15188
  return existing.output as T;
15029
15189
  }
15030
15190
 
@@ -15047,15 +15207,20 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15047
15207
  // publish an enclosing step fallback that would make a later resume skip
15048
15208
  // the suspended work.
15049
15209
  this.assertNoPendingDurableSuspension();
15210
+ const sleepCount = rowStore?.replayCallIndexes
15211
+ ? (callAdvances.get('sleep:legacy') ?? 0)
15212
+ : this.sleepBoundaryIndex - sleepBoundaryIndexBefore;
15213
+ if (sleepCount > 0 && this.durableDirectToolResultsBackedByReceipts) {
15214
+ await this.persistStepSleepCount(boundaryId, sleepCount);
15215
+ }
15216
+ this.assertNoPendingDurableSuspension();
15050
15217
  this.checkpoint.resolvedBoundaries = {
15051
15218
  ...(this.checkpoint.resolvedBoundaries ?? {}),
15052
15219
  [boundaryId]: {
15053
15220
  kind: 'step',
15054
15221
  stepId: normalizedKey,
15055
15222
  output,
15056
- sleepCount: rowStore?.replayCallIndexes
15057
- ? (callAdvances.get('sleep:legacy') ?? 0)
15058
- : this.sleepBoundaryIndex - sleepBoundaryIndexBefore,
15223
+ sleepCount,
15059
15224
  completedAt: Date.now(),
15060
15225
  },
15061
15226
  };
@@ -15100,8 +15265,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15100
15265
  : options?.semanticKey,
15101
15266
  staleAfterSeconds: options?.staleAfterSeconds,
15102
15267
  force: this.#options.cachePolicy?.forceStepRefresh === true,
15103
- markSkipped: (output) => {
15268
+ markSkipped: async (output, receipt) => {
15104
15269
  assertJsonSerializableStepOutput(normalizedKey, output);
15270
+ await advanceRecoveredSleepCursor(receipt);
15105
15271
  },
15106
15272
  execute: executeStep,
15107
15273
  },
@@ -12,6 +12,7 @@ import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
12
12
  import type { PlayStructuredDefinition } from '../plays/definition';
13
13
  import type { PlayStaticPipeline } from '../plays/static-pipeline';
14
14
  import type { PlayLogContext, PlayLogMetadata } from './log-context';
15
+ import type { RuntimeSheetDatasetPageReader } from './runtime-api';
15
16
  import type {
16
17
  AsyncPlayRunRef,
17
18
  DurableCallStaleAfterSeconds,
@@ -772,6 +773,15 @@ export type IntegrationEventWaitHandler = {
772
773
  suspendAfterTimeoutMs?: number;
773
774
  };
774
775
 
776
+ export type RuntimeSheetDatasetPageReaderScope = {
777
+ orgId?: string;
778
+ executorToken: string;
779
+ playName: string;
780
+ runId: string;
781
+ tableNamespace: string;
782
+ rowMode?: 'output' | 'all';
783
+ };
784
+
775
785
  export interface ContextOptions {
776
786
  /**
777
787
  * Immutable worker projection resolved once from the admitted Play artifact.
@@ -815,11 +825,17 @@ export interface ContextOptions {
815
825
  rowAdmissionSignal?: AbortSignal;
816
826
  /** Local runner cancellation; never serialized into a Play artifact. */
817
827
  executionSignal?: AbortSignal;
828
+ /** Runner-local deadline for stopping new work, captured when its timer arms. */
829
+ executionDrainAtMs?: number;
818
830
  /**
819
831
  * Runtime-sheet transport selected by the runner. Daytona sandboxes use the
820
832
  * execution gateway and must never fall back to minting direct DB sessions.
821
833
  */
822
834
  dbSessionStrategy?: 'preloaded' | 'gateway_only' | 'trusted_dynamic';
835
+ /** Runner-owned reader; scoped database authority stays outside authored code. */
836
+ createRuntimeSheetDatasetPageReader?: (
837
+ scope: RuntimeSheetDatasetPageReaderScope,
838
+ ) => RuntimeSheetDatasetPageReader;
823
839
  vercelProtectionBypassToken?: string | null;
824
840
  /** Optional per-run integration execution mode for provider calls. */
825
841
  integrationMode?: 'live' | 'eval_stub' | 'fixture';
@@ -1244,6 +1260,8 @@ export interface PlayCheckpoint {
1244
1260
  | {
1245
1261
  kind: 'sleep';
1246
1262
  delayMs: number;
1263
+ /** Original wake deadline. Historical boundaries retain relative waits. */
1264
+ wakeAtMs?: number;
1247
1265
  completedAt?: number;
1248
1266
  scope?: {
1249
1267
  type: 'workflow' | 'map_row';
@@ -777,7 +777,10 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
777
777
  runningReceiptWaitMaxAttempts?: number;
778
778
  runningReceiptWaitDelayMs?: number;
779
779
  reclaimRunning?: boolean;
780
- markSkipped?: (output: T) => Promise<void> | void;
780
+ markSkipped?: (
781
+ output: T,
782
+ receipt: RuntimeStepReceipt,
783
+ ) => Promise<void> | void;
781
784
  onRecovered?: (
782
785
  output: T,
783
786
  receipt: RuntimeStepReceipt,
@@ -812,6 +815,9 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
812
815
  ): Promise<T> => {
813
816
  input.log(`ctx.${input.operation}(${input.id}): reused completed work`);
814
817
  if (receipt.output === undefined) {
818
+ if (input.operation === 'step' && input.markSkipped) {
819
+ await input.markSkipped(receipt.output as T, receipt);
820
+ }
815
821
  return receipt.output as T;
816
822
  }
817
823
  const output = runtimeReceiptOutput<T>(receipt);
@@ -819,7 +825,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
819
825
  ? input.onRecovered(output, receipt, 'cache')
820
826
  : output;
821
827
  if (input.markSkipped) {
822
- await input.markSkipped(recovered);
828
+ await input.markSkipped(recovered, receipt);
823
829
  }
824
830
  return recovered;
825
831
  };
@@ -87,6 +87,7 @@ import { getPlayRuntimeBatchStrategy } from '../play-runtime-batching-registry';
87
87
  import type { ToolResultMetadataInput } from '../tool-result';
88
88
  import type {
89
89
  ContextOptions,
90
+ RuntimeSheetDatasetPageReaderScope,
90
91
  IntegrationEventWaitBoundary,
91
92
  IntegrationEventWaitHandler,
92
93
  IntegrationEventWaitRuntimeContext,
@@ -121,6 +122,8 @@ import {
121
122
  import {
122
123
  resolveRuntimeReferencedPlay,
123
124
  createCompleteRuntimeMapRowsOperation,
125
+ createRuntimeSheetDatasetPageReader,
126
+ type RuntimeSheetDatasetPageReader,
124
127
  readRuntimeSheetDatasetRowKeys,
125
128
  readRuntimeSheetDatasetRows,
126
129
  releaseRuntimeWorkReceipts,
@@ -1159,6 +1162,7 @@ export interface ExecutePlayRunnerOptions {
1159
1162
  signal?: AbortSignal;
1160
1163
  /** Stops future row admission while already admitted rows can persist. */
1161
1164
  rowAdmissionSignal?: AbortSignal;
1165
+ executionDrainAtMs?: number;
1162
1166
  /** Runner-local terminal state made available to the deadline drain. */
1163
1167
  onRuntimeReady?: (runtime: {
1164
1168
  log: (line: string) => void;
@@ -1318,6 +1322,48 @@ export function assertPlayRunnerRuntimeSheetPersistenceContext(
1318
1322
  }
1319
1323
  }
1320
1324
 
1325
+ /** Keep launch-scoped database authority in the runner that admitted it. */
1326
+ export function createPlayRunnerRuntimeSheetDatasetPageReader(input: {
1327
+ context: PlayRunnerExecutionConfig['context'];
1328
+ preloadedDbSessions: PlayRunnerExecutionConfig['context']['preloadedDbSessions'];
1329
+ postgresSessionUnwrapKey: string | null;
1330
+ abortSignal: AbortSignal;
1331
+ scope: RuntimeSheetDatasetPageReaderScope;
1332
+ }): RuntimeSheetDatasetPageReader {
1333
+ const { context, scope } = input;
1334
+ assertPlayRunnerRuntimeSheetPersistenceContext(context);
1335
+ const rootRunId = context.runId ?? context.workflowId;
1336
+ if (
1337
+ scope.orgId !== context.orgId ||
1338
+ scope.playName !== context.playName ||
1339
+ scope.runId !== rootRunId ||
1340
+ scope.executorToken !== context.executorToken
1341
+ ) {
1342
+ throw new Error(
1343
+ 'Runtime Sheet reader scope does not match the run launch authority.',
1344
+ );
1345
+ }
1346
+ return createRuntimeSheetDatasetPageReader({
1347
+ context: {
1348
+ baseUrl: context.baseUrl,
1349
+ executorToken: scope.executorToken,
1350
+ orgId: scope.orgId,
1351
+ playName: scope.playName,
1352
+ runId: scope.runId,
1353
+ userEmail: context.userEmail,
1354
+ dbSessionStrategy: context.dbSessionStrategy,
1355
+ preloadedDbSessions: input.preloadedDbSessions,
1356
+ postgresSessionUnwrapKey: input.postgresSessionUnwrapKey,
1357
+ vercelProtectionBypassToken: context.vercelProtectionBypassToken,
1358
+ runtimeTestFaultHeader: context.runtimeTestFaultHeader ?? null,
1359
+ abortSignal: input.abortSignal,
1360
+ },
1361
+ tableNamespace: scope.tableNamespace,
1362
+ runId: scope.runId,
1363
+ rowMode: scope.rowMode,
1364
+ });
1365
+ }
1366
+
1321
1367
  function collectReturnedDatasets(value: unknown): unknown[] {
1322
1368
  const datasets: unknown[] = [];
1323
1369
  const seen = new Set<unknown>();
@@ -2036,14 +2082,15 @@ async function run(
2036
2082
  runnerRunAttempt,
2037
2083
  );
2038
2084
  const workCounterSegmentId = randomUUID();
2085
+ const runtimeLimitMs =
2086
+ validatePlaySandboxRuntimeLimits(
2087
+ config.context.sandboxRuntimeLimits ??
2088
+ STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
2089
+ ).timeoutSeconds * 1_000;
2039
2090
  const persistenceSignal =
2040
- options?.signal ??
2041
- AbortSignal.timeout(
2042
- validatePlaySandboxRuntimeLimits(
2043
- config.context.sandboxRuntimeLimits ??
2044
- STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
2045
- ).timeoutSeconds * 1_000,
2046
- );
2091
+ options?.signal ?? AbortSignal.timeout(runtimeLimitMs);
2092
+ const executionDrainAtMs =
2093
+ options?.executionDrainAtMs ?? runStartedAt + runtimeLimitMs;
2047
2094
  const contextSetupStartedAt = Date.now();
2048
2095
  const getRuntimeToolMetadata = createRuntimeToolMetadataResolver(config);
2049
2096
  const getRuntimeToolAuthScopeDigest =
@@ -2225,6 +2272,7 @@ async function run(
2225
2272
  artifactRuntimeContract,
2226
2273
  rowAdmissionSignal: options?.rowAdmissionSignal,
2227
2274
  executionSignal: persistenceSignal,
2275
+ executionDrainAtMs,
2228
2276
  executorToken: config.context.executorToken,
2229
2277
  baseUrl: config.context.baseUrl,
2230
2278
  // Runtime control stays on baseUrl (the receipt gateway); provider calls
@@ -2274,6 +2322,14 @@ async function run(
2274
2322
  }
2275
2323
  },
2276
2324
  dbSessionStrategy: config.context.dbSessionStrategy,
2325
+ createRuntimeSheetDatasetPageReader: (scope) =>
2326
+ createPlayRunnerRuntimeSheetDatasetPageReader({
2327
+ context: config.context,
2328
+ preloadedDbSessions: runtimePreloadedDbSessions,
2329
+ postgresSessionUnwrapKey: runtimePostgresSessionUnwrapKey,
2330
+ abortSignal: persistenceSignal,
2331
+ scope,
2332
+ }),
2277
2333
  fetchImpl: safeNodeStreamingOutboundFetch,
2278
2334
  vercelProtectionBypassToken: config.context.vercelProtectionBypassToken,
2279
2335
  orgId: config.context.orgId,
@@ -3894,6 +3950,7 @@ async function main() {
3894
3950
  result = await executePlayRunner(config, {
3895
3951
  signal: executionController.signal,
3896
3952
  rowAdmissionSignal: rowAdmissionController.signal,
3953
+ executionDrainAtMs: runtimeDeadline.drainAtMs,
3897
3954
  onRuntimeReady: (runtime) => {
3898
3955
  runtimeState = runtime;
3899
3956
  },