deepline 0.1.109 → 0.1.111

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 (34) hide show
  1. package/dist/cli/index.js +2634 -1532
  2. package/dist/cli/index.mjs +2547 -1451
  3. package/dist/index.d.mts +21 -14
  4. package/dist/index.d.ts +21 -14
  5. package/dist/index.js +97 -23
  6. package/dist/index.mjs +97 -23
  7. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +192 -121
  8. package/dist/repo/apps/play-runner-workers/src/entry.ts +254 -65
  9. package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +18 -27
  10. package/dist/repo/apps/play-runner-workers/src/workflow-instance-create.ts +44 -0
  11. package/dist/repo/apps/play-runner-workers/src/workflow-retry.ts +7 -11
  12. package/dist/repo/sdk/src/client.ts +35 -12
  13. package/dist/repo/sdk/src/errors.ts +2 -2
  14. package/dist/repo/sdk/src/http.ts +87 -7
  15. package/dist/repo/sdk/src/play.ts +1 -1
  16. package/dist/repo/sdk/src/plays/bundle-play-file.ts +5 -1
  17. package/dist/repo/sdk/src/release.ts +13 -10
  18. package/dist/repo/sdk/src/tool-output.ts +2 -2
  19. package/dist/repo/sdk/src/types.ts +9 -6
  20. package/dist/repo/shared_libs/play-runtime/fullenrich-batching.ts +229 -0
  21. package/dist/repo/shared_libs/play-runtime/governor/policy.ts +1 -1
  22. package/dist/repo/shared_libs/play-runtime/play-runtime-batching-registry.ts +20 -0
  23. package/dist/repo/shared_libs/play-runtime/run-failure.ts +20 -12
  24. package/dist/repo/shared_libs/play-runtime/run-ledger.ts +147 -70
  25. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +6 -2
  26. package/dist/repo/shared_libs/play-runtime/secret-redaction.ts +15 -0
  27. package/dist/repo/shared_libs/play-runtime/work-receipts.ts +1 -0
  28. package/dist/repo/shared_libs/plays/bundling/index.ts +193 -21
  29. package/dist/repo/shared_libs/plays/static-pipeline.ts +1 -3
  30. package/dist/repo/shared_libs/security/outbound-url-policy.ts +238 -0
  31. package/dist/repo/shared_libs/security/safe-fetch.ts +118 -0
  32. package/dist/viewer/viewer.css +617 -0
  33. package/dist/viewer/viewer.js +1496 -0
  34. package/package.json +5 -1
