deepline 0.1.26 → 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.
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +360 -41
- package/dist/repo/apps/play-runner-workers/src/entry.ts +101 -42
- package/dist/repo/sdk/src/plays/harness-stub.ts +2 -20
- package/dist/repo/sdk/src/version.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -266,7 +266,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// src/version.ts
|
|
269
|
-
var SDK_VERSION = "0.1.
|
|
269
|
+
var SDK_VERSION = "0.1.27";
|
|
270
270
|
var SDK_API_CONTRACT = "2026-05-runs-v2";
|
|
271
271
|
|
|
272
272
|
// ../shared_libs/play-runtime/coordinator-headers.ts
|
package/dist/cli/index.mjs
CHANGED
|
@@ -243,7 +243,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// src/version.ts
|
|
246
|
-
var SDK_VERSION = "0.1.
|
|
246
|
+
var SDK_VERSION = "0.1.27";
|
|
247
247
|
var SDK_API_CONTRACT = "2026-05-runs-v2";
|
|
248
248
|
|
|
249
249
|
// ../shared_libs/play-runtime/coordinator-headers.ts
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -232,7 +232,7 @@ interface CoordinatorEnv {
|
|
|
232
232
|
/**
|
|
233
233
|
* Service binding to the long-lived Play Harness Worker
|
|
234
234
|
* (apps/play-harness-worker). Provides typed RPC access to leaf-level
|
|
235
|
-
* helpers (
|
|
235
|
+
* helpers (runtime-API HTTP forwarder, Neon dataset IO, …) that we
|
|
236
236
|
* deliberately keep OUT of every per-graphHash play bundle.
|
|
237
237
|
*
|
|
238
238
|
* Optional: when missing (e.g. an older deploy that hasn't been wired
|
|
@@ -547,10 +547,10 @@ const WORKFLOW_POOL_READY_POLL_MS = 250;
|
|
|
547
547
|
const WORKFLOW_POOL_REFILL_ON_MISS_TIMEOUT_MS = 2_500;
|
|
548
548
|
const WORKFLOW_POOL_REFILL_ON_MISS_MIN_AVAILABLE = 4;
|
|
549
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;
|
|
550
552
|
const SUBMIT_INITIAL_STATE_MAX_WAIT_MS = 0;
|
|
551
553
|
const SUBMIT_INITIAL_STATE_POLL_MS = 50;
|
|
552
|
-
const WORKFLOW_POOL_DISABLED_REASON =
|
|
553
|
-
'Cloudflare Workflows start runs directly; waitForEvent is reserved for real durable external waits.';
|
|
554
554
|
function buildDynamicWorkflowMetadata(
|
|
555
555
|
params: PlayWorkflowParams,
|
|
556
556
|
): DynamicWorkflowMetadata {
|
|
@@ -623,7 +623,7 @@ function readWorkflowTraceContext(event: unknown): {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
function workflowPoolEnabled(): boolean {
|
|
626
|
-
return
|
|
626
|
+
return WORKFLOW_POOL_TARGET_SIZE > 0;
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
function workflowPoolTargetSize(): number {
|
|
@@ -889,8 +889,33 @@ async function mapRunToWorkflowInstance(input: {
|
|
|
889
889
|
env: CoordinatorEnv;
|
|
890
890
|
runId: string;
|
|
891
891
|
instanceId: string;
|
|
892
|
-
|
|
893
|
-
|
|
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', {
|
|
894
919
|
method: 'POST',
|
|
895
920
|
body: JSON.stringify({
|
|
896
921
|
runId: input.runId,
|
|
@@ -898,6 +923,82 @@ async function mapRunToWorkflowInstance(input: {
|
|
|
898
923
|
version: WORKFLOW_POOL_PROTOCOL_VERSION,
|
|
899
924
|
}),
|
|
900
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
|
+
};
|
|
901
1002
|
}
|
|
902
1003
|
|
|
903
1004
|
async function resolveWorkflowInstanceIdForRun(
|
|
@@ -907,15 +1008,8 @@ async function resolveWorkflowInstanceIdForRun(
|
|
|
907
1008
|
if (!workflowPoolEnabled()) {
|
|
908
1009
|
return workflowInstanceId(runId);
|
|
909
1010
|
}
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
`/pool-resolve-run?runId=${encodeURIComponent(runId)}&version=${encodeURIComponent(
|
|
913
|
-
WORKFLOW_POOL_PROTOCOL_VERSION,
|
|
914
|
-
)}`,
|
|
915
|
-
).catch(() => ({ instanceId: null }));
|
|
916
|
-
return typeof body.instanceId === 'string' && body.instanceId
|
|
917
|
-
? body.instanceId
|
|
918
|
-
: workflowInstanceId(runId);
|
|
1011
|
+
const mapping = await readWorkflowPoolRunMapping({ env, runId });
|
|
1012
|
+
return mapping.instanceId ? mapping.instanceId : workflowInstanceId(runId);
|
|
919
1013
|
}
|
|
920
1014
|
|
|
921
1015
|
async function clearWorkflowPool(env: CoordinatorEnv): Promise<number> {
|
|
@@ -927,7 +1021,10 @@ async function clearWorkflowPool(env: CoordinatorEnv): Promise<number> {
|
|
|
927
1021
|
);
|
|
928
1022
|
await Promise.all(
|
|
929
1023
|
entries.map(async (entry) => {
|
|
930
|
-
const instance = await env
|
|
1024
|
+
const instance = await getWorkflowPoolInstance(env, entry.id);
|
|
1025
|
+
if (!instance) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
931
1028
|
try {
|
|
932
1029
|
await instance.terminate().catch(() => undefined);
|
|
933
1030
|
} finally {
|
|
@@ -942,6 +1039,27 @@ function workflowStatusName(status: InstanceStatus | null): string {
|
|
|
942
1039
|
return typeof status?.status === 'string' ? status.status : 'unknown';
|
|
943
1040
|
}
|
|
944
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
|
+
|
|
945
1063
|
function workflowPoolStatusIsReady(statusName: string): boolean {
|
|
946
1064
|
// This is only a liveness guard. Readiness itself comes from the pooled
|
|
947
1065
|
// Workflow calling /pool-ready after waitForEvent("play_start") has been
|
|
@@ -1024,7 +1142,11 @@ async function refillWorkflowPoolOnce(
|
|
|
1024
1142
|
const promotedIds: string[] = [];
|
|
1025
1143
|
const removedIds: string[] = [];
|
|
1026
1144
|
for (const entry of warmingEntries) {
|
|
1027
|
-
const instance = await env
|
|
1145
|
+
const instance = await getWorkflowPoolInstance(env, entry.id);
|
|
1146
|
+
if (!instance) {
|
|
1147
|
+
removedIds.push(entry.id);
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1028
1150
|
try {
|
|
1029
1151
|
if (entry.state === 'ready' && entry.readyAt !== null) {
|
|
1030
1152
|
promotedIds.push(entry.id);
|
|
@@ -1251,7 +1373,21 @@ async function submitViaPooledWorkflow(input: {
|
|
|
1251
1373
|
return null;
|
|
1252
1374
|
}
|
|
1253
1375
|
|
|
1254
|
-
const instance = await input.env
|
|
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
|
+
}
|
|
1255
1391
|
const readyCheckStartedAt = Date.now();
|
|
1256
1392
|
const status = await instance.status().catch(() => null);
|
|
1257
1393
|
const statusName = workflowStatusName(status);
|
|
@@ -1262,6 +1398,11 @@ async function submitViaPooledWorkflow(input: {
|
|
|
1262
1398
|
extra: { instanceId: pooledInstanceId, status: statusName },
|
|
1263
1399
|
});
|
|
1264
1400
|
if (!workflowPoolStatusIsReady(statusName)) {
|
|
1401
|
+
await blockWorkflowPoolRun({
|
|
1402
|
+
env: input.env,
|
|
1403
|
+
runId: input.params.runId,
|
|
1404
|
+
instanceId: pooledInstanceId,
|
|
1405
|
+
}).catch(() => undefined);
|
|
1265
1406
|
await instance.terminate().catch(() => undefined);
|
|
1266
1407
|
disposeRpcStub(instance);
|
|
1267
1408
|
return null;
|
|
@@ -1273,6 +1414,11 @@ async function submitViaPooledWorkflow(input: {
|
|
|
1273
1414
|
payload: buildDispatcherEnvelope(input.params),
|
|
1274
1415
|
});
|
|
1275
1416
|
} catch (error) {
|
|
1417
|
+
await blockWorkflowPoolRun({
|
|
1418
|
+
env: input.env,
|
|
1419
|
+
runId: input.params.runId,
|
|
1420
|
+
instanceId: pooledInstanceId,
|
|
1421
|
+
}).catch(() => undefined);
|
|
1276
1422
|
disposeRpcStub(instance);
|
|
1277
1423
|
console.warn('[coordinator.workflow_pool] sendEvent failed; falling back', {
|
|
1278
1424
|
runId: input.params.runId,
|
|
@@ -1287,7 +1433,63 @@ async function submitViaPooledWorkflow(input: {
|
|
|
1287
1433
|
graphHash: input.params.graphHash ?? null,
|
|
1288
1434
|
extra: { instanceId: pooledInstanceId },
|
|
1289
1435
|
});
|
|
1290
|
-
|
|
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;
|
|
1291
1493
|
}
|
|
1292
1494
|
|
|
1293
1495
|
function readWorkflowPayload(event: unknown): Record<string, unknown> | null {
|
|
@@ -2186,9 +2388,35 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
|
|
|
2186
2388
|
dispatchedEvent = {
|
|
2187
2389
|
payload: startEvent.payload,
|
|
2188
2390
|
timestamp: startEvent.timestamp,
|
|
2189
|
-
instanceId: workflowEvent.instanceId,
|
|
2391
|
+
instanceId: workflowEvent.instanceId ?? pooledPayload.poolId,
|
|
2190
2392
|
};
|
|
2191
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
|
+
}
|
|
2192
2420
|
const eventDeliveryMs = Math.max(
|
|
2193
2421
|
0,
|
|
2194
2422
|
Date.now() - startEvent.timestamp.getTime(),
|
|
@@ -2472,7 +2700,15 @@ const coordinatorEntrypoint = {
|
|
|
2472
2700
|
const entries = await listWorkflowPoolEntries(env);
|
|
2473
2701
|
const detailed = [];
|
|
2474
2702
|
for (const entry of entries) {
|
|
2475
|
-
const instance = await env
|
|
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
|
+
}
|
|
2476
2712
|
try {
|
|
2477
2713
|
const status = await instance.status().catch(() => null);
|
|
2478
2714
|
detailed.push({
|
|
@@ -2536,6 +2772,14 @@ const coordinatorEntrypoint = {
|
|
|
2536
2772
|
async tail(events: unknown[], env: CoordinatorEnv): Promise<void> {
|
|
2537
2773
|
await flushTailRunLogs(events, env);
|
|
2538
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
|
+
},
|
|
2539
2783
|
};
|
|
2540
2784
|
|
|
2541
2785
|
export default coordinatorEntrypoint;
|
|
@@ -2698,36 +2942,100 @@ async function handleWorkflowRoute(input: {
|
|
|
2698
2942
|
'Start apps/play-harness-worker before the coordinator or fix wrangler.toml services.',
|
|
2699
2943
|
);
|
|
2700
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
|
+
}
|
|
2701
2985
|
let instance: WorkflowInstance | null = null;
|
|
2702
2986
|
try {
|
|
2703
|
-
const
|
|
2987
|
+
const statusEventStartedAt = Date.now();
|
|
2988
|
+
await appendCoordinatorRunEvent(env, {
|
|
2989
|
+
runId: submittedRunId,
|
|
2990
|
+
type: 'status',
|
|
2991
|
+
status: 'running',
|
|
2992
|
+
ts: Date.now(),
|
|
2993
|
+
});
|
|
2704
2994
|
recordSubmitTiming({
|
|
2705
|
-
phase: 'coordinator.
|
|
2706
|
-
ms:
|
|
2995
|
+
phase: 'coordinator.submit_status_event',
|
|
2996
|
+
ms: Date.now() - statusEventStartedAt,
|
|
2707
2997
|
graphHash: params.graphHash ?? null,
|
|
2708
|
-
extra: {
|
|
2709
|
-
usedPool: false,
|
|
2710
|
-
disabled: true,
|
|
2711
|
-
reason: WORKFLOW_POOL_DISABLED_REASON,
|
|
2712
|
-
},
|
|
2713
2998
|
});
|
|
2714
|
-
const
|
|
2715
|
-
|
|
2999
|
+
const dispatchStartedAt = Date.now();
|
|
3000
|
+
const poolAttemptStartedAt = Date.now();
|
|
3001
|
+
instance = await submitViaPooledWorkflow({
|
|
2716
3002
|
env,
|
|
2717
|
-
id: defaultInstanceId,
|
|
2718
3003
|
params,
|
|
3004
|
+
recordSubmitTiming,
|
|
2719
3005
|
});
|
|
2720
3006
|
recordSubmitTiming({
|
|
2721
|
-
phase: 'coordinator.
|
|
2722
|
-
ms: Date.now() -
|
|
3007
|
+
phase: 'coordinator.workflow_pool_attempt',
|
|
3008
|
+
ms: Date.now() - poolAttemptStartedAt,
|
|
2723
3009
|
graphHash: params.graphHash ?? null,
|
|
2724
|
-
extra: {
|
|
3010
|
+
extra: {
|
|
3011
|
+
usedPool: Boolean(instance),
|
|
3012
|
+
enabled: workflowPoolEnabled(),
|
|
3013
|
+
},
|
|
2725
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
|
+
}
|
|
2726
3029
|
recordSubmitTiming({
|
|
2727
3030
|
phase: 'coordinator.dispatch_workflow',
|
|
2728
3031
|
ms: Date.now() - dispatchStartedAt,
|
|
2729
3032
|
graphHash: params.graphHash ?? null,
|
|
2730
|
-
extra: {
|
|
3033
|
+
extra: {
|
|
3034
|
+
startMode:
|
|
3035
|
+
instance.id === defaultInstanceId
|
|
3036
|
+
? 'direct_workflow_create'
|
|
3037
|
+
: 'pooled_workflow_start_event',
|
|
3038
|
+
},
|
|
2731
3039
|
});
|
|
2732
3040
|
const initialWaitMsRaw = Number(
|
|
2733
3041
|
new URL(request.url).searchParams.get('initialWaitMs') ?? '0',
|
|
@@ -2761,6 +3069,9 @@ async function handleWorkflowRoute(input: {
|
|
|
2761
3069
|
ms: totalMs,
|
|
2762
3070
|
graphHash: params.graphHash ?? null,
|
|
2763
3071
|
});
|
|
3072
|
+
if (workflowPoolEnabled() && instance.id === defaultInstanceId) {
|
|
3073
|
+
input.ctx?.waitUntil(refillWorkflowPool(env).catch(() => undefined));
|
|
3074
|
+
}
|
|
2764
3075
|
return Response.json({
|
|
2765
3076
|
runId,
|
|
2766
3077
|
status: 'submitted',
|
|
@@ -3016,11 +3327,19 @@ async function handleWorkflowRoute(input: {
|
|
|
3016
3327
|
afterSeq,
|
|
3017
3328
|
timeoutMs: waitMs,
|
|
3018
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;
|
|
3019
3338
|
const coordinatorTrace =
|
|
3020
|
-
includeTrace &&
|
|
3339
|
+
includeTrace && events.length
|
|
3021
3340
|
? await listCoordinatorPerfTrace(env, runId).catch(() => [])
|
|
3022
3341
|
: [];
|
|
3023
|
-
const terminalEvent =
|
|
3342
|
+
const terminalEvent = events.find(
|
|
3024
3343
|
(event): event is Extract<CoordinatorRunEvent, { type: 'terminal' }> =>
|
|
3025
3344
|
event.type === 'terminal',
|
|
3026
3345
|
);
|
|
@@ -3036,7 +3355,7 @@ async function handleWorkflowRoute(input: {
|
|
|
3036
3355
|
completedAt: terminalEvent.ts,
|
|
3037
3356
|
liveLogs: sanitizeLiveLogLines(terminalEvent.liveLogs),
|
|
3038
3357
|
liveNodeProgress: terminalEvent.liveNodeProgress ?? null,
|
|
3039
|
-
events
|
|
3358
|
+
events,
|
|
3040
3359
|
latestSeq: eventResult?.latestSeq ?? afterSeq,
|
|
3041
3360
|
wait: null,
|
|
3042
3361
|
coordinatorObserve: {
|
|
@@ -3052,7 +3371,7 @@ async function handleWorkflowRoute(input: {
|
|
|
3052
3371
|
return Response.json({
|
|
3053
3372
|
runId,
|
|
3054
3373
|
status: 'running',
|
|
3055
|
-
events
|
|
3374
|
+
events,
|
|
3056
3375
|
latestSeq: eventResult?.latestSeq ?? afterSeq,
|
|
3057
3376
|
wait: null,
|
|
3058
3377
|
coordinatorObserve: {
|
|
@@ -3250,7 +3569,7 @@ function stableHash(value: string): string {
|
|
|
3250
3569
|
return (hash >>> 0).toString(36);
|
|
3251
3570
|
}
|
|
3252
3571
|
|
|
3253
|
-
const DYNAMIC_PLAY_WORKER_HARNESS_VERSION = '
|
|
3572
|
+
const DYNAMIC_PLAY_WORKER_HARNESS_VERSION = 'h16-coordinator-only-prewarm';
|
|
3254
3573
|
const DYNAMIC_WORKER_BUNDLED_CODE_CACHE_MAX_ENTRIES = 64;
|
|
3255
3574
|
const dynamicWorkerBundledCodeCache = new Map<string, string>();
|
|
3256
3575
|
|
|
@@ -106,7 +106,6 @@ import {
|
|
|
106
106
|
// re-bundle harness internals into per-play. Keep that in mind.
|
|
107
107
|
import {
|
|
108
108
|
harnessPersistCompletedSheetRows,
|
|
109
|
-
harnessPrewarmPostgresSessions,
|
|
110
109
|
harnessReadSheetDatasetRows,
|
|
111
110
|
harnessReadStagedFileChunk,
|
|
112
111
|
harnessStartSheetDataset,
|
|
@@ -568,7 +567,11 @@ async function postRuntimeApi<T>(
|
|
|
568
567
|
// hits the same handler with the same auth — only the transport changes.
|
|
569
568
|
const serializedBody = JSON.stringify(body);
|
|
570
569
|
let lastError: unknown = null;
|
|
571
|
-
for (
|
|
570
|
+
for (
|
|
571
|
+
let attempt = 0;
|
|
572
|
+
attempt <= RUNTIME_API_RETRY_DELAYS_MS.length;
|
|
573
|
+
attempt += 1
|
|
574
|
+
) {
|
|
572
575
|
let res: Response;
|
|
573
576
|
try {
|
|
574
577
|
res = await fetchRuntimeApi(baseUrl, '/api/v2/plays/internal/runtime', {
|
|
@@ -618,7 +621,13 @@ function isRetryableRuntimeApiError(error: unknown): boolean {
|
|
|
618
621
|
}
|
|
619
622
|
|
|
620
623
|
function isRetryableRuntimeApiResponse(status: number, body: string): boolean {
|
|
621
|
-
if (
|
|
624
|
+
if (
|
|
625
|
+
status === 408 ||
|
|
626
|
+
status === 429 ||
|
|
627
|
+
status === 502 ||
|
|
628
|
+
status === 503 ||
|
|
629
|
+
status === 504
|
|
630
|
+
) {
|
|
622
631
|
return true;
|
|
623
632
|
}
|
|
624
633
|
return (
|
|
@@ -2465,7 +2474,10 @@ function readHarnessStagedFileChunks(input: {
|
|
|
2465
2474
|
}
|
|
2466
2475
|
|
|
2467
2476
|
const requiredBytes = expectedBytes ?? objectSize;
|
|
2468
|
-
if (
|
|
2477
|
+
if (
|
|
2478
|
+
typeof requiredBytes === 'number' &&
|
|
2479
|
+
observedBytes !== requiredBytes
|
|
2480
|
+
) {
|
|
2469
2481
|
recordRunnerPerfTrace({
|
|
2470
2482
|
req: input.req,
|
|
2471
2483
|
phase: 'csv.read_mismatch',
|
|
@@ -2761,6 +2773,28 @@ async function prepareMapRows(input: {
|
|
|
2761
2773
|
userEmail: input.req.userEmail,
|
|
2762
2774
|
preloadedDbSessions: input.req.preloadedDbSessions ?? null,
|
|
2763
2775
|
});
|
|
2776
|
+
for (const timing of result.timings ?? []) {
|
|
2777
|
+
const phase =
|
|
2778
|
+
typeof timing.phase === 'string' && timing.phase.trim()
|
|
2779
|
+
? timing.phase.trim()
|
|
2780
|
+
: 'unknown';
|
|
2781
|
+
const ms =
|
|
2782
|
+
typeof timing.ms === 'number' && Number.isFinite(timing.ms)
|
|
2783
|
+
? timing.ms
|
|
2784
|
+
: 0;
|
|
2785
|
+
const { phase: _phase, ms: _ms, ...extra } = timing;
|
|
2786
|
+
void _phase;
|
|
2787
|
+
void _ms;
|
|
2788
|
+
recordRunnerPerfTrace({
|
|
2789
|
+
req: input.req,
|
|
2790
|
+
phase: `sheet_start.${phase}`,
|
|
2791
|
+
ms,
|
|
2792
|
+
extra: {
|
|
2793
|
+
tableNamespace: input.tableNamespace,
|
|
2794
|
+
...extra,
|
|
2795
|
+
},
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2764
2798
|
return {
|
|
2765
2799
|
inserted: result.inserted,
|
|
2766
2800
|
skipped: result.skipped,
|
|
@@ -3717,7 +3751,8 @@ function createMinimalWorkerCtx(
|
|
|
3717
3751
|
if (matchByPath) {
|
|
3718
3752
|
file = {
|
|
3719
3753
|
logicalPath: matchByPath.playPath,
|
|
3720
|
-
fileName:
|
|
3754
|
+
fileName:
|
|
3755
|
+
matchByPath.playPath.split('/').pop() ?? matchByPath.playPath,
|
|
3721
3756
|
storageKey: matchByPath.storageKey,
|
|
3722
3757
|
contentType: matchByPath.contentType,
|
|
3723
3758
|
bytes: matchByPath.bytes,
|
|
@@ -4245,19 +4280,6 @@ async function executeRunRequest(
|
|
|
4245
4280
|
});
|
|
4246
4281
|
const abortController = options?.abortController ?? new AbortController();
|
|
4247
4282
|
const abortSignal = abortController.signal;
|
|
4248
|
-
const postgresPrewarmStartedAt = nowMs();
|
|
4249
|
-
await harnessPrewarmPostgresSessions({
|
|
4250
|
-
executorToken: req.executorToken,
|
|
4251
|
-
sessions: req.preloadedDbSessions ?? [],
|
|
4252
|
-
});
|
|
4253
|
-
recordRunnerPerfTrace({
|
|
4254
|
-
req,
|
|
4255
|
-
phase: 'runner.prewarm_postgres',
|
|
4256
|
-
ms: nowMs() - postgresPrewarmStartedAt,
|
|
4257
|
-
extra: {
|
|
4258
|
-
sessions: req.preloadedDbSessions?.length ?? 0,
|
|
4259
|
-
},
|
|
4260
|
-
});
|
|
4261
4283
|
let runLogBuffer: string[] = [];
|
|
4262
4284
|
let pendingRunLogLines: string[] = [];
|
|
4263
4285
|
let stepProgressByNodeId: LiveNodeProgressMap = {};
|
|
@@ -4427,6 +4449,11 @@ async function executeRunRequest(
|
|
|
4427
4449
|
if (!options?.persistResultDatasets) return;
|
|
4428
4450
|
await ledgerFlushInFlight.catch(() => undefined);
|
|
4429
4451
|
const now = nowMs();
|
|
4452
|
+
pendingRunLogLines = runLogBuffer;
|
|
4453
|
+
dirtyProgressNodeIds = new Set([
|
|
4454
|
+
...dirtyProgressNodeIds,
|
|
4455
|
+
...Object.keys(stepProgressByNodeId),
|
|
4456
|
+
]);
|
|
4430
4457
|
pendingLedgerEvents = [...pendingLedgerEvents, terminalEvent];
|
|
4431
4458
|
const events = drainPendingLedgerEvents(now);
|
|
4432
4459
|
if (events.length === 0) return;
|
|
@@ -4510,12 +4537,12 @@ async function executeRunRequest(
|
|
|
4510
4537
|
ms: nowMs() - serializeStartedAt,
|
|
4511
4538
|
});
|
|
4512
4539
|
if (options?.persistResultDatasets) {
|
|
4513
|
-
const
|
|
4540
|
+
const ledgerFlushWaitStartedAt = nowMs();
|
|
4514
4541
|
await ledgerFlushInFlight.catch(() => undefined);
|
|
4515
4542
|
recordRunnerPerfTrace({
|
|
4516
4543
|
req,
|
|
4517
4544
|
phase: 'runner.run_ledger_flush_wait',
|
|
4518
|
-
ms: nowMs() -
|
|
4545
|
+
ms: nowMs() - ledgerFlushWaitStartedAt,
|
|
4519
4546
|
});
|
|
4520
4547
|
const resultDatasetStartedAt = nowMs();
|
|
4521
4548
|
await persistResultDatasets(req, result, serializedResult);
|
|
@@ -4526,31 +4553,58 @@ async function executeRunRequest(
|
|
|
4526
4553
|
});
|
|
4527
4554
|
const terminalResult = trimResultForStatus(serializedResult);
|
|
4528
4555
|
const terminalOccurredAt = nowMs();
|
|
4556
|
+
const terminalLedgerPromise = (async () => {
|
|
4557
|
+
const terminalUpdateStartedAt = nowMs();
|
|
4558
|
+
await flushTerminalLedgerEvents({
|
|
4559
|
+
type: 'run.completed',
|
|
4560
|
+
runId: req.runId,
|
|
4561
|
+
source: 'worker',
|
|
4562
|
+
occurredAt: terminalOccurredAt,
|
|
4563
|
+
result: terminalResult,
|
|
4564
|
+
});
|
|
4565
|
+
recordRunnerPerfTrace({
|
|
4566
|
+
req,
|
|
4567
|
+
phase: 'runner.terminal_ledger_append',
|
|
4568
|
+
ms: nowMs() - terminalUpdateStartedAt,
|
|
4569
|
+
});
|
|
4570
|
+
})().catch((error) => {
|
|
4571
|
+
console.error(
|
|
4572
|
+
`[play-harness] non-fatal terminal ledger append failed runId=${req.runId}: ${
|
|
4573
|
+
error instanceof Error ? error.message : String(error)
|
|
4574
|
+
}`,
|
|
4575
|
+
);
|
|
4576
|
+
});
|
|
4577
|
+
|
|
4578
|
+
await terminalLedgerPromise;
|
|
4579
|
+
|
|
4529
4580
|
const billingStartedAt = nowMs();
|
|
4530
|
-
|
|
4581
|
+
const billingPromise = finalizeWorkerComputeBilling({
|
|
4531
4582
|
req,
|
|
4532
4583
|
success: true,
|
|
4533
4584
|
actionEstimate: 4,
|
|
4585
|
+
}).then(() => {
|
|
4586
|
+
recordRunnerPerfTrace({
|
|
4587
|
+
req,
|
|
4588
|
+
phase: 'runner.compute_billing_finalize',
|
|
4589
|
+
ms: nowMs() - billingStartedAt,
|
|
4590
|
+
});
|
|
4534
4591
|
});
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
phase: 'runner.terminal_ledger_append',
|
|
4552
|
-
ms: nowMs() - terminalUpdateStartedAt,
|
|
4553
|
-
});
|
|
4592
|
+
if (extractMaxCreditsPerRun(req.contractSnapshot) !== null) {
|
|
4593
|
+
await billingPromise;
|
|
4594
|
+
} else {
|
|
4595
|
+
const nonBlockingBillingPromise = billingPromise.catch((error) => {
|
|
4596
|
+
console.error(
|
|
4597
|
+
`[play-harness] non-fatal compute billing finalize failed runId=${req.runId}: ${
|
|
4598
|
+
error instanceof Error ? error.message : String(error)
|
|
4599
|
+
}`,
|
|
4600
|
+
);
|
|
4601
|
+
});
|
|
4602
|
+
if (options?.waitUntil) {
|
|
4603
|
+
options.waitUntil(nonBlockingBillingPromise);
|
|
4604
|
+
} else {
|
|
4605
|
+
await nonBlockingBillingPromise;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4554
4608
|
}
|
|
4555
4609
|
const parentSignalStartedAt = nowMs();
|
|
4556
4610
|
await signalParentPlayTerminal({
|
|
@@ -4713,7 +4767,9 @@ function runRequestFromWorkflowParams(
|
|
|
4713
4767
|
inputFile && inputStorageKey
|
|
4714
4768
|
? {
|
|
4715
4769
|
[fileName]: {
|
|
4716
|
-
logicalPath: String(
|
|
4770
|
+
logicalPath: String(
|
|
4771
|
+
inputFile.logicalPath ?? inputFile.path ?? fileName,
|
|
4772
|
+
),
|
|
4717
4773
|
fileName,
|
|
4718
4774
|
storageKey: inputStorageKey,
|
|
4719
4775
|
contentType:
|
|
@@ -5255,7 +5311,10 @@ function inferOutputRows(result: unknown): number {
|
|
|
5255
5311
|
}
|
|
5256
5312
|
if (typeof value !== 'object') return;
|
|
5257
5313
|
const record = value as Record<string, unknown>;
|
|
5258
|
-
if (
|
|
5314
|
+
if (
|
|
5315
|
+
typeof record.tableNamespace === 'string' &&
|
|
5316
|
+
typeof record.count === 'number'
|
|
5317
|
+
) {
|
|
5259
5318
|
datasets.push(record.count);
|
|
5260
5319
|
}
|
|
5261
5320
|
for (const [key, child] of Object.entries(record)) {
|
|
@@ -8,13 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* What it does:
|
|
10
10
|
* - Exposes thin functions that look like the in-bundle implementations
|
|
11
|
-
* they're replacing (
|
|
11
|
+
* they're replacing (runtime-api call, staged dataset IO, …).
|
|
12
12
|
* - Each function calls `env.HARNESS.<method>(...)` — the typed RPC
|
|
13
13
|
* stub provided by the Cloudflare service binding.
|
|
14
14
|
*
|
|
15
15
|
* What it does NOT do:
|
|
16
|
-
* - Does NOT import zod (that's the whole point — zod stays in the
|
|
17
|
-
* harness Worker).
|
|
18
16
|
* - Does NOT import the harness Worker's `PlayHarness` class (that
|
|
19
17
|
* would re-bundle the whole harness; only TYPES are imported).
|
|
20
18
|
* - Does NOT cache RPC results across plays — caching belongs to
|
|
@@ -36,12 +34,10 @@ import type {
|
|
|
36
34
|
PreloadedRuntimeDbSessionInput,
|
|
37
35
|
RuntimeApiCallInput,
|
|
38
36
|
RuntimeApiCallResult,
|
|
39
|
-
RuntimePayloadSchemaId,
|
|
40
37
|
SheetDatasetRowsInput,
|
|
41
38
|
SheetDatasetRowsResult,
|
|
42
39
|
StagedFileChunkInput,
|
|
43
40
|
StagedFileChunkResult,
|
|
44
|
-
ValidatePayloadResult,
|
|
45
41
|
} from '../../../apps/play-harness-worker/src/rpc-types';
|
|
46
42
|
|
|
47
43
|
/**
|
|
@@ -173,6 +169,7 @@ export async function harnessStartSheetDataset(input: {
|
|
|
173
169
|
pendingRows: Array<Record<string, unknown>>;
|
|
174
170
|
completedRows: Array<Record<string, unknown>>;
|
|
175
171
|
tableNamespace: string;
|
|
172
|
+
timings?: Array<Record<string, unknown>>;
|
|
176
173
|
}> {
|
|
177
174
|
return requireBinding().startSheetDataset(input);
|
|
178
175
|
}
|
|
@@ -195,18 +192,3 @@ export async function harnessPersistCompletedSheetRows(input: {
|
|
|
195
192
|
}): Promise<{ ok: true; rowsWritten: number; tableNamespace: string }> {
|
|
196
193
|
return requireBinding().persistCompletedMapRows(input);
|
|
197
194
|
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Validate a payload against a named schema. The schema definitions
|
|
201
|
-
* (and zod itself) live in the harness Worker — see
|
|
202
|
-
* `apps/play-harness-worker/src/leaves/validate.ts → KNOWN_SCHEMAS`.
|
|
203
|
-
*
|
|
204
|
-
* Use a schema id of the form `tool:<provider>:<operation>` for tool
|
|
205
|
-
* inputs, or `runtime-api:<action>` for runtime-api request bodies.
|
|
206
|
-
*/
|
|
207
|
-
export async function harnessValidatePayload(
|
|
208
|
-
schemaId: RuntimePayloadSchemaId,
|
|
209
|
-
payload: unknown,
|
|
210
|
-
): Promise<ValidatePayloadResult> {
|
|
211
|
-
return requireBinding().validatePayload({ schemaId, payload });
|
|
212
|
-
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.1.
|
|
1
|
+
export const SDK_VERSION = "0.1.27";
|
|
2
2
|
export const SDK_API_CONTRACT = "2026-05-runs-v2";
|