deepline 0.1.25 → 0.1.27

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.
@@ -28,7 +28,7 @@ import type {
28
28
  PlayRuntimeManifest,
29
29
  PlayRuntimeManifestMap,
30
30
  } from '../../../shared_libs/plays/compiler-manifest';
31
- import { runtimeRunActions } from '../../../shared_libs/play-runtime/runtime-actions';
31
+ import type { PlayRunLedgerEvent } from '../../../shared_libs/play-runtime/run-ledger';
32
32
  import {
33
33
  COORDINATOR_INTERNAL_TOKEN_HEADER,
34
34
  COORDINATOR_RUN_SCOPE_HEADER,
@@ -45,11 +45,22 @@ export type PlayWorkflowParams = {
45
45
  artifactHash: string;
46
46
  graphHash: string;
47
47
  input: Record<string, unknown>;
48
- inputFile?: { name?: string; r2Key?: string; path?: string } | null;
48
+ inputFile?: {
49
+ name?: string;
50
+ r2Key?: string;
51
+ storageKey?: string;
52
+ path?: string;
53
+ fileName?: string;
54
+ logicalPath?: string;
55
+ contentType?: string;
56
+ bytes?: number;
57
+ } | null;
49
58
  inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
50
59
  packagedFiles?: Array<{
51
60
  playPath: string;
52
61
  storageKey: string;
62
+ contentType?: string;
63
+ bytes?: number;
53
64
  inlineText?: string;
54
65
  }> | null;
55
66
  contractSnapshot?: unknown;
@@ -221,7 +232,7 @@ interface CoordinatorEnv {
221
232
  /**
222
233
  * Service binding to the long-lived Play Harness Worker
223
234
  * (apps/play-harness-worker). Provides typed RPC access to leaf-level
224
- * helpers (zod validation, runtime-API HTTP forwarder, …) that we
235
+ * helpers (runtime-API HTTP forwarder, Neon dataset IO, …) that we
225
236
  * deliberately keep OUT of every per-graphHash play bundle.
226
237
  *
227
238
  * Optional: when missing (e.g. an older deploy that hasn't been wired
@@ -508,6 +519,8 @@ type DynamicWorkflowMetadata = {
508
519
  packagedFiles?: Array<{
509
520
  playPath: string;
510
521
  storageKey: string;
522
+ contentType?: string;
523
+ bytes?: number;
511
524
  inlineText?: string;
512
525
  }> | null;
513
526
  };
@@ -534,10 +547,10 @@ const WORKFLOW_POOL_READY_POLL_MS = 250;
534
547
  const WORKFLOW_POOL_REFILL_ON_MISS_TIMEOUT_MS = 2_500;
535
548
  const WORKFLOW_POOL_REFILL_ON_MISS_MIN_AVAILABLE = 4;
536
549
  const WORKFLOW_POOL_CONTROL_TIMEOUT_MS = 750;
550
+ const WORKFLOW_POOL_START_ACK_TIMEOUT_MS = 750;
551
+ const WORKFLOW_POOL_START_ACK_POLL_MS = 25;
537
552
  const SUBMIT_INITIAL_STATE_MAX_WAIT_MS = 0;
538
553
  const SUBMIT_INITIAL_STATE_POLL_MS = 50;
539
- const WORKFLOW_POOL_DISABLED_REASON =
540
- 'Cloudflare Workflows start runs directly; waitForEvent is reserved for real durable external waits.';
541
554
  function buildDynamicWorkflowMetadata(
542
555
  params: PlayWorkflowParams,
543
556
  ): DynamicWorkflowMetadata {
@@ -610,7 +623,7 @@ function readWorkflowTraceContext(event: unknown): {
610
623
  }
611
624
 
612
625
  function workflowPoolEnabled(): boolean {
613
- return false;
626
+ return WORKFLOW_POOL_TARGET_SIZE > 0;
614
627
  }
615
628
 
616
629
  function workflowPoolTargetSize(): number {
@@ -876,8 +889,33 @@ async function mapRunToWorkflowInstance(input: {
876
889
  env: CoordinatorEnv;
877
890
  runId: string;
878
891
  instanceId: string;
879
- }): Promise<void> {
880
- await callWorkflowPool(input.env, '/pool-map-run', {
892
+ started?: boolean;
893
+ }): Promise<boolean> {
894
+ const body = await callWorkflowPool<{ mapped?: unknown }>(
895
+ input.env,
896
+ '/pool-map-run',
897
+ {
898
+ method: 'POST',
899
+ body: JSON.stringify({
900
+ runId: input.runId,
901
+ instanceId: input.instanceId,
902
+ started: input.started === true,
903
+ version: WORKFLOW_POOL_PROTOCOL_VERSION,
904
+ }),
905
+ },
906
+ );
907
+ return body.mapped !== false;
908
+ }
909
+
910
+ async function blockWorkflowPoolRun(input: {
911
+ env: CoordinatorEnv;
912
+ runId: string;
913
+ instanceId: string;
914
+ }): Promise<{ blocked: boolean; started: boolean }> {
915
+ const body = await callWorkflowPool<{
916
+ blocked?: unknown;
917
+ started?: unknown;
918
+ }>(input.env, '/pool-block-run', {
881
919
  method: 'POST',
882
920
  body: JSON.stringify({
883
921
  runId: input.runId,
@@ -885,6 +923,82 @@ async function mapRunToWorkflowInstance(input: {
885
923
  version: WORKFLOW_POOL_PROTOCOL_VERSION,
886
924
  }),
887
925
  });
926
+ return {
927
+ blocked: body.blocked === true,
928
+ started: body.started === true,
929
+ };
930
+ }
931
+
932
+ async function readWorkflowPoolRunMapping(input: {
933
+ env: CoordinatorEnv;
934
+ runId: string;
935
+ }): Promise<{ instanceId: string | null; startedAt: number | null }> {
936
+ const body = await callWorkflowPool<{
937
+ instanceId?: unknown;
938
+ startedAt?: unknown;
939
+ }>(
940
+ input.env,
941
+ `/pool-resolve-run?runId=${encodeURIComponent(input.runId)}&version=${encodeURIComponent(
942
+ WORKFLOW_POOL_PROTOCOL_VERSION,
943
+ )}`,
944
+ ).catch(() => ({ instanceId: null, startedAt: null }));
945
+ return {
946
+ instanceId:
947
+ typeof body.instanceId === 'string' && body.instanceId
948
+ ? body.instanceId
949
+ : null,
950
+ startedAt:
951
+ typeof body.startedAt === 'number' && Number.isFinite(body.startedAt)
952
+ ? body.startedAt
953
+ : null,
954
+ };
955
+ }
956
+
957
+ async function waitForWorkflowPoolStartAck(input: {
958
+ env: CoordinatorEnv;
959
+ runId: string;
960
+ instanceId: string;
961
+ timeoutMs: number;
962
+ }): Promise<{
963
+ acknowledged: boolean;
964
+ ms: number;
965
+ polls: number;
966
+ startedAt: number | null;
967
+ mappedInstanceId: string | null;
968
+ }> {
969
+ const startedAt = Date.now();
970
+ let polls = 0;
971
+ let latestMapping: { instanceId: string | null; startedAt: number | null } = {
972
+ instanceId: null,
973
+ startedAt: null,
974
+ };
975
+ while (Date.now() - startedAt < input.timeoutMs) {
976
+ polls += 1;
977
+ latestMapping = await readWorkflowPoolRunMapping({
978
+ env: input.env,
979
+ runId: input.runId,
980
+ });
981
+ if (
982
+ latestMapping.instanceId === input.instanceId &&
983
+ latestMapping.startedAt !== null
984
+ ) {
985
+ return {
986
+ acknowledged: true,
987
+ ms: Date.now() - startedAt,
988
+ polls,
989
+ startedAt: latestMapping.startedAt,
990
+ mappedInstanceId: latestMapping.instanceId,
991
+ };
992
+ }
993
+ await sleep(WORKFLOW_POOL_START_ACK_POLL_MS);
994
+ }
995
+ return {
996
+ acknowledged: false,
997
+ ms: Date.now() - startedAt,
998
+ polls,
999
+ startedAt: latestMapping.startedAt,
1000
+ mappedInstanceId: latestMapping.instanceId,
1001
+ };
888
1002
  }
889
1003
 
890
1004
  async function resolveWorkflowInstanceIdForRun(
@@ -894,15 +1008,8 @@ async function resolveWorkflowInstanceIdForRun(
894
1008
  if (!workflowPoolEnabled()) {
895
1009
  return workflowInstanceId(runId);
896
1010
  }
897
- const body = await callWorkflowPool<{ instanceId?: unknown }>(
898
- env,
899
- `/pool-resolve-run?runId=${encodeURIComponent(runId)}&version=${encodeURIComponent(
900
- WORKFLOW_POOL_PROTOCOL_VERSION,
901
- )}`,
902
- ).catch(() => ({ instanceId: null }));
903
- return typeof body.instanceId === 'string' && body.instanceId
904
- ? body.instanceId
905
- : workflowInstanceId(runId);
1011
+ const mapping = await readWorkflowPoolRunMapping({ env, runId });
1012
+ return mapping.instanceId ? mapping.instanceId : workflowInstanceId(runId);
906
1013
  }
907
1014
 
908
1015
  async function clearWorkflowPool(env: CoordinatorEnv): Promise<number> {
@@ -914,7 +1021,10 @@ async function clearWorkflowPool(env: CoordinatorEnv): Promise<number> {
914
1021
  );
915
1022
  await Promise.all(
916
1023
  entries.map(async (entry) => {
917
- const instance = await env.PLAY_WORKFLOW.get(entry.id);
1024
+ const instance = await getWorkflowPoolInstance(env, entry.id);
1025
+ if (!instance) {
1026
+ return;
1027
+ }
918
1028
  try {
919
1029
  await instance.terminate().catch(() => undefined);
920
1030
  } finally {
@@ -929,6 +1039,27 @@ function workflowStatusName(status: InstanceStatus | null): string {
929
1039
  return typeof status?.status === 'string' ? status.status : 'unknown';
930
1040
  }
931
1041
 
1042
+ function isWorkflowInstanceNotFoundError(error: unknown): boolean {
1043
+ const message = error instanceof Error ? error.message : String(error);
1044
+ return /not[ _]found|not_found|does not exist|no such instance|404/i.test(
1045
+ message,
1046
+ );
1047
+ }
1048
+
1049
+ async function getWorkflowPoolInstance(
1050
+ env: CoordinatorEnv,
1051
+ instanceId: string,
1052
+ ): Promise<WorkflowInstance | null> {
1053
+ try {
1054
+ return await env.PLAY_WORKFLOW.get(instanceId);
1055
+ } catch (error) {
1056
+ if (isWorkflowInstanceNotFoundError(error)) {
1057
+ return null;
1058
+ }
1059
+ throw error;
1060
+ }
1061
+ }
1062
+
932
1063
  function workflowPoolStatusIsReady(statusName: string): boolean {
933
1064
  // This is only a liveness guard. Readiness itself comes from the pooled
934
1065
  // Workflow calling /pool-ready after waitForEvent("play_start") has been
@@ -1011,7 +1142,11 @@ async function refillWorkflowPoolOnce(
1011
1142
  const promotedIds: string[] = [];
1012
1143
  const removedIds: string[] = [];
1013
1144
  for (const entry of warmingEntries) {
1014
- const instance = await env.PLAY_WORKFLOW.get(entry.id);
1145
+ const instance = await getWorkflowPoolInstance(env, entry.id);
1146
+ if (!instance) {
1147
+ removedIds.push(entry.id);
1148
+ continue;
1149
+ }
1015
1150
  try {
1016
1151
  if (entry.state === 'ready' && entry.readyAt !== null) {
1017
1152
  promotedIds.push(entry.id);
@@ -1238,7 +1373,21 @@ async function submitViaPooledWorkflow(input: {
1238
1373
  return null;
1239
1374
  }
1240
1375
 
1241
- const instance = await input.env.PLAY_WORKFLOW.get(pooledInstanceId);
1376
+ const instance = await getWorkflowPoolInstance(input.env, pooledInstanceId);
1377
+ if (!instance) {
1378
+ await blockWorkflowPoolRun({
1379
+ env: input.env,
1380
+ runId: input.params.runId,
1381
+ instanceId: pooledInstanceId,
1382
+ }).catch(() => undefined);
1383
+ input.recordSubmitTiming({
1384
+ phase: 'coordinator.workflow_pool_ready_check',
1385
+ ms: Date.now() - leaseStartedAt,
1386
+ graphHash: input.params.graphHash ?? null,
1387
+ extra: { instanceId: pooledInstanceId, status: 'missing' },
1388
+ });
1389
+ return null;
1390
+ }
1242
1391
  const readyCheckStartedAt = Date.now();
1243
1392
  const status = await instance.status().catch(() => null);
1244
1393
  const statusName = workflowStatusName(status);
@@ -1249,6 +1398,11 @@ async function submitViaPooledWorkflow(input: {
1249
1398
  extra: { instanceId: pooledInstanceId, status: statusName },
1250
1399
  });
1251
1400
  if (!workflowPoolStatusIsReady(statusName)) {
1401
+ await blockWorkflowPoolRun({
1402
+ env: input.env,
1403
+ runId: input.params.runId,
1404
+ instanceId: pooledInstanceId,
1405
+ }).catch(() => undefined);
1252
1406
  await instance.terminate().catch(() => undefined);
1253
1407
  disposeRpcStub(instance);
1254
1408
  return null;
@@ -1260,6 +1414,11 @@ async function submitViaPooledWorkflow(input: {
1260
1414
  payload: buildDispatcherEnvelope(input.params),
1261
1415
  });
1262
1416
  } catch (error) {
1417
+ await blockWorkflowPoolRun({
1418
+ env: input.env,
1419
+ runId: input.params.runId,
1420
+ instanceId: pooledInstanceId,
1421
+ }).catch(() => undefined);
1263
1422
  disposeRpcStub(instance);
1264
1423
  console.warn('[coordinator.workflow_pool] sendEvent failed; falling back', {
1265
1424
  runId: input.params.runId,
@@ -1274,7 +1433,63 @@ async function submitViaPooledWorkflow(input: {
1274
1433
  graphHash: input.params.graphHash ?? null,
1275
1434
  extra: { instanceId: pooledInstanceId },
1276
1435
  });
1277
- return instance;
1436
+ const ack = await waitForWorkflowPoolStartAck({
1437
+ env: input.env,
1438
+ runId: input.params.runId,
1439
+ instanceId: pooledInstanceId,
1440
+ timeoutMs: WORKFLOW_POOL_START_ACK_TIMEOUT_MS,
1441
+ });
1442
+ if (ack.acknowledged) {
1443
+ input.recordSubmitTiming({
1444
+ phase: 'coordinator.workflow_pool_start_ack',
1445
+ ms: ack.ms,
1446
+ graphHash: input.params.graphHash ?? null,
1447
+ extra: {
1448
+ acknowledged: true,
1449
+ instanceId: pooledInstanceId,
1450
+ polls: ack.polls,
1451
+ startedAt: ack.startedAt,
1452
+ },
1453
+ });
1454
+ return instance;
1455
+ }
1456
+
1457
+ const blockStartedAt = Date.now();
1458
+ const block = await blockWorkflowPoolRun({
1459
+ env: input.env,
1460
+ runId: input.params.runId,
1461
+ instanceId: pooledInstanceId,
1462
+ }).catch(() => ({ blocked: false, started: false }));
1463
+ input.recordSubmitTiming({
1464
+ phase: 'coordinator.workflow_pool_start_ack',
1465
+ ms: ack.ms,
1466
+ graphHash: input.params.graphHash ?? null,
1467
+ extra: {
1468
+ acknowledged: block.started,
1469
+ instanceId: pooledInstanceId,
1470
+ polls: ack.polls,
1471
+ startedAt: ack.startedAt,
1472
+ mappedInstanceId: ack.mappedInstanceId,
1473
+ blocked: block.blocked,
1474
+ blockMs: Date.now() - blockStartedAt,
1475
+ },
1476
+ });
1477
+ if (block.started) {
1478
+ return instance;
1479
+ }
1480
+ await instance.terminate().catch(() => undefined);
1481
+ disposeRpcStub(instance);
1482
+ input.recordSubmitTiming({
1483
+ phase: 'coordinator.workflow_pool_fallback',
1484
+ ms: Date.now() - sendStartedAt,
1485
+ graphHash: input.params.graphHash ?? null,
1486
+ extra: {
1487
+ reason: 'start_ack_timeout',
1488
+ instanceId: pooledInstanceId,
1489
+ ackTimeoutMs: WORKFLOW_POOL_START_ACK_TIMEOUT_MS,
1490
+ },
1491
+ });
1492
+ return null;
1278
1493
  }
1279
1494
 
1280
1495
  function readWorkflowPayload(event: unknown): Record<string, unknown> | null {
@@ -1311,16 +1526,21 @@ async function markWorkflowRuntimeFailure(input: {
1311
1526
  });
1312
1527
  const bypass = input.env.VERCEL_PROTECTION_BYPASS_TOKEN?.trim();
1313
1528
  if (bypass) headers.set('x-vercel-protection-bypass', bypass);
1314
- const body = JSON.stringify(
1315
- runtimeRunActions.updateStatus({
1316
- playId: runId,
1317
- status: 'failed',
1318
- error: `DynamicWorkflow runner failed: ${errorName}: ${errorMessage}${
1319
- errorStack ? `\n${errorStack}` : ''
1320
- }`,
1321
- runtimeBackend: 'cf_workflows_dynamic_worker',
1322
- }),
1323
- );
1529
+ const body = JSON.stringify({
1530
+ action: 'append_run_events',
1531
+ playId: runId,
1532
+ events: [
1533
+ {
1534
+ type: 'run.failed',
1535
+ runId,
1536
+ source: 'coordinator',
1537
+ occurredAt: Date.now(),
1538
+ error: `DynamicWorkflow runner failed: ${errorName}: ${errorMessage}${
1539
+ errorStack ? `\n${errorStack}` : ''
1540
+ }`,
1541
+ } satisfies PlayRunLedgerEvent,
1542
+ ],
1543
+ });
1324
1544
  const url = `${baseUrl.replace(/\/$/, '')}/api/v2/plays/internal/runtime`;
1325
1545
  const backoffMs = [200, 500, 1500];
1326
1546
  let lastError: unknown = null;
@@ -1621,6 +1841,15 @@ function buildChildWorkflowParams(input: {
1621
1841
  function runRequestFromPlayWorkflowParams(
1622
1842
  params: PlayWorkflowParams,
1623
1843
  ): Record<string, unknown> {
1844
+ const inputFileName = String(
1845
+ params.inputFile?.name ??
1846
+ params.inputFile?.fileName ??
1847
+ params.inputFile?.logicalPath ??
1848
+ params.inputFile?.path ??
1849
+ '',
1850
+ );
1851
+ const inputStorageKey =
1852
+ params.inputFile?.r2Key ?? params.inputFile?.storageKey ?? null;
1624
1853
  return {
1625
1854
  runId: params.runId,
1626
1855
  callbackUrl: params.baseUrl,
@@ -1632,12 +1861,22 @@ function runRequestFromPlayWorkflowParams(
1632
1861
  userEmail: params.userEmail,
1633
1862
  runtimeInput: params.input,
1634
1863
  inlineCsv: params.inlineCsv ?? null,
1635
- inputR2Keys:
1636
- params.inputFile?.r2Key &&
1637
- (params.inputFile.name || params.inputFile.path)
1864
+ inputFiles:
1865
+ inputStorageKey && inputFileName
1638
1866
  ? {
1639
- [String(params.inputFile.name ?? params.inputFile.path)]:
1640
- params.inputFile.r2Key,
1867
+ [inputFileName]: {
1868
+ logicalPath:
1869
+ params.inputFile?.logicalPath ??
1870
+ params.inputFile?.path ??
1871
+ inputFileName,
1872
+ fileName: params.inputFile?.fileName ?? inputFileName,
1873
+ storageKey: inputStorageKey,
1874
+ contentType: params.inputFile?.contentType ?? null,
1875
+ bytes:
1876
+ typeof params.inputFile?.bytes === 'number'
1877
+ ? params.inputFile.bytes
1878
+ : null,
1879
+ },
1641
1880
  }
1642
1881
  : null,
1643
1882
  packagedFiles: params.packagedFiles ?? null,
@@ -1996,13 +2235,6 @@ export class RuntimeApi extends WorkerEntrypoint<CoordinatorEnv, undefined> {
1996
2235
  ? this.env.DEEPLINE_API_BASE_URL.trim()
1997
2236
  : 'https://code.deepline.com';
1998
2237
  const target = new URL(incoming.pathname + incoming.search, apiBaseUrl);
1999
- const runtimeStatusBody =
2000
- incoming.pathname === '/api/v2/plays/internal/runtime'
2001
- ? await request
2002
- .clone()
2003
- .json()
2004
- .catch(() => null)
2005
- : null;
2006
2238
  const forwarded = new Request(target.toString(), request);
2007
2239
  const bypassToken = this.env.VERCEL_PROTECTION_BYPASS_TOKEN;
2008
2240
  if (typeof bypassToken === 'string' && bypassToken) {
@@ -2018,41 +2250,9 @@ export class RuntimeApi extends WorkerEntrypoint<CoordinatorEnv, undefined> {
2018
2250
  `[RUNTIME_API] ${incoming.pathname} failed: status=${res.status} ` +
2019
2251
  `target=${target.toString()} body=${body.slice(0, 500)}`,
2020
2252
  );
2021
- } else {
2022
- await this.recordRuntimeStatusEvent(runtimeStatusBody).catch(() => null);
2023
2253
  }
2024
2254
  return res;
2025
2255
  }
2026
-
2027
- private async recordRuntimeStatusEvent(body: unknown): Promise<void> {
2028
- if (!isRecord(body) || body.action !== 'update_run_status') {
2029
- return;
2030
- }
2031
- const runId = typeof body.playId === 'string' ? body.playId : '';
2032
- const status = typeof body.status === 'string' ? body.status : '';
2033
- if (!runId || !status) {
2034
- return;
2035
- }
2036
- await appendCoordinatorRunEvent(this.env, {
2037
- runId,
2038
- type: 'progress',
2039
- status,
2040
- ts: Date.now(),
2041
- logs: sanitizeLiveLogLines(body.liveLogs) ?? undefined,
2042
- activeNodeId:
2043
- typeof body.activeNodeId === 'string' ? body.activeNodeId : null,
2044
- activeArtifactTableNamespace:
2045
- typeof body.activeArtifactTableNamespace === 'string'
2046
- ? body.activeArtifactTableNamespace
2047
- : null,
2048
- updatedAt:
2049
- typeof body.lastCheckpointAt === 'number'
2050
- ? body.lastCheckpointAt
2051
- : null,
2052
- liveNodeProgress:
2053
- body.liveNodeProgress !== undefined ? body.liveNodeProgress : undefined,
2054
- });
2055
- }
2056
2256
  }
2057
2257
 
2058
2258
  export class CoordinatorControl extends WorkerEntrypoint<
@@ -2188,9 +2388,35 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
2188
2388
  dispatchedEvent = {
2189
2389
  payload: startEvent.payload,
2190
2390
  timestamp: startEvent.timestamp,
2191
- instanceId: workflowEvent.instanceId,
2391
+ instanceId: workflowEvent.instanceId ?? pooledPayload.poolId,
2192
2392
  };
2193
2393
  const dispatchedTrace = readWorkflowTraceContext(dispatchedEvent);
2394
+ const mapped = await mapRunToWorkflowInstance({
2395
+ env: this.env,
2396
+ runId: dispatchedTrace.runId,
2397
+ instanceId: pooledPayload.poolId,
2398
+ started: true,
2399
+ }).catch((error) => {
2400
+ console.warn('[coordinator.workflow_pool] start ack failed', {
2401
+ poolId: pooledPayload.poolId,
2402
+ runId: dispatchedTrace.runId,
2403
+ message: error instanceof Error ? error.message : String(error),
2404
+ });
2405
+ return false;
2406
+ });
2407
+ if (!mapped) {
2408
+ trace({
2409
+ runId: dispatchedTrace.runId,
2410
+ phase: 'coordinator.workflow_pool_start_blocked',
2411
+ ms: 0,
2412
+ graphHash: dispatchedTrace.graphHash,
2413
+ extra: {
2414
+ instanceId: pooledPayload.poolId,
2415
+ eventType: startEvent.type,
2416
+ },
2417
+ });
2418
+ return { ok: false, blocked: true, runId: dispatchedTrace.runId };
2419
+ }
2194
2420
  const eventDeliveryMs = Math.max(
2195
2421
  0,
2196
2422
  Date.now() - startEvent.timestamp.getTime(),
@@ -2474,7 +2700,15 @@ const coordinatorEntrypoint = {
2474
2700
  const entries = await listWorkflowPoolEntries(env);
2475
2701
  const detailed = [];
2476
2702
  for (const entry of entries) {
2477
- const instance = await env.PLAY_WORKFLOW.get(entry.id);
2703
+ const instance = await getWorkflowPoolInstance(env, entry.id);
2704
+ if (!instance) {
2705
+ detailed.push({
2706
+ ...entry,
2707
+ status: 'missing',
2708
+ mappedStatus: 'failed',
2709
+ });
2710
+ continue;
2711
+ }
2478
2712
  try {
2479
2713
  const status = await instance.status().catch(() => null);
2480
2714
  detailed.push({
@@ -2538,6 +2772,14 @@ const coordinatorEntrypoint = {
2538
2772
  async tail(events: unknown[], env: CoordinatorEnv): Promise<void> {
2539
2773
  await flushTailRunLogs(events, env);
2540
2774
  },
2775
+ async scheduled(
2776
+ _controller: unknown,
2777
+ env: CoordinatorEnv,
2778
+ ctx?: ExecutionContext,
2779
+ ): Promise<void> {
2780
+ if (!workflowPoolEnabled()) return;
2781
+ ctx?.waitUntil(refillWorkflowPool(env).catch(() => undefined));
2782
+ },
2541
2783
  };
2542
2784
 
2543
2785
  export default coordinatorEntrypoint;
@@ -2700,36 +2942,100 @@ async function handleWorkflowRoute(input: {
2700
2942
  'Start apps/play-harness-worker before the coordinator or fix wrangler.toml services.',
2701
2943
  );
2702
2944
  }
2945
+ const preloadedDbSessions = Array.isArray(params.preloadedDbSessions)
2946
+ ? params.preloadedDbSessions
2947
+ : [];
2948
+ if (preloadedDbSessions.length > 0 && params.executorToken) {
2949
+ const prewarmStartedAt = Date.now();
2950
+ recordSubmitTiming({
2951
+ phase: 'coordinator.harness_prewarm_postgres_start',
2952
+ ms: 0,
2953
+ graphHash: params.graphHash ?? null,
2954
+ extra: { sessions: preloadedDbSessions.length },
2955
+ });
2956
+ const prewarmPromise = env.HARNESS.prewarmPostgresSessions({
2957
+ executorToken: params.executorToken,
2958
+ sessions: preloadedDbSessions,
2959
+ })
2960
+ .then((result) => {
2961
+ recordSubmitTiming({
2962
+ phase: 'coordinator.harness_prewarm_postgres',
2963
+ ms: Date.now() - prewarmStartedAt,
2964
+ graphHash: params.graphHash ?? null,
2965
+ extra: {
2966
+ status: 'ok',
2967
+ sessions: result.sessions,
2968
+ },
2969
+ });
2970
+ })
2971
+ .catch((error: unknown) => {
2972
+ recordSubmitTiming({
2973
+ phase: 'coordinator.harness_prewarm_postgres',
2974
+ ms: Date.now() - prewarmStartedAt,
2975
+ graphHash: params.graphHash ?? null,
2976
+ extra: {
2977
+ status: 'failed',
2978
+ sessions: preloadedDbSessions.length,
2979
+ error: error instanceof Error ? error.message : String(error),
2980
+ },
2981
+ });
2982
+ });
2983
+ input.ctx?.waitUntil(prewarmPromise);
2984
+ }
2703
2985
  let instance: WorkflowInstance | null = null;
2704
2986
  try {
2705
- const dispatchStartedAt = Date.now();
2987
+ const statusEventStartedAt = Date.now();
2988
+ await appendCoordinatorRunEvent(env, {
2989
+ runId: submittedRunId,
2990
+ type: 'status',
2991
+ status: 'running',
2992
+ ts: Date.now(),
2993
+ });
2706
2994
  recordSubmitTiming({
2707
- phase: 'coordinator.workflow_pool_attempt',
2708
- ms: 0,
2995
+ phase: 'coordinator.submit_status_event',
2996
+ ms: Date.now() - statusEventStartedAt,
2709
2997
  graphHash: params.graphHash ?? null,
2710
- extra: {
2711
- usedPool: false,
2712
- disabled: true,
2713
- reason: WORKFLOW_POOL_DISABLED_REASON,
2714
- },
2715
2998
  });
2716
- const createStartedAt = Date.now();
2717
- instance = await createDynamicWorkflowInstance({
2999
+ const dispatchStartedAt = Date.now();
3000
+ const poolAttemptStartedAt = Date.now();
3001
+ instance = await submitViaPooledWorkflow({
2718
3002
  env,
2719
- id: defaultInstanceId,
2720
3003
  params,
3004
+ recordSubmitTiming,
2721
3005
  });
2722
3006
  recordSubmitTiming({
2723
- phase: 'coordinator.workflow_create',
2724
- ms: Date.now() - createStartedAt,
3007
+ phase: 'coordinator.workflow_pool_attempt',
3008
+ ms: Date.now() - poolAttemptStartedAt,
2725
3009
  graphHash: params.graphHash ?? null,
2726
- extra: { instanceId: instance.id },
3010
+ extra: {
3011
+ usedPool: Boolean(instance),
3012
+ enabled: workflowPoolEnabled(),
3013
+ },
2727
3014
  });
3015
+ if (!instance) {
3016
+ const createStartedAt = Date.now();
3017
+ instance = await createDynamicWorkflowInstance({
3018
+ env,
3019
+ id: defaultInstanceId,
3020
+ params,
3021
+ });
3022
+ recordSubmitTiming({
3023
+ phase: 'coordinator.workflow_create',
3024
+ ms: Date.now() - createStartedAt,
3025
+ graphHash: params.graphHash ?? null,
3026
+ extra: { instanceId: instance.id },
3027
+ });
3028
+ }
2728
3029
  recordSubmitTiming({
2729
3030
  phase: 'coordinator.dispatch_workflow',
2730
3031
  ms: Date.now() - dispatchStartedAt,
2731
3032
  graphHash: params.graphHash ?? null,
2732
- extra: { startMode: 'direct_workflow_create' },
3033
+ extra: {
3034
+ startMode:
3035
+ instance.id === defaultInstanceId
3036
+ ? 'direct_workflow_create'
3037
+ : 'pooled_workflow_start_event',
3038
+ },
2733
3039
  });
2734
3040
  const initialWaitMsRaw = Number(
2735
3041
  new URL(request.url).searchParams.get('initialWaitMs') ?? '0',
@@ -2763,6 +3069,9 @@ async function handleWorkflowRoute(input: {
2763
3069
  ms: totalMs,
2764
3070
  graphHash: params.graphHash ?? null,
2765
3071
  });
3072
+ if (workflowPoolEnabled() && instance.id === defaultInstanceId) {
3073
+ input.ctx?.waitUntil(refillWorkflowPool(env).catch(() => undefined));
3074
+ }
2766
3075
  return Response.json({
2767
3076
  runId,
2768
3077
  status: 'submitted',
@@ -3018,11 +3327,19 @@ async function handleWorkflowRoute(input: {
3018
3327
  afterSeq,
3019
3328
  timeoutMs: waitMs,
3020
3329
  }).catch(() => null);
3330
+ const rawEvents = eventResult?.events ?? [];
3331
+ const terminalEventIndex = rawEvents.findIndex(
3332
+ (event) => event.type === 'terminal',
3333
+ );
3334
+ const events =
3335
+ terminalEventIndex >= 0
3336
+ ? rawEvents.slice(0, terminalEventIndex + 1)
3337
+ : rawEvents;
3021
3338
  const coordinatorTrace =
3022
- includeTrace && eventResult?.events.length
3339
+ includeTrace && events.length
3023
3340
  ? await listCoordinatorPerfTrace(env, runId).catch(() => [])
3024
3341
  : [];
3025
- const terminalEvent = eventResult?.events.find(
3342
+ const terminalEvent = events.find(
3026
3343
  (event): event is Extract<CoordinatorRunEvent, { type: 'terminal' }> =>
3027
3344
  event.type === 'terminal',
3028
3345
  );
@@ -3038,7 +3355,7 @@ async function handleWorkflowRoute(input: {
3038
3355
  completedAt: terminalEvent.ts,
3039
3356
  liveLogs: sanitizeLiveLogLines(terminalEvent.liveLogs),
3040
3357
  liveNodeProgress: terminalEvent.liveNodeProgress ?? null,
3041
- events: eventResult?.events ?? [],
3358
+ events,
3042
3359
  latestSeq: eventResult?.latestSeq ?? afterSeq,
3043
3360
  wait: null,
3044
3361
  coordinatorObserve: {
@@ -3054,7 +3371,7 @@ async function handleWorkflowRoute(input: {
3054
3371
  return Response.json({
3055
3372
  runId,
3056
3373
  status: 'running',
3057
- events: eventResult?.events ?? [],
3374
+ events,
3058
3375
  latestSeq: eventResult?.latestSeq ?? afterSeq,
3059
3376
  wait: null,
3060
3377
  coordinatorObserve: {
@@ -3252,7 +3569,7 @@ function stableHash(value: string): string {
3252
3569
  return (hash >>> 0).toString(36);
3253
3570
  }
3254
3571
 
3255
- const DYNAMIC_PLAY_WORKER_HARNESS_VERSION = 'h7-skip-high-volume-tool-traces';
3572
+ const DYNAMIC_PLAY_WORKER_HARNESS_VERSION = 'h16-coordinator-only-prewarm';
3256
3573
  const DYNAMIC_WORKER_BUNDLED_CODE_CACHE_MAX_ENTRIES = 64;
3257
3574
  const dynamicWorkerBundledCodeCache = new Map<string, string>();
3258
3575
 
@@ -3548,6 +3865,14 @@ function normalizePackagedFiles(
3548
3865
  .map((entry) => ({
3549
3866
  playPath: String(entry.playPath ?? '').replace(/^\.\//, ''),
3550
3867
  storageKey: String(entry.storageKey ?? ''),
3868
+ contentType:
3869
+ typeof entry.contentType === 'string' ? entry.contentType : undefined,
3870
+ bytes:
3871
+ typeof entry.bytes === 'number' &&
3872
+ Number.isSafeInteger(entry.bytes) &&
3873
+ entry.bytes >= 0
3874
+ ? entry.bytes
3875
+ : undefined,
3551
3876
  inlineText:
3552
3877
  typeof entry.inlineText === 'string' ? entry.inlineText : undefined,
3553
3878
  }))
@@ -4062,8 +4387,13 @@ function mapWorkflowResult(
4062
4387
  runId: string,
4063
4388
  status: InstanceStatus,
4064
4389
  ): Record<string, unknown> {
4065
- const error = readWorkflowError(status);
4066
- const mapped = resolveTerminalStatus(status, error);
4390
+ const rawError = readWorkflowError(status);
4391
+ const mapped = resolveTerminalStatus(status, rawError);
4392
+ const error =
4393
+ rawError ??
4394
+ (mapped === 'failed'
4395
+ ? `Cloudflare workflow reported ${String(status.status ?? 'failed')} without an error payload.`
4396
+ : null);
4067
4397
  const output =
4068
4398
  status.output && typeof status.output === 'object'
4069
4399
  ? (status.output as Record<string, unknown>)
@@ -4131,9 +4461,11 @@ function resolveTerminalStatus(
4131
4461
  function readWorkflowError(status: InstanceStatus): string | null {
4132
4462
  const error = status.error as unknown;
4133
4463
  if (!error) return null;
4134
- if (typeof error === 'string') return error;
4464
+ if (typeof error === 'string') return error.trim() || null;
4135
4465
  if (typeof error === 'object' && 'message' in error) {
4136
- return String((error as { message?: unknown }).message ?? '');
4466
+ const message = String((error as { message?: unknown }).message ?? '');
4467
+ return message.trim() || null;
4137
4468
  }
4138
- return String(error);
4469
+ const message = String(error);
4470
+ return message.trim() || null;
4139
4471
  }