package/dist/index.mjs CHANGED
@@ -196,10 +196,10 @@ var SDK_RELEASE = {
196
196
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
197
197
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
198
198
  // the SDK enrich generator's one-second stale policy.
199
- version: "0.1.109",
199
+ version: "0.1.111",
200
200
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
201
201
  supportPolicy: {
202
- latest: "0.1.109",
202
+ latest: "0.1.111",
203
203
  minimumSupported: "0.1.53",
204
204
  deprecatedBelow: "0.1.53",
205
205
  commandMinimumSupported: [
@@ -226,6 +226,7 @@ var SYNTHETIC_RUN_HEADER = "x-deepline-synthetic-run";
226
226
 
227
227
  // src/http.ts
228
228
  var MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
229
+ var COWORK_NETWORK_HINT = "Claude Cowork appears to be running Deepline in a network-restricted sandbox. In Claude Desktop, open Settings > Capabilities, turn on Allow network egress, and set Domain allowlist to All domains for the Cowork session.";
229
230
  var HttpClient = class {
230
231
  constructor(config) {
231
232
  this.config = config;
@@ -261,6 +262,7 @@ var HttpClient = class {
261
262
  "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
262
263
  "X-Deepline-Client-Family": "sdk",
263
264
  "X-Deepline-CLI-Family": "sdk",
265
+ "X-Deepline-Agent-Runtime": detectAgentRuntime(),
264
266
  "X-Deepline-CLI-Version": SDK_VERSION,
265
267
  "X-Deepline-SDK-Version": SDK_VERSION,
266
268
  "X-Deepline-API-Contract": SDK_API_CONTRACT,
@@ -363,12 +365,13 @@ var HttpClient = class {
363
365
  parsed = body;
364
366
  }
365
367
  if (!response.ok) {
368
+ const retryableApiError = options?.retryApiErrors === true && isRetryableApiErrorResponse(response.status);
366
369
  const htmlError = detectHtmlErrorBody(
367
370
  body,
368
371
  response.headers.get("content-type")
369
372
  );
370
373
  if (htmlError) {
371
- throw new DeeplineError(
374
+ lastError = new DeeplineError(
372
375
  htmlError.message(response.status),
373
376
  response.status,
374
377
  "API_ERROR",
@@ -378,12 +381,23 @@ var HttpClient = class {
378
381
  ...htmlError.workerThrewException ? { workerThrewException: true } : {}
379
382
  }
380
383
  );
384
+ if (retryableApiError && attempt < this.config.maxRetries) {
385
+ retryAfterDelayMs = parseOptionalRetryAfter(response);
386
+ break;
387
+ }
388
+ throw lastError;
381
389
  }
382
390
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
383
391
  const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
384
- throw new DeeplineError(msg, response.status, "API_ERROR", {
392
+ const apiErrorCode = errorValue && typeof errorValue === "object" && typeof errorValue.code === "string" ? errorValue.code : "API_ERROR";
393
+ lastError = new DeeplineError(msg, response.status, apiErrorCode, {
385
394
  response: parsed
386
395
  });
396
+ if (retryableApiError && attempt < this.config.maxRetries) {
397
+ retryAfterDelayMs = parseOptionalRetryAfter(response);
398
+ break;
399
+ }
400
+ throw lastError;
387
401
  }
388
402
  return parsed;
389
403
  } catch (error) {
@@ -404,7 +418,7 @@ var HttpClient = class {
404
418
  throw lastError;
405
419
  }
406
420
  const errorMessage = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
407
- throw new DeeplineError(errorMessage);
421
+ throw new DeeplineError(withCoworkNetworkHint(errorMessage));
408
422
  }
409
423
  /**
410
424
  * Send a GET request.
@@ -476,7 +490,9 @@ var HttpClient = class {
476
490
  }
477
491
  }
478
492
  throw new DeeplineError(
479
- lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
493
+ withCoworkNetworkHint(
494
+ lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
495
+ )
480
496
  );
481
497
  }
482
498
  /**
@@ -486,11 +502,12 @@ var HttpClient = class {
486
502
  * @param path - API path
487
503
  * @param body - Request body (will be JSON-serialized)
488
504
  */
489
- async post(path, body, headers) {
505
+ async post(path, body, headers, options) {
490
506
  return this.request(path, {
491
507
  method: "POST",
492
508
  body,
493
- headers
509
+ headers,
510
+ ...options
494
511
  });
495
512
  }
496
513
  async postFormData(path, formData, headers) {
@@ -531,6 +548,9 @@ function parseResponseBody(body) {
531
548
  return body;
532
549
  }
533
550
  }
551
+ function isRetryableApiErrorResponse(status) {
552
+ return status === 408 || status === 425 || status >= 500 && status < 600;
553
+ }
534
554
  function detectHtmlErrorBody(body, contentType) {
535
555
  const trimmed = body.trim();
536
556
  const lower = trimmed.toLowerCase();
@@ -570,6 +590,9 @@ function apiErrorMessage(parsed, status) {
570
590
  return `HTTP ${status}`;
571
591
  }
572
592
  function parseRetryAfter(response) {
593
+ return parseOptionalRetryAfter(response) ?? 5e3;
594
+ }
595
+ function parseOptionalRetryAfter(response) {
573
596
  const header = response.headers.get("retry-after");
574
597
  if (header) {
575
598
  const seconds = Number(header);
@@ -577,7 +600,7 @@ function parseRetryAfter(response) {
577
600
  return seconds * 1e3;
578
601
  }
579
602
  }
580
- return 5e3;
603
+ return null;
581
604
  }
582
605
  function buildCandidateUrls(url) {
583
606
  try {
@@ -634,6 +657,39 @@ function decodeSseFrame(frame) {
634
657
  function sleep(ms) {
635
658
  return new Promise((resolve2) => setTimeout(resolve2, ms));
636
659
  }
660
+ function isTruthyEnv(name) {
661
+ const normalized = process.env[name]?.trim().toLowerCase();
662
+ return ["1", "true", "yes", "on"].includes(normalized ?? "");
663
+ }
664
+ function isCoworkLikeSandbox() {
665
+ const pluginMode = isTruthyEnv("DEEPLINE_PLUGIN_MODE");
666
+ const claudeRemote = isTruthyEnv("CLAUDE_CODE_REMOTE");
667
+ const projectDir = Boolean(process.env.CLAUDE_PROJECT_DIR?.trim());
668
+ const pluginRoot = Boolean(process.env.DEEPLINE_PLUGIN_ROOT?.trim());
669
+ const home = process.env.HOME?.trim() ?? "";
670
+ const sessionHome = home.startsWith("/sessions/");
671
+ return (pluginMode || pluginRoot) && (claudeRemote || projectDir || sessionHome);
672
+ }
673
+ function detectAgentRuntime() {
674
+ if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
675
+ if (isCoworkLikeSandbox()) return "claude_cowork";
676
+ if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
677
+ if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
678
+ if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
679
+ return "cursor";
680
+ }
681
+ if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
682
+ return "windsurf";
683
+ }
684
+ return "unknown";
685
+ }
686
+ function withCoworkNetworkHint(message) {
687
+ if (!isCoworkLikeSandbox() || message.includes(COWORK_NETWORK_HINT)) {
688
+ return message;
689
+ }
690
+ return `${message}
691
+ ${COWORK_NETWORK_HINT}`;
692
+ }
637
693
 
638
694
  // src/stream-reconnect.ts
639
695
  var STREAM_RECONNECT_BASE_DELAY_MS = 500;
@@ -1910,12 +1966,12 @@ var DeeplineClient = class {
1910
1966
  * Returns everything from {@link ToolDefinition} plus pricing info, sample
1911
1967
  * inputs/outputs, failure modes, and cost estimates.
1912
1968
  *
1913
- * @param toolId - Tool identifier (e.g. `"apollo_people_search"`)
1969
+ * @param toolId - Tool identifier (e.g. `"dropleads_search_people"`)
1914
1970
  * @returns Full tool metadata
1915
1971
  *
1916
1972
  * @example
1917
1973
  * ```typescript
1918
- * const meta = await client.getTool('apollo_people_search');
1974
+ * const meta = await client.getTool('dropleads_search_people');
1919
1975
  * console.log(`Cost: ${meta.estimatedCreditsRange} credits`);
1920
1976
  * console.log(`Input schema:`, meta.inputSchema);
1921
1977
  * ```
@@ -1947,7 +2003,8 @@ var DeeplineClient = class {
1947
2003
  return this.http.post(
1948
2004
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
1949
2005
  { payload: input },
1950
- headers
2006
+ headers,
2007
+ { forbiddenAsApiError: true }
1951
2008
  );
1952
2009
  }
1953
2010
  /**
@@ -2029,7 +2086,7 @@ var DeeplineClient = class {
2029
2086
  ...request.force ? { force: true } : {},
2030
2087
  ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
2031
2088
  // Profile selection is the API's job, not the CLI's. The server
2032
- // hardcodes workers_edge as the default; tests that want a
2089
+ // defaults to workers_edge; tests and runtime probes that want a
2033
2090
  // different profile pass `request.profile` explicitly.
2034
2091
  ...request.profile ? { profile: request.profile } : {}
2035
2092
  }
@@ -2094,10 +2151,15 @@ var DeeplineClient = class {
2094
2151
  sourceFiles: input.sourceFiles,
2095
2152
  artifact: input.artifact
2096
2153
  });
2097
- return this.http.post("/api/v2/plays/artifacts", {
2098
- ...input,
2099
- compilerManifest
2100
- });
2154
+ return this.http.post(
2155
+ "/api/v2/plays/artifacts",
2156
+ {
2157
+ ...input,
2158
+ compilerManifest
2159
+ },
2160
+ void 0,
2161
+ { forbiddenAsApiError: true, retryApiErrors: true }
2162
+ );
2101
2163
  }
2102
2164
  /**
2103
2165
  * Register multiple bundled play artifacts in one request.
@@ -2107,7 +2169,12 @@ var DeeplineClient = class {
2107
2169
  */
2108
2170
  async registerPlayArtifacts(artifacts) {
2109
2171
  if (artifacts.length === 0) {
2110
- return this.http.post("/api/v2/plays/artifacts", { artifacts });
2172
+ return this.http.post(
2173
+ "/api/v2/plays/artifacts",
2174
+ { artifacts },
2175
+ void 0,
2176
+ { forbiddenAsApiError: true, retryApiErrors: true }
2177
+ );
2111
2178
  }
2112
2179
  const compiledArtifacts = await mapWithConcurrency(
2113
2180
  artifacts,
@@ -2125,9 +2192,14 @@ var DeeplineClient = class {
2125
2192
  const responses = [];
2126
2193
  for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
2127
2194
  responses.push(
2128
- await this.http.post("/api/v2/plays/artifacts", {
2129
- artifacts: chunk
2130
- })
2195
+ await this.http.post(
2196
+ "/api/v2/plays/artifacts",
2197
+ {
2198
+ artifacts: chunk
2199
+ },
2200
+ void 0,
2201
+ { forbiddenAsApiError: true, retryApiErrors: true }
2202
+ )
2131
2203
  );
2132
2204
  }
2133
2205
  return {
@@ -2932,7 +3004,9 @@ var DeeplineClient = class {
2932
3004
  const encodedName = encodeURIComponent(name);
2933
3005
  return this.http.post(
2934
3006
  `/api/v2/plays/${encodedName}/live`,
2935
- request
3007
+ request,
3008
+ void 0,
3009
+ { forbiddenAsApiError: true }
2936
3010
  );
2937
3011
  }
2938
3012
  /**
@@ -4174,7 +4248,7 @@ var DeeplineContext = class {
4174
4248
  * @example
4175
4249
  * ```typescript
4176
4250
  * const tools = await deepline.tools.list();
4177
- * const meta = await deepline.tools.get('apollo_people_search');
4251
+ * const meta = await deepline.tools.get('dropleads_search_people');
4178
4252
  * const companyLookup = await deepline.tools.execute('test_company_search', { domain: 'stripe.com' });
4179
4253
  * const company = companyLookup.toolResponse.raw;
4180
4254
  * ```
@@ -44,6 +44,7 @@ import type {
44
44
  PlayRuntimeManifest,
45
45
  PlayRuntimeManifestMap,
46
46
  } from '../../../shared_libs/plays/compiler-manifest';
47
+ import { normalizePlayRunFailure } from '../../../shared_libs/play-runtime/run-failure';
47
48
  import type { PlayRunLedgerEvent } from '../../../shared_libs/play-runtime/run-ledger';
48
49
  import {
49
50
  COORDINATOR_INTERNAL_TOKEN_HEADER,
@@ -61,6 +62,7 @@ import {
61
62
  workflowRetryParamsStorageKey,
62
63
  type WorkflowRetryParamsRef,
63
64
  } from './workflow-retry-state';
65
+ import { createOrAttachWorkflowInstance } from './workflow-instance-create';
64
66
  import { sanitizeLiveLogLines } from './runtime/live-progress';
65
67
 
66
68
  export { DynamicWorkflowBinding };
@@ -2896,136 +2898,181 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
2896
2898
  },
2897
2899
  });
2898
2900
 
2899
- return dispatchWorkflow(
2900
- { env: this.env, ctx: this.ctx },
2901
- dispatchedEvent as Parameters<typeof dispatchWorkflow>[1],
2902
- step as Parameters<typeof dispatchWorkflow>[2],
2903
- async ({ metadata, env }) => {
2904
- const graphHash = readMetadataString(metadata, 'graphHash');
2905
- const artifactStorageKey = readMetadataString(
2906
- metadata,
2907
- 'artifactStorageKey',
2908
- );
2909
- const runIdForTrace =
2910
- typeof (metadata as Record<string, unknown>).runId === 'string'
2911
- ? ((metadata as Record<string, unknown>).runId as string)
2912
- : graphHash;
2913
- const loaderStartedAt = Date.now();
2914
- trace({
2915
- runId: runIdForTrace,
2916
- phase: 'coordinator.loader_callback_entry',
2917
- ms: 0,
2918
- graphHash,
2919
- extra: { artifactStorageKey },
2920
- });
2921
- const stub = loadDynamicPlayWorkerSync(
2922
- env,
2923
- {
2901
+ const dispatchWorkflowStartedAt = Date.now();
2902
+ try {
2903
+ return await dispatchWorkflow(
2904
+ { env: this.env, ctx: this.ctx },
2905
+ dispatchedEvent as Parameters<typeof dispatchWorkflow>[1],
2906
+ step as Parameters<typeof dispatchWorkflow>[2],
2907
+ async ({ metadata, env }) => {
2908
+ const graphHash = readMetadataString(metadata, 'graphHash');
2909
+ const artifactStorageKey = readMetadataString(
2910
+ metadata,
2911
+ 'artifactStorageKey',
2912
+ );
2913
+ const runIdForTrace =
2914
+ typeof (metadata as Record<string, unknown>).runId === 'string'
2915
+ ? ((metadata as Record<string, unknown>).runId as string)
2916
+ : graphHash;
2917
+ const loaderStartedAt = Date.now();
2918
+ trace({
2924
2919
  runId: runIdForTrace,
2920
+ phase: 'coordinator.loader_callback_entry',
2921
+ ms: 0,
2925
2922
  graphHash,
2926
- artifactStorageKey,
2927
- artifactHash:
2928
- typeof metadata.artifactHash === 'string'
2929
- ? metadata.artifactHash
2930
- : null,
2931
- dynamicWorkerCode:
2932
- typeof metadata.dynamicWorkerCode === 'string'
2933
- ? metadata.dynamicWorkerCode
2934
- : null,
2935
- packagedFiles: normalizePackagedFiles(metadata.packagedFiles),
2936
- },
2937
- trace,
2938
- );
2939
- const entrypoint = stub.getEntrypoint(
2940
- 'TenantWorkflow',
2941
- ) as unknown as WorkflowRunner;
2942
- trace({
2943
- runId: runIdForTrace,
2944
- phase: 'coordinator.loader_compile',
2945
- ms: Date.now() - loaderStartedAt,
2946
- graphHash,
2947
- });
2948
- // Wrap the entrypoint so its run() failure surfaces here rather
2949
- // than disappearing into the framework's silent rpcMethod=run
2950
- // exception path.
2951
- return {
2952
- run: async (innerEvent: unknown, innerStep: unknown) => {
2953
- const innerStartedAt = Date.now();
2954
- trace({
2923
+ extra: { artifactStorageKey },
2924
+ });
2925
+ const stub = loadDynamicPlayWorkerSync(
2926
+ env,
2927
+ {
2955
2928
  runId: runIdForTrace,
2956
- phase: 'coordinator.runner_run_start',
2957
- ms: 0,
2958
2929
  graphHash,
2959
- });
2960
- try {
2961
- const result = await (
2962
- entrypoint as unknown as {
2963
- run(e: unknown, s: unknown): Promise<unknown>;
2964
- }
2965
- ).run(innerEvent, innerStep);
2966
- const output = isRecord(result) ? result : null;
2967
- await writeCoordinatorTerminalState(env, {
2968
- runId: runIdForTrace,
2969
- status: 'completed',
2970
- result: output?.result ?? result,
2971
- totalRows: output?.totalRows ?? output?.outputRows ?? null,
2972
- durationMs: output?.durationMs ?? null,
2973
- playName:
2974
- typeof output?.playName === 'string' ? output.playName : null,
2975
- liveLogs: sanitizeLiveLogLines(output?.liveLogs),
2976
- liveNodeProgress: output?.liveNodeProgress ?? null,
2977
- });
2930
+ artifactStorageKey,
2931
+ artifactHash:
2932
+ typeof metadata.artifactHash === 'string'
2933
+ ? metadata.artifactHash
2934
+ : null,
2935
+ dynamicWorkerCode:
2936
+ typeof metadata.dynamicWorkerCode === 'string'
2937
+ ? metadata.dynamicWorkerCode
2938
+ : null,
2939
+ packagedFiles: normalizePackagedFiles(metadata.packagedFiles),
2940
+ },
2941
+ trace,
2942
+ );
2943
+ const entrypointStartedAt = Date.now();
2944
+ const entrypoint = stub.getEntrypoint(
2945
+ 'TenantWorkflow',
2946
+ ) as unknown as WorkflowRunner;
2947
+ trace({
2948
+ runId: runIdForTrace,
2949
+ phase: 'coordinator.loader_get_entrypoint',
2950
+ ms: Date.now() - entrypointStartedAt,
2951
+ graphHash,
2952
+ });
2953
+ trace({
2954
+ runId: runIdForTrace,
2955
+ phase: 'coordinator.loader_compile',
2956
+ ms: Date.now() - loaderStartedAt,
2957
+ graphHash,
2958
+ });
2959
+ // Wrap the entrypoint so its run() failure surfaces here rather
2960
+ // than disappearing into the framework's silent rpcMethod=run
2961
+ // exception path.
2962
+ return {
2963
+ run: async (innerEvent: unknown, innerStep: unknown) => {
2964
+ const innerStartedAt = Date.now();
2978
2965
  trace({
2979
2966
  runId: runIdForTrace,
2980
- phase: 'coordinator.runner_run',
2981
- ms: Date.now() - innerStartedAt,
2982
- graphHash,
2983
- });
2984
- return result;
2985
- } catch (innerError) {
2986
- console.error('[coordinator] DynamicWorkflow runner.run threw', {
2967
+ phase: 'coordinator.runner_run_start',
2968
+ ms: 0,
2987
2969
  graphHash,
2988
- message:
2989
- innerError instanceof Error
2990
- ? innerError.message
2991
- : String(innerError),
2992
- name: innerError instanceof Error ? innerError.name : null,
2993
- stack:
2994
- innerError instanceof Error &&
2995
- typeof innerError.stack === 'string'
2996
- ? innerError.stack.split('\n').slice(0, 12).join('\n')
2997
- : null,
2998
2970
  });
2999
- await markWorkflowRuntimeFailure({
3000
- env,
3001
- event: innerEvent,
3002
- error: innerError,
3003
- }).catch((markError) => {
2971
+ try {
2972
+ const result = await (
2973
+ entrypoint as unknown as {
2974
+ run(e: unknown, s: unknown): Promise<unknown>;
2975
+ }
2976
+ ).run(innerEvent, innerStep);
2977
+ const output = isRecord(result) ? result : null;
2978
+ const terminalStartedAt = Date.now();
2979
+ try {
2980
+ await writeCoordinatorTerminalState(env, {
2981
+ runId: runIdForTrace,
2982
+ status: 'completed',
2983
+ result: output?.result ?? result,
2984
+ totalRows: output?.totalRows ?? output?.outputRows ?? null,
2985
+ durationMs: output?.durationMs ?? null,
2986
+ playName:
2987
+ typeof output?.playName === 'string'
2988
+ ? output.playName
2989
+ : null,
2990
+ liveLogs: sanitizeLiveLogLines(output?.liveLogs),
2991
+ liveNodeProgress: output?.liveNodeProgress ?? null,
2992
+ });
2993
+ trace({
2994
+ runId: runIdForTrace,
2995
+ phase: 'coordinator.terminal_state_write',
2996
+ ms: Date.now() - terminalStartedAt,
2997
+ graphHash,
2998
+ extra: { status: 'completed' },
2999
+ });
3000
+ } catch (terminalError) {
3001
+ console.warn(
3002
+ '[coordinator] completed terminal cache write failed; preserving completed run',
3003
+ {
3004
+ graphHash,
3005
+ runId: runIdForTrace,
3006
+ message:
3007
+ terminalError instanceof Error
3008
+ ? terminalError.message
3009
+ : String(terminalError),
3010
+ },
3011
+ );
3012
+ }
3013
+ trace({
3014
+ runId: runIdForTrace,
3015
+ phase: 'coordinator.runner_run',
3016
+ ms: Date.now() - innerStartedAt,
3017
+ graphHash,
3018
+ });
3019
+ return result;
3020
+ } catch (innerError) {
3021
+ const failure = normalizePlayRunFailure(innerError);
3004
3022
  console.error(
3005
- '[coordinator] failed to forward DynamicWorkflow runner error',
3023
+ '[coordinator] DynamicWorkflow runner.run threw',
3006
3024
  {
3007
3025
  graphHash,
3008
3026
  message:
3009
- markError instanceof Error
3010
- ? markError.message
3011
- : String(markError),
3027
+ innerError instanceof Error
3028
+ ? innerError.message
3029
+ : String(innerError),
3030
+ name: innerError instanceof Error ? innerError.name : null,
3031
+ stack:
3032
+ innerError instanceof Error &&
3033
+ typeof innerError.stack === 'string'
3034
+ ? innerError.stack.split('\n').slice(0, 12).join('\n')
3035
+ : null,
3012
3036
  },
3013
3037
  );
3014
- });
3015
- await writeCoordinatorTerminalState(env, {
3016
- runId: runIdForTrace,
3017
- status: 'failed',
3018
- error:
3019
- innerError instanceof Error
3020
- ? innerError.message
3021
- : String(innerError),
3022
- }).catch(() => undefined);
3023
- throw innerError;
3024
- }
3025
- },
3026
- };
3027
- },
3028
- );
3038
+ await markWorkflowRuntimeFailure({
3039
+ env,
3040
+ event: innerEvent,
3041
+ error: innerError,
3042
+ }).catch((markError) => {
3043
+ console.error(
3044
+ '[coordinator] failed to forward DynamicWorkflow runner error',
3045
+ {
3046
+ graphHash,
3047
+ message:
3048
+ markError instanceof Error
3049
+ ? markError.message
3050
+ : String(markError),
3051
+ },
3052
+ );
3053
+ });
3054
+ await writeCoordinatorTerminalState(env, {
3055
+ runId: runIdForTrace,
3056
+ status: 'failed',
3057
+ error: failure.message,
3058
+ }).catch(() => undefined);
3059
+ throw innerError;
3060
+ }
3061
+ },
3062
+ };
3063
+ },
3064
+ );
3065
+ } finally {
3066
+ trace({
3067
+ runId: dispatchTrace.runId,
3068
+ phase: 'coordinator.dispatch_workflow_total',
3069
+ ms: Date.now() - dispatchWorkflowStartedAt,
3070
+ graphHash: dispatchTrace.graphHash,
3071
+ extra: {
3072
+ instanceId: dispatchTrace.instanceId,
3073
+ },
3074
+ });
3075
+ }
3029
3076
  }
3030
3077
  }
3031
3078
 
@@ -3410,6 +3457,18 @@ async function handleWorkflowRoute(input: {
3410
3457
  const parseStartedAt = Date.now();
3411
3458
  const params = (await request.json()) as PlayWorkflowParams;
3412
3459
  submittedRunId = params.runId ?? runId;
3460
+ recordSubmitTiming({
3461
+ phase: 'coordinator.submit_received',
3462
+ ms: Date.now() - submitStartedAt,
3463
+ graphHash: params.graphHash ?? null,
3464
+ extra: {
3465
+ hasDynamicWorkerCode: Boolean(params.dynamicWorkerCode),
3466
+ dynamicWorkerBytes:
3467
+ typeof params.dynamicWorkerCode === 'string'
3468
+ ? params.dynamicWorkerCode.length
3469
+ : 0,
3470
+ },
3471
+ });
3413
3472
  recordSubmitTiming({
3414
3473
  phase: 'coordinator.submit_parse_body',
3415
3474
  ms: Date.now() - parseStartedAt,
@@ -3539,17 +3598,28 @@ async function handleWorkflowRoute(input: {
3539
3598
  try {
3540
3599
  const dispatchStartedAt = Date.now();
3541
3600
  const createStartedAt = Date.now();
3542
- instance = await createDynamicWorkflowInstance({
3543
- env,
3544
- id: defaultInstanceId,
3545
- params: workflowParams,
3601
+ recordSubmitTiming({
3602
+ phase: 'coordinator.workflow_create_start',
3603
+ ms: 0,
3604
+ graphHash: params.graphHash ?? null,
3605
+ extra: { instanceId: defaultInstanceId },
3606
+ });
3607
+ const createResult = await createOrAttachWorkflowInstance({
3608
+ create: () =>
3609
+ createDynamicWorkflowInstance({
3610
+ env,
3611
+ id: defaultInstanceId,
3612
+ params: workflowParams,
3613
+ }),
3614
+ getExisting: () => env.PLAY_WORKFLOW.get(defaultInstanceId),
3546
3615
  });
3616
+ instance = createResult.instance;
3547
3617
  const workflowCreatedAt = Date.now();
3548
3618
  recordSubmitTiming({
3549
3619
  phase: 'coordinator.workflow_create',
3550
3620
  ms: workflowCreatedAt - createStartedAt,
3551
3621
  graphHash: params.graphHash ?? null,
3552
- extra: { instanceId: instance.id },
3622
+ extra: { instanceId: instance.id, startMode: createResult.startMode },
3553
3623
  });
3554
3624
  const instanceIdRecord = recordWorkflowInstanceId({
3555
3625
  env,
@@ -3569,6 +3639,7 @@ async function handleWorkflowRoute(input: {
3569
3639
  graphHash: params.graphHash ?? null,
3570
3640
  extra: {
3571
3641
  startMode: 'direct_workflow_create',
3642
+ workflowCreateMode: createResult.startMode,
3572
3643
  instanceIdRecord: 'waitUntil',
3573
3644
  },
3574
3645
  